minitest-parallel_fork 1.3.1 → 2.0.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: 2698fbcfb5e14bed9fbf23e2abe38c5395b615f4443e335ce606b2f674883e30
4
+ data.tar.gz: dff7fbdc3104a729405415688ff35e574d65df94ac0c2086f3791e5005a95dfd
5
5
  SHA512:
6
- metadata.gz: 874cd2b108f8f52135e6ea193dd5bb46b9416247dafab76e135a02456cc456a499c05f140aca081b9bf6f5a7c27affdd553cf752d51d8f7513ff150a1dca2d5d
7
- data.tar.gz: ad5e405c811f861ae524a56c9d7e1d9bb397f4421898d5a103dddc646747b1c6dd5f02d20aafc2e0b40397308e4a897fb13611f3d9615c9d25766e309492ec85
6
+ metadata.gz: 80b75938b994edb1f6cfe17c5b8ad31550ab6081738f178ac3dd3c991a7a61fd1a6730d0e855cec3d1d0f5113dddda6ecec455f310ccf4ebcc74298a763a43ae
7
+ data.tar.gz: 398db64ae6468875db78f49ba7221f8cb1f789161adf361a2125daee43a85acd194c0afbbeb1faab35290c72bc377b4eba1cd01dbfba9ace41255d6278eb4a5b
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ === 2.0.0 (2023-11-08)
2
+
3
+ * Avoid method redefined warning in verbose warning mode (jeremyevans)
4
+
5
+ * 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)
6
+
7
+ * Refactor internals for easier extension (jeremyevans)
8
+
1
9
  === 1.3.1 (2023-09-25)
2
10
 
3
11
  * 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,17 @@ 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
+
71
82
  == ActiveRecord
72
83
 
73
84
  To use this with Rails/ActiveRecord, you probably want to use hooks similar to:
@@ -0,0 +1,69 @@
1
+ require_relative '../parallel_fork'
2
+
3
+ module Minitest::ParalleForkFailFast
4
+ def run_after_parallel_fork_hook(i)
5
+ super
6
+ Signal.trap(:USR1) do
7
+ @parallel_fork_stop = true
8
+ end
9
+ end
10
+
11
+ def parallel_fork_data_to_marshal
12
+ super << @parallel_fork_stop
13
+ end
14
+
15
+ def parallel_fork_data_from_marshal(data)
16
+ data = Marshal.load(data)
17
+ @parallel_fork_stop = true if data.pop
18
+ data
19
+ end
20
+
21
+ def parallel_fork_run_test_suites(suites, reporter, options)
22
+ suites.each do |suite|
23
+ parallel_fork_run_test_suite(suite, reporter, options)
24
+
25
+ # Fail fast if this child process had a failure,
26
+ # Or the USR1 signal was received indicating other child processes had a failure.
27
+ break if @parallel_fork_stop
28
+ end
29
+ end
30
+
31
+ def parallel_fork_run_test_suite(suite, reporter, options)
32
+ super
33
+
34
+
35
+ if parallel_fork_stat_reporter.results.any?{|r| !r.failure.is_a?(Minitest::Skip)}
36
+ # At least one failure or error, mark as failing fast
37
+ @parallel_fork_stop = true
38
+ end
39
+ end
40
+
41
+ def parallel_fork_child_data(data)
42
+ threads = {}
43
+ data.each{|pid, read| threads[pid] = Thread.new(read, &:read)}
44
+ results = []
45
+
46
+ while sleep(0.01) && !threads.empty?
47
+ threads.to_a.each do |pid, thread|
48
+ unless thread.alive?
49
+ threads.delete(pid)
50
+ results << parallel_fork_data_from_marshal(thread.value)
51
+
52
+ if @parallel_fork_stop
53
+ # If any child failed fast, signal other children to fail fast
54
+ threads.each_key do |pid|
55
+ Process.kill(:USR1, pid)
56
+ end
57
+
58
+ # Set a flag indicating that all child processes have been signaled
59
+ @parallel_fork_stop = :FINISHED
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ results
66
+ end
67
+ end
68
+
69
+ Minitest.singleton_class.prepend(Minitest::ParalleForkFailFast)
@@ -1,89 +1,132 @@
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
+ 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
+ n = parallel_fork_number
85
+ n.times.map do |i|
45
86
  read, write = IO.pipe.each{|io| io.binmode}
46
- reads << read
47
- Process.fork do
87
+ pid = Process.fork do
48
88
  read.close
49
- if @after_parallel_fork
50
- @after_parallel_fork.call(i)
51
- end
89
+ run_after_parallel_fork_hook(i)
52
90
 
53
91
  p_suites = []
54
92
  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
93
+ parallel_fork_run_test_suites(p_suites, reporter, options)
59
94
 
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))
95
+ write.write(Marshal.dump(parallel_fork_data_to_marshal))
65
96
  write.close
66
97
  end
67
98
  write.close
99
+ [pid, read]
68
100
  end
101
+ end
69
102
 
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
103
+ def parallel_fork_child_data(data)
104
+ data.map{|_pid, read| Thread.new(read, &:read)}.map(&:value).map{|data| parallel_fork_data_from_marshal(data)}
105
+ end
106
+
107
+ def parallel_fork_wait_for_children(child_info, reporter)
108
+ parallel_fork_child_data(child_info).each do |data|
109
+ count, assertions, results = data
79
110
  reporter.reporters.each do |rep|
80
- next unless %w'count assertions results count= assertions='.all?{|meth| rep.respond_to?(meth)}
111
+ next unless %i'count assertions results count= assertions='.all?{|meth| rep.respond_to?(meth)}
81
112
  rep.count += count
82
113
  rep.assertions += assertions
83
114
  rep.results.concat(results)
84
115
  end
85
116
  end
117
+ end
86
118
 
119
+ def parallel_fork_number
120
+ (ENV['NCPU'] || 4).to_i
121
+ end
122
+
123
+ # Avoid method redefined verbose warning
124
+ alias __run __run
125
+
126
+ # Override __run to use a child forks to run the speeds, which
127
+ # allows for parallel spec execution on MRI.
128
+ def __run(reporter, options)
129
+ parallel_fork_wait_for_children(parallel_fork_setup_children(parallel_fork_suites, reporter, options), reporter)
87
130
  nil
88
131
  end
89
132
  end
metadata CHANGED
@@ -1,14 +1,14 @@
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.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-09-25 00:00:00.000000000 Z
11
+ date: 2023-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -71,6 +71,7 @@ files:
71
71
  - MIT-LICENSE
72
72
  - README.rdoc
73
73
  - lib/minitest/parallel_fork.rb
74
+ - lib/minitest/parallel_fork/fail_fast.rb
74
75
  homepage: http://github.com/jeremyevans/minitest-parallel_fork
75
76
  licenses:
76
77
  - MIT