parallel_tests 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ *.sh
@@ -0,0 +1,119 @@
1
+ Speedup Test::Unit + RSpec + Cucumber by running parallel on multiple CPUs.
2
+
3
+ Setup for Rails
4
+ ===============
5
+
6
+ sudo gem install parallel
7
+ script/plugin install git://github.com/grosser/parallel_tests.git
8
+
9
+ ### 1: Add to `config/database.yml`
10
+ test:
11
+ database: xxx_test<%= ENV['TEST_ENV_NUMBER'] %>
12
+
13
+ ### 2: Create additional database(s)
14
+ script/db_console
15
+ create database xxx_test2;
16
+ ...
17
+
18
+ ### 3: Copy development schema (repeat after migrations)
19
+ rake parallel:prepare
20
+
21
+ ### 4: Run!
22
+ rake parallel:test # Test::Unit
23
+ rake parallel:spec # RSpec
24
+ rake parallel:features # Cucumber
25
+
26
+ rake parallel:test[1] --> force 1 CPU --> 86 seconds
27
+ rake parallel:test --> got 2 CPUs? --> 47 seconds
28
+ rake parallel:test --> got 4 CPUs? --> 26 seconds
29
+ ...
30
+
31
+ Test just a subfolder (e.g. use one integration server per subfolder)
32
+ rake parallel:test[models]
33
+ rake parallel:test[something/else]
34
+
35
+ partial paths are OK too...
36
+ rake parallel:test[functional] == rake parallel:test[fun]
37
+
38
+ Example output
39
+ --------------
40
+ 2 processes for 210 specs, ~ 105 specs per process
41
+ ... test output ...
42
+
43
+ Results:
44
+ 877 examples, 0 failures, 11 pending
45
+ 843 examples, 0 failures, 1 pending
46
+
47
+ Took 29.925333 seconds
48
+
49
+ Even process runtimes (for specs only atm)
50
+ -----------------
51
+ Add to your `spec/parallel_specs.opts` (or `spec/spec.opts`) :
52
+ --format ParallelSpecs::SpecRuntimeLogger:tmp/parallel_profile.log
53
+ It will log test runtime and partition the test-load accordingly.
54
+
55
+ Setup for non-rails
56
+ ===================
57
+ sudo gem install parallel_tests
58
+ # go to your project dir
59
+ parallel_test OR parallel_spec OR parallel_cucumber
60
+ # [Optional] use ENV['TEST_ENV_NUMBER'] inside your tests for separate db/resources/etc.
61
+
62
+ Options are:
63
+ -n [PROCESSES] How many processes to use, default: available CPUs
64
+ -p, --path [PATH] run tests inside this path only
65
+ -r, --root [PATH] execute test commands from this path
66
+ -e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUM']
67
+ -o, --test-options [SOMETHING] execute test commands with those options
68
+ -t, --type [TYPE] which type of tests to run? test, spec or features
69
+ -v, --version Show Version
70
+ -h, --help Show this.
71
+
72
+ You can run any kind of code with -e / --execute
73
+ parallel_test -n 5 -e 'ruby -e "puts %[hello from process #{ENV[:TEST_ENV_NUMBER.to_s].inspect}]"'
74
+ hello from process "2"
75
+ hello from process ""
76
+ hello from process "3"
77
+ hello from process "5"
78
+ hello from process "4"
79
+
80
+ <table>
81
+ <tr><td></td><td>1 Process</td><td>2 Processes</td><td>4 Processes</td></tr>
82
+ <tr><td>RSpec spec-suite</td><td>18</td><td>14</td><td>10</td></tr>
83
+ <tr><td>Rails-ActionPack</td><td>88</td><td>53</td><td>44</td></tr>
84
+ </table>
85
+
86
+ TIPS
87
+ ====
88
+ - [RSpec] add a `spec/parallel_spec.opts` to use different options, e.g. no --drb (default: `spec/spec.opts`)
89
+ - [RSpec] if something looks fishy try to delete `script/spec`
90
+ - [RSpec] if `script/spec` is missing parallel:spec uses just `spec` (which solves some issues with double-loaded environment.rb)
91
+ - [RSpec] 'script/spec_server' or [spork](http://github.com/timcharper/spork/tree/master) do not work in parallel
92
+ - [RSpec] `./script/generate rspec` if you are running rspec from gems (this plugin uses script/spec which may fail if rspec files are outdated)
93
+ - [Bundler] if you have a `.bundle/environment.rb` then `bundle exec xxx` will be used to run tests
94
+ - with zsh this would be `rake "parallel:prepare[3]"`
95
+
96
+ TODO
97
+ ====
98
+ - build parallel:bootstrap [idea/basics](http://github.com/garnierjm/parallel_specs/commit/dd8005a2639923dc5adc6400551c4dd4de82bf9a)
99
+ - make jRuby compatible [basics](http://yehudakatz.com/2009/07/01/new-rails-isolation-testing/)
100
+ - make windows compatible (does anyone care ?)
101
+
102
+ Authors
103
+ ====
104
+ inspired by [pivotal labs](http://pivotallabs.com/users/miked/blog/articles/849-parallelize-your-rspec-suite)
105
+
106
+ ###Contributors (alphabetical)
107
+ - [Charles Finkel](http://charlesfinkel.com/)
108
+ - [Jason Morrison](http://jayunit.net)
109
+ - [Joakim Kolsjö](http://www.rubyblocks.se)
110
+ - [Kpumuk](http://kpumuk.info/)
111
+ - [Maksim Horbu](http://github.com/mhorbul)
112
+ - [Rohan Deshpande](http://github.com/rdeshpande)
113
+ - [Tchandy](http://thiagopradi.net/)
114
+ - [Terence Lee](http://hone.heroku.com/)
115
+ - [Will Bryant](http://willbryant.net/)
116
+
117
+ [Michael Grosser](http://pragmatig.wordpress.com)
118
+ grosser.michael@gmail.com
119
+ Hereby placed under public domain, do what you want, just do not hold me accountable...
@@ -0,0 +1,19 @@
1
+ task :default => :spec
2
+ require 'spec/rake/spectask'
3
+ Spec::Rake::SpecTask.new {|t| t.spec_opts = ['--color']}
4
+
5
+ begin
6
+ require 'jeweler'
7
+ project_name = 'parallel_tests'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = project_name
10
+ gem.summary = "Run tests / specs / features in parallel"
11
+ gem.email = "grosser.michael@gmail.com"
12
+ gem.homepage = "http://github.com/grosser/#{project_name}"
13
+ gem.authors = ["Michael Grosser"]
14
+ end
15
+
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
19
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ exec "#{File.join(File.dirname(__FILE__), 'parallel_test')} -t features #{ARGV * ' '}"
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+ exec "#{File.join(File.dirname(__FILE__), 'parallel_test')} -t spec #{ARGV * ' '}"
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'optparse'
4
+ lib_folder = File.join(File.dirname(__FILE__), '..', 'lib')
5
+ require File.join(lib_folder, "parallel_tests")
6
+
7
+ options = {}
8
+ OptionParser.new do |opts|
9
+ opts.banner = <<BANNER
10
+ Run tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2', '3', ...)
11
+
12
+ Options are:
13
+ BANNER
14
+ opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs"){|n| options[:count] = n }
15
+ opts.on("-p", '--path [PATH]', "run tests inside this path only"){|path| options[:path_prefix] = path }
16
+ opts.on("-r", '--root [PATH]', "execute test commands from this path"){|path| options[:root] = path }
17
+ opts.on("-e", '--exec [COMMAND]', "execute this code parallel and with ENV['TEST_ENV_NUM']"){|path| options[:execute] = path }
18
+ opts.on("-o", '--test-options [SOMETHING]', "execute test commands with those options"){|arg| options[:test_options] = arg }
19
+ opts.on("-t", "--type [TYPE]", "which type of tests to run? test, spec or features"){|type| options[:type] = type }
20
+ opts.on('-v', '--version', 'Show Version'){ puts ParallelTests::VERSION; exit}
21
+ opts.on("-h", "--help", "Show this.") { puts opts; exit }
22
+ end.parse!
23
+
24
+ require 'parallel'
25
+ num_processes = options[:count] || Parallel.processor_count
26
+
27
+ if options[:execute]
28
+ require File.join(lib_folder, "parallel_tests")
29
+ Parallel.in_processes(num_processes) do |i|
30
+ ParallelTests.execute_command(options[:execute], i)
31
+ end
32
+ else
33
+ lib, name, task = {
34
+ 'test' => ["tests", "test", "test"],
35
+ 'spec' => ["specs", "spec", "spec"],
36
+ 'features' => ["cucumber", "feature", "features"]
37
+ }[options[:type]||'test']
38
+
39
+ require File.join(lib_folder, "parallel_#{lib}")
40
+ klass = eval("Parallel#{lib.capitalize}")
41
+
42
+ start = Time.now
43
+
44
+ tests_folder = File.join(task, options[:path_prefix].to_s)
45
+ tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
46
+
47
+ groups = klass.tests_in_groups(tests_folder, num_processes)
48
+ num_processes = groups.size
49
+
50
+ #adjust processes to groups
51
+ abort "no #{name}s found!" if groups.size == 0
52
+
53
+ num_tests = groups.inject(0){|sum,item| sum + item.size }
54
+ puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
55
+
56
+ output = Parallel.map(groups, :in_processes => num_processes) do |group|
57
+ klass.run_tests(group, groups.index(group), options[:test_options])
58
+ end
59
+
60
+ #parse and print results
61
+ results = klass.find_results(output*"")
62
+ puts ""
63
+ puts "Results:"
64
+ results.each{|r| puts r}
65
+
66
+ #report total time taken
67
+ puts ""
68
+ puts "Took #{Time.now - start} seconds"
69
+
70
+ #exit with correct status code
71
+ # - rake parallel:test && echo 123 ==> 123 should not show up when test failed
72
+ # - rake parallel:test db:reset ==> works when tests succeed
73
+ abort "#{name.capitalize}s Failed" if klass.failed?(results)
74
+ end
@@ -0,0 +1,33 @@
1
+ require File.join(File.dirname(__FILE__), 'parallel_tests')
2
+
3
+ class ParallelCucumber < ParallelTests
4
+ def self.run_tests(test_files, process_number, options)
5
+ color = ($stdout.tty? ? 'export AUTOTEST=1 ;' : '')#display color when we are in a terminal
6
+ cmd = "export RAILS_ENV=test ; #{color} #{executable} #{options} #{test_files*' '}"
7
+ execute_command(cmd, process_number)
8
+ end
9
+
10
+ def self.executable
11
+ if File.file?(".bundle/environment.rb")
12
+ "bundle exec cucumber"
13
+ elsif File.file?("script/cucumber")
14
+ "script/cucumber"
15
+ else
16
+ "cucumber"
17
+ end
18
+ end
19
+
20
+ protected
21
+
22
+ def self.line_is_result?(line)
23
+ line =~ /^\d+ (steps|scenarios)/
24
+ end
25
+
26
+ def self.line_is_failure?(line)
27
+ line =~ /^\d+ (steps|scenarios).*(\d{2,}|[1-9]) failed/
28
+ end
29
+
30
+ def self.find_tests(root)
31
+ Dir["#{root}**/**/*.feature"]
32
+ end
33
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), 'parallel_tests')
2
+
3
+ class ParallelSpecs < ParallelTests
4
+ def self.run_tests(test_files, process_number, options)
5
+ spec_opts = ['spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
6
+ spec_opts = (spec_opts ? "-O #{spec_opts}" : nil)
7
+ color = ($stdout.tty? ? 'export RSPEC_COLOR=1 ;' : '')#display color when we are in a terminal
8
+ cmd = "export RAILS_ENV=test ; #{color} #{executable} #{options} #{spec_opts} #{test_files*' '}"
9
+ execute_command(cmd, process_number)
10
+ end
11
+
12
+ def self.executable
13
+ if File.file?(".bundle/environment.rb")
14
+ "bundle exec spec"
15
+ elsif File.file?("script/spec")
16
+ "script/spec"
17
+ else
18
+ "spec"
19
+ end
20
+ end
21
+
22
+ protected
23
+
24
+ def self.find_tests(root)
25
+ Dir["#{root}**/**/*_spec.rb"]
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec/runner/formatter/progress_bar_formatter'
2
+
3
+ class ParallelSpecs::SpecRuntimeLogger < Spec::Runner::Formatter::BaseTextFormatter
4
+ def initialize(options, output)
5
+ if String === output
6
+ FileUtils.mkdir_p(File.dirname(output))
7
+ File.open(output,'w'){|f| f.write ''} # clean the file
8
+ @output = File.open(output, 'a+') #append so that multiple processes can write at once
9
+ else
10
+ @output = output
11
+ end
12
+ @example_times = Hash.new(0)
13
+ end
14
+
15
+ def example_started(*args)
16
+ @time = Time.now
17
+ end
18
+
19
+ def example_passed(example)
20
+ file = example.location.split(':').first
21
+ @example_times[file] += Time.now - @time
22
+ end
23
+
24
+ def start_dump(*args)
25
+ return unless ENV['TEST_ENV_NUMBER'] #only record when running in parallel
26
+ # TODO: Figure out why sometimes time can be less than 0
27
+ @output.puts @example_times.map { |file, time| "#{file}:#{time > 0 ? time : 0}" }
28
+ @output.flush
29
+ end
30
+
31
+ # stubs so that rspec doe not crash
32
+
33
+ def example_pending(*args)
34
+ end
35
+
36
+ def dump_summary(*args)
37
+ end
38
+
39
+ def dump_pending(*args)
40
+ end
41
+
42
+ def dump_failure(*args)
43
+ end
44
+
45
+ #stolen from Rspec
46
+ def close
47
+ @output.close if (IO === @output) & (@output != $stdout)
48
+ end
49
+ end
@@ -0,0 +1,119 @@
1
+ require 'parallel'
2
+
3
+ class ParallelTests
4
+ VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
5
+
6
+ # parallel:spec[2,controller] <-> parallel:spec[controller]
7
+ def self.parse_rake_args (args)
8
+ num_processes = Parallel.processor_count
9
+ options = ""
10
+ if args[:count].to_s =~ /^\d*$/ # number or empty
11
+ num_processes = args[:count] unless args[:count].to_s.empty?
12
+ prefix = args[:path_prefix]
13
+ options = args[:options] if args[:options]
14
+ else # something stringy
15
+ prefix = args[:count]
16
+ end
17
+ [num_processes.to_i, prefix.to_s, options]
18
+ end
19
+
20
+ # finds all tests and partitions them into groups
21
+ def self.tests_in_groups(root, num)
22
+ tests_with_sizes = slow_specs_first(find_tests_with_sizes(root))
23
+
24
+ groups = []
25
+ current_group = current_size = 0
26
+ tests_with_sizes.each do |test, size|
27
+ # inserts into next group if current is full and we are not in the last group
28
+ if (0.5*size + current_size) > group_size(tests_with_sizes, num) and num > current_group + 1
29
+ current_size = size
30
+ current_group += 1
31
+ else
32
+ current_size += size
33
+ end
34
+ groups[current_group] ||= []
35
+ groups[current_group] << test
36
+ end
37
+ groups.compact
38
+ end
39
+
40
+ def self.run_tests(test_files, process_number, options)
41
+ require_list = test_files.map { |filename| "\"#{filename}\"" }.join(",")
42
+ cmd = "export RAILS_ENV=test ; ruby -Itest #{options} -e '[#{require_list}].each {|f| require f }'"
43
+ execute_command(cmd, process_number)
44
+ end
45
+
46
+ def self.execute_command(cmd, process_number)
47
+ cmd = "export TEST_ENV_NUMBER=#{test_env_number(process_number)} ; #{cmd}"
48
+ f = open("|#{cmd}", 'r')
49
+ all = ''
50
+ while char = f.getc
51
+ char = (char.is_a?(Fixnum) ? char.chr : char) # 1.8 <-> 1.9
52
+ all << char
53
+ print char
54
+ STDOUT.flush
55
+ end
56
+ all
57
+ end
58
+
59
+ def self.find_results(test_output)
60
+ test_output.split("\n").map {|line|
61
+ line = line.gsub(/\.|F|\*/,'')
62
+ next unless line_is_result?(line)
63
+ line
64
+ }.compact
65
+ end
66
+
67
+ def self.failed?(results)
68
+ return true if results.empty?
69
+ !! results.detect{|line| line_is_failure?(line)}
70
+ end
71
+
72
+ def self.test_env_number(process_number)
73
+ process_number == 0 ? '' : process_number + 1
74
+ end
75
+
76
+ protected
77
+
78
+ def self.slow_specs_first(tests)
79
+ tests.sort_by{|test, size| size }.reverse
80
+ end
81
+
82
+ def self.line_is_result?(line)
83
+ line =~ /\d+ failure/
84
+ end
85
+
86
+ def self.line_is_failure?(line)
87
+ line =~ /(\d{2,}|[1-9]) (failure|error)/
88
+ end
89
+
90
+ def self.group_size(tests_with_sizes, num_groups)
91
+ total_size = tests_with_sizes.inject(0) { |sum, test| sum += test[1] }
92
+ total_size / num_groups.to_f
93
+ end
94
+
95
+ def self.find_tests_with_sizes(root)
96
+ tests = find_tests(root).sort
97
+
98
+ #TODO get the real root, atm this only works for complete runs when root point to e.g. real_root/spec
99
+ runtime_file = File.join(root,'..','tmp','parallel_profile.log')
100
+ lines = File.read(runtime_file).split("\n") rescue []
101
+
102
+ if lines.size * 1.5 > tests.size
103
+ # use recorded test runtime if we got enough data
104
+ times = Hash.new(1)
105
+ lines.each do |line|
106
+ test, time = line.split(":")
107
+ times[test] = time.to_f
108
+ end
109
+ tests.map { |test| [ test, times[test] ] }
110
+ else
111
+ # use file sizes
112
+ tests.map { |test| [ test, File.stat(test).size ] }
113
+ end
114
+ end
115
+
116
+ def self.find_tests(root)
117
+ Dir["#{root}**/**/*_test.rb"]
118
+ end
119
+ end
@@ -0,0 +1,61 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{parallel_tests}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Michael Grosser"]
12
+ s.date = %q{2010-03-02}
13
+ s.email = %q{grosser.michael@gmail.com}
14
+ s.executables = ["parallel_test", "parallel_spec", "parallel_cucumber"]
15
+ s.extra_rdoc_files = [
16
+ "README.markdown"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README.markdown",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "bin/parallel_cucumber",
24
+ "bin/parallel_spec",
25
+ "bin/parallel_test",
26
+ "lib/parallel_cucumber.rb",
27
+ "lib/parallel_specs.rb",
28
+ "lib/parallel_specs/spec_runtime_logger.rb",
29
+ "lib/parallel_tests.rb",
30
+ "parallel_tests.gemspec",
31
+ "spec/integration_spec.rb",
32
+ "spec/parallel_cucumber_spec.rb",
33
+ "spec/parallel_specs_spec.rb",
34
+ "spec/parallel_tests_spec.rb",
35
+ "spec/spec_helper.rb",
36
+ "tasks/parallel_specs.rake"
37
+ ]
38
+ s.homepage = %q{http://github.com/grosser/parallel_tests}
39
+ s.rdoc_options = ["--charset=UTF-8"]
40
+ s.require_paths = ["lib"]
41
+ s.rubygems_version = %q{1.3.6}
42
+ s.summary = %q{Run tests / specs / features in parallel}
43
+ s.test_files = [
44
+ "spec/spec_helper.rb",
45
+ "spec/parallel_tests_spec.rb",
46
+ "spec/parallel_specs_spec.rb",
47
+ "spec/parallel_cucumber_spec.rb",
48
+ "spec/integration_spec.rb"
49
+ ]
50
+
51
+ if s.respond_to? :specification_version then
52
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
53
+ s.specification_version = 3
54
+
55
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
56
+ else
57
+ end
58
+ else
59
+ end
60
+ end
61
+
@@ -0,0 +1,83 @@
1
+ describe 'CLI' do
2
+ before do
3
+ `rm -rf #{folder}`
4
+ end
5
+
6
+ after do
7
+ `rm -rf #{folder}`
8
+ end
9
+
10
+ def folder
11
+ "/tmp/parallel_tests_tests"
12
+ end
13
+
14
+ def write(file, content)
15
+ path = "#{folder}/spec/#{file}"
16
+ `mkdir -p #{File.dirname(path)}` unless File.exist?(File.dirname(path))
17
+ File.open(path, 'w'){|f| f.write content }
18
+ path
19
+ end
20
+
21
+ def bin_folder
22
+ "#{File.expand_path(File.dirname(__FILE__))}/../bin"
23
+ end
24
+
25
+ def executable
26
+ "#{bin_folder}/parallel_test"
27
+ end
28
+
29
+ def run_specs(options={})
30
+ `cd #{folder} && #{executable} -t spec -n #{options[:processes]||2} 2>&1 && echo 'i ran!'`
31
+ end
32
+
33
+ it "runs tests in parallel" do
34
+ write 'xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
35
+ write 'xxx2_spec.rb', 'describe("it"){it("should"){puts "TEST2"}}'
36
+ result = run_specs
37
+
38
+ # test ran and gave their puts
39
+ result.should include('TEST1')
40
+ result.should include('TEST2')
41
+
42
+ # all results present
43
+ result.scan('1 example, 0 failure').size.should == 4 # 2 results + 2 result summary
44
+ result.scan(/Finished in \d+\.\d+ seconds/).size.should == 2
45
+ result.scan(/Took \d+\.\d+ seconds/).size.should == 1 # parallel summary
46
+
47
+ result.should include('i ran!')
48
+ end
49
+
50
+ it "fails when tests fail" do
51
+ write 'xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
52
+ write 'xxx2_spec.rb', 'describe("it"){it("should"){1.should == 2}}'
53
+ result = run_specs
54
+
55
+ result.scan('1 example, 1 failure').size.should == 2
56
+ result.scan('1 example, 0 failure').size.should == 2
57
+ result.should =~ /specs failed/i
58
+ result.should_not include('i ran!')
59
+ end
60
+
61
+ it "can exec given commands with ENV['TEST_ENV_NUM']" do
62
+ result = `#{executable} -e 'ruby -e "puts ENV[:TEST_ENV_NUMBER.to_s].inspect"' -n 4`
63
+ result.split("\n").sort.should == %w["" "2" "3" "4"]
64
+ end
65
+
66
+ it "can run through parallel_spec / parallel_cucumber" do
67
+ version = `#{executable} -v`
68
+ `#{bin_folder}/parallel_spec -v`.should == version
69
+ `#{bin_folder}/parallel_cucumber -v`.should == version
70
+ end
71
+
72
+ it "runs faster with more processes" do
73
+ write 'xxx_spec.rb', 'describe("it"){it("should"){sleep 2}}'
74
+ write 'xxx2_spec.rb', 'describe("it"){it("should"){sleep 2}}'
75
+ write 'xxx3_spec.rb', 'describe("it"){it("should"){sleep 2}}'
76
+ write 'xxx4_spec.rb', 'describe("it"){it("should"){sleep 2}}'
77
+ write 'xxx5_spec.rb', 'describe("it"){it("should"){sleep 2}}'
78
+ write 'xxx6_spec.rb', 'describe("it"){it("should"){sleep 2}}'
79
+ t = Time.now
80
+ run_specs :processes => 6
81
+ (Time.now - t).should < 5
82
+ end
83
+ end
@@ -0,0 +1,101 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe ParallelCucumber do
4
+ test_tests_in_groups(ParallelCucumber, 'features', ".feature")
5
+
6
+ describe :run_tests do
7
+ before(:each) do
8
+ File.stub!(:file?).with('.bundle/environment.rb').and_return false
9
+ File.stub!(:file?).with('script/cucumber').and_return true
10
+ end
11
+
12
+ it "uses TEST_ENV_NUMBER=blank when called for process 0" do
13
+ ParallelCucumber.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER= /}.and_return mock(:getc=>false)
14
+ ParallelCucumber.run_tests(['xxx'],0,'')
15
+ end
16
+
17
+ it "uses TEST_ENV_NUMBER=2 when called for process 1" do
18
+ ParallelCucumber.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER=2/}.and_return mock(:getc=>false)
19
+ ParallelCucumber.run_tests(['xxx'],1,'')
20
+ end
21
+
22
+ it "returns the output" do
23
+ io = open('spec/spec_helper.rb')
24
+ ParallelCucumber.stub!(:print)
25
+ ParallelCucumber.should_receive(:open).and_return io
26
+ ParallelCucumber.run_tests(['xxx'],1,'').should =~ /\$LOAD_PATH << File/
27
+ end
28
+
29
+ it "runs bundle exec cucumber when on bundler 0.9" do
30
+ File.stub!(:file?).with('.bundle/environment.rb').and_return true
31
+ ParallelCucumber.should_receive(:open).with{|x,y| x =~ %r{bundle exec cucumber}}.and_return mock(:getc=>false)
32
+ ParallelCucumber.run_tests(['xxx'],1,'')
33
+ end
34
+
35
+ it "runs script/cucumber when script/cucumber is found" do
36
+ ParallelCucumber.should_receive(:open).with{|x,y| x =~ %r{script/cucumber}}.and_return mock(:getc=>false)
37
+ ParallelCucumber.run_tests(['xxx'],1,'')
38
+ end
39
+
40
+ it "runs cucumber by default" do
41
+ File.stub!(:file?).with('script/cucumber').and_return false
42
+ ParallelCucumber.should_receive(:open).with{|x,y| x !~ %r{(script/cucumber)|(bundle exec cucumber)}}.and_return mock(:getc=>false)
43
+ ParallelCucumber.run_tests(['xxx'],1,'')
44
+ end
45
+
46
+ it "uses options passed in" do
47
+ ParallelCucumber.should_receive(:open).with{|x,y| x =~ %r{script/cucumber -p default}}.and_return mock(:getc=>false)
48
+ ParallelCucumber.run_tests(['xxx'],1,'-p default')
49
+ end
50
+ end
51
+
52
+ describe :find_results do
53
+ it "finds multiple results in test output" do
54
+ output = <<EOF
55
+ And I should not see "/en/" # features/step_definitions/webrat_steps.rb:87
56
+
57
+ 7 scenarios (3 failed, 4 passed)
58
+ 33 steps (3 failed, 2 skipped, 28 passed)
59
+ /apps/rs/features/signup.feature:2
60
+ Given I am on "/" # features/step_definitions/common_steps.rb:12
61
+ When I click "register" # features/step_definitions/common_steps.rb:6
62
+ And I should have "2" emails # features/step_definitions/user_steps.rb:25
63
+
64
+ 4 scenarios (4 passed)
65
+ 40 steps (40 passed)
66
+
67
+ EOF
68
+ ParallelCucumber.find_results(output).should == ["7 scenarios (3 failed, 4 passed)", "33 steps (3 failed, 2 skipped, 28 passed)", "4 scenarios (4 passed)", "40 steps (40 passed)"]
69
+ end
70
+ end
71
+
72
+ describe :failed do
73
+ it "fails with single failed" do
74
+ ParallelCucumber.failed?(['40 steps (40 passed)','33 steps (3 failed, 2 skipped, 28 passed)']).should == true
75
+ end
76
+
77
+ it "fails with multiple failed tests" do
78
+ ParallelCucumber.failed?(['33 steps (3 failed, 2 skipped, 28 passed)','33 steps (3 failed, 2 skipped, 28 passed)']).should == true
79
+ end
80
+
81
+ it "fails with a single scenario failure during setup phase" do
82
+ ParallelCucumber.failed?(['1 scenarios (1 failed)']).should == true
83
+ end
84
+
85
+ it "fails with scenario failures during setup phase when other steps pass" do
86
+ ParallelCucumber.failed?(['7 scenarios (3 failed, 4 passed)','40 steps (40 passed)']).should == true
87
+ end
88
+
89
+ it "does not fail with successful tests" do
90
+ ParallelCucumber.failed?(['4 scenarios (4 passed)','40 steps (40 passed)','4 scenarios (4 passed)','40 steps (40 passed)']).should == false
91
+ end
92
+
93
+ it "does not fail with 0 failures" do
94
+ ParallelCucumber.failed?(['4 scenarios (4 passed 0 failed)','40 steps (40 passed 0 failed)','4 scenarios (4 passed 0 failed)','40 steps (40 passed)']).should == false
95
+ end
96
+
97
+ it "does fail with 10 failures" do
98
+ ParallelCucumber.failed?(['40 steps (40 passed 10 failed)','40 steps (40 passed)']).should == true
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,134 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe ParallelSpecs do
4
+ test_tests_in_groups(ParallelSpecs, 'spec', '_spec.rb')
5
+
6
+ describe :run_tests do
7
+ before do
8
+ File.stub!(:file?).with('.bundle/environment.rb').and_return false
9
+ File.stub!(:file?).with('script/spec').and_return true
10
+ File.stub!(:file?).with('spec/spec.opts').and_return true
11
+ File.stub!(:file?).with('spec/parallel_spec.opts').and_return false
12
+ end
13
+
14
+ it "uses TEST_ENV_NUMBER=blank when called for process 0" do
15
+ ParallelSpecs.should_receive(:open).with{|x,y|x=~/TEST_ENV_NUMBER= /}.and_return mock(:getc=>false)
16
+ ParallelSpecs.run_tests(['xxx'],0,'')
17
+ end
18
+
19
+ it "uses TEST_ENV_NUMBER=2 when called for process 1" do
20
+ ParallelSpecs.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER=2/}.and_return mock(:getc=>false)
21
+ ParallelSpecs.run_tests(['xxx'],1,'')
22
+ end
23
+
24
+ it "runs with color when called from cmdline" do
25
+ ParallelSpecs.should_receive(:open).with{|x,y| x=~/RSPEC_COLOR=1/}.and_return mock(:getc=>false)
26
+ $stdout.should_receive(:tty?).and_return true
27
+ ParallelSpecs.run_tests(['xxx'],1,'')
28
+ end
29
+
30
+ it "runs without color when not called from cmdline" do
31
+ ParallelSpecs.should_receive(:open).with{|x,y| x !~ /RSPEC_COLOR/}.and_return mock(:getc=>false)
32
+ $stdout.should_receive(:tty?).and_return false
33
+ ParallelSpecs.run_tests(['xxx'],1,'')
34
+ end
35
+
36
+ it "run bundle exec spec when on bundler 0.9" do
37
+ File.stub!(:file?).with('.bundle/environment.rb').and_return true
38
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{bundle exec spec}}.and_return mock(:getc=>false)
39
+ ParallelSpecs.run_tests(['xxx'],1,'')
40
+ end
41
+
42
+ it "runs script/spec when script/spec can be found" do
43
+ File.should_receive(:file?).with('script/spec').and_return true
44
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec}}.and_return mock(:getc=>false)
45
+ ParallelSpecs.run_tests(['xxx'],1,'')
46
+ end
47
+
48
+ it "runs spec when script/spec cannot be found" do
49
+ File.stub!(:file?).with('script/spec').and_return false
50
+ ParallelSpecs.should_receive(:open).with{|x,y| x !~ %r{(script/spec)|(bundle exec spec)}}.and_return mock(:getc=>false)
51
+ ParallelSpecs.run_tests(['xxx'],1,'')
52
+ end
53
+
54
+ it "uses no -O when no opts where found" do
55
+ File.stub!(:file?).with('spec/spec.opts').and_return false
56
+ ParallelSpecs.should_receive(:open).with{|x,y| x !~ %r{spec/spec.opts}}.and_return mock(:getc=>false)
57
+ ParallelSpecs.run_tests(['xxx'],1,'')
58
+ end
59
+
60
+ it "uses spec/spec.opts when found" do
61
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec\s+-O spec/spec.opts}}.and_return mock(:getc=>false)
62
+ ParallelSpecs.run_tests(['xxx'],1,'')
63
+ end
64
+
65
+ it "uses spec/parallel_spec.opts when found" do
66
+ File.should_receive(:file?).with('spec/parallel_spec.opts').and_return true
67
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec\s+-O spec/parallel_spec.opts}}.and_return mock(:getc=>false)
68
+ ParallelSpecs.run_tests(['xxx'],1,'')
69
+ end
70
+
71
+ it "uses options passed in" do
72
+ ParallelSpecs.should_receive(:open).with{|x,y| x =~ %r{script/spec -f n}}.and_return mock(:getc=>false)
73
+ ParallelSpecs.run_tests(['xxx'],1,'-f n')
74
+ end
75
+
76
+ it "returns the output" do
77
+ io = open('spec/spec_helper.rb')
78
+ ParallelSpecs.stub!(:print)
79
+ ParallelSpecs.should_receive(:open).and_return io
80
+ ParallelSpecs.run_tests(['xxx'],1,'').should =~ /\$LOAD_PATH << File/
81
+ end
82
+ end
83
+
84
+ describe :find_results do
85
+ it "finds multiple results in spec output" do
86
+ output = <<EOF
87
+ ....F...
88
+ ..
89
+ failute fsddsfsd
90
+ ...
91
+ ff.**..
92
+ 0 examples, 0 failures, 0 pending
93
+ ff.**..
94
+ 1 example, 1 failure, 1 pending
95
+ EOF
96
+
97
+ ParallelSpecs.find_results(output).should == ['0 examples, 0 failures, 0 pending','1 example, 1 failure, 1 pending']
98
+ end
99
+
100
+ it "is robust against scrambeled output" do
101
+ output = <<EOF
102
+ ....F...
103
+ ..
104
+ failute fsddsfsd
105
+ ...
106
+ ff.**..
107
+ 0 exFampl*es, 0 failures, 0 pend.ing
108
+ ff.**..
109
+ 1 exampF.les, 1 failures, 1 pend.ing
110
+ EOF
111
+
112
+ ParallelSpecs.find_results(output).should == ['0 examples, 0 failures, 0 pending','1 examples, 1 failures, 1 pending']
113
+ end
114
+ end
115
+
116
+ describe :failed do
117
+ it "fails with single failed specs" do
118
+ ParallelSpecs.failed?(['0 examples, 0 failures, 0 pending','1 examples, 1 failure, 1 pending']).should == true
119
+ end
120
+
121
+ it "fails with multiple failed specs" do
122
+ ParallelSpecs.failed?(['0 examples, 1 failure, 0 pending','1 examples, 111 failures, 1 pending']).should == true
123
+ end
124
+
125
+ it "does not fail with successful specs" do
126
+ ParallelSpecs.failed?(['0 examples, 0 failures, 0 pending','1 examples, 0 failures, 1 pending']).should == false
127
+ end
128
+
129
+ it "does fail with 10 failures" do
130
+ ParallelSpecs.failed?(['0 examples, 10 failures, 0 pending','1 examples, 0 failures, 1 pending']).should == true
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,130 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe ParallelTests do
4
+ test_tests_in_groups(ParallelTests, 'test', '_test.rb')
5
+
6
+ describe :parse_rake_args do
7
+ it "should return the count" do
8
+ args = {:count => 2}
9
+ ParallelTests.parse_rake_args(args).should == [2, '', ""]
10
+ end
11
+
12
+ it "should default to the prefix" do
13
+ args = {:count => "models"}
14
+ ParallelTests.parse_rake_args(args).should == [2, "models", ""]
15
+ end
16
+
17
+ it "should return the count and prefix" do
18
+ args = {:count => 2, :path_prefix => "models"}
19
+ ParallelTests.parse_rake_args(args).should == [2, "models", ""]
20
+ end
21
+
22
+ it "should return the count, prefix, and options" do
23
+ args = {:count => 2, :path_prefix => "plain", :options => "-p default" }
24
+ ParallelTests.parse_rake_args(args).should == [2, "plain", "-p default"]
25
+ end
26
+ end
27
+
28
+ describe :run_tests do
29
+ it "uses TEST_ENV_NUMBER=blank when called for process 0" do
30
+ ParallelTests.should_receive(:open).with{|x,y|x=~/TEST_ENV_NUMBER= /}.and_return mock(:getc=>false)
31
+ ParallelTests.run_tests(['xxx'],0,'')
32
+ end
33
+
34
+ it "uses TEST_ENV_NUMBER=2 when called for process 1" do
35
+ ParallelTests.should_receive(:open).with{|x,y| x=~/TEST_ENV_NUMBER=2/}.and_return mock(:getc=>false)
36
+ ParallelTests.run_tests(['xxx'],1,'')
37
+ end
38
+
39
+ it "uses options" do
40
+ ParallelTests.should_receive(:open).with{|x,y| x=~ %r{ruby -Itest -v}}.and_return mock(:getc=>false)
41
+ ParallelTests.run_tests(['xxx'],1,'-v')
42
+ end
43
+
44
+ it "returns the output" do
45
+ io = open('spec/spec_helper.rb')
46
+ ParallelTests.stub!(:print)
47
+ ParallelTests.should_receive(:open).and_return io
48
+ ParallelTests.run_tests(['xxx'],1,'').should =~ /\$LOAD_PATH << File/
49
+ end
50
+ end
51
+
52
+ describe :find_results do
53
+ it "finds multiple results in test output" do
54
+ output = <<EOF
55
+ Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
56
+ Started
57
+ ..............
58
+ Finished in 0.145069 seconds.
59
+
60
+ 10 tests, 20 assertions, 0 failures, 0 errors
61
+ Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
62
+ Started
63
+ ..............
64
+ Finished in 0.145069 seconds.
65
+
66
+ 14 tests, 20 assertions, 0 failures, 0 errors
67
+
68
+ EOF
69
+
70
+ ParallelTests.find_results(output).should == ['10 tests, 20 assertions, 0 failures, 0 errors','14 tests, 20 assertions, 0 failures, 0 errors']
71
+ end
72
+
73
+ it "is robust against scrambeled output" do
74
+ output = <<EOF
75
+ Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
76
+ Started
77
+ ..............
78
+ Finished in 0.145069 seconds.
79
+
80
+ 10 tests, 20 assertions, 0 failures, 0 errors
81
+ Loaded suite /opt/ruby-enterprise/lib/ruby/gems/1.8/gems/rake-0.8.4/lib/rake/rake_test_loader
82
+ Started
83
+ ..............
84
+ Finished in 0.145069 seconds.
85
+
86
+ 14 te.dsts, 20 assertions, 0 failures, 0 errors
87
+ EOF
88
+
89
+ ParallelTests.find_results(output).should == ['10 tests, 20 assertions, 0 failures, 0 errors','14 tedsts, 20 assertions, 0 failures, 0 errors']
90
+ end
91
+ end
92
+
93
+ describe :failed do
94
+ it "fails with single failed" do
95
+ ParallelTests.failed?(['10 tests, 20 assertions, 0 failures, 0 errors','10 tests, 20 assertions, 1 failure, 0 errors']).should == true
96
+ end
97
+
98
+ it "fails with single error" do
99
+ ParallelTests.failed?(['10 tests, 20 assertions, 0 failures, 1 errors','10 tests, 20 assertions, 0 failures, 0 errors']).should == true
100
+ end
101
+
102
+ it "fails with failed and error" do
103
+ ParallelTests.failed?(['10 tests, 20 assertions, 0 failures, 1 errors','10 tests, 20 assertions, 1 failures, 1 errors']).should == true
104
+ end
105
+
106
+ it "fails with multiple failed tests" do
107
+ ParallelTests.failed?(['10 tests, 20 assertions, 2 failures, 0 errors','10 tests, 1 assertion, 1 failures, 0 errors']).should == true
108
+ end
109
+
110
+ it "does not fail with successful tests" do
111
+ ParallelTests.failed?(['10 tests, 20 assertions, 0 failures, 0 errors','10 tests, 20 assertions, 0 failures, 0 errors']).should == false
112
+ end
113
+
114
+ it "does fail with 10 failures" do
115
+ ParallelTests.failed?(['10 tests, 20 assertions, 10 failures, 0 errors','10 tests, 20 assertions, 0 failures, 0 errors']).should == true
116
+ end
117
+
118
+ it "is not failed with empty results" do
119
+ ParallelTests.failed?(['0 tests, 0 assertions, 0 failures, 0 errors']).should == false
120
+ end
121
+
122
+ it "is failed when there are no results" do
123
+ ParallelTests.failed?([]).should == true
124
+ end
125
+ end
126
+
127
+ it "has a version" do
128
+ ParallelTests::VERSION.should =~ /^\d+\.\d+\.\d+$/
129
+ end
130
+ end
@@ -0,0 +1,78 @@
1
+ # ---- requirements
2
+ $LOAD_PATH << File.expand_path("../lib", File.dirname(__FILE__))
3
+ require 'rubygems'
4
+
5
+ FAKE_RAILS_ROOT = '/tmp/pspecs/fixtures'
6
+
7
+ require 'parallel_specs'
8
+ require 'parallel_cucumber'
9
+
10
+ def size_of(group)
11
+ group.inject(0) { |sum, test| sum += File.stat(test).size }
12
+ end
13
+
14
+ def test_tests_in_groups(klass, folder, suffix)
15
+ test_root = "#{FAKE_RAILS_ROOT}/#{folder}"
16
+
17
+ describe :tests_in_groups do
18
+ before :all do
19
+ system "rm -rf #{FAKE_RAILS_ROOT}; mkdir -p #{test_root}/temp"
20
+
21
+ @files = [0,1,2,3,4,5,6,7].map do |i|
22
+ size = 99
23
+ file = "#{test_root}/temp/x#{i}#{suffix}"
24
+ File.open(file, 'w') { |f| f.puts 'x' * size }
25
+ file
26
+ end
27
+
28
+ @log = "#{FAKE_RAILS_ROOT}/tmp/parallel_profile.log"
29
+ `mkdir #{File.dirname(@log)}`
30
+ `rm -f #{@log}`
31
+ end
32
+
33
+ it "finds all tests" do
34
+ found = klass.tests_in_groups(test_root, 1)
35
+ all = [ Dir["#{test_root}/**/*#{suffix}"] ]
36
+ (found.flatten - all.flatten).should == []
37
+ end
38
+
39
+ it "partitions them into groups by equal size" do
40
+ groups = klass.tests_in_groups(test_root, 2)
41
+ groups.size.should == 2
42
+ size_of(groups[0]).should == 400
43
+ size_of(groups[1]).should == 400
44
+ end
45
+
46
+ it 'should partition correctly with a group size of 4' do
47
+ groups = klass.tests_in_groups(test_root, 4)
48
+ groups.size.should == 4
49
+ size_of(groups[0]).should == 200
50
+ size_of(groups[1]).should == 200
51
+ size_of(groups[2]).should == 200
52
+ size_of(groups[3]).should == 200
53
+ end
54
+
55
+ it 'should partition correctly with an uneven group size' do
56
+ groups = klass.tests_in_groups(test_root, 3)
57
+ groups.size.should == 3
58
+ size_of(groups[0]).should == 300
59
+ size_of(groups[1]).should == 300
60
+ size_of(groups[2]).should == 200
61
+ end
62
+
63
+ it "partitions by runtime when runtime-data is available" do
64
+ File.open(@log,'w') do |f|
65
+ @files[1..-1].each{|file| f.puts "#{file}:#{@files.index(file)}"}
66
+ f.puts "#{@files[0]}:10"
67
+ end
68
+
69
+ groups = klass.tests_in_groups(test_root, 2)
70
+ groups.size.should == 2
71
+ # 10 + 7 = 17
72
+ groups[0].should == [@files[0],@files[7]]
73
+ # 6+5+4+3+2+1 = 21
74
+ # still room for optimization...
75
+ groups[1].should == [@files[6],@files[5],@files[4],@files[3],@files[2],@files[1]]
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,56 @@
1
+ namespace :parallel do
2
+ desc "update test databases by running db:test:prepare for each --> parallel:prepare[num_cpus]"
3
+ task :prepare, :count do |t,args|
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', "parallel_tests")
5
+
6
+ Parallel.in_processes(args[:count] ? args[:count].to_i : nil) do |i|
7
+ puts "Preparing test database #{i + 1}"
8
+ `export TEST_ENV_NUMBER=#{ParallelTests.test_env_number(i)} ; rake db:test:prepare`
9
+ end
10
+ end
11
+
12
+ # Useful when dumping/resetting takes too long
13
+ desc "update test databases by running db:mgrate for each --> parallel:migrate[num_cpus]"
14
+ task :migrate, :count do |t,args|
15
+ require File.join(File.dirname(__FILE__), '..', 'lib', "parallel_tests")
16
+
17
+ Parallel.in_processes(args[:count] ? args[:count].to_i : nil) do |i|
18
+ puts "Migrating test database #{i + 1}"
19
+ `export TEST_ENV_NUMBER=#{ParallelTests.test_env_number(i)} ; rake db:migrate RAILS_ENV=test`
20
+ end
21
+ end
22
+
23
+ ['test', 'spec', 'features'].each do |type|
24
+ desc "run #{type} in parallel with parallel:#{type}[num_cpus]"
25
+ task type, :count, :path_prefix, :options do |t,args|
26
+ require File.join(File.dirname(__FILE__), '..', 'lib', "parallel_tests")
27
+ count, prefix, options = ParallelTests.parse_rake_args(args)
28
+ sh "#{File.join(File.dirname(__FILE__), '..', 'bin', 'parallel_test')} --type #{type} -n #{count} -p '#{prefix}' -r '#{RAILS_ROOT}' -o '#{options}'"
29
+ end
30
+ end
31
+ end
32
+
33
+ #backwards compatability
34
+ #spec:parallel:prepare
35
+ #spec:parallel
36
+ #test:parallel
37
+ namespace :spec do
38
+ namespace :parallel do
39
+ task :prepare, :count do |t,args|
40
+ $stderr.puts "WARNING -- Deprecated! use parallel:prepare"
41
+ Rake::Task['parallel:prepare'].invoke(args[:count])
42
+ end
43
+ end
44
+
45
+ task :parallel, :count, :path_prefix do |t,args|
46
+ $stderr.puts "WARNING -- Deprecated! use parallel:spec"
47
+ Rake::Task['parallel:spec'].invoke(args[:count], args[:path_prefix])
48
+ end
49
+ end
50
+
51
+ namespace :test do
52
+ task :parallel, :count, :path_prefix do |t,args|
53
+ $stderr.puts "WARNING -- Deprecated! use parallel:test"
54
+ Rake::Task['parallel:test'].invoke(args[:count], args[:path_prefix])
55
+ end
56
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: parallel_tests
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 3
8
+ - 0
9
+ version: 0.3.0
10
+ platform: ruby
11
+ authors:
12
+ - Michael Grosser
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-03-02 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description:
22
+ email: grosser.michael@gmail.com
23
+ executables:
24
+ - parallel_test
25
+ - parallel_spec
26
+ - parallel_cucumber
27
+ extensions: []
28
+
29
+ extra_rdoc_files:
30
+ - README.markdown
31
+ files:
32
+ - .gitignore
33
+ - README.markdown
34
+ - Rakefile
35
+ - VERSION
36
+ - bin/parallel_cucumber
37
+ - bin/parallel_spec
38
+ - bin/parallel_test
39
+ - lib/parallel_cucumber.rb
40
+ - lib/parallel_specs.rb
41
+ - lib/parallel_specs/spec_runtime_logger.rb
42
+ - lib/parallel_tests.rb
43
+ - parallel_tests.gemspec
44
+ - spec/integration_spec.rb
45
+ - spec/parallel_cucumber_spec.rb
46
+ - spec/parallel_specs_spec.rb
47
+ - spec/parallel_tests_spec.rb
48
+ - spec/spec_helper.rb
49
+ - tasks/parallel_specs.rake
50
+ has_rdoc: true
51
+ homepage: http://github.com/grosser/parallel_tests
52
+ licenses: []
53
+
54
+ post_install_message:
55
+ rdoc_options:
56
+ - --charset=UTF-8
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ segments:
71
+ - 0
72
+ version: "0"
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.6
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: Run tests / specs / features in parallel
80
+ test_files:
81
+ - spec/spec_helper.rb
82
+ - spec/parallel_tests_spec.rb
83
+ - spec/parallel_specs_spec.rb
84
+ - spec/parallel_cucumber_spec.rb
85
+ - spec/integration_spec.rb