rspec-core-parallel 0.2.1 → 0.4.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 +13 -0
- data/README.md +15 -1
- data/lib/rspec/core/parallel/core_ext.rb +67 -1
- data/lib/rspec/core/parallel/rails_integration.rb +32 -0
- data/lib/rspec/core/parallel/simplecov_integration.rb +14 -0
- data/lib/rspec/core/parallel/version.rb +1 -1
- data/lib/rspec/core/parallel.rb +1 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 959cdd2e6744a83648486825d673ea9164f39f2aa0fbafafd2f4b49eda5163ae
|
|
4
|
+
data.tar.gz: 8657334668c6e6aaeb8ceca2fabf5c413d1860f19379e34d463a26b01bcdb837
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d12df7ed1e4abe97795bc1643779d16dd9c348458121952733e974340ae5024abe3e7b18840c308294b8955b5294cb77353506276e0428dc5dfd6775f9080430
|
|
7
|
+
data.tar.gz: '02925aa2a3aff6acbe74d6c499802d247942cbc83336cba8dce061cba2d41fe73a62d230cac84f26e1b2e70ed73f94414a5c264cd509735fa1226644c6816b2e'
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
* In Rails apps every worker writes to its own log file (`log/test-1.log`,
|
|
6
|
+
`log/test-2.log`...) instead of `log/test.log`.
|
|
7
|
+
|
|
8
|
+
## 0.3.0
|
|
9
|
+
|
|
10
|
+
* `before(:run)` and `after(:run)` hooks, which run once per run: in the main
|
|
11
|
+
process around the workers in parallel runs, around the `:suite` hooks in
|
|
12
|
+
sequential runs.
|
|
13
|
+
* SimpleCov merges the coverage of workers that finished more than its
|
|
14
|
+
`merge_timeout` (10 minutes by default) before the slowest one.
|
|
15
|
+
|
|
3
16
|
## 0.2.1
|
|
4
17
|
|
|
5
18
|
* No deprecation warning with SimpleCov 1.x, which renamed
|
data/README.md
CHANGED
|
@@ -68,6 +68,9 @@ loaded from the schema when needed, like `rails test` does in parallel. Set
|
|
|
68
68
|
(`maintain_test_schema!` runs before the workers are forked), so on CI create it as usual, e.g. with
|
|
69
69
|
`bin/rails db:create db:schema:load`, rather than with `parallel:create`.
|
|
70
70
|
|
|
71
|
+
**Logs.** In Rails apps every worker writes to its own log file (`log/test-1.log`, `log/test-2.log`, ...), so that the
|
|
72
|
+
lines of the workers don't mix in `log/test.log`, which keeps the lines of the main process.
|
|
73
|
+
|
|
71
74
|
**Other resources.** Each worker gets a `TEST_ENV_NUMBER` environment variable (`""`, `"2"`, `"3"`, ...) as with
|
|
72
75
|
parallel_tests, and runs the `parallel_worker_setup` hooks. Workers are forked after the suite is loaded, so anything
|
|
73
76
|
set up while loading, like a constant with a directory or a Redis client, is the same in all of them. This also applies
|
|
@@ -83,7 +86,18 @@ RSpec.configure do |config|
|
|
|
83
86
|
end
|
|
84
87
|
```
|
|
85
88
|
|
|
86
|
-
**Hooks.** `before(:suite)`, `after(:suite)` and `at_exit` hooks run in every worker.
|
|
89
|
+
**Hooks.** `before(:suite)`, `after(:suite)` and `at_exit` hooks run in every worker. For what all the workers share,
|
|
90
|
+
like starting a service or cleaning it up, use `before(:run)` and `after(:run)`: they run once, in the main process,
|
|
91
|
+
before the workers are forked and after they have exited. In sequential runs they run around the `:suite` hooks.
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
RSpec.configure do |config|
|
|
95
|
+
config.before(:run) { TestServer.start }
|
|
96
|
+
config.after(:run) { TestServer.stop }
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
When a `before(:run)` hook fails, no example runs, but the `after(:run)` hooks still do.
|
|
87
101
|
|
|
88
102
|
## Code coverage
|
|
89
103
|
|
|
@@ -107,8 +107,74 @@ module RSpec
|
|
|
107
107
|
Parallel.supported? ? Parallel.default_worker_count : 1
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
+
# `before(:run)` and `after(:run)` hooks run once per run: around the
|
|
111
|
+
# `:suite` hooks in sequential runs, and in the main process around
|
|
112
|
+
# the workers in parallel runs, where `:suite` hooks run in every
|
|
113
|
+
# worker. Use them for what all the workers share, like starting a
|
|
114
|
+
# service or cleaning it up.
|
|
115
|
+
def before(scope=nil, *meta, &block)
|
|
116
|
+
return super unless scope == :run
|
|
117
|
+
|
|
118
|
+
run_hooks(:before) << Hooks::BeforeHook.new(block, {})
|
|
119
|
+
end
|
|
120
|
+
alias_method :append_before, :before
|
|
121
|
+
|
|
122
|
+
def prepend_before(scope=nil, *meta, &block)
|
|
123
|
+
return super unless scope == :run
|
|
124
|
+
|
|
125
|
+
run_hooks(:before).unshift Hooks::BeforeHook.new(block, {})
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def after(scope=nil, *meta, &block)
|
|
129
|
+
return super unless scope == :run
|
|
130
|
+
|
|
131
|
+
run_hooks(:after).unshift Hooks::AfterHook.new(block, {})
|
|
132
|
+
end
|
|
133
|
+
alias_method :prepend_after, :after
|
|
134
|
+
|
|
135
|
+
def append_after(scope=nil, *meta, &block)
|
|
136
|
+
return super unless scope == :run
|
|
137
|
+
|
|
138
|
+
run_hooks(:after) << Hooks::AfterHook.new(block, {})
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def with_suite_hooks
|
|
142
|
+
return super if parallel_worker_number
|
|
143
|
+
|
|
144
|
+
with_run_hooks { super }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# @private
|
|
148
|
+
#
|
|
149
|
+
# Examples are not run when a `before(:run)` hook fails, but all the
|
|
150
|
+
# `after(:run)` hooks are, so that everything started is stopped.
|
|
151
|
+
def with_run_hooks
|
|
152
|
+
return yield if dry_run?
|
|
153
|
+
|
|
154
|
+
begin
|
|
155
|
+
run_run_hooks('a `before(:run)` hook', run_hooks(:before), :stop_on_failure) ? yield : false
|
|
156
|
+
ensure
|
|
157
|
+
run_run_hooks('an `after(:run)` hook', run_hooks(:after))
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
110
161
|
private
|
|
111
162
|
|
|
163
|
+
def run_hooks(position)
|
|
164
|
+
(@run_hooks ||= { :before => [], :after => [] })[position]
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def run_run_hooks(description, hooks, stop_on_failure=false)
|
|
168
|
+
context = SuiteHookContext.new(description, reporter)
|
|
169
|
+
hooks.each do |hook|
|
|
170
|
+
hook.run(context)
|
|
171
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue => e
|
|
172
|
+
context.set_exception(e)
|
|
173
|
+
return false if stop_on_failure
|
|
174
|
+
end
|
|
175
|
+
true
|
|
176
|
+
end
|
|
177
|
+
|
|
112
178
|
# rspec-core runs the default path (`spec`) when no files are given
|
|
113
179
|
# only when its command is `rspec`.
|
|
114
180
|
def command
|
|
@@ -148,7 +214,7 @@ module RSpec
|
|
|
148
214
|
|
|
149
215
|
examples_count = @world.example_count(example_groups)
|
|
150
216
|
examples_passed = @configuration.reporter.report(examples_count) do |reporter|
|
|
151
|
-
Parallel.run(@configuration, @world, parallel_units, reporter)
|
|
217
|
+
@configuration.with_run_hooks { Parallel.run(@configuration, @world, parallel_units, reporter) }
|
|
152
218
|
end
|
|
153
219
|
|
|
154
220
|
exit_code(examples_passed)
|
|
@@ -11,6 +11,8 @@ module RSpec
|
|
|
11
11
|
# `ActiveSupport.parallelize_test_databases` to `false` to opt out.
|
|
12
12
|
# Workers are numbered from 0 for these hooks, as in Rails. Rails
|
|
13
13
|
# versions before 8.1 have no hooks to run before forking.
|
|
14
|
+
# * gives every worker its own log file (`log/test-1.log`, `log/test-2.log`...),
|
|
15
|
+
# rather than mixing the lines of all the workers in `log/test.log`.
|
|
14
16
|
# * loads the translations before forking, as Rails does when eager
|
|
15
17
|
# loading, rather than in every worker.
|
|
16
18
|
#
|
|
@@ -22,9 +24,39 @@ module RSpec
|
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def self.after_fork(worker_number)
|
|
27
|
+
split_log(worker_number)
|
|
25
28
|
hooks(:after_fork_hooks).each { |hook| hook.call(worker_number - 1) }
|
|
26
29
|
end
|
|
27
30
|
|
|
31
|
+
# Rails opens the log file itself and hands the open file to its
|
|
32
|
+
# logger, so the path comes from the file the logger writes to.
|
|
33
|
+
# Loggers writing elsewhere, like to STDOUT, are left as they are.
|
|
34
|
+
def self.split_log(worker_number)
|
|
35
|
+
return unless defined?(::Rails.logger) && ::Rails.logger
|
|
36
|
+
|
|
37
|
+
loggers = ::Rails.logger.respond_to?(:broadcasts) ? ::Rails.logger.broadcasts : [::Rails.logger]
|
|
38
|
+
loggers.each do |logger|
|
|
39
|
+
device = logger.instance_variable_get(:@logdev)&.dev
|
|
40
|
+
next unless device.is_a?(::File)
|
|
41
|
+
|
|
42
|
+
logger.reopen(open_log_file(worker_log_path(device.path, worker_number)))
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.worker_log_path(path, worker_number)
|
|
47
|
+
extension = ::File.extname(path)
|
|
48
|
+
"#{path.delete_suffix(extension)}-#{worker_number}#{extension}"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# As Rails opens its own log file: in binary mode, so that lines in
|
|
52
|
+
# any encoding can be written, and without buffering.
|
|
53
|
+
def self.open_log_file(path)
|
|
54
|
+
file = ::File.open(path, 'a')
|
|
55
|
+
file.binmode
|
|
56
|
+
file.sync = true
|
|
57
|
+
file
|
|
58
|
+
end
|
|
59
|
+
|
|
28
60
|
def self.run_cleanup(worker_number)
|
|
29
61
|
hooks(:run_cleanup_hooks).each { |hook| hook.call(worker_number - 1) }
|
|
30
62
|
end
|
|
@@ -16,6 +16,8 @@ module RSpec
|
|
|
16
16
|
# `TEST_ENV_NUMBER`), so workers finishing early checked the coverage
|
|
17
17
|
# collected so far and printed false "below minimum coverage"
|
|
18
18
|
# warnings. The main process exits after all the workers.
|
|
19
|
+
# * extends SimpleCov's `merge_timeout` by the time the workers ran, so
|
|
20
|
+
# that the main process merges the results of all of them.
|
|
19
21
|
#
|
|
20
22
|
# @private
|
|
21
23
|
module SimpleCovIntegration
|
|
@@ -28,7 +30,10 @@ module RSpec
|
|
|
28
30
|
|
|
29
31
|
def self.before_fork
|
|
30
32
|
@command_name = nil
|
|
33
|
+
@forked_at = nil
|
|
31
34
|
return unless defined?(::SimpleCov) && defined?(::Coverage.running?) && ::Coverage.running?
|
|
35
|
+
|
|
36
|
+
@forked_at = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
32
37
|
return if ::SimpleCov.enabled_for_subprocesses?
|
|
33
38
|
|
|
34
39
|
@command_name = ::SimpleCov.command_name
|
|
@@ -47,6 +52,15 @@ module RSpec
|
|
|
47
52
|
::SimpleCov.command_name "#{@command_name} (worker #{worker_number})" if @command_name
|
|
48
53
|
::SimpleCov.singleton_class.__send__(:prepend, NotFinalResultProcess)
|
|
49
54
|
end
|
|
55
|
+
|
|
56
|
+
# SimpleCov merges only results younger than `merge_timeout`: those of
|
|
57
|
+
# workers finished long before the slowest one would be left out.
|
|
58
|
+
def self.after_workers
|
|
59
|
+
return unless @forked_at && ::SimpleCov.respond_to?(:merge_timeout)
|
|
60
|
+
|
|
61
|
+
workers_time = (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - @forked_at).ceil
|
|
62
|
+
::SimpleCov.merge_timeout(::SimpleCov.merge_timeout + workers_time)
|
|
63
|
+
end
|
|
50
64
|
end
|
|
51
65
|
end
|
|
52
66
|
end
|
data/lib/rspec/core/parallel.rb
CHANGED
|
@@ -51,6 +51,7 @@ module RSpec
|
|
|
51
51
|
def self.run(configuration, world, units, reporter)
|
|
52
52
|
worker_count = [configuration.parallel_workers.to_i, units.size].min
|
|
53
53
|
passed = Coordinator.new(configuration, world, worker_count).run(units, reporter)
|
|
54
|
+
SimpleCovIntegration.after_workers
|
|
54
55
|
persist_run_times(configuration, world)
|
|
55
56
|
passed
|
|
56
57
|
end
|