rspec-core-parallel 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +56 -84
- data/lib/rspec/core/parallel/coordinator.rb +1 -0
- data/lib/rspec/core/parallel/core_ext.rb +16 -0
- data/lib/rspec/core/parallel/simplecov_integration.rb +27 -9
- data/lib/rspec/core/parallel/version.rb +1 -1
- data/lib/rspec/core/parallel/worker.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b57c9af852c507ef4696411ab9de26231b9c04d9400fb8cdb48beb99edf4a89
|
|
4
|
+
data.tar.gz: ef94f289bf3f6128f124c7bf3c0300a11d435cf0e8a40c2405beaae4b3c274d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c57a12dc5500fc4578a6ac51bfaa27b68b4f98da09e4297e2857993328ed2b6a9923851caffd14db0cc9a88fd9aecaa30dbdccb9fe44fcc721b48b4885329baa
|
|
7
|
+
data.tar.gz: d5b61024b3bc242e08c1c35a0b65feb8877083542f85f33d970a4250e30fcdc0a2f473f097cb0fad9c816e1e393ad54406637d39deef1ed55c1e8ed456273a3a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
* SimpleCov collects the coverage of the workers without any configuration.
|
|
6
|
+
* `--profile` shows the slowest examples as a share of the time taken by all
|
|
7
|
+
the examples, rather than of the run time, which could exceed 100%.
|
|
8
|
+
* README: require the gem in `spec_helper.rb` in Rails apps too, to configure it
|
|
9
|
+
there, and create the test database on CI as usual.
|
|
10
|
+
|
|
3
11
|
## 0.1.0
|
|
4
12
|
|
|
5
13
|
* Loading the gem makes `rspec` run the suite in forked worker processes, one
|
data/README.md
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
# rspec-core-parallel
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Run your RSpec suite on all the cores of your machine. Add the gem and `rspec` runs in parallel, with one worker per
|
|
4
|
+
physical CPU core.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
formatters, `--only-failures`, `--profile`, `--fail-fast`, `--bisect` and exit
|
|
10
|
-
codes work as usual.
|
|
11
|
-
|
|
12
|
-
This gem brings `--parallel` to rspec-core 3.13 until it is part of rspec-core
|
|
13
|
-
itself.
|
|
6
|
+
The suite is loaded once, then forked into worker processes. Workers pick up the longest groups first and send their
|
|
7
|
+
results back to the main process, which prints a single report. Formatters, `--only-failures`, `--profile`,
|
|
8
|
+
`--fail-fast`, `--bisect` and the exit code work as in a normal run.
|
|
14
9
|
|
|
15
10
|
## Installation
|
|
16
11
|
|
|
@@ -20,19 +15,20 @@ group :test do
|
|
|
20
15
|
end
|
|
21
16
|
```
|
|
22
17
|
|
|
23
|
-
Rails apps load it with the
|
|
24
|
-
|
|
18
|
+
And require it at the top of `spec_helper.rb`. Rails apps also load it with the rest of the `test` group, but only
|
|
19
|
+
when the app boots, which is usually after `spec_helper.rb` is loaded: until then, settings like `parallel_workers`
|
|
20
|
+
and `parallel_worker_setup` don't exist.
|
|
25
21
|
|
|
26
22
|
```ruby
|
|
27
23
|
require "rspec/core/parallel"
|
|
28
24
|
```
|
|
29
25
|
|
|
30
|
-
|
|
26
|
+
Then run `bundle exec rspec` as usual.
|
|
31
27
|
|
|
32
28
|
## Number of workers
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
By default you get one worker per physical CPU core. To change it, set `parallel_workers` in `spec_helper.rb`.
|
|
31
|
+
`1` turns parallel runs off.
|
|
36
32
|
|
|
37
33
|
```ruby
|
|
38
34
|
RSpec.configure do |config|
|
|
@@ -40,51 +36,44 @@ RSpec.configure do |config|
|
|
|
40
36
|
end
|
|
41
37
|
```
|
|
42
38
|
|
|
43
|
-
To choose it on the command line,
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
To choose it on the command line, use `rspec-parallel`. It is `rspec` with the gem loaded before the options are
|
|
40
|
+
parsed, so it also accepts `--parallel [COUNT]` (`-j`) and `--no-parallel`. Command line options win over
|
|
41
|
+
`config.parallel_workers`.
|
|
46
42
|
|
|
47
43
|
```
|
|
48
44
|
$ bundle exec rspec-parallel --parallel 4
|
|
49
45
|
$ bundle exec rspec-parallel --no-parallel spec/models/user_spec.rb
|
|
50
46
|
```
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## How it compares
|
|
48
|
+
## Compared to parallel_tests
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
them:
|
|
50
|
+
[parallel_tests](https://github.com/grosser/parallel_tests) starts one rspec process per CPU and splits the spec files
|
|
51
|
+
between them up front. With rspec-core-parallel:
|
|
59
52
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
- the app boots once and the workers are forked from it;
|
|
54
|
+
- groups are handed out while the suite runs, longest first, based on the previous run, and long files are split, so
|
|
55
|
+
the workers finish at about the same time;
|
|
56
|
+
- you get one report and one exit code;
|
|
57
|
+
- in Rails apps, the test databases of the workers are created and loaded for you.
|
|
65
58
|
|
|
66
59
|
## Isolation
|
|
67
60
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
the
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
Each worker gets a `TEST_ENV_NUMBER` environment variable (`""`, `"2"`, `"3"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
setups often do for Redis URLs): the variable is only set in the workers, so
|
|
85
|
-
such resources must be pointed at a per-worker instance in
|
|
86
|
-
`parallel_worker_setup`. For example, to give every worker its own directory
|
|
87
|
-
for uploaded files:
|
|
61
|
+
Each worker is a separate process, so examples running at the same time never share global state like constants,
|
|
62
|
+
stubs or `RSpec.configuration`. Most suites run unchanged. What workers do share are external resources: databases,
|
|
63
|
+
files, Redis, and so on.
|
|
64
|
+
|
|
65
|
+
**Databases.** In Rails apps every worker gets its own test database (`app_test_0`, `app_test_1`, ...), created and
|
|
66
|
+
loaded from the schema when needed, like `rails test` does in parallel. Set
|
|
67
|
+
`ActiveSupport.parallelize_test_databases = false` to opt out. The main process still uses the test database itself
|
|
68
|
+
(`maintain_test_schema!` runs before the workers are forked), so on CI create it as usual, e.g. with
|
|
69
|
+
`bin/rails db:create db:schema:load`, rather than with `parallel:create`.
|
|
70
|
+
|
|
71
|
+
**Other resources.** Each worker gets a `TEST_ENV_NUMBER` environment variable (`""`, `"2"`, `"3"`, ...) as with
|
|
72
|
+
parallel_tests, and runs the `parallel_worker_setup` hooks. Workers are forked after the suite is loaded, so anything
|
|
73
|
+
set up while loading, like a constant with a directory or a Redis client, is the same in all of them. This also applies
|
|
74
|
+
to config that reads `TEST_ENV_NUMBER` while the app boots: the variable doesn't exist yet at that point. Point those
|
|
75
|
+
resources to a per-worker instance in `parallel_worker_setup` instead. For example, a separate upload directory per
|
|
76
|
+
worker:
|
|
88
77
|
|
|
89
78
|
```ruby
|
|
90
79
|
RSpec.configure do |config|
|
|
@@ -94,54 +83,37 @@ RSpec.configure do |config|
|
|
|
94
83
|
end
|
|
95
84
|
```
|
|
96
85
|
|
|
97
|
-
`before(:suite)`, `after(:suite)` and `at_exit` hooks run in every worker.
|
|
86
|
+
**Hooks.** `before(:suite)`, `after(:suite)` and `at_exit` hooks run in every worker.
|
|
98
87
|
|
|
99
88
|
## Code coverage
|
|
100
89
|
|
|
101
|
-
|
|
102
|
-
|
|
90
|
+
SimpleCov collects the coverage of the workers without any configuration: the gem turns on SimpleCov's support for
|
|
91
|
+
subprocesses and names the results of each worker after its number, so that they replace those of the previous run. If
|
|
92
|
+
you already call `SimpleCov.enable_for_subprocesses true`, your `at_fork` configuration is kept.
|
|
103
93
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
SimpleCov.start
|
|
107
|
-
|
|
108
|
-
RSpec.configure do |config|
|
|
109
|
-
# A stable name per worker, so that each run replaces the results of the
|
|
110
|
-
# previous one instead of adding to them (SimpleCov names subprocesses after
|
|
111
|
-
# their pid by default).
|
|
112
|
-
config.parallel_worker_setup do |worker_number|
|
|
113
|
-
SimpleCov.command_name "RSpec (worker #{worker_number})"
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
Workers only save their coverage: the main process, which exits last, merges
|
|
119
|
-
it, writes the report and checks the minimum coverage.
|
|
94
|
+
Workers only save their results. The main process exits last: it merges them, writes the report and checks the
|
|
95
|
+
minimum coverage.
|
|
120
96
|
|
|
121
97
|
## Splitting long groups
|
|
122
98
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
directory at a fixed path (such as a browser download directory) with
|
|
131
|
-
`:parallel_split => false`, or use a path per worker.
|
|
99
|
+
A group that would take longer than a worker's share of the suite is split into its child groups and chunks of
|
|
100
|
+
examples, which run in different workers. Groups with their own `before(:context)` or `after(:context)` hooks are not
|
|
101
|
+
split, because those hooks would run once per chunk. Set `config.parallel_split_groups_with_context_hooks = true` to
|
|
102
|
+
split them anyway.
|
|
103
|
+
|
|
104
|
+
Since examples of the same file can run at the same time, tag groups that use a fixed file or directory (a browser
|
|
105
|
+
download folder, for example) with `parallel_split: false`, or give each worker its own path.
|
|
132
106
|
|
|
133
107
|
## Run times
|
|
134
108
|
|
|
135
|
-
The run time of
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
`config.parallel_run_times_file_path`). On CI, cache this file between builds.
|
|
109
|
+
The run time of each example is saved to `tmp/rspec_parallel_run_times.txt`, or to
|
|
110
|
+
`config.example_status_persistence_file_path` if you set one. The next run uses it to start the longest groups
|
|
111
|
+
first. You can change the path with `config.parallel_run_times_file_path`. On CI, cache this file between builds.
|
|
139
112
|
|
|
140
113
|
## Requirements
|
|
141
114
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
sequentially.
|
|
115
|
+
- rspec-core 3.13
|
|
116
|
+
- Ruby 3.3 or later. On JRuby and Windows, where `Process.fork` isn't available, the suite runs sequentially.
|
|
145
117
|
|
|
146
118
|
## License
|
|
147
119
|
|
|
@@ -195,6 +195,21 @@ module RSpec
|
|
|
195
195
|
end
|
|
196
196
|
end
|
|
197
197
|
|
|
198
|
+
# @private
|
|
199
|
+
module ProfileNotification
|
|
200
|
+
# Examples running in parallel take longer in total than the run:
|
|
201
|
+
# the slowest ones are then a share of the time taken by all the
|
|
202
|
+
# examples, rather than of the run time.
|
|
203
|
+
def percentage
|
|
204
|
+
@percentage ||=
|
|
205
|
+
begin
|
|
206
|
+
examples_time = examples.inject(0.0) { |total, example| total + example.execution_result.run_time.to_f }
|
|
207
|
+
time_taken = slow_duration / [duration, examples_time].max
|
|
208
|
+
'%.1f' % ((time_taken.nan? ? 0.0 : time_taken) * 100)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
198
213
|
# Order dependencies can only be reproduced in a single process.
|
|
199
214
|
#
|
|
200
215
|
# @private
|
|
@@ -247,6 +262,7 @@ module RSpec
|
|
|
247
262
|
Example.__send__(:include, Parallel::CoreExt::Example)
|
|
248
263
|
ExampleGroup.singleton_class.__send__(:prepend, Parallel::CoreExt::ExampleGroupClassMethods)
|
|
249
264
|
Reporter.__send__(:include, Parallel::CoreExt::Reporter)
|
|
265
|
+
Notifications::ProfileNotification.__send__(:prepend, Parallel::CoreExt::ProfileNotification)
|
|
250
266
|
Bisect::ShellCommand.__send__(:prepend, Parallel::CoreExt::BisectShellCommand)
|
|
251
267
|
Bisect::ForkRunner::RunDispatcher.__send__(:prepend, Parallel::CoreExt::BisectRunDispatcher)
|
|
252
268
|
end
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
module RSpec
|
|
2
2
|
module Core
|
|
3
3
|
module Parallel
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# its
|
|
10
|
-
#
|
|
11
|
-
#
|
|
4
|
+
# Collects the coverage of the workers with SimpleCov, without any
|
|
5
|
+
# configuration:
|
|
6
|
+
#
|
|
7
|
+
# * turns on SimpleCov's support for subprocesses, which starts SimpleCov
|
|
8
|
+
# again in every forked worker, and names the results of each worker
|
|
9
|
+
# after its number, so that they replace those of the previous run.
|
|
10
|
+
# Suites already enabling it keep their own `at_fork` configuration.
|
|
11
|
+
# * leaves the report to the main process. SimpleCov saves the coverage
|
|
12
|
+
# of every process at exit, and the process it considers the final one
|
|
13
|
+
# merges them, writes the report and checks the minimum coverage. It
|
|
14
|
+
# can't tell the workers from the main process (SimpleCov 1.x even
|
|
15
|
+
# takes the first worker for the final process, because of its
|
|
16
|
+
# `TEST_ENV_NUMBER`), so workers finishing early checked the coverage
|
|
17
|
+
# collected so far and printed false "below minimum coverage"
|
|
18
|
+
# warnings. The main process exits after all the workers.
|
|
12
19
|
#
|
|
13
20
|
# @private
|
|
14
21
|
module SimpleCovIntegration
|
|
@@ -19,9 +26,20 @@ module RSpec
|
|
|
19
26
|
end
|
|
20
27
|
end
|
|
21
28
|
|
|
22
|
-
def self.
|
|
29
|
+
def self.before_fork
|
|
30
|
+
@command_name = nil
|
|
31
|
+
return unless defined?(::SimpleCov) && defined?(::Coverage.running?) && ::Coverage.running?
|
|
32
|
+
return if ::SimpleCov.enabled_for_subprocesses?
|
|
33
|
+
|
|
34
|
+
@command_name = ::SimpleCov.command_name
|
|
35
|
+
::SimpleCov.enable_for_subprocesses true
|
|
36
|
+
require 'simplecov/process'
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.after_fork(worker_number)
|
|
23
40
|
return unless defined?(::SimpleCov) && ::SimpleCov.respond_to?(:final_result_process?)
|
|
24
41
|
|
|
42
|
+
::SimpleCov.command_name "#{@command_name} (worker #{worker_number})" if @command_name
|
|
25
43
|
::SimpleCov.singleton_class.__send__(:prepend, NotFinalResultProcess)
|
|
26
44
|
end
|
|
27
45
|
end
|
|
@@ -77,7 +77,7 @@ module RSpec
|
|
|
77
77
|
|
|
78
78
|
begin
|
|
79
79
|
RailsIntegration.after_fork(@number)
|
|
80
|
-
SimpleCovIntegration.after_fork
|
|
80
|
+
SimpleCovIntegration.after_fork(@number)
|
|
81
81
|
configuration.parallel_worker_hooks(:setup).each { |hook| hook.call(@number) }
|
|
82
82
|
configuration.with_suite_hooks { process_commands(registry, reporter, commands) }
|
|
83
83
|
configuration.parallel_worker_hooks(:teardown).each { |hook| hook.call(@number) }
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rspec-core-parallel
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alessandro Rodi
|
|
@@ -79,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
79
79
|
- !ruby/object:Gem::Version
|
|
80
80
|
version: '0'
|
|
81
81
|
requirements: []
|
|
82
|
-
rubygems_version:
|
|
82
|
+
rubygems_version: 4.0.20
|
|
83
83
|
specification_version: 4
|
|
84
84
|
summary: 'Run an RSpec suite in forked worker processes: rspec --parallel'
|
|
85
85
|
test_files: []
|