minitest-distributed 0.2.13 → 0.3.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.
@@ -85,6 +85,7 @@ module Minitest
85
85
  const :enqueued_runnable, EnqueuedRunnable
86
86
  const :initial_result, Minitest::Result
87
87
  const :commit, Commit
88
+ const :force_discard, T::Boolean, default: false
88
89
 
89
90
  sig { returns(String) }
90
91
  def entry_id
@@ -104,7 +105,7 @@ module Minitest
104
105
  sig { returns(Minitest::Result) }
105
106
  def committed_result
106
107
  @committed_result ||= T.let(
107
- if final? && commit.failure?
108
+ if force_discard || (final? && commit.failure?)
108
109
  # If a runnable result is final, but the acked failed, we will discard the result.
109
110
  Minitest::Discard.wrap(
110
111
  initial_result,
@@ -22,12 +22,44 @@ module Minitest
22
22
 
23
23
  sig { override.void }
24
24
  def report
25
- print_discard_warning if local_results.discards > 0
25
+ print_discard_warning if local_results.discards > 0 && !current_attempt_truncated? && !superseded?
26
26
 
27
- if configuration.coordinator.aborted?
27
+ if superseded?
28
+ print_local_results("This worker was superseded; its local results do not affect the authoritative run.")
29
+ return
30
+ elsif registration_rejected?
31
+ print_local_results("Combined results are unavailable because coordinator registration was rejected.")
32
+ return
33
+ elsif truncation_state_invalid?
34
+ print_local_results("Combined results are unavailable because the retained truncation state is incomplete.")
35
+ return
36
+ elsif coordinator_stalled?
37
+ print_local_results("Combined results are unavailable because the Redis coordinator state is invalid.")
38
+ return
39
+ elsif retry_refused_due_to_truncation?
28
40
  io.puts("Cannot retry a run that was cut short during the previous attempt.")
29
- io.puts
30
- elsif combined_results.abort?
41
+ print_local_results("Combined results were not read for this rejected retry.")
42
+ return
43
+ elsif current_attempt_truncated?
44
+ io.puts("The run was cut short after another worker reached the max-failures limit.")
45
+ print_local_results("Combined results were not read after truncation.")
46
+ return
47
+ elsif configuration.coordinator.aborted?
48
+ print_local_results("Combined results are unavailable because the coordinator aborted this worker.")
49
+ return
50
+ end
51
+
52
+ persisted_truncation = configuration.coordinator.persisted_truncation?
53
+ @combined_results = nil
54
+ unless configuration.coordinator.valid_combined_results?
55
+ if persisted_truncation
56
+ io.puts("The run was cut short after reaching the limit of #{configuration.max_failures} test failures.")
57
+ end
58
+ print_local_results("Combined results are unavailable because terminal coordinator state is invalid.")
59
+ return
60
+ end
61
+
62
+ if persisted_truncation
31
63
  io.puts("The run was cut short after reaching the limit of #{configuration.max_failures} test failures.")
32
64
  io.puts
33
65
  end
@@ -43,6 +75,7 @@ module Minitest
43
75
 
44
76
  sig { override.returns(T::Boolean) }
45
77
  def passed?
78
+ return true if superseded?
46
79
  return false if configuration.coordinator.aborted?
47
80
 
48
81
  # Generally, we want the workers to fail that had at least one failed or errored
@@ -50,11 +83,18 @@ module Minitest
50
83
  # encountered a failed test. We trust that the other worker will do this correctly,
51
84
  # but we do verify that the statistics for the complete run are valid,
52
85
  # to have some protection against unknown edge cases and bugs.
53
- local_results.passed? && combined_results.valid?
86
+ local_results.passed? && configuration.coordinator.valid_combined_results?
54
87
  end
55
88
 
56
89
  protected
57
90
 
91
+ sig { params(message: String).void }
92
+ def print_local_results(message)
93
+ formatted_duration = format("(in %0.3fs)", Minitest.clock_time - @start_time)
94
+ io.puts("This worker: #{local_results} #{formatted_duration}")
95
+ io.puts(message)
96
+ end
97
+
58
98
  sig { void }
59
99
  def print_discard_warning
60
100
  io.puts(<<~WARNING)
@@ -65,6 +105,42 @@ module Minitest
65
105
  WARNING
66
106
  end
67
107
 
108
+ sig { returns(T::Boolean) }
109
+ def superseded?
110
+ coordinator = T.unsafe(configuration.coordinator)
111
+ coordinator.respond_to?(:superseded?) && !!coordinator.superseded?
112
+ end
113
+
114
+ sig { returns(T::Boolean) }
115
+ def registration_rejected?
116
+ coordinator = T.unsafe(configuration.coordinator)
117
+ coordinator.respond_to?(:registration_rejected?) && !!coordinator.registration_rejected?
118
+ end
119
+
120
+ sig { returns(T::Boolean) }
121
+ def truncation_state_invalid?
122
+ coordinator = T.unsafe(configuration.coordinator)
123
+ coordinator.respond_to?(:truncation_state_invalid?) && !!coordinator.truncation_state_invalid?
124
+ end
125
+
126
+ sig { returns(T::Boolean) }
127
+ def retry_refused_due_to_truncation?
128
+ coordinator = T.unsafe(configuration.coordinator)
129
+ coordinator.respond_to?(:retry_refused_due_to_truncation?) && !!coordinator.retry_refused_due_to_truncation?
130
+ end
131
+
132
+ sig { returns(T::Boolean) }
133
+ def current_attempt_truncated?
134
+ coordinator = T.unsafe(configuration.coordinator)
135
+ coordinator.respond_to?(:current_attempt_truncated?) && !!coordinator.current_attempt_truncated?
136
+ end
137
+
138
+ sig { returns(T::Boolean) }
139
+ def coordinator_stalled?
140
+ coordinator = T.unsafe(configuration.coordinator)
141
+ coordinator.respond_to?(:stalled?) && !!coordinator.stalled?
142
+ end
143
+
68
144
  sig { returns(ResultAggregate) }
69
145
  def local_results
70
146
  @local_results ||= T.let(configuration.coordinator.local_results, T.nilable(ResultAggregate))
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Minitest
5
5
  module Distributed
6
- VERSION = "0.2.13"
6
+ VERSION = "0.3.0"
7
7
  end
8
8
  end
data/sorbet/config CHANGED
@@ -1,3 +1,5 @@
1
1
  --dir
2
2
  .
3
3
  --ignore=vendor
4
+ --typed-override
5
+ sorbet/typed_overrides.yaml
@@ -1,4 +1,4 @@
1
- # typed: strict
1
+ # typed: true
2
2
 
3
3
  module Minitest
4
4
  class Runnable
@@ -171,7 +171,7 @@ module Minitest::Assertions
171
171
 
172
172
  sig do
173
173
  params(
174
- collection: T::Enumerable[T.untyped],
174
+ collection: T.untyped,
175
175
  obj: BasicObject,
176
176
  msg: T.nilable(String)
177
177
  ).returns(TrueClass)
@@ -188,10 +188,11 @@ module Minitest::Assertions
188
188
 
189
189
  sig do
190
190
  params(
191
- exp: T.untyped
191
+ exp: T.untyped,
192
+ blk: T.untyped
192
193
  ).returns(TrueClass)
193
194
  end
194
- def assert_raises(*exp); end
195
+ def assert_raises(*exp, &blk); end
195
196
 
196
197
  sig do
197
198
  params(
@@ -0,0 +1,13 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ # Shim for prism 1.9.0's bundled RBI, which references `Prism::LexCompat::Result`
5
+ # in a sig at rbi/prism.rbi:16. `LexCompat` was renamed to `LexResult` upstream
6
+ # but the sig wasn't updated, so Sorbet errors with 5002 (Unable to resolve
7
+ # constant `LexCompat`). Defining the namespace here makes the sig resolve
8
+ # without globally suppressing 5002 — keeping 5002 active for our own code.
9
+ module Prism
10
+ class LexCompat
11
+ class Result; end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # Force autogenerated gem RBIs to `# typed: false` via --typed-override
2
+ # (sorbet/config). Editing the sigil in the files themselves would be
3
+ # wiped by `srb rbi gems`.
4
+ false:
5
+ - './sorbet/rbi/gems/ast.rbi'
6
+ - './sorbet/rbi/gems/connection_pool.rbi'
7
+ - './sorbet/rbi/gems/minitest-distributed.rbi'
8
+ - './sorbet/rbi/gems/minitest.rbi'
9
+ - './sorbet/rbi/gems/parallel.rbi'
10
+ - './sorbet/rbi/gems/parser.rbi'
11
+ - './sorbet/rbi/gems/rainbow.rbi'
12
+ - './sorbet/rbi/gems/redis-client.rbi'
13
+ - './sorbet/rbi/gems/redis.rbi'
14
+ - './sorbet/rbi/gems/rexml.rbi'
15
+ - './sorbet/rbi/gems/rubocop-ast.rbi'
16
+ - './sorbet/rbi/gems/rubocop-minitest.rbi'
17
+ - './sorbet/rbi/gems/rubocop-rake.rbi'
18
+ - './sorbet/rbi/gems/rubocop-sorbet.rbi'
19
+ - './sorbet/rbi/gems/ruby-progressbar.rbi'
20
+ - './sorbet/rbi/gems/toxiproxy.rbi'
21
+ - './sorbet/rbi/gems/unicode-display_width.rbi'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: minitest-distributed
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.13
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Willem van Bergen
@@ -161,7 +161,9 @@ files:
161
161
  - sorbet/rbi/minitest.rbi
162
162
  - sorbet/rbi/rails.rbi
163
163
  - sorbet/rbi/rbconfig.rbi
164
+ - sorbet/rbi/shims/prism.rbi
164
165
  - sorbet/rbi/winsize.rbi
166
+ - sorbet/typed_overrides.yaml
165
167
  homepage: https://github.com/Shopify/minitest-distributed
166
168
  licenses:
167
169
  - MIT
@@ -183,7 +185,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
185
  - !ruby/object:Gem::Version
184
186
  version: '0'
185
187
  requirements: []
186
- rubygems_version: 4.0.12
188
+ rubygems_version: 4.0.21
187
189
  specification_version: 4
188
190
  summary: Distributed test executor plugin for Minitest
189
191
  test_files: []