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