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

results[R]

Public Class Methods

new(io, options) click to toggle source
Calls superclass method
# 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

format_document(doc, io) click to toggle source
# 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
generate_document() click to toggle source
# 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
record(result) click to toggle source
# 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
report() click to toggle source
# 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

add_failure_tag_if_needed(testcase, result) click to toggle source
# 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
add_tests_to(testsuites, suite, results) click to toggle source
# 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
aggregate_suite_results(results) click to toggle source
# 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
failure?(result_type) click to toggle source
# 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
truncate_message(message) click to toggle source
# 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