minitest-distributed 0.1.2 → 0.2.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/.rubocop.yml +4 -0
- data/Gemfile +1 -1
- data/README.md +29 -13
- data/bin/setup +0 -2
- data/lib/minitest/distributed/configuration.rb +49 -4
- data/lib/minitest/distributed/coordinators/coordinator_interface.rb +3 -0
- data/lib/minitest/distributed/coordinators/memory_coordinator.rb +29 -9
- data/lib/minitest/distributed/coordinators/redis_coordinator.rb +258 -156
- data/lib/minitest/distributed/enqueued_runnable.rb +193 -41
- data/lib/minitest/distributed/filters/exclude_filter.rb +4 -4
- data/lib/minitest/distributed/filters/filter_interface.rb +3 -3
- data/lib/minitest/distributed/filters/include_filter.rb +4 -4
- data/lib/minitest/distributed/reporters/distributed_progress_reporter.rb +2 -2
- data/lib/minitest/distributed/reporters/distributed_summary_reporter.rb +49 -10
- data/lib/minitest/distributed/reporters/redis_coordinator_warnings_reporter.rb +11 -16
- data/lib/minitest/distributed/result_aggregate.rb +38 -9
- data/lib/minitest/distributed/result_type.rb +76 -2
- data/lib/minitest/distributed/test_selector.rb +4 -6
- data/lib/minitest/distributed/version.rb +1 -1
- data/lib/minitest/distributed_plugin.rb +1 -25
- data/sorbet/rbi/minitest.rbi +18 -3
- data/sorbet/rbi/redis.rbi +19 -4
- metadata +2 -2
@@ -6,41 +6,51 @@ module Minitest
|
|
6
6
|
class ResultAggregate < T::Struct
|
7
7
|
extend T::Sig
|
8
8
|
|
9
|
+
const :max_failures, T.nilable(Integer)
|
10
|
+
|
11
|
+
# These are maintained between different attempts for the same run ID
|
9
12
|
prop :runs, Integer, default: 0
|
10
13
|
prop :assertions, Integer, default: 0
|
11
14
|
prop :passes, Integer, default: 0
|
12
15
|
prop :failures, Integer, default: 0
|
13
16
|
prop :errors, Integer, default: 0
|
14
17
|
prop :skips, Integer, default: 0
|
15
|
-
prop :
|
18
|
+
prop :requeues, Integer, default: 0
|
19
|
+
prop :discards, Integer, default: 0
|
20
|
+
|
21
|
+
# These are reset between different attempts for the same run ID
|
16
22
|
prop :acks, Integer, default: 0
|
17
23
|
prop :size, Integer, default: 0
|
18
24
|
|
19
|
-
sig { params(
|
20
|
-
def update_with_result(
|
21
|
-
case (result_type = ResultType.of(
|
25
|
+
sig { params(runnable_result: EnqueuedRunnable::Result).void }
|
26
|
+
def update_with_result(runnable_result)
|
27
|
+
case (result_type = ResultType.of(runnable_result.committed_result))
|
22
28
|
when ResultType::Passed then self.passes += 1
|
23
29
|
when ResultType::Failed then self.failures += 1
|
24
30
|
when ResultType::Error then self.errors += 1
|
25
31
|
when ResultType::Skipped then self.skips += 1
|
32
|
+
when ResultType::Discarded then self.discards += 1
|
33
|
+
when ResultType::Requeued then self.requeues += 1
|
26
34
|
else T.absurd(result_type)
|
27
35
|
end
|
28
36
|
|
37
|
+
self.acks += 1 if runnable_result.final? && runnable_result.commit.success?
|
29
38
|
self.runs += 1
|
30
|
-
self.assertions +=
|
39
|
+
self.assertions += runnable_result.committed_result.assertions
|
31
40
|
end
|
32
41
|
|
33
42
|
sig { returns(String) }
|
34
43
|
def to_s
|
35
44
|
str = +"#{runs} runs, #{assertions} assertions, #{passes} passes, #{failures} failures, #{errors} errors"
|
36
45
|
str << ", #{skips} skips" if skips > 0
|
37
|
-
str << ", #{
|
46
|
+
str << ", #{requeues} re-queued" if requeues > 0
|
47
|
+
str << ", #{discards} discarded" if discards > 0
|
38
48
|
str
|
39
49
|
end
|
40
50
|
|
41
51
|
sig { returns(Integer) }
|
42
52
|
def unique_runs
|
43
|
-
runs -
|
53
|
+
runs - requeues - discards
|
44
54
|
end
|
45
55
|
|
46
56
|
sig { returns(Integer) }
|
@@ -49,18 +59,37 @@ module Minitest
|
|
49
59
|
end
|
50
60
|
|
51
61
|
sig { returns(T::Boolean) }
|
52
|
-
def
|
62
|
+
def complete?
|
53
63
|
acks == size
|
54
64
|
end
|
55
65
|
|
66
|
+
sig { returns(T::Boolean) }
|
67
|
+
def abort?
|
68
|
+
if (max = max_failures)
|
69
|
+
total_failures >= max
|
70
|
+
else
|
71
|
+
false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
56
75
|
sig { returns(T::Boolean) }
|
57
76
|
def valid?
|
77
|
+
all_runs_reported? && (complete? || abort?)
|
78
|
+
end
|
79
|
+
|
80
|
+
sig { returns(T::Boolean) }
|
81
|
+
def all_runs_reported?
|
58
82
|
unique_runs == reported_results
|
59
83
|
end
|
60
84
|
|
85
|
+
sig { returns(Integer) }
|
86
|
+
def total_failures
|
87
|
+
failures + errors
|
88
|
+
end
|
89
|
+
|
61
90
|
sig { returns(T::Boolean) }
|
62
91
|
def passed?
|
63
|
-
|
92
|
+
total_failures == 0
|
64
93
|
end
|
65
94
|
end
|
66
95
|
end
|
@@ -2,6 +2,74 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
module Minitest
|
5
|
+
class Discard < Minitest::Skip
|
6
|
+
extend T::Sig
|
7
|
+
|
8
|
+
sig { returns(Minitest::Result) }
|
9
|
+
attr_reader :original_result
|
10
|
+
|
11
|
+
sig { params(message: String, original_result: Minitest::Result).void }
|
12
|
+
def initialize(message, original_result:)
|
13
|
+
@original_result = original_result
|
14
|
+
super(message)
|
15
|
+
end
|
16
|
+
|
17
|
+
sig { override.returns(String) }
|
18
|
+
def result_label
|
19
|
+
"Discarded"
|
20
|
+
end
|
21
|
+
|
22
|
+
sig { params(result: Minitest::Result, test_timeout_seconds: Float).returns(Minitest::Result) }
|
23
|
+
def self.wrap(result, test_timeout_seconds:)
|
24
|
+
message = +"This test result was discarded, because it could not be committed to the test run coordinator."
|
25
|
+
if result.time > test_timeout_seconds
|
26
|
+
message << format(
|
27
|
+
"\n\nThe test took %0.3fs to run, longer than the test timeout which is configured to be %0.1fs.\n" \
|
28
|
+
"Another worker likely claimed ownership of this test, and will commit the result instead.\n" \
|
29
|
+
"For best results, make sure that all your tests finish within %0.1fs.",
|
30
|
+
result.time, test_timeout_seconds, test_timeout_seconds
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
discard_assertion = Minitest::Discard.new(message, original_result: result)
|
35
|
+
discard_assertion.set_backtrace(caller)
|
36
|
+
discarded_result = result.dup
|
37
|
+
discarded_result.failures = [discard_assertion]
|
38
|
+
discarded_result
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Requeue < Minitest::Skip
|
43
|
+
extend T::Sig
|
44
|
+
|
45
|
+
sig { params(message: String, original_result: Minitest::Result).void }
|
46
|
+
def initialize(message, original_result:)
|
47
|
+
@original_result = original_result
|
48
|
+
super(message)
|
49
|
+
end
|
50
|
+
|
51
|
+
sig { override.returns(String) }
|
52
|
+
def result_label
|
53
|
+
"Requeued"
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { params(result: Minitest::Result, attempt: Integer, max_attempts: Integer).returns(Minitest::Result) }
|
57
|
+
def self.wrap(result, attempt:, max_attempts:)
|
58
|
+
failure = T.must(result.failure)
|
59
|
+
|
60
|
+
message = "#{failure.message}\n\nThe test will be retried (attempt #{attempt} of #{max_attempts})"
|
61
|
+
requeue_assertion = Minitest::Requeue.new(message, original_result: result)
|
62
|
+
requeue_assertion.set_backtrace(failure.backtrace)
|
63
|
+
|
64
|
+
requeued_result = result.dup
|
65
|
+
requeued_result.failures = [requeue_assertion]
|
66
|
+
requeued_result
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class AttemptsExhausted < Minitest::Assertion
|
71
|
+
end
|
72
|
+
|
5
73
|
module Distributed
|
6
74
|
class ResultType < T::Enum
|
7
75
|
extend T::Sig
|
@@ -11,16 +79,22 @@ module Minitest
|
|
11
79
|
Failed = new
|
12
80
|
Error = new
|
13
81
|
Skipped = new
|
82
|
+
Discarded = new
|
83
|
+
Requeued = new
|
14
84
|
end
|
15
85
|
|
16
86
|
sig { params(result: Minitest::Result).returns(ResultType) }
|
17
87
|
def self.of(result)
|
18
88
|
if result.passed?
|
19
89
|
Passed
|
20
|
-
elsif result.
|
21
|
-
|
90
|
+
elsif result.failure.is_a?(Minitest::Requeue)
|
91
|
+
Requeued
|
92
|
+
elsif result.failure.is_a?(Minitest::Discard)
|
93
|
+
Discarded
|
22
94
|
elsif result.skipped?
|
23
95
|
Skipped
|
96
|
+
elsif result.error?
|
97
|
+
Error
|
24
98
|
else
|
25
99
|
Failed
|
26
100
|
end
|
@@ -26,16 +26,14 @@ module Minitest
|
|
26
26
|
@filters << Filters::ExcludeFilter.new(options[:exclude]) if options[:exclude]
|
27
27
|
end
|
28
28
|
|
29
|
-
sig { returns(T::Array[
|
29
|
+
sig { returns(T::Array[Minitest::Runnable]) }
|
30
30
|
def discover_tests
|
31
31
|
Minitest::Runnable.runnables.flat_map do |runnable|
|
32
|
-
runnable.runnable_methods.map
|
33
|
-
EnqueuedRunnable.new(class_name: runnable.name, method_name: method_name)
|
34
|
-
end
|
32
|
+
runnable.runnable_methods.map { |method_name| runnable.new(method_name) }
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
38
|
-
sig { params(tests: T::Array[
|
36
|
+
sig { params(tests: T::Array[Minitest::Runnable]).returns(T::Array[Minitest::Runnable]) }
|
39
37
|
def select_tests(tests)
|
40
38
|
return tests if filters.empty?
|
41
39
|
tests.flat_map do |runnable_method|
|
@@ -45,7 +43,7 @@ module Minitest
|
|
45
43
|
end.compact
|
46
44
|
end
|
47
45
|
|
48
|
-
sig { returns(T::Array[
|
46
|
+
sig { returns(T::Array[Minitest::Runnable]) }
|
49
47
|
def tests
|
50
48
|
select_tests(discover_tests)
|
51
49
|
end
|
@@ -12,31 +12,7 @@ module Minitest
|
|
12
12
|
options[:disable_distributed] = true
|
13
13
|
end
|
14
14
|
|
15
|
-
options[:distributed] = Minitest::Distributed::Configuration.
|
16
|
-
|
17
|
-
opts.on('--coordinator=URI', "The URI pointing to the coordinator") do |uri|
|
18
|
-
options[:distributed].coordinator_uri = URI.parse(uri)
|
19
|
-
end
|
20
|
-
|
21
|
-
opts.on('--test-timeout=TIMEOUT', "The maximum run time for a single test in seconds") do |timeout|
|
22
|
-
options[:distributed].test_timeout = Float(timeout)
|
23
|
-
end
|
24
|
-
|
25
|
-
opts.on('--max-attempts=ATTEMPTS', "The maximum number of attempts to run a test") do |attempts|
|
26
|
-
options[:distributed].max_attempts = Integer(attempts)
|
27
|
-
end
|
28
|
-
|
29
|
-
opts.on('--test-batch-size=NUMBER', "The number of tests to process per batch") do |batch_size|
|
30
|
-
options[:distributed].test_batch_size = Integer(batch_size)
|
31
|
-
end
|
32
|
-
|
33
|
-
opts.on('--run-id=ID', "The ID for this run shared between coordinated workers") do |id|
|
34
|
-
options[:distributed].run_id = id
|
35
|
-
end
|
36
|
-
|
37
|
-
opts.on('--worker-id=ID', "The unique ID for this worker") do |id|
|
38
|
-
options[:distributed].worker_id = id
|
39
|
-
end
|
15
|
+
options[:distributed] = Minitest::Distributed::Configuration.from_command_line_options(opts)
|
40
16
|
end
|
41
17
|
|
42
18
|
def plugin_distributed_init(options)
|
data/sorbet/rbi/minitest.rbi
CHANGED
@@ -12,12 +12,17 @@ module Minitest
|
|
12
12
|
def self.run_one_method(klass, method_name, reporter); end
|
13
13
|
def self.runnables; end
|
14
14
|
|
15
|
+
def initialize(method_name); end
|
16
|
+
|
15
17
|
def name; end
|
16
18
|
def time; end
|
17
19
|
def time=(duration); end
|
18
20
|
def failures; end
|
19
|
-
|
20
|
-
def
|
21
|
+
def failures=(failures); end
|
22
|
+
def source_location; end
|
23
|
+
def source_location=(value); end
|
24
|
+
def klass; end
|
25
|
+
def klass=(value); end
|
21
26
|
end
|
22
27
|
|
23
28
|
class Test < Runnable
|
@@ -61,7 +66,7 @@ module Minitest
|
|
61
66
|
def result_code; end
|
62
67
|
end
|
63
68
|
|
64
|
-
class Skip <
|
69
|
+
class Skip < Assertion
|
65
70
|
end
|
66
71
|
|
67
72
|
class UnexpectedError < Assertion
|
@@ -190,6 +195,16 @@ module Minitest::Assertions
|
|
190
195
|
end
|
191
196
|
def assert_predicate(obj, predicate, msg = nil); end
|
192
197
|
|
198
|
+
sig do
|
199
|
+
params(
|
200
|
+
value: BasicObject,
|
201
|
+
operator: Symbol,
|
202
|
+
comparison: BasicObject,
|
203
|
+
msg: T.nilable(String)
|
204
|
+
).returns(TrueClass)
|
205
|
+
end
|
206
|
+
def assert_operator(value, operator, comparison, msg = nil); end
|
207
|
+
|
193
208
|
sig { params(test: T.untyped, msg: T.nilable(String)).returns(TrueClass) }
|
194
209
|
def refute(test, msg = nil); end
|
195
210
|
|
data/sorbet/rbi/redis.rbi
CHANGED
@@ -7,9 +7,17 @@ class Redis
|
|
7
7
|
class CommandError < Error
|
8
8
|
end
|
9
9
|
|
10
|
+
class Future
|
11
|
+
sig { returns(T.untyped) }
|
12
|
+
def value; end
|
13
|
+
end
|
14
|
+
|
10
15
|
sig { params(options: T::Hash[Symbol, T.untyped]).void }
|
11
16
|
def initialize(options); end
|
12
17
|
|
18
|
+
sig { params(block: T.proc.params(arg0: String).void).void }
|
19
|
+
def monitor(&block); end
|
20
|
+
|
13
21
|
sig { void }
|
14
22
|
def flushdb; end
|
15
23
|
|
@@ -49,7 +57,7 @@ class Redis
|
|
49
57
|
sig { params(key_value_pairs: T.untyped).returns(T::Boolean) }
|
50
58
|
def msetnx(*key_value_pairs); end
|
51
59
|
|
52
|
-
sig { params(key: String).
|
60
|
+
sig { params(key: String).returns(Integer) }
|
53
61
|
def incr(key); end
|
54
62
|
|
55
63
|
sig { params(key: String, value: T.untyped).void }
|
@@ -58,13 +66,20 @@ class Redis
|
|
58
66
|
sig { params(key: String, start: Integer, stop: Integer).void }
|
59
67
|
def lrange(key, start, stop); end
|
60
68
|
|
61
|
-
sig { params(key: String,
|
69
|
+
sig { params(key: String, value: T.untyped).returns(T.untyped) }
|
70
|
+
def sadd(key, value); end
|
71
|
+
|
72
|
+
sig { params(key: String, value: T.untyped).returns(T::Boolean) }
|
73
|
+
def srem(key, value); end
|
74
|
+
|
75
|
+
sig { params(key: String, amount: Integer).returns(Integer) }
|
62
76
|
def incrby(key, amount); end
|
63
77
|
|
64
|
-
def xadd(
|
65
|
-
def xack(*); end
|
78
|
+
def xadd(key, value); end
|
79
|
+
def xack(stream_key, group_name, *entry_ids); end
|
66
80
|
def xgroup(*); end
|
67
81
|
def xpending(*); end
|
68
82
|
def xreadgroup(*); end
|
69
83
|
def xclaim(*); end
|
84
|
+
def xinfo(*); end
|
70
85
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: minitest-distributed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Willem van Bergen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|