parallel_tests 4.2.2 → 4.6.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/Readme.md +44 -9
- data/lib/parallel_tests/cli.rb +1 -0
- data/lib/parallel_tests/rspec/runner.rb +3 -1
- data/lib/parallel_tests/rspec/runtime_logger.rb +2 -0
- data/lib/parallel_tests/rspec/verbose_logger.rb +62 -0
- data/lib/parallel_tests/spinach/runner.rb +1 -1
- data/lib/parallel_tests/tasks.rb +74 -33
- data/lib/parallel_tests/test/runner.rb +5 -2
- data/lib/parallel_tests/version.rb +1 -1
- data/lib/parallel_tests.rb +2 -0
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 82339af23324161c5818c27e55fdadcbcac91ab832f76ce791a6deb289f77233
|
4
|
+
data.tar.gz: 0fd923d52f7e368ef4c8e99e7874a465c7213960b1fd6e455404e472dc00ab90
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee5cb110a617b78e8761c5e01894827b63cccbe6a239b491841949bf2ebe2e9d68a43d4bb0d0a5600cdc6904e8cffba816eb424ca5883865daf661cf7b208f91
|
7
|
+
data.tar.gz: e23de90f4aed3c4571252385a251c3765718c3e2fd34aaf342307667b6710819aff652b4c1709dab2152fe0b81217f2d2fb1b98cf308d8c40e3190d91374337d
|
data/Readme.md
CHANGED
@@ -3,8 +3,8 @@
|
|
3
3
|
[](https://rubygems.org/gems/parallel_tests)
|
4
4
|
[](https://github.com/grosser/parallel_tests/actions?query=workflow%3Atest)
|
5
5
|
|
6
|
-
Speedup
|
7
|
-
ParallelTests splits tests into
|
6
|
+
Speedup Minitest + RSpec + Turnip + Cucumber + Spinach by running parallel on multiple CPU cores.<br/>
|
7
|
+
ParallelTests splits tests into balanced groups (by number of lines or runtime) and runs each group in a process with its own database.
|
8
8
|
|
9
9
|
Setup for Rails
|
10
10
|
===============
|
@@ -33,20 +33,30 @@ test:
|
|
33
33
|
### Create additional database(s)
|
34
34
|
rake parallel:create
|
35
35
|
|
36
|
+
### (Multi-DB) Create individual database
|
37
|
+
rake parallel:create:<database>
|
38
|
+
rake parallel:create:secondary
|
39
|
+
|
36
40
|
### Copy development schema (repeat after migrations)
|
37
41
|
rake parallel:prepare
|
38
42
|
|
39
43
|
### Run migrations in additional database(s) (repeat after migrations)
|
40
44
|
rake parallel:migrate
|
41
45
|
|
46
|
+
### (Multi-DB) Run migrations in individual database
|
47
|
+
rake parallel:migrate:<database>
|
48
|
+
|
42
49
|
### Setup environment from scratch (create db and loads schema, useful for CI)
|
43
50
|
rake parallel:setup
|
44
51
|
|
45
52
|
### Drop all test databases
|
46
53
|
rake parallel:drop
|
47
54
|
|
55
|
+
### (Multi-DB) Drop individual test database
|
56
|
+
rake parallel:drop:<database>
|
57
|
+
|
48
58
|
### Run!
|
49
|
-
rake parallel:test #
|
59
|
+
rake parallel:test # Minitest
|
50
60
|
rake parallel:spec # RSpec
|
51
61
|
rake parallel:features # Cucumber
|
52
62
|
rake parallel:features-spinach # Spinach
|
@@ -106,11 +116,13 @@ at_exit do
|
|
106
116
|
end
|
107
117
|
```
|
108
118
|
|
109
|
-
Even test group
|
110
|
-
|
119
|
+
Even test group runtimes
|
120
|
+
========================
|
121
|
+
|
122
|
+
Test groups will often run for different times, making the full test run as slow as the slowest group.
|
111
123
|
|
112
|
-
|
113
|
-
|
124
|
+
Step 1: Use these loggers (see below) to record test runtime
|
125
|
+
Step 2: Your next run will use the recorded test runtimes (use `--runtime-log <file>` if you picked a location different from below)
|
114
126
|
|
115
127
|
### RSpec
|
116
128
|
|
@@ -128,9 +140,11 @@ Add to your `test_helper.rb`:
|
|
128
140
|
require 'parallel_tests/test/runtime_logger' if ENV['RECORD_RUNTIME']
|
129
141
|
```
|
130
142
|
|
131
|
-
results will be logged to tmp/parallel_runtime_test.log when `RECORD_RUNTIME` is set,
|
143
|
+
results will be logged to `tmp/parallel_runtime_test.log` when `RECORD_RUNTIME` is set,
|
132
144
|
so it is not always required or overwritten.
|
133
145
|
|
146
|
+
### TODO: add instructions for other frameworks
|
147
|
+
|
134
148
|
Loggers
|
135
149
|
=======
|
136
150
|
|
@@ -147,7 +161,7 @@ Add the following to your `.rspec_parallel` (or `.rspec`) :
|
|
147
161
|
RSpec: FailuresLogger
|
148
162
|
-----------------------
|
149
163
|
|
150
|
-
Produce
|
164
|
+
Produce pasteable command-line snippets for each failed example. For example:
|
151
165
|
|
152
166
|
```bash
|
153
167
|
rspec /path/to/my_spec.rb:123 # should do something
|
@@ -160,6 +174,24 @@ Add to `.rspec_parallel` or use as CLI flag:
|
|
160
174
|
|
161
175
|
(Not needed to retry failures, for that pass [--only-failures](https://relishapp.com/rspec/rspec-core/docs/command-line/only-failures) to rspec)
|
162
176
|
|
177
|
+
|
178
|
+
RSpec: VerboseLogger
|
179
|
+
-----------------------
|
180
|
+
|
181
|
+
Prints a single line for starting and finishing each example, to see what is currently running in each process.
|
182
|
+
|
183
|
+
```
|
184
|
+
# PID, parallel process number, spec status, example description
|
185
|
+
[14403] [2] [STARTED] Foo foo
|
186
|
+
[14402] [1] [STARTED] Bar bar
|
187
|
+
[14402] [1] [PASSED] Bar bar
|
188
|
+
```
|
189
|
+
|
190
|
+
Add to `.rspec_parallel` or use as CLI flag:
|
191
|
+
|
192
|
+
--format ParallelTests::RSpec::VerboseLogger
|
193
|
+
|
194
|
+
|
163
195
|
Cucumber: FailuresLogger
|
164
196
|
-----------------------
|
165
197
|
|
@@ -247,6 +279,7 @@ Options are:
|
|
247
279
|
--nice execute test commands with low priority.
|
248
280
|
--runtime-log [PATH] Location of previously recorded test runtimes
|
249
281
|
--allowed-missing [INT] Allowed percentage of missing runtimes (default = 50)
|
282
|
+
--allow-duplicates When detecting files to run, allow duplicates. Useful for local debugging
|
250
283
|
--unknown-runtime [FLOAT] Use given number as unknown runtime (otherwise use average time)
|
251
284
|
--first-is-1 Use "1" as TEST_ENV_NUMBER to not reuse the default test environment
|
252
285
|
--fail-fast Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported
|
@@ -405,6 +438,8 @@ inspired by [pivotal labs](https://blog.pivotal.io/labs/labs/parallelize-your-rs
|
|
405
438
|
- [Jon Dufresne](https://github.com/jdufresne)
|
406
439
|
- [Eric Kessler](https://github.com/enkessler)
|
407
440
|
- [Adis Osmonov](https://github.com/adis-io)
|
441
|
+
- [Josh Westbrook](https://github.com/joshwestbrook)
|
442
|
+
- [Jay Dorsey](https://github.com/jaydorsey)
|
408
443
|
|
409
444
|
[Michael Grosser](http://grosser.it)<br/>
|
410
445
|
michael@grosser.it<br/>
|
data/lib/parallel_tests/cli.rb
CHANGED
@@ -278,6 +278,7 @@ module ParallelTests
|
|
278
278
|
opts.on("--nice", "execute test commands with low priority.") { options[:nice] = true }
|
279
279
|
opts.on("--runtime-log [PATH]", "Location of previously recorded test runtimes") { |path| options[:runtime_log] = path }
|
280
280
|
opts.on("--allowed-missing [INT]", Integer, "Allowed percentage of missing runtimes (default = 50)") { |percent| options[:allowed_missing_percent] = percent }
|
281
|
+
opts.on('--allow-duplicates', 'When detecting files to run, allow duplicates') { options[:allow_duplicates] = true }
|
281
282
|
opts.on("--unknown-runtime [FLOAT]", Float, "Use given number as unknown runtime (otherwise use average time)") { |time| options[:unknown_runtime] = time }
|
282
283
|
opts.on("--first-is-1", "Use \"1\" as TEST_ENV_NUMBER to not reuse the default test environment") { options[:first_is_1] = true }
|
283
284
|
opts.on("--fail-fast", "Stop all groups when one group fails (best used with --test-options '--fail-fast' if supported") { options[:fail_fast] = true }
|
@@ -36,6 +36,8 @@ class ParallelTests::RSpec::RuntimeLogger < ParallelTests::RSpec::LoggerBase
|
|
36
36
|
def start_dump(*)
|
37
37
|
return unless ENV['TEST_ENV_NUMBER'] # only record when running in parallel
|
38
38
|
lock_output do
|
39
|
+
# Order the output from slowest to fastest
|
40
|
+
@example_times = @example_times.sort_by(&:last).reverse
|
39
41
|
@example_times.each do |file, time|
|
40
42
|
relative_path = file.sub(%r{^#{Regexp.escape Dir.pwd}/}, '').sub(%r{^\./}, "")
|
41
43
|
@output.puts "#{relative_path}:#{[time, 0].max}"
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rspec/core/formatters/base_text_formatter'
|
4
|
+
require 'parallel_tests/rspec/runner'
|
5
|
+
|
6
|
+
class ParallelTests::RSpec::VerboseLogger < RSpec::Core::Formatters::BaseTextFormatter
|
7
|
+
RSpec::Core::Formatters.register(
|
8
|
+
self,
|
9
|
+
:example_group_started,
|
10
|
+
:example_group_finished,
|
11
|
+
:example_started,
|
12
|
+
:example_passed,
|
13
|
+
:example_pending,
|
14
|
+
:example_failed
|
15
|
+
)
|
16
|
+
|
17
|
+
def initialize(output)
|
18
|
+
super
|
19
|
+
@line = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def example_group_started(notification)
|
23
|
+
@line.push(notification.group.description)
|
24
|
+
end
|
25
|
+
|
26
|
+
def example_group_finished(_notification)
|
27
|
+
@line.pop
|
28
|
+
end
|
29
|
+
|
30
|
+
def example_started(notification)
|
31
|
+
@line.push(notification.example.description)
|
32
|
+
output_formatted_line('STARTED', :yellow)
|
33
|
+
end
|
34
|
+
|
35
|
+
def example_passed(_passed)
|
36
|
+
output_formatted_line('PASSED', :success)
|
37
|
+
@line.pop
|
38
|
+
end
|
39
|
+
|
40
|
+
def example_pending(_pending)
|
41
|
+
output_formatted_line('PENDING', :pending)
|
42
|
+
@line.pop
|
43
|
+
end
|
44
|
+
|
45
|
+
def example_failed(_failure)
|
46
|
+
output_formatted_line('FAILED', :failure)
|
47
|
+
@line.pop
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def output_formatted_line(status, console_code)
|
53
|
+
prefix = ["[#{Process.pid}]"]
|
54
|
+
if ENV.include?('TEST_ENV_NUMBER')
|
55
|
+
test_env_number = ENV['TEST_ENV_NUMBER'] == '' ? 1 : Integer(ENV['TEST_ENV_NUMBER'])
|
56
|
+
prefix << "[#{test_env_number}]"
|
57
|
+
end
|
58
|
+
prefix << RSpec::Core::Formatters::ConsoleCodes.wrap("[#{status}]", console_code)
|
59
|
+
|
60
|
+
output.puts [*prefix, *@line].join(' ')
|
61
|
+
end
|
62
|
+
end
|
data/lib/parallel_tests/tasks.rb
CHANGED
@@ -48,6 +48,9 @@ module ParallelTests
|
|
48
48
|
activate_pipefail = "set -o pipefail"
|
49
49
|
remove_ignored_lines = %{(grep -v #{Shellwords.escape(ignore_regex)} || true)}
|
50
50
|
|
51
|
+
# remove nil values (ex: #purge_before_load returns nil)
|
52
|
+
command.compact!
|
53
|
+
|
51
54
|
if system('/bin/bash', '-c', "#{activate_pipefail} 2>/dev/null")
|
52
55
|
shell_command = "#{activate_pipefail} && (#{Shellwords.shelljoin(command)}) | #{remove_ignored_lines}"
|
53
56
|
['/bin/bash', '-c', shell_command]
|
@@ -125,6 +128,24 @@ module ParallelTests
|
|
125
128
|
command
|
126
129
|
end
|
127
130
|
|
131
|
+
def configured_databases
|
132
|
+
return [] unless defined?(ActiveRecord) && rails_61_or_greater?
|
133
|
+
|
134
|
+
@@configured_databases ||= ActiveRecord::Tasks::DatabaseTasks.setup_initial_database_yaml
|
135
|
+
end
|
136
|
+
|
137
|
+
def for_each_database(&block)
|
138
|
+
# Use nil to represent all databases
|
139
|
+
block&.call(nil)
|
140
|
+
|
141
|
+
# skip if not rails or old rails version
|
142
|
+
return if !defined?(ActiveRecord::Tasks::DatabaseTasks) || !ActiveRecord::Tasks::DatabaseTasks.respond_to?(:for_each)
|
143
|
+
|
144
|
+
ActiveRecord::Tasks::DatabaseTasks.for_each(configured_databases) do |name|
|
145
|
+
block&.call(name)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
128
149
|
private
|
129
150
|
|
130
151
|
def rails_7_or_greater?
|
@@ -145,25 +166,33 @@ namespace :parallel do
|
|
145
166
|
ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
|
146
167
|
end
|
147
168
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
169
|
+
ParallelTests::Tasks.for_each_database do |name|
|
170
|
+
task_name = 'create'
|
171
|
+
task_name += ":#{name}" if name
|
172
|
+
desc "Create test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
|
173
|
+
task task_name.to_sym, :count do |_, args|
|
174
|
+
ParallelTests::Tasks.run_in_parallel(
|
175
|
+
[$0, "db:#{task_name}", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
|
176
|
+
args
|
177
|
+
)
|
178
|
+
end
|
154
179
|
end
|
155
180
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
181
|
+
ParallelTests::Tasks.for_each_database do |name|
|
182
|
+
task_name = 'drop'
|
183
|
+
task_name += ":#{name}" if name
|
184
|
+
desc "Drop test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
|
185
|
+
task task_name.to_sym, :count do |_, args|
|
186
|
+
ParallelTests::Tasks.run_in_parallel(
|
187
|
+
[
|
188
|
+
$0,
|
189
|
+
"db:#{task_name}",
|
190
|
+
"RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
|
191
|
+
"DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
192
|
+
],
|
193
|
+
args
|
194
|
+
)
|
195
|
+
end
|
167
196
|
end
|
168
197
|
|
169
198
|
desc "Update test databases by dumping and loading --> parallel:prepare[num_cpus]"
|
@@ -190,12 +219,16 @@ namespace :parallel do
|
|
190
219
|
end
|
191
220
|
|
192
221
|
# when dumping/resetting takes too long
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
222
|
+
ParallelTests::Tasks.for_each_database do |name|
|
223
|
+
task_name = 'migrate'
|
224
|
+
task_name += ":#{name}" if name
|
225
|
+
desc "Update test#{" #{name}" if name} database via db:#{task_name} --> parallel:#{task_name}[num_cpus]"
|
226
|
+
task task_name.to_sym, :count do |_, args|
|
227
|
+
ParallelTests::Tasks.run_in_parallel(
|
228
|
+
[$0, "db:#{task_name}", "RAILS_ENV=#{ParallelTests::Tasks.rails_env}"],
|
229
|
+
args
|
230
|
+
)
|
231
|
+
end
|
199
232
|
end
|
200
233
|
|
201
234
|
desc "Rollback test databases via db:rollback --> parallel:rollback[num_cpus]"
|
@@ -207,16 +240,24 @@ namespace :parallel do
|
|
207
240
|
end
|
208
241
|
|
209
242
|
# just load the schema (good for integration server <-> no development db)
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
243
|
+
ParallelTests::Tasks.for_each_database do |name|
|
244
|
+
rails_task = 'db:schema:load'
|
245
|
+
rails_task += ":#{name}" if name
|
246
|
+
|
247
|
+
task_name = 'load_schema'
|
248
|
+
task_name += ":#{name}" if name
|
249
|
+
|
250
|
+
desc "Load dumped schema for test#{" #{name}" if name} database via #{rails_task} --> parallel:#{task_name}[num_cpus]"
|
251
|
+
task task_name.to_sym, :count do |_, args|
|
252
|
+
command = [
|
253
|
+
$0,
|
254
|
+
ParallelTests::Tasks.purge_before_load,
|
255
|
+
rails_task,
|
256
|
+
"RAILS_ENV=#{ParallelTests::Tasks.rails_env}",
|
257
|
+
"DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
|
258
|
+
]
|
259
|
+
ParallelTests::Tasks.run_in_parallel(ParallelTests::Tasks.suppress_schema_load_output(command), args)
|
260
|
+
end
|
220
261
|
end
|
221
262
|
|
222
263
|
# load the structure from the structure.sql file
|
@@ -238,8 +238,9 @@ module ParallelTests
|
|
238
238
|
suffix_pattern = options[:suffix] || test_suffix
|
239
239
|
include_pattern = options[:pattern] || //
|
240
240
|
exclude_pattern = options[:exclude_pattern]
|
241
|
+
allow_duplicates = options[:allow_duplicates]
|
241
242
|
|
242
|
-
(tests || []).flat_map do |file_or_folder|
|
243
|
+
files = (tests || []).flat_map do |file_or_folder|
|
243
244
|
if File.directory?(file_or_folder)
|
244
245
|
files = files_in_folder(file_or_folder, options)
|
245
246
|
files = files.grep(suffix_pattern).grep(include_pattern)
|
@@ -248,7 +249,9 @@ module ParallelTests
|
|
248
249
|
else
|
249
250
|
file_or_folder
|
250
251
|
end
|
251
|
-
end
|
252
|
+
end
|
253
|
+
|
254
|
+
allow_duplicates ? files : files.uniq
|
252
255
|
end
|
253
256
|
|
254
257
|
def files_in_folder(folder, options = {})
|
data/lib/parallel_tests.rb
CHANGED
@@ -44,6 +44,8 @@ module ParallelTests
|
|
44
44
|
|
45
45
|
def stop_all_processes
|
46
46
|
pids.all.each { |pid| Process.kill(:INT, pid) }
|
47
|
+
rescue Errno::ESRCH
|
48
|
+
# Process already terminated, do nothing
|
47
49
|
end
|
48
50
|
|
49
51
|
# copied from http://github.com/carlhuda/bundler Bundler::SharedHelpers#find_gemfile
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parallel_tests
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Grosser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parallel
|
@@ -58,6 +58,7 @@ files:
|
|
58
58
|
- lib/parallel_tests/rspec/runner.rb
|
59
59
|
- lib/parallel_tests/rspec/runtime_logger.rb
|
60
60
|
- lib/parallel_tests/rspec/summary_logger.rb
|
61
|
+
- lib/parallel_tests/rspec/verbose_logger.rb
|
61
62
|
- lib/parallel_tests/spinach/runner.rb
|
62
63
|
- lib/parallel_tests/tasks.rb
|
63
64
|
- lib/parallel_tests/test/runner.rb
|
@@ -68,8 +69,8 @@ licenses:
|
|
68
69
|
- MIT
|
69
70
|
metadata:
|
70
71
|
bug_tracker_uri: https://github.com/grosser/parallel_tests/issues
|
71
|
-
documentation_uri: https://github.com/grosser/parallel_tests/blob/v4.
|
72
|
-
source_code_uri: https://github.com/grosser/parallel_tests/tree/v4.
|
72
|
+
documentation_uri: https://github.com/grosser/parallel_tests/blob/v4.6.0/Readme.md
|
73
|
+
source_code_uri: https://github.com/grosser/parallel_tests/tree/v4.6.0
|
73
74
|
wiki_uri: https://github.com/grosser/parallel_tests/wiki
|
74
75
|
post_install_message:
|
75
76
|
rdoc_options: []
|
@@ -86,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
87
|
- !ruby/object:Gem::Version
|
87
88
|
version: '0'
|
88
89
|
requirements: []
|
89
|
-
rubygems_version: 3.
|
90
|
+
rubygems_version: 3.4.10
|
90
91
|
signing_key:
|
91
92
|
specification_version: 4
|
92
93
|
summary: Run Test::Unit / RSpec / Cucumber / Spinach in parallel
|