minitest-distributed 0.1.2 → 0.2.2
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 +2 -2
- data/README.md +35 -13
- data/bin/setup +0 -2
- data/lib/minitest/distributed/configuration.rb +66 -4
- data/lib/minitest/distributed/coordinators/coordinator_interface.rb +3 -0
- data/lib/minitest/distributed/coordinators/memory_coordinator.rb +30 -9
- data/lib/minitest/distributed/coordinators/redis_coordinator.rb +259 -154
- data/lib/minitest/distributed/enqueued_runnable.rb +197 -40
- data/lib/minitest/distributed/filters/exclude_file_filter.rb +18 -0
- data/lib/minitest/distributed/filters/exclude_filter.rb +4 -4
- data/lib/minitest/distributed/filters/file_filter_base.rb +29 -0
- data/lib/minitest/distributed/filters/filter_interface.rb +3 -3
- data/lib/minitest/distributed/filters/include_file_filter.rb +18 -0
- data/lib/minitest/distributed/filters/include_filter.rb +4 -4
- data/lib/minitest/distributed/reporters/distributed_progress_reporter.rb +13 -5
- data/lib/minitest/distributed/reporters/distributed_summary_reporter.rb +49 -10
- data/lib/minitest/distributed/reporters/junitxml_reporter.rb +150 -0
- 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 +12 -6
- data/lib/minitest/distributed/version.rb +1 -1
- data/lib/minitest/distributed.rb +3 -0
- data/lib/minitest/distributed_plugin.rb +1 -25
- data/lib/minitest/junitxml_plugin.rb +21 -0
- data/sorbet/rbi/minitest.rbi +29 -11
- data/sorbet/rbi/redis.rbi +19 -4
- metadata +11 -7
- data/.travis.yml +0 -6
@@ -0,0 +1,150 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
module Minitest
|
8
|
+
module Distributed
|
9
|
+
module Reporters
|
10
|
+
# Reporter that generates a JUnit XML report of the results it is presented.
|
11
|
+
#
|
12
|
+
# The JUnitXML schema is not very well standardized, and many implementations deviate
|
13
|
+
# from the schema (see https://www.ibm.com/support/knowledgecenter/SSQ2R2_14.2.0/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html).
|
14
|
+
#
|
15
|
+
# This JunitXML importer embraces this flexibility, and extends the format with some additional
|
16
|
+
# information that we can use to create more meaningful annotations. For instance, the information
|
17
|
+
# can be use to set annotations on your build system or for annotations using the GitHub checks API.
|
18
|
+
#
|
19
|
+
# For the implementation, we use REXML to prevent the need of additional dependencies on this gem.
|
20
|
+
# We also use XML 1.1, which allows more characters to be valid. We are primarily interested in
|
21
|
+
# this so \e is an allowed character, which is used for ANSI color coding.
|
22
|
+
class JUnitXMLReporter < Minitest::Reporter
|
23
|
+
extend T::Sig
|
24
|
+
|
25
|
+
sig { returns(T::Hash[String, T::Array[Minitest::Result]]) }
|
26
|
+
attr_reader :results
|
27
|
+
|
28
|
+
sig { params(io: IO, options: T::Hash[Symbol, T.untyped]).void }
|
29
|
+
def initialize(io, options)
|
30
|
+
@io = io
|
31
|
+
@report_path = T.let(options.fetch(:junitxml), String)
|
32
|
+
@results = T.let(Hash.new { |hash, key| hash[key] = [] }, T::Hash[String, T::Array[Minitest::Result]])
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { override.params(result: Minitest::Result).void }
|
36
|
+
def record(result)
|
37
|
+
case (result_type = ResultType.of(result))
|
38
|
+
when ResultType::Passed, ResultType::Failed, ResultType::Error
|
39
|
+
T.must(results[result.klass]) << result
|
40
|
+
when ResultType::Skipped, ResultType::Requeued, ResultType::Discarded
|
41
|
+
# We will not include skipped, requeued, and discarded tests in JUnitXML reports,
|
42
|
+
# because they will not fail builds, but also didn't pass.
|
43
|
+
else
|
44
|
+
T.absurd(result_type)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
sig { override.void }
|
49
|
+
def report
|
50
|
+
FileUtils.mkdir_p(File.dirname(@report_path))
|
51
|
+
File.open(@report_path, 'w+') do |file|
|
52
|
+
format_document(generate_document, file)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
sig { returns(REXML::Document) }
|
57
|
+
def generate_document
|
58
|
+
doc = REXML::Document.new(nil, prologue_quote: :quote, attribute_quote: :quote)
|
59
|
+
doc << REXML::XMLDecl.new('1.1', 'utf-8')
|
60
|
+
|
61
|
+
testsuites = doc.add_element('testsuites')
|
62
|
+
results.each do |suite, tests|
|
63
|
+
add_tests_to(testsuites, suite, tests)
|
64
|
+
end
|
65
|
+
doc
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(doc: REXML::Document, io: IO).void }
|
69
|
+
def format_document(doc, io)
|
70
|
+
formatter = REXML::Formatters::Pretty.new
|
71
|
+
formatter.write(doc, io)
|
72
|
+
io << "\n"
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
sig { params(testsuites: REXML::Element, suite: String, results: T::Array[Minitest::Result]).void }
|
78
|
+
def add_tests_to(testsuites, suite, results)
|
79
|
+
# TODO: make path relative to project root
|
80
|
+
relative_path = T.must(results.first).source_location.first
|
81
|
+
lineno = T.must(results.first).source_location.last
|
82
|
+
|
83
|
+
testsuite = testsuites.add_element(
|
84
|
+
'testsuite',
|
85
|
+
{ 'name' => suite, 'filepath' => relative_path }.merge(aggregate_suite_results(results))
|
86
|
+
)
|
87
|
+
|
88
|
+
results.each do |test|
|
89
|
+
attributes = {
|
90
|
+
'name' => test.name,
|
91
|
+
'classname' => suite,
|
92
|
+
'assertions' => test.assertions,
|
93
|
+
'time' => test.time,
|
94
|
+
# 'run-command' => ... # TODO
|
95
|
+
}
|
96
|
+
attributes['lineno'] = lineno if lineno != -1
|
97
|
+
|
98
|
+
testcase_tag = testsuite.add_element('testcase', attributes)
|
99
|
+
add_failure_tag_if_needed(testcase_tag, test)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
sig { params(testcase: REXML::Element, result: Minitest::Result).void }
|
104
|
+
def add_failure_tag_if_needed(testcase, result)
|
105
|
+
case (result_type = ResultType.of(result))
|
106
|
+
when ResultType::Passed, ResultType::Skipped, ResultType::Requeued, ResultType::Discarded
|
107
|
+
# noop
|
108
|
+
when ResultType::Error, ResultType::Failed
|
109
|
+
failure = T.must(result.failure)
|
110
|
+
failure_tag = testcase.add_element('failure',
|
111
|
+
'type' => result_type.serialize,
|
112
|
+
'message' => truncate_message(failure.message))
|
113
|
+
failure_tag.add_text(REXML::CData.new(result.to_s))
|
114
|
+
else
|
115
|
+
T.absurd(result_type)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
sig { params(message: String).returns(String) }
|
120
|
+
def truncate_message(message)
|
121
|
+
T.must(message.lines.first).chomp.gsub(/\e\[[^m]+m/, '')
|
122
|
+
end
|
123
|
+
|
124
|
+
sig { params(results: T::Array[Minitest::Result]).returns(T::Hash[String, Numeric]) }
|
125
|
+
def aggregate_suite_results(results)
|
126
|
+
aggregate = Hash.new(0)
|
127
|
+
results.each do |result|
|
128
|
+
aggregate['assertions'] += result.assertions
|
129
|
+
aggregate['failures'] += 1 if failure?(ResultType.of(result))
|
130
|
+
aggregate['tests'] += 1
|
131
|
+
aggregate['time'] += result.time
|
132
|
+
end
|
133
|
+
aggregate
|
134
|
+
end
|
135
|
+
|
136
|
+
sig { params(result_type: ResultType).returns(T::Boolean) }
|
137
|
+
def failure?(result_type)
|
138
|
+
case result_type
|
139
|
+
when ResultType::Failed, ResultType::Error
|
140
|
+
true
|
141
|
+
when ResultType::Passed, ResultType::Skipped, ResultType::Discarded, ResultType::Requeued
|
142
|
+
false
|
143
|
+
else
|
144
|
+
T.absurd(result_type)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -9,9 +9,10 @@ module Minitest
|
|
9
9
|
|
10
10
|
sig { override.void }
|
11
11
|
def report
|
12
|
-
[
|
13
|
-
|
12
|
+
warnings = [reclaim_timeout_warning, reclaim_failed_warning].compact
|
13
|
+
warnings.each do |warning|
|
14
14
|
io.puts(warning)
|
15
|
+
io.puts
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
@@ -28,30 +29,24 @@ module Minitest
|
|
28
29
|
end
|
29
30
|
|
30
31
|
sig { returns(T.nilable(String)) }
|
31
|
-
def
|
32
|
-
if redis_coordinator.
|
32
|
+
def reclaim_timeout_warning
|
33
|
+
if redis_coordinator.reclaimed_timeout_tests.any?
|
33
34
|
<<~WARNING
|
34
35
|
WARNING: The following tests were reclaimed from another worker:
|
35
|
-
#{redis_coordinator.
|
36
|
+
#{redis_coordinator.reclaimed_timeout_tests.map { |test| "- #{test.identifier}" }.join("\n")}
|
36
37
|
|
37
|
-
The original worker did not complete running
|
38
|
+
The original worker did not complete running these tests in #{configuration.test_timeout_seconds}s.
|
38
39
|
This either means that the worker unexpectedly went away, or that the test is too slow.
|
39
40
|
WARNING
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
43
44
|
sig { returns(T.nilable(String)) }
|
44
|
-
def
|
45
|
-
|
46
|
-
if local_results.acks < local_results.size
|
45
|
+
def reclaim_failed_warning
|
46
|
+
if redis_coordinator.reclaimed_failed_tests.any?
|
47
47
|
<<~WARNING
|
48
|
-
WARNING:
|
49
|
-
|
50
|
-
This means that this worker took too long to report the status of one or more tests,
|
51
|
-
and these tests were claimed by other workers. As a result, the total number of
|
52
|
-
reported runs may be larger than the size of the test suite.
|
53
|
-
|
54
|
-
Make sure that all your tests complete within #{configuration.test_timeout}ms.
|
48
|
+
WARNING: The following tests were reclaimed from another worker because they failed:
|
49
|
+
#{redis_coordinator.reclaimed_failed_tests.map { |test| "- #{test.identifier}" }.join("\n")}
|
55
50
|
WARNING
|
56
51
|
end
|
57
52
|
end
|
@@ -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
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# typed: strict
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
+
require 'pathname'
|
5
|
+
|
4
6
|
module Minitest
|
5
7
|
module Distributed
|
6
8
|
class TestSelector
|
@@ -24,18 +26,22 @@ module Minitest
|
|
24
26
|
def initialize_filters
|
25
27
|
@filters << Filters::IncludeFilter.new(options[:filter]) if options[:filter]
|
26
28
|
@filters << Filters::ExcludeFilter.new(options[:exclude]) if options[:exclude]
|
29
|
+
|
30
|
+
exclude_file = options[:distributed].exclude_file
|
31
|
+
@filters << Filters::ExcludeFileFilter.new(Pathname.new(exclude_file)) if exclude_file
|
32
|
+
|
33
|
+
include_file = options[:distributed].include_file
|
34
|
+
@filters << Filters::IncludeFileFilter.new(Pathname.new(include_file)) if include_file
|
27
35
|
end
|
28
36
|
|
29
|
-
sig { returns(T::Array[
|
37
|
+
sig { returns(T::Array[Minitest::Runnable]) }
|
30
38
|
def discover_tests
|
31
39
|
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
|
40
|
+
runnable.runnable_methods.map { |method_name| runnable.new(method_name) }
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
38
|
-
sig { params(tests: T::Array[
|
44
|
+
sig { params(tests: T::Array[Minitest::Runnable]).returns(T::Array[Minitest::Runnable]) }
|
39
45
|
def select_tests(tests)
|
40
46
|
return tests if filters.empty?
|
41
47
|
tests.flat_map do |runnable_method|
|
@@ -45,7 +51,7 @@ module Minitest
|
|
45
51
|
end.compact
|
46
52
|
end
|
47
53
|
|
48
|
-
sig { returns(T::Array[
|
54
|
+
sig { returns(T::Array[Minitest::Runnable]) }
|
49
55
|
def tests
|
50
56
|
select_tests(discover_tests)
|
51
57
|
end
|
data/lib/minitest/distributed.rb
CHANGED
@@ -13,6 +13,9 @@ require "minitest/distributed/result_aggregate"
|
|
13
13
|
require "minitest/distributed/filters/filter_interface"
|
14
14
|
require "minitest/distributed/filters/include_filter"
|
15
15
|
require "minitest/distributed/filters/exclude_filter"
|
16
|
+
require "minitest/distributed/filters/file_filter_base"
|
17
|
+
require "minitest/distributed/filters/exclude_file_filter"
|
18
|
+
require "minitest/distributed/filters/include_file_filter"
|
16
19
|
require "minitest/distributed/coordinators/coordinator_interface"
|
17
20
|
require "minitest/distributed/coordinators/memory_coordinator"
|
18
21
|
require "minitest/distributed/coordinators/redis_coordinator"
|
@@ -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, options)
|
40
16
|
end
|
41
17
|
|
42
18
|
def plugin_distributed_init(options)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Minitest
|
5
|
+
class << self
|
6
|
+
def plugin_junitxml_options(opts, options)
|
7
|
+
options[:junitxml] = ENV['MINITEST_JUNITXML']
|
8
|
+
|
9
|
+
opts.on('--junitxml=PATH', "Generate a JUnitXML report at the specified path") do |path|
|
10
|
+
options[:junitxml] = path
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def plugin_junitxml_init(options)
|
15
|
+
return if options[:junitxml].nil?
|
16
|
+
|
17
|
+
require 'minitest/distributed/reporters/junitxml_reporter'
|
18
|
+
reporter << Minitest::Distributed::Reporters::JUnitXMLReporter.new(options[:io], options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/sorbet/rbi/minitest.rbi
CHANGED
@@ -1,23 +1,23 @@
|
|
1
|
-
#
|
2
|
-
# srb rbi sorbet-typed
|
3
|
-
#
|
4
|
-
# If you would like to make changes to this file, great! Please upstream any changes you make here:
|
5
|
-
#
|
6
|
-
# https://github.com/sorbet/sorbet-typed/edit/master/lib/minitest/all/minitest.rbi
|
7
|
-
#
|
8
|
-
# typed: strong
|
1
|
+
# typed: true
|
9
2
|
|
10
3
|
module Minitest
|
11
4
|
class Runnable
|
12
5
|
def self.run_one_method(klass, method_name, reporter); end
|
13
6
|
def self.runnables; end
|
14
7
|
|
8
|
+
def initialize(method_name); end
|
9
|
+
|
15
10
|
def name; end
|
16
11
|
def time; end
|
17
12
|
def time=(duration); end
|
18
13
|
def failures; end
|
19
|
-
|
20
|
-
def
|
14
|
+
def failures=(failures); end
|
15
|
+
def assertions; end
|
16
|
+
def assertions=(assertions); end
|
17
|
+
def source_location; end
|
18
|
+
def source_location=(value); end
|
19
|
+
def klass; end
|
20
|
+
def klass=(value); end
|
21
21
|
end
|
22
22
|
|
23
23
|
class Test < Runnable
|
@@ -61,10 +61,15 @@ module Minitest
|
|
61
61
|
def result_code; end
|
62
62
|
end
|
63
63
|
|
64
|
-
class Skip <
|
64
|
+
class Skip < Assertion
|
65
65
|
end
|
66
66
|
|
67
67
|
class UnexpectedError < Assertion
|
68
|
+
sig { params(error: Exception).void }
|
69
|
+
def initialize(error); end
|
70
|
+
|
71
|
+
sig { returns(Exception) }
|
72
|
+
def error; end
|
68
73
|
end
|
69
74
|
|
70
75
|
class AbstractReporter
|
@@ -126,6 +131,9 @@ module Minitest
|
|
126
131
|
|
127
132
|
sig { returns(Float) }
|
128
133
|
def self.clock_time; end
|
134
|
+
|
135
|
+
def self.backtrace_filter; end
|
136
|
+
def self.backtrace_filter=(filter); end
|
129
137
|
end
|
130
138
|
|
131
139
|
module Minitest::Assertions
|
@@ -190,6 +198,16 @@ module Minitest::Assertions
|
|
190
198
|
end
|
191
199
|
def assert_predicate(obj, predicate, msg = nil); end
|
192
200
|
|
201
|
+
sig do
|
202
|
+
params(
|
203
|
+
value: BasicObject,
|
204
|
+
operator: Symbol,
|
205
|
+
comparison: BasicObject,
|
206
|
+
msg: T.nilable(String)
|
207
|
+
).returns(TrueClass)
|
208
|
+
end
|
209
|
+
def assert_operator(value, operator, comparison, msg = nil); end
|
210
|
+
|
193
211
|
sig { params(test: T.untyped, msg: T.nilable(String)).returns(TrueClass) }
|
194
212
|
def refute(test, msg = nil); end
|
195
213
|
|
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
|