moxiesoft_parallel_tests 0.4.13 → 0.6.18
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 +4 -2
- data/Gemfile.lock +26 -18
- data/Rakefile +3 -3
- data/Readme.md +105 -21
- data/VERSION +1 -1
- data/bin/parallel_test +21 -18
- data/lib/parallel_cucumber.rb +10 -3
- data/lib/parallel_specs/spec_runtime_logger.rb +16 -31
- data/lib/parallel_specs.rb +20 -16
- data/lib/parallel_tests/grouper.rb +34 -16
- data/lib/parallel_tests/tasks.rb +9 -9
- data/lib/parallel_tests.rb +83 -39
- data/parallel_tests.gemspec +64 -0
- data/spec/integration_spec.rb +77 -30
- data/spec/parallel_cucumber_spec.rb +10 -10
- data/spec/parallel_specs_spec.rb +49 -25
- data/spec/parallel_tests_spec.rb +100 -13
- data/spec/spec_helper.rb +48 -9
- metadata +30 -40
- data/moxiesoft_parallel_tests.gemspec +0 -65
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,34 +1,42 @@
|
|
1
1
|
GEM
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
|
+
builder (3.0.0)
|
5
|
+
cucumber (1.1.4)
|
6
|
+
builder (>= 2.1.2)
|
7
|
+
diff-lcs (>= 1.1.2)
|
8
|
+
gherkin (~> 2.7.1)
|
9
|
+
json (>= 1.4.6)
|
10
|
+
term-ansicolor (>= 1.0.6)
|
4
11
|
diff-lcs (1.1.2)
|
5
|
-
|
12
|
+
gherkin (2.7.6)
|
13
|
+
json (>= 1.4.6)
|
6
14
|
git (1.2.5)
|
7
|
-
jeweler (1.
|
8
|
-
|
15
|
+
jeweler (1.6.3)
|
16
|
+
bundler (~> 1.0)
|
9
17
|
git (>= 1.2.5)
|
10
|
-
|
11
|
-
|
18
|
+
rake
|
19
|
+
json (1.6.4)
|
12
20
|
parallel (0.5.1)
|
13
21
|
rake (0.8.7)
|
14
|
-
rspec (2.0
|
15
|
-
rspec-core (~> 2.0
|
16
|
-
rspec-expectations (~> 2.0
|
17
|
-
rspec-mocks (~> 2.0
|
18
|
-
rspec-core (2.
|
19
|
-
rspec-expectations (2.0
|
20
|
-
diff-lcs (
|
21
|
-
rspec-mocks (2.0
|
22
|
-
|
23
|
-
|
24
|
-
rubyforge (2.0.4)
|
25
|
-
json_pure (>= 1.1.7)
|
22
|
+
rspec (2.6.0)
|
23
|
+
rspec-core (~> 2.6.0)
|
24
|
+
rspec-expectations (~> 2.6.0)
|
25
|
+
rspec-mocks (~> 2.6.0)
|
26
|
+
rspec-core (2.6.4)
|
27
|
+
rspec-expectations (2.6.0)
|
28
|
+
diff-lcs (~> 1.1.2)
|
29
|
+
rspec-mocks (2.6.0)
|
30
|
+
term-ansicolor (1.0.7)
|
31
|
+
test-unit (2.4.4)
|
26
32
|
|
27
33
|
PLATFORMS
|
28
34
|
ruby
|
29
35
|
|
30
36
|
DEPENDENCIES
|
37
|
+
cucumber
|
31
38
|
jeweler
|
32
39
|
parallel
|
33
40
|
rake
|
34
|
-
rspec (
|
41
|
+
rspec (>= 2.4)
|
42
|
+
test-unit
|
data/Rakefile
CHANGED
@@ -10,12 +10,12 @@ begin
|
|
10
10
|
gem.name = "moxiesoft_parallel_tests"
|
11
11
|
gem.summary = "Run tests / specs / features in parallel"
|
12
12
|
gem.email = "grosser.michael@gmail.com"
|
13
|
-
gem.homepage = "http://github.com/
|
14
|
-
gem.authors = ['Michael Grosser', '
|
13
|
+
gem.homepage = "http://github.com/ssherman/#{gem.name}"
|
14
|
+
gem.authors = ['Michael Grosser', 'Eric DeBill']
|
15
15
|
gem.add_dependency "parallel"
|
16
16
|
end
|
17
17
|
|
18
18
|
Jeweler::GemcutterTasks.new
|
19
19
|
rescue LoadError
|
20
20
|
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install jeweler"
|
21
|
-
end
|
21
|
+
end
|
data/Readme.md
CHANGED
@@ -1,37 +1,53 @@
|
|
1
|
-
Speedup Test::Unit + RSpec + Cucumber by running parallel on multiple CPUs(or cores)
|
1
|
+
Speedup Test::Unit + RSpec + Cucumber by running parallel on multiple CPUs (or cores).<br/>
|
2
|
+
ParallelTests splits tests into even groups(by number of tests or runtime) and runs each group in a single process with its own database.
|
2
3
|
|
3
4
|
Setup for Rails
|
4
5
|
===============
|
5
|
-
|
6
6
|
## Install
|
7
7
|
### Rails 3
|
8
|
+
If you use RSpec: ensure you got >= 2.4
|
9
|
+
|
8
10
|
As gem
|
9
|
-
|
11
|
+
|
10
12
|
# add to Gemfile
|
11
|
-
gem "parallel_tests", :group
|
13
|
+
gem "parallel_tests", :group => :development
|
12
14
|
|
13
15
|
OR as plugin
|
14
|
-
|
16
|
+
|
15
17
|
rails plugin install git://github.com/grosser/parallel_tests.git
|
16
18
|
|
19
|
+
# add to Gemfile
|
20
|
+
gem "parallel", :group => :development
|
21
|
+
|
22
|
+
|
17
23
|
### Rails 2
|
18
24
|
|
19
25
|
As gem
|
20
|
-
|
26
|
+
|
27
|
+
gem install parallel_tests
|
28
|
+
|
21
29
|
# add to config/environments/development.rb
|
22
30
|
config.gem "parallel_tests"
|
31
|
+
|
23
32
|
# add to Rakefile
|
24
33
|
begin; require 'parallel_tests/tasks'; rescue LoadError; end
|
25
34
|
|
26
35
|
OR as plugin
|
27
36
|
|
28
|
-
|
37
|
+
gem install parallel
|
38
|
+
|
39
|
+
# add to config/environments/development.rb
|
40
|
+
config.gem "parallel"
|
41
|
+
|
29
42
|
./script/plugin install git://github.com/grosser/parallel_tests.git
|
30
43
|
|
31
44
|
## Setup
|
45
|
+
ParallelTests uses 1 database per test-process, 2 processes will use `*_test` and `*_test2`.
|
46
|
+
|
47
|
+
|
32
48
|
### 1: Add to `config/database.yml`
|
33
49
|
test:
|
34
|
-
database:
|
50
|
+
database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>
|
35
51
|
|
36
52
|
### 2: Create additional database(s)
|
37
53
|
rake parallel:create
|
@@ -49,29 +65,80 @@ OR as plugin
|
|
49
65
|
rake parallel:test --> got 4 CPUs? --> 26 seconds
|
50
66
|
...
|
51
67
|
|
52
|
-
Test
|
53
|
-
|
54
|
-
rake parallel:test[
|
68
|
+
Test by pattern (e.g. use one integration server per subfolder / see if you broke any 'user'-related tests)
|
69
|
+
|
70
|
+
rake parallel:test[^unit] # everything in test/unit folder (every test file matching /^unit/)
|
71
|
+
rake parallel:test[user] # run users_controller + user_helper + user tests
|
72
|
+
rake parallel:test['user|product'] # run user and product related tests
|
55
73
|
|
56
|
-
partial paths are OK too...
|
57
|
-
rake parallel:test[functional] == rake parallel:test[fun]
|
58
74
|
|
59
75
|
Example output
|
60
76
|
--------------
|
61
77
|
2 processes for 210 specs, ~ 105 specs per process
|
62
78
|
... test output ...
|
63
79
|
|
64
|
-
Results:
|
65
|
-
877 examples, 0 failures, 11 pending
|
66
80
|
843 examples, 0 failures, 1 pending
|
67
81
|
|
68
82
|
Took 29.925333 seconds
|
69
83
|
|
70
|
-
|
84
|
+
Loggers
|
85
|
+
===================
|
86
|
+
|
87
|
+
Even process runtimes
|
71
88
|
-----------------
|
72
|
-
|
73
|
-
|
74
|
-
|
89
|
+
|
90
|
+
Log test runtime to give each process the same runtime.
|
91
|
+
|
92
|
+
Rspec: Add to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
|
93
|
+
|
94
|
+
RSpec 1.x:
|
95
|
+
--format progress
|
96
|
+
--require parallel_specs/spec_runtime_logger
|
97
|
+
--format ParallelSpecs::SpecRuntimeLogger:tmp/parallel_profile.log
|
98
|
+
RSpec >= 2.4:
|
99
|
+
If installed as plugin: -I vendor/plugins/parallel_tests/lib
|
100
|
+
--format progress
|
101
|
+
--format ParallelSpecs::SpecRuntimeLogger --out tmp/parallel_profile.log
|
102
|
+
|
103
|
+
Test::Unit: Add to your `test_helper.rb`:
|
104
|
+
require 'parallel_tests/runtime_logger'
|
105
|
+
|
106
|
+
|
107
|
+
SpecSummaryLogger
|
108
|
+
--------------------
|
109
|
+
|
110
|
+
This logger logs the test output without the different processes overwriting each other.
|
111
|
+
|
112
|
+
Add the following to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
|
113
|
+
|
114
|
+
RSpec 1.x:
|
115
|
+
--format progress
|
116
|
+
--require parallel_specs/spec_summary_logger
|
117
|
+
--format ParallelSpecs::SpecSummaryLogger:tmp/spec_summary.log
|
118
|
+
RSpec >= 2.2:
|
119
|
+
If installed as plugin: -I vendor/plugins/parallel_tests/lib
|
120
|
+
--format progress
|
121
|
+
--format ParallelSpecs::SpecSummaryLogger --out tmp/spec_summary.log
|
122
|
+
|
123
|
+
SpecFailuresLogger
|
124
|
+
-----------------------
|
125
|
+
|
126
|
+
This logger produces pasteable command-line snippets for each failed example.
|
127
|
+
|
128
|
+
E.g.
|
129
|
+
|
130
|
+
rspec /path/to/my_spec.rb:123 # should do something
|
131
|
+
|
132
|
+
Add the following to your `spec/parallel_spec.opts` (or `spec/spec.opts`) :
|
133
|
+
|
134
|
+
RSpec 1.x:
|
135
|
+
--format progress
|
136
|
+
--require parallel_specs/spec_failures_logger
|
137
|
+
--format ParallelSpecs::SpecFailuresLogger:tmp/failing_specs.log
|
138
|
+
RSpec >= 2.4:
|
139
|
+
If installed as plugin: -I vendor/plugins/parallel_tests/lib
|
140
|
+
--format progress
|
141
|
+
--format ParallelSpecs::SpecFailuresLogger --out tmp/failing_specs.log
|
75
142
|
|
76
143
|
Setup for non-rails
|
77
144
|
===================
|
@@ -81,9 +148,11 @@ Setup for non-rails
|
|
81
148
|
# [Optional] use ENV['TEST_ENV_NUMBER'] inside your tests to select separate db/memcache/etc.
|
82
149
|
|
83
150
|
[optional] Only run selected files & folders:
|
84
|
-
|
151
|
+
|
152
|
+
parallel_test test/bar test/baz/foo_text.rb
|
85
153
|
|
86
154
|
Options are:
|
155
|
+
|
87
156
|
-n [PROCESSES] How many processes to use, default: available CPUs
|
88
157
|
-p, --path [PATH] run tests inside this path only
|
89
158
|
--no-sort do not sort files before running them
|
@@ -97,6 +166,7 @@ Options are:
|
|
97
166
|
-h, --help Show this.
|
98
167
|
|
99
168
|
You can run any kind of code with -e / --execute
|
169
|
+
|
100
170
|
parallel_test -n 5 -e 'ruby -e "puts %[hello from process #{ENV[:TEST_ENV_NUMBER.to_s].inspect}]"'
|
101
171
|
hello from process "2"
|
102
172
|
hello from process ""
|
@@ -118,10 +188,14 @@ TIPS
|
|
118
188
|
- [RSpec] if `script/spec` is missing parallel:spec uses just `spec` (which solves some issues with double-loaded environment.rb)
|
119
189
|
- [RSpec] 'script/spec_server' or [spork](http://github.com/timcharper/spork/tree/master) do not work in parallel
|
120
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)
|
191
|
+
- [RSpec] remove --loadby from you spec/*.opts
|
121
192
|
- [Bundler] if you have a `Gemfile` then `bundle exec` will be used to run tests
|
122
193
|
- [Capybara setup](https://github.com/grosser/parallel_tests/wiki)
|
123
194
|
- [Sphinx setup](https://github.com/grosser/parallel_tests/wiki)
|
195
|
+
- [Capistrano setup](https://github.com/grosser/parallel_tests/wiki/Remotely-with-capistrano) let your tests run on a big box instead of your laptop
|
124
196
|
- [SQL schema format] use :ruby schema format to get faster parallel:prepare`
|
197
|
+
- [ActiveRecord] if you do not have `db:abort_if_pending_migrations` add this to your Rakefile: `task('db:abort_if_pending_migrations'){}`
|
198
|
+
- `export PARALLEL_TEST_PROCESSORS=X` in your environment and parallel_tests will use this number of processors by default
|
125
199
|
- with zsh this would be `rake "parallel:prepare[3]"`
|
126
200
|
|
127
201
|
TODO
|
@@ -150,7 +224,17 @@ inspired by [pivotal labs](http://pivotallabs.com/users/miked/blog/articles/849-
|
|
150
224
|
- [Fred Wu](http://fredwu.me)
|
151
225
|
- [xxx](https://github.com/xxx)
|
152
226
|
- [Levent Ali](http://purebreeze.com/)
|
227
|
+
- [Michael Kintzer](https://github.com/rockrep)
|
228
|
+
- [nathansobo](https://github.com/nathansobo)
|
229
|
+
- [Joe Yates](http://titusd.co.uk)
|
230
|
+
- [asmega](http://www.ph-lee.com)
|
231
|
+
- [Doug Barth](https://github.com/dougbarth)
|
232
|
+
- [Geoffrey Hichborn](https://github.com/phene)
|
233
|
+
- [Trae Robrock](https://github.com/trobrock)
|
234
|
+
- [Lawrence Wang](https://github.com/levity)
|
235
|
+
- [Sean Walbran](https://github.com/seanwalbran)
|
153
236
|
|
154
237
|
[Michael Grosser](http://grosser.it)<br/>
|
155
238
|
michael@grosser.it<br/>
|
156
|
-
Hereby placed under public domain, do what you want, just do not hold me accountable
|
239
|
+
Hereby placed under public domain, do what you want, just do not hold me accountable...<br/>
|
240
|
+
[](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)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.18
|
data/bin/parallel_test
CHANGED
@@ -17,18 +17,25 @@ Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2',
|
|
17
17
|
Options are:
|
18
18
|
BANNER
|
19
19
|
opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs"){|n| options[:count] = n }
|
20
|
-
opts.on("-p", '--
|
20
|
+
opts.on("-p", '--pattern [PATTERN]', "run tests matching this pattern"){|pattern| options[:pattern] = pattern }
|
21
21
|
opts.on("--no-sort", "do not sort files before running them"){ |no_sort| options[:no_sort] = no_sort }
|
22
22
|
opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run"){ |multiply| options[:multiply] = multiply }
|
23
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
|
24
28
|
opts.on("-e", '--exec [COMMAND]', "execute this code parallel and with ENV['TEST_ENV_NUM']"){|path| options[:execute] = path }
|
25
29
|
opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options"){|arg| options[:test_options] = arg }
|
26
30
|
opts.on("-t", "--type [TYPE]", "which type of tests to run? test, spec or features"){|type| options[:type] = type }
|
27
|
-
opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec"){
|
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 }
|
28
33
|
opts.on('-v', '--version', 'Show Version'){ puts ParallelTests::VERSION; exit}
|
29
34
|
opts.on("-h", "--help", "Show this.") { puts opts; exit }
|
30
35
|
end.parse!
|
31
36
|
|
37
|
+
raise "--no-sort and --single-process are not supported" if options[:no_sort] and options[:single_process]
|
38
|
+
|
32
39
|
# get files to run from arguments
|
33
40
|
options[:files] = ARGV if ARGV.size > 0
|
34
41
|
|
@@ -39,11 +46,11 @@ if options[:execute]
|
|
39
46
|
runs = (0...num_processes).to_a
|
40
47
|
results = if options[:non_parallel]
|
41
48
|
runs.map do |i|
|
42
|
-
ParallelTests.execute_command(options[:execute], i)
|
49
|
+
ParallelTests.execute_command(options[:execute], i, options)
|
43
50
|
end
|
44
51
|
else
|
45
52
|
Parallel.map(runs, :in_processes => num_processes) do |i|
|
46
|
-
ParallelTests.execute_command(options[:execute], i)
|
53
|
+
ParallelTests.execute_command(options[:execute], i, options)
|
47
54
|
end
|
48
55
|
end.flatten
|
49
56
|
abort if results.any?{|r| r[:exit_status] != 0 }
|
@@ -59,17 +66,10 @@ else
|
|
59
66
|
|
60
67
|
start = Time.now
|
61
68
|
|
62
|
-
|
63
|
-
tests_folder = options[:
|
64
|
-
tests_folder = File.join(task, path.to_s)
|
65
|
-
tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
|
66
|
-
tests_folder
|
67
|
-
end
|
68
|
-
tests_folder = nil if tests_folder.empty?
|
69
|
+
tests_folder = task
|
70
|
+
tests_folder = File.join(options[:root], tests_folder) unless options[:root].to_s.empty?
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
groups = klass.tests_in_groups(options[:files] || tests_folder, num_processes, :no_sort => options[:no_sort])
|
72
|
+
groups = klass.tests_in_groups(options[:files] || tests_folder, num_processes, options)
|
73
73
|
num_processes = groups.size
|
74
74
|
|
75
75
|
#adjust processes to groups
|
@@ -79,14 +79,17 @@ else
|
|
79
79
|
puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
|
80
80
|
|
81
81
|
test_results = Parallel.map(groups, :in_processes => num_processes) do |group|
|
82
|
-
|
82
|
+
if group.empty?
|
83
|
+
{:stdout => '', :exit_status => 0}
|
84
|
+
else
|
85
|
+
klass.run_tests(group, groups.index(group), options)
|
86
|
+
end
|
83
87
|
end
|
84
88
|
|
85
89
|
#parse and print results
|
86
90
|
results = klass.find_results(test_results.map{|result| result[:stdout] }*"")
|
87
91
|
puts ""
|
88
|
-
puts
|
89
|
-
results.each{|r| puts r }
|
92
|
+
puts klass.summarize_results(results)
|
90
93
|
|
91
94
|
#report total time taken
|
92
95
|
puts ""
|
@@ -95,4 +98,4 @@ else
|
|
95
98
|
#exit with correct status code so rake parallel:test && echo 123 works
|
96
99
|
failed = test_results.any?{|result| result[:exit_status] != 0 }
|
97
100
|
abort "#{name.capitalize}s Failed" if failed
|
98
|
-
end
|
101
|
+
end
|
data/lib/parallel_cucumber.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
require
|
1
|
+
require 'parallel_tests'
|
2
2
|
|
3
3
|
class ParallelCucumber < ParallelTests
|
4
4
|
def self.run_tests(test_files, process_number, options)
|
5
5
|
color = ($stdout.tty? ? 'AUTOTEST=1 ; export AUTOTEST ;' : '')#display color when we are in a terminal
|
6
|
-
|
7
|
-
|
6
|
+
runtime_logging = " --format ParallelCucumber::RuntimeLogger --out #{runtime_log}"
|
7
|
+
cmd = "#{color} #{executable}"
|
8
|
+
cmd << runtime_logging if File.directory?(File.dirname(runtime_log))
|
9
|
+
cmd << " #{options[:test_options]} #{test_files*' '}"
|
10
|
+
execute_command(cmd, process_number, options)
|
8
11
|
end
|
9
12
|
|
10
13
|
def self.executable
|
@@ -17,6 +20,10 @@ class ParallelCucumber < ParallelTests
|
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
23
|
+
def self.runtime_log
|
24
|
+
'tmp/parallel_runtime_cucumber.log'
|
25
|
+
end
|
26
|
+
|
20
27
|
protected
|
21
28
|
|
22
29
|
def self.test_suffix
|
@@ -1,14 +1,8 @@
|
|
1
|
-
require '
|
1
|
+
require 'parallel_specs/spec_logger_base'
|
2
2
|
|
3
|
-
class ParallelSpecs::SpecRuntimeLogger <
|
4
|
-
def initialize(
|
5
|
-
|
6
|
-
FileUtils.mkdir_p(File.dirname(output))
|
7
|
-
File.open(output,'w'){|f| f.write ''} # clean the file
|
8
|
-
@output = File.open(output, 'a+') #append so that multiple processes can write at once
|
9
|
-
else
|
10
|
-
@output = output
|
11
|
-
end
|
3
|
+
class ParallelSpecs::SpecRuntimeLogger < ParallelSpecs::SpecLoggerBase
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
12
6
|
@example_times = Hash.new(0)
|
13
7
|
end
|
14
8
|
|
@@ -21,29 +15,20 @@ class ParallelSpecs::SpecRuntimeLogger < Spec::Runner::Formatter::BaseTextFormat
|
|
21
15
|
@example_times[file] += Time.now - @time
|
22
16
|
end
|
23
17
|
|
18
|
+
def dump_summary(*args);end
|
19
|
+
def dump_failures(*args);end
|
20
|
+
def dump_failure(*args);end
|
21
|
+
def dump_pending(*args);end
|
22
|
+
|
24
23
|
def start_dump(*args)
|
25
24
|
return unless ENV['TEST_ENV_NUMBER'] #only record when running in parallel
|
26
25
|
# TODO: Figure out why sometimes time can be less than 0
|
27
|
-
|
26
|
+
lock_output do
|
27
|
+
@example_times.each do |file, time|
|
28
|
+
relative_path = file.sub(/^#{Regexp.escape Dir.pwd}\//,'')
|
29
|
+
@output.puts "#{relative_path}:#{time > 0 ? time : 0}"
|
30
|
+
end
|
31
|
+
end
|
28
32
|
@output.flush
|
29
33
|
end
|
30
|
-
|
31
|
-
# stubs so that rspec doe not crash
|
32
|
-
|
33
|
-
def example_pending(*args)
|
34
|
-
end
|
35
|
-
|
36
|
-
def dump_summary(*args)
|
37
|
-
end
|
38
|
-
|
39
|
-
def dump_pending(*args)
|
40
|
-
end
|
41
|
-
|
42
|
-
def dump_failure(*args)
|
43
|
-
end
|
44
|
-
|
45
|
-
#stolen from Rspec
|
46
|
-
def close
|
47
|
-
@output.close if (IO === @output) & (@output != $stdout)
|
48
|
-
end
|
49
|
-
end
|
34
|
+
end
|
data/lib/parallel_specs.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
require
|
1
|
+
require 'parallel_tests'
|
2
2
|
|
3
3
|
class ParallelSpecs < ParallelTests
|
4
4
|
def self.run_tests(test_files, process_number, options)
|
5
|
-
exe = executable #
|
6
|
-
|
7
|
-
|
5
|
+
exe = executable # expensive, so we cache
|
6
|
+
version = (exe =~ /\brspec\b/ ? 2 : 1)
|
7
|
+
cmd = "#{rspec_1_color if version == 1}#{exe} #{options[:test_options]} #{rspec_2_color if version == 2}#{spec_opts(version)} #{test_files*' '}"
|
8
|
+
execute_command(cmd, process_number, options)
|
8
9
|
end
|
9
10
|
|
10
11
|
def self.executable
|
@@ -19,6 +20,11 @@ class ParallelSpecs < ParallelTests
|
|
19
20
|
cmd or raise("Can't find executables rspec or spec")
|
20
21
|
end
|
21
22
|
|
23
|
+
# legacy <-> people log to this file using rspec options
|
24
|
+
def self.runtime_log
|
25
|
+
'tmp/parallel_profile.log'
|
26
|
+
end
|
27
|
+
|
22
28
|
protected
|
23
29
|
|
24
30
|
# so it can be stubbed....
|
@@ -26,20 +32,18 @@ class ParallelSpecs < ParallelTests
|
|
26
32
|
`#{cmd}`
|
27
33
|
end
|
28
34
|
|
29
|
-
def self.
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
else
|
36
|
-
"-O #{opts}"
|
37
|
-
end
|
35
|
+
def self.rspec_1_color
|
36
|
+
'RSPEC_COLOR=1 ; export RSPEC_COLOR ;' if $stdout.tty?
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.rspec_2_color
|
40
|
+
'--color --tty ' if $stdout.tty?
|
38
41
|
end
|
39
42
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
+
def self.spec_opts(rspec_version)
|
44
|
+
options_file = ['.rspec_parallel', 'spec/parallel_spec.opts', 'spec/spec.opts'].detect{|f| File.file?(f) }
|
45
|
+
return unless options_file
|
46
|
+
"-O #{options_file}"
|
43
47
|
end
|
44
48
|
|
45
49
|
def self.test_suffix
|
@@ -1,31 +1,49 @@
|
|
1
1
|
class ParallelTests
|
2
2
|
class Grouper
|
3
3
|
def self.in_groups(items, num_groups)
|
4
|
-
[]
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
4
|
+
groups = Array.new(num_groups){ [] }
|
5
|
+
|
6
|
+
until items.empty?
|
7
|
+
num_groups.times do |group_number|
|
8
|
+
groups[group_number] << items.shift
|
10
9
|
end
|
11
10
|
end
|
11
|
+
|
12
|
+
groups.map!(&:sort!)
|
12
13
|
end
|
13
14
|
|
14
|
-
def self.in_even_groups_by_size(items_with_sizes, num_groups)
|
15
|
-
items_with_size = smallest_first(items_with_sizes)
|
15
|
+
def self.in_even_groups_by_size(items_with_sizes, num_groups, options={})
|
16
16
|
groups = Array.new(num_groups){{:items => [], :size => 0}}
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
smallest
|
17
|
+
|
18
|
+
# add all files that should run in a single process to one group
|
19
|
+
(options[:single_process]||[]).each do |pattern|
|
20
|
+
matched, items_with_sizes = items_with_sizes.partition{|item, size| item =~ pattern }
|
21
|
+
smallest = smallest_group(groups)
|
22
|
+
matched.each{|item,size| add_to_group(smallest, item, size) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# add all other files
|
26
|
+
largest_first(items_with_sizes).each do |item, size|
|
27
|
+
smallest = smallest_group(groups)
|
28
|
+
add_to_group(smallest, item, size)
|
22
29
|
end
|
23
30
|
|
24
|
-
groups.map{|g| g[:items] }
|
31
|
+
groups.map!{|g| g[:items].sort }
|
25
32
|
end
|
26
33
|
|
27
|
-
def self.
|
34
|
+
def self.largest_first(files)
|
28
35
|
files.sort_by{|item, size| size }.reverse
|
29
36
|
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.smallest_group(groups)
|
41
|
+
groups.min_by{|g| g[:size] }
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.add_to_group(group, item, size)
|
45
|
+
group[:items] << item
|
46
|
+
group[:size] += size
|
47
|
+
end
|
30
48
|
end
|
31
|
-
end
|
49
|
+
end
|
data/lib/parallel_tests/tasks.rb
CHANGED
@@ -18,7 +18,7 @@ namespace :parallel do
|
|
18
18
|
|
19
19
|
desc "update test databases by dumping and loading --> parallel:prepare[num_cpus]"
|
20
20
|
task(:prepare, [:count] => 'db:abort_if_pending_migrations') do |t,args|
|
21
|
-
if ActiveRecord::Base.schema_format == :ruby
|
21
|
+
if defined?(ActiveRecord) && ActiveRecord::Base.schema_format == :ruby
|
22
22
|
# dump then load in parallel
|
23
23
|
Rake::Task['db:schema:dump'].invoke
|
24
24
|
Rake::Task['parallel:load_schema'].invoke(args[:count])
|
@@ -30,7 +30,7 @@ namespace :parallel do
|
|
30
30
|
end
|
31
31
|
|
32
32
|
# when dumping/resetting takes too long
|
33
|
-
desc "update test databases via db:
|
33
|
+
desc "update test databases via db:migrate --> parallel:migrate[num_cpus]"
|
34
34
|
task :migrate, :count do |t,args|
|
35
35
|
run_in_parallel('rake db:migrate RAILS_ENV=test', args)
|
36
36
|
end
|
@@ -43,12 +43,12 @@ namespace :parallel do
|
|
43
43
|
|
44
44
|
['test', 'spec', 'features'].each do |type|
|
45
45
|
desc "run #{type} in parallel with parallel:#{type}[num_cpus]"
|
46
|
-
task type, :count, :
|
46
|
+
task type, [:count, :pattern, :options] => 'db:abort_if_pending_migrations' do |t,args|
|
47
47
|
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
48
48
|
require "parallel_tests"
|
49
|
-
count,
|
49
|
+
count, pattern, options = ParallelTests.parse_rake_args(args)
|
50
50
|
executable = File.join(File.dirname(__FILE__), '..', '..', 'bin', 'parallel_test')
|
51
|
-
command = "#{executable} --type #{type} -n #{count} -p '#{
|
51
|
+
command = "#{executable} --type #{type} -n #{count} -p '#{pattern}' -r '#{Rails.root}' -o '#{options}'"
|
52
52
|
abort unless system(command) # allow to chain tasks e.g. rake parallel:spec parallel:features
|
53
53
|
end
|
54
54
|
end
|
@@ -66,15 +66,15 @@ namespace :spec do
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
-
task :parallel, :count, :
|
69
|
+
task :parallel, :count, :pattern do |t,args|
|
70
70
|
$stderr.puts "WARNING -- Deprecated! use parallel:spec"
|
71
|
-
Rake::Task['parallel:spec'].invoke(args[:count], args[:
|
71
|
+
Rake::Task['parallel:spec'].invoke(args[:count], args[:pattern])
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
75
|
namespace :test do
|
76
|
-
task :parallel, :count, :
|
76
|
+
task :parallel, :count, :pattern do |t,args|
|
77
77
|
$stderr.puts "WARNING -- Deprecated! use parallel:test"
|
78
|
-
Rake::Task['parallel:test'].invoke(args[:count], args[:
|
78
|
+
Rake::Task['parallel:test'].invoke(args[:count], args[:pattern])
|
79
79
|
end
|
80
80
|
end
|