minitest-distributed 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|