parallel_tests 0.6.20 → 0.7.0.alpha

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.
Files changed (40) hide show
  1. data/.gitignore +1 -0
  2. data/Gemfile +2 -4
  3. data/Gemfile.lock +7 -7
  4. data/Rakefile +18 -16
  5. data/Readme.md +15 -12
  6. data/bin/parallel_test +2 -98
  7. data/lib/parallel_tests.rb +2 -125
  8. data/lib/parallel_tests/cli.rb +102 -0
  9. data/lib/parallel_tests/cucumber/runner.rb +40 -0
  10. data/lib/parallel_tests/cucumber/runtime_logger.rb +58 -0
  11. data/lib/parallel_tests/grouper.rb +2 -2
  12. data/lib/parallel_tests/railtie.rb +3 -3
  13. data/lib/{parallel_specs/spec_failures_logger.rb → parallel_tests/spec/failures_logger.rb} +4 -3
  14. data/lib/{parallel_specs/spec_logger_base.rb → parallel_tests/spec/logger_base.rb} +7 -3
  15. data/lib/parallel_tests/spec/runner.rb +56 -0
  16. data/lib/{parallel_specs/spec_runtime_logger.rb → parallel_tests/spec/runtime_logger.rb} +2 -2
  17. data/lib/{parallel_specs/spec_summary_logger.rb → parallel_tests/spec/summary_logger.rb} +2 -2
  18. data/lib/parallel_tests/tasks.rb +0 -25
  19. data/lib/parallel_tests/test/runner.rb +126 -0
  20. data/lib/parallel_tests/test/runtime_logger.rb +92 -0
  21. data/lib/parallel_tests/version.rb +3 -0
  22. data/parallel_tests.gemspec +10 -61
  23. data/spec/parallel_tests/cucumber/runner_spec.rb +76 -0
  24. data/spec/{parallel_specs/spec_failure_logger_spec.rb → parallel_tests/spec/failure_logger_spec.rb} +8 -8
  25. data/spec/parallel_tests/spec/runner_spec.rb +178 -0
  26. data/spec/{parallel_specs/spec_runtime_logger_spec.rb → parallel_tests/spec/runtime_logger_spec.rb} +4 -4
  27. data/spec/{parallel_specs/spec_summary_logger_spec.rb → parallel_tests/spec/summary_logger_spec.rb} +2 -2
  28. data/spec/parallel_tests/test/runner_spec.rb +179 -0
  29. data/spec/parallel_tests/{runtime_logger_spec.rb → test/runtime_logger_spec.rb} +19 -16
  30. data/spec/parallel_tests_spec.rb +2 -158
  31. data/spec/spec_helper.rb +9 -7
  32. metadata +30 -26
  33. data/VERSION +0 -1
  34. data/lib/parallel_cucumber.rb +0 -36
  35. data/lib/parallel_cucumber/runtime_logger.rb +0 -57
  36. data/lib/parallel_specs.rb +0 -52
  37. data/lib/parallel_tests/runtime_logger.rb +0 -78
  38. data/lib/tasks/parallel_tests.rake +0 -1
  39. data/spec/parallel_cucumber_spec.rb +0 -72
  40. data/spec/parallel_specs_spec.rb +0 -173
@@ -0,0 +1 @@
1
+ *.sh
data/Gemfile CHANGED
@@ -1,11 +1,9 @@
1
1
  source :rubygems
2
+ gemspec
2
3
 
3
- gem 'parallel'
4
-
5
- group :dev do
4
+ group :development do
6
5
  gem 'test-unit', :platform => :ruby_19
7
6
  gem 'rspec', '>=2.4'
8
7
  gem 'cucumber'
9
8
  gem 'rake'
10
- gem 'jeweler'
11
9
  end
@@ -1,3 +1,9 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ parallel_tests (0.7.0.alpha)
5
+ parallel
6
+
1
7
  GEM
2
8
  remote: http://rubygems.org/
3
9
  specs:
@@ -11,11 +17,6 @@ GEM
11
17
  diff-lcs (1.1.2)
12
18
  gherkin (2.7.6)
13
19
  json (>= 1.4.6)
