spec_distributed 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|