diff --git a/scripts/pylib/twister/twisterlib/reports.py b/scripts/pylib/twister/twisterlib/reports.py index 2131c38a0dd..8a11097b391 100644 --- a/scripts/pylib/twister/twisterlib/reports.py +++ b/scripts/pylib/twister/twisterlib/reports.py @@ -372,7 +372,6 @@ class Reporting: suite["available_rom"] = available_rom if instance.status in [TwisterStatus.ERROR, TwisterStatus.FAIL]: suite['status'] = instance.status - suite["reason"] = instance.reason # FIXME if os.path.exists(pytest_log): suite["log"] = self.process_log(pytest_log) @@ -382,6 +381,11 @@ class Reporting: suite["log"] = self.process_log(device_log) else: suite["log"] = self.process_log(build_log) + + suite["reason"] = self.get_detailed_reason(instance.reason, suite["log"]) + # update the reason to get more details also in other reports (e.g. junit) + # where build log is not available + instance.reason = suite["reason"] elif instance.status == TwisterStatus.FILTER: suite["status"] = TwisterStatus.FILTER suite["reason"] = instance.reason @@ -798,3 +802,55 @@ class Reporting: self.json_report(json_platform_file + "_footprint.json", version=self.env.version, platform=platform.name, filters=self.json_filters['footprint.json']) + + def get_detailed_reason(self, reason: str, log: str) -> str: + if reason == 'CMake build failure': + if error_key := self._parse_cmake_build_failure(log): + return f"{reason} - {error_key}" + elif reason == 'Build failure': # noqa SIM102 + if error_key := self._parse_build_failure(log): + return f"{reason} - {error_key}" + return reason + + @staticmethod + def _parse_cmake_build_failure(log: str) -> str | None: + last_warning = 'no warning found' + lines = log.splitlines() + for i, line in enumerate(lines): + if "warning: " in line: + last_warning = line + elif "devicetree error: " in line: + return "devicetree error" + elif "fatal error: " in line: + return line[line.index('fatal error: ') :].strip() + elif "error: " in line: # error: Aborting due to Kconfig warnings + if "undefined symbol" in last_warning: + return last_warning[last_warning.index('undefined symbol') :].strip() + return last_warning + elif "CMake Error at" in line: + for next_line in lines[i + 1 :]: + if next_line.strip(): + return line + ' ' + next_line + return line + return None + + @staticmethod + def _parse_build_failure(log: str) -> str | None: + last_warning = '' + lines = log.splitlines() + for i, line in enumerate(lines): + if "undefined reference" in line: + return line[line.index('undefined reference') :].strip() + elif "error: ld returned" in line: + if last_warning: + return last_warning + elif "overflowed by" in lines[i - 1]: + return "ld.bfd: region overflowed" + elif "ld.bfd: warning: " in lines[i - 1]: + return "ld.bfd:" + lines[i - 1].split("ld.bfd:", 1)[-1] + return line + elif "error: " in line: + return line[line.index('error: ') :].strip() + elif ": in function " in line: + last_warning = line[line.index('in function') :].strip() + return None