diff options
Diffstat (limited to 'meta/lib/oeqa/utils/logparser.py')
-rw-r--r-- | meta/lib/oeqa/utils/logparser.py | 98 |
1 files changed, 59 insertions, 39 deletions
diff --git a/meta/lib/oeqa/utils/logparser.py b/meta/lib/oeqa/utils/logparser.py index 60e16d500e..496d9e0c90 100644 --- a/meta/lib/oeqa/utils/logparser.py +++ b/meta/lib/oeqa/utils/logparser.py | |||
@@ -1,8 +1,10 @@ | |||
1 | # | 1 | # |
2 | # Copyright OpenEmbedded Contributors | ||
3 | # | ||
2 | # SPDX-License-Identifier: MIT | 4 | # SPDX-License-Identifier: MIT |
3 | # | 5 | # |
4 | 6 | ||
5 | import sys | 7 | import enum |
6 | import os | 8 | import os |
7 | import re | 9 | import re |
8 | 10 | ||
@@ -42,6 +44,8 @@ class PtestParser(object): | |||
42 | result = section_regex['begin'].search(line) | 44 | result = section_regex['begin'].search(line) |
43 | if result: | 45 | if result: |
44 | current_section['name'] = result.group(1) | 46 | current_section['name'] = result.group(1) |
47 | if current_section['name'] not in self.results: | ||
48 | self.results[current_section['name']] = {} | ||
45 | continue | 49 | continue |
46 | 50 | ||
47 | result = section_regex['end'].search(line) | 51 | result = section_regex['end'].search(line) |
@@ -73,9 +77,10 @@ class PtestParser(object): | |||
73 | for t in test_regex: | 77 | for t in test_regex: |
74 | result = test_regex[t].search(line) | 78 | result = test_regex[t].search(line) |
75 | if result: | 79 | if result: |
76 | if current_section['name'] not in self.results: | 80 | try: |
77 | self.results[current_section['name']] = {} | 81 | self.results[current_section['name']][result.group(1).strip()] = t |
78 | self.results[current_section['name']][result.group(1).strip()] = t | 82 | except KeyError: |
83 | bb.warn("Result with no section: %s - %s" % (t, result.group(1).strip())) | ||
79 | 84 | ||
80 | # Python performance for repeatedly joining long strings is poor, do it all at once at the end. | 85 | # Python performance for repeatedly joining long strings is poor, do it all at once at the end. |
81 | # For 2.1 million lines in a log this reduces 18 hours to 12s. | 86 | # For 2.1 million lines in a log this reduces 18 hours to 12s. |
@@ -101,30 +106,48 @@ class PtestParser(object): | |||
101 | f.write(status + ": " + test_name + "\n") | 106 | f.write(status + ": " + test_name + "\n") |
102 | 107 | ||
103 | 108 | ||
104 | # ltp log parsing | 109 | class LtpParser: |
105 | class LtpParser(object): | 110 | """ |
106 | def __init__(self): | 111 | Parse the machine-readable LTP log output into a ptest-friendly data structure. |
107 | self.results = {} | 112 | """ |
108 | self.section = {'duration': "", 'log': ""} | ||
109 | |||
110 | def parse(self, logfile): | 113 | def parse(self, logfile): |
111 | test_regex = {} | 114 | results = {} |
112 | test_regex['PASSED'] = re.compile(r"PASS") | 115 | # Aaccumulate the duration here but as the log rounds quick tests down |
113 | test_regex['FAILED'] = re.compile(r"FAIL") | 116 | # to 0 seconds this is very much a lower bound. The caller can replace |
114 | test_regex['SKIPPED'] = re.compile(r"SKIP") | 117 | # the value. |
115 | 118 | section = {"duration": 0, "log": ""} | |
116 | with open(logfile, errors='replace') as f: | 119 | |
120 | class LtpExitCode(enum.IntEnum): | ||
121 | # Exit codes as defined in ltp/include/tst_res_flags.h | ||
122 | TPASS = 0 # Test passed flag | ||
123 | TFAIL = 1 # Test failed flag | ||
124 | TBROK = 2 # Test broken flag | ||
125 | TWARN = 4 # Test warning flag | ||
126 | TINFO = 16 # Test information flag | ||
127 | TCONF = 32 # Test not appropriate for configuration flag | ||
128 | |||
129 | with open(logfile, errors="replace") as f: | ||
130 | # Lines look like this: | ||
131 | # tag=cfs_bandwidth01 stime=1689762564 dur=0 exit=exited stat=32 core=no cu=0 cs=0 | ||
117 | for line in f: | 132 | for line in f: |
118 | for t in test_regex: | 133 | if not line.startswith("tag="): |
119 | result = test_regex[t].search(line) | 134 | continue |
120 | if result: | ||
121 | self.results[line.split()[0].strip()] = t | ||
122 | 135 | ||
123 | for test in self.results: | 136 | values = dict(s.split("=") for s in line.strip().split()) |
124 | result = self.results[test] | ||
125 | self.section['log'] = self.section['log'] + ("%s: %s\n" % (result.strip()[:-2], test.strip())) | ||
126 | 137 | ||
127 | return self.results, self.section | 138 | section["duration"] += int(values["dur"]) |
139 | exitcode = int(values["stat"]) | ||
140 | if values["exit"] == "exited" and exitcode == LtpExitCode.TCONF: | ||
141 | # Exited normally with the "invalid configuration" code | ||
142 | results[values["tag"]] = "SKIPPED" | ||
143 | elif exitcode == LtpExitCode.TPASS: | ||
144 | # Successful exit | ||
145 | results[values["tag"]] = "PASSED" | ||
146 | else: | ||
147 | # Other exit | ||
148 | results[values["tag"]] = "FAILED" | ||
149 | |||
150 | return results, section | ||
128 | 151 | ||
129 | 152 | ||
130 | # ltp Compliance log parsing | 153 | # ltp Compliance log parsing |
@@ -135,30 +158,27 @@ class LtpComplianceParser(object): | |||
135 | 158 | ||
136 | def parse(self, logfile): | 159 | def parse(self, logfile): |
137 | test_regex = {} | 160 | test_regex = {} |
138 | test_regex['PASSED'] = re.compile(r"^PASS") | 161 | test_regex['FAILED'] = re.compile(r"FAIL") |
139 | test_regex['FAILED'] = re.compile(r"^FAIL") | ||
140 | test_regex['SKIPPED'] = re.compile(r"(?:UNTESTED)|(?:UNSUPPORTED)") | ||
141 | 162 | ||
142 | section_regex = {} | 163 | section_regex = {} |
143 | section_regex['test'] = re.compile(r"^Testing") | 164 | section_regex['test'] = re.compile(r"^Executing") |
144 | 165 | ||
145 | with open(logfile, errors='replace') as f: | 166 | with open(logfile, errors='replace') as f: |
167 | name = logfile | ||
168 | result = "PASSED" | ||
146 | for line in f: | 169 | for line in f: |
147 | result = section_regex['test'].search(line) | 170 | regex_result = section_regex['test'].search(line) |
148 | if result: | 171 | if regex_result: |
149 | self.name = "" | 172 | name = line.split()[1].strip() |
150 | self.name = line.split()[1].strip() | ||
151 | self.results[self.name] = "PASSED" | ||
152 | failed = 0 | ||
153 | 173 | ||
154 | failed_result = test_regex['FAILED'].search(line) | 174 | regex_result = test_regex['FAILED'].search(line) |
155 | if failed_result: | 175 | if regex_result: |
156 | failed = line.split()[1].strip() | 176 | result = "FAILED" |
157 | if int(failed) > 0: | 177 | self.results[name] = result |
158 | self.results[self.name] = "FAILED" | ||
159 | 178 | ||
160 | for test in self.results: | 179 | for test in self.results: |
161 | result = self.results[test] | 180 | result = self.results[test] |
181 | print (self.results) | ||
162 | self.section['log'] = self.section['log'] + ("%s: %s\n" % (result.strip()[:-2], test.strip())) | 182 | self.section['log'] = self.section['log'] + ("%s: %s\n" % (result.strip()[:-2], test.strip())) |
163 | 183 | ||
164 | return self.results, self.section | 184 | return self.results, self.section |