spec_distributed 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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