parse_output.rb 9.6 KB
Newer Older
1
#============================================================
2 3
#  Author: John Theofanopoulos
#  A simple parser. Takes the output files generated during the
F
Fabian Zahn 已提交
4
#  build process and extracts information relating to the tests.
5 6 7
#
#  Notes:
#    To capture an output file under VS builds use the following:
8
#      devenv [build instructions] > Output.txt & type Output.txt
9
#
F
Fabian Zahn 已提交
10
#    To capture an output file under Linux builds use the following:
11 12 13 14
#      make | tee Output.txt
#
#    To use this parser use the following command
#    ruby parseOutput.rb [options] [file]
15 16
#        options: -xml  : produce a JUnit compatible XML file
#           file: file to scan for results
17 18
#============================================================

F
Fabian Zahn 已提交
19
# Parser class for handling the input file
20 21
class ParseOutput
  def initialize
22 23 24 25 26
    # internal data
    @class_name_idx = 0
    @path_delim = nil

    # xml output related
27 28
    @xml_out = false
    @array_list = false
29 30

    # current suite name and statistics
31
    @test_suite = nil
32 33 34 35
    @total_tests  = 0
    @test_passed  = 0
    @test_failed  = 0
    @test_ignored = 0
36 37
  end

F
Fabian Zahn 已提交
38
  # Set the flag to indicate if there will be an XML output file or not
39 40 41 42
  def set_xml_output
    @xml_out = true
  end

F
Fabian Zahn 已提交
43
  # If write our output to XML
44 45 46 47 48 49
  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
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
  end

  # Pushes the suite info as xml to the array list, which will be written later
  def push_xml_output_suite_info
    # Insert opening tag at front
    heading = '<testsuite name="Unity" tests="' + @total_tests.to_s + '" failures="' + @test_failed.to_s + '"' + ' skips="' + @test_ignored.to_s + '">'
    @array_list.insert(0, heading)
    # Push back the closing tag
    @array_list.push '</testsuite>'
  end

  # Pushes xml output data to the array list, which will be written later
  def push_xml_output_passed(test_name)
    @array_list.push '    <testcase classname="' + @test_suite + '" name="' + test_name + '"/>'
  end

  # Pushes xml output data to the array list, which will be written later
  def push_xml_output_failed(test_name, reason)
    @array_list.push '    <testcase classname="' + @test_suite + '" name="' + test_name + '">'
    @array_list.push '        <failure type="ASSERT FAILED">' + reason + '</failure>'
    @array_list.push '    </testcase>'
  end

  # Pushes xml output data to the array list, which will be written later
  def push_xml_output_ignored(test_name, reason)
    @array_list.push '    <testcase classname="' + @test_suite + '" name="' + test_name + '">'
    @array_list.push '        <skipped type="TEST IGNORED">' + reason + '</skipped>'
    @array_list.push '    </testcase>'
78 79
  end

80
  # This function will try and determine when the suite is changed. This is
81 82 83
  # is the name that gets added to the classname parameter.
  def test_suite_verify(test_suite_name)
    # Split the path name
84
    test_name = test_suite_name.split(@path_delim)
F
Fabian Zahn 已提交
85

86 87 88
    # Remove the extension and extract the base_name
    base_name = test_name[test_name.size - 1].split('.')[0]

F
Fabian Zahn 已提交
89 90 91 92 93
    # Return if the test suite hasn't changed
    return unless base_name.to_s != @test_suite.to_s

    @test_suite = base_name
    printf "New Test: %s\n", @test_suite
94 95
  end

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
  # Prepares the line for verbose fixture output ("-v")
  def prepare_fixture_line(line)
    line = line.sub('IGNORE_TEST(', '')
    line = line.sub('TEST(', '')
    line = line.sub(')', ',')
    line = line.chomp
    array = line.split(',')
    array.map { |x| x.to_s.lstrip.chomp }
  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)
    class_name = array[0]
    test_name  = array[1]
    test_suite_verify(class_name)
112 113
    printf "%-40s PASS\n", test_name

114 115
    push_xml_output_passed(test_name) if @xml_out
  end
116

117 118 119 120 121 122 123 124 125 126 127 128
  # Test was flagged as having failed so format the output.
  # This is using the Unity fixture output and not the original Unity output.
  def test_failed_unity_fixture(array)
    class_name = array[0]
    test_name  = array[1]
    test_suite_verify(class_name)
    reason_array = array[2].split(':')
    reason = reason_array[-1].lstrip.chomp + ' at line: ' + reason_array[-4]

    printf "%-40s FAILED\n", test_name

    push_xml_output_failed(test_name, reason) if @xml_out
129 130
  end

131
  # Test was flagged as being ignored so format the output.
132
  # This is using the Unity fixture output and not the original Unity output.
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
  def test_ignored_unity_fixture(array)
    class_name = array[0]
    test_name  = array[1]
    reason = 'No reason given'
    if array.size > 2
      reason_array = array[2].split(':')
      tmp_reason = reason_array[-1].lstrip.chomp
      reason = tmp_reason == 'IGNORE' ? 'No reason given' : tmp_reason
    end
    test_suite_verify(class_name)
    printf "%-40s IGNORED\n", test_name

    push_xml_output_ignored(test_name, reason) if @xml_out
  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_idx])
