SERVER-126405 Use the same UUID for multiple golden tests within a single resmoke.py run (#53607)
GitOrigin-RevId: 36ff9d4cf4800638313ca4b8831eca9f7d2f8d72
This commit is contained in:
parent
864f5f4f40
commit
45a46c476a
@ -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)"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 = (
|
||||
|
||||
@ -11,6 +11,7 @@ py_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//buildscripts/ciconfig",
|
||||
"//buildscripts/util",
|
||||
dependency(
|
||||
"requests",
|
||||
group = "core",
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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",
|
||||
|
||||
40
buildscripts/util/golden_test_config.py
Normal file
40
buildscripts/util/golden_test_config.py
Normal file
@ -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")
|
||||
@ -231,9 +231,13 @@ GoldenTestOptions GoldenTestOptions::parseEnvironment() {
|
||||
GoldenTestOptions opts;
|
||||
po::options_description desc_env;
|
||||
boost::optional<std::string> configPath;
|
||||
boost::optional<std::string> outputRootPatternOverride;
|
||||
desc_env.add_options() //
|
||||
("config_path",
|
||||
po::value<std::string>()->notifier([&configPath](auto v) { configPath = v; }));
|
||||
po::value<std::string>()->notifier([&configPath](auto v) { configPath = v; })) //
|
||||
("output_root_pattern",
|
||||
po::value<std::string>()->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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user