1c342db35SBrad Bishop# 292b42cb3SPatrick Williams# Copyright OpenEmbedded Contributors 392b42cb3SPatrick Williams# 4c342db35SBrad Bishop# SPDX-License-Identifier: MIT 5c342db35SBrad Bishop# 6eb8dc403SDave Cobbley 7*8f840685SAndrew Geisslerimport enum 8eb8dc403SDave Cobbleyimport os 9eb8dc403SDave Cobbleyimport re 10eb8dc403SDave Cobbley 11eb8dc403SDave Cobbley# A parser that can be used to identify weather a line is a test result or a section statement. 1299467dabSAndrew Geisslerclass PtestParser(object): 13eb8dc403SDave Cobbley def __init__(self): 1499467dabSAndrew Geissler self.results = {} 1599467dabSAndrew Geissler self.sections = {} 16eb8dc403SDave Cobbley 1799467dabSAndrew Geissler def parse(self, logfile): 1899467dabSAndrew Geissler test_regex = {} 1999467dabSAndrew Geissler test_regex['PASSED'] = re.compile(r"^PASS:(.+)") 20f3fd288eSBrad Bishop test_regex['FAILED'] = re.compile(r"^FAIL:([^(]+)") 2199467dabSAndrew Geissler test_regex['SKIPPED'] = re.compile(r"^SKIP:(.+)") 22eb8dc403SDave Cobbley 2399467dabSAndrew Geissler section_regex = {} 2499467dabSAndrew Geissler section_regex['begin'] = re.compile(r"^BEGIN: .*/(.+)/ptest") 2599467dabSAndrew Geissler section_regex['end'] = re.compile(r"^END: .*/(.+)/ptest") 2699467dabSAndrew Geissler section_regex['duration'] = re.compile(r"^DURATION: (.+)") 2799467dabSAndrew Geissler section_regex['exitcode'] = re.compile(r"^ERROR: Exit status is (.+)") 2899467dabSAndrew Geissler section_regex['timeout'] = re.compile(r"^TIMEOUT: .*/(.+)/ptest") 29eb8dc403SDave Cobbley 3082c905dcSAndrew Geissler # Cache markers so we don't take the re.search() hit all the time. 3182c905dcSAndrew Geissler markers = ("PASS:", "FAIL:", "SKIP:", "BEGIN:", "END:", "DURATION:", "ERROR: Exit", "TIMEOUT:") 3282c905dcSAndrew Geissler 3399467dabSAndrew Geissler def newsection(): 3482c905dcSAndrew Geissler return { 'name': "No-section", 'log': [] } 3599467dabSAndrew Geissler 3699467dabSAndrew Geissler current_section = newsection() 3799467dabSAndrew Geissler 3899467dabSAndrew Geissler with open(logfile, errors='replace') as f: 3999467dabSAndrew Geissler for line in f: 4082c905dcSAndrew Geissler if not line.startswith(markers): 4182c905dcSAndrew Geissler current_section['log'].append(line) 4282c905dcSAndrew Geissler continue 4382c905dcSAndrew Geissler 4499467dabSAndrew Geissler result = section_regex['begin'].search(line) 4599467dabSAndrew Geissler if result: 4699467dabSAndrew Geissler current_section['name'] = result.group(1) 47028142bdSAndrew Geissler if current_section['name'] not in self.results: 48028142bdSAndrew Geissler self.results[current_section['name']] = {} 4999467dabSAndrew Geissler continue 5099467dabSAndrew Geissler 5199467dabSAndrew Geissler result = section_regex['end'].search(line) 5299467dabSAndrew Geissler if result: 5399467dabSAndrew Geissler if current_section['name'] != result.group(1): 5499467dabSAndrew Geissler bb.warn("Ptest END log section mismatch %s vs. %s" % (current_section['name'], result.group(1))) 5599467dabSAndrew Geissler if current_section['name'] in self.sections: 5699467dabSAndrew Geissler bb.warn("Ptest duplicate section for %s" % (current_section['name'])) 5799467dabSAndrew Geissler self.sections[current_section['name']] = current_section 5899467dabSAndrew Geissler del self.sections[current_section['name']]['name'] 5999467dabSAndrew Geissler current_section = newsection() 6099467dabSAndrew Geissler continue 6199467dabSAndrew Geissler 6299467dabSAndrew Geissler result = section_regex['timeout'].search(line) 6399467dabSAndrew Geissler if result: 6499467dabSAndrew Geissler if current_section['name'] != result.group(1): 6599467dabSAndrew Geissler bb.warn("Ptest TIMEOUT log section mismatch %s vs. %s" % (current_section['name'], result.group(1))) 6699467dabSAndrew Geissler current_section['timeout'] = True 6799467dabSAndrew Geissler continue 6899467dabSAndrew Geissler 6999467dabSAndrew Geissler for t in ['duration', 'exitcode']: 7099467dabSAndrew Geissler result = section_regex[t].search(line) 7199467dabSAndrew Geissler if result: 7299467dabSAndrew Geissler current_section[t] = result.group(1) 7399467dabSAndrew Geissler continue 7499467dabSAndrew Geissler 7582c905dcSAndrew Geissler current_section['log'].append(line) 7699467dabSAndrew Geissler 7799467dabSAndrew Geissler for t in test_regex: 7899467dabSAndrew Geissler result = test_regex[t].search(line) 7999467dabSAndrew Geissler if result: 80520786ccSPatrick Williams try: 81f3fd288eSBrad Bishop self.results[current_section['name']][result.group(1).strip()] = t 82520786ccSPatrick Williams except KeyError: 83520786ccSPatrick Williams bb.warn("Result with no section: %s - %s" % (t, result.group(1).strip())) 8499467dabSAndrew Geissler 8582c905dcSAndrew Geissler # Python performance for repeatedly joining long strings is poor, do it all at once at the end. 8682c905dcSAndrew Geissler # For 2.1 million lines in a log this reduces 18 hours to 12s. 8782c905dcSAndrew Geissler for section in self.sections: 8882c905dcSAndrew Geissler self.sections[section]['log'] = "".join(self.sections[section]['log']) 8982c905dcSAndrew Geissler 9099467dabSAndrew Geissler return self.results, self.sections 91eb8dc403SDave Cobbley 92eb8dc403SDave Cobbley # Log the results as files. The file name is the section name and the contents are the tests in that section. 9399467dabSAndrew Geissler def results_as_files(self, target_dir): 94eb8dc403SDave Cobbley if not os.path.exists(target_dir): 95eb8dc403SDave Cobbley raise Exception("Target directory does not exist: %s" % target_dir) 96eb8dc403SDave Cobbley 9799467dabSAndrew Geissler for section in self.results: 9899467dabSAndrew Geissler prefix = 'No-section' 99eb8dc403SDave Cobbley if section: 10099467dabSAndrew Geissler prefix = section 101eb8dc403SDave Cobbley section_file = os.path.join(target_dir, prefix) 102eb8dc403SDave Cobbley # purge the file contents if it exists 10399467dabSAndrew Geissler with open(section_file, 'w') as f: 10499467dabSAndrew Geissler for test_name in sorted(self.results[section]): 10599467dabSAndrew Geissler status = self.results[section][test_name] 10699467dabSAndrew Geissler f.write(status + ": " + test_name + "\n") 107eb8dc403SDave Cobbley 108c342db35SBrad Bishop 109*8f840685SAndrew Geisslerclass LtpParser: 110*8f840685SAndrew Geissler """ 111*8f840685SAndrew Geissler Parse the machine-readable LTP log output into a ptest-friendly data structure. 112*8f840685SAndrew Geissler """ 113c342db35SBrad Bishop def parse(self, logfile): 114*8f840685SAndrew Geissler results = {} 115*8f840685SAndrew Geissler # Aaccumulate the duration here but as the log rounds quick tests down 116*8f840685SAndrew Geissler # to 0 seconds this is very much a lower bound. The caller can replace 117*8f840685SAndrew Geissler # the value. 118*8f840685SAndrew Geissler section = {"duration": 0, "log": ""} 119c342db35SBrad Bishop 120*8f840685SAndrew Geissler class LtpExitCode(enum.IntEnum): 121*8f840685SAndrew Geissler # Exit codes as defined in ltp/include/tst_res_flags.h 122*8f840685SAndrew Geissler TPASS = 0 # Test passed flag 123*8f840685SAndrew Geissler TFAIL = 1 # Test failed flag 124*8f840685SAndrew Geissler TBROK = 2 # Test broken flag 125*8f840685SAndrew Geissler TWARN = 4 # Test warning flag 126*8f840685SAndrew Geissler TINFO = 16 # Test information flag 127*8f840685SAndrew Geissler TCONF = 32 # Test not appropriate for configuration flag 128*8f840685SAndrew Geissler 129*8f840685SAndrew Geissler with open(logfile, errors="replace") as f: 130*8f840685SAndrew Geissler # Lines look like this: 131*8f840685SAndrew Geissler # tag=cfs_bandwidth01 stime=1689762564 dur=0 exit=exited stat=32 core=no cu=0 cs=0 132c342db35SBrad Bishop for line in f: 133*8f840685SAndrew Geissler if not line.startswith("tag="): 134*8f840685SAndrew Geissler continue 135c342db35SBrad Bishop 136*8f840685SAndrew Geissler values = dict(s.split("=") for s in line.strip().split()) 137c342db35SBrad Bishop 138*8f840685SAndrew Geissler section["duration"] += int(values["dur"]) 139*8f840685SAndrew Geissler exitcode = int(values["stat"]) 140*8f840685SAndrew Geissler if values["exit"] == "exited" and exitcode == LtpExitCode.TCONF: 141*8f840685SAndrew Geissler # Exited normally with the "invalid configuration" code 142*8f840685SAndrew Geissler results[values["tag"]] = "SKIPPED" 143*8f840685SAndrew Geissler elif exitcode == LtpExitCode.TPASS: 144*8f840685SAndrew Geissler # Successful exit 145*8f840685SAndrew Geissler results[values["tag"]] = "PASSED" 146*8f840685SAndrew Geissler else: 147*8f840685SAndrew Geissler # Other exit 148*8f840685SAndrew Geissler results[values["tag"]] = "FAILED" 149*8f840685SAndrew Geissler 150*8f840685SAndrew Geissler return results, section 151c342db35SBrad Bishop 152c342db35SBrad Bishop 153c342db35SBrad Bishop# ltp Compliance log parsing 154c342db35SBrad Bishopclass LtpComplianceParser(object): 155c342db35SBrad Bishop def __init__(self): 156c342db35SBrad Bishop self.results = {} 157c342db35SBrad Bishop self.section = {'duration': "", 'log': ""} 158c342db35SBrad Bishop 159c342db35SBrad Bishop def parse(self, logfile): 160c342db35SBrad Bishop test_regex = {} 1617e0e3c0cSAndrew Geissler test_regex['FAILED'] = re.compile(r"FAIL") 162c342db35SBrad Bishop 163c342db35SBrad Bishop section_regex = {} 1647e0e3c0cSAndrew Geissler section_regex['test'] = re.compile(r"^Executing") 165c342db35SBrad Bishop 166c342db35SBrad Bishop with open(logfile, errors='replace') as f: 1677e0e3c0cSAndrew Geissler name = logfile 1687e0e3c0cSAndrew Geissler result = "PASSED" 169c342db35SBrad Bishop for line in f: 1707e0e3c0cSAndrew Geissler regex_result = section_regex['test'].search(line) 1717e0e3c0cSAndrew Geissler if regex_result: 1727e0e3c0cSAndrew Geissler name = line.split()[1].strip() 173c342db35SBrad Bishop 1747e0e3c0cSAndrew Geissler regex_result = test_regex['FAILED'].search(line) 1757e0e3c0cSAndrew Geissler if regex_result: 1767e0e3c0cSAndrew Geissler result = "FAILED" 1777e0e3c0cSAndrew Geissler self.results[name] = result 178c342db35SBrad Bishop 179c342db35SBrad Bishop for test in self.results: 180c342db35SBrad Bishop result = self.results[test] 1817e0e3c0cSAndrew Geissler print (self.results) 182c342db35SBrad Bishop self.section['log'] = self.section['log'] + ("%s: %s\n" % (result.strip()[:-2], test.strip())) 183c342db35SBrad Bishop 184c342db35SBrad Bishop return self.results, self.section 185