minitest-parallel_fork 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7def66702fe6ea3e7cf52578a812f0ecdbb4b3545c2bd4d04ee5ff73a417af5
4
- data.tar.gz: 138a5104f9f344284e806a6b90deb20d61fdf2b16562488b8afc3917f9d8738d
3
+ metadata.gz: 2698fbcfb5e14bed9fbf23e2abe38c5395b615f4443e335ce606b2f674883e30
4
+ data.tar.gz: dff7fbdc3104a729405415688ff35e574d65df94ac0c2086f3791e5005a95dfd
5
5
  SHA512:
6
- metadata.gz: cbc75286347260d6a6cd54e3c8bb1c21f1cd3c9c24c2e843a3942ab7056fc53e4ee092a4a65cb66e52816433a96dfbdfd338b871e334acc1e973fa80ad184d6c
7
- data.tar.gz: f882ac98930a53fa6ec7d02b3edf50bf9a34b3bb7ce17f72b834879313df0f251c9aa23ea45b5cb1f4d02e969933728dca5ec7f1e8898c3c4cc9b2e7d95fe487
6
+ metadata.gz: 80b75938b994edb1f6cfe17c5b8ad31550ab6081738f178ac3dd3c991a7a61fd1a6730d0e855cec3d1d0f5113dddda6ecec455f310ccf4ebcc74298a763a43ae
7
+ data.tar.gz: 398db64ae6468875db78f49ba7221f8cb1f789161adf361a2125daee43a85acd194c0afbbeb1faab35290c72bc377b4eba1cd01dbfba9ace41255d6278eb4a5b
data/CHANGELOG CHANGED
@@ -1,3 +1,17 @@
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
+
9
+ === 1.3.1 (2023-09-25)
10
+
11
+ * Bump required_ruby_version to 2.2, since that is lowest version supported by minitest 5.15.0 (jeremyevans)
12
+
13
+ * Update count and assertions for all configured statistics reporters, not just the first (akimd) (#10)
14
+
1
15
  === 1.3.0 (2022-07-05)
2
16
 
3
17
  * Bump minimum minitest version to 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,86 +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
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
110
+ reporter.reporters.each do |rep|
111
+ next unless %i'count assertions results count= assertions='.all?{|meth| rep.respond_to?(meth)}
112
+ rep.count += count
113
+ rep.assertions += assertions
114
+ rep.results.concat(results)
78
115
  end
79
- stat_reporter.count += count
80
- stat_reporter.assertions += assertions
81
- stat_reporter.results.concat(results)
82
116
  end
117
+ end
83
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)
84
130
  nil
85
131
  end
86
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.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-05 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
@@ -79,7 +80,7 @@ metadata:
79
80
  changelog_uri: https://github.com/jeremyevans/minitest-parallel_fork/blob/master/CHANGELOG
80
81
  mailing_list_uri: https://github.com/jeremyevans/minitest-parallel_fork/discussions
81
82
  source_code_uri: https://github.com/jeremyevans/minitest-parallel_fork
82
- post_install_message:
83
+ post_install_message:
83
84
  rdoc_options:
84
85
  - "--quiet"
85
86
  - "--line-numbers"
@@ -94,15 +95,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
95
  requirements:
95
96
  - - ">="
96
97
  - !ruby/object:Gem::Version
97
- version: '0'
98
+ version: '2.2'
98
99
  required_rubygems_version: !ruby/object:Gem::Requirement
99
100
  requirements:
100
101
  - - ">="
101
102
  - !ruby/object:Gem::Version
102
103
  version: '0'
103
104
  requirements: []
104
- rubygems_version: 3.3.7
105
- signing_key:
105
+ rubygems_version: 3.4.10
106
+ signing_key:
106
107
  specification_version: 4
107
108
  summary: Fork-based parallelization for minitest
108
109
  test_files: []