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