minitest-parallel_fork 1.3.1 → 2.1.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 +12 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +25 -0
- data/lib/minitest/parallel_fork/fail_fast.rb +17 -0
- data/lib/minitest/parallel_fork/halt.rb +55 -0
- data/lib/minitest/parallel_fork/interrupt.rb +45 -0
- data/lib/minitest/parallel_fork.rb +100 -53
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 617125ad38c5fbb98d110eb3374a6f4ffac5d95ffedfed197c6354e7b273d7ef
|
4
|
+
data.tar.gz: 7e4768a75b06806da43547902dc02c89ae793a907ea2dc0f00dc18a5bf69ec82
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79d9a44f01350fe19f9e50cfdbef0957b692ddf2fc5322ba91d2ceadf3c0e6c091681e08c8c3b5b86f9115ae3ac80055aa9c5eea8e88f076be5d9b161a888e9d
|
7
|
+
data.tar.gz: e86395fb13775e0a4d11249639446ff64a5ca8743350f261cfc8f01b6b4416162e02d15b9977071f75801b95a4fe51a07832749e0493eeb78d67a2b90cd2d63c
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
=== 2.1.0 (2025-07-02)
|
2
|
+
|
3
|
+
* Add minitest/parallel_fork/interrupt with support handling interrupts (stackmystack, jeremyevans) (#14)
|
4
|
+
|
5
|
+
=== 2.0.0 (2023-11-08)
|
6
|
+
|
7
|
+
* Avoid method redefined warning in verbose warning mode (jeremyevans)
|
8
|
+
|
9
|
+
* Add minitest/parallel_fork/fail_fast with support for stopping execution as soon as there is a failure in any child process (jeremyevans, stackmystack) (#12)
|
10
|
+
|
11
|
+
* Refactor internals for easier extension (jeremyevans)
|
12
|
+
|
1
13
|
=== 1.3.1 (2023-09-25)
|
2
14
|
|
3
15
|
* Bump required_ruby_version to 2.2, since that is lowest version supported by minitest 5.15.0 (jeremyevans)
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -68,6 +68,31 @@ it reports results.
|
|
68
68
|
# Gather relevant logs for more debugging
|
69
69
|
end
|
70
70
|
|
71
|
+
== Fail Fast Support
|
72
|
+
|
73
|
+
If you would like to run tests in parallel, but stop running tests at the first
|
74
|
+
failure, you can use:
|
75
|
+
|
76
|
+
RUBYOPT=-rminitest/parallel_fork/fail_fast rake spec
|
77
|
+
|
78
|
+
Note that minitest-parallel_fork uses suite-based parallelism, so tests will not
|
79
|
+
stop until one child has a failing test suite (test class that has a failing test
|
80
|
+
method), and other children are signaled and also stop processing.
|
81
|
+
|
82
|
+
== Interrupt Support
|
83
|
+
|
84
|
+
If you would like to run tests in parallel, but allow for shutting down children
|
85
|
+
when SIGINT is sent to the process, you can use:
|
86
|
+
|
87
|
+
RUBYOPT=-rminitest/parallel_fork/interrupt ruby spec_runner.rb
|
88
|
+
|
89
|
+
The first SIGINT to a process will ask the child processes to shut down gracefully
|
90
|
+
with SIGUSR1. The second SIGINT to a process will kill the child processes with
|
91
|
+
SIGKILL.
|
92
|
+
|
93
|
+
Note that if you use rake to run the specs, the second SIGINT may not be sent to
|
94
|
+
to the parent process, as rake does it's own SIGINT handling.
|
95
|
+
|
71
96
|
== ActiveRecord
|
72
97
|
|
73
98
|
To use this with Rails/ActiveRecord, you probably want to use hooks similar to:
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'halt'
|
2
|
+
require_relative '../parallel_fork'
|
3
|
+
|
4
|
+
module Minitest::ParalleForkFailFast
|
5
|
+
include Minitest::ParallelForkHalt
|
6
|
+
|
7
|
+
def parallel_fork_run_test_suite(suite, reporter, options)
|
8
|
+
super
|
9
|
+
|
10
|
+
if parallel_fork_stat_reporter.results.any?{|r| !r.failure.is_a?(Minitest::Skip)}
|
11
|
+
# At least one failure or error, mark as failing fast
|
12
|
+
@parallel_fork_stop = true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Minitest.singleton_class.prepend(Minitest::ParalleForkFailFast)
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Minitest::ParallelForkHalt
|
2
|
+
def run_after_parallel_fork_hook(i)
|
3
|
+
super
|
4
|
+
Signal.trap(:USR1) do
|
5
|
+
@parallel_fork_stop = true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def parallel_fork_data_to_marshal
|
10
|
+
super << @parallel_fork_stop
|
11
|
+
end
|
12
|
+
|
13
|
+
def parallel_fork_data_from_marshal(data)
|
14
|
+
data = Marshal.load(data)
|
15
|
+
@parallel_fork_stop = true if data.pop
|
16
|
+
data
|
17
|
+
end
|
18
|
+
|
19
|
+
def parallel_fork_run_test_suites(suites, reporter, options)
|
20
|
+
suites.each do |suite|
|
21
|
+
parallel_fork_run_test_suite(suite, reporter, options)
|
22
|
+
|
23
|
+
# Halt if this child process requested an exit,
|
24
|
+
# Or other child processes requested an exit.
|
25
|
+
break if @parallel_fork_stop
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def parallel_fork_child_data(data)
|
30
|
+
threads = {}
|
31
|
+
data.each{|pid, read| threads[pid] = Thread.new(read, &:read)}
|
32
|
+
results = []
|
33
|
+
|
34
|
+
while sleep(0.01) && !threads.empty?
|
35
|
+
threads.to_a.each do |pid, thread|
|
36
|
+
unless thread.alive?
|
37
|
+
threads.delete(pid)
|
38
|
+
results << parallel_fork_data_from_marshal(thread.value)
|
39
|
+
|
40
|
+
if @parallel_fork_stop
|
41
|
+
# If halt is requested, signal other children to halt
|
42
|
+
threads.each_key do |pid|
|
43
|
+
Process.kill(:USR1, pid)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Set a flag indicating that all child processes have been signaled
|
47
|
+
@parallel_fork_stop = :FINISHED
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
results
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'minitest'
|
2
|
+
|
3
|
+
require_relative 'halt'
|
4
|
+
require_relative '../parallel_fork'
|
5
|
+
|
6
|
+
module Minitest
|
7
|
+
@parallel_fork_child_pids = []
|
8
|
+
end
|
9
|
+
|
10
|
+
module Minitest::ParallelForkInterrupt
|
11
|
+
include Minitest::ParallelForkHalt
|
12
|
+
|
13
|
+
def run_before_parallel_fork_hook
|
14
|
+
Signal.trap(:INT) do
|
15
|
+
Signal.trap(:INT) do
|
16
|
+
parallel_fork_kill_all :KILL
|
17
|
+
end
|
18
|
+
$stderr.puts "\nInterrupted.\nExiting ...\nInterrupt again to exit immediately."
|
19
|
+
parallel_fork_kill_all :USR1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run_after_parallel_fork_hook(i)
|
24
|
+
super
|
25
|
+
Signal.trap(:INT, 'IGNORE')
|
26
|
+
end
|
27
|
+
|
28
|
+
def parallel_fork_fork_child(i, suites, reporter, options)
|
29
|
+
res = super
|
30
|
+
@parallel_fork_child_pids << res[0]
|
31
|
+
res
|
32
|
+
end
|
33
|
+
|
34
|
+
def parallel_fork_kill_all(signal)
|
35
|
+
@parallel_fork_child_pids.each do |pid|
|
36
|
+
begin
|
37
|
+
Process.kill(signal, pid)
|
38
|
+
rescue Errno::ESRCH
|
39
|
+
# Process already terminated
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Minitest.singleton_class.prepend(Minitest::ParallelForkInterrupt)
|
@@ -1,89 +1,136 @@
|
|
1
|
-
gem 'minitest'
|
2
1
|
require 'minitest'
|
3
2
|
|
3
|
+
module Minitest::Unparallelize
|
4
|
+
define_method(:run_one_method, &Minitest::Test.method(:run_one_method))
|
5
|
+
end
|
6
|
+
|
4
7
|
module Minitest
|
8
|
+
@before_parallel_fork = nil
|
9
|
+
@after_parallel_fork = nil
|
10
|
+
@on_parallel_fork_marshal_failure = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
class << Minitest
|
5
14
|
# Set the before_parallel_fork block to the given block
|
6
|
-
def
|
15
|
+
def before_parallel_fork(&block)
|
7
16
|
@before_parallel_fork = block
|
8
17
|
end
|
9
|
-
@before_parallel_fork = nil
|
10
18
|
|
11
19
|
# Set the after_parallel_fork block to the given block
|
12
|
-
def
|
20
|
+
def after_parallel_fork(i=nil, &block)
|
13
21
|
@after_parallel_fork = block
|
14
22
|
end
|
15
|
-
@after_parallel_fork = nil
|
16
23
|
|
17
24
|
# Set the on_parallel_fork_marshal_failure block to the given block
|
18
|
-
def
|
25
|
+
def on_parallel_fork_marshal_failure(&block)
|
19
26
|
@on_parallel_fork_marshal_failure = block
|
20
27
|
end
|
21
|
-
@on_parallel_fork_marshal_failure = nil
|
22
28
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def self.parallel_fork_stat_reporter(reporter)
|
28
|
-
reporter.reporters.detect do |rep|
|
29
|
+
attr_reader :parallel_fork_stat_reporter
|
30
|
+
|
31
|
+
def set_parallel_fork_stat_reporter(reporter)
|
32
|
+
@parallel_fork_stat_reporter = reporter.reporters.detect do |rep|
|
29
33
|
%w'count assertions results count= assertions='.all?{|meth| rep.respond_to?(meth)}
|
30
34
|
end
|
31
35
|
end
|
32
36
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
suites = Runnable.runnables.shuffle
|
37
|
-
stat_reporter = parallel_fork_stat_reporter(reporter)
|
37
|
+
def parallel_fork_suites
|
38
|
+
Minitest::Runnable.runnables.shuffle
|
39
|
+
end
|
38
40
|
|
39
|
-
|
40
|
-
reads = []
|
41
|
+
def run_before_parallel_fork_hook
|
41
42
|
if @before_parallel_fork
|
42
43
|
@before_parallel_fork.call
|
43
44
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
45
|
+
end
|
46
|
+
|
47
|
+
def run_after_parallel_fork_hook(i)
|
48
|
+
if @after_parallel_fork
|
49
|
+
@after_parallel_fork.call(i)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def parallel_fork_data_to_marshal
|
54
|
+
%i'count assertions results'.map{|meth| parallel_fork_stat_reporter.send(meth)}
|
55
|
+
end
|
56
|
+
|
57
|
+
def parallel_fork_data_from_marshal(data)
|
58
|
+
Marshal.load(data)
|
59
|
+
rescue ArgumentError
|
60
|
+
if @on_parallel_fork_marshal_failure
|
61
|
+
@on_parallel_fork_marshal_failure.call
|
62
|
+
end
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
|
66
|
+
def parallel_fork_run_test_suites(suites, reporter, options)
|
67
|
+
suites.each do |suite|
|
68
|
+
parallel_fork_run_test_suite(suite, reporter, options)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parallel_fork_run_test_suite(suite, reporter, options)
|
73
|
+
if suite.is_a?(Minitest::Parallel::Test::ClassMethods)
|
74
|
+
suite.extend(Minitest::Unparallelize)
|
75
|
+
end
|
76
|
+
|
77
|
+
suite.run(reporter, options)
|
78
|
+
end
|
79
|
+
|
80
|
+
def parallel_fork_setup_children(suites, reporter, options)
|
81
|
+
set_parallel_fork_stat_reporter(reporter)
|
82
|
+
run_before_parallel_fork_hook
|
83
|
+
|
84
|
+
parallel_fork_number.times.map do |i|
|
85
|
+
parallel_fork_fork_child(i, suites, reporter, options)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def parallel_fork_fork_child(i, suites, reporter, options)
|
90
|
+
read, write = IO.pipe.each{|io| io.binmode}
|
91
|
+
pid = Process.fork do
|
92
|
+
read.close
|
93
|
+
run_after_parallel_fork_hook(i)
|
94
|
+
|
95
|
+
p_suites = []
|
96
|
+
n = parallel_fork_number
|
97
|
+
suites.each_with_index{|s, j| p_suites << s if j % n == i}
|
98
|
+
parallel_fork_run_test_suites(p_suites, reporter, options)
|
99
|
+
|
100
|
+
write.write(Marshal.dump(parallel_fork_data_to_marshal))
|
67
101
|
write.close
|
68
102
|
end
|
103
|
+
write.close
|
104
|
+
[pid, read]
|
105
|
+
end
|
69
106
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
raise
|
78
|
-
end
|
107
|
+
def parallel_fork_child_data(data)
|
108
|
+
data.map{|_pid, read| Thread.new(read, &:read)}.map(&:value).map{|data| parallel_fork_data_from_marshal(data)}
|
109
|
+
end
|
110
|
+
|
111
|
+
def parallel_fork_wait_for_children(child_info, reporter)
|
112
|
+
parallel_fork_child_data(child_info).each do |data|
|
113
|
+
count, assertions, results = data
|
79
114
|
reporter.reporters.each do |rep|
|
80
|
-
next unless %
|
115
|
+
next unless %i'count assertions results count= assertions='.all?{|meth| rep.respond_to?(meth)}
|
81
116
|
rep.count += count
|
82
117
|
rep.assertions += assertions
|
83
118
|
rep.results.concat(results)
|
84
119
|
end
|
85
120
|
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def parallel_fork_number
|
124
|
+
(ENV['NCPU'] || 4).to_i
|
125
|
+
end
|
86
126
|
|
127
|
+
# Avoid method redefined verbose warning
|
128
|
+
alias __run __run
|
129
|
+
|
130
|
+
# Override __run to use a child forks to run the speeds, which
|
131
|
+
# allows for parallel spec execution on MRI.
|
132
|
+
def __run(reporter, options)
|
133
|
+
parallel_fork_wait_for_children(parallel_fork_setup_children(parallel_fork_suites, reporter, options), reporter)
|
87
134
|
nil
|
88
135
|
end
|
89
136
|
end
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-parallel_fork
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: minitest
|
@@ -63,14 +62,17 @@ email: code@jeremyevans.net
|
|
63
62
|
executables: []
|
64
63
|
extensions: []
|
65
64
|
extra_rdoc_files:
|
66
|
-
- README.rdoc
|
67
65
|
- CHANGELOG
|
68
66
|
- MIT-LICENSE
|
67
|
+
- README.rdoc
|
69
68
|
files:
|
70
69
|
- CHANGELOG
|
71
70
|
- MIT-LICENSE
|
72
71
|
- README.rdoc
|
73
72
|
- lib/minitest/parallel_fork.rb
|
73
|
+
- lib/minitest/parallel_fork/fail_fast.rb
|
74
|
+
- lib/minitest/parallel_fork/halt.rb
|
75
|
+
- lib/minitest/parallel_fork/interrupt.rb
|
74
76
|
homepage: http://github.com/jeremyevans/minitest-parallel_fork
|
75
77
|
licenses:
|
76
78
|
- MIT
|
@@ -79,7 +81,6 @@ metadata:
|
|
79
81
|
changelog_uri: https://github.com/jeremyevans/minitest-parallel_fork/blob/master/CHANGELOG
|
80
82
|
mailing_list_uri: https://github.com/jeremyevans/minitest-parallel_fork/discussions
|
81
83
|
source_code_uri: https://github.com/jeremyevans/minitest-parallel_fork
|
82
|
-
post_install_message:
|
83
84
|
rdoc_options:
|
84
85
|
- "--quiet"
|
85
86
|
- "--line-numbers"
|
@@ -101,8 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
102
|
- !ruby/object:Gem::Version
|
102
103
|
version: '0'
|
103
104
|
requirements: []
|
104
|
-
rubygems_version: 3.
|
105
|
-
signing_key:
|
105
|
+
rubygems_version: 3.6.7
|
106
106
|
specification_version: 4
|
107
107
|
summary: Fork-based parallelization for minitest
|
108
108
|
test_files: []
|