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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bdbf96cdc1b208955148ed3e6ccba3d93542785b6e0c628a9b73acad4bcc0614
4
- data.tar.gz: 18a0e8ed8d41d9c2d36d8927939d3b8865264b363df7194fefe1cb8238cecac3
3
+ metadata.gz: 617125ad38c5fbb98d110eb3374a6f4ffac5d95ffedfed197c6354e7b273d7ef
4
+ data.tar.gz: 7e4768a75b06806da43547902dc02c89ae793a907ea2dc0f00dc18a5bf69ec82
5
5
  SHA512:
6
- metadata.gz: 874cd2b108f8f52135e6ea193dd5bb46b9416247dafab76e135a02456cc456a499c05f140aca081b9bf6f5a7c27affdd553cf752d51d8f7513ff150a1dca2d5d
7
- data.tar.gz: ad5e405c811f861ae524a56c9d7e1d9bb397f4421898d5a103dddc646747b1c6dd5f02d20aafc2e0b40397308e4a897fb13611f3d9615c9d25766e309492ec85
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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015,2017 Jeremy Evans
1
+ Copyright (c) 2015-2023 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
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 self.before_parallel_fork(&block)
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 self.after_parallel_fork(i=nil, &block)
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 self.on_parallel_fork_marshal_failure(&block)
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
- module Unparallelize
24
- define_method(:run_one_method, &Minitest::Test.method(:run_one_method))
25
- end
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
- # Override __run to use a child forks to run the speeds, which
34
- # allows for parallel spec execution on MRI.
35
- def self.__run(reporter, options)
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
- n = (ENV['NCPU'] || 4).to_i
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
- n.times do |i|
45
- read, write = IO.pipe.each{|io| io.binmode}
46
- reads << read
47
- Process.fork do
48
- read.close
49
- if @after_parallel_fork
50
- @after_parallel_fork.call(i)
51
- end
52
-
53
- p_suites = []
54
- suites.each_with_index{|s, j| p_suites << s if j % n == i}
55
- p_suites.each do |s|
56
- if s.is_a?(Minitest::Parallel::Test::ClassMethods)
57
- s.extend(Unparallelize)
58
- end
59
-
60
- s.run(reporter, options)
61
- end
62
-
63
- data = %w'count assertions results'.map{|meth| stat_reporter.send(meth)}
64
- write.write(Marshal.dump(data))
65
- write.close
66
- end
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
- reads.map{|read| Thread.new(read, &:read)}.map(&:value).each do |data|
71
- begin
72
- count, assertions, results = Marshal.load(data)
73
- rescue ArgumentError
74
- if @on_parallel_fork_marshal_failure
75
- @on_parallel_fork_marshal_failure.call
76
- end
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 %w'count assertions results count= assertions='.all?{|meth| rep.respond_to?(meth)}
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.3.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: 2023-09-25 00:00:00.000000000 Z
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.4.10
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: []