153
    printf "%-40s PASS\n", test_name
F
Fabian Zahn 已提交
154

155 156
    return unless @xml_out

157
    push_xml_output_passed(test_name) if @xml_out
158 159
  end

160 161
  # Test was flagged as having failed so format the line
  def test_failed(array)
162 163
    last_item = array.length - 1
    test_name = array[last_item - 2]
164 165
    reason = array[last_item].chomp.lstrip + ' at line: ' + array[last_item - 3]
    class_name = array[@class_name_idx]
166 167 168

    if test_name.start_with? 'TEST('
      array2 = test_name.split(' ')
169 170 171 172 173

      test_suite = array2[0].sub('TEST(', '')
      test_suite = test_suite.sub(',', '')
      class_name = test_suite

174 175 176
      test_name = array2[1].sub(')', '')
    end

177
    test_suite_verify(class_name)
178
    printf "%-40s FAILED\n", test_name
179

180
    push_xml_output_failed(test_name, reason) if @xml_out
181 182
  end

183 184
  # Test was flagged as being ignored so format the output
  def test_ignored(array)
185 186
    last_item = array.length - 1
    test_name = array[last_item - 2]
187 188
    reason = array[last_item].chomp.lstrip
    class_name = array[@class_name_idx]
189 190 191

    if test_name.start_with? 'TEST('
      array2 = test_name.split(' ')
192 193 194 195 196

      test_suite = array2[0].sub('TEST(', '')
      test_suite = test_suite.sub(',', '')
      class_name = test_suite

197 198 199
      test_name = array2[1].sub(')', '')
    end

200
    test_suite_verify(class_name)
201
    printf "%-40s IGNORED\n", test_name
202

203
    push_xml_output_ignored(test_name, reason) if @xml_out
204 205
  end

206 207 208 209 210
  # Adjusts the os specific members according to the current path style
  # (Windows or Unix based)
  def set_os_specifics(line)
    if line.include? '\\'
      # Windows X:\Y\Z
211
      @class_name_idx = 1
212 213 214
      @path_delim = '\\'
    else
      # Unix Based /X/Y/Z
215
      @class_name_idx = 0
216 217
      @path_delim = '/'
    end
218 219 220
  end

  # Main function used to parse the file that was captured.
F
Fabian Zahn 已提交
221
  def process(file_name)
222 223
    @array_list = []

F
Fabian Zahn 已提交
224
    puts 'Parsing file: ' + file_name
225

226 227 228
    @test_passed = 0
    @test_failed = 0
    @test_ignored = 0
229 230 231
    puts ''
    puts '=================== RESULTS ====================='
    puts ''
F
Fabian Zahn 已提交
232
    File.open(file_name).each do |line|
233 234 235 236 237 238
      # 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)
239
      set_os_specifics(line)
240 241 242
      line_array = line.split(':')

      # If we were able to split the line then we can look to see if any of our target words
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
      # were found. Case is important.
      if (line_array.size >= 4) || (line.start_with? 'TEST(') || (line.start_with? 'IGNORE_TEST(')

        # check if the output is fixture output (with verbose flag "-v")
        if (line.start_with? 'TEST(') || (line.start_with? 'IGNORE_TEST(')
          line_array = prepare_fixture_line(line)
          if line.include? ' PASS'
            test_passed_unity_fixture(line_array)
            @test_passed += 1
          elsif line.include? 'FAIL'
            test_failed_unity_fixture(line_array)
            @test_failed += 1
          elsif line.include? 'IGNORE'
            test_ignored_unity_fixture(line_array)
            @test_ignored += 1
          end
        # normal output / fixture output (without verbose "-v")
        elsif line.include? ':PASS'
261
          test_passed(line_array)
262
          @test_passed += 1
F
Fabian Zahn 已提交
263
        elsif line.include? ':FAIL'
264
          test_failed(line_array)
265
          @test_failed += 1
266 267
        elsif line.include? ':IGNORE:'
          test_ignored(line_array)
268
          @test_ignored += 1
F
Fabian Zahn 已提交
269 270 271
        elsif line.include? ':IGNORE'
          line_array.push('No reason given')
          test_ignored(line_array)
272
          @test_ignored += 1
273
        end
274
        @total_tests = @test_passed + @test_failed + @test_ignored
275 276 277 278 279
      end
    end
    puts ''
    puts '=================== SUMMARY ====================='
    puts ''
280 281 282
    puts 'Tests Passed  : ' + @test_passed.to_s
    puts 'Tests Failed  : ' + @test_failed.to_s
    puts 'Tests Ignored : ' + @test_ignored.to_s
283 284 285

    return unless @xml_out

286 287 288
    # push information about the suite
    push_xml_output_suite_info
    # write xml output file
289 290 291 292 293 294 295 296
    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
F
Fabian Zahn 已提交
297 298
  ARGV.each do |arg|
    if arg == '-xml'
299 300
      parse_my_file.set_xml_output
    else
F
Fabian Zahn 已提交
301
      parse_my_file.process(arg)
302 303 304 305
      break
    end
  end
end