parse_output.rb 10.4 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
#      make | tee Output.txt
#
13 14 15 16 17
#    This script can handle the following output formats:
#    - normal output (raw unity)
#    - fixture output (unity_fixture.h/.c)
#    - fixture output with verbose flag set ("-v")
#
18 19
#    To use this parser use the following command
#    ruby parseOutput.rb [options] [file]
20 21
#        options: -xml  : produce a JUnit compatible XML file
#           file: file to scan for results
22 23
#============================================================

F
Fabian Zahn 已提交
24
# Parser class for handling the input file
25 26
class ParseOutput
  def initialize
27 28 29 30 31
    # internal data
    @class_name_idx = 0
    @path_delim = nil

    # xml output related
32 33
    @xml_out = false
    @array_list = false
34 35

    # current suite name and statistics
36
    @test_suite = nil
M
mvandervoord 已提交
37 38 39
    @total_tests = 0
    @test_passed = 0
    @test_failed = 0
40
    @test_ignored = 0
41 42
  end

F
Fabian Zahn 已提交
43
  # Set the flag to indicate if there will be an XML output file or not
44 45 46 47
  def set_xml_output
    @xml_out = true
  end

F
Fabian Zahn 已提交
48
  # If write our output to XML
49 50 51 52 53 54
  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
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
  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>'
83 84
  end

85
  # This function will try and determine when the suite is changed. This is
86 87 88
  # is the name that gets added to the classname parameter.
  def test_suite_verify(test_suite_name)
    # Split the path name
89
    test_name = test_suite_name.split(@path_delim)
F
Fabian Zahn 已提交
90

91 92 93
    # Remove the extension and extract the base_name
    base_name = test_name[test_name.size - 1].split('.')[0]

F
Fabian Zahn 已提交
94 95 96 97 98
    # 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
99 100
  end

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
  # 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)
117 118
    printf "%-40s PASS\n", test_name

119 120
    push_xml_output_passed(test_name) if @xml_out
  end
121

122 123 124 125 126 127 128 129 130 131 132 133
  # 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
134 135
  end

136
  # Test was flagged as being ignored so format the output.
137
  # This is using the Unity fixture output and not the original Unity output.
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
  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])
158
    printf "%-40s PASS\n", test_name
F
Fabian Zahn 已提交
159

160 161
    return unless @xml_out

162
    push_xml_output_passed(test_name) if @xml_out
163 164
  end

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

    if test_name.start_with? 'TEST('
      array2 = test_name.split(' ')
174 175 176 177 178

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

179 180 181
      test_name = array2[1].sub(')', '')
    end

182
    test_suite_verify(class_name)
183
    printf "%-40s FAILED\n", test_name
184

185
    push_xml_output_failed(test_name, reason) if @xml_out
186 187
  end

188 189
  # Test was flagged as being ignored so format the output
  def test_ignored(array)
190 191
    last_item = array.length - 1
    test_name = array[last_item - 2]
192 193
    reason = array[last_item].chomp.lstrip
    class_name = array[@class_name_idx]
194 195 196

    if test_name.start_with? 'TEST('
      array2 = test_name.split(' ')
197 198 199 200 201

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

202 203 204
      test_name = array2[1].sub(')', '')
    end

205
    test_suite_verify(class_name)
206
    printf "%-40s IGNORED\n", test_name
207

208
    push_xml_output_ignored(test_name, reason) if @xml_out
209 210
  end

211 212
  # Adjusts the os specific members according to the current path style
  # (Windows or Unix based)
J
John Lindgren 已提交
213
  def detect_os_specifics(line)
214 215
    if line.include? '\\'
      # Windows X:\Y\Z
216
      @class_name_idx = 1
217 218 219
      @path_delim = '\\'
    else
      # Unix Based /X/Y/Z
220
      @class_name_idx = 0
221 222
      @path_delim = '/'
    end
223 224 225
  end

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

F
Fabian Zahn 已提交
229
    puts 'Parsing file: ' + file_name
230

231 232 233
    @test_passed = 0
    @test_failed = 0
    @test_ignored = 0
234 235 236
    puts ''
    puts '=================== RESULTS ====================='
    puts ''
F
Fabian Zahn 已提交
237
    File.open(file_name).each do |line|
238 239 240
      # Typical test lines look like these:
      # ----------------------------------------------------
      # 1. normal output:
241 242 243 244
      # <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
      #
245 246 247 248 249 250 251 252 253 254 255 256
      # 2. fixture output
      # <path>/<test_file>.c:63:TEST(<test_group>, <test_function>):FAIL: Expected 0x00001234 Was 0x00005A5A
      # <path>/<test_file>.c:36:TEST(<test_group>, <test_function>):IGNORE
      # Note: "PASS" information won't be generated in this mode
      #
      # 3. fixture output with verbose information ("-v")
      # TEST(<test_group, <test_file>)<path>/<test_file>:168::FAIL: Expected 0x8D Was 0x8C
      # TEST(<test_group>, <test_file>)<path>/<test_file>:22::IGNORE: This Test Was Ignored On Purpose
      # IGNORE_TEST(<test_group, <test_file>)
      # TEST(<test_group, <test_file>) PASS
      #
      # Note: Where path is different on Unix vs Windows devices (Windows leads with a drive letter)!
J
John Lindgren 已提交
257
      detect_os_specifics(line)
258 259 260
      line_array = line.split(':')

      # If we were able to split the line then we can look to see if any of our target words
261
      # were found. Case is important.
J
John Lindgren 已提交
262 263 264 265 266 267 268
      next unless (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)
269
          @test_passed += 1
J
John Lindgren 已提交
270 271
        elsif line.include? 'FAIL'
          test_failed_unity_fixture(line_array)
272
          @test_failed += 1
J
John Lindgren 已提交
273 274
        elsif line.include? 'IGNORE'
          test_ignored_unity_fixture(line_array)
275
          @test_ignored += 1
276
        end
J
John Lindgren 已提交
277 278 279 280 281 282 283 284 285 286 287 288 289 290
      # normal output / fixture output (without verbose "-v")
      elsif line.include? ':PASS'
        test_passed(line_array)
        @test_passed += 1
      elsif line.include? ':FAIL'
        test_failed(line_array)
        @test_failed += 1
      elsif line.include? ':IGNORE:'
        test_ignored(line_array)
        @test_ignored += 1
      elsif line.include? ':IGNORE'
        line_array.push('No reason given')
        test_ignored(line_array)
        @test_ignored += 1
291
      end
J
John Lindgren 已提交
292
      @total_tests = @test_passed + @test_failed + @test_ignored
293 294 295 296
    end
    puts ''
    puts '=================== SUMMARY ====================='
    puts ''
297 298 299
    puts 'Tests Passed  : ' + @test_passed.to_s
    puts 'Tests Failed  : ' + @test_failed.to_s
    puts 'Tests Ignored : ' + @test_ignored.to_s
300 301 302

    return unless @xml_out

303 304 305
    # push information about the suite
    push_xml_output_suite_info
    # write xml output file
306 307 308 309 310 311 312 313
    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 已提交
314 315
  ARGV.each do |arg|
    if arg == '-xml'
316 317
      parse_my_file.set_xml_output
    else
F
Fabian Zahn 已提交
318
      parse_my_file.process(arg)
319 320 321 322
      break
    end
  end
end