1#! python3 2# ========================================== 3# Fork from Unity Project - A Test Framework for C 4# Pull request on Gerrit in progress, the objective of this file is to be deleted when official Unity deliveries 5# include that modification 6# Copyright (c) 2015 Alexander Mueller / XelaRellum@web.de 7# [Released under MIT License. Please refer to license.txt for details] 8# ========================================== 9import sys 10import os 11from glob import glob 12import argparse 13 14from pyparsing import * 15from junit_xml import TestSuite, TestCase 16 17 18class UnityTestSummary: 19 def __init__(self): 20 self.report = '' 21 self.total_tests = 0 22 self.failures = 0 23 self.ignored = 0 24 self.targets = 0 25 self.root = None 26 self.output = None 27 self.test_suites = dict() 28 29 def run(self): 30 # Clean up result file names 31 results = [] 32 for target in self.targets: 33 results.append(target.replace('\\', '/')) 34 35 # Dig through each result file, looking for details on pass/fail: 36 for result_file in results: 37 lines = list(map(lambda line: line.rstrip(), open(result_file, "r").read().split('\n'))) 38 if len(lines) == 0: 39 raise Exception("Empty test result file: %s" % result_file) 40 41 # define an expression for your file reference 42 entry_one = Combine( 43 oneOf(list(alphas)) + ':/' + 44 Word(alphanums + '_-./')) 45 46 entry_two = Word(printables + ' ', excludeChars=':') 47 entry = entry_one | entry_two 48 49 delimiter = Literal(':').suppress() 50 # Format of a result line is `[file_name]:line:test_name:RESULT[:msg]` 51 tc_result_line = Group(ZeroOrMore(entry.setResultsName('tc_file_name')) 52 + delimiter + entry.setResultsName('tc_line_nr') 53 + delimiter + entry.setResultsName('tc_name') 54 + delimiter + entry.setResultsName('tc_status') + 55 Optional(delimiter + entry.setResultsName('tc_msg'))).setResultsName("tc_line") 56 57 eol = LineEnd().suppress() 58 sol = LineStart().suppress() 59 blank_line = sol + eol 60 61 # Format of the summary line is `# Tests # Failures # Ignored` 62 tc_summary_line = Group(Word(nums).setResultsName("num_of_tests") + "Tests" + Word(nums).setResultsName( 63 "num_of_fail") + "Failures" + Word(nums).setResultsName("num_of_ignore") + "Ignored").setResultsName( 64 "tc_summary") 65 tc_end_line = Or(Literal("FAIL"), Literal('Ok')).setResultsName("tc_result") 66 67 # run it and see... 68 pp1 = tc_result_line | Optional(tc_summary_line | tc_end_line) 69 pp1.ignore(blank_line | OneOrMore("-")) 70 71 result = list() 72 for l in lines: 73 result.append((pp1.parseString(l)).asDict()) 74 # delete empty results 75 result = filter(None, result) 76 77 tc_list = list() 78 for r in result: 79 if 'tc_line' in r: 80 tmp_tc_line = r['tc_line'] 81 82 # get only the file name which will be used as the classname 83 if 'tc_file_name' in tmp_tc_line: 84 file_name = tmp_tc_line['tc_file_name'].split('\\').pop().split('/').pop().rsplit('.', 1)[0] 85 else: 86 file_name = result_file.strip("./") 87 tmp_tc = TestCase(name=tmp_tc_line['tc_name'], classname=file_name) 88 if 'tc_status' in tmp_tc_line: 89 if str(tmp_tc_line['tc_status']) == 'IGNORE': 90 if 'tc_msg' in tmp_tc_line: 91 tmp_tc.add_skipped_info(message=tmp_tc_line['tc_msg'], 92 output=r'[File]={0}, [Line]={1}'.format( 93 tmp_tc_line['tc_file_name'], tmp_tc_line['tc_line_nr'])) 94 else: 95 tmp_tc.add_skipped_info(message=" ") 96 elif str(tmp_tc_line['tc_status']) == 'FAIL': 97 if 'tc_msg' in tmp_tc_line: 98 tmp_tc.add_failure_info(message=tmp_tc_line['tc_msg'], 99 output=r'[File]={0}, [Line]={1}'.format( 100 tmp_tc_line['tc_file_name'], tmp_tc_line['tc_line_nr'])) 101 else: 102 tmp_tc.add_failure_info(message=" ") 103 104 tc_list.append((str(result_file), tmp_tc)) 105 106 for k, v in tc_list: 107 try: 108 self.test_suites[k].append(v) 109 except KeyError: 110 self.test_suites[k] = [v] 111 ts = [] 112 for suite_name in self.test_suites: 113 ts.append(TestSuite(suite_name, self.test_suites[suite_name])) 114 115 with open(self.output, 'w') as f: 116 TestSuite.to_file(f, ts, prettyprint='True', encoding='utf-8') 117 118 return self.report 119 120 def set_targets(self, target_array): 121 self.targets = target_array 122 123 def set_root_path(self, path): 124 self.root = path 125 126 def set_output(self, output): 127 self.output = output 128 129 130if __name__ == '__main__': 131 uts = UnityTestSummary() 132 parser = argparse.ArgumentParser(description= 133 """Takes as input the collection of *.testpass and *.testfail result 134 files, and converts them to a JUnit formatted XML.""") 135 parser.add_argument('targets_dir', metavar='result_file_directory', 136 type=str, nargs='?', default='./', 137 help="""The location of your results files. 138 Defaults to current directory if not specified.""") 139 parser.add_argument('root_path', nargs='?', 140 default='os.path.split(__file__)[0]', 141 help="""Helpful for producing more verbose output if 142 using relative paths.""") 143 parser.add_argument('--output', '-o', type=str, default="result.xml", 144 help="""The name of the JUnit-formatted file (XML).""") 145 args = parser.parse_args() 146 147 if args.targets_dir[-1] != '/': 148 args.targets_dir+='/' 149 targets = list(map(lambda x: x.replace('\\', '/'), glob(args.targets_dir + '*.test*'))) 150 if len(targets) == 0: 151 raise Exception("No *.testpass or *.testfail files found in '%s'" % args.targets_dir) 152 uts.set_targets(targets) 153 154 # set the root path 155 uts.set_root_path(args.root_path) 156 157 # set output 158 uts.set_output(args.output) 159 160 # run the summarizer 161 print(uts.run()) 162