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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 829613fe27e895208e49a116fd8f72e3685e1e91eb95209db170ad51707d2115
4
- data.tar.gz: 1e0932553fe7d007e40392f53f80dd49eb6dafd533077d8234d4feb86629e8d4
3
+ metadata.gz: 6a8b0499822e88334b7ce7d06210c43d3054485d4dbbc3170f97e485c5d78c3d
4
+ data.tar.gz: 2e4c720af2bf722152ace13867b0cf66c58aafbf712ac1a5d4b9e308f0c2fd8f
5
5
  SHA512:
6
- metadata.gz: 71dbe335df12886c11af8f438dec954678ad9d5841847e44521e492dfc641eddc76da66e73c0f5d96840f75746e027f3304d08ed46171817a0541d773495b32f
7
- data.tar.gz: fb5a8f6f781c71944df6228e4da980b29f2bd01fd9733aebe10437ac8eaa867d7d55b49397ad2d4f1acca9d503e8b2a8191fcfc8f935675a78f9d75208e0a39f
6
+ metadata.gz: 8c974a610c8770a9a0ff8c944780182a178fbe1d22737a53a474b9f8f2abcd90765aac1f6a0ee9134cddb1e19dca235bb124b7950585337579b7dba5d4ebf956
7
+ data.tar.gz: 8337bf3b5c849d386ccbdb13e4cba6d725a274ead9893c60f3d60f9599e5ba7cde0e12ceea035219483ebd9aee0fc06ee9cc9e6a4747c08663f133dab30fd4d0
data/Gemfile CHANGED
@@ -4,7 +4,7 @@ source "https://rubygems.org"
4
4
  # Specify your gem's dependencies in minitest-distributed.gemspec
5
5
  gemspec
6
6
 
7
- gem "rake", "~> 12.0"
7
+ gem "rake"
8
8
  gem "minitest", "~> 5.0"
9
9
  gem "sorbet"
10
10
  gem "rubocop"
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]) }
@@ -3,6 +3,6 @@
3
3
 
4
4
  module Minitest
5
5
  module Distributed
6
- VERSION = "0.2.1"
6
+ VERSION = "0.2.2"
7
7
  end
8
8
  end
@@ -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
@@ -1,11 +1,4 @@
1
- # This file is autogenerated. Do not edit it by hand. Regenerate it with:
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.1
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: 2020-07-26 00:00:00.000000000 Z
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.0.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: []
data/.travis.yml DELETED
@@ -1,6 +0,0 @@
1
- ---
2
- language: ruby
3
- cache: bundler
4
- rvm:
5
- - 2.6.5
6
- before_install: gem install bundler -v 2.1.4