14
- git (1.2.5)
15
- jeweler (1.6.3)
16
- bundler (~> 1.0)
17
- git (>= 1.2.5)
18
- rake
19
20
  json (1.6.4)
20
21
  parallel (0.5.1)
21
22
  rake (0.8.7)
@@ -35,8 +36,7 @@ PLATFORMS
35
36
 
36
37
  DEPENDENCIES
37
38
  cucumber
38
- jeweler
39
- parallel
39
+ parallel_tests!
40
40
  rake
41
41
  rspec (>= 2.4)
42
42
  test-unit
data/Rakefile CHANGED
@@ -1,20 +1,22 @@
1
- task :default => :spec
2
- require "rspec/core/rake_task"
3
- RSpec::Core::RakeTask.new(:spec) do |t|
4
- t.rspec_opts = '--backtrace --color'
1
+ require 'bundler/gem_tasks'
2
+
3
+ task :default do
4
+ sh "rspec spec/"
5
5
  end
6
6
 
7
- begin
8
- require 'jeweler'
9
- Jeweler::Tasks.new do |gem|
10
- gem.name = "parallel_tests"
11
- gem.summary = "Run tests / specs / features in parallel"
12
- gem.email = "grosser.michael@gmail.com"
13
- gem.homepage = "http://github.com/grosser/#{gem.name}"
14
- gem.authors = "Michael Grosser"
15
- end
7
+ # extracted from https://github.com/grosser/project_template
8
+ rule /^version:bump:.*/ do |t|
9
+ sh "git status | grep 'nothing to commit'" # ensure we are not dirty
10
+ index = ['major', 'minor','patch'].index(t.name.split(':').last)
11
+ file = 'lib/GEM_NAME/version.rb'
12
+
13
+ version_file = File.read(file)
14
+ old_version, *version_parts = version_file.match(/(\d+)\.(\d+)\.(\d+)/).to_a
15
+ version_parts[index] = version_parts[index].to_i + 1
16
+ version_parts[2] = 0 if index < 2 # remove patch for minor
17
+ version_parts[1] = 0 if index < 1 # remove minor for major
18
+ new_version = version_parts * '.'
19
+ File.open(file,'w'){|f| f.write(version_file.sub(old_version, new_version)) }
16
20
 
17
- Jeweler::GemcutterTasks.new
18
- rescue LoadError
19
- puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
21
+ sh "bundle && git add #{file} Gemfile.lock && git commit -m 'bump version to #{new_version}'"
20
22
  end
data/Readme.md CHANGED
@@ -89,16 +89,16 @@ Even process runtimes
89
89
 
90
90
  Log test runtime to give each process the same runtime.
91
91
 
92
- Rspec: Add to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
92
+ Rspec: Add to your `.rspec_parallel` (or `.rspec`) :
93
93
 
94
94
  RSpec 1.x:
95
95
  --format progress
96
- --require parallel_specs/spec_runtime_logger
97
- --format ParallelSpecs::SpecRuntimeLogger:tmp/parallel_profile.log
96
+ --require parallel_tests/spec/runtime_logger
97
+ --format ParallelTests::Spec::RuntimeLogger:tmp/parallel_profile.log
98
98
  RSpec >= 2.4:
99
99
  If installed as plugin: -I vendor/plugins/parallel_tests/lib
100
100
  --format progress
101
- --format ParallelSpecs::SpecRuntimeLogger --out tmp/parallel_profile.log
101
+ --format ParallelTests::Spec::RuntimeLogger --out tmp/parallel_profile.log
102
102
 
103
103
  Test::Unit: Add to your `test_helper.rb`:
104
104
  require 'parallel_tests/runtime_logger'
@@ -109,16 +109,16 @@ SpecSummaryLogger
109
109
 
110
110
  This logger logs the test output without the different processes overwriting each other.
111
111
 
112
- Add the following to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
112
+ Add the following to your `.rspec_parallel` (or `.rspec`) :
113
113
 
114
114
  RSpec 1.x:
115
115
  --format progress
