parallel_tests 0.7.0.alpha → 0.7.0.alpha2
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/Gemfile.lock +1 -1
- data/Readme.md +21 -55
- data/ReadmeRails2.md +44 -0
- data/bin/parallel_cucumber +1 -1
- data/lib/parallel_tests/cli.rb +65 -60
- data/lib/parallel_tests/cucumber/runner.rb +3 -1
- data/lib/parallel_tests/spec/runner.rb +9 -5
- data/lib/parallel_tests/tasks.rb +2 -1
- data/lib/parallel_tests/test/runner.rb +46 -31
- data/lib/parallel_tests/version.rb +1 -1
- data/parallel_tests.gemspec +1 -1
- data/spec/integration_spec.rb +39 -19
- data/spec/parallel_tests/test/runner_spec.rb +20 -8
- data/spec/spec_helper.rb +13 -13
- metadata +7 -6
data/Gemfile.lock
CHANGED
data/Readme.md
CHANGED
@@ -3,8 +3,9 @@ ParallelTests splits tests into even groups(by number of tests or runtime) and r
|
|
3
3
|
|
4
4
|
Setup for Rails
|
5
5
|
===============
|
6
|
+
[still using Rails 2?](https://github.com/grosser/parallel_tests/blob/master/ReadmeRails2.md)
|
7
|
+
|
6
8
|
## Install
|
7
|
-
### Rails 3
|
8
9
|
If you use RSpec: ensure you got >= 2.4
|
9
10
|
|
10
11
|
As gem
|
@@ -19,28 +20,6 @@ OR as plugin
|
|
19
20
|
# add to Gemfile
|
20
21
|
gem "parallel", :group => :development
|
21
22
|
|
22
|
-
|
23
|
-
### Rails 2
|
24
|
-
|
25
|
-
As gem
|
26
|
-
|
27
|
-
gem install parallel_tests
|
28
|
-
|
29
|
-
# add to config/environments/development.rb
|
30
|
-
config.gem "parallel_tests"
|
31
|
-
|
32
|
-
# add to Rakefile
|
33
|
-
begin; require 'parallel_tests/tasks'; rescue LoadError; end
|
34
|
-
|
35
|
-
OR as plugin
|
36
|
-
|
37
|
-
gem install parallel
|
38
|
-
|
39
|
-
# add to config/environments/development.rb
|
40
|
-
config.gem "parallel"
|
41
|
-
|
42
|
-
./script/plugin install git://github.com/grosser/parallel_tests.git
|
43
|
-
|
44
23
|
## Setup
|
45
24
|
ParallelTests uses 1 database per test-process, 2 processes will use `*_test` and `*_test2`.
|
46
25
|
|
@@ -91,17 +70,14 @@ Log test runtime to give each process the same runtime.
|
|
91
70
|
|
92
71
|
Rspec: Add to your `.rspec_parallel` (or `.rspec`) :
|
93
72
|
|
94
|
-
RSpec
|
95
|
-
--format progress
|
96
|
-
--require parallel_tests/spec/runtime_logger
|
97
|
-
--format ParallelTests::Spec::RuntimeLogger:tmp/parallel_profile.log
|
98
|
-
RSpec >= 2.4:
|
73
|
+
RSpec
|
99
74
|
If installed as plugin: -I vendor/plugins/parallel_tests/lib
|
100
75
|
--format progress
|
101
76
|
--format ParallelTests::Spec::RuntimeLogger --out tmp/parallel_profile.log
|
102
77
|
|
103
78
|
Test::Unit: Add to your `test_helper.rb`:
|
104
|
-
|
79
|
+
|
80
|
+
require 'parallel_tests/test/runtime_logger'
|
105
81
|
|
106
82
|
|
107
83
|
SpecSummaryLogger
|
@@ -111,11 +87,7 @@ This logger logs the test output without the different processes overwriting eac
|
|
111
87
|
|
112
88
|
Add the following to your `.rspec_parallel` (or `.rspec`) :
|
113
89
|
|
114
|
-
RSpec
|
115
|
-
--format progress
|
116
|
-
--require parallel_tests/spec/summary_logger
|
117
|
-
--format ParallelTests::Spec::SummaryLogger:tmp/spec_summary.log
|
118
|
-
RSpec >= 2.2:
|
90
|
+
RSpec:
|
119
91
|
If installed as plugin: -I vendor/plugins/parallel_tests/lib
|
120
92
|
--format progress
|
121
93
|
--format ParallelTests::Spec::SummaryLogger --out tmp/spec_summary.log
|
@@ -131,23 +103,21 @@ E.g.
|
|
131
103
|
|
132
104
|
Add the following to your `.rspec_parallel` (or `.rspec`) :
|
133
105
|
|
134
|
-
RSpec
|
135
|
-
--format progress
|
136
|
-
--require parallel_tests/spec/failures_logger
|
137
|
-
--format ParallelTests::Spec::FailuresLogger:tmp/failing_specs.log
|
138
|
-
RSpec >= 2.4:
|
106
|
+
RSpec:
|
139
107
|
If installed as plugin: -I vendor/plugins/parallel_tests/lib
|
140
108
|
--format progress
|
141
109
|
--format ParallelTests::Spec::FailuresLogger --out tmp/failing_specs.log
|
142
110
|
|
143
111
|
Setup for non-rails
|
144
112
|
===================
|
145
|
-
|
113
|
+
gem install parallel_tests
|
146
114
|
# go to your project dir
|
147
|
-
parallel_test
|
148
|
-
|
115
|
+
parallel_test test/
|
116
|
+
parallel_spec spec/
|
117
|
+
parallel_cucumber features/
|
149
118
|
|
150
|
-
[
|
119
|
+
- use ENV['TEST_ENV_NUMBER'] inside your tests to select separate db/memcache/etc.
|
120
|
+
- Only run selected files & folders:
|
151
121
|
|
152
122
|
parallel_test test/bar test/baz/foo_text.rb
|
153
123
|
|
@@ -157,15 +127,14 @@ Options are:
|
|
157
127
|
-p, --path [PATH] run tests inside this path only
|
158
128
|
--no-sort do not sort files before running them
|
159
129
|
-m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
|
160
|
-
-r, --root [PATH] execute test commands from this path
|
161
130
|
-e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUM']
|
162
131
|
-o, --test-options '[OPTIONS]' execute test commands with those options
|
163
|
-
-t, --type [TYPE]
|
132
|
+
-t, --type [TYPE] test(default) / spec / cucumber
|
164
133
|
--non-parallel execute same commands but do not in parallel, needs --exec
|
165
134
|
-v, --version Show Version
|
166
135
|
-h, --help Show this.
|
167
136
|
|
168
|
-
You can run any kind of code with -e / --execute
|
137
|
+
You can run any kind of code in parallel with -e / --execute
|
169
138
|
|
170
139
|
parallel_test -n 5 -e 'ruby -e "puts %[hello from process #{ENV[:TEST_ENV_NUMBER.to_s].inspect}]"'
|
171
140
|
hello from process "2"
|
@@ -183,11 +152,9 @@ You can run any kind of code with -e / --execute
|
|
183
152
|
TIPS
|
184
153
|
====
|
185
154
|
- [Capybara + Selenium] add to env.rb: `Capybara.server_port = 8888 + ENV['TEST_ENV_NUMBER'].to_i`
|
186
|
-
- [RSpec] add a `
|
187
|
-
- [RSpec]
|
188
|
-
- [RSpec]
|
189
|
-
- [RSpec] 'script/spec_server' or [spork](http://github.com/timcharper/spork/tree/master) do not work in parallel
|
190
|
-
- [RSpec] `./script/generate rspec` if you are running rspec from gems (this plugin uses script/spec which may fail if rspec files are outdated)
|
155
|
+
- [RSpec] add a `.rspec_parallel` to use different options, e.g. **no --drb**
|
156
|
+
- [RSpec] delete `script/spec`
|
157
|
+
- [RSpec] [spork](https://github.com/timcharper/spork) does not work in parallel
|
191
158
|
- [RSpec] remove --loadby from you spec/*.opts
|
192
159
|
- [Bundler] if you have a `Gemfile` then `bundle exec` will be used to run tests
|
193
160
|
- [Capybara setup](https://github.com/grosser/parallel_tests/wiki)
|
@@ -196,11 +163,11 @@ TIPS
|
|
196
163
|
- [SQL schema format] use :ruby schema format to get faster parallel:prepare`
|
197
164
|
- [ActiveRecord] if you do not have `db:abort_if_pending_migrations` add this to your Rakefile: `task('db:abort_if_pending_migrations'){}`
|
198
165
|
- `export PARALLEL_TEST_PROCESSORS=X` in your environment and parallel_tests will use this number of processors by default
|
199
|
-
-
|
166
|
+
- [ZSH] use quotes to use rake arguments `rake "parallel:prepare[3]"`
|
200
167
|
|
201
168
|
TODO
|
202
169
|
====
|
203
|
-
-
|
170
|
+
- document how to use cucumber runtime logger
|
204
171
|
- unify runtime-log location
|
205
172
|
- add tests for cucumber runtime formatter
|
206
173
|
- make jRuby compatible [basics](http://yehudakatz.com/2009/07/01/new-rails-isolation-testing/)
|
@@ -240,5 +207,4 @@ inspired by [pivotal labs](http://pivotallabs.com/users/miked/blog/articles/849-
|
|
240
207
|
|
241
208
|
[Michael Grosser](http://grosser.it)<br/>
|
242
209
|
michael@grosser.it<br/>
|
243
|
-
|
244
|
-
[](https://flattr.com/submit/auto?user_id=grosser&url=https://github.com/grosser/parallel_tests&title=parallel_tests&language=en_GB&tags=github&category=software)
|
210
|
+
License: MIT
|
data/ReadmeRails2.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
### Install
|
2
|
+
|
3
|
+
As gem
|
4
|
+
|
5
|
+
gem install parallel_tests
|
6
|
+
|
7
|
+
# add to config/environments/development.rb
|
8
|
+
config.gem "parallel_tests"
|
9
|
+
|
10
|
+
# add to Rakefile
|
11
|
+
begin; require 'parallel_tests/tasks'; rescue LoadError; end
|
12
|
+
|
13
|
+
OR as plugin
|
14
|
+
|
15
|
+
gem install parallel
|
16
|
+
|
17
|
+
# add to config/environments/development.rb
|
18
|
+
config.gem "parallel"
|
19
|
+
|
20
|
+
./script/plugin install git://github.com/grosser/parallel_tests.git
|
21
|
+
|
22
|
+
Even process runtimes
|
23
|
+
-----------------
|
24
|
+
|
25
|
+
RSpec 1.x:
|
26
|
+
--format progress
|
27
|
+
--require parallel_tests/spec/runtime_logger
|
28
|
+
--format ParallelTests::Spec::RuntimeLogger:tmp/parallel_profile.log
|
29
|
+
|
30
|
+
SpecSummaryLogger
|
31
|
+
--------------------
|
32
|
+
|
33
|
+
RSpec 1.x:
|
34
|
+
--format progress
|
35
|
+
--require parallel_tests/spec/summary_logger
|
36
|
+
--format ParallelTests::Spec::SummaryLogger:tmp/spec_summary.log
|
37
|
+
|
38
|
+
SpecFailuresLogger
|
39
|
+
-----------------------
|
40
|
+
|
41
|
+
RSpec 1.x:
|
42
|
+
--format progress
|
43
|
+
--require parallel_tests/spec/failures_logger
|
44
|
+
--format ParallelTests::Spec::FailuresLogger:tmp/failing_specs.log
|
data/bin/parallel_cucumber
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
exec "#{File.join(File.dirname(__FILE__), 'parallel_test')} -t
|
2
|
+
exec "#{File.join(File.dirname(__FILE__), 'parallel_test')} -t cucumber #{ARGV * ' '}"
|
data/lib/parallel_tests/cli.rb
CHANGED
@@ -4,6 +4,51 @@ require 'parallel_tests/test/runner'
|
|
4
4
|
module ParallelTest
|
5
5
|
module CLI
|
6
6
|
def self.run(argv)
|
7
|
+
options = parse_options!(argv)
|
8
|
+
test_results = nil
|
9
|
+
|
10
|
+
num_processes = ParallelTests.determine_number_of_processes(options[:count])
|
11
|
+
num_processes = num_processes * (options[:multiply] || 1)
|
12
|
+
|
13
|
+
if options[:execute]
|
14
|
+
execute_shell_command_in_parallel(options[:execute], num_processes, options)
|
15
|
+
else
|
16
|
+
lib = options[:type] || 'test'
|
17
|
+
require "parallel_tests/#{lib}/runner"
|
18
|
+
runner = eval("ParallelTests::#{lib.capitalize}::Runner")
|
19
|
+
name = runner.test_file_name
|
20
|
+
|
21
|
+
report_time_taken do
|
22
|
+
groups = runner.tests_in_groups(options[:files], num_processes, options)
|
23
|
+
abort "no #{name}s found!" if groups.size == 0
|
24
|
+
|
25
|
+
num_processes = groups.size
|
26
|
+
num_tests = groups.inject(0) { |sum, item| sum + item.size }
|
27
|
+
puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
|
28
|
+
|
29
|
+
test_results = Parallel.map(groups, :in_processes => num_processes) do |group|
|
30
|
+
if group.empty?
|
31
|
+
{:stdout => '', :exit_status => 0}
|
32
|
+
else
|
33
|
+
runner.run_tests(group, groups.index(group), options)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#parse and print results
|
38
|
+
results = runner.find_results(test_results.map { |result| result[:stdout] }*"")
|
39
|
+
puts ""
|
40
|
+
puts runner.summarize_results(results)
|
41
|
+
end
|
42
|
+
|
43
|
+
#exit with correct status code so rake parallel:test && echo 123 works
|
44
|
+
failed = test_results.any? { |result| result[:exit_status] != 0 }
|
45
|
+
abort "#{lib.capitalize}s Failed" if failed
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def self.parse_options!(argv)
|
7
52
|
options = {}
|
8
53
|
OptionParser.new do |opts|
|
9
54
|
opts.banner = <<BANNER
|
@@ -18,14 +63,13 @@ BANNER
|
|
18
63
|
opts.on("-p", '--pattern [PATTERN]', "run tests matching this pattern") { |pattern| options[:pattern] = pattern }
|
19
64
|
opts.on("--no-sort", "do not sort files before running them") { |no_sort| options[:no_sort] = no_sort }
|
20
65
|
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
66
|
opts.on("-s [PATTERN]", "--single [PATTERN]", "Run all matching files in only one process") do |pattern|
|
23
67
|
options[:single_process] ||= []
|
24
68
|
options[:single_process] << /#{pattern}/
|
25
69
|
end
|
26
70
|
opts.on("-e", '--exec [COMMAND]', "execute this code parallel and with ENV['TEST_ENV_NUM']") { |path| options[:execute] = path }
|
27
71
|
opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = arg }
|
28
|
-
opts.on("-t", "--type [TYPE]", "
|
72
|
+
opts.on("-t", "--type [TYPE]", "test(default) / spec / cucumber") { |type| options[:type] = type }
|
29
73
|
opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec") { options[:non_parallel] = true }
|
30
74
|
opts.on("--chunk-timeout [TIMEOUT]", "timeout before re-printing the output of a child-process") { |timeout| options[:chunk_timeout] = timeout.to_f }
|
31
75
|
opts.on('-v', '--version', 'Show Version') { puts ParallelTests::VERSION; exit }
|
@@ -34,69 +78,30 @@ BANNER
|
|
34
78
|
|
35
79
|
raise "--no-sort and --single-process are not supported" if options[:no_sort] and options[:single_process]
|
36
80
|
|
37
|
-
|
38
|
-
options
|
39
|
-
|
40
|
-
num_processes = ParallelTests.determine_number_of_processes(options[:count])
|
41
|
-
num_processes = num_processes * (options[:multiply] || 1)
|
81
|
+
options[:files] = argv
|
82
|
+
options
|
83
|
+
end
|
42
84
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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 }
|
85
|
+
def self.execute_shell_command_in_parallel(command, num_processes, options)
|
86
|
+
runs = (0...num_processes).to_a
|
87
|
+
results = if options[:non_parallel]
|
88
|
+
runs.map do |i|
|
89
|
+
ParallelTests::Test::Runner.execute_command(command, i, options)
|
90
|
+
end
|
55
91
|
else
|
56
|
-
|
57
|
-
|
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
|
92
|
+
Parallel.map(runs, :in_processes => num_processes) do |i|
|
93
|
+
ParallelTests::Test::Runner.execute_command(command, i, options)
|
85
94
|
end
|
95
|
+
end.flatten
|
86
96
|
|
87
|
-
|
88
|
-
|
89
|
-
puts ""
|
90
|
-
puts klass.summarize_results(results)
|
91
|
-
|
92
|
-
#report total time taken
|
93
|
-
puts ""
|
94
|
-
puts "Took #{Time.now - start} seconds"
|
97
|
+
abort if results.any? { |r| r[:exit_status] != 0 }
|
98
|
+
end
|
95
99
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
+
def self.report_time_taken
|
101
|
+
start = Time.now
|
102
|
+
yield
|
103
|
+
puts ""
|
104
|
+
puts "Took #{Time.now - start} seconds"
|
100
105
|
end
|
101
106
|
end
|
102
107
|
end
|
@@ -27,7 +27,15 @@ module ParallelTests
|
|
27
27
|
'tmp/parallel_profile.log'
|
28
28
|
end
|
29
29
|
|
30
|
-
|
30
|
+
def self.test_file_name
|
31
|
+
"spec"
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.test_suffix
|
35
|
+
"_spec.rb"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
31
39
|
|
32
40
|
# so it can be stubbed....
|
33
41
|
def self.run(cmd)
|
@@ -47,10 +55,6 @@ module ParallelTests
|
|
47
55
|
return unless options_file
|
48
56
|
"-O #{options_file}"
|
49
57
|
end
|
50
|
-
|
51
|
-
def self.test_suffix
|
52
|
-
"_spec.rb"
|
53
|
-
end
|
54
58
|
end
|
55
59
|
end
|
56
60
|
end
|
data/lib/parallel_tests/tasks.rb
CHANGED
@@ -47,8 +47,9 @@ namespace :parallel do
|
|
47
47
|
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
48
48
|
require "parallel_tests"
|
49
49
|
count, pattern, options = ParallelTests.parse_rake_args(args)
|
50
|
+
test_type = (type == 'features' ? 'cucumber' : type)
|
50
51
|
executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
|
51
|
-
command = "#{executable} --type #{
|
52
|
+
command = "#{executable} '#{Rails.root}/#{type}' --type #{test_type} -n #{count} -p '#{pattern}' -o '#{options}'"
|
52
53
|
abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
|
53
54
|
end
|
54
55
|
end
|
@@ -1,9 +1,36 @@
|
|
1
1
|
module ParallelTests
|
2
2
|
module Test
|
3
3
|
class Runner
|
4
|
+
# --- usually overwritten by other runners
|
5
|
+
|
6
|
+
def self.runtime_log
|
7
|
+
'tmp/parallel_runtime_test.log'
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.test_suffix
|
11
|
+
"_test.rb"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.test_file_name
|
15
|
+
"test"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.run_tests(test_files, process_number, options)
|
19
|
+
require_list = test_files.map { |filename| %{"#{File.expand_path filename}"} }.join(",")
|
20
|
+
cmd = "ruby -Itest -e '[#{require_list}].each {|f| require f }' -- #{options[:test_options]}"
|
21
|
+
execute_command(cmd, process_number, options)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.line_is_result?(line)
|
25
|
+
line =~ /\d+ failure/
|
26
|
+
end
|
27
|
+
|
28
|
+
# --- usually used by other runners
|
29
|
+
|
4
30
|
# finds all tests and partitions them into groups
|
5
|
-
def self.tests_in_groups(
|
6
|
-
tests = find_tests(
|
31
|
+
def self.tests_in_groups(tests, num_groups, options={})
|
32
|
+
tests = find_tests(tests, options)
|
33
|
+
|
7
34
|
if options[:no_sort] == true
|
8
35
|
Grouper.in_groups(tests, num_groups)
|
9
36
|
else
|
@@ -12,12 +39,6 @@ module ParallelTests
|
|
12
39
|
end
|
13
40
|
end
|
14
41
|
|
15
|
-
def self.run_tests(test_files, process_number, options)
|
16
|
-
require_list = test_files.map { |filename| %{"#{File.expand_path filename}"} }.join(",")
|
17
|
-
cmd = "ruby -Itest -e '[#{require_list}].each {|f| require f }' -- #{options[:test_options]}"
|
18
|
-
execute_command(cmd, process_number, options)
|
19
|
-
end
|
20
|
-
|
21
42
|
def self.execute_command(cmd, process_number, options)
|
22
43
|
cmd = "TEST_ENV_NUMBER=#{test_env_number(process_number)} ; export TEST_ENV_NUMBER; #{cmd}"
|
23
44
|
f = open("|#{cmd}", 'r')
|
@@ -38,10 +59,6 @@ module ParallelTests
|
|
38
59
|
process_number == 0 ? '' : process_number + 1
|
39
60
|
end
|
40
61
|
|
41
|
-
def self.runtime_log
|
42
|
-
'tmp/parallel_runtime_test.log'
|
43
|
-
end
|
44
|
-
|
45
62
|
def self.summarize_results(results)
|
46
63
|
results = results.join(' ').gsub(/s\b/,'') # combine and singularize results
|
47
64
|
counts = results.scan(/(\d+) (\w+)/)
|
@@ -83,14 +100,6 @@ module ParallelTests
|
|
83
100
|
all
|
84
101
|
end
|
85
102
|
|
86
|
-
def self.line_is_result?(line)
|
87
|
-
line =~ /\d+ failure/
|
88
|
-
end
|
89
|
-
|
90
|
-
def self.test_suffix
|
91
|
-
"_test.rb"
|
92
|
-
end
|
93
|
-
|
94
103
|
def self.with_runtime_info(tests)
|
95
104
|
lines = File.read(runtime_log).split("\n") rescue []
|
96
105
|
|
@@ -109,17 +118,23 @@ module ParallelTests
|
|
109
118
|
end
|
110
119
|
end
|
111
120
|
|
112
|
-
def self.find_tests(
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
121
|
+
def self.find_tests(tests, options={})
|
122
|
+
(tests||[]).map do |file_or_folder|
|
123
|
+
if File.directory?(file_or_folder)
|
124
|
+
tests_in_folder(file_or_folder, options)
|
125
|
+
else
|
126
|
+
file_or_folder
|
127
|
+
end
|
128
|
+
end.flatten.uniq
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.tests_in_folder(folder, options)
|
132
|
+
# follow one symlink and direct children
|
133
|
+
# http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob
|
134
|
+
files = Dir["#{folder}/**{,/*/**}/*#{test_suffix}"].uniq
|
135
|
+
files = files.map{|f| f.sub(folder+'/','') }
|
136
|
+
files = files.grep(/#{options[:pattern]}/)
|
137
|
+
files.map{|f| "#{folder}/#{f}" }
|
123
138
|
end
|
124
139
|
end
|
125
140
|
end
|
data/parallel_tests.gemspec
CHANGED
@@ -3,7 +3,7 @@ name = "parallel_tests"
|
|
3
3
|
require "#{name}/version"
|
4
4
|
|
5
5
|
Gem::Specification.new name, ParallelTests::VERSION do |s|
|
6
|
-
s.summary = "Run
|
6
|
+
s.summary = "Run Test::Unit / RSpec / Cucumber in parallel"
|
7
7
|
s.authors = ["Michael Grosser"]
|
8
8
|
s.email = "michael@grosser.it"
|
9
9
|
s.homepage = "http://github.com/grosser/#{name}"
|
data/spec/integration_spec.rb
CHANGED
@@ -15,7 +15,7 @@ describe 'CLI' do
|
|
15
15
|
|
16
16
|
def write(file, content)
|
17
17
|
path = "#{folder}/#{file}"
|
18
|
-
|
18
|
+
ensure_folder File.dirname(path)
|
19
19
|
File.open(path, 'w'){|f| f.write content }
|
20
20
|
path
|
21
21
|
end
|
@@ -28,15 +28,22 @@ describe 'CLI' do
|
|
28
28
|
"#{bin_folder}/parallel_test"
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
31
|
+
def ensure_folder(folder)
|
32
|
+
`mkdir -p #{folder}` unless File.exist?(folder)
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_tests(test_folder, options={})
|
36
|
+
ensure_folder folder
|
32
37
|
processes = "-n #{options[:processes]||2}" unless options[:processes] == false
|
33
|
-
`cd #{folder} && #{options[:export]} #{executable} --chunk-timeout 999 -t #{options[:type] || 'spec'} #{processes} #{options[:add]} 2>&1`
|
38
|
+
result = `cd #{folder} && #{options[:export]} #{executable} #{test_folder} --chunk-timeout 999 -t #{options[:type] || 'spec'} #{processes} #{options[:add]} 2>&1`
|
39
|
+
raise "FAILED #{result}" if $?.success? == !!options[:fail]
|
40
|
+
result
|
34
41
|
end
|
35
42
|
|
36
43
|
it "runs tests in parallel" do
|
37
44
|
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
|
38
45
|
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){puts "TEST2"}}'
|
39
|
-
result = run_tests
|
46
|
+
result = run_tests "spec"
|
40
47
|
|
41
48
|
# test ran and gave their puts
|
42
49
|
result.should include('TEST1')
|
@@ -47,12 +54,11 @@ describe 'CLI' do
|
|
47
54
|
result.scan('2 examples, 0 failures').size.should == 1 # 1 summary
|
48
55
|
result.scan(/Finished in \d+\.\d+ seconds/).size.should == 2
|
49
56
|
result.scan(/Took \d+\.\d+ seconds/).size.should == 1 # parallel summary
|
50
|
-
$?.success?.should == true
|
51
57
|
end
|
52
58
|
|
53
59
|
it "does not run any tests if there are none" do
|
54
60
|
write 'spec/xxx_spec.rb', '1'
|
55
|
-
result = run_tests
|
61
|
+
result = run_tests "spec"
|
56
62
|
result.should include('No examples found')
|
57
63
|
result.should include('Took')
|
58
64
|
end
|
@@ -60,12 +66,11 @@ describe 'CLI' do
|
|
60
66
|
it "fails when tests fail" do
|
61
67
|
write 'spec/xxx_spec.rb', 'describe("it"){it("should"){puts "TEST1"}}'
|
62
68
|
write 'spec/xxx2_spec.rb', 'describe("it"){it("should"){1.should == 2}}'
|
63
|
-
result = run_tests
|
69
|
+
result = run_tests "spec", :fail => true
|
64
70
|
|
65
71
|
result.scan('1 example, 1 failure').size.should == 1
|
66
72
|
result.scan('1 example, 0 failure').size.should == 1
|
67
73
|
result.scan('2 examples, 1 failure').size.should == 1
|
68
|
-
$?.success?.should == false
|
69
74
|
end
|
70
75
|
|
71
76
|
it "can exec given commands with ENV['TEST_ENV_NUM']" do
|
@@ -101,7 +106,7 @@ describe 'CLI' do
|
|
101
106
|
write "spec/xxx#{i}_spec.rb", 'describe("it"){it("should"){sleep 5}}; $stderr.puts ENV["TEST_ENV_NUMBER"]'
|
102
107
|
}
|
103
108
|
t = Time.now
|
104
|
-
run_tests(:processes => 2)
|
109
|
+
run_tests("spec", :processes => 2)
|
105
110
|
expected = 10
|
106
111
|
(Time.now - t).should <= expected
|
107
112
|
end
|
@@ -110,16 +115,22 @@ describe 'CLI' do
|
|
110
115
|
write "spec/x1_spec.rb", "puts '111'"
|
111
116
|
write "spec/x2_spec.rb", "puts '222'"
|
112
117
|
write "spec/x3_spec.rb", "puts '333'"
|
113
|
-
result = run_tests(
|
118
|
+
result = run_tests("spec/x1_spec.rb spec/x3_spec.rb")
|
114
119
|
result.should include('111')
|
115
120
|
result.should include('333')
|
116
121
|
result.should_not include('222')
|
117
122
|
end
|
118
123
|
|
124
|
+
it "runs successfully without any files" do
|
125
|
+
results = run_tests("")
|
126
|
+
results.should include("2 processes for 0 specs")
|
127
|
+
results.should include("Took")
|
128
|
+
end
|
129
|
+
|
119
130
|
it "can run with test-options" do
|
120
131
|
write "spec/x1_spec.rb", "111"
|
121
132
|
write "spec/x2_spec.rb", "111"
|
122
|
-
result = run_tests(:add => "--test-options ' --version'", :processes => 2)
|
133
|
+
result = run_tests("spec", :add => "--test-options ' --version'", :processes => 2)
|
123
134
|
result.should =~ /\d+\.\d+\.\d+.*\d+\.\d+\.\d+/m # prints version twice
|
124
135
|
end
|
125
136
|
|
@@ -128,23 +139,28 @@ describe 'CLI' do
|
|
128
139
|
processes.times{|i|
|
129
140
|
write "spec/x#{i}_spec.rb", "puts %{ENV-\#{ENV['TEST_ENV_NUMBER']}-}"
|
130
141
|
}
|
131
|
-
result = run_tests(:export => "PARALLEL_TEST_PROCESSORS=#{processes}", :processes => processes)
|
142
|
+
result = run_tests("spec", :export => "PARALLEL_TEST_PROCESSORS=#{processes}", :processes => processes)
|
132
143
|
result.scan(/ENV-.?-/).should =~ ["ENV--", "ENV-2-", "ENV-3-", "ENV-4-", "ENV-5-"]
|
133
144
|
end
|
134
145
|
|
135
146
|
context "Test::Unit" do
|
136
147
|
it "runs" do
|
137
148
|
write "test/x1_test.rb", "require 'test/unit'; class XTest < Test::Unit::TestCase; def test_xxx; end; end"
|
138
|
-
result = run_tests(:type => :test)
|
149
|
+
result = run_tests("test", :type => :test)
|
139
150
|
result.should include('1 test')
|
140
|
-
$?.success?.should == true
|
141
151
|
end
|
142
152
|
|
143
153
|
it "passes test options" do
|
144
154
|
write "test/x1_test.rb", "require 'test/unit'; class XTest < Test::Unit::TestCase; def test_xxx; end; end"
|
145
|
-
result = run_tests(:type => :test, :add => '--test-options "-v"')
|
155
|
+
result = run_tests("test", :type => :test, :add => '--test-options "-v"')
|
146
156
|
result.should include('test_xxx') # verbose output of every test
|
147
157
|
end
|
158
|
+
|
159
|
+
it "runs successfully without any files" do
|
160
|
+
results = run_tests("", :type => "test")
|
161
|
+
results.should include("2 processes for 0 tests")
|
162
|
+
results.should include("Took")
|
163
|
+
end
|
148
164
|
end
|
149
165
|
|
150
166
|
context "Cucumber" do
|
@@ -154,8 +170,7 @@ describe 'CLI' do
|
|
154
170
|
write "features/b.feature", "Feature: xxx\n Scenario: xxx\n Given I FAIL"
|
155
171
|
write "features/steps/a.rb", "Given('I print TEST_ENV_NUMBER'){ puts \"YOUR TEST ENV IS \#{ENV['TEST_ENV_NUMBER']}!\" }"
|
156
172
|
|
157
|
-
result = run_tests :type =>
|
158
|
-
$?.success?.should == true
|
173
|
+
result = run_tests "features", :type => "cucumber", :add => '--pattern good'
|
159
174
|
|
160
175
|
result.should include('YOUR TEST ENV IS 2!')
|
161
176
|
result.should include('YOUR TEST ENV IS !')
|
@@ -167,9 +182,14 @@ describe 'CLI' do
|
|
167
182
|
2.times{|i|
|
168
183
|
write "features/good#{i}.feature", "Feature: xxx\n Scenario: xxx\n Given I print TEST_ENV_NUMBER"
|
169
184
|
}
|
170
|
-
result = run_tests :type =>
|
171
|
-
$?.success?.should == true
|
185
|
+
result = run_tests "features", :type => "cucumber", :add => '-n 3'
|
172
186
|
result.scan(/YOUR TEST ENV IS \d?!/).sort.should == ["YOUR TEST ENV IS !", "YOUR TEST ENV IS 2!"]
|
173
187
|
end
|
188
|
+
|
189
|
+
it "runs successfully without any files" do
|
190
|
+
results = run_tests("", :type => "cucumber")
|
191
|
+
results.should include("2 processes for 0 features")
|
192
|
+
results.should include("Took")
|
193
|
+
end
|
174
194
|
end
|
175
195
|
end
|
@@ -104,11 +104,7 @@ EOF
|
|
104
104
|
ParallelTests::Test::Runner.send(:find_tests, *args)
|
105
105
|
end
|
106
106
|
|
107
|
-
it "
|
108
|
-
call([1]).should == [1]
|
109
|
-
end
|
110
|
-
|
111
|
-
it "finds all test files" do
|
107
|
+
it "finds test files nested in folders" do
|
112
108
|
begin
|
113
109
|
root = "/tmp/test-find_tests-#{rand(999)}"
|
114
110
|
`mkdir #{root}`
|
@@ -121,7 +117,7 @@ EOF
|
|
121
117
|
`touch #{root}/b/test.rb`
|
122
118
|
`ln -s #{root}/b #{root}/c`
|
123
119
|
`ln -s #{root}/b #{root}/a/`
|
124
|
-
call(root).sort.should == [
|
120
|
+
call([root]).sort.should == [
|
125
121
|
"#{root}/a/b/y_test.rb",
|
126
122
|
"#{root}/a/x_test.rb",
|
127
123
|
"#{root}/b/y_test.rb",
|
@@ -133,7 +129,7 @@ EOF
|
|
133
129
|
end
|
134
130
|
end
|
135
131
|
|
136
|
-
it "finds files by pattern" do
|
132
|
+
it "finds test files in folders by pattern" do
|
137
133
|
begin
|
138
134
|
root = "/tmp/test-find_tests-#{rand(999)}"
|
139
135
|
`mkdir #{root}`
|
@@ -141,7 +137,7 @@ EOF
|
|
141
137
|
`touch #{root}/a/x_test.rb`
|
142
138
|
`touch #{root}/a/y_test.rb`
|
143
139
|
`touch #{root}/a/z_test.rb`
|
144
|
-
call(root, :pattern => '^a/(y|z)_test').sort.should == [
|
140
|
+
call([root], :pattern => '^a/(y|z)_test').sort.should == [
|
145
141
|
"#{root}/a/y_test.rb",
|
146
142
|
"#{root}/a/z_test.rb",
|
147
143
|
]
|
@@ -149,6 +145,22 @@ EOF
|
|
149
145
|
`rm -rf #{root}`
|
150
146
|
end
|
151
147
|
end
|
148
|
+
|
149
|
+
it "finds nothing if I pass nothing" do
|
150
|
+
call(nil).should == []
|
151
|
+
end
|
152
|
+
|
153
|
+
it "finds nothing if I pass nothing (empty array)" do
|
154
|
+
call([]).should == []
|
155
|
+
end
|
156
|
+
|
157
|
+
it "keeps invalid files" do
|
158
|
+
call(['baz']).should == ['baz']
|
159
|
+
end
|
160
|
+
|
161
|
+
it "discards duplicates" do
|
162
|
+
call(['baz','baz']).should == ['baz']
|
163
|
+
end
|
152
164
|
end
|
153
165
|
|
154
166
|
describe :summarize_results do
|
data/spec/spec_helper.rb
CHANGED
@@ -75,6 +75,13 @@ def test_tests_in_groups(klass, folder, suffix)
|
|
75
75
|
`rm -f #{klass.runtime_log}`
|
76
76
|
end
|
77
77
|
|
78
|
+
def setup_runtime_log
|
79
|
+
File.open(@log,'w') do |f|
|
80
|
+
@files[1..-1].each{|file| f.puts "#{file}:#{@files.index(file)}"}
|
81
|
+
f.puts "#{@files[0]}:10"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
78
85
|
it "groups when given an array of files" do
|
79
86
|
list_of_files = Dir["#{test_root}/**/*#{suffix}"]
|
80
87
|
found = klass.with_runtime_info(list_of_files)
|
@@ -82,38 +89,31 @@ def test_tests_in_groups(klass, folder, suffix)
|
|
82
89
|
end
|
83
90
|
|
84
91
|
it "finds all tests" do
|
85
|
-
found = klass.tests_in_groups(test_root, 1)
|
92
|
+
found = klass.tests_in_groups([test_root], 1)
|
86
93
|
all = [ Dir["#{test_root}/**/*#{suffix}"] ]
|
87
94
|
(found.flatten - all.flatten).should == []
|
88
95
|
end
|
89
96
|
|
90
97
|
it "partitions them into groups by equal size" do
|
91
|
-
groups = klass.tests_in_groups(test_root, 2)
|
98
|
+
groups = klass.tests_in_groups([test_root], 2)
|
92
99
|
groups.map{|g| size_of(g)}.should == [400, 400]
|
93
100
|
end
|
94
101
|
|
95
102
|
it 'should partition correctly with a group size of 4' do
|
96
|
-
groups = klass.tests_in_groups(test_root, 4)
|
103
|
+
groups = klass.tests_in_groups([test_root], 4)
|
97
104
|
groups.map{|g| size_of(g)}.should == [200, 200, 200, 200]
|
98
105
|
end
|
99
106
|
|
100
107
|
it 'should partition correctly with an uneven group size' do
|
101
|
-
groups = klass.tests_in_groups(test_root, 3)
|
108
|
+
groups = klass.tests_in_groups([test_root], 3)
|
102
109
|
groups.map{|g| size_of(g)}.should =~ [300, 300, 200]
|
103
110
|
end
|
104
111
|
|
105
|
-
def setup_runtime_log
|
106
|
-
File.open(@log,'w') do |f|
|
107
|
-
@files[1..-1].each{|file| f.puts "#{file}:#{@files.index(file)}"}
|
108
|
-
f.puts "#{@files[0]}:10"
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
112
|
it "partitions by runtime when runtime-data is available" do
|
113
113
|
klass.stub!(:puts)
|
114
114
|
setup_runtime_log
|
115
115
|
|
116
|
-
groups = klass.tests_in_groups(test_root, 2)
|
116
|
+
groups = klass.tests_in_groups([test_root], 2)
|
117
117
|
groups.size.should == 2
|
118
118
|
# 10 + 1 + 3 + 5 = 19
|
119
119
|
groups[0].should == [@files[0],@files[1],@files[3],@files[5]]
|
@@ -125,7 +125,7 @@ def test_tests_in_groups(klass, folder, suffix)
|
|
125
125
|
klass.stub!(:puts)
|
126
126
|
setup_runtime_log
|
127
127
|
|
128
|
-
groups = klass.tests_in_groups(test_root, 2)
|
128
|
+
groups = klass.tests_in_groups([test_root], 2)
|
129
129
|
groups.size.should == 2
|
130
130
|
|
131
131
|
groups[0].should == groups[0].sort
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parallel_tests
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.0.
|
4
|
+
version: 0.7.0.alpha2
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
12
|
+
date: 2012-02-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: parallel
|
16
|
-
requirement: &
|
16
|
+
requirement: &16386080 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *16386080
|
25
25
|
description:
|
26
26
|
email: michael@grosser.it
|
27
27
|
executables:
|
@@ -36,6 +36,7 @@ files:
|
|
36
36
|
- Gemfile.lock
|
37
37
|
- Rakefile
|
38
38
|
- Readme.md
|
39
|
+
- ReadmeRails2.md
|
39
40
|
- bin/parallel_cucumber
|
40
41
|
- bin/parallel_spec
|
41
42
|
- bin/parallel_test
|
@@ -80,7 +81,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
80
81
|
version: '0'
|
81
82
|
segments:
|
82
83
|
- 0
|
83
|
-
hash: -
|
84
|
+
hash: -3495402516497603622
|
84
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
86
|
none: false
|
86
87
|
requirements:
|
@@ -92,5 +93,5 @@ rubyforge_project:
|
|
92
93
|
rubygems_version: 1.8.15
|
93
94
|
signing_key:
|
94
95
|
specification_version: 3
|
95
|
-
summary: Run
|
96
|
+
summary: Run Test::Unit / RSpec / Cucumber in parallel
|
96
97
|
test_files: []
|