From 07515fa50d9ea82af968f4bb506f8b356967a730 Mon Sep 17 00:00:00 2001 From: Christian Bruckmayer Date: Wed, 26 Mar 2025 18:37:54 +0000 Subject: [PATCH] Export JunitXML failure file --- .../minitest/queue/build_status_reporter.rb | 84 +++++++++++++++++++ ruby/lib/minitest/queue/error_report.rb | 4 + ruby/test/integration/minitest_redis_test.rb | 8 ++ 3 files changed, 96 insertions(+) diff --git a/ruby/lib/minitest/queue/build_status_reporter.rb b/ruby/lib/minitest/queue/build_status_reporter.rb index 39d57383..f050a695 100644 --- a/ruby/lib/minitest/queue/build_status_reporter.rb +++ b/ruby/lib/minitest/queue/build_status_reporter.rb @@ -4,6 +4,88 @@ module Queue class BuildStatusReporter < Minitest::Reporters::BaseReporter include ::CI::Queue::OutputHelpers + class JUnitReporter + def initialize(file, error_reports) + @file = file + @error_reports = error_reports + end + + def write + File.open(@file, 'w+') do |file| + format_document(generate_document(@error_reports), file) + end + end + + private + + def generate_document(error_reports) + suites = error_reports.group_by { |error_report| error_report.test_suite } + + doc = REXML::Document.new(nil, { + :prologue_quote => :quote, + :attribute_quote => :quote, + }) + doc << REXML::XMLDecl.new('1.1', 'utf-8') + + testsuites = doc.add_element('testsuites') + suites.each do |suite, error_reports| + add_tests_to(testsuites, suite, error_reports) + end + + doc + end + + def format_document(doc, io) + formatter = REXML::Formatters::Pretty.new + formatter.write(doc, io) + io << "\n" + end + + def add_tests_to(testsuites, suite, error_reports) + testsuite = testsuites.add_element( + 'testsuite', + 'name' => suite, + 'filepath' => Minitest::Queue.relative_path(error_reports.first.test_file), + 'tests' => error_reports.count, + ) + + error_reports.each do |error_report| + attributes = { + 'name' => error_report.test_name, + 'classname' => error_report.test_suite, + } + attributes['lineno'] = error_report.test_line + + testcase = testsuite.add_element('testcase', attributes) + add_xml_message_for(testcase, error_report) + rescue REXML::ParseException, RuntimeError => error + puts error + end + end + + def add_xml_message_for(testcase, error_report) + failure = testcase.add_element('failure', 'type' => error_report.error_class, 'message' => truncate_message(error_report.to_s)) + failure.add_text(REXML::CData.new(message_for(error_report))) + end + + def truncate_message(message) + message.lines.first.chomp.gsub(/\e\[[^m]+m/, '') + end + + def project_root_path_matcher + @project_root_path_matcher ||= %r{(?<=\s)#{Regexp.escape(Minitest::Queue.project_root)}/} + end + + def message_for(error_report) + suite = error_report.test_suite + name = error_report.test_name + error = error_report.to_s + + message_with_relative_paths = error.gsub(project_root_path_matcher, '') + "\nFailure:\n#{name}(#{suite}) [#{Minitest::Queue.relative_path(error_report.test_file)}]:\n#{message_with_relative_paths}\n" + end + end + def initialize(supervisor:, **options) @supervisor = supervisor @build = supervisor.build @@ -110,6 +192,8 @@ def write_failure_file(file) File.open(file, 'w') do |f| JSON.dump(error_reports.map(&:to_h), f) end + xml_file = File.join(File.dirname(file), "#{File.basename(file, File.extname(file))}.xml") + JUnitReporter.new(xml_file, error_reports).write end def write_flaky_tests_file(file) diff --git a/ruby/lib/minitest/queue/error_report.rb b/ruby/lib/minitest/queue/error_report.rb index 281c248b..06dd3693 100644 --- a/ruby/lib/minitest/queue/error_report.rb +++ b/ruby/lib/minitest/queue/error_report.rb @@ -54,6 +54,10 @@ def test_name @data[:test_name] end + def error_class + @data[:error_class] + end + def test_and_module_name @data[:test_and_module_name] end diff --git a/ruby/test/integration/minitest_redis_test.rb b/ruby/test/integration/minitest_redis_test.rb index 59ccb769..7b8c1172 100644 --- a/ruby/test/integration/minitest_redis_test.rb +++ b/ruby/test/integration/minitest_redis_test.rb @@ -816,6 +816,14 @@ def test_redis_reporter_failure_file .sort_by { |failure_report| failure_report[:test_line] } .first + xml_file = File.join(File.dirname(failure_file), "#{File.basename(failure_file, File.extname(failure_file))}.xml") + xml_content = File.read(xml_file) + xml = REXML::Document.new(xml_content) + testcase = xml.elements['testsuites/testsuite/testcase[@name="test_bar"]'] + assert_equal "ATest", testcase.attributes['classname'] + assert_equal "test_bar", testcase.attributes['name'] + assert_equal "test/dummy_test.rb", testcase.parent.attributes['filepath'] + assert_equal "ATest", testcase.parent.attributes['name'] ## output and test_file expected = {