116
- --require parallel_specs/spec_summary_logger
117
- --format ParallelSpecs::SpecSummaryLogger:tmp/spec_summary.log
116
+ --require parallel_tests/spec/summary_logger
117
+ --format ParallelTests::Spec::SummaryLogger:tmp/spec_summary.log
118
118
  RSpec >= 2.2:
119
119
  If installed as plugin: -I vendor/plugins/parallel_tests/lib
120
120
  --format progress
121
- --format ParallelSpecs::SpecSummaryLogger --out tmp/spec_summary.log
121
+ --format ParallelTests::Spec::SummaryLogger --out tmp/spec_summary.log
122
122
 
123
123
  SpecFailuresLogger
124
124
  -----------------------
@@ -129,16 +129,16 @@ E.g.
129
129
 
130
130
  rspec /path/to/my_spec.rb:123 # should do something
131
131
 
132
- Add the following to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
132
+ Add the following to your `.rspec_parallel` (or `.rspec`) :
133
133
 
134
134
  RSpec 1.x:
135
135
  --format progress
136
- --require parallel_specs/spec_failures_logger
137
- --format ParallelSpecs::SpecFailuresLogger:tmp/failing_specs.log
136
+ --require parallel_tests/spec/failures_logger
137
+ --format ParallelTests::Spec::FailuresLogger:tmp/failing_specs.log
138
138
  RSpec >= 2.4:
139
139
  If installed as plugin: -I vendor/plugins/parallel_tests/lib
140
140
  --format progress
141
- --format ParallelSpecs::SpecFailuresLogger --out tmp/failing_specs.log
141
+ --format ParallelTests::Spec::FailuresLogger --out tmp/failing_specs.log
142
142
 
143
143
  Setup for non-rails
144
144
  ===================
@@ -200,6 +200,9 @@ TIPS
200
200
 
201
201
  TODO
202
202
  ====
