ESP32-basiertes Kraftort-Suchger�t mit GPS, LED-Ring und PlatformIO-Firmware.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

391 lines
14KB

  1. # =========================================================================
  2. # Unity - A Test Framework for C
  3. # ThrowTheSwitch.org
  4. # Copyright (c) 2007-25 Mike Karlesky, Mark VanderVoord, & Greg Williams
  5. # SPDX-License-Identifier: MIT
  6. # =========================================================================
  7. #============================================================
  8. # Author: John Theofanopoulos
  9. # A simple parser. Takes the output files generated during the
  10. # build process and extracts information relating to the tests.
  11. #
  12. # Notes:
  13. # To capture an output file under VS builds use the following:
  14. # devenv [build instructions] > Output.txt & type Output.txt
  15. #
  16. # To capture an output file under Linux builds use the following:
  17. # make | tee Output.txt
  18. #
  19. # This script can handle the following output formats:
  20. # - normal output (raw unity)
  21. # - fixture output (unity_fixture.h/.c)
  22. # - fixture output with verbose flag set ("-v")
  23. # - time output flag set (UNITY_INCLUDE_EXEC_TIME define enabled with milliseconds output)
  24. #
  25. # To use this parser use the following command
  26. # ruby parseOutput.rb [options] [file]
  27. # options: -xml : produce a JUnit compatible XML file
  28. # -suiteRequiredSuiteName
  29. # : replace default test suite name to
  30. # "RequiredSuiteName" (can be any name)
  31. # file: file to scan for results
  32. #============================================================
  33. # Parser class for handling the input file
  34. class ParseOutput
  35. def initialize
  36. # internal data
  37. @class_name_idx = 0
  38. @result_usual_idx = 3
  39. @path_delim = nil
  40. # xml output related
  41. @xml_out = false
  42. @array_list = false
  43. # current suite name and statistics
  44. ## testsuite name
  45. @real_test_suite_name = 'Unity'
  46. ## classname for testcase
  47. @test_suite = nil
  48. @total_tests = 0
  49. @test_passed = 0
  50. @test_failed = 0
  51. @test_ignored = 0
  52. end
  53. # Set the flag to indicate if there will be an XML output file or not
  54. def set_xml_output
  55. @xml_out = true
  56. end
  57. # Set the flag to indicate if there will be an XML output file or not
  58. def test_suite_name=(cli_arg)
  59. @real_test_suite_name = cli_arg
  60. puts "Real test suite name will be '#{@real_test_suite_name}'"
  61. end
  62. def xml_encode_s(str)
  63. str.encode(:xml => :attr)
  64. end
  65. # If write our output to XML
  66. def write_xml_output
  67. output = File.open('report.xml', 'w')
  68. output << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
  69. @array_list.each do |item|
  70. output << item << "\n"
  71. end
  72. end
  73. # Pushes the suite info as xml to the array list, which will be written later
  74. def push_xml_output_suite_info
  75. # Insert opening tag at front
  76. heading = "<testsuite name=#{xml_encode_s(@real_test_suite_name)} tests=\"#{@total_tests}\" failures=\"#{@test_failed}\" skips=\"#{@test_ignored}\">"
  77. @array_list.insert(0, heading)
  78. # Push back the closing tag
  79. @array_list.push '</testsuite>'
  80. end
  81. # Pushes xml output data to the array list, which will be written later
  82. def push_xml_output_passed(test_name, execution_time = 0)
  83. @array_list.push " <testcase classname=#{xml_encode_s(@test_suite)} name=#{xml_encode_s(test_name)} time=#{xml_encode_s((execution_time / 1000.0).to_s)} />"
  84. end
  85. # Pushes xml output data to the array list, which will be written later
  86. def push_xml_output_failed(test_name, reason, execution_time = 0)
  87. @array_list.push " <testcase classname=#{xml_encode_s(@test_suite)} name=#{xml_encode_s(test_name)} time=#{xml_encode_s((execution_time / 1000.0).to_s)} >"
  88. @array_list.push " <failure type=\"ASSERT FAILED\">#{reason}</failure>"
  89. @array_list.push ' </testcase>'
  90. end
  91. # Pushes xml output data to the array list, which will be written later
  92. def push_xml_output_ignored(test_name, reason, execution_time = 0)
  93. @array_list.push " <testcase classname=#{xml_encode_s(@test_suite)} name=#{xml_encode_s(test_name)} time=#{xml_encode_s((execution_time / 1000.0).to_s)} >"
  94. @array_list.push " <skipped type=\"TEST IGNORED\">#{reason}</skipped>"
  95. @array_list.push ' </testcase>'
  96. end
  97. # This function will try and determine when the suite is changed. This is
  98. # is the name that gets added to the classname parameter.
  99. def test_suite_verify(test_suite_name)
  100. # Split the path name
  101. test_name = test_suite_name.split(@path_delim)
  102. # Remove the extension and extract the base_name
  103. base_name = test_name[test_name.size - 1].split('.')[0]
  104. # Return if the test suite hasn't changed
  105. return unless base_name.to_s != @test_suite.to_s
  106. @test_suite = base_name
  107. printf "New Test: %s\n", @test_suite
  108. end
  109. # Prepares the line for verbose fixture output ("-v")
  110. def prepare_fixture_line(line)
  111. line = line.sub('IGNORE_TEST(', '')
  112. line = line.sub('TEST(', '')
  113. line = line.sub(')', ',')
  114. line = line.chomp
  115. array = line.split(',')
  116. array.map { |x| x.to_s.lstrip.chomp }
  117. end
  118. # Test was flagged as having passed so format the output.
  119. # This is using the Unity fixture output and not the original Unity output.
  120. def test_passed_unity_fixture(array)
  121. class_name = array[0]
  122. test_name = array[1]
  123. test_suite_verify(class_name)
  124. printf "%-40s PASS\n", test_name
  125. push_xml_output_passed(test_name) if @xml_out
  126. end
  127. # Test was flagged as having failed so format the output.
  128. # This is using the Unity fixture output and not the original Unity output.
  129. def test_failed_unity_fixture(array)
  130. class_name = array[0]
  131. test_name = array[1]
  132. test_suite_verify(class_name)
  133. reason_array = array[2].split(':')
  134. reason = "#{reason_array[-1].lstrip.chomp} at line: #{reason_array[-4]}"
  135. printf "%-40s FAILED\n", test_name
  136. push_xml_output_failed(test_name, reason) if @xml_out
  137. end
  138. # Test was flagged as being ignored so format the output.
  139. # This is using the Unity fixture output and not the original Unity output.
  140. def test_ignored_unity_fixture(array)
  141. class_name = array[0]
  142. test_name = array[1]
  143. reason = 'No reason given'
  144. if array.size > 2
  145. reason_array = array[2].split(':')
  146. tmp_reason = reason_array[-1].lstrip.chomp
  147. reason = tmp_reason == 'IGNORE' ? 'No reason given' : tmp_reason
  148. end
  149. test_suite_verify(class_name)
  150. printf "%-40s IGNORED\n", test_name
  151. push_xml_output_ignored(test_name, reason) if @xml_out
  152. end
  153. # Test was flagged as having passed so format the output
  154. def test_passed(array)
  155. # ':' symbol will be valid in function args now
  156. real_method_name = array[@result_usual_idx - 1..-2].join(':')
  157. array = array[0..@result_usual_idx - 2] + [real_method_name] + [array[-1]]
  158. last_item = array.length - 1
  159. test_time = get_test_time(array[last_item])
  160. test_name = array[last_item - 1]
  161. test_suite_verify(array[@class_name_idx])
  162. printf "%-40s PASS %10d ms\n", test_name, test_time
  163. return unless @xml_out
  164. push_xml_output_passed(test_name, test_time) if @xml_out
  165. end
  166. # Test was flagged as having failed so format the line
  167. def test_failed(array)
  168. # ':' symbol will be valid in function args now
  169. real_method_name = array[@result_usual_idx - 1..-3].join(':')
  170. array = array[0..@result_usual_idx - 3] + [real_method_name] + array[-2..]
  171. last_item = array.length - 1
  172. test_time = get_test_time(array[last_item])
  173. test_name = array[last_item - 2]
  174. reason = "#{array[last_item].chomp.lstrip} at line: #{array[last_item - 3]}"
  175. class_name = array[@class_name_idx]
  176. if test_name.start_with? 'TEST('
  177. array2 = test_name.split(' ')
  178. test_suite = array2[0].sub('TEST(', '')
  179. test_suite = test_suite.sub(',', '')
  180. class_name = test_suite
  181. test_name = array2[1].sub(')', '')
  182. end
  183. test_suite_verify(class_name)
  184. printf "%-40s FAILED %10d ms\n", test_name, test_time
  185. push_xml_output_failed(test_name, reason, test_time) if @xml_out
  186. end
  187. # Test was flagged as being ignored so format the output
  188. def test_ignored(array)
  189. # ':' symbol will be valid in function args now
  190. real_method_name = array[@result_usual_idx - 1..-3].join(':')
  191. array = array[0..@result_usual_idx - 3] + [real_method_name] + array[-2..]
  192. last_item = array.length - 1
  193. test_time = get_test_time(array[last_item])
  194. test_name = array[last_item - 2]
  195. reason = array[last_item].chomp.lstrip
  196. class_name = array[@class_name_idx]
  197. if test_name.start_with? 'TEST('
  198. array2 = test_name.split(' ')
  199. test_suite = array2[0].sub('TEST(', '')
  200. test_suite = test_suite.sub(',', '')
  201. class_name = test_suite
  202. test_name = array2[1].sub(')', '')
  203. end
  204. test_suite_verify(class_name)
  205. printf "%-40s IGNORED %10d ms\n", test_name, test_time
  206. push_xml_output_ignored(test_name, reason, test_time) if @xml_out
  207. end
  208. # Test time will be in ms
  209. def get_test_time(value_with_time)
  210. test_time_array = value_with_time.scan(/\((-?\d+.?\d*) ms\)\s*$/).flatten.map do |arg_value_str|
  211. arg_value_str.include?('.') ? arg_value_str.to_f : arg_value_str.to_i
  212. end
  213. test_time_array.any? ? test_time_array[0] : 0
  214. end
  215. # Adjusts the os specific members according to the current path style
  216. # (Windows or Unix based)
  217. def detect_os_specifics(line)
  218. if line.include? '\\'
  219. # Windows X:\Y\Z
  220. @class_name_idx = 1
  221. @path_delim = '\\'
  222. else
  223. # Unix Based /X/Y/Z
  224. @class_name_idx = 0
  225. @path_delim = '/'
  226. end
  227. end
  228. # Main function used to parse the file that was captured.
  229. def process(file_name)
  230. @array_list = []
  231. puts "Parsing file: #{file_name}"
  232. @test_passed = 0
  233. @test_failed = 0
  234. @test_ignored = 0
  235. puts ''
  236. puts '=================== RESULTS ====================='
  237. puts ''
  238. # Apply binary encoding. Bad symbols will be unchanged
  239. File.open(file_name, 'rb').each do |line|
  240. # Typical test lines look like these:
  241. # ----------------------------------------------------
  242. # 1. normal output:
  243. # <path>/<test_file>.c:36:test_tc1000_opsys:FAIL: Expected 1 Was 0
  244. # <path>/<test_file>.c:112:test_tc5004_initCanChannel:IGNORE: Not Yet Implemented
  245. # <path>/<test_file>.c:115:test_tc5100_initCanVoidPtrs:PASS
  246. #
  247. # 2. fixture output
  248. # <path>/<test_file>.c:63:TEST(<test_group>, <test_function>):FAIL: Expected 0x00001234 Was 0x00005A5A
  249. # <path>/<test_file>.c:36:TEST(<test_group>, <test_function>):IGNORE
  250. # Note: "PASS" information won't be generated in this mode
  251. #
  252. # 3. fixture output with verbose information ("-v")
  253. # TEST(<test_group, <test_file>)<path>/<test_file>:168::FAIL: Expected 0x8D Was 0x8C
  254. # TEST(<test_group>, <test_file>)<path>/<test_file>:22::IGNORE: This Test Was Ignored On Purpose
  255. # IGNORE_TEST(<test_group, <test_file>)
  256. # TEST(<test_group, <test_file>) PASS
  257. #
  258. # Note: Where path is different on Unix vs Windows devices (Windows leads with a drive letter)!
  259. detect_os_specifics(line)
  260. line_array = line.split(':')
  261. # If we were able to split the line then we can look to see if any of our target words
  262. # were found. Case is important.
  263. next unless (line_array.size >= 4) || (line.start_with? 'TEST(') || (line.start_with? 'IGNORE_TEST(')
  264. # check if the output is fixture output (with verbose flag "-v")
  265. if (line.start_with? 'TEST(') || (line.start_with? 'IGNORE_TEST(')
  266. line_array = prepare_fixture_line(line)
  267. if line.include? ' PASS'
  268. test_passed_unity_fixture(line_array)
  269. @test_passed += 1
  270. elsif line.include? 'FAIL'
  271. test_failed_unity_fixture(line_array)
  272. @test_failed += 1
  273. elsif line.include? 'IGNORE'
  274. test_ignored_unity_fixture(line_array)
  275. @test_ignored += 1
  276. end
  277. # normal output / fixture output (without verbose "-v")
  278. elsif line.include? ':PASS'
  279. test_passed(line_array)
  280. @test_passed += 1
  281. elsif line.include? ':FAIL'
  282. test_failed(line_array)
  283. @test_failed += 1
  284. elsif line.include? ':IGNORE:'
  285. test_ignored(line_array)
  286. @test_ignored += 1
  287. elsif line.include? ':IGNORE'
  288. line_array.push('No reason given')
  289. test_ignored(line_array)
  290. @test_ignored += 1
  291. elsif line_array.size >= 4
  292. # We will check output from color compilation
  293. if line_array[@result_usual_idx..].any? { |l| l.include? 'PASS' }
  294. test_passed(line_array)
  295. @test_passed += 1
  296. elsif line_array[@result_usual_idx..].any? { |l| l.include? 'FAIL' }
  297. test_failed(line_array)
  298. @test_failed += 1
  299. elsif line_array[@result_usual_idx..-2].any? { |l| l.include? 'IGNORE' }
  300. test_ignored(line_array)
  301. @test_ignored += 1
  302. elsif line_array[@result_usual_idx..].any? { |l| l.include? 'IGNORE' }
  303. line_array.push("No reason given (#{get_test_time(line_array[@result_usual_idx..])} ms)")
  304. test_ignored(line_array)
  305. @test_ignored += 1
  306. end
  307. end
  308. @total_tests = @test_passed + @test_failed + @test_ignored
  309. end
  310. puts ''
  311. puts '=================== SUMMARY ====================='
  312. puts ''
  313. puts "Tests Passed : #{@test_passed}"
  314. puts "Tests Failed : #{@test_failed}"
  315. puts "Tests Ignored : #{@test_ignored}"
  316. return unless @xml_out
  317. # push information about the suite
  318. push_xml_output_suite_info
  319. # write xml output file
  320. write_xml_output
  321. end
  322. end
  323. # If the command line has no values in, used a default value of Output.txt
  324. parse_my_file = ParseOutput.new
  325. if ARGV.size >= 1
  326. ARGV.each do |arg|
  327. if arg == '-xml'
  328. parse_my_file.set_xml_output
  329. elsif arg.start_with?('-suite')
  330. parse_my_file.test_suite_name = arg.delete_prefix('-suite')
  331. else
  332. parse_my_file.process(arg)
  333. break
  334. end
  335. end
  336. end