vinted-parallel_tests 0.13.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +6 -0
  5. data/Gemfile +8 -0
  6. data/Gemfile.lock +48 -0
  7. data/Rakefile +6 -0
  8. data/Readme.md +293 -0
  9. data/ReadmeRails2.md +48 -0
  10. data/bin/parallel_cucumber +5 -0
  11. data/bin/parallel_rspec +5 -0
  12. data/bin/parallel_test +5 -0
  13. data/lib/parallel_tests/cli.rb +187 -0
  14. data/lib/parallel_tests/cucumber/failures_logger.rb +25 -0
  15. data/lib/parallel_tests/cucumber/gherkin_listener.rb +82 -0
  16. data/lib/parallel_tests/cucumber/io.rb +41 -0
  17. data/lib/parallel_tests/cucumber/runner.rb +98 -0
  18. data/lib/parallel_tests/cucumber/runtime_logger.rb +28 -0
  19. data/lib/parallel_tests/grouper.rb +56 -0
  20. data/lib/parallel_tests/railtie.rb +8 -0
  21. data/lib/parallel_tests/rspec/failures_logger.rb +44 -0
  22. data/lib/parallel_tests/rspec/logger_base.rb +52 -0
  23. data/lib/parallel_tests/rspec/runner.rb +72 -0
  24. data/lib/parallel_tests/rspec/runtime_logger.rb +54 -0
  25. data/lib/parallel_tests/rspec/summary_logger.rb +19 -0
  26. data/lib/parallel_tests/tasks.rb +139 -0
  27. data/lib/parallel_tests/test/runner.rb +168 -0
  28. data/lib/parallel_tests/test/runtime_logger.rb +97 -0
  29. data/lib/parallel_tests/version.rb +3 -0
  30. data/lib/parallel_tests.rb +61 -0
  31. data/parallel_tests.gemspec +14 -0
  32. data/spec/integration_spec.rb +285 -0
  33. data/spec/parallel_tests/cli_spec.rb +71 -0
  34. data/spec/parallel_tests/cucumber/failure_logger_spec.rb +43 -0
  35. data/spec/parallel_tests/cucumber/gherkin_listener_spec.rb +97 -0
  36. data/spec/parallel_tests/cucumber/runner_spec.rb +179 -0
  37. data/spec/parallel_tests/grouper_spec.rb +52 -0
  38. data/spec/parallel_tests/rspec/failures_logger_spec.rb +82 -0
  39. data/spec/parallel_tests/rspec/runner_spec.rb +187 -0
  40. data/spec/parallel_tests/rspec/runtime_logger_spec.rb +126 -0
  41. data/spec/parallel_tests/rspec/summary_logger_spec.rb +37 -0
  42. data/spec/parallel_tests/tasks_spec.rb +151 -0
  43. data/spec/parallel_tests/test/runner_spec.rb +413 -0
  44. data/spec/parallel_tests/test/runtime_logger_spec.rb +90 -0
  45. data/spec/parallel_tests_spec.rb +137 -0
  46. data/spec/spec_helper.rb +157 -0
  47. metadata +110 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 14191ef3ae581ab27537c66e193d840bda6cd543
