rspec-core-parallel 0.1.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 03f7e28ca852dcd4b84d72d272a6db18e6d8df1e9c090517613f548fbe374a5f
4
- data.tar.gz: 7f3efdb57f46ed2164770dad99c66c08925a2258a0c3440f2e1fa0b9eee3209c
3
+ metadata.gz: 77f7b9d3542750b2b8a90a8f2ec8c8730205432dbdcd2615516684939343d300
4
+ data.tar.gz: 7107cf809562b46795cb35f720b709d6892ec758bedfeba55e3df22e9214272f
5
5
  SHA512:
6
- metadata.gz: 46abf9b5942f67930ec91ac9b5c3d926ddf82f721d22c2314f0cc7663f78c7c929996b206348b8a182fd3afb0c4be91a23c947acaedbcb01cbf5c1c23161446e
7
- data.tar.gz: '0886bc56896897d0e33c9467934cbfd9e622955f3ea8606b4df7635196f8ec98dafe9a1a48c420026b15962636e48783324aa089da725a5a4f70c4b0a66657c1'
6
+ metadata.gz: c5243cfee9b3851588bd9cd041a34e9f0f090568b4519bb08942f6f4f82e3ca891b2e99f851192922f7f623728e667cfd6ffe489487733b71271b01e72008114
7
+ data.tar.gz: 0672ec4583dacff0e6e8a69e1b394b3bb197bc6ff647ef868c1db2a9ba5d7217a55c32d1c30bf6badbd8fd1bd42517049cab0a59029e35a2058ecd66fa33ab68
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.1
4
+
5
+ * No deprecation warning with SimpleCov 1.x, which renamed
6
+ `enable_for_subprocesses` to `merge_subprocesses`.
7
+
8
+ ## 0.2.0
9
+
10
+ * SimpleCov collects the coverage of the workers without any configuration.
11
+ * `--profile` shows the slowest examples as a share of the time taken by all
12
+ the examples, rather than of the run time, which could exceed 100%.
13
+ * README: require the gem in `spec_helper.rb` in Rails apps too, to configure it
14
+ there, and create the test database on CI as usual.
15
+
3
16
  ## 0.1.0
4
17
 
5
18
  * 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
- Runs an RSpec suite on all the cores of your machine. Add the gem, and `rspec`
4
- runs in parallel, with one worker process per physical CPU core.
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
- RSpec loads the suite once and forks worker processes, which share the loaded
7
- code. Groups are handed out to idle workers, longest first, and every result is
8
- sent back to the main process, which reports them like a sequential run: all
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 other gems of the `test` group. Elsewhere, require
24
- it at the top of `spec_helper.rb`:
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
- That's it: `bundle exec rspec` now runs in parallel.
26
+ Then run `bundle exec rspec` as usual.
31
27
 
32
28
  ## Number of workers
33
29
 
34
- The default is one worker per physical CPU core. Set it in `spec_helper.rb`
35
- (`1` runs the suite sequentially):
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, run `rspec-parallel`, which is `rspec` with
44
- the gem loaded before the options are read, so that it also takes
45
- `--parallel [COUNT]` (`-j`) and `--no-parallel`:
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
- Options given on the command line win over `config.parallel_workers`.
53
-
54
- ## How it compares
48
+ ## Compared to parallel_tests
55
49
 
56
- Unlike [parallel_tests](https://github.com/grosser/parallel_tests) and similar
57
- runners, which start one rspec process per CPU and split the spec files between
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
- * the app boots once, then workers are forked from it;
61
- * groups are dispatched dynamically, longest first (using the run times of the
62
- previous run), and long files are split, so all the workers finish together;
63
- * there is one combined report, and one exit code;
64
- * in Rails apps, the worker databases are created and loaded by the runner.
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
- Because every worker is a separate process, examples running at the same time
69
- never share global state (constants, stubs, `RSpec.configuration`...), so
70
- existing suites run unchanged, provided that they don't share external
71
- resources such as databases or files between processes.
72
-
73
- Rails apps need no setup: as with `rails test` in parallel, every worker gets
74
- its own test database (`app_test_0`, `app_test_1`...), created and loaded from
75
- the schema when needed. Set `ActiveSupport.parallelize_test_databases = false`
76
- to opt out.
77
-
78
- Each worker gets a `TEST_ENV_NUMBER` environment variable (`""`, `"2"`, `"3"`...,
79
- as with parallel_tests) and runs `parallel_worker_setup` hooks, which you can
80
- use to give every worker its own resources. Workers are forked once the suite
81
- is loaded, so values computed while loading it, such as a constant holding a
82
- directory or a Redis client, are the same in all of them. This includes
83
- configuration reading `TEST_ENV_NUMBER` while the app boots (as parallel_tests
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
- To collect code coverage from all the workers with SimpleCov, enable its
102
- support for forked processes:
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
- ```ruby
105
- SimpleCov.enable_for_subprocesses true
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
- To spread long groups over several workers, a group expected to take longer
124
- than a worker's share of the suite is split into its child groups and parts of
125
- its examples, unless it has `before(:context)`/`after(:context)` hooks of its
126
- own (which can be allowed with
127
- `config.parallel_split_groups_with_context_hooks = true`) or is tagged with
128
- `:parallel_split => false`. Examples of the same file can therefore run at the
129
- same time in different workers: tag groups whose examples share a file or
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 every example is saved in `tmp/rspec_parallel_run_times.txt`,
136
- or in `config.example_status_persistence_file_path` when it is set, so that the
137
- next runs hand out the longest groups first (see
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
- * rspec-core 3.13
143
- * Ruby 3.3 or later with `Process.fork`: on JRuby and Windows the suite runs
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
 
@@ -68,6 +68,7 @@ module RSpec
68
68
  def start_workers
69
69
  flush_output_streams
70
70
  RailsIntegration.before_fork
71
+ SimpleCovIntegration.before_fork
71
72
  Process.warmup if Process.respond_to?(:warmup)
72
73
 
73
74
  @workers = []
@@ -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
- # Leaves SimpleCov's report to the main process. SimpleCov saves the
5
- # coverage of every process at exit, and the process it considers the
6
- # final one merges them, writes the report and checks the minimum
7
- # coverage. It can't tell the workers from the main process (SimpleCov
8
- # 1.x even takes the first worker for the final process, because of
9
- # its `TEST_ENV_NUMBER`), so workers finishing early checked the
10
- # coverage collected so far and printed false "below minimum coverage"
11
- # warnings. The main process exits after all the workers.
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,25 @@ module RSpec
19
26
  end
20
27
  end
21
28
 
22
- def self.after_fork
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 1.x renamed `enable_for_subprocesses`, deprecating it.
36
+ if ::SimpleCov.respond_to?(:merge_subprocesses)
37
+ ::SimpleCov.merge_subprocesses true
38
+ else
39
+ ::SimpleCov.enable_for_subprocesses true
40
+ end
41
+ require 'simplecov/process'
42
+ end
43
+
44
+ def self.after_fork(worker_number)
23
45
  return unless defined?(::SimpleCov) && ::SimpleCov.respond_to?(:final_result_process?)
24
46
 
47
+ ::SimpleCov.command_name "#{@command_name} (worker #{worker_number})" if @command_name
25
48
  ::SimpleCov.singleton_class.__send__(:prepend, NotFinalResultProcess)
26
49
  end
27
50
  end
@@ -1,7 +1,7 @@
1
1
  module RSpec
2
2
  module Core
3
3
  module Parallel
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
6
6
  end
7
7
  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.1.0
4
+ version: 0.2.1
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: 3.6.9
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: []