203
+ - move everything Rails 2 related to a e.g. Rails2Readme.md and link it
204
+ - unify runtime-log location
205
+ - add tests for cucumber runtime formatter
203
206
  - make jRuby compatible [basics](http://yehudakatz.com/2009/07/01/new-rails-isolation-testing/)
204
207
  - make windows compatible
205
208
 
@@ -1,101 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- require 'optparse'
4
- require 'parallel'
5
- raise "please ' gem install parallel '" if Gem::Version.new(Parallel::VERSION) < Gem::Version.new('0.4.2')
6
2
  $LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
7
3
  require "parallel_tests"
8
-
9
- options = {}
10
- OptionParser.new do |opts|
11
- opts.banner = <<BANNER
12
- Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2', '3', ...)
13
-
14
- [optional] Only run selected files & folders:
15
- parallel_test test/bar test/baz/xxx_text.rb
16
-
17
- Options are:
18
- BANNER
19
- opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs"){|n| options[:count] = n }
20
- opts.on("-p", '--pattern [PATTERN]', "run tests matching this pattern"){|pattern| options[:pattern] = pattern }
21
- opts.on("--no-sort", "do not sort files before running them"){ |no_sort| options[:no_sort] = no_sort }
22
- opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run"){ |multiply| options[:multiply] = multiply }
23
- opts.on("-r", '--root [PATH]', "execute test commands from this path"){|path| options[:root] = path }
24
- opts.on("-s [PATTERN]", "--single [PATTERN]", "Run all matching files in only one process") do |pattern|
25
- options[:single_process] ||= []
26
- options[:single_process] << /#{pattern}/
27
- end
28
- opts.on("-e", '--exec [COMMAND]', "execute this code parallel and with ENV['TEST_ENV_NUM']"){|path| options[:execute] = path }
29
- opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options"){|arg| options[:test_options] = arg }
30
- opts.on("-t", "--type [TYPE]", "which type of tests to run? test, spec or features"){|type| options[:type] = type }
31
- opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec"){ options[:non_parallel] = true }
32
- opts.on("--chunk-timeout [TIMEOUT]", "timeout before re-printing the output of a child-process"){|timeout| options[:chunk_timeout] = timeout.to_f }
33
- opts.on('-v', '--version', 'Show Version'){ puts ParallelTests::VERSION; exit}
34
- opts.on("-h", "--help", "Show this.") { puts opts; exit }
35
- end.parse!
36
-
37
- raise "--no-sort and --single-process are not supported" if options[:no_sort] and options[:single_process]
38
-
39
- # get files to run from arguments
40
- options[:files] = ARGV if ARGV.size > 0
41
-
42
- num_processes = ParallelTests.determine_number_of_processes(options[:count])
43
- num_processes = num_processes * (options[:multiply] || 1)
44
-
45
- if options[:execute]
46
- runs = (0...num_processes).to_a
47
- results = if options[:non_parallel]
48
- runs.map do |i|
49
- ParallelTests.execute_command(options[:execute], i, options)
50
- end
51
- else
52
- Parallel.map(runs, :in_processes => num_processes) do |i|
53
- ParallelTests.execute_command(options[:execute], i, options)
54
- end
55
- end.flatten
56
- abort if results.any?{|r| r[:exit_status] != 0 }
57
- else
58
- lib, name, task = {
59
- 'test' => ["tests", "test", "test"],
60
- 'spec' => ["specs", "spec", "spec"],
61
- 'features' => ["cucumber", "feature", "features"]
62
- }[options[:type]||'test']
63
-
64
- require "parallel_#{lib}"
65
- klass = eval("Parallel#{lib.capitalize}")
66
-
67
- start = Time.now
68
-
69
- tests_folder = task
70
- tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
71
-
72
- groups = klass.tests_in_groups(options[:files] || tests_folder, num_processes, options)
73
- num_processes = groups.size
74
-
75
- #adjust processes to groups
76
- abort "no #{name}s found!" if groups.size == 0
77
-
78
- num_tests = groups.inject(0){|sum,item| sum + item.size }
79
- puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
80
-
81
- test_results = Parallel.map(groups, :in_processes => num_processes) do |group|
82
- if group.empty?
83
- {:stdout => '', :exit_status => 0}
84
- else
85
- klass.run_tests(group, groups.index(group), options)
86
- end
87
- end
88
-
89
- #parse and print results
90
- results = klass.find_results(test_results.map{|result| result[:stdout] }*"")
91
- puts ""
92
- puts klass.summarize_results(results)
93
-
94
- #report total time taken
95
- puts ""
96
- puts "Took #{Time.now - start} seconds"
97
-
98
- #exit with correct status code so rake parallel:test && echo 123 works
99
- failed = test_results.any?{|result| result[:exit_status] != 0 }
100
- abort "#{name.capitalize}s Failed" if failed
101
- end
4
+ require "parallel_tests/cli"
5
+ ParallelTest::CLI.run(ARGV)
@@ -1,10 +1,8 @@
1
1
  require 'parallel'
2
+ require 'parallel_tests/version'
2
3
  require 'parallel_tests/grouper'
3
- require 'parallel_tests/railtie'
4
-
5
- class ParallelTests
6
- VERSION = File.read( File.join(File.dirname(__FILE__),'..','VERSION') ).strip
7
4
 
5
+ module ParallelTests
8
6
  def self.determine_number_of_processes(count)
9
7
  [
10
8
  count,
@@ -29,88 +27,6 @@ class ParallelTests
29
27
  [num_processes, pattern.to_s, options.to_s]
30
28
  end
31
29
 
32
- # finds all tests and partitions them into groups
33
- def self.tests_in_groups(root, num_groups, options={})
34
- tests = find_tests(root, options)
35
- if options[:no_sort] == true
36
- Grouper.in_groups(tests, num_groups)
37
- else
38
- tests = with_runtime_info(tests)
39
- Grouper.in_even_groups_by_size(tests, num_groups, options)
40
- end
41
- end
42
-
43
- def self.run_tests(test_files, process_number, options)
44
- require_list = test_files.map { |filename| %{"#{File.expand_path filename}"} }.join(",")
45
- cmd = "ruby -Itest -e '[#{require_list}].each {|f| require f }' -- #{options[:test_options]}"
46
- execute_command(cmd, process_number, options)
47
- end
48
-
49
- def self.execute_command(cmd, process_number, options)
50
- cmd = "TEST_ENV_NUMBER=#{test_env_number(process_number)} ; export TEST_ENV_NUMBER; #{cmd}"
51
- f = open("|#{cmd}", 'r')
52
- output = fetch_output(f, options)
53
- f.close
54
- {:stdout => output, :exit_status => $?.exitstatus}
55
- end
56
-
57
- def self.find_results(test_output)
58
- test_output.split("\n").map {|line|
59
- line = line.gsub(/\.|F|\*/,'')
60
- next unless line_is_result?(line)
61
- line
62
- }.compact
63
- end
64
-
65
- def self.test_env_number(process_number)
66
- process_number == 0 ? '' : process_number + 1
67
- end
68
-
69
- def self.runtime_log
70
- 'tmp/parallel_runtime_test.log'
71
- end
72
-
73
- def self.summarize_results(results)
74
- results = results.join(' ').gsub(/s\b/,'') # combine and singularize results
75
- counts = results.scan(/(\d+) (\w+)/)
76
- sums = counts.inject(Hash.new(0)) do |sum, (number, word)|
77
- sum[word] += number.to_i
78
- sum
79
- end
80
- sums.sort.map{|word, number| "#{number} #{word}#{'s' if number != 1}" }.join(', ')
81
- end
82
-
83
- protected
84
-
85
- # read output of the process and print in in chucks
86
- def self.fetch_output(process, options)
87
- all = ''
88
- buffer = ''
89
- timeout = options[:chunk_timeout] || 0.2
90
- flushed = Time.now.to_f
91
-
92
- while char = process.getc
93
- char = (char.is_a?(Fixnum) ? char.chr : char) # 1.8 <-> 1.9
94
- all << char
95
-
96
- # print in chunks so large blocks stay together
97
- now = Time.now.to_f
98
- buffer << char
99
- if flushed + timeout < now
100
- $stdout.print buffer
101
- $stdout.flush
102
- buffer = ''
103
- flushed = now
104
- end
105
- end
106
-
107
- # print the remainder
108
- $stdout.print buffer
109
- $stdout.flush
110
-
111
- all
112
- end
113
-
114
30
  # copied from http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile
115
31
  def self.bundler_enabled?
116
32
  return true if Object.const_defined?(:Bundler)
@@ -126,43 +42,4 @@ class ParallelTests
126
42
 
127
43
  false
128
44
  end
129
-
130
- def self.line_is_result?(line)
131
- line =~ /\d+ failure/
132
- end
133
-
134
- def self.test_suffix
135
- "_test.rb"
136
- end
137
-
138
- def self.with_runtime_info(tests)
139
- lines = File.read(runtime_log).split("\n") rescue []
140
-
141
- # use recorded test runtime if we got enough data
142
- if lines.size * 1.5 > tests.size
143
- puts "Using recorded test runtime"
144
- times = Hash.new(1)
145
- lines.each do |line|
146
- test, time = line.split(":")
147
- next unless test and time
148
- times[File.expand_path(test)] = time.to_f
149
- end
150
- tests.sort.map{|test| [test, times[test]] }
151
- else # use file sizes
152
- tests.sort.map{|test| [test, File.stat(test).size] }
153
- end
154
- end
155
-
156
- def self.find_tests(root, options={})
157
- if root.is_a?(Array)
158
- root
159
- else
160
- # follow one symlink and direct children
161
- # http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
162
- files = Dir["#{root}/**{,/*/**}/*#{test_suffix}"].uniq
163
- files = files.map{|f| f.sub(root+'/','') }
164
- files = files.grep(/#{options[:pattern]}/)
165
- files.map{|f| "#{root}/#{f}" }
166
- end
167
- end
168
45
  end
@@ -0,0 +1,102 @@
1
+ require 'optparse'
2
+ require 'parallel_tests/test/runner'
3
+
4
+ module ParallelTest
5
+ module CLI
6
+ def self.run(argv)
7
+ options = {}
8
+ OptionParser.new do |opts|
9
+ opts.banner = <<BANNER
10
+ Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2', '3', ...)
11
+
12
+ [optional] Only run selected files & folders:
13
+ parallel_test test/bar test/baz/xxx_text.rb
14
+
15
+ Options are:
16
+ BANNER
17
+ opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs") { |n| options[:count] = n }
18
+ opts.on("-p", '--pattern [PATTERN]', "run tests matching this pattern") { |pattern| options[:pattern] = pattern }
19
+ opts.on("--no-sort", "do not sort files before running them") { |no_sort| options[:no_sort] = no_sort }
20
+ opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run") { |multiply| options[:multiply] = multiply }
21
+ opts.on("-r", '--root [PATH]', "execute test commands from this path") { |path| options[:root] = path }
22
+ opts.on("-s [PATTERN]", "--single [PATTERN]", "Run all matching files in only one process") do |pattern|
23
+ options[:single_process] ||= []
24
+ options[:single_process] << /#{pattern}/
25
+ end
26
+ opts.on("-e", '--exec [COMMAND]', "execute this code parallel and with ENV['TEST_ENV_NUM']") { |path| options[:execute] = path }
27
+ opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = arg }
28
+ opts.on("-t", "--type [TYPE]", "which type of tests to run? test, spec or features") { |type| options[:type] = type }
29
+ opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec") { options[:non_parallel] = true }
30
+ opts.on("--chunk-timeout [TIMEOUT]", "timeout before re-printing the output of a child-process") { |timeout| options[:chunk_timeout] = timeout.to_f }
31
+ opts.on('-v', '--version', 'Show Version') { puts ParallelTests::VERSION; exit }
32
+ opts.on("-h", "--help", "Show this.") { puts opts; exit }
33
+ end.parse!(argv)
34
+
35
+ raise "--no-sort and --single-process are not supported" if options[:no_sort] and options[:single_process]
36
+
37
+ # get files to run from arguments
38
+ options[:files] = argv if argv.size > 0
39
+
40
+ num_processes = ParallelTests.determine_number_of_processes(options[:count])
41
+ num_processes = num_processes * (options[:multiply] || 1)
42
+
43
+ if options[:execute]
44
+ runs = (0...num_processes).to_a
45
+ results = if options[:non_parallel]
46
+ runs.map do |i|
47
+ ParallelTests::Test::Runner.execute_command(options[:execute], i, options)
48
+ end
49
+ else
50
+ Parallel.map(runs, :in_processes => num_processes) do |i|
51
+ ParallelTests::Test::Runner.execute_command(options[:execute], i, options)
52
+ end
53
+ end.flatten
54
+ abort if results.any? { |r| r[:exit_status] != 0 }
55
+ else
56
+ lib, name, task = {
57
+ 'test' => ["test", "test", "test"],
58
+ 'spec' => ["spec", "spec", "spec"],
59
+ 'features' => ["cucumber", "feature", "features"]
60
+ }[options[:type]||'test']
61
+
62
+ require "parallel_tests/#{lib}/runner"
63
+ klass = eval("ParallelTests::#{lib.capitalize}::Runner")
64
+
65
+ start = Time.now
66
+
67
+ tests_folder = task
68
+ tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
69
+
70
+ groups = klass.tests_in_groups(options[:files] || tests_folder, num_processes, options)
71
+ num_processes = groups.size
72
+
73
+ #adjust processes to groups
74
+ abort "no #{name}s found!" if groups.size == 0
75
+
76
+ num_tests = groups.inject(0) { |sum, item| sum + item.size }
77
+ puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
78
+
79
+ test_results = Parallel.map(groups, :in_processes => num_processes) do |group|
80
+ if group.empty?
81
+ {:stdout => '', :exit_status => 0}
82
+ else
83
+ klass.run_tests(group, groups.index(group), options)
84
+ end
85
+ end
86
+
87
+ #parse and print results
88
+ results = klass.find_results(test_results.map { |result| result[:stdout] }*"")
89
+ puts ""
90
+ puts klass.summarize_results(results)
91
+
92
+ #report total time taken
93
+ puts ""
94
+ puts "Took #{Time.now - start} seconds"
95
+
96
+ #exit with correct status code so rake parallel:test && echo 123 works
97
+ failed = test_results.any? { |result| result[:exit_status] != 0 }
98
+ abort "#{name.capitalize}s Failed" if failed
99
+ end
100
+ end
101
+ end
102
+ end