4
+ data.tar.gz: b8a86d1c3f2ca975d563c0c7531fe7871ac3a06c
5
+ SHA512:
6
+ metadata.gz: fdcf4a6e6456c7c142ab7fe45ceb751886f6d124585f6a3e1cc8d46dcf09d418ef30c4b5ed6c3426a55b8f901921c3518198ba7ce57ab547e806cb73ca59cce5
7
+ data.tar.gz: fc9842768d2028f2943e43d905028b2bf0f692682265eae17c0c9ff3ac080073454f8a9cfd7826518e1084c85f277f570a479f8d0da9261645609bc0a5184e35
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.sh
2
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --backtrace
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ rvm:
2
+ - ree
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - jruby-1.7.3
6
+ - jruby-head
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'bump'
5
+ gem 'test-unit'
6
+ gem 'rspec', '>=2.4'
7
+ gem 'cucumber'
8
+ gem 'rake'
data/Gemfile.lock ADDED
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ parallel_tests (0.13.3)
5
+ parallel
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ builder (3.0.0)
11
+ bump (0.3.8)
12
+ cucumber (1.1.4)
13
+ builder (>= 2.1.2)
14
+ diff-lcs (>= 1.1.2)
15
+ gherkin (~> 2.7.1)
16
+ json (>= 1.4.6)
17
+ term-ansicolor (>= 1.0.6)
18
+ diff-lcs (1.2.4)
19
+ gherkin (2.7.6)
20
+ json (>= 1.4.6)
21
+ gherkin (2.7.6-java)
22
+ json (>= 1.4.6)
23
+ json (1.7.5)
24
+ json (1.7.5-java)
25
+ parallel (0.6.5)
26
+ rake (10.0.3)
27
+ rspec (2.13.0)
28
+ rspec-core (~> 2.13.0)
29
+ rspec-expectations (~> 2.13.0)
30
+ rspec-mocks (~> 2.13.0)
31
+ rspec-core (2.13.1)
32
+ rspec-expectations (2.13.0)
33
+ diff-lcs (>= 1.1.3, < 2.0)
34
+ rspec-mocks (2.13.1)
35
+ term-ansicolor (1.0.7)
36
+ test-unit (2.4.4)
37
+
38
+ PLATFORMS
39
+ java
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ bump
44
+ cucumber
45
+ parallel_tests!
46
+ rake
47
+ rspec (>= 2.4)
48
+ test-unit
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bump/tasks'
2
+ require 'bundler/gem_tasks'
3
+
4
+ task :default do
5
+ sh "rspec spec/"
6
+ end
data/Readme.md ADDED
@@ -0,0 +1,293 @@
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.
3
+
4
+ [upgrading from 0.6 ?](https://github.com/grosser/parallel_tests/wiki/Upgrading-0.6.x-to-0.7.x)
5
+
6
+ Setup for Rails
7
+ ===============
8
+ [RailsCasts episode #413 Fast Tests](http://railscasts.com/episodes/413-fast-tests)
9
+ [still using Rails 2?](https://github.com/grosser/parallel_tests/blob/master/ReadmeRails2.md)
10
+
11
+ ### Install
12
+ If you use RSpec: ensure you got >= 2.4
13
+
14
+ As gem
15
+
16
+ ```ruby
17
+ # add to Gemfile
18
+ gem "parallel_tests", :group => :development
19
+ ```
20
+ OR as plugin
21
+
22
+ rails plugin install git://github.com/grosser/parallel_tests.git
23
+
24
+ ```ruby
25
+ # add to Gemfile
26
+ gem "parallel", :group => :development
27
+ ```
28
+
29
+ ### Add to `config/database.yml`
30
+ ParallelTests uses 1 database per test-process.
31
+ <table>
32
+ <tr><td>Process number</td><td>1</td><td>2</td><td>3</td></tr>
33
+ <tr><td>`ENV['TEST_ENV_NUMBER']`</td><td>''</td><td>'2'</td><td>'3'</td></tr>
34
+ </table>
35
+
36
+ ```yaml
37
+ test:
38
+ database: yourproject_test<%= ENV['TEST_ENV_NUMBER'] %>
39
+ ```
40
+
41
+ ### Create additional database(s)
42
+ rake parallel:create
43
+
44
+ ### Copy development schema (repeat after migrations)
45
+ rake parallel:prepare
46
+
47
+ ### Run!
48
+ rake parallel:test # Test::Unit
49
+ rake parallel:spec # RSpec
50
+ rake parallel:features # Cucumber
51
+
52
+ rake parallel:test[1] --> force 1 CPU --> 86 seconds
53
+ rake parallel:test --> got 2 CPUs? --> 47 seconds
54
+ rake parallel:test --> got 4 CPUs? --> 26 seconds
55
+ ...
56
+
57
+ Test by pattern (e.g. use one integration server per subfolder / see if you broke any 'user'-related tests)
58
+
59
+ rake parallel:test[^test/unit] # every test file in test/unit folder
60
+ rake parallel:test[user] # run users_controller + user_helper + user tests
61
+ rake parallel:test['user|product'] # run user and product related tests
62
+
63
+
64
+ ### Example output
65
+
66
+ 2 processes for 210 specs, ~ 105 specs per process
67
+ ... test output ...
68
+
69
+ 843 examples, 0 failures, 1 pending
70
+
71
+ Took 29.925333 seconds
72
+
73
+ ### Run an arbitrary task in parallel
74
+ ```Bash
75
+ RAILS_ENV=test parallel_test -e "rake my:custom:task"
76
+ # or
77
+ rake parallel:rake[my:custom:task]
78
+ ```
79
+
80
+
81
+ Running things once
82
+ ===================
83
+
84
+ ```Ruby
85
+ # effected by race-condition: first process may boot slower the second
86
+ # either sleep a bit or use a lock for example File.lock
87
+ ParallelTests.first_process? ? do_something : sleep(1)
88
+
89
+ at_exit do
90
+ if ParallelTests.first_process?
91
+ ParallelTests.wait_for_other_processes_to_finish
92
+ undo_something
93
+ end
94
+ end
95
+ ```
96
+
97
+ Loggers
98
+ ===================
99
+
100
+ Even process runtimes
101
+ -----------------
102
+
103
+ Log test runtime to give each process the same runtime.
104
+
105
+ Rspec: Add to your `.rspec_parallel` (or `.rspec`) :
106
+
107
+ If installed as plugin: -I vendor/plugins/parallel_tests/lib
108
+ --format progress
109
+ --format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
110
+
111
+ Test::Unit: Add to your `test_helper.rb`:
112
+ ```ruby
113
+ require 'parallel_tests/test/runtime_logger'
114
+ ```
115
+
116
+ RSpec: SummaryLogger
117
+ --------------------
118
+
119
+ This logger logs the test output without the different processes overwriting each other.
120
+
121
+ Add the following to your `.rspec_parallel` (or `.rspec`) :
122
+
123
+ If installed as plugin: -I vendor/plugins/parallel_tests/lib
124
+ --format progress
125
+ --format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
126
+
127
+ RSpec: FailuresLogger
128
+ -----------------------
129
+
130
+ This logger produces pasteable command-line snippets for each failed example.
131
+
132
+ E.g.
133
+
134
+ rspec /path/to/my_spec.rb:123 # should do something
135
+
136
+ Add the following to your `.rspec_parallel` (or `.rspec`) :
137
+
138
+ If installed as plugin: -I vendor/plugins/parallel_tests/lib
139
+ --format progress
140
+ --format ParallelTests::RSpec::FailuresLogger --out tmp/failing_specs.log
141
+
142
+ Cucumber: FailuresLogger
143
+ -----------------------
144
+
145
+ This logger logs failed cucumber scenarios to the specified file. The filename can be passed to cucumber, prefixed with '@' to rerun failures.
146
+
147
+ Usage:
148
+
149
+ cucumber --format ParallelTests::Cucumber::FailuresLogger --out tmp/cucumber_failures.log
150
+
151
+ Or add the formatter to the `parallel:` profile of your `cucumber.yml`:
152
+
153
+ parallel: --format progress --format ParallelTests::Cucumber::FailuresLogger --out tmp/cucumber_failures.log
154
+
155
+
156
+ To rerun failures:
157
+
158
+ cucumber @tmp/cucumber_failures.log
159
+
160
+ Setup for non-rails
161
+ ===================
162
+ gem install parallel_tests
163
+ # go to your project dir
164
+ parallel_test test/
165
+ parallel_rspec spec/
166
+ parallel_cucumber features/
167
+
168
+ - use ENV['TEST_ENV_NUMBER'] inside your tests to select separate db/memcache/etc.
169
+ - Only run selected files & folders:
170
+
171
+ parallel_test test/bar test/baz/foo_text.rb
172
+
173
+ Options are:
174
+
175
+ -n [PROCESSES] How many processes to use, default: available CPUs
176
+ -p, --pattern [PATTERN] run tests matching this pattern
177
+ --group-by [TYPE] group tests by:
178
+ found - order of finding files
179
+ steps - number of cucumber steps
180
+ default - runtime or filesize
181
+ -m, --multiply-processes [FLOAT] use given number as a multiplier of processes to run
182
+ -s, --single [PATTERN] Run all matching files in the same process
183
+ -i, --isolate Do not run any other tests in the group used by --single(-s)
184
+ -e, --exec [COMMAND] execute this code parallel and with ENV['TEST_ENV_NUM']
185
+ -o, --test-options '[OPTIONS]' execute test commands with those options
186
+ -t, --type [TYPE] test(default) / rspec / cucumber
187
+ --serialize-stdout Serialize stdout output, nothing will be written until everything is done
188
+ --non-parallel execute same commands but do not in parallel, needs --exec
189
+ --no-symlinks Do not traverse symbolic links to find test files
190
+ --ignore-tags [PATTERN] When counting steps ignore scenarios with tags that match this pattern
191
+ --nice execute test commands with low priority.
192
+ -v, --version Show Version
193
+ -h, --help Show this.
194
+
195
+ You can run any kind of code in parallel with -e / --execute
196
+
197
+ parallel_test -n 5 -e 'ruby -e "puts %[hello from process #{ENV[:TEST_ENV_NUMBER.to_s].inspect}]"'
198
+ hello from process "2"
199
+ hello from process ""
200
+ hello from process "3"
201
+ hello from process "5"
202
+ hello from process "4"
203
+
204
+ <table>
205
+ <tr><td></td><td>1 Process</td><td>2 Processes</td><td>4 Processes</td></tr>
206
+ <tr><td>RSpec spec-suite</td><td>18s</td><td>14s</td><td>10s</td></tr>
207
+ <tr><td>Rails-ActionPack</td><td>88s</td><td>53s</td><td>44s</td></tr>
208
+ </table>
209
+
210
+ TIPS
211
+ ====
212
+ - [RSpec] add a `.rspec_parallel` to use different options, e.g. **no --drb**
213
+ - [RSpec] delete `script/spec`
214
+ - [[Spork](https://github.com/sporkrb/spork)] does not work with parallel_tests
215
+ - [RSpec] remove --loadby from you spec/*.opts
216
+ - [RSpec] Instantly see failures (instead of just a red F) with [rspec-instafail](https://github.com/grosser/rspec-instafail)
217
+ - [Bundler] if you have a `Gemfile` then `bundle exec` will be used to run tests
218
+ - [Cucumber] add a `parallel: foo` profile to your `config/cucumber.yml` and it will be used to run parallel tests
219
+ - [Capybara setup](https://github.com/grosser/parallel_tests/wiki)
220
+ - [Sphinx setup](https://github.com/grosser/parallel_tests/wiki)
221
+ - [Capistrano setup](https://github.com/grosser/parallel_tests/wiki/Remotely-with-capistrano) let your tests run on a big box instead of your laptop
222
+ - [SQL schema format] use :ruby schema format to get faster parallel:prepare`
223
+ - `export PARALLEL_TEST_PROCESSORS=X` in your environment and parallel_tests will use this number of processors by default
224
+ - [ZSH] use quotes to use rake arguments `rake "parallel:prepare[3]"`
225
+ - [email_spec and/or action_mailer_cache_delivery](https://github.com/grosser/parallel_tests/wiki)
226
+ - [Memcached] use different namespaces e.g. `config.cache_store = ..., :namespace => "test_#{ENV['TEST_ENV_NUMBER']}"`
227
+
228
+ TODO
229
+ ====
230
+ - make tests consistently pass with `--order random` in .rspec
231
+ - fix tests vs cucumber >= 1.2 `unknown option --format`
232
+ - add integration tests for the rake tasks, maybe generate a rails project ...
233
+ - add unit tests for cucumber runtime formatter
234
+ - make windows compatible
235
+
236
+ Authors
237
+ ====
238
+ inspired by [pivotal labs](http://pivotallabs.com/users/miked/blog/articles/849-parallelize-your-rspec-suite)
239
+
240
+ ### [Contributors](http://github.com/grosser/parallel_tests/contributors)
241
+ - [Charles Finkel](http://charlesfinkel.com/)
242
+ - [Indrek Juhkam](http://urgas.eu)
243
+ - [Jason Morrison](http://jayunit.net)
244
+ - [jinzhu](http://github.com/jinzhu)
245
+ - [Joakim Kolsjö](http://www.rubyblocks.se)
246
+ - [Kevin Scaldeferri](http://kevin.scaldeferri.com/blog/)
247
+ - [Kpumuk](http://kpumuk.info/)
248
+ - [Maksim Horbul](http://github.com/mhorbul)
249
+ - [Pivotal Labs](http://www.pivotallabs.com)
250
+ - [Rohan Deshpande](http://github.com/rdeshpande)
251
+ - [Tchandy](http://thiagopradi.net/)
252
+ - [Terence Lee](http://hone.heroku.com/)
253
+ - [Will Bryant](http://willbryant.net/)
254
+ - [Fred Wu](http://fredwu.me)
255
+ - [xxx](https://github.com/xxx)
256
+ - [Levent Ali](http://purebreeze.com/)
257
+ - [Michael Kintzer](https://github.com/rockrep)
258
+ - [nathansobo](https://github.com/nathansobo)
259
+ - [Joe Yates](http://titusd.co.uk)
260
+ - [asmega](http://www.ph-lee.com)
261
+ - [Doug Barth](https://github.com/dougbarth)
262
+ - [Geoffrey Hichborn](https://github.com/phene)
263
+ - [Trae Robrock](https://github.com/trobrock)
264
+ - [Lawrence Wang](https://github.com/levity)
265
+ - [Sean Walbran](https://github.com/seanwalbran)
266
+ - [Lawrence Wang](https://github.com/levity)
267
+ - [Potapov Sergey](https://github.com/greyblake)
268
+ - [Łukasz Tackowiak](https://github.com/lukasztackowiak)
269
+ - [Pedro Carriço](https://github.com/pedrocarrico)
270
+ - [Pablo Manrubia Díez](https://github.com/pmanrubia)
271
+ - [Slawomir Smiechura](https://github.com/ssmiech)
272
+ - [Georg Friedrich](https://github.com/georg)
273
+ - [R. Tyler Croy](https://github.com/rtyler)
274
+ - [Ulrich Berkmüller](https://github.com/ulrich-berkmueller)
275
+ - [Grzegorz Derebecki](https://github.com/madmax)
276
+ - [Florian Motlik](https://github.com/flomotlik)
277
+ - [Artem Kuzko](https://github.com/akuzko)
278
+ - [Zeke Fast](https://github.com/zekefast)
279
+ - [Joseph Shraibman](https://github.com/jshraibman-mdsol)
280
+ - [David Davis](https://github.com/daviddavis)
281
+ - [Ari Pollak](https://github.com/aripollak)
282
+ - [Aaron Jensen](https://github.com/aaronjensen)
283
+ - [Artur Roszczyk](https://github.com/sevos)
284
+ - [Caleb Tomlinson](https://github.com/calebTomlinson)
285
+ - [Jawwad Ahmad](https://github.com/jawwad)
286
+ - [Iain Beeston](https://github.com/iainbeeston)
287
+ - [Alejandro Pulver](https://github.com/alepulver)
288
+ - [Felix Clack](https://github.com/felixclack)
289
+
290
+ [Michael Grosser](http://grosser.it)<br/>
291
+ michael@grosser.it<br/>
292
+ License: MIT<br/>
293
+ [![Build Status](https://travis-ci.org/grosser/parallel_tests.png)](https://travis-ci.org/grosser/parallel_tests)
data/ReadmeRails2.md ADDED
@@ -0,0 +1,48 @@
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
+ # add to Rakefile
23
+ begin; require 'vendor/plugins/parallel_tests/lib/parallel_tests/tasks'; rescue LoadError; end
24
+
25
+
26
+ Even process runtimes
27
+ -----------------
28
+
29
+ RSpec 1.x:
30
+ --format progress
31
+ --require parallel_tests/rspec/runtime_logger
32
+ --format ParallelTests::RSpec::RuntimeLogger:tmp/parallel_runtime_rspec.log
33
+
34
+ SpecSummaryLogger
35
+ --------------------
36
+
37
+ RSpec 1.x:
38
+ --format progress
39
+ --require parallel_tests/rspec/summary_logger
40
+ --format ParallelTests::RSpec::SummaryLogger:tmp/spec_summary.log
41
+
42
+ SpecFailuresLogger
43
+ -----------------------
44
+
45
+ RSpec 1.x:
46
+ --format progress
47
+ --require parallel_tests/rspec/failures_logger
48
+ --format ParallelTests::RSpec::FailuresLogger:tmp/failing_specs.log
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
3
+ require "parallel_tests"
4
+
5
+ ParallelTests::CLI.new.run(["--type", "cucumber"] + ARGV)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
3
+ require "parallel_tests"
4
+
5
+ ParallelTests::CLI.new.run(["--type", "rspec"] + ARGV)
data/bin/parallel_test ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH << File.expand_path("../../lib", __FILE__)
3
+ require "parallel_tests"
4
+
5
+ ParallelTests::CLI.new.run(["--type", "test"] + ARGV)
@@ -0,0 +1,187 @@
1
+ require 'optparse'
2
+ require 'tempfile'
3
+ require 'parallel_tests'
4
+
5
+ module ParallelTests
6
+ class CLI
7
+ def run(argv)
8
+ options = parse_options!(argv)
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
+ run_tests_in_parallel(num_processes, options)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def execute_in_parallel(items, num_processes, options)
23
+ Tempfile.open 'parallel_tests-lock' do |lock|
24
+ return Parallel.map(items, :in_threads => num_processes) do |item|
25
+ result = yield(item)
26
+ report_output(result, lock) if options[:serialize_stdout]
27
+ result
28
+ end
29
+ end
30
+ end
31
+
32
+ def run_tests_in_parallel(num_processes, options)
33
+ test_results = nil
34
+
35
+ report_time_taken do
36
+ groups = @runner.tests_in_groups(options[:files], num_processes, options)
37
+ report_number_of_tests(groups)
38
+
39
+ test_results = execute_in_parallel(groups, groups.size, options) do |group|
40
+ run_tests(group, groups.index(group), num_processes, options)
41
+ end
42
+
43
+ report_results(test_results)
44
+ end
45
+
46
+ abort final_fail_message if any_test_failed?(test_results)
47
+ end
48
+
49
+ def run_tests(group, process_number, num_processes, options)
50
+ if group.empty?
51
+ {:stdout => '', :exit_status => 0}
52
+ else
53
+ @runner.run_tests(group, process_number, num_processes, options)
54
+ end
55
+ end
56
+
57
+ def report_output(result, lock)
58
+ lock.flock File::LOCK_EX
59
+ $stdout.puts result[:stdout]
60
+ $stdout.flush
61
+ ensure
62
+ lock.flock File::LOCK_UN
63
+ end
64
+
65
+ def report_results(test_results)
66
+ results = @runner.find_results(test_results.map { |result| result[:stdout] }*"")
67
+ puts ""
68
+ puts @runner.summarize_results(results)
69
+ end
70
+
71
+ def report_number_of_tests(groups)
72
+ name = @runner.test_file_name
73
+ num_processes = groups.size
74
+ num_tests = groups.map(&:size).inject(:+)
75
+ puts "#{num_processes} processes for #{num_tests} #{name}s, ~ #{num_tests / groups.size} #{name}s per process"
76
+ end
77
+
78
+ #exit with correct status code so rake parallel:test && echo 123 works
79
+ def any_test_failed?(test_results)
80
+ test_results.any? { |result| result[:exit_status] != 0 }
81
+ end
82
+
83
+ def parse_options!(argv)
84
+ options = {}
85
+ OptionParser.new do |opts|
86
+ opts.banner = <<BANNER
87
+ Run all tests in parallel, giving each process ENV['TEST_ENV_NUMBER'] ('', '2', '3', ...)
88
+
89
+ [optional] Only run selected files & folders:
90
+ parallel_test test/bar test/baz/xxx_text.rb
91
+
92
+ Options are:
93
+ BANNER
94
+ opts.on("-n [PROCESSES]", Integer, "How many processes to use, default: available CPUs") { |n| options[:count] = n }
95
+ opts.on("-p", "--pattern [PATTERN]", "run tests matching this pattern") { |pattern| options[:pattern] = /#{pattern}/ }
96
+ opts.on("--group-by [TYPE]", <<-TEXT
97
+ group tests by:
98
+ found - order of finding files
99
+ steps - number of cucumber steps
100
+ default - runtime or filesize
101
+ TEXT
102
+ ) { |type| options[:group_by] = type.to_sym }
103
+ opts.on("-m [FLOAT]", "--multiply-processes [FLOAT]", Float, "use given number as a multiplier of processes to run") { |multiply| options[:multiply] = multiply }
104
+
105
+ opts.on("-s [PATTERN]", "--single [PATTERN]",
106
+ "Run all matching files in the same process") do |pattern|
107
+
108
+ options[:single_process] ||= []
109
+ options[:single_process] << /#{pattern}/
110
+ end
111
+
112
+ opts.on("-i", "--isolate",
113
+ "Do not run any other tests in the group used by --single(-s)") do |pattern|
114
+
115
+ options[:isolate] = true
116
+ end
117
+
118
+ opts.on("-e", "--exec [COMMAND]", "execute this code parallel and with ENV['TEST_ENV_NUM']") { |path| options[:execute] = path }
119
+ opts.on("-o", "--test-options '[OPTIONS]'", "execute test commands with those options") { |arg| options[:test_options] = arg }
120
+ opts.on("-t", "--type [TYPE]", "test(default) / rspec / cucumber") do |type|
121
+ begin
122
+ @runner = load_runner(type)
123
+ rescue NameError, LoadError => e
124
+ puts "Runner for `#{type}` type has not been found! (#{e})"
125
+ abort
126
+ end
127
+ end
128
+ opts.on("--serialize-stdout", "Serialize stdout output, nothing will be written until everything is done") { options[:serialize_stdout] = true }
129
+ opts.on("--non-parallel", "execute same commands but do not in parallel, needs --exec") { options[:non_parallel] = true }
130
+ opts.on("--no-symlinks", "Do not traverse symbolic links to find test files") { options[:symlinks] = false }
131
+ opts.on("--advance-number [NUMBER]", Integer, "Advance test env number by specified number") { |n| options[:advance_number] = n }
132
+ opts.on('--ignore-tags [PATTERN]', 'When counting steps ignore scenarios with tags that match this pattern') { |arg| options[:ignore_tag_pattern] = arg }
133
+ opts.on("--nice", "execute test commands with low priority.") { options[:nice] = true }
134
+ opts.on("-v", "--version", "Show Version") { puts ParallelTests::VERSION; exit }
135
+ opts.on("-h", "--help", "Show this.") { puts opts; exit }
136
+ end.parse!(argv)
137
+
138
+ raise "--group-by found and --single-process are not supported" if options[:group_by] == :found and options[:single_process]
139
+
140
+ if options[:count] == 0
141
+ options.delete(:count)
142
+ options[:non_parallel] = true
143
+ end
144
+
145
+ options[:files] = argv
146
+ options
147
+ end
148
+
149
+ def load_runner(type)
150
+ require "parallel_tests/#{type}/runner"
151
+ klass_name = "ParallelTests::#{type.capitalize.sub("Rspec", "RSpec")}::Runner"
152
+ klass_name.split('::').inject(Object) { |x, y| x.const_get(y) }
153
+ end
154
+
155
+ def execute_shell_command_in_parallel(command, num_processes, options)
156
+ runs = (0...num_processes).to_a
157
+ results = if options[:non_parallel]
158
+ runs.map do |i|
159
+ ParallelTests::Test::Runner.execute_command(command, i, num_processes, options)
160
+ end
161
+ else
162
+ execute_in_parallel(runs, num_processes, options) do |i|
163
+ ParallelTests::Test::Runner.execute_command(command, i, num_processes, options)
164
+ end
165
+ end.flatten
166
+
167
+ abort if results.any? { |r| r[:exit_status] != 0 }
168
+ end
169
+
170
+ def report_time_taken
171
+ start = Time.now
172
+ yield
173
+ puts "\nTook #{Time.now - start} seconds"
174
+ end
175
+
176
+ def final_fail_message
177
+ fail_message = "#{@runner.name}s Failed"
178
+ fail_message = "\e[31m#{fail_message}\e[0m" if use_colors?
179
+
180
+ fail_message
181
+ end
182
+
183
+ def use_colors?
184
+ $stdout.tty?
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,25 @@
1
+ require 'cucumber/formatter/rerun'
2
+ require 'parallel_tests/cucumber/io'
3
+
4
+ module ParallelTests
5
+ module Cucumber
6
+ class FailuresLogger < ::Cucumber::Formatter::Rerun
7
+ include Io
8
+
9
+ def initialize(runtime, path_or_io, options)
10
+ @io = prepare_io(path_or_io)
11
+ end
12
+
13
+ def after_feature(feature)
14
+ unless @lines.empty?
15
+ lock_output do
16
+ @lines.each do |line|
17
+ @io.puts "#{feature.file}:#{line}"
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end