class RSpecJUnitFormatter

Dumps rspec results as a JUnit XML file. Based on XML schema: windyroad.org/dl/Open%20Source/JUnit.xsd

Constants

DISCOURAGED_REGEXP

Discouraged characters from www.w3.org/TR/xml/#charsets Plus special characters with well-known entity replacements

DISCOURAGED_REPLACEMENTS

Translate well-known entities, or use generic unicode hex entity

ILLEGAL_REGEXP

Inversion of character range from www.w3.org/TR/xml/#charsets

ILLEGAL_REPLACEMENT

Replace illegals with a Ruby-like escape

STRIP_DIFF_COLORS_BLOCK_REGEXP
STRIP_DIFF_COLORS_CODES_REGEXP

Attributes

started[R]

Public Instance Methods

dump_summary(duration, example_count, failure_count, pending_count) click to toggle source
Calls superclass method
# File lib/rspec_junit_formatter/rspec2.rb, line 9
def dump_summary(duration, example_count, failure_count, pending_count)
  super
  xml_dump
end
start(example_count) click to toggle source
Calls superclass method
# File lib/rspec_junit_formatter/rspec2.rb, line 4
def start(example_count)
  @started = Time.now
  super
end
stop(notification) click to toggle source
# File lib/rspec_junit_formatter/rspec3.rb, line 13
def stop(notification)
  @examples_notification = notification
end

Private Instance Methods

classname_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 28
def classname_for(example)
  fp = example_group_file_path_for(example)
  fp.sub(%r{\.[^/.]+\Z}, "").gsub("/", ".").gsub(/\A\.+|\.+\Z/, "")
end
description_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 37
def description_for(example)
  example.full_description
end
duration() click to toggle source
# File lib/rspec_junit_formatter/rspec3.rb, line 38
def duration
  @summary_notification.duration
end
duration_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 33
def duration_for(example)
  example.execution_result[:run_time]
end
escape(text) click to toggle source
# File lib/rspec_junit_formatter.rb, line 163
def escape(text)
  # Make sure it's utf-8, replace illegal characters with ruby-like escapes, and replace special and discouraged characters with entities
  text.to_s.encode(Encoding::UTF_8).gsub(ILLEGAL_REGEXP, ILLEGAL_REPLACEMENT).gsub(DISCOURAGED_REGEXP, DISCOURAGED_REPLACEMENTS)
end
example_count() click to toggle source
# File lib/rspec_junit_formatter/rspec3.rb, line 26
def example_count
  @summary_notification.example_count
end
example_group_file_path_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 20
def example_group_file_path_for(example)
  meta = example.metadata
  while meta[:example_group]
    meta = meta[:example_group]
  end
  meta[:file_path]
end
examples() click to toggle source
# File lib/rspec_junit_formatter/rspec3.rb, line 42
def examples
  @examples_notification.notifications
end
exception_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 41
def exception_for(example)
  example.execution_result[:exception]
end
failure_count() click to toggle source
# File lib/rspec_junit_formatter/rspec3.rb, line 34
def failure_count
  @summary_notification.failure_count
end
failure_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 53
def failure_for(example)
  exception = exception_for(example)
  message   = strip_diff_colors(exception.message)
  backtrace = format_backtrace(exception.backtrace, example)

  if shared_group = find_shared_group(example)
    backtrace << "Shared Example Group: \"#{shared_group.metadata[:shared_group_name]}\" called from #{shared_group.metadata[:example_group][:location]}"
  end

  "#{message}\n#{backtrace.join("\n")}"
end
failure_message_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 49
def failure_message_for(example)
  strip_diff_colors(exception_for(example).to_s)
end
failure_type_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 45
def failure_type_for(example)
  exception_for(example).class.name
