From 45a46c476a88db40bd78c0d05b005e9832b1f9b3 Mon Sep 17 00:00:00 2001 From: Philip Stoev Date: Mon, 18 May 2026 13:46:41 +0300 Subject: [PATCH] SERVER-126405 Use the same UUID for multiple golden tests within a single resmoke.py run (#53607) GitOrigin-RevId: 36ff9d4cf4800638313ca4b8831eca9f7d2f8d72 --- bazel/test_wrapper.sh | 1 + buildscripts/OWNERS.yml | 5 +++- buildscripts/golden_test.py | 23 ++------------ buildscripts/resmokelib/run/BUILD.bazel | 1 + buildscripts/resmokelib/run/__init__.py | 40 +++++++++++++++++++++++++ buildscripts/util/BUILD.bazel | 1 + buildscripts/util/golden_test_config.py | 40 +++++++++++++++++++++++++ src/mongo/unittest/golden_test_base.cpp | 14 ++++++++- src/mongo/unittest/golden_test_base.h | 3 ++ 9 files changed, 105 insertions(+), 23 deletions(-) create mode 100644 buildscripts/util/golden_test_config.py diff --git a/bazel/test_wrapper.sh b/bazel/test_wrapper.sh index 870113c94ad..b7f698ea763 100755 --- a/bazel/test_wrapper.sh +++ b/bazel/test_wrapper.sh @@ -2,6 +2,7 @@ # Golden tests are currently incompatible with Bazel Remote Execution unset GOLDEN_TEST_CONFIG_PATH +unset GOLDEN_TEST_OUTPUT_ROOT_PATTERN is_ppc64le() { local -r arch="$(uname -m)" diff --git a/buildscripts/OWNERS.yml b/buildscripts/OWNERS.yml index ac71ae17471..1d95ee1adc4 100644 --- a/buildscripts/OWNERS.yml +++ b/buildscripts/OWNERS.yml @@ -40,7 +40,10 @@ filters: - 10gen/devprod-test-infrastructure - "golden_test.py": approvers: - - 10gen/query-optimization + - 10gen/query-optimization-correctness + - "util/golden_test_config.py": + approvers: + - 10gen/query-optimization-correctness - "evergreen_gen_streams*": approvers: - 10gen/streams-engine diff --git a/buildscripts/golden_test.py b/buildscripts/golden_test.py index ed103e88079..a62b53066db 100755 --- a/buildscripts/golden_test.py +++ b/buildscripts/golden_test.py @@ -21,7 +21,7 @@ import click if __name__ == "__main__" and __package__ is None: sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from buildscripts.util.fileops import read_yaml_file +from buildscripts.util.golden_test_config import GoldenTestConfig assert sys.version_info >= (3, 7) @@ -43,25 +43,6 @@ class AppError(Exception): pass -class GoldenTestConfig(object): - """Represents the golden test configuration. - - See: docs/golden_data_test_framework.md#appendix---config-file-reference - """ - - def __init__(self, iterable=(), **kwargs): - """Initialize the fields.""" - self.__dict__.update(iterable, **kwargs) - - outputRootPattern: str - diffCmd: str - - @classmethod - def from_yaml_file(cls, path: str) -> "GoldenTestConfig": - """Read the golden test configuration from the given file.""" - return cls(**read_yaml_file(path)) - - class OutputPaths(object): """Represents actual and expected output paths.""" @@ -250,7 +231,7 @@ class GoldenTestApp(object): def setup_linux(self): # Create config file - config_path = os.path.join(os.path.expanduser("~"), ".golden_test_config.yml") + config_path = GoldenTestConfig.default_config_path() if not os.path.isfile(config_path): print(f"Creating {config_path}") config_contents = ( diff --git a/buildscripts/resmokelib/run/BUILD.bazel b/buildscripts/resmokelib/run/BUILD.bazel index a08f60084f4..1d1231f2f78 100644 --- a/buildscripts/resmokelib/run/BUILD.bazel +++ b/buildscripts/resmokelib/run/BUILD.bazel @@ -11,6 +11,7 @@ py_library( visibility = ["//visibility:public"], deps = [ "//buildscripts/ciconfig", + "//buildscripts/util", dependency( "requests", group = "core", diff --git a/buildscripts/resmokelib/run/__init__.py b/buildscripts/resmokelib/run/__init__.py index a0072ac5016..2dec743a9bb 100644 --- a/buildscripts/resmokelib/run/__init__.py +++ b/buildscripts/resmokelib/run/__init__.py @@ -46,6 +46,11 @@ from buildscripts.resmokelib.testing.suite import Suite from buildscripts.resmokelib.utils import runtime_recorder from buildscripts.resmokelib.utils.dictionary import get_dict_value from buildscripts.util.download_utils import get_s3_client +from buildscripts.util.golden_test_config import ( + GOLDEN_TEST_CONFIG_PATH_ENV, + GOLDEN_TEST_OUTPUT_ROOT_PATTERN_ENV, + GoldenTestConfig, +) from buildscripts.util.teststats import HistoricTaskData _INTERNAL_OPTIONS_TITLE = "Internal Options" @@ -90,6 +95,39 @@ class TestRunner(Subcommand): "treating logs as incomplete" ) + def _setup_golden_test(self): + """Pre-calculate the golden test output root pattern so that all golden tests + end up in the same directory. + + Each jstest is executed in its own mongo shell subprocess. The C++ + golden test framework reads ``outputRootPattern`` from the YAML config + pointed to by ``GOLDEN_TEST_CONFIG_PATH`` and replaces each ``%`` in + the basename with a random hex digit. Because that substitution happens + once per process, each test would otherwise produce its own + UUID-suffixed output directory. + """ + if GOLDEN_TEST_OUTPUT_ROOT_PATTERN_ENV in os.environ: + return + + golden_test_cfg_path = os.environ.get(GOLDEN_TEST_CONFIG_PATH_ENV) + if not golden_test_cfg_path: + return + + golden_test_cfg = GoldenTestConfig.from_yaml_file(golden_test_cfg_path) + if not golden_test_cfg.outputRootPattern or "%" not in golden_test_cfg.outputRootPattern: + return + + # Substitute `%` placeholders only in the file name, mirroring `fs::unique_path(filename)` + # on the C++ side. + parent_dir, file_name = os.path.split(golden_test_cfg.outputRootPattern) + file_name_uuid = "".join( + random.choice("0123456789abcdef") if c == "%" else c for c in file_name + ) + + # Store the calculated value in the `GOLDEN_TEST_OUTPUT_ROOT_PATTERN` variable so + # that the C++ code can reference it. + os.environ[GOLDEN_TEST_OUTPUT_ROOT_PATTERN_ENV] = os.path.join(parent_dir, file_name_uuid) + def execute(self): """Execute the 'run' subcommand.""" @@ -203,6 +241,8 @@ class TestRunner(Subcommand): def run_tests(self): """Run the suite and tests specified.""" + self._setup_golden_test() + # This code path should only execute when resmoke is running from a workload container. if config.REQUIRES_WORKLOAD_CONTAINER_SETUP: self._setup_workload_container() diff --git a/buildscripts/util/BUILD.bazel b/buildscripts/util/BUILD.bazel index d067c9a0bdc..d385508aa6b 100644 --- a/buildscripts/util/BUILD.bazel +++ b/buildscripts/util/BUILD.bazel @@ -12,6 +12,7 @@ py_library( "expansions.py", "fileops.py", "generate_co_jira_map.py", + "golden_test_config.py", "oauth.py", "read_config.py", "runcommand.py", diff --git a/buildscripts/util/golden_test_config.py b/buildscripts/util/golden_test_config.py new file mode 100644 index 00000000000..4d1493a8f34 --- /dev/null +++ b/buildscripts/util/golden_test_config.py @@ -0,0 +1,40 @@ +"""Shared helpers for the golden data test framework configuration. + +Both ``buildscripts/golden_test.py`` (the user-facing diff/accept CLI) and +``buildscripts/resmokelib/run/__init__.py`` (resmoke.py's ``run`` subcommand) +need to read the YAML config pointed to by the ``GOLDEN_TEST_CONFIG_PATH`` +environment variable. This module centralises that handling. + +For details on the framework see ``docs/golden_data_test_framework.md``. +""" + +import os +from dataclasses import dataclass +from typing import Optional + +from buildscripts.util.fileops import read_yaml_file + +GOLDEN_TEST_CONFIG_PATH_ENV = "GOLDEN_TEST_CONFIG_PATH" +GOLDEN_TEST_OUTPUT_ROOT_PATTERN_ENV = "GOLDEN_TEST_OUTPUT_ROOT_PATTERN" + + +@dataclass +class GoldenTestConfig: + """Parsed contents of a golden test YAML config file.""" + + outputRootPattern: Optional[str] = None + diffCmd: Optional[str] = None + + @classmethod + def from_yaml_file(cls, path: str) -> "GoldenTestConfig": + """Read the golden test configuration from a YAML file.""" + data = read_yaml_file(path) or {} + return cls( + outputRootPattern=data.get("outputRootPattern"), + diffCmd=data.get("diffCmd"), + ) + + @staticmethod + def default_config_path() -> str: + """Return the default path for the golden test configuration file.""" + return os.path.join(os.path.expanduser("~"), ".golden_test_config.yml") diff --git a/src/mongo/unittest/golden_test_base.cpp b/src/mongo/unittest/golden_test_base.cpp index 301c673c097..33f0b70b756 100644 --- a/src/mongo/unittest/golden_test_base.cpp +++ b/src/mongo/unittest/golden_test_base.cpp @@ -231,9 +231,13 @@ GoldenTestOptions GoldenTestOptions::parseEnvironment() { GoldenTestOptions opts; po::options_description desc_env; boost::optional configPath; + boost::optional outputRootPatternOverride; desc_env.add_options() // ("config_path", - po::value()->notifier([&configPath](auto v) { configPath = v; })); + po::value()->notifier([&configPath](auto v) { configPath = v; })) // + ("output_root_pattern", + po::value()->notifier( + [&outputRootPatternOverride](auto v) { outputRootPatternOverride = v; })); po::variables_map vm_env; po::store(po::parse_environment(desc_env, "GOLDEN_TEST_"), vm_env); @@ -254,6 +258,14 @@ GoldenTestOptions GoldenTestOptions::parseEnvironment() { } } + // GOLDEN_TEST_OUTPUT_ROOT_PATTERN takes precedence over any value loaded from the YAML config. + // This allows resmoke.py to pre-resolve a single output root pattern and share it across all + // mongo shell subprocesses of one invocation, so every jstest writes its actual results under + // the same UUID-suffixed directory. + if (outputRootPatternOverride) { + opts.outputRootPattern = *outputRootPatternOverride; + } + return opts; } diff --git a/src/mongo/unittest/golden_test_base.h b/src/mongo/unittest/golden_test_base.h index eecf3f220ed..e773b31602a 100644 --- a/src/mongo/unittest/golden_test_base.h +++ b/src/mongo/unittest/golden_test_base.h @@ -265,6 +265,9 @@ struct GoldenTestOptions { * - GOLDEN_TEST_CONFIG_PATH: (optional) specifies the yaml config file. * See config file reference: * docs/golden_data_test_framework.md#appendix---config-file-reference + * - GOLDEN_TEST_OUTPUT_ROOT_PATTERN: (optional) overrides the `outputRootPattern` value + * loaded from the yaml config. Intended to let resmoke.py pre-resolve a single output + * root and share it across all golden tests in one invocation. */ static GoldenTestOptions parseEnvironment();