Module: RSpecFlake

Defined in:
lib/rspec_flake/parse.rb,
lib/rspec_flake/merge.rb,
lib/rspec_flake/stats.rb,
lib/rspec_flake/convert.rb,
lib/rspec_flake/version.rb,
lib/rspec_flake/file_time.rb,
lib/rspec_flake/rspec_flake.rb

Overview

<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

Defined Under Namespace

Classes: ReportParser, Runner

Constant Summary collapse

VERSION =
'0.2.3'
DATE =
'2015-06-19'

Class Method Summary collapse

Class Method Details

.cdata(content) ⇒ Object



30
31
32
# File 'lib/rspec_flake/convert.rb', line 30

def cdata content
  "\n<![CDATA[#{content}]]>\n"
end

.file_time(opts = {}) ⇒ Object

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

Examples:

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




8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 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) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 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 = {}) ⇒ Object

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

@example:

merge_individual_xml files: Dir.glob('tmp/*.xml')


9
10
11
12
13
14
15
16
17
# 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 = {}) ⇒ Object

merge input xml files into a hash

input - single path or array of paths to input xml files

Returns:

  • merged hash



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# 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) ⇒ Object

Parse JUnit xml into a Hash keyed on location



73
74
75
76
77
78
79
80
81
# 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) ⇒ Object



68
69
70
# 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) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 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) ⇒ Object



34
35
36
37
38
39
40
# 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