Skip to content

Commit

Permalink
ci: twister: Group CMake and Build failures in Twister analysis
Browse files Browse the repository at this point in the history
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 <grzegorz.chwierut@nordicsemi.no>
  • Loading branch information
gchwier authored and kartben committed Feb 13, 2025
1 parent 33b202e commit 145b1b9
Showing 1 changed file with 39 additions and 42 deletions.
81 changes: 39 additions & 42 deletions scripts/ci/twister_report_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,22 @@ def add_counter(self, key: str, test: str = '') -> None:
def print_counters(self, indent: int = 0):
for key, value in self.counters.items():
print(f'{" " * indent}{value.quantity:4} {key}')
if value.subcounters.counters:
if value.has_subcounters():
value.subcounters.print_counters(indent + 4)

def sort_by_quantity(self):
self.counters = dict(
sorted(self.counters.items(), key=lambda item: item[1].quantity, reverse=True)
)
for value in self.counters.values():
if value.subcounters.counters:
if value.has_subcounters():
value.subcounters.sort_by_quantity()

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

def _flatten(self):
Expand All @@ -110,7 +110,7 @@ def _flatten(self):
do not contain any further nested subcounters.
"""
for key, value in self.counters.items():
if value.subcounters.counters:
if value.has_subcounters():
yield from value.subcounters._flatten()
else:
yield key, value
Expand All @@ -130,6 +130,9 @@ def append(self, test: str = ''):
if test:
self.tests.append(test)

def has_subcounters(self):
return bool(self.subcounters.counters)


class TwisterReports:
def __init__(self):
Expand Down Expand Up @@ -161,41 +164,44 @@ def parse_testsuite(self, testsuite):
if ts_status not in ('error', 'failed'):
return

ts_reason = testsuite.get('reason') or 'Unknown reason'
self.errors.add_counter(ts_reason)
ts_platform = testsuite.get('platform') or 'Unknown platform'
self.platforms.add_counter(ts_platform)
ts_reason = testsuite.get('reason') or 'Unknown reason'
ts_log = testsuite.get('log')
test_identifier = f'{testsuite.get("platform")}:{testsuite.get("name")}'

matched = self._parse_ts_error_log(
self.errors.counters[ts_reason].subcounters, ts_reason, ts_log, test_identifier
)
# CMake and Build failures are treated separately.
# Extract detailed information to group errors. Keep the parsing methods
# to allow for further customization and keep backward compatibility.
if ts_reason.startswith('CMake build failure'):
reason = 'CMake build failure'
self.errors.add_counter(reason)
error_key = ts_reason.split(reason, 1)[-1].lstrip(' -')
if not error_key:
error_key = self._parse_cmake_build_failure(ts_log)
self.errors.counters[reason].subcounters.add_counter(error_key, test_identifier)
ts_reason = reason
elif ts_reason.startswith('Build failure'):
reason = 'Build failure'
self.errors.add_counter(reason)
error_key = ts_reason.split(reason, 1)[-1].lstrip(' -')
if not error_key:
error_key = self._parse_build_failure(ts_log)
self.errors.counters[reason].subcounters.add_counter(error_key, test_identifier)
ts_reason = reason
else:
self.errors.add_counter(ts_reason)

# Process testcases
for tc in testsuite.get('testcases', []):
tc_reason = tc.get('reason')
tc_log = tc.get('log')
if tc_reason and tc_log:
self.errors.counters[ts_reason].subcounters.add_counter(tc_reason, test_identifier)
matched = True

if not matched:
if not self.errors.counters[ts_reason].has_subcounters():
self.errors.counters[ts_reason].tests.append(test_identifier)

def _parse_ts_error_log(
self, counters: Counters, reason: str, log: str, test: str = ''
) -> bool:
if reason == 'CMake build failure':
if error_key := self._parse_cmake_build_failure(log):
counters.add_counter(error_key, test)
return True
elif reason == 'Build failure': # noqa SIM102
if error_key := self._parse_build_failure(log):
counters.add_counter(error_key, test)
return True
return False

def _parse_cmake_build_failure(self, log: str) -> str | None:
last_warning = 'no warning found'
lines = log.splitlines()
Expand Down Expand Up @@ -263,32 +269,23 @@ def parse_testsuite(self, testsuite):
if ts_status not in ('error', 'failed'):
return

ts_reason = testsuite.get('reason') or 'Unknown reason'
self.errors.add_counter(ts_reason)
ts_log = testsuite.get('log')
test_identifier = f'{testsuite.get("platform")}:{testsuite.get("name")}'
self._parse_log_with_error_paterns(
self.errors.counters[ts_reason].subcounters, ts_log, test_identifier
)
if key := self._parse_log_with_error_paterns(ts_log):
self.errors.add_counter(key, test_identifier)
# Process testcases
for tc in testsuite.get('testcases', []):
tc_reason = tc.get('reason')
tc_log = tc.get('log')
if tc_reason and tc_log:
self.errors.counters[ts_reason].subcounters.add_counter(tc_reason)
self._parse_log_with_error_paterns(
self.errors.counters[ts_reason].subcounters.counters[tc_reason].subcounters,
tc_log,
test_identifier,
)

def _parse_log_with_error_paterns(self, counters: Counters, log: str, test: str = ''):
if tc_log and (key := self._parse_log_with_error_paterns(tc_log)):
self.errors.add_counter(key, test_identifier)

def _parse_log_with_error_paterns(self, log: str) -> str | None:
for line in log.splitlines():
for error_pattern in self.error_patterns:
if error_pattern in line:
logger.debug(f'Matched: {error_pattern} in {line}')
counters.add_counter(error_pattern, test)
return
return error_pattern
return None


class EnhancedJSONEncoder(json.JSONEncoder):
Expand Down Expand Up @@ -363,7 +360,7 @@ def main():
if not reports.errors.counters:
return

if args.platforms:
if args.platforms and reports.platforms.counters:
print('\nErrors per platform:')
reports.platforms.print_counters()

Expand Down

0 comments on commit 145b1b9

Please sign in to comment.