@@ -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+ "\n Failure:\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 )
0 commit comments