minitest-distributed 0.2.1 → 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/Gemfile +1 -1
- data/README.md +6 -0
- data/lib/minitest/distributed/configuration.rb +11 -0
- data/lib/minitest/distributed/filters/exclude_file_filter.rb +18 -0
- data/lib/minitest/distributed/filters/file_filter_base.rb +29 -0
- data/lib/minitest/distributed/filters/include_file_filter.rb +18 -0
- data/lib/minitest/distributed/reporters/junitxml_reporter.rb +150 -0
- data/lib/minitest/distributed/test_selector.rb +8 -0
- data/lib/minitest/distributed/version.rb +1 -1
- data/lib/minitest/distributed.rb +3 -0
- data/lib/minitest/junitxml_plugin.rb +21 -0
- data/sorbet/rbi/minitest.rbi +11 -8
- metadata +11 -7
- data/.travis.yml +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a8b0499822e88334b7ce7d06210c43d3054485d4dbbc3170f97e485c5d78c3d
|
4
|
+
data.tar.gz: 2e4c720af2bf722152ace13867b0cf66c58aafbf712ac1a5d4b9e308f0c2fd8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c974a610c8770a9a0ff8c944780182a178fbe1d22737a53a474b9f8f2abcd90765aac1f6a0ee9134cddb1e19dca235bb124b7950585337579b7dba5d4ebf956
|
7
|
+
data.tar.gz: 8337bf3b5c849d386ccbdb13e4cba6d725a274ead9893c60f3d60f9599e5ba7cde0e12ceea035219483ebd9aee0fc06ee9cc9e6a4747c08663f133dab30fd4d0
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -80,6 +80,12 @@ them to fail.
|
|
80
80
|
- `--worker-id=IDENTIFIER` or `ENV[MINITEST_WORKER_ID]`: The ID of the worker,
|
81
81
|
which should be unique to the cluster. We will default to a UUID if this is
|
82
82
|
not set, which generally is fine.
|
83
|
+
- `--exclude-file=PATH_TO_FILE`: Specify a file of tests to be excluded
|
84
|
+
from running. The file should include test identifiers seperated by
|
85
|
+
newlines.
|
86
|
+
- `--include-file=PATH_TO_FILE`: Specify a file of tests to be included in
|
87
|
+
the test run. The file should include test identifiers seperated by
|
88
|
+
newlines.
|
83
89
|
|
84
90
|
## Limitations
|
85
91
|
|
@@ -71,12 +71,21 @@ module Minitest
|
|
71
71
|
configuration.progress = enabled
|
72
72
|
end
|
73
73
|
|
74
|
+
opts.on('--exclude-file=FILE_PATH', "Specify a file of tests to be excluded from running") do |file_path|
|
75
|
+
configuration.exclude_file = file_path
|
76
|
+
end
|
77
|
+
|
78
|
+
opts.on('--include-file=FILE_PATH', "Specify a file of tests to be included in the test run") do |file_path|
|
79
|
+
configuration.include_file = file_path
|
80
|
+
end
|
81
|
+
|
74
82
|
configuration
|
75
83
|
end
|
76
84
|
end
|
77
85
|
|
78
86
|
extend T::Sig
|
79
87
|
|
88
|
+
# standard minitest options don't need to be specified
|
80
89
|
prop :coordinator_uri, URI::Generic, default: URI('memory:')
|
81
90
|
prop :run_id, String, factory: -> { SecureRandom.uuid }
|
82
91
|
prop :worker_id, String, factory: -> { SecureRandom.uuid }
|
@@ -86,6 +95,8 @@ module Minitest
|
|
86
95
|
prop :max_failures, T.nilable(Integer)
|
87
96
|
prop :retry_failures, T::Boolean, default: true
|
88
97
|
prop :progress, T::Boolean, default: false
|
98
|
+
prop :exclude_file, T.nilable(String)
|
99
|
+
prop :include_file, T.nilable(String)
|
89
100
|
|
90
101
|
sig { returns(Coordinators::CoordinatorInterface) }
|
91
102
|
def coordinator
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Minitest
|
5
|
+
module Distributed
|
6
|
+
module Filters
|
7
|
+
class ExcludeFileFilter < FileFilterBase
|
8
|
+
extend T::Sig
|
9
|
+
include FilterInterface
|
10
|
+
|
11
|
+
sig { override.params(runnable: Minitest::Runnable).returns(T::Array[Runnable]) }
|
12
|
+
def call(runnable)
|
13
|
+
tests.include?(DefinedRunnable.identifier(runnable)) ? [] : [runnable]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Minitest
|
5
|
+
module Distributed
|
6
|
+
module Filters
|
7
|
+
class FileFilterBase
|
8
|
+
extend T::Sig
|
9
|
+
|
10
|
+
sig { returns(Pathname) }
|
11
|
+
attr_reader :file
|
12
|
+
|
13
|
+
sig { params(file: Pathname).void }
|
14
|
+
def initialize(file)
|
15
|
+
@file = file
|
16
|
+
@tests = T.let(nil, T.nilable(T::Set[String]))
|
17
|
+
end
|
18
|
+
|
19
|
+
sig { returns(T::Set[String]) }
|
20
|
+
def tests
|
21
|
+
@tests ||= begin
|
22
|
+
tests = File.readlines(@file, chomp: true)
|
23
|
+
Set.new(tests)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# typed: strict
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Minitest
|
5
|
+
module Distributed
|
6
|
+
module Filters
|
7
|
+
class IncludeFileFilter < FileFilterBase
|
8
|
+
extend T::Sig
|
9
|
+
include FilterInterface
|
10
|
+
|
11
|
+
sig { override.params(runnable: Minitest::Runnable).returns(T::Array[Runnable]) }
|
12
|
+
def call(runnable)
|
13
|
+
tests.include?(DefinedRunnable.identifier(runnable)) ? [runnable] : []
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -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
|
@@ -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,6 +26,12 @@ 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
37
|
sig { returns(T::Array[Minitest::Runnable]) }
|
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"
|
@@ -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,11 +1,4 @@
|
|
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
|
@@ -19,6 +12,8 @@ module Minitest
|
|
19
12
|
def time=(duration); end
|
20
13
|
def failures; end
|
21
14
|
def failures=(failures); end
|
15
|
+
def assertions; end
|
16
|
+
def assertions=(assertions); end
|
22
17
|
def source_location; end
|
23
18
|
def source_location=(value); end
|
24
19
|
def klass; end
|
@@ -70,6 +65,11 @@ module Minitest
|
|
70
65
|
end
|
71
66
|
|
72
67
|
class UnexpectedError < Assertion
|
68
|
+
sig { params(error: Exception).void }
|
69
|
+
def initialize(error); end
|
70
|
+
|
71
|
+
sig { returns(Exception) }
|
72
|
+
def error; end
|
73
73
|
end
|
74
74
|
|
75
75
|
class AbstractReporter
|
@@ -131,6 +131,9 @@ module Minitest
|
|
131
131
|
|
132
132
|
sig { returns(Float) }
|
133
133
|
def self.clock_time; end
|
134
|
+
|
135
|
+
def self.backtrace_filter; end
|
136
|
+
def self.backtrace_filter=(filter); end
|
134
137
|
end
|
135
138
|
|
136
139
|
module Minitest::Assertions
|
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.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Willem van Bergen
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-03-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -77,7 +77,6 @@ files:
|
|
77
77
|
- ".github/workflows/ruby.yml"
|
78
78
|
- ".gitignore"
|
79
79
|
- ".rubocop.yml"
|
80
|
-
- ".travis.yml"
|
81
80
|
- CODE_OF_CONDUCT.md
|
82
81
|
- Gemfile
|
83
82
|
- LICENSE.txt
|
@@ -94,11 +93,15 @@ files:
|
|
94
93
|
- lib/minitest/distributed/coordinators/memory_coordinator.rb
|
95
94
|
- lib/minitest/distributed/coordinators/redis_coordinator.rb
|
96
95
|
- lib/minitest/distributed/enqueued_runnable.rb
|
96
|
+
- lib/minitest/distributed/filters/exclude_file_filter.rb
|
97
97
|
- lib/minitest/distributed/filters/exclude_filter.rb
|
98
|
+
- lib/minitest/distributed/filters/file_filter_base.rb
|
98
99
|
- lib/minitest/distributed/filters/filter_interface.rb
|
100
|
+
- lib/minitest/distributed/filters/include_file_filter.rb
|
99
101
|
- lib/minitest/distributed/filters/include_filter.rb
|
100
102
|
- lib/minitest/distributed/reporters/distributed_progress_reporter.rb
|
101
103
|
- lib/minitest/distributed/reporters/distributed_summary_reporter.rb
|
104
|
+
- lib/minitest/distributed/reporters/junitxml_reporter.rb
|
102
105
|
- lib/minitest/distributed/reporters/redis_coordinator_warnings_reporter.rb
|
103
106
|
- lib/minitest/distributed/result_aggregate.rb
|
104
107
|
- lib/minitest/distributed/result_type.rb
|
@@ -106,6 +109,7 @@ files:
|
|
106
109
|
- lib/minitest/distributed/test_selector.rb
|
107
110
|
- lib/minitest/distributed/version.rb
|
108
111
|
- lib/minitest/distributed_plugin.rb
|
112
|
+
- lib/minitest/junitxml_plugin.rb
|
109
113
|
- minitest-distributed.gemspec
|
110
114
|
- sorbet/config
|
111
115
|
- sorbet/rbi/minitest.rbi
|
@@ -120,7 +124,7 @@ metadata:
|
|
120
124
|
allowed_push_host: https://rubygems.org
|
121
125
|
homepage_uri: https://github.com/Shopify/minitest-distributed
|
122
126
|
source_code_uri: https://github.com/Shopify/minitest-distributed
|
123
|
-
post_install_message:
|
127
|
+
post_install_message:
|
124
128
|
rdoc_options: []
|
125
129
|
require_paths:
|
126
130
|
- lib
|
@@ -135,8 +139,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
135
139
|
- !ruby/object:Gem::Version
|
136
140
|
version: '0'
|
137
141
|
requirements: []
|
138
|
-
rubygems_version: 3.
|
139
|
-
signing_key:
|
142
|
+
rubygems_version: 3.2.20
|
143
|
+
signing_key:
|
140
144
|
specification_version: 4
|
141
145
|
summary: Distributed test executor plugin for Minitest
|
142
146
|
test_files: []
|