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.
@@ -0,0 +1,2 @@
1
+ == 1.2.2 / 2007-07-23
2
+ * Initial version, moved from RSpec's subversion.
@@ -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.
@@ -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
@@ -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
+
@@ -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,9 @@
1
+ describe "first" do
2
+ it "example a" do
3
+ sleep 1
4
+ end
5
+
6
+ it "example b" do
7
+ sleep 1
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ describe "fourth" do
2
+ it "example g" do
3
+ sleep 1
4
+ end
5
+
6
+ it "example h" do
7
+ sleep 0.5
8
+ 1.should == 2
9
+ end
10
+
11
+ it "example i" do
12
+ sleep 0.5
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ describe "second" do
2
+ it "example c" do
3
+ sleep 1
4
+ raise "error"
5
+ end
6
+
7
+ it "example d" do
8
+ sleep 1
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ describe "third" do
2
+ it "example e" do
3
+ sleep 1
4
+ end
5
+
6
+ it "example f" do
7
+ sleep 1
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec/distributed/version'
2
+ require 'spec/distributed/master_runner'
3
+ require 'spec/distributed/slave_runner'
4
+ require 'spec/distributed/tuple_args'
5
+ require 'spec/distributed/rinda_master_runner'
6
+ require 'spec/distributed/rinda_slave_runner'
@@ -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