ci-queue 0.19.0 → 0.20.4
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/README.md +4 -2
- data/lib/ci/queue/version.rb +1 -1
- data/lib/minitest/queue.rb +42 -0
- data/lib/minitest/queue/error_report.rb +4 -0
- data/lib/minitest/queue/failure_formatter.rb +1 -0
- data/lib/minitest/queue/junit_reporter.rb +79 -52
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c7ce73cd1dd2d350b7e9b5293f92380d0bdb61a21aea1924e587fe897a31755
|
4
|
+
data.tar.gz: 70fb38ffa65e591ee0f86b3990dbc07bf587776f9e6d05937209efb50565b467
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3affe2bb8f9d7f1b4b25acd42257e671c981ee212f5c792c69af917cfdc4394de0d0e83c5b94b155d71ef60004c66a01115a3aaf2b19f872dbe81b63cdc7191
|
7
|
+
data.tar.gz: b8493fa643cb3405a42879b320a8714d192c7dfed6be62efd39220ee9d951c10a074ee4a69204784eb9119faa523c50f17788cb286032baf447cca3bbb636e84
|
data/README.md
CHANGED
@@ -51,7 +51,9 @@ The runner also comes with a tool to investigate leaky tests:
|
|
51
51
|
minitest-queue --queue path/to/test_order.log --failing-test 'SomeTest#test_something' bisect -Itest test/**/*_test.rb
|
52
52
|
```
|
53
53
|
|
54
|
-
### RSpec
|
54
|
+
### RSpec [DEPRECATED]
|
55
|
+
|
56
|
+
The rspec-queue runner is deprecated. The minitest-queue runner continues to be supported and is actively being improved. At Shopify, we strongly recommend that new projects set up their test suite using Minitest rather than RSpec.
|
55
57
|
|
56
58
|
Assuming you use one of the supported CI providers, the command can be as simple as:
|
57
59
|
|
@@ -67,4 +69,4 @@ rspec-queue --queue redis://example.com --timeout 600 --report
|
|
67
69
|
|
68
70
|
#### Limitations
|
69
71
|
|
70
|
-
Because of how `ci-queue`
|
72
|
+
Because of how `ci-queue` executes the examples, `before(:all)` and `after(:all)` hooks are not supported. `rspec-queue` will explicitly reject them.
|
data/lib/ci/queue/version.rb
CHANGED
data/lib/minitest/queue.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'shellwords'
|
2
3
|
require 'minitest'
|
3
4
|
require 'minitest/reporters'
|
4
5
|
|
@@ -102,6 +103,47 @@ module Minitest
|
|
102
103
|
end
|
103
104
|
|
104
105
|
module Queue
|
106
|
+
attr_writer :run_command_formatter, :project_root
|
107
|
+
|
108
|
+
def run_command_formatter
|
109
|
+
@run_command_formatter ||= if defined?(Rails) && defined?(Rails::TestUnitRailtie)
|
110
|
+
RAILS_RUN_COMMAND_FORMATTER
|
111
|
+
else
|
112
|
+
DEFAULT_RUN_COMMAND_FORMATTER
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
DEFAULT_RUN_COMMAND_FORMATTER = lambda do |runnable|
|
117
|
+
filename = Minitest::Queue.relative_path(runnable.source_location[0])
|
118
|
+
identifier = "#{runnable.klass}##{runnable.name}"
|
119
|
+
['bundle', 'exec', 'ruby', '-Ilib:test', filename, '-n', identifier]
|
120
|
+
end
|
121
|
+
|
122
|
+
RAILS_RUN_COMMAND_FORMATTER = lambda do |runnable|
|
123
|
+
filename = Minitest::Queue.relative_path(runnable.source_location[0])
|
124
|
+
lineno = runnable.source_location[1]
|
125
|
+
['bin/rails', 'test', "#{filename}:#{lineno}"]
|
126
|
+
end
|
127
|
+
|
128
|
+
def run_command_for_runnable(runnable)
|
129
|
+
command = run_command_formatter.call(runnable)
|
130
|
+
if command.is_a?(Array)
|
131
|
+
Shellwords.join(command)
|
132
|
+
else
|
133
|
+
command
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.project_root
|
138
|
+
@project_root ||= Dir.pwd
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.relative_path(path, root: project_root)
|
142
|
+
Pathname(path).relative_path_from(Pathname(root)).to_s
|
143
|
+
rescue ArgumentError
|
144
|
+
path
|
145
|
+
end
|
146
|
+
|
105
147
|
class SingleExample
|
106
148
|
|
107
149
|
def initialize(runnable, method_name)
|
@@ -1,101 +1,128 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'minitest/reporters'
|
3
|
-
require '
|
4
|
+
require 'rexml/document'
|
4
5
|
require 'fileutils'
|
5
6
|
|
6
7
|
module Minitest
|
7
8
|
module Queue
|
8
9
|
class JUnitReporter < Minitest::Reporters::BaseReporter
|
9
|
-
class XmlMarkup < ::Builder::XmlMarkup
|
10
|
-
def trunc!(txt)
|
11
|
-
txt.sub(/\n.*/m, '...')
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
10
|
def initialize(report_path = 'log/junit.xml', options = {})
|
16
11
|
super({})
|
17
12
|
@report_path = File.absolute_path(report_path)
|
18
13
|
@base_path = options[:base_path] || Dir.pwd
|
19
14
|
end
|
20
15
|
|
21
|
-
def
|
22
|
-
super
|
23
|
-
|
16
|
+
def generate_document
|
24
17
|
suites = tests.group_by { |test| test.klass }
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
19
|
+
doc = REXML::Document.new(nil, {
|
20
|
+
:prologue_quote => :quote,
|
21
|
+
:attribute_quote => :quote,
|
22
|
+
})
|
23
|
+
doc << REXML::XMLDecl.new('1.1', 'utf-8')
|
24
|
+
|
25
|
+
testsuites = doc.add_element('testsuites')
|
26
|
+
suites.each do |suite, tests|
|
27
|
+
add_tests_to(testsuites, suite, tests)
|
32
28
|
end
|
29
|
+
doc
|
30
|
+
end
|
31
|
+
|
32
|
+
def format_document(doc, io)
|
33
|
+
formatter = REXML::Formatters::Pretty.new
|
34
|
+
formatter.write(doc, io)
|
35
|
+
io << "\n"
|
36
|
+
end
|
37
|
+
|
38
|
+
def report
|
39
|
+
super
|
40
|
+
|
33
41
|
FileUtils.mkdir_p(File.dirname(@report_path))
|
34
|
-
File.open(@report_path, 'w+')
|
42
|
+
File.open(@report_path, 'w+') do |file|
|
43
|
+
format_document(generate_document, file)
|
44
|
+
end
|
35
45
|
end
|
36
46
|
|
37
47
|
private
|
38
48
|
|
39
|
-
def add_tests_to(
|
49
|
+
def add_tests_to(testsuites, suite, tests)
|
40
50
|
suite_result = analyze_suite(tests)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
relative_path = location_for_runnable(tests.first) || '<unknown>'
|
52
|
+
|
53
|
+
testsuite = testsuites.add_element(
|
54
|
+
'testsuite',
|
55
|
+
'name' => suite,
|
56
|
+
'filepath' => relative_path,
|
57
|
+
'skipped' => suite_result[:skip_count],
|
58
|
+
'failures' => suite_result[:fail_count],
|
59
|
+
'errors' => suite_result[:error_count],
|
60
|
+
'tests' => suite_result[:test_count],
|
61
|
+
'assertions' => suite_result[:assertion_count],
|
62
|
+
'time' => suite_result[:time],
|
63
|
+
)
|
64
|
+
|
65
|
+
tests.each do |test|
|
66
|
+
lineno = tests.first.source_location.last
|
67
|
+
attributes = {
|
68
|
+
'name' => test.name,
|
69
|
+
'classname' => suite,
|
70
|
+
'assertions' => test.assertions,
|
71
|
+
'time' => test.time,
|
72
|
+
'flaky_test' => test.flaked?,
|
73
|
+
'run-command' => Minitest.run_command_for_runnable(test),
|
74
|
+
}
|
75
|
+
attributes['lineno'] = lineno if lineno != -1
|
76
|
+
|
77
|
+
testcase = testsuite.add_element('testcase', attributes)
|
78
|
+
add_xml_message_for(testcase, test) unless test.passed?
|
56
79
|
end
|
57
80
|
end
|
58
81
|
|
59
|
-
def
|
60
|
-
xml = XmlMarkup.new(indent: 2, margin: 2)
|
82
|
+
def add_xml_message_for(testcase, test)
|
61
83
|
failure = test.failure
|
62
|
-
|
63
84
|
if test.skipped? && !test.flaked?
|
64
|
-
|
85
|
+
testcase.add_element('skipped', 'type' => failure.error.class.name)
|
65
86
|
elsif test.error?
|
66
|
-
|
67
|
-
|
68
|
-
end
|
87
|
+
error = testcase.add_element('error', 'type' => failure.error.class.name, 'message' => truncate_message(failure.message))
|
88
|
+
error.add_text(REXML::CData.new(message_for(test)))
|
69
89
|
elsif failure
|
70
|
-
|
71
|
-
|
72
|
-
end
|
90
|
+
failure = testcase.add_element('failure', 'type' => failure.error.class.name, 'message' => truncate_message(failure.message))
|
91
|
+
failure.add_text(REXML::CData.new(message_for(test)))
|
73
92
|
end
|
74
93
|
end
|
75
94
|
|
95
|
+
def truncate_message(message)
|
96
|
+
message.lines.first.chomp.gsub(/\e\[[^m]+m/, '')
|
97
|
+
end
|
98
|
+
|
99
|
+
def project_root_path_matcher
|
100
|
+
@project_root_path_matcher ||= %r{(?<=\s)#{Regexp.escape(Minitest::Queue.project_root)}/}
|
101
|
+
end
|
102
|
+
|
76
103
|
def message_for(test)
|
77
104
|
suite = test.klass
|
78
105
|
name = test.name
|
79
106
|
error = test.failure
|
80
107
|
|
108
|
+
message_with_relative_paths = error.message.gsub(project_root_path_matcher, '')
|
81
109
|
if test.passed?
|
82
110
|
nil
|
83
111
|
elsif test.skipped?
|
84
|
-
"
|
112
|
+
"\nSkipped:\n#{name}(#{suite}) [#{location_for_runnable(test)}]:\n#{message_with_relative_paths}\n"
|
85
113
|
elsif test.failure
|
86
|
-
"
|
114
|
+
"\nFailure:\n#{name}(#{suite}) [#{location_for_runnable(test)}]:\n#{message_with_relative_paths}\n"
|
87
115
|
elsif test.error?
|
88
|
-
"
|
116
|
+
"\nError:\n#{name}(#{suite}) [#{location_for_runnable(test)}]:\n#{message_with_relative_paths}\n"
|
89
117
|
end
|
90
118
|
end
|
91
119
|
|
92
|
-
def
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
120
|
+
def location_for_runnable(runnable)
|
121
|
+
if runnable.source_location.first == 'unknown'
|
122
|
+
nil
|
123
|
+
else
|
124
|
+
Minitest::Queue.relative_path(runnable.source_location.first)
|
97
125
|
end
|
98
|
-
last_before_assertion.sub(/:in .*$/, '')
|
99
126
|
end
|
100
127
|
|
101
128
|
def analyze_suite(tests)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ci-queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.20.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jean Boussier
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-05-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|