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.
- data/.gitignore +1 -0
- data/Gemfile +2 -4
- data/Gemfile.lock +7 -7
- data/Rakefile +18 -16
- data/Readme.md +15 -12
- data/bin/parallel_test +2 -98
- data/lib/parallel_tests.rb +2 -125
- data/lib/parallel_tests/cli.rb +102 -0
- data/lib/parallel_tests/cucumber/runner.rb +40 -0
- data/lib/parallel_tests/cucumber/runtime_logger.rb +58 -0
- data/lib/parallel_tests/grouper.rb +2 -2
- data/lib/parallel_tests/railtie.rb +3 -3
- data/lib/{parallel_specs/spec_failures_logger.rb → parallel_tests/spec/failures_logger.rb} +4 -3
- data/lib/{parallel_specs/spec_logger_base.rb → parallel_tests/spec/logger_base.rb} +7 -3
- data/lib/parallel_tests/spec/runner.rb +56 -0
- data/lib/{parallel_specs/spec_runtime_logger.rb → parallel_tests/spec/runtime_logger.rb} +2 -2
- data/lib/{parallel_specs/spec_summary_logger.rb → parallel_tests/spec/summary_logger.rb} +2 -2
- data/lib/parallel_tests/tasks.rb +0 -25
- data/lib/parallel_tests/test/runner.rb +126 -0
- data/lib/parallel_tests/test/runtime_logger.rb +92 -0
- data/lib/parallel_tests/version.rb +3 -0
- data/parallel_tests.gemspec +10 -61
- data/spec/parallel_tests/cucumber/runner_spec.rb +76 -0
- data/spec/{parallel_specs/spec_failure_logger_spec.rb → parallel_tests/spec/failure_logger_spec.rb} +8 -8
- data/spec/parallel_tests/spec/runner_spec.rb +178 -0
- data/spec/{parallel_specs/spec_runtime_logger_spec.rb → parallel_tests/spec/runtime_logger_spec.rb} +4 -4
- data/spec/{parallel_specs/spec_summary_logger_spec.rb → parallel_tests/spec/summary_logger_spec.rb} +2 -2
- data/spec/parallel_tests/test/runner_spec.rb +179 -0
- data/spec/parallel_tests/{runtime_logger_spec.rb → test/runtime_logger_spec.rb} +19 -16
- data/spec/parallel_tests_spec.rb +2 -158
- data/spec/spec_helper.rb +9 -7
- metadata +30 -26
- data/VERSION +0 -1
- data/lib/parallel_cucumber.rb +0 -36
- data/lib/parallel_cucumber/runtime_logger.rb +0 -57
- data/lib/parallel_specs.rb +0 -52
- data/lib/parallel_tests/runtime_logger.rb +0 -78
- data/lib/tasks/parallel_tests.rake +0 -1
- data/spec/parallel_cucumber_spec.rb +0 -72
- data/spec/parallel_specs_spec.rb +0 -173
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*.sh
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -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
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
task :default do
|
4
|
+
sh "rspec spec/"
|
5
5
|
end
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
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 `
|
92
|
+
Rspec: Add to your `.rspec_parallel` (or `.rspec`) :
|
93
93
|
|
94
94
|
RSpec 1.x:
|
95
95
|
--format progress
|
96
|
-
--require
|
97
|
-
--format
|
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
|
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 `
|
112
|
+
Add the following to your `.rspec_parallel` (or `.rspec`) :
|
113
113
|
|
114
114
|
RSpec 1.x:
|
115
115
|
--format progress
|
116
|
-
--require
|
117
|
-
--format
|
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
|
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 `
|
132
|
+
Add the following to your `.rspec_parallel` (or `.rspec`) :
|
133
133
|
|
134
134
|
RSpec 1.x:
|
135
135
|
--format progress
|
136
|
-
--require
|
137
|
-
--format
|
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
|
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
|
|
data/bin/parallel_test
CHANGED
@@ -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
|
-
|
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)
|
data/lib/parallel_tests.rb
CHANGED
@@ -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
|