diff --git a/logspec/errors/kbuild.py b/logspec/errors/kbuild.py index 60e2481..c1f66d5 100644 --- a/logspec/errors/kbuild.py +++ b/logspec/errors/kbuild.py @@ -492,9 +492,11 @@ def _parse(self, text): """Parses a log fragment looking for a generic Kbuild error and updates the object with the extracted information. - Strategy: if a target was specified, search for errors _after_ - the first appearance of the `target' string in the log. To - search for these errors, look for unindented lines. + Strategy: if a target was specified, search for errors at or + after the first appearance of the `target' string in the log. + When text follows the target on the same line, keep that entire + line so diagnostics containing the target are not truncated. + To search for these errors, look for unindented lines. Parameters: text (str): the text log containing the modpost error @@ -505,11 +507,28 @@ def _parse(self, text): self.error_type = "kbuild.other" end = 0 if self.target: - match = re.search(self.target, text) - if not match: + target_match = re.search(re.escape(self.target), text) + if not target_match: return end + + line_start = text.rfind('\n', 0, target_match.start()) + 1 + line_end = text.find('\n', target_match.end()) + if line_end == -1: + line_end = len(text) + + # A diagnostic can contain the target followed by the actual + # failure, for example: "cp: cannot create '': File + # exists". Start at the beginning of that line instead of + # returning only the text after the target. If the target ends + # the line (as it commonly does in a command), start at the next + # line and retain the existing command-exclusion behaviour. + trailing_text = text[target_match.end():line_end].strip() + search_start = ( + line_start if trailing_text else min(line_end + 1, len(text)) + ) + summary_strings = [] - match = re.finditer(r'^[^\s]+.*$', text[match.end():], flags=re.MULTILINE) + match = re.finditer(r'^[^\s]+.*$', text[search_start:], flags=re.MULTILINE) for m in match: current_match = m.group() self._report += f"{current_match}\n" @@ -523,7 +542,7 @@ def _parse(self, text): generic_error_match = re.search(fr'{TIMESTAMP}(.*error:.*)', current_match) if generic_error_match: summary_strings.append(generic_error_match.group(1)) - end = m.end() + end = search_start + m.end() if summary_strings: self.error_summary = " ".join([string for string in summary_strings if string]) return end @@ -629,7 +648,11 @@ def find_kbuild_error(text): None if no error report was found. """ end = 0 - match = re.search(r'make.*?: \*\*\* (?P.*)', text) + match = re.search( + fr'^{TIMESTAMP}make.*?: \*\*\* (?P.*)', + text, + flags=re.MULTILINE, + ) if not match: return None error_str = match.group('error_str') @@ -658,6 +681,12 @@ def find_kbuild_error(text): # Catch-all condition for non-specific errors error = KbuildGenericError(script=script, target=target) error.parse(error_text) + if isinstance(error, KbuildGenericError) and not error._report: + # Some generic failures have no preceding diagnostic or target + # occurrence. The Make failure is still useful context and must + # not result in an empty report body. + line_start = text.rfind('\n', 0, start) + 1 + error._report = f"{text[line_start:end]}\n" else: # Unrecognized error, these are marked as unknown and not parsed error = KbuildUnknownError(error_str) diff --git a/tests/logs/index.txt b/tests/logs/index.txt index c86684e..38cace9 100644 --- a/tests/logs/index.txt +++ b/tests/logs/index.txt @@ -133,6 +133,13 @@ - kbuild_024.log: Device Tree Compiler fatal file error FATAL ERROR: Couldn't open "arch/arm64/boot/dts/qcom/missing.dtsi": No such file or directory + - kbuild_025.log: Generic generated-header failure without a diagnostic + make[5]: *** [/tmp/kci/linux/arch/x86/Makefile:269: arch/x86/include/generated/asm/cpufeaturemasks.h] Error 1 + + - kbuild_026.log: Generic diagnostic containing the target path + cp: cannot create regular file '/tmp/kci/artifacts/build/kselftest/arm64/signal/fake_sigreturn_bad_magic': File exists + + ./linux_boot diff --git a/tests/logs/kbuild/kbuild_025.log b/tests/logs/kbuild/kbuild_025.log new file mode 100644 index 0000000..cefbaf3 --- /dev/null +++ b/tests/logs/kbuild/kbuild_025.log @@ -0,0 +1,3 @@ +00:00:15 make --silent --keep-going --jobs=8 O=/tmp/kci/artifacts/build ARCH=x86_64 HOSTCC=clang CC=clang +00:13:13 make[5]: *** [/tmp/kci/linux/arch/x86/Makefile:269: arch/x86/include/generated/asm/cpufeaturemasks.h] Error 1 +00:13:13 make[5]: Target 'prepare' not remade because of errors. diff --git a/tests/logs/kbuild/kbuild_026.log b/tests/logs/kbuild/kbuild_026.log new file mode 100644 index 0000000..707d98c --- /dev/null +++ b/tests/logs/kbuild/kbuild_026.log @@ -0,0 +1,5 @@ +04:41:39 CC acct_syscall +04:41:39 CC tags_test +04:41:43 cp: cannot create regular file '/tmp/kci/artifacts/build/kselftest/arm64/signal/fake_sigreturn_bad_magic': File exists +04:41:43 make[2]: *** [Makefile:21: /tmp/kci/artifacts/build/kselftest/arm64/signal/fake_sigreturn_bad_magic] Error 1 +04:41:43 make[2]: Target 'all' not remade because of errors. diff --git a/tests/test_kbuild.py b/tests/test_kbuild.py index 30d94b4..1ef15d4 100644 --- a/tests/test_kbuild.py +++ b/tests/test_kbuild.py @@ -567,6 +567,32 @@ "target": "arch/arm64/boot/dts/qcom/example.dtb" } ] + }), + + # Generic generated-header failure without an earlier target occurrence. + ('kbuild_025.log', + 'kbuild', + { + "errors": [ + { + "error_type": "kbuild.other", + "script": "/tmp/kci/linux/arch/x86/Makefile:269", + "target": "arch/x86/include/generated/asm/cpufeaturemasks.h" + } + ] + }), + + # Generic diagnostic containing the complete target path. + ('kbuild_026.log', + 'kbuild', + { + "errors": [ + { + "error_type": "kbuild.other", + "script": "Makefile:21", + "target": "/tmp/kci/artifacts/build/kselftest/arm64/signal/fake_sigreturn_bad_magic" + } + ] }) ]) def test_kbuild(log_file, parser_id, expected): @@ -604,3 +630,28 @@ def test_kbuild_dtc_check_report(): "288.8-296.3\n" "ERROR: Input tree has errors, aborting (use -f to force output)\n" ) + + +def test_kbuild_generic_make_fallback_report(): + log_file = os.path.join(LOG_DIR, 'kbuild_025.log') + parsed_data = load_parser_and_parse_log( + log_file, 'kbuild', tests.setup.PARSER_DEFS_FILE + ) + + assert parsed_data['errors'][0]._report == ( + "00:13:13 make[5]: *** [/tmp/kci/linux/arch/x86/Makefile:269: " + "arch/x86/include/generated/asm/cpufeaturemasks.h] Error 1\n" + ) + + +def test_kbuild_generic_preserves_diagnostic_line(): + log_file = os.path.join(LOG_DIR, 'kbuild_026.log') + parsed_data = load_parser_and_parse_log( + log_file, 'kbuild', tests.setup.PARSER_DEFS_FILE + ) + + assert parsed_data['errors'][0]._report == ( + "04:41:43 cp: cannot create regular file " + "'/tmp/kci/artifacts/build/kselftest/arm64/signal/" + "fake_sigreturn_bad_magic': File exists\n" + )