class Minitest::Distributed::Reporters::JUnitXMLReporter
Reporter that generates a JUnit XML report of the results it is presented.
The JUnitXML schema is not very well standardized, and many implementations deviate from the schema (see www.ibm.com/support/knowledgecenter/SSQ2R2_14.2.0/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html).
This JunitXML importer embraces this flexibility, and extends the format with some additional information that we can use to create more meaningful annotations. For instance, the information can be use to set annotations on your build system or for annotations using the GitHub checks API.
For the implementation, we use REXML to prevent the need of additional dependencies on this gem. We also use XML 1.1, which allows more characters to be valid. We are primarily interested in this so e is an allowed character, which is used for ANSI color coding.
Attributes
Public Class Methods
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 29 def initialize(io, options) super @report_path = T.let(options.fetch(:junitxml), String) @results = T.let(Hash.new { |hash, key| hash[key] = [] }, T::Hash[String, T::Array[Minitest::Result]]) end
Public Instance Methods
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 69 def format_document(doc, io) formatter = REXML::Formatters::Pretty.new formatter.write(doc, io) io << "\n" end
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 57 def generate_document doc = REXML::Document.new(nil, prologue_quote: :quote, attribute_quote: :quote) doc << REXML::XMLDecl.new("1.1", "utf-8") testsuites = doc.add_element("testsuites") results.each do |suite, tests| add_tests_to(testsuites, suite, tests) end doc end
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 36 def record(result) case (result_type = ResultType.of(result)) when ResultType::Passed, ResultType::Failed, ResultType::Error T.must(results[result.klass]) << result when ResultType::Skipped, ResultType::Requeued, ResultType::Discarded # We will not include skipped, requeued, and discarded tests in JUnitXML reports, # because they will not fail builds, but also didn't pass. else T.absurd(result_type) end end
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 49 def report FileUtils.mkdir_p(File.dirname(@report_path)) File.open(@report_path, "w+") do |file| format_document(generate_document, file) end end
Private Instance Methods
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 104 def add_failure_tag_if_needed(testcase, result) case (result_type = ResultType.of(result)) when ResultType::Passed, ResultType::Skipped, ResultType::Requeued, ResultType::Discarded # noop when ResultType::Error, ResultType::Failed failure = T.must(result.failure) failure_tag = testcase.add_element("failure", "type" => result_type.serialize, "message" => truncate_message(failure.message)) failure_tag.add_text(REXML::CData.new(result.to_s)) else T.absurd(result_type) end end
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 78 def add_tests_to(testsuites, suite, results) # TODO: make path relative to project root relative_path = T.must(results.first).source_location.first lineno = T.must(results.first).source_location.last testsuite = testsuites.add_element( "testsuite", { "name" => suite, "filepath" => relative_path }.merge(aggregate_suite_results(results)) ) results.each do |test| attributes = { "name" => test.name, "classname" => suite, "assertions" => test.assertions, "time" => test.time, # 'run-command' => ... # TODO } attributes["lineno"] = lineno if lineno != -1 testcase_tag = testsuite.add_element("testcase", attributes) add_failure_tag_if_needed(testcase_tag, test) end end
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 125 def aggregate_suite_results(results) aggregate = Hash.new(0) results.each do |result| aggregate["assertions"] += result.assertions aggregate["failures"] += 1 if failure?(ResultType.of(result)) aggregate["tests"] += 1 aggregate["time"] += result.time end aggregate end
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 137 def failure?(result_type) case result_type when ResultType::Failed, ResultType::Error true when ResultType::Passed, ResultType::Skipped, ResultType::Discarded, ResultType::Requeued false else T.absurd(result_type) end end
# File lib/minitest/distributed/reporters/junitxml_reporter.rb, line 120 def truncate_message(message) T.must(message.lines.first).chomp.gsub(/\e\[[^m]+m/, "") end