spec_distributed 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +2 -0
- data/License.txt +20 -0
- data/Manifest.txt +27 -0
- data/README.txt +132 -0
- data/Rakefile +133 -0
- data/examples/first_spec.rb +9 -0
- data/examples/fourth_spec.rb +14 -0
- data/examples/second_spec.rb +10 -0
- data/examples/third_spec.rb +9 -0
- data/lib/spec/distributed.rb +6 -0
- data/lib/spec/distributed/master_runner.rb +74 -0
- data/lib/spec/distributed/rinda_master_runner.rb +31 -0
- data/lib/spec/distributed/rinda_slave_runner.rb +64 -0
- data/lib/spec/distributed/slave_runner.rb +136 -0
- data/lib/spec/distributed/tuple_args.rb +25 -0
- data/lib/spec/distributed/version.rb +11 -0
- data/scripts/txt2html +67 -0
- data/setup.rb +1585 -0
- data/spec/spec/distributed/rinda_master_runner_spec.rb +26 -0
- data/spec/spec/distributed/rinda_slave_runner_spec.rb +26 -0
- data/spec/spec/distributed/tuple_args_spec.rb +36 -0
- data/spec/spec_helper.rb +9 -0
- data/website/index.html +89 -0
- data/website/index.txt +35 -0
- data/website/javascripts/rounded_corners_lite.inc.js +285 -0
- data/website/stylesheets/screen.css +138 -0
- data/website/template.rhtml +48 -0
- metadata +87 -0
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Aslak Hellesoy and Bob Cotton
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
examples/first_spec.rb
|
7
|
+
examples/fourth_spec.rb
|
8
|
+
examples/second_spec.rb
|
9
|
+
examples/third_spec.rb
|
10
|
+
lib/spec/distributed.rb
|
11
|
+
lib/spec/distributed/master_runner.rb
|
12
|
+
lib/spec/distributed/rinda_master_runner.rb
|
13
|
+
lib/spec/distributed/rinda_slave_runner.rb
|
14
|
+
lib/spec/distributed/slave_runner.rb
|
15
|
+
lib/spec/distributed/tuple_args.rb
|
16
|
+
lib/spec/distributed/version.rb
|
17
|
+
scripts/txt2html
|
18
|
+
setup.rb
|
19
|
+
spec/spec/distributed/rinda_master_runner_spec.rb
|
20
|
+
spec/spec/distributed/rinda_slave_runner_spec.rb
|
21
|
+
spec/spec/distributed/tuple_args_spec.rb
|
22
|
+
spec/spec_helper.rb
|
23
|
+
website/index.html
|
24
|
+
website/index.txt
|
25
|
+
website/javascripts/rounded_corners_lite.inc.js
|
26
|
+
website/stylesheets/screen.css
|
27
|
+
website/template.rhtml
|
data/README.txt
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
Spec::Distributed
|
2
|
+
=================
|
3
|
+
|
4
|
+
Spec::Distributed makes it possible to run specs in a distributed fashion, in parallel
|
5
|
+
on different slaves. It's something you can consider using when you have a *very* slow
|
6
|
+
RSpec suite (for example using Spec::Ui).
|
7
|
+
|
8
|
+
== How it works ==
|
9
|
+
When you use Spec::Distributed you will have one master process, and two or more slave processes.
|
10
|
+
The master distributes behaviours (describe blocks) to slaves via DRb.
|
11
|
+
|
12
|
+
== Example ==
|
13
|
+
Note: If you want to run the examples from an svn checkout, replace 'spec' with
|
14
|
+
'ruby -Ilib ../rspec/bin/spec'
|
15
|
+
|
16
|
+
Start two slave runners:
|
17
|
+
|
18
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::SlaveRunner:druby://localhost:8991
|
19
|
+
|
20
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::SlaveRunner:druby://localhost:8992
|
21
|
+
|
22
|
+
Start master runner:
|
23
|
+
|
24
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::MasterRunner:druby://localhost:8991,druby://localhost:8992
|
25
|
+
|
26
|
+
== Using Subversion ==
|
27
|
+
It is very important that the slaves and the master have identical sources. If the master is run from
|
28
|
+
a Subversion working copy, it will automatically detect the local revision and tell the slaves to
|
29
|
+
update accordingly prior to running the behaviours.
|
30
|
+
|
31
|
+
== Spec::Ui and formatters ==
|
32
|
+
Slaves should be using Spec::Ui::SlaveScreenshotFormatter and the master should be using
|
33
|
+
Spec::Ui::MasterScreenshotFormatter. In order to get a report without dead links to screenshot
|
34
|
+
PNGs and browser HTML snapshots, all formatters must write to the same location.
|
35
|
+
Since slaves and masters typically will run on different machines, you might want to set
|
36
|
+
up a shared location using Samba or NTFS shares.
|
37
|
+
|
38
|
+
== Using Rinda for Autodiscovery ==
|
39
|
+
The slave class Spec::Distributed::RindaSlaveRunner will be used in
|
40
|
+
conjunction with Spec::Distributed::RindaMasterRunner so that masters
|
41
|
+
and slaves may auto-discover each other.
|
42
|
+
|
43
|
+
To use the Rinda Runners start one more slave runner:
|
44
|
+
|
45
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaSlaveRunner
|
46
|
+
|
47
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaSlaveRunner
|
48
|
+
|
49
|
+
Then start the master runner:
|
50
|
+
|
51
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::MasterRunner
|
52
|
+
|
53
|
+
Note the slaves and masters don't need prior knowledge of each other.
|
54
|
+
|
55
|
+
The slave runner will attempt to contact any RingServer on the local
|
56
|
+
network. If none exists it will start one. Subsequent slaves will use
|
57
|
+
this RingServer to publish themselves.
|
58
|
+
|
59
|
+
When the master starts, it will contact the RingServer and query for
|
60
|
+
all available slave servers. Then the master will create a thread for
|
61
|
+
each available slave.
|
62
|
+
|
63
|
+
When the master uses a slave, it removes it from the pool of available
|
64
|
+
slaves. The slave will re-publish itself back into the tuplespace
|
65
|
+
after running the spec.
|
66
|
+
|
67
|
+
== Partitioning the Tuplespace ==
|
68
|
+
With no additional configuration options passed to either the Master
|
69
|
+
or Slave runners the RindaMasterRunner will use all available slaves.
|
70
|
+
|
71
|
+
Suppose you have more than one set of masters and slaves running at
|
72
|
+
the same time. For example, Bob and Joe want to run a pool of slaves,
|
73
|
+
but don't want to share them with each other. One solution would be to
|
74
|
+
run seperate RingServers on different ports, but that defeats the
|
75
|
+
purpose of auto-discovery.
|
76
|
+
|
77
|
+
Both Spec::Distributed::RindaSlaveRunner and
|
78
|
+
Spec::Distributed::RindaMasterRunner take an optional list of
|
79
|
+
"tuplespace selectors", which are a comma seperated list of strings.
|
80
|
+
|
81
|
+
For example, to Joe might start his slaves like this:
|
82
|
+
|
83
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaSlaveRunner:Joe
|
84
|
+
|
85
|
+
Joe would then start his RindaMasterRunner as so:
|
86
|
+
|
87
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaMasterRunner:Joe
|
88
|
+
|
89
|
+
This master runner will only find slaves that have been configured
|
90
|
+
with the same arguments.
|
91
|
+
|
92
|
+
Joe may also have several builds he want to test, so he might setup
|
93
|
+
two pools of slave servers to run:
|
94
|
+
|
95
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaSlaveRunner:Joe,1
|
96
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaSlaveRunner:Joe,2
|
97
|
+
|
98
|
+
Then Joe could create two master runners, one for each build:
|
99
|
+
|
100
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaMasterRunner:Joe,1
|
101
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaMasterRunner:Joe,2
|
102
|
+
|
103
|
+
Again, the master runners would only find slaves that have been
|
104
|
+
configured with the same parameters.
|
105
|
+
|
106
|
+
== Wildcarding the Tuplespace ==
|
107
|
+
|
108
|
+
To continue with the example, lets suppose that Bob knows that Joe is
|
109
|
+
out to lunch, and wants to use some of his slave runners while he
|
110
|
+
gone. Bob has his own slave runners configured similarly to Joe's:
|
111
|
+
|
112
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaSlaveRunner:Bob,1
|
113
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaSlaveRunner:Bob,2
|
114
|
+
|
115
|
+
So Bob wants to use all of Joe's (and everyone's) build #2 slave
|
116
|
+
servers. So he starts his master runner and passes in a wild-card in
|
117
|
+
the first position:
|
118
|
+
|
119
|
+
spec examples/*_spec.rb --require spec/distributed --runner Spec::Distributed::RindaMasterRunner:*,2
|
120
|
+
|
121
|
+
This will select all the slave runners that were configured with two
|
122
|
+
arguments, and the value of the second argument is 2.
|
123
|
+
|
124
|
+
Which is to say, slaves will only be selected if the number of
|
125
|
+
"tuplespace selectors" matches, and all of the values match or are a
|
126
|
+
wildcard (*). Zero selectors will only match slaves started with zero
|
127
|
+
selectors, a single wild-card will only match slaves started with one
|
128
|
+
selector.
|
129
|
+
|
130
|
+
This can be useful for partitioning seperate builds, platforms, dev
|
131
|
+
groups etc.
|
132
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
require 'spec/rake/spectask'
|
12
|
+
|
13
|
+
include FileUtils
|
14
|
+
require File.join(File.dirname(__FILE__), 'lib', 'spec', 'distributed', 'version')
|
15
|
+
|
16
|
+
AUTHOR = 'Spec::Distributed'
|
17
|
+
EMAIL = 'aslak.hellesoy@gmail.com', 'bob.cotton@rallydev.com'
|
18
|
+
DESCRIPTION = "Run RSpec distributed with DRb or Rinda"
|
19
|
+
GEM_NAME = 'spec_distributed' # what ppl will type to install your gem
|
20
|
+
|
21
|
+
@config_file = "~/.rubyforge/user-config.yml"
|
22
|
+
@config = nil
|
23
|
+
def rubyforge_username
|
24
|
+
unless @config
|
25
|
+
begin
|
26
|
+
@config = YAML.load(File.read(File.expand_path(@config_file)))
|
27
|
+
rescue
|
28
|
+
puts <<-EOS
|
29
|
+
ERROR: No rubyforge config file found: #{@config_file}"
|
30
|
+
Run 'rubyforge setup' to prepare your env for access to Rubyforge
|
31
|
+
- See http://newgem.rubyforge.org/rubyforge.html for more details
|
32
|
+
EOS
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
end
|
36
|
+
@rubyforge_username ||= @config["username"]
|
37
|
+
end
|
38
|
+
|
39
|
+
RUBYFORGE_PROJECT = 'rspec-ext' # The unix name for your project
|
40
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
41
|
+
|
42
|
+
|
43
|
+
#REV = YAML.load(`svn info`)['Revision'] rescue nil
|
44
|
+
REV=nil
|
45
|
+
VERS = Spec::Distributed::VERSION::STRING + (REV ? ".#{REV}" : "")
|
46
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
|
47
|
+
RDOC_OPTS = ['--quiet', '--title', 'spec_distributed documentation',
|
48
|
+
"--opname", "index.html",
|
49
|
+
"--line-numbers",
|
50
|
+
"--main", "README",
|
51
|
+
"--inline-source"]
|
52
|
+
|
53
|
+
class Hoe
|
54
|
+
def extra_deps
|
55
|
+
@extra_deps.reject { |x| Array(x).first == 'hoe' }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Generate all the Rake tasks
|
60
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
61
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
62
|
+
p.author = AUTHOR
|
63
|
+
p.description = DESCRIPTION
|
64
|
+
p.email = EMAIL
|
65
|
+
p.summary = DESCRIPTION
|
66
|
+
p.url = HOMEPATH
|
67
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
68
|
+
p.test_globs = ["test/**/test_*.rb"]
|
69
|
+
p.clean_globs |= CLEAN #An array of file patterns to delete on clean.
|
70
|
+
|
71
|
+
# == Optional
|
72
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
73
|
+
p.extra_deps = [['rspec', '>= 1.0.8']] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
|
74
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
75
|
+
end
|
76
|
+
|
77
|
+
CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\n\n")
|
78
|
+
PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
79
|
+
hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
|
80
|
+
|
81
|
+
desc 'Generate website files'
|
82
|
+
task :website_generate do
|
83
|
+
Dir['website/**/*.txt'].each do |txt|
|
84
|
+
sh %{ ruby scripts/txt2html #{txt} > #{txt.gsub(/txt$/,'html')} }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
desc 'Upload website files to rubyforge'
|
89
|
+
task :website_upload do
|
90
|
+
host = "#{rubyforge_username}@rubyforge.org"
|
91
|
+
remote_dir = "/var/www/gforge-projects/#{PATH}/"
|
92
|
+
local_dir = 'website'
|
93
|
+
sh %{rsync -aCv #{local_dir}/ #{host}:#{remote_dir}}
|
94
|
+
end
|
95
|
+
|
96
|
+
desc 'Generate and upload website files'
|
97
|
+
task :website => [:website_generate, :website_upload, :publish_docs]
|
98
|
+
|
99
|
+
desc 'Release the website and new gem version'
|
100
|
+
task :deploy => [:check_version, :website, :release] do
|
101
|
+
puts "Remember to create SVN tag:"
|
102
|
+
puts "svn copy svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/trunk " +
|
103
|
+
"svn+ssh://#{rubyforge_username}@rubyforge.org/var/svn/#{PATH}/tags/REL-#{VERS} "
|
104
|
+
puts "Suggested comment:"
|
105
|
+
puts "Tagging release #{CHANGES}"
|
106
|
+
end
|
107
|
+
|
108
|
+
desc 'Runs tasks website_generate and install_gem as a local deployment of the gem'
|
109
|
+
task :local_deploy => [:website_generate, :install_gem]
|
110
|
+
|
111
|
+
task :check_version do
|
112
|
+
unless ENV['VERSION']
|
113
|
+
puts 'Must pass a VERSION=x.y.z release version'
|
114
|
+
exit
|
115
|
+
end
|
116
|
+
unless ENV['VERSION'] == VERS
|
117
|
+
puts "Please update your version.rb to match the release version, currently #{VERS}"
|
118
|
+
exit
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
desc "Run specs"
|
123
|
+
Spec::Rake::SpecTask.new do |t|
|
124
|
+
t.spec_opts = ['--options', "spec/spec.opts"]
|
125
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
126
|
+
end
|
127
|
+
|
128
|
+
# Hoe insists on setting task :default => :test
|
129
|
+
# !@#$ no easy way to empty the default list of prerequisites
|
130
|
+
Rake::Task['default'].send :instance_variable_set, "@prerequisites", FileList[]
|
131
|
+
desc "Default task is to run specs"
|
132
|
+
task :default => :spec
|
133
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'spec/distributed/slave_runner'
|
3
|
+
|
4
|
+
module Spec
|
5
|
+
module Distributed
|
6
|
+
class MasterRunner < ::Spec::Runner::BehaviourRunner
|
7
|
+
def initialize(options, args=nil)
|
8
|
+
super(options)
|
9
|
+
process_args(args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def process_args(args)
|
13
|
+
@slave_urls = args.split(",")
|
14
|
+
raise "You must pass the DRb URLs: --runner #{self.class}:druby://host1:port1,drb://host2:port2" if @slave_urls.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(paths, exit_when_done)
|
18
|
+
@master_paths = paths
|
19
|
+
@svn_rev = `svn info`.match(/Revision: (\d+)/m)[1] rescue nil
|
20
|
+
STDERR.puts "WARNING - no local svn revision found. Your slaves may be out of sync." if @svn_rev.nil?
|
21
|
+
super(paths, exit_when_done)
|
22
|
+
end
|
23
|
+
|
24
|
+
def run_behaviours
|
25
|
+
DRb.start_service
|
26
|
+
behaviour_reports = Queue.new
|
27
|
+
index_queue = Queue.new
|
28
|
+
@behaviours.length.times {|index| index_queue << index}
|
29
|
+
|
30
|
+
@threads = slave_runners.map do |slave_runner|
|
31
|
+
Thread.new do
|
32
|
+
slave_runner.prepare_run(@master_paths, @svn_rev)
|
33
|
+
drb_error = nil
|
34
|
+
while !index_queue.empty?
|
35
|
+
begin
|
36
|
+
i = index_queue.pop
|
37
|
+
behaviour = @behaviours[i]
|
38
|
+
behaviour_report = slave_runner.run_behaviour_at(i, @options.dry_run, @options.reverse, @options.timeout)
|
39
|
+
behaviour_reports << behaviour_report
|
40
|
+
rescue DRb::DRbConnError => e
|
41
|
+
# Maybe the slave is down. Put the index back and die
|
42
|
+
index_queue << i
|
43
|
+
drb_error = e
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
unless drb_error
|
49
|
+
slave_runner.report_end
|
50
|
+
slave_runner.report_dump
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return unless @threads.length > 0
|
56
|
+
|
57
|
+
# Add a last thread for the reporter
|
58
|
+
@threads << Thread.new do
|
59
|
+
@behaviours.length.times do
|
60
|
+
behaviour_reports.pop.replay(@options.reporter)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
@threads.each do |t|
|
65
|
+
t.join
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def slave_runners
|
70
|
+
@slave_urls.map { |slave_url| DRbObject.new_with_uri(slave_url) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|