parallel_tests 0.7.0.alpha → 0.7.0.alpha2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](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: []
|