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.
- checksums.yaml +4 -4
- data/README.md +30 -0
- data/lib/minitest/distributed/configuration.rb +46 -0
- data/lib/minitest/distributed/coordinators/coordinator_interface.rb +6 -0
- data/lib/minitest/distributed/coordinators/memory_coordinator.rb +10 -0
- data/lib/minitest/distributed/coordinators/redis_coordinator.rb +2129 -195
- data/lib/minitest/distributed/enqueued_runnable.rb +2 -1
- data/lib/minitest/distributed/reporters/distributed_summary_reporter.rb +81 -5
- data/lib/minitest/distributed/version.rb +1 -1
- data/sorbet/config +2 -0
- data/sorbet/rbi/minitest.rbi +5 -4
- data/sorbet/rbi/shims/prism.rbi +13 -0
- data/sorbet/typed_overrides.yaml +21 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d4be59a24cfaf9122de65b8b5919dda05f39bcecd0a6bf9998c44f085edabe5
|
|
4
|
+
data.tar.gz: 4672adf9de55fde54385954316756688b27c0f37ba6318b73b1288d89af5b6d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 40768ad960753cef68edfc3cdcba75fa86c6203b0612f4eb35d387ead059d745d40ffeffaacdc6bac6487a7f3795de1be17c72cd8f2262b1d9ff8b34e6e44b8b
|
|
7
|
+
data.tar.gz: 536c77381692428674e69e3492903ae60acaf455e2b320686f5eee26f2ad88c80614f4c7dd1a769379fdecfa7c66eefe545e702dbaf704bfc06d2a2f4755c8c6
|
data/README.md
CHANGED
|
@@ -81,6 +81,30 @@ them to fail.
|
|
|
81
81
|
- `--worker-id=IDENTIFIER` or `ENV[MINITEST_WORKER_ID]`: The ID of the worker,
|
|
82
82
|
which should be unique to the cluster. We will default to a UUID if this is
|
|
83
83
|
not set, which generally is fine.
|
|
84
|
+
- `--key-ttl=SECONDS` or `ENV[MINITEST_KEY_TTL_SECONDS]` (default: 86400, i.e.
|
|
85
|
+
24 hours). The expiry applied to every Redis key a run owns, refreshed on
|
|
86
|
+
every write. A run's statistics deliberately outlive the run itself so retry
|
|
87
|
+
mode can read them, and this is what eventually reclaims them. Raise it if you
|
|
88
|
+
retry runs more than a day later. Once these keys expire, a later invocation
|
|
89
|
+
with the same run ID performs a full run rather than a selective retry. Do not
|
|
90
|
+
set it below the duration of a run: required state expiring mid-run aborts the
|
|
91
|
+
run to avoid reporting incomplete results. Active workers must use the same
|
|
92
|
+
TTL. A later retry may increase the retained run's TTL, but cannot decrease it
|
|
93
|
+
while that state exists, because doing so could invalidate another worker's
|
|
94
|
+
completion-grace wait.
|
|
95
|
+
- `--stall-timeout=SECONDS` or `ENV[MINITEST_STALL_TIMEOUT_SECONDS]` (default:
|
|
96
|
+
300, i.e. 5 minutes). After this long without processing a batch or observing
|
|
97
|
+
the run counters change, inspect the Redis stream. If it has no pending or
|
|
98
|
+
undelivered tests but `acks` does not equal `size`, abort with a diagnostic
|
|
99
|
+
instead of waiting silently forever. A producer heartbeat keeps slow test
|
|
100
|
+
discovery from being mistaken for a stall. Keep this comfortably above the
|
|
101
|
+
normal end-of-run wait for your suite.
|
|
102
|
+
- `--completion-grace=SECONDS` or `ENV[MINITEST_COMPLETION_GRACE_SECONDS]`
|
|
103
|
+
(default: 30). Before reusing a recently completed run ID, wait this long for
|
|
104
|
+
late workers from the previous cohort to finish. The key TTL must exceed this
|
|
105
|
+
grace period by at least one second so the retained retry snapshot cannot
|
|
106
|
+
expire while a worker is waiting. Lower this only when your CI guarantees
|
|
107
|
+
tighter worker-start synchronization.
|
|
84
108
|
- `--exclude-file=PATH_TO_FILE`: Specify a file of tests to be excluded
|
|
85
109
|
from running. The file should include test identifiers seperated by
|
|
86
110
|
newlines.
|
|
@@ -88,6 +112,12 @@ them to fail.
|
|
|
88
112
|
the test run. The file should include test identifiers seperated by
|
|
89
113
|
newlines.
|
|
90
114
|
|
|
115
|
+
Version 0.3 uses the versioned Redis namespace `minitest/v3/...`; it does not
|
|
116
|
+
share streams or counters with the legacy 0.2 protocol. All workers in one
|
|
117
|
+
cohort should still use the same gem version. Mixed-version cohorts run in
|
|
118
|
+
separate namespaces and can duplicate test execution, but cannot mutate each
|
|
119
|
+
other's coordinator state.
|
|
120
|
+
|
|
91
121
|
**Limitations**
|
|
92
122
|
|
|
93
123
|
**Parallel tests not supported:** Minitest comes bundled with a parallel test
|
|
@@ -11,6 +11,14 @@ module Minitest
|
|
|
11
11
|
DEFAULT_MAX_ATTEMPTS = 1
|
|
12
12
|
DEFAULT_TEST_TIMEOUT_SECONDS = 30.0 # seconds
|
|
13
13
|
|
|
14
|
+
# Expiry applied to every Redis key the coordinator writes, refreshed on
|
|
15
|
+
# every write, so a finished run's bookkeeping does not live in the
|
|
16
|
+
# coordinator forever.
|
|
17
|
+
DEFAULT_KEY_TTL_SECONDS = 86_400 # 24 hours
|
|
18
|
+
DEFAULT_STALL_TIMEOUT_SECONDS = 300.0 # 5 minutes
|
|
19
|
+
DEFAULT_COMPLETION_GRACE_SECONDS = 30.0
|
|
20
|
+
MIN_COMPLETION_GRACE_TTL_MARGIN_SECONDS = 1.0
|
|
21
|
+
|
|
14
22
|
class << self
|
|
15
23
|
extend T::Sig
|
|
16
24
|
|
|
@@ -24,6 +32,11 @@ module Minitest
|
|
|
24
32
|
test_batch_size: Integer(env["MINITEST_TEST_BATCH_SIZE"] || DEFAULT_BATCH_SIZE),
|
|
25
33
|
max_attempts: Integer(env["MINITEST_MAX_ATTEMPTS"] || DEFAULT_MAX_ATTEMPTS),
|
|
26
34
|
max_failures: (max_failures_env = env["MINITEST_MAX_FAILURES"]) ? Integer(max_failures_env) : nil,
|
|
35
|
+
key_ttl_seconds: Integer(env["MINITEST_KEY_TTL_SECONDS"] || DEFAULT_KEY_TTL_SECONDS),
|
|
36
|
+
stall_timeout_seconds: Float(env["MINITEST_STALL_TIMEOUT_SECONDS"] || DEFAULT_STALL_TIMEOUT_SECONDS),
|
|
37
|
+
completion_grace_seconds: Float(
|
|
38
|
+
env["MINITEST_COMPLETION_GRACE_SECONDS"] || DEFAULT_COMPLETION_GRACE_SECONDS,
|
|
39
|
+
),
|
|
27
40
|
)
|
|
28
41
|
end
|
|
29
42
|
|
|
@@ -83,6 +96,18 @@ module Minitest
|
|
|
83
96
|
configuration.shuffle_suites = enabled
|
|
84
97
|
end
|
|
85
98
|
|
|
99
|
+
opts.on("--key-ttl=SECONDS", "Expiry for the coordinator's Redis keys, refreshed on every write") do |ttl|
|
|
100
|
+
configuration.key_ttl_seconds = Integer(ttl)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
opts.on("--stall-timeout=SECONDS", "Abort a drained Redis queue that has stopped making progress") do |timeout|
|
|
104
|
+
configuration.stall_timeout_seconds = Float(timeout)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
opts.on("--completion-grace=SECONDS", "Wait before reusing a recently completed run ID") do |grace|
|
|
108
|
+
configuration.completion_grace_seconds = Float(grace)
|
|
109
|
+
end
|
|
110
|
+
|
|
86
111
|
configuration
|
|
87
112
|
end
|
|
88
113
|
end
|
|
@@ -102,9 +127,13 @@ module Minitest
|
|
|
102
127
|
prop :exclude_file, T.nilable(String)
|
|
103
128
|
prop :include_file, T.nilable(String)
|
|
104
129
|
prop :shuffle_suites, T::Boolean, default: true
|
|
130
|
+
prop :key_ttl_seconds, Integer, default: DEFAULT_KEY_TTL_SECONDS
|
|
131
|
+
prop :stall_timeout_seconds, Float, default: DEFAULT_STALL_TIMEOUT_SECONDS
|
|
132
|
+
prop :completion_grace_seconds, Float, default: DEFAULT_COMPLETION_GRACE_SECONDS
|
|
105
133
|
|
|
106
134
|
sig { returns(Coordinators::CoordinatorInterface) }
|
|
107
135
|
def coordinator
|
|
136
|
+
validate!
|
|
108
137
|
@coordinator ||= T.let(
|
|
109
138
|
case coordinator_uri.scheme
|
|
110
139
|
when "redis"
|
|
@@ -117,6 +146,23 @@ module Minitest
|
|
|
117
146
|
T.nilable(Coordinators::CoordinatorInterface),
|
|
118
147
|
)
|
|
119
148
|
end
|
|
149
|
+
|
|
150
|
+
sig { void }
|
|
151
|
+
def validate!
|
|
152
|
+
raise ArgumentError, "key_ttl_seconds must be greater than zero" unless key_ttl_seconds.positive?
|
|
153
|
+
|
|
154
|
+
unless stall_timeout_seconds.positive? && stall_timeout_seconds.finite?
|
|
155
|
+
raise ArgumentError, "stall_timeout_seconds must be finite and greater than zero"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
unless completion_grace_seconds.positive? && completion_grace_seconds.finite?
|
|
159
|
+
raise ArgumentError, "completion_grace_seconds must be finite and greater than zero"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
if key_ttl_seconds < completion_grace_seconds + MIN_COMPLETION_GRACE_TTL_MARGIN_SECONDS
|
|
163
|
+
raise ArgumentError, "key_ttl_seconds must exceed completion_grace_seconds by at least one second"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
120
166
|
end
|
|
121
167
|
end
|
|
122
168
|
end
|
|
@@ -21,6 +21,12 @@ module Minitest
|
|
|
21
21
|
sig { abstract.returns(T::Boolean) }
|
|
22
22
|
def aborted?; end
|
|
23
23
|
|
|
24
|
+
sig { abstract.returns(T::Boolean) }
|
|
25
|
+
def valid_combined_results?; end
|
|
26
|
+
|
|
27
|
+
sig { abstract.returns(T::Boolean) }
|
|
28
|
+
def persisted_truncation?; end
|
|
29
|
+
|
|
24
30
|
sig { abstract.params(test_selector: TestSelector).void }
|
|
25
31
|
def produce(test_selector:); end
|
|
26
32
|
|
|
@@ -39,6 +39,16 @@ module Minitest
|
|
|
39
39
|
@aborted
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
sig { override.returns(T::Boolean) }
|
|
43
|
+
def valid_combined_results?
|
|
44
|
+
combined_results.valid?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sig { override.returns(T::Boolean) }
|
|
48
|
+
def persisted_truncation?
|
|
49
|
+
combined_results.abort?
|
|
50
|
+
end
|
|
51
|
+
|
|
42
52
|
sig { override.params(test_selector: TestSelector).void }
|
|
43
53
|
def produce(test_selector:)
|
|
44
54
|
if @leader.try_lock
|