end
find_shared_group(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 65
def find_shared_group(example)
  group_and_parent_groups(example).find { |group| group.metadata[:shared_group_name] }
end
group_and_parent_groups(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 69
def group_and_parent_groups(example)
  example.example_group.parent_groups + [example.example_group]
end
pending_count() click to toggle source
# File lib/rspec_junit_formatter/rspec3.rb, line 30
def pending_count
  @summary_notification.pending_count
end
result_of(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 16
def result_of(example)
  example.execution_result[:status].to_sym
end
stderr_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 77
def stderr_for(example)
  example.metadata[:stderr]
end
stdout_for(example) click to toggle source
# File lib/rspec_junit_formatter/rspec2.rb, line 73
def stdout_for(example)
  example.metadata[:stdout]
end
strip_diff_colors(string) click to toggle source
# File lib/rspec_junit_formatter.rb, line 171
def strip_diff_colors(string)
  # XXX: RSpec diffs are appended to the message lines fairly early and will
  # contain ANSI escape codes for colorizing terminal output if the global
  # rspec configuration is turned on, regardless of which notification lines
  # we ask for. We need to strip the codes from the diff part of the message
  # for XML output here.
  #
  # We also only want to target the diff hunks because the failure message
  # itself might legitimately contain ansi escape codes.
  #
  string.sub(STRIP_DIFF_COLORS_BLOCK_REGEXP) { |match| match.gsub(STRIP_DIFF_COLORS_CODES_REGEXP, "".freeze) }
end
swap_rspec_configuration(key, value) { || ... } click to toggle source

rspec makes it really difficult to swap in configuration temporarily due to the way it cascades defaults, command line arguments, and user configuration. This method makes sure configuration gets swapped in correctly, but also that the original state is definitely restored.

# File lib/rspec_junit_formatter/rspec3.rb, line 91
def swap_rspec_configuration(key, value)
  unset = Object.new
  force = RSpec.configuration.send(:value_for, key) { unset }
  if unset.equal?(force)
    previous = RSpec.configuration.send(key)
    RSpec.configuration.send(:"#{key}=", value)
  else
    RSpec.configuration.force({key => value})
  end
  yield
ensure
  if unset.equal?(force)
    RSpec.configuration.send(:"#{key}=", previous)
  else
    RSpec.configuration.force({key => force})
  end
end
without_color(&block) click to toggle source
# File lib/rspec_junit_formatter/rspec3.rb, line 112
def without_color(&block)
  swap_rspec_configuration(:color_mode, :off, &block)
end
xml_dump() click to toggle source

rspec 2 and 3 implements are in separate files.

# File lib/rspec_junit_formatter.rb, line 14
def xml_dump
  output << %{<?xml version="1.0" encoding="UTF-8"?>\n}
  output << %{<testsuite}
  output << %{ name="rspec#{escape(ENV["TEST_ENV_NUMBER"].to_s)}"}
  output << %{ tests="#{example_count}"}
  output << %{ skipped="#{pending_count}"}
  output << %{ failures="#{failure_count}"}
  output << %{ errors="0"}
  output << %{ time="#{escape("%.6f" % duration)}"}
  output << %{ timestamp="#{escape(started.iso8601)}"}
  output << %{ hostname="#{escape(Socket.gethostname)}"}
  output << %{>\n}
  output << %{<properties>\n}
  output << %{<property}
  output << %{ name="seed"}
  output << %{ value="#{escape(RSpec.configuration.seed.to_s)}"}
  output << %{/>\n}
  output << %{</properties>\n}
  xml_dump_examples
  output << %{</testsuite>\n}
end
xml_dump_example(example) { || ... } click to toggle source
# File lib/rspec_junit_formatter.rb, line 66
def xml_dump_example(example)
  output << %{<testcase}
  output << %{ classname="#{escape(classname_for(example))}"}
  output << %{ name="#{escape(description_for(example))}"}
  output << %{ file="#{escape(example_group_file_path_for(example))}"}
  output << %{ time="#{escape("%.6f" % duration_for(example))}"}
  output << %{>}
  yield if block_given?
  xml_dump_output(example)
  output << %{</testcase>\n}
end
xml_dump_examples() click to toggle source
# File lib/rspec_junit_formatter.rb, line 36
def xml_dump_examples
  examples.each do |example|
    case result_of(example)
    when :pending
      xml_dump_pending(example)
    when :failed
      xml_dump_failed(example)
    else
      xml_dump_example(example)
    end
  end
end
xml_dump_failed(example) click to toggle source
# File lib/rspec_junit_formatter.rb, line 55
def xml_dump_failed(example)
  xml_dump_example(example) do
    output << %{<failure}
    output << %{ message="#{escape(failure_message_for(example))}"}
    output << %{ type="#{escape(failure_type_for(example))}"}
    output << %{>}
    output << escape(failure_for(example))
    output << %{</failure>}
  end
end
xml_dump_output(example) click to toggle source
# File lib/rspec_junit_formatter.rb, line 78
def xml_dump_output(example)
  if (stdout = stdout_for(example)) && !stdout.empty?
    output << %{<system-out>}
    output << escape(stdout)
    output << %{</system-out>}
  end

  if (stderr = stderr_for(example)) && !stderr.empty?
    output << %{<system-err>}
    output << escape(stderr)
    output << %{</system-err>}
  end
end
xml_dump_pending(example) click to toggle source
# File lib/rspec_junit_formatter.rb, line 49
def xml_dump_pending(example)
  xml_dump_example(example) do
    output << %{<skipped/>}
  end
end