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 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