parse_output.rb 6.8 KB
Newer Older
1 2 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 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 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220
#============================================================
#  Author:   John Theofanopoulos
#  A simple parser.   Takes the output files generated during the build process and
# extracts information relating to the tests.
#
#  Notes:
#    To capture an output file under VS builds use the following:
#      devenv [build instructions]  > Output.txt & type Output.txt
#
#    To capture an output file under GCC/Linux builds use the following:
#      make | tee Output.txt
#
#    To use this parser use the following command
#    ruby parseOutput.rb [options] [file]
#        options:  -xml  : produce a JUnit compatible XML file
#        file      :  file to scan for results
#============================================================

class ParseOutput
  def initialize
    @test_flag = false
    @xml_out = false
    @array_list = false
    @total_tests = false
    @class_index = false
  end

  #   Set the flag to indicate if there will be an XML output file or not
  def set_xml_output
    @xml_out = true
  end

  #  if write our output to XML
  def write_xml_output
    output = File.open('report.xml', 'w')
    output << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    @array_list.each do |item|
      output << item << "\n"
    end
    output << "</testsuite>\n"
  end

  #  This function will try and determine when the suite is changed.   This is
  # is the name that gets added to the classname parameter.
  def test_suite_verify(test_suite_name)
    return if @test_flag

    @test_flag = true
    # Split the path name
    test_name = test_suite_name.split('/')
    # Remove the extension
    base_name = test_name[test_name.size - 1].split('.')
    @test_suite = 'test.' + base_name[0]
    printf "New Test: %s\n", @test_suite
  end

  # Test was flagged as having passed so format the output
  def test_passed(array)
    last_item = array.length - 1
    test_name = array[last_item - 1]
    test_suite_verify(array[@class_name])
    printf "%-40s PASS\n", test_name

    return unless @xml_out

    @array_list.push '     <testcase classname="' + @test_suite + '" name="' + test_name + '"/>'
  end

  # Test was flagged as having passed so format the output.
  # This is using the Unity fixture output and not the original Unity output.
  def test_passed_unity_fixture(array)
    test_suite = array[0].sub('TEST(', '')
    test_suite = test_suite.sub(',', '')
    test_name = array[1].sub(')', '')

    return unless @xml_out

    @array_list.push '     <testcase classname="' + test_suite + '" name="' + test_name + '"/>'
  end

  # Test was flagged as being ingored so format the output
  def test_ignored(array)
    last_item = array.length - 1
    test_name = array[last_item - 2]
    reason = array[last_item].chomp
    test_suite_verify(array[@class_name])
    printf "%-40s IGNORED\n", test_name

    if test_name.start_with? 'TEST('
      array2 = test_name.split(' ')
      @test_suite = array2[0].sub('TEST(', '')
      @test_suite = @test_suite.sub(',', '')
      test_name = array2[1].sub(')', '')
    end

    return unless @xml_out

    @array_list.push '     <testcase classname="' + @test_suite + '" name="' + test_name + '">'
    @array_list.push '            <skipped type="TEST IGNORED"> ' + reason + ' </skipped>'
    @array_list.push '     </testcase>'
  end

  # Test was flagged as having failed  so format the line
  def test_failed(array)
    last_item = array.length - 1
    test_name = array[last_item - 2]
    reason = array[last_item].chomp + ' at line: ' + array[last_item - 3]
    test_suite_verify(array[@class_name])
    printf "%-40s FAILED\n", test_name

    if test_name.start_with? 'TEST('
      array2 = test_name.split(' ')
      @test_suite = array2[0].sub('TEST(', '')
      @test_suite = @test_suite.sub(',', '')
      test_name = array2[1].sub(')', '')
    end

    return unless @xml_out

    @array_list.push '     <testcase classname="' + @test_suite + '" name="' + test_name + '">'
    @array_list.push '            <failure type="ASSERT FAILED"> ' + reason + ' </failure>'
    @array_list.push '     </testcase>'
  end

  # Figure out what OS we are running on.   For now we are assuming if it's not Windows it must
  # be Unix based.
  def detect_os
    os = RUBY_PLATFORM.split('-')
    @class_name = if os.size == 2
                    if os[1] == 'mingw32'
                      1
                    else
                      0
                    end
                  else
                    0
                  end
  end

  # Main function used to parse the file that was captured.
  def process(name)
    @test_flag = false
    @array_list = []

    detect_os

    puts 'Parsing file: ' + name

    test_pass = 0
    test_fail = 0
    test_ignore = 0
    puts ''
    puts '=================== RESULTS ====================='
    puts ''
    File.open(name).each do |line|
      # Typical test lines look like this:
      # <path>/<test_file>.c:36:test_tc1000_opsys:FAIL: Expected 1 Was 0
      # <path>/<test_file>.c:112:test_tc5004_initCanChannel:IGNORE: Not Yet Implemented
      # <path>/<test_file>.c:115:test_tc5100_initCanVoidPtrs:PASS
      #
      # where path is different on Unix vs Windows devices (Windows leads with a drive letter)
      line_array = line.split(':')

      # If we were able to split the line then we can look to see if any of our target words
      # were found.  Case is important.
      if (line_array.size >= 4) || (line.start_with? 'TEST(')
        # Determine if this test passed
        if line.include? ':PASS'
          test_passed(line_array)
          test_pass += 1
        elsif line.include? ':FAIL:'
          test_failed(line_array)
          test_fail += 1
        elsif line.include? ':IGNORE:'
          test_ignored(line_array)
          test_ignore += 1
        elsif line.start_with? 'TEST('
          if line.include? ' PASS'
            line_array = line.split(' ')
            test_passed_unity_fixture(line_array)
            test_pass += 1
          end
        # If none of the keywords are found there are no more tests for this suite so clear
        # the test flag
        else
          @test_flag = false
        end
      else
        @test_flag = false
      end
    end
    puts ''
    puts '=================== SUMMARY ====================='
    puts ''
    puts 'Tests Passed  : ' + test_pass.to_s
    puts 'Tests Failed  : ' + test_fail.to_s
    puts 'Tests Ignored : ' + test_ignore.to_s
    @total_tests = test_pass + test_fail + test_ignore

    return unless @xml_out

    heading = '<testsuite tests="' + @total_tests.to_s + '" failures="' + test_fail.to_s + '"' + ' skips="' + test_ignore.to_s + '">'
    @array_list.insert(0, heading)
    write_xml_output
  end
end

# If the command line has no values in, used a default value of Output.txt
parse_my_file = ParseOutput.new

if ARGV.size >= 1
  ARGV.each do |a|
    if a == '-xml'
      parse_my_file.set_xml_output
    else
      parse_my_file.process(a)
      break
    end
  end
end