Skip to content

Commit b79453d

Browse files
gchwierDatGizmo
authored andcommitted
ci: twister: Group CMake and Build failures in Twister analysis
After adding more detailed information to the reason field in Twister report, update twister_report_analyzer.py to group CMake and Build failures. Signed-off-by: Grzegorz Chwierut <[email protected]>
1 parent 22abfc7 commit b79453d

File tree

1 file changed

+39
-42
lines changed

1 file changed

+39
-42
lines changed

scripts/ci/twister_report_analyzer.py

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -85,22 +85,22 @@ def add_counter(self, key: str, test: str = '') -> None:
8585
def print_counters(self, indent: int = 0):
8686
for key, value in self.counters.items():
8787
print(f'{" " * indent}{value.quantity:4} {key}')
88-
if value.subcounters.counters:
88+
if value.has_subcounters():
8989
value.subcounters.print_counters(indent + 4)
9090

9191
def sort_by_quantity(self):
9292
self.counters = dict(
9393
sorted(self.counters.items(), key=lambda item: item[1].quantity, reverse=True)
9494
)
9595
for value in self.counters.values():
96-
if value.subcounters.counters:
96+
if value.has_subcounters():
9797
value.subcounters.sort_by_quantity()
9898

9999
def get_next_entry(self, depth: int = 0, max_depth: int = 10):
100100
for key, value in self.counters.items():
101101
# limit number of test files to 100 to not exceed CSV cell limit
102102
yield depth, value.quantity, key, ', '.join(value.tests[0:100])
103-
if value.subcounters.counters and depth < max_depth:
103+
if value.has_subcounters() and depth < max_depth:
104104
yield from value.subcounters.get_next_entry(depth + 1, max_depth)
105105

106106
def _flatten(self):
@@ -110,7 +110,7 @@ def _flatten(self):
110110
do not contain any further nested subcounters.
111111
"""
112112
for key, value in self.counters.items():
113-
if value.subcounters.counters:
113+
if value.has_subcounters():
114114
yield from value.subcounters._flatten()
115115
else:
116116
yield key, value
@@ -130,6 +130,9 @@ def append(self, test: str = ''):
130130
if test:
131131
self.tests.append(test)
132132

133+
def has_subcounters(self):
134+
return bool(self.subcounters.counters)
135+
133136

134137
class TwisterReports:
135138
def __init__(self):
@@ -161,41 +164,44 @@ def parse_testsuite(self, testsuite):
161164
if ts_status not in ('error', 'failed'):
162165
return
163166

164-
ts_reason = testsuite.get('reason') or 'Unknown reason'
165-
self.errors.add_counter(ts_reason)
166167
ts_platform = testsuite.get('platform') or 'Unknown platform'
167168
self.platforms.add_counter(ts_platform)
169+
ts_reason = testsuite.get('reason') or 'Unknown reason'
168170
ts_log = testsuite.get('log')
169171
test_identifier = f'{testsuite.get("platform")}:{testsuite.get("name")}'
170172

171-
matched = self._parse_ts_error_log(
172-
self.errors.counters[ts_reason].subcounters, ts_reason, ts_log, test_identifier
173-
)
173+
# CMake and Build failures are treated separately.
174+
# Extract detailed information to group errors. Keep the parsing methods
175+
# to allow for further customization and keep backward compatibility.
176+
if ts_reason.startswith('CMake build failure'):
177+
reason = 'CMake build failure'
178+
self.errors.add_counter(reason)
179+
error_key = ts_reason.split(reason, 1)[-1].lstrip(' -')
180+
if not error_key:
181+
error_key = self._parse_cmake_build_failure(ts_log)
182+
self.errors.counters[reason].subcounters.add_counter(error_key, test_identifier)
183+
ts_reason = reason
184+
elif ts_reason.startswith('Build failure'):
185+
reason = 'Build failure'
186+
self.errors.add_counter(reason)
187+
error_key = ts_reason.split(reason, 1)[-1].lstrip(' -')
188+
if not error_key:
189+
error_key = self._parse_build_failure(ts_log)
190+
self.errors.counters[reason].subcounters.add_counter(error_key, test_identifier)
191+
ts_reason = reason
192+
else:
193+
self.errors.add_counter(ts_reason)
174194

175195
# Process testcases
176196
for tc in testsuite.get('testcases', []):
177197
tc_reason = tc.get('reason')
178198
tc_log = tc.get('log')
179199
if tc_reason and tc_log:
180200
self.errors.counters[ts_reason].subcounters.add_counter(tc_reason, test_identifier)
181-
matched = True
182201

183-
if not matched:
202+
if not self.errors.counters[ts_reason].has_subcounters():
184203
self.errors.counters[ts_reason].tests.append(test_identifier)
185204

186-
def _parse_ts_error_log(
187-
self, counters: Counters, reason: str, log: str, test: str = ''
188-
) -> bool:
189-
if reason == 'CMake build failure':
190-
if error_key := self._parse_cmake_build_failure(log):
191-
counters.add_counter(error_key, test)
192-
return True
193-
elif reason == 'Build failure': # noqa SIM102
194-
if error_key := self._parse_build_failure(log):
195-
counters.add_counter(error_key, test)
196-
return True
197-
return False
198-
199205
def _parse_cmake_build_failure(self, log: str) -> str | None:
200206
last_warning = 'no warning found'
201207
lines = log.splitlines()
@@ -263,32 +269,23 @@ def parse_testsuite(self, testsuite):
263269
if ts_status not in ('error', 'failed'):
264270
return
265271

266-
ts_reason = testsuite.get('reason') or 'Unknown reason'
267-
self.errors.add_counter(ts_reason)
268272
ts_log = testsuite.get('log')
269273
test_identifier = f'{testsuite.get("platform")}:{testsuite.get("name")}'
270-
self._parse_log_with_error_paterns(
271-
self.errors.counters[ts_reason].subcounters, ts_log, test_identifier
272-
)
274+
if key := self._parse_log_with_error_paterns(ts_log):
275+
self.errors.add_counter(key, test_identifier)
273276
# Process testcases
274277
for tc in testsuite.get('testcases', []):
275-
tc_reason = tc.get('reason')
276278
tc_log = tc.get('log')
277-
if tc_reason and tc_log:
278-
self.errors.counters[ts_reason].subcounters.add_counter(tc_reason)
279-
self._parse_log_with_error_paterns(
280-
self.errors.counters[ts_reason].subcounters.counters[tc_reason].subcounters,
281-
tc_log,
282-
test_identifier,
283-
)
284-
285-
def _parse_log_with_error_paterns(self, counters: Counters, log: str, test: str = ''):
279+
if tc_log and (key := self._parse_log_with_error_paterns(tc_log)):
280+
self.errors.add_counter(key, test_identifier)
281+
282+
def _parse_log_with_error_paterns(self, log: str) -> str | None:
286283
for line in log.splitlines():
287284
for error_pattern in self.error_patterns:
288285
if error_pattern in line:
289286
logger.debug(f'Matched: {error_pattern} in {line}')
290-
counters.add_counter(error_pattern, test)
291-
return
287+
return error_pattern
288+
return None
292289

293290

294291
class EnhancedJSONEncoder(json.JSONEncoder):
@@ -363,7 +360,7 @@ def main():
363360
if not reports.errors.counters:
364361
return
365362

366-
if args.platforms:
363+
if args.platforms and reports.platforms.counters:
367364
print('\nErrors per platform:')
368365
reports.platforms.print_counters()
369366

0 commit comments

Comments
 (0)