1#============================================================ 2# Author: John Theofanopoulos 3# A simple parser. Takes the output files generated during the build process and 4# extracts information relating to the tests. 5# 6# Notes: 7# To capture an output file under VS builds use the following: 8# devenv [build instructions] > Output.txt & type Output.txt 9# 10# To capture an output file under GCC/Linux builds use the following: 11# make | tee Output.txt 12# 13# To use this parser use the following command 14# ruby parseOutput.rb [options] [file] 15# options: -xml : produce a JUnit compatible XML file 16# file : file to scan for results 17#============================================================ 18 19class ParseOutput 20 def initialize 21 @test_flag = false 22 @xml_out = false 23 @array_list = false 24 @total_tests = false 25 @class_index = false 26 end 27 28 # Set the flag to indicate if there will be an XML output file or not 29 def set_xml_output 30 @xml_out = true 31 end 32 33 # if write our output to XML 34 def write_xml_output 35 output = File.open('report.xml', 'w') 36 output << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 37 @array_list.each do |item| 38 output << item << "\n" 39 end 40 output << "</testsuite>\n" 41 end 42 43 # This function will try and determine when the suite is changed. This is 44 # is the name that gets added to the classname parameter. 45 def test_suite_verify(test_suite_name) 46 return if @test_flag 47 48 @test_flag = true 49 # Split the path name 50 test_name = test_suite_name.split('/') 51 # Remove the extension 52 base_name = test_name[test_name.size - 1].split('.') 53 @test_suite = 'test.' + base_name[0] 54 printf "New Test: %s\n", @test_suite 55 end 56 57 # Test was flagged as having passed so format the output 58 def test_passed(array) 59 last_item = array.length - 1 60 test_name = array[last_item - 1] 61 test_suite_verify(array[@class_name]) 62 printf "%-40s PASS\n", test_name 63 64 return unless @xml_out 65 66 @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '"/>' 67 end 68 69 # Test was flagged as having passed so format the output. 70 # This is using the Unity fixture output and not the original Unity output. 71 def test_passed_unity_fixture(array) 72 test_suite = array[0].sub('TEST(', '') 73 test_suite = test_suite.sub(',', '') 74 test_name = array[1].sub(')', '') 75 76 return unless @xml_out 77 78 @array_list.push ' <testcase classname="' + test_suite + '" name="' + test_name + '"/>' 79 end 80 81 # Test was flagged as being ingored so format the output 82 def test_ignored(array) 83 last_item = array.length - 1 84 test_name = array[last_item - 2] 85 reason = array[last_item].chomp 86 test_suite_verify(array[@class_name]) 87 printf "%-40s IGNORED\n", test_name 88 89 if test_name.start_with? 'TEST(' 90 array2 = test_name.split(' ') 91 @test_suite = array2[0].sub('TEST(', '') 92 @test_suite = @test_suite.sub(',', '') 93 test_name = array2[1].sub(')', '') 94 end 95 96 return unless @xml_out 97 98 @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">' 99 @array_list.push ' <skipped type="TEST IGNORED"> ' + reason + ' </skipped>' 100 @array_list.push ' </testcase>' 101 end 102 103 # Test was flagged as having failed so format the line 104 def test_failed(array) 105 last_item = array.length - 1 106 test_name = array[last_item - 2] 107 reason = array[last_item].chomp + ' at line: ' + array[last_item - 3] 108 test_suite_verify(array[@class_name]) 109 printf "%-40s FAILED\n", test_name 110 111 if test_name.start_with? 'TEST(' 112 array2 = test_name.split(' ') 113 @test_suite = array2[0].sub('TEST(', '') 114 @test_suite = @test_suite.sub(',', '') 115 test_name = array2[1].sub(')', '') 116 end 117 118 return unless @xml_out 119 120 @array_list.push ' <testcase classname="' + @test_suite + '" name="' + test_name + '">' 121 @array_list.push ' <failure type="ASSERT FAILED"> ' + reason + ' </failure>' 122 @array_list.push ' </testcase>' 123 end 124 125 # Figure out what OS we are running on. For now we are assuming if it's not Windows it must 126 # be Unix based. 127 def detect_os 128 os = RUBY_PLATFORM.split('-') 129 @class_name = if os.size == 2 130 if os[1] == 'mingw32' 131 1 132 else 133 0 134 end 135 else 136 0 137 end 138 end 139 140 # Main function used to parse the file that was captured. 141 def process(name) 142 @test_flag = false 143 @array_list = [] 144 145 detect_os 146 147 puts 'Parsing file: ' + name 148 149 test_pass = 0 150 test_fail = 0 151 test_ignore = 0 152 puts '' 153 puts '=================== RESULTS =====================' 154 puts '' 155 File.open(name).each do |line| 156 # Typical test lines look like this: 157 # <path>/<test_file>.c:36:test_tc1000_opsys:FAIL: Expected 1 Was 0 158 # <path>/<test_file>.c:112:test_tc5004_initCanChannel:IGNORE: Not Yet Implemented 159 # <path>/<test_file>.c:115:test_tc5100_initCanVoidPtrs:PASS 160 # 161 # where path is different on Unix vs Windows devices (Windows leads with a drive letter) 162 line_array = line.split(':') 163 164 # If we were able to split the line then we can look to see if any of our target words 165 # were found. Case is important. 166 if (line_array.size >= 4) || (line.start_with? 'TEST(') 167 # Determine if this test passed 168 if line.include? ':PASS' 169 test_passed(line_array) 170 test_pass += 1 171 elsif line.include? ':FAIL:' 172 test_failed(line_array) 173 test_fail += 1 174 elsif line.include? ':IGNORE:' 175 test_ignored(line_array) 176 test_ignore += 1 177 elsif line.start_with? 'TEST(' 178 if line.include? ' PASS' 179 line_array = line.split(' ') 180 test_passed_unity_fixture(line_array) 181 test_pass += 1 182 end 183 # If none of the keywords are found there are no more tests for this suite so clear 184 # the test flag 185 else 186 @test_flag = false 187 end 188 else 189 @test_flag = false 190 end 191 end 192 puts '' 193 puts '=================== SUMMARY =====================' 194 puts '' 195 puts 'Tests Passed : ' + test_pass.to_s 196 puts 'Tests Failed : ' + test_fail.to_s 197 puts 'Tests Ignored : ' + test_ignore.to_s 198 @total_tests = test_pass + test_fail + test_ignore 199 200 return unless @xml_out 201 202 heading = '<testsuite tests="' + @total_tests.to_s + '" failures="' + test_fail.to_s + '"' + ' skips="' + test_ignore.to_s + '">' 203 @array_list.insert(0, heading) 204 write_xml_output 205 end 206end 207 208# If the command line has no values in, used a default value of Output.txt 209parse_my_file = ParseOutput.new 210 211if ARGV.size >= 1 212 ARGV.each do |a| 213 if a == '-xml' 214 parse_my_file.set_xml_output 215 else 216 parse_my_file.process(a) 217 break 218 end 219 end 220end 221