Skip to content

Commit ef514cb

Browse files
authored
Merge pull request #319 from Shopify/cbruckmayer/export-junitxml-failure-file
Export JunitXML failure file
2 parents ef974ad + 07515fa commit ef514cb

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

ruby/lib/minitest/queue/build_status_reporter.rb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,88 @@ module Queue
44
class BuildStatusReporter < Minitest::Reporters::BaseReporter
55
include ::CI::Queue::OutputHelpers
66

7+
class JUnitReporter
8+
def initialize(file, error_reports)
9+
@file = file
10+
@error_reports = error_reports
11+
end
12+
13+
def write
14+
File.open(@file, 'w+') do |file|
15+
format_document(generate_document(@error_reports), file)
16+
end
17+
end
18+
19+
private
20+
21+
def generate_document(error_reports)
22+
suites = error_reports.group_by { |error_report| error_report.test_suite }
23+
24+
doc = REXML::Document.new(nil, {
25+
:prologue_quote => :quote,
26+
:attribute_quote => :quote,
27+
})
28+
doc << REXML::XMLDecl.new('1.1', 'utf-8')
29+
30+
testsuites = doc.add_element('testsuites')
31+
suites.each do |suite, error_reports|
32+
add_tests_to(testsuites, suite, error_reports)
33+
end
34+
35+
doc
36+
end
37+
38+
def format_document(doc, io)
39+
formatter = REXML::Formatters::Pretty.new
40+
formatter.write(doc, io)
41+
io << "\n"
42+
end
43+
44+
def add_tests_to(testsuites, suite, error_reports)
45+
testsuite = testsuites.add_element(
46+
'testsuite',
47+
'name' => suite,
48+
'filepath' => Minitest::Queue.relative_path(error_reports.first.test_file),
49+
'tests' => error_reports.count,
50+
)
51+
52+
error_reports.each do |error_report|
53+
attributes = {
54+
'name' => error_report.test_name,
55+
'classname' => error_report.test_suite,
56+
}
57+
attributes['lineno'] = error_report.test_line
58+
59+
testcase = testsuite.add_element('testcase', attributes)
60+
add_xml_message_for(testcase, error_report)
61+
rescue REXML::ParseException, RuntimeError => error
62+
puts error
63+
end
64+
end
65+
66+
def add_xml_message_for(testcase, error_report)
67+
failure = testcase.add_element('failure', 'type' => error_report.error_class, 'message' => truncate_message(error_report.to_s))
68+
failure.add_text(REXML::CData.new(message_for(error_report)))
69+
end
70+
71+
def truncate_message(message)
72+
message.lines.first.chomp.gsub(/\e\[[^m]+m/, '')
73+
end
74+
75+
def project_root_path_matcher
76+
@project_root_path_matcher ||= %r{(?<=\s)#{Regexp.escape(Minitest::Queue.project_root)}/}
77+
end
78+
79+
def message_for(error_report)
80+
suite = error_report.test_suite
81+
name = error_report.test_name
82+
error = error_report.to_s
83+
84+
message_with_relative_paths = error.gsub(project_root_path_matcher, '')
85+
"\nFailure:\n#{name}(#{suite}) [#{Minitest::Queue.relative_path(error_report.test_file)}]:\n#{message_with_relative_paths}\n"
86+
end
87+
end
88+
789
def initialize(supervisor:, **options)
890
@supervisor = supervisor
991
@build = supervisor.build
@@ -110,6 +192,8 @@ def write_failure_file(file)
110192
File.open(file, 'w') do |f|
111193
JSON.dump(error_reports.map(&:to_h), f)
112194
end
195+
xml_file = File.join(File.dirname(file), "#{File.basename(file, File.extname(file))}.xml")
196+
JUnitReporter.new(xml_file, error_reports).write
113197
end
114198

115199
def write_flaky_tests_file(file)

ruby/lib/minitest/queue/error_report.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ def test_name
5454
@data[:test_name]
5555
end
5656

57+
def error_class
58+
@data[:error_class]
59+
end
60+
5761
def test_and_module_name
5862
@data[:test_and_module_name]
5963
end

ruby/test/integration/minitest_redis_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,6 +816,14 @@ def test_redis_reporter_failure_file
816816
.sort_by { |failure_report| failure_report[:test_line] }
817817
.first
818818

819+
xml_file = File.join(File.dirname(failure_file), "#{File.basename(failure_file, File.extname(failure_file))}.xml")
820+
xml_content = File.read(xml_file)
821+
xml = REXML::Document.new(xml_content)
822+
testcase = xml.elements['testsuites/testsuite/testcase[@name="test_bar"]']
823+
assert_equal "ATest", testcase.attributes['classname']
824+
assert_equal "test_bar", testcase.attributes['name']
825+
assert_equal "test/dummy_test.rb", testcase.parent.attributes['filepath']
826+
assert_equal "ATest", testcase.parent.attributes['name']
819827

820828
## output and test_file
821829
expected = {

0 commit comments

Comments
 (0)