require "time" require "rspec/core" require "rspec/core/formatters/base_formatter" # Dumps rspec results as a JUnit XML file. # Based on XML schema: http://windyroad.org/dl/Open%20Source/JUnit.xsd class RSpecJUnitFormatter < RSpec::Core::Formatters::BaseFormatter # rspec 2 and 3 implements are in separate files. private def xml_dump output << %{\n} output << %{\n} output << %{\n} output << %{\n} output << %{\n} xml_dump_examples output << %{\n} end def xml_dump_examples examples.each do |example| send :"xml_dump_#{result_of(example)}", example end end def xml_dump_passed(example) xml_dump_example(example) end def xml_dump_pending(example) xml_dump_example(example) do output << %{} end end def xml_dump_failed(example) exception = exception_for(example) xml_dump_example(example) do output << %{} output << escape(failure_for(example)) output << %{} end end def xml_dump_example(example) output << %{} yield if block_given? output << %{\n} end # Based on valid characters allowed in XML unescaped, with restricted and # discouraged characters removed # # See https://www.w3.org/TR/xml/#dt-chardata ESCAPE_REGEXP = Regexp.new( "[^" << "\u{9}" << # => \t "\u{a}" << # =>\n "\u{d}" << # => \r "\u{20}-\u{21}" << # "\u{22}" << # => " "\u{23}-\u{25}" << # "\u{26}" << # => & # "\u{27}" << # => ' "\u{28}-\u{3b}" << # "\u{3c}" << # => < "\u{3d}" << # "\u{3e}" << # => > "\u{3f}-\u{7e}" << # "\u{7f}-\u{84}" << # discouraged control characters "\u{85}" << # "\u{86}-\u{9f}" << # discouraged control characters "\u{a0}-\u{d7ff}" << "\u{e000}-\u{ffcf}" << # "\u{ffd0}-\u{fdef}" << "\u{fdf0}-\u{fffd}" << # things get murky from here, just escape anything with a higher codepoint # "\u{10000}-\u{10ffff}" << "]" ) # Translate well-known entities, or use generic unicode hex entity ESCAPE_ENTITY = Hash.new { |_, c| "&##{c.ord.to_s(16)};".freeze }.update( ?" => """.freeze, ?& => "&".freeze, ?' => "'".freeze, ?< => "<".freeze, ?> => ">".freeze, ).freeze def escape(text) # Make sure it's utf-8 (this will throw errors for bad output, but that # seems okay) and replace invalid xml characters with entities text.to_s.encode(Encoding::UTF_8).gsub(ESCAPE_REGEXP, ESCAPE_ENTITY) end end RspecJunitFormatter = RSpecJUnitFormatter if RSpec::Core::Version::STRING.start_with? "2." require "rspec_junit_formatter/rspec2" else require "rspec_junit_formatter/rspec3" end