module RSpecFlake

<testsuite errors=“0” failures=“0” skipped=“0” tests=“4” time=“0.001283” timestamp=“2015-01-15T10:25:40-05:00”>

<testsuite location="./spec/a_spec.rb:1" name="a" tests="2" errors="0" failures="0" skipped="0">
  <testcase name="a a 1" time="0.000177" location="./spec/a_spec.rb:6">

# sax parser # nokogiri.org/Nokogiri/XML/SAX.html

Constants

DATE
VERSION

Public Class Methods

cdata(content) click to toggle source
# File lib/rspec_flake/convert.rb, line 30
def cdata content
  "\n<![CDATA[#{content}]]>\n"
end
file_time(opts={}) click to toggle source

Read in JUnit xml files and output an xml report that lists the time spent per file

@example puts RSpecFlake.file_time files: Dir.glob(“*.xml”)

# File lib/rspec_flake/file_time.rb, line 8
def file_time opts={}
  files = opts[:files]
  files = [files] unless files.is_a?(Array)

  file_times = Hash.new 0

  xml = Nokogiri::XML(merge_individual_xml files: files)
  xml.xpath('//testcase').each do |test|
    file_name = test.attr('location').split(':').first
    file_times[file_name] += test.attr('time').to_i
  end

  file_times = file_times.sort_by { |file, time| time }.reverse

  # Paste into Google Sheets with Ctrl/Command + Shift + V
  output = ''
  file_times.each do |file, time|
    time = ChronicDuration.output(time) || '0'
    output += "#{time.ljust(12)}\t#{file}\n"
  end

  output
end
hash_to_xml(source_hash) click to toggle source
# File lib/rspec_flake/convert.rb, line 3
def hash_to_xml source_hash
  # top level attribues from root node
  converted             = source_hash[:testsuites][:attrs] || {}
  converted[:testsuite] = []

  source_hash[:testsuites][:testsuite].each do |suite_location, suite_obj|
    suite_with_tests            = suite_obj[:attrs] || {}
    suite_with_tests[:testcase] = []
    suite_obj[:testcase].each do |testcase_location, testcase_obj|
      testcase = testcase_obj[:attrs]
      if testcase_obj[:failure]
        fail_content = testcase_obj[:failure][:content]
        fail_content = cdata fail_content
        # content key must be a string for xml simple
        testcase.merge!(failure: testcase_obj[:failure][:attrs].merge('content' => fail_content))
      end
      suite_with_tests[:testcase] << testcase
    end

    converted[:testsuite] << suite_with_tests
  end

  # ap converted, index: false, indent: 2

  xml_out converted
end
merge_individual_xml(opts={}) click to toggle source

Merges individual xml reports (from parallel_rspec) into a single combined xml file

@example:

merge_individual_xml files: Dir.glob('tmp/*.xml')
# File lib/rspec_flake/merge.rb, line 9
def merge_individual_xml opts={}
  files = opts[:files]
  files = [files] unless files.is_a?(Array)

  files_hash = {}
  files.each { |file| files_hash.merge!(RSpecFlake.parse_xml(file)[:testsuites][:testsuite]) }

  RSpecFlake.hash_to_xml({ testsuites: { testsuite: files_hash } })
end
merge_xml(opts={}) click to toggle source

merge input xml files into a hash

input - single path or array of paths to input xml files @return merged hash

# File lib/rspec_flake/merge.rb, line 23
def merge_xml opts={}
  input = opts[:input]
  raise 'input path(s) not provided' unless input
  input = [input] unless input.kind_of?(Array)

  merged = { testsuite: { testcase: {} } }

  # annotate each testcase node with two attributes
  #
  # failures - amount of times this test failed
  # runs     - amount of times this test was executed
  #
  # name/time - saved from the first run of the testcase
  # location - stored for each run

  input.each do |xml_path|
    parsed = parse_xml(xml_path)

    parsed[:testsuites][:testsuite].each do |suite_location, suite_obj|
      suite_obj[:testcase].each do |testcase_location, testcase_obj|
        attrs = testcase_obj[:attrs]
        test  = merged[:testsuite][:testcase][testcase_location] ||= { failures: 0, runs: 0, name: attrs['name'], location: attrs['location'], time: [] }

        if testcase_obj[:failure]
          test[:failures] += 1
          test[:failure]  ||= []
          test[:failure] << { 'content' => cdata(testcase_obj[:failure][:content]) }
        end
        test[:runs] += 1
        test[:time] << attrs['time']
      end
    end
  end

  # transform merge hash keyed on location into simple testcase array
  # for xml conversion
  converted = { testsuite: { testcase: [] } }

  merged[:testsuite][:testcase].each do |testcase_location, testcase_obj|
    converted[:testsuite][:testcase] << testcase_obj
  end

  xml_out converted
end
parse_xml(xml_path) click to toggle source

Parse JUnit xml into a Hash keyed on location

# File lib/rspec_flake/parse.rb, line 73
def parse_xml xml_path
  xml = read_as_utf8 xml_path

  parser = @parser ||= Nokogiri::XML::SAX::Parser.new(ReportParser.new)
  parser.document.reset
  parser.parse xml

  parser.document.result
end
read_as_utf8(path) click to toggle source
# File lib/rspec_flake/parse.rb, line 68
def read_as_utf8 path
  File.read(path).encode!('UTF-8', invalid: :replace, undef: :replace, replace: '')
end
stats_from_merge_xml(xml_string) click to toggle source
# File lib/rspec_flake/stats.rb, line 3
def stats_from_merge_xml xml_string
  output = ''

  xml = Nokogiri::XML(xml_string)
  xml.xpath('//testcase').each do |testcase|
    # <testcase failures="0" runs="3" name="a a 1" location="./spec/a_spec.rb:6">
    failures = testcase[:failures]
    runs     = testcase[:runs]
    name     = testcase[:name]
    location = testcase[:location]

    times = []
    testcase.xpath('time').each do |time|
     times << time.content.to_f
    end

    average_time = (times.inject(:+).to_f / times.size).round 2

    output += "#{name} - runs: #{runs} - failures: #{failures} - avg time: #{average_time} - #{location}\n"
  end

  output
end
xml_out(hash) click to toggle source
# File lib/rspec_flake/convert.rb, line 34
def xml_out hash
  xml_header = %Q(<?xml version="1.0" encoding="UTF-8"?>\n)
  xml_body   = XmlSimple.xml_out(hash, RootName: 'testsuites')
  # xmlsimple will escape the cdata by default
  xml_body   = EscapeUtils.unescape_html xml_body
  xml_header + xml_body
end