require "html" module Spec # :nodoc: class JUnitFormatter < Formatter @started_at = Time.utc @results = [] of Spec::Result @summary = {} of Status => Int32 def report(result) current = @summary[result.kind]? || 0 @summary[result.kind] = current + 1 @results << result end def finish(elapsed_time, aborted) io = @io io.puts %() io << %() io.puts @results.each { |r| write_report(r, io) } io << %() io.close end def self.file(cli : CLI, output_path : Path) if output_path.extension != ".xml" output_path = output_path.join("output.xml") end Dir.mkdir_p(output_path.dirname) file = File.new(output_path, "w") JUnitFormatter.new(cli, file) end private def escape_xml_attr(value) String.build do |io| reader = Char::Reader.new(value) while reader.has_next? case current_char = reader.current_char when .control? current_char.to_s.inspect_unquoted(io) else current_char.to_s(io) end reader.next_char end end end # -------- private utility methods private def write_report(result, io) io << %( ) if (exception = result.exception) && !result.kind.pending? write_inner_content(tag, exception, io) else io << " <" << tag << "/>\n" end io.puts " " else io.puts %("/>) end end private def inner_content_tag(kind) case kind in .error? then "error" in .fail? then "failure" in .pending? then "skipped" in .success? then nil end end private def write_inner_content(tag, exception, io) io << " <" << tag if message = exception.message io << %( message=") HTML.escape(message, io) io << '"' end if tag == "error" io << %( type=") io << exception.class.name io << '"' end io << '>' if backtrace = exception.backtrace? HTML.escape(backtrace.join('\n'), io) end io << "\n" end private def classname(result) path = Path.new result.file path.expand.relative_to(Dir.current).parts.join('.').rchop path.extension end end end