From f7c6ecd63410eb7c286fd171cda95a86ec8bea4f Mon Sep 17 00:00:00 2001 From: Daniel Moody Date: Tue, 27 Jan 2026 16:11:54 -0600 Subject: [PATCH] SERVER-117835 fix symbol check suggested command (#47067) GitOrigin-RevId: 68c94d6912ada19f0b87499ac8030674b9b75be0 --- buildscripts/bazel_testbuilds/BUILD.bazel | 14 ++++ .../test_generate_symbol_check_report.py | 78 +++++++++++++++++++ buildscripts/util/BUILD.bazel | 4 + evergreen/generate_symbol_check_report.py | 40 +++++++++- 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 buildscripts/bazel_testbuilds/test_generate_symbol_check_report.py diff --git a/buildscripts/bazel_testbuilds/BUILD.bazel b/buildscripts/bazel_testbuilds/BUILD.bazel index 7ca44cb1dff..4104d6678d2 100644 --- a/buildscripts/bazel_testbuilds/BUILD.bazel +++ b/buildscripts/bazel_testbuilds/BUILD.bazel @@ -9,6 +9,20 @@ mongo_cc_binary( srcs = ["unit_test.cpp"], ) +# Verifies evergreen symbol-check report uses the exact bazel invocation +# emitted by the Evergreen bazel compile script, and suggests *_with_debug targets. +py_test( + name = "test_generate_symbol_check_report", + srcs = ["test_generate_symbol_check_report.py"], + data = [ + "//evergreen:generate_symbol_check_report.py", + ], + deps = [ + "//buildscripts:simple_report", + "//buildscripts/util", + ], +) + # Test for verifying test_wrapper.sh timeout behavior and coredump generation. # This test intentionally hangs to trigger the timeout mechanism in test_wrapper.sh. # When run via the --run_under=//bazel:test_wrapper param, it should: diff --git a/buildscripts/bazel_testbuilds/test_generate_symbol_check_report.py b/buildscripts/bazel_testbuilds/test_generate_symbol_check_report.py new file mode 100644 index 00000000000..7af14705611 --- /dev/null +++ b/buildscripts/bazel_testbuilds/test_generate_symbol_check_report.py @@ -0,0 +1,78 @@ +import json +import os +import pathlib +import runpy +import tempfile +import unittest + + +def _repo_root_from_runfiles() -> pathlib.Path: + test_srcdir = os.environ.get("TEST_SRCDIR") + test_workspace = os.environ.get("TEST_WORKSPACE") + if not test_srcdir or not test_workspace: + raise RuntimeError( + "Expected TEST_SRCDIR and TEST_WORKSPACE to be set by Bazel test environment" + ) + return pathlib.Path(test_srcdir) / test_workspace + + +class GenerateSymbolCheckReportTest(unittest.TestCase): + def test_uses_bazel_compile_raw_invocation_and_flags(self): + repo_root = _repo_root_from_runfiles() + script_path = repo_root / "evergreen" / "generate_symbol_check_report.py" + self.assertTrue(script_path.exists(), f"Missing runfile: {script_path}") + + # Simulate the Evergreen layout: + # - script runs with cwd=.../src + # - expansions.yml lives one directory above cwd (../expansions.yml) + with tempfile.TemporaryDirectory() as td: + workdir = pathlib.Path(td) + (workdir / "expansions.yml").write_text('run_for_symbol_check: "true"\n') + + src = workdir / "src" + src.mkdir(parents=True) + + bazel_build_flags = "--config=evg --config=symbol-checker --keep_going" + bazel_build_invocation = "bazel build --config=evg --config=symbol-checker //src/..." + (src / ".bazel_build_flags").write_text(bazel_build_flags + "\n") + (src / ".bazel_build_invocation").write_text(bazel_build_invocation + "\n") + + checked_dir = src / "bazel-bin" / "buildscripts" / "bazel_testbuilds" + checked_dir.mkdir(parents=True) + checked_path = checked_dir / "unit_test_checked" + checked_payload = { + "status": "failed", + "target": "//buildscripts/bazel_testbuilds:unit_test", + "sym_file": "ignored.sym", + "missing": ["some_symbol"], + } + checked_path.write_text(json.dumps(checked_payload)) + + old_cwd = os.getcwd() + try: + os.chdir(src) + with self.assertRaises(SystemExit) as ctx: + runpy.run_path(str(script_path), run_name="__main__") + finally: + os.chdir(old_cwd) + + # With a failing _checked file, the report script should exit non-zero. + self.assertEqual(ctx.exception.code, 1) + + # The helper artifact should prefer the exact invocation written by bazel_compile.sh. + self.assertEqual( + (src / "bazel-invocation.txt").read_text().strip(), bazel_build_invocation + ) + + # The report content should prefer the exact flags written by bazel_compile.sh. + report = json.loads((src / "report.json").read_text()) + log_raw = report["results"][0]["log_raw"] + expected_repro_target = checked_payload["target"] + "_with_debug" + self.assertIn( + f"bazel build {bazel_build_flags} {expected_repro_target}", + log_raw, + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/buildscripts/util/BUILD.bazel b/buildscripts/util/BUILD.bazel index a14f2f843d4..461206c60a7 100644 --- a/buildscripts/util/BUILD.bazel +++ b/buildscripts/util/BUILD.bazel @@ -25,6 +25,10 @@ py_library( "oauthlib", group = "testing", ), + dependency( + "pyyaml", + group = "core", + ), dependency( "structlog", group = "evergreen", diff --git a/evergreen/generate_symbol_check_report.py b/evergreen/generate_symbol_check_report.py index 1367026ad92..2996c69ee39 100644 --- a/evergreen/generate_symbol_check_report.py +++ b/evergreen/generate_symbol_check_report.py @@ -5,6 +5,23 @@ import sys from buildscripts.simple_report import make_report, put_report, try_combine_reports from buildscripts.util.read_config import read_config_file + +def _read_optional_text_file(path: str) -> str | None: + try: + with open(path) as f: + content = f.read().strip() + except OSError: + return None + return content or None + + +def _with_debug_target(target: str) -> str: + """Symbol-check aspect emits cc_library-like targets as *_with_debug.""" + if target.endswith("_with_debug"): + return target + return f"{target}_with_debug" + + # 1. detect if we should run symbol-check reporting expansions = read_config_file("../expansions.yml") symbol_check = expansions.get("run_for_symbol_check", None) @@ -14,6 +31,11 @@ if not symbol_check: failures = [] +# Prefer the exact Bazel flags/invocation emitted by the Evergreen Bazel compile script. +# This script is run from the "src" directory (see evergreen/run_python_script.sh). +bazel_build_flags = _read_optional_text_file(".bazel_build_flags") +bazel_build_invocation = _read_optional_text_file(".bazel_build_invocation") + # 2. walk bazel-bin for *_checked files emitted by the aspect for root, _, files in os.walk("bazel-bin"): for name in files: @@ -51,7 +73,11 @@ for root, _, files in os.walk("bazel-bin"): repro_target = target or sym_file or checked_path lines.append("") lines.append("To reproduce:") - lines.append(f" bazel build --config=symbol-checker {repro_target}") + repro_target = _with_debug_target(str(repro_target)) + if bazel_build_flags: + lines.append(f" bazel build {bazel_build_flags} {repro_target}") + else: + lines.append(f" bazel build --config=symbol-checker {repro_target}") content = "\n".join(lines) @@ -62,9 +88,17 @@ for root, _, files in os.walk("bazel-bin"): failures.append((synthetic_file, content)) # 3. write a helper invocation file -# adjust this to your actual symbol-check build config if you have one with open("bazel-invocation.txt", "w") as f: - f.write("bazel build --config=symbol-checker //src/...") + if bazel_build_invocation: + # Keep the exact command that CI ran (targets included). + f.write(bazel_build_invocation) + elif bazel_build_flags: + # Fall back to the exact flags from CI (best effort on targets). + f.write(f"bazel build {bazel_build_flags} //src/...") + else: + # Last-resort fallback. + f.write("bazel build --config=symbol-checker //src/...") + # 4. emit reports if failures: