module Danger # Report, or inspect any JUnit XML formatted test suite report. # # Testing frameworks have standardized on the JUnit XML format for # reporting results, this means that projects using Rspec, Jasmine, Mocha, # XCTest and more - can all use the same Danger error reporting. Perfect. # # You can see some examples on [this page from Circle CI](https://circleci.com/docs/test-metadata/) # about how you can add JUnit XML output for your testing projects. # # @example Parse the XML file, and let the plugin do your reporting # # junit.parse "/path/to/output.xml" # junit.report # # @example Let the plugin parse the XML file, and report yourself # # junit.parse "/path/to/output.xml" # fail("Tests failed") unless junit.fails.empty? # # @example Warn on a report about skipped tests # # junit.parse "/path/to/output.xml" # junit.show_skipped_tests = true # junit.report # # @see orta/danger-junit, danger/danger, artsy/eigen # @tags testing, reporting, junit, rspec, jasmine, jest, xcpretty # class DangerJunit < Plugin # An array of XML elements that represent passed tests. # # @return [Array] attr_accessor :passes # An array of XML elements that represent failed tests. # # @return [Array] attr_accessor :failures # An array of XML elements that represent passed tests. # # @return [Array] attr_accessor :errors # An array of XML elements that represent skipped tests. # # @return [Array] attr_accessor :skipped # An attribute to make the plugin show a warning on skipped tests. # # @return [Bool] attr_accessor :show_skipped_tests # Parses an XML file, which fills all the attributes # will `raise` for errors # @return [void] def parse(file) require 'ox' raise "No Junit file was found at #{file}" unless File.exist? file xml_string = File.read file @doc = Ox.parse xml_string suite_root = @doc.nodes.first.value == 'testsuites' ? @doc.nodes.first : @doc tests = suite_root.nodes.map(&:nodes).flatten.select { |node| node.value == 'testcase' } failed_suites = suite_root.nodes.select { |suite| suite[:failures].to_i > 0 || suite[:errors].to_i > 0 } failed_tests = failed_suites.map(&:nodes).flatten.select { |node| node.value == 'testcase' } @failures = failed_tests.select { |test| test.nodes.count > 0 }.select { |test| test.nodes.first.value == 'failure' } @errors = failed_tests.select { |test| test.nodes.count > 0 }.select { |test| test.nodes.first.value == 'error' } @skipped = tests.select { |test| test.nodes.count > 0 }.select { |test| test.nodes.first.value == 'skipped' } @passes = tests - @failures - @errors - @skipped end # Causes a build fail if there are test failures, # and outputs a markdown table of the results. # # @return [void] def report warn("Skipped #{skipped.count} tests.") if show_skipped_tests && skipped.count > 0 unless failures.empty? && errors.empty? fail("Tests have failed, see below for more information.", sticky: false) message = "### Tests: \n\n" tests = (failures + errors) keys = tests.first.attributes.keys attributes = keys.map(&:to_s).map(&:capitalize) # Create the header message << attributes.join(' | ') + "|\n" message << attributes.map { |_| '---' }.join(' | ') + "|\n" tests.each do |test| message << test.attributes.values.join(' | ') + "|\n" end markdown message end end end end