unity_to_junit.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import sys
  2. import os
  3. from glob import glob
  4. from pyparsing import *
  5. from junit_xml import TestSuite, TestCase
  6. class UnityTestSummary:
  7. def __init__(self):
  8. self.report = ''
  9. self.total_tests = 0
  10. self.failures = 0
  11. self.ignored = 0
  12. self.targets = 0
  13. self.root = None
  14. self.test_suites = dict()
  15. def run(self):
  16. # Clean up result file names
  17. results = []
  18. for target in self.targets:
  19. results.append(target.replace('\\', '/'))
  20. # Dig through each result file, looking for details on pass/fail:
  21. for result_file in results:
  22. lines = list(map(lambda line: line.rstrip(), open(result_file, "r").read().split('\n')))
  23. if len(lines) == 0:
  24. raise Exception("Empty test result file: %s" % result_file)
  25. # define an expression for your file reference
  26. entry_one = Combine(
  27. oneOf(list(alphas)) + ':/' +
  28. Word(alphanums + '_-./'))
  29. entry_two = Word(printables + ' ', excludeChars=':')
  30. entry = entry_one | entry_two
  31. delimiter = Literal(':').suppress()
  32. tc_result_line = Group(entry.setResultsName('tc_file_name') + delimiter + entry.setResultsName(
  33. 'tc_line_nr') + delimiter + entry.setResultsName('tc_name') + delimiter + entry.setResultsName(
  34. 'tc_status') + Optional(
  35. delimiter + entry.setResultsName('tc_msg'))).setResultsName("tc_line")
  36. eol = LineEnd().suppress()
  37. sol = LineStart().suppress()
  38. blank_line = sol + eol
  39. tc_summary_line = Group(Word(nums).setResultsName("num_of_tests") + "Tests" + Word(nums).setResultsName(
  40. "num_of_fail") + "Failures" + Word(nums).setResultsName("num_of_ignore") + "Ignored").setResultsName(
  41. "tc_summary")
  42. tc_end_line = Or(Literal("FAIL"), Literal('Ok')).setResultsName("tc_result")
  43. # run it and see...
  44. pp1 = tc_result_line | Optional(tc_summary_line | tc_end_line)
  45. pp1.ignore(blank_line | OneOrMore("-"))
  46. result = list()
  47. for l in lines:
  48. result.append((pp1.parseString(l)).asDict())
  49. # delete empty results
  50. result = filter(None, result)
  51. tc_list = list()
  52. for r in result:
  53. if 'tc_line' in r:
  54. tmp_tc_line = r['tc_line']
  55. # get only the file name which will be used as the classname
  56. file_name = tmp_tc_line['tc_file_name'].split('\\').pop().split('/').pop().rsplit('.', 1)[0]
  57. tmp_tc = TestCase(name=tmp_tc_line['tc_name'], classname=file_name)
  58. if 'tc_status' in tmp_tc_line:
  59. if str(tmp_tc_line['tc_status']) == 'IGNORE':
  60. if 'tc_msg' in tmp_tc_line:
  61. tmp_tc.add_skipped_info(message=tmp_tc_line['tc_msg'],
  62. output=r'[File]={0}, [Line]={1}'.format(
  63. tmp_tc_line['tc_file_name'], tmp_tc_line['tc_line_nr']))
  64. else:
  65. tmp_tc.add_skipped_info(message=" ")
  66. elif str(tmp_tc_line['tc_status']) == 'FAIL':
  67. if 'tc_msg' in tmp_tc_line:
  68. tmp_tc.add_failure_info(message=tmp_tc_line['tc_msg'],
  69. output=r'[File]={0}, [Line]={1}'.format(
  70. tmp_tc_line['tc_file_name'], tmp_tc_line['tc_line_nr']))
  71. else:
  72. tmp_tc.add_failure_info(message=" ")
  73. tc_list.append((str(result_file), tmp_tc))
  74. for k, v in tc_list:
  75. try:
  76. self.test_suites[k].append(v)
  77. except KeyError:
  78. self.test_suites[k] = [v]
  79. ts = []
  80. for suite_name in self.test_suites:
  81. ts.append(TestSuite(suite_name, self.test_suites[suite_name]))
  82. with open('result.xml', 'w') as f:
  83. TestSuite.to_file(f, ts, prettyprint='True', encoding='utf-8')
  84. return self.report
  85. def set_targets(self, target_array):
  86. self.targets = target_array
  87. def set_root_path(self, path):
  88. self.root = path
  89. @staticmethod
  90. def usage(err_msg=None):
  91. print("\nERROR: ")
  92. if err_msg:
  93. print(err_msg)
  94. print("\nUsage: unity_test_summary.py result_file_directory/ root_path/")
  95. print(" result_file_directory - The location of your results files.")
  96. print(" Defaults to current directory if not specified.")
  97. print(" Should end in / if specified.")
  98. print(" root_path - Helpful for producing more verbose output if using relative paths.")
  99. sys.exit(1)
  100. if __name__ == '__main__':
  101. uts = UnityTestSummary()
  102. try:
  103. # look in the specified or current directory for result files
  104. if len(sys.argv) > 1:
  105. targets_dir = sys.argv[1]
  106. else:
  107. targets_dir = './'
  108. targets = list(map(lambda x: x.replace('\\', '/'), glob(targets_dir + '*.test*')))
  109. if len(targets) == 0:
  110. raise Exception("No *.testpass or *.testfail files found in '%s'" % targets_dir)
  111. uts.set_targets(targets)
  112. # set the root path
  113. if len(sys.argv) > 2:
  114. root_path = sys.argv[2]
  115. else:
  116. root_path = os.path.split(__file__)[0]
  117. uts.set_root_path(root_path)
  118. # run the summarizer
  119. print(uts.run())
  120. except Exception as e:
  121. UnityTestSummary.usage(e)