Skip to content

Commit 6c893d3

Browse files
fix(benchmarks): pin deterministic pytest-randomly seed for host shards (#625)
Benchmark host-validation shards ran without a pinned pytest-randomly seed, so each CI runner used a different random collection order. pytest-split's LeastDurationAlgorithm partitions the differently ordered collections independently, assigning the same test to multiple groups and producing 58 duplicate timing entries across four shards. Wire the existing pytest_randomly_shard_seed from .github/ci-config.json (the same SSOT used by the CI workflow for domain/composition shards) through pytest_arguments() in host_validation.py, injecting --randomly-seed into every shard's pytest command vector. This ensures all shards see an identical collection order, so pytest-split produces disjoint groups. The defensive merge behavior from #624 remains intact as a second line of defense. Generated with [Devin](https://devin.ai) Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent a57ca82 commit 6c893d3

2 files changed

Lines changed: 54 additions & 0 deletions

File tree

benchmarks/tooling/host_validation.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
ROOT = Path(__file__).resolve().parents[2]
2727
TIMING_PATH = ROOT / ".ci" / "benchmark-test-durations.json"
28+
_CI_CONFIG = ROOT / ".github" / "ci-config.json"
2829
_DIGEST = re.compile(r"sha256:[0-9a-f]{64}\Z")
2930
_SHA = re.compile(r"[0-9a-f]{40,64}\Z")
3031
_HOST_NAME = re.compile(r"[A-Za-z0-9_.-]+\Z")
@@ -33,6 +34,16 @@
3334
_MAX_TIMING_ENTRIES = 10_000
3435

3536

37+
def _shard_seed() -> int:
38+
"""Return the pinned pytest-randomly seed from ci-config.json."""
39+
40+
payload = json.loads(_CI_CONFIG.read_text(encoding="utf-8"))
41+
seed = payload.get("pytest_randomly_shard_seed")
42+
if not isinstance(seed, int) or isinstance(seed, bool) or seed < 0:
43+
raise ValueError("pytest_randomly_shard_seed must be a nonnegative integer")
44+
return seed
45+
46+
3647
@dataclass(frozen=True, slots=True)
3748
class ExecutionProvenance:
3849
"""Immutable identities shared by every shard in one host-validation run."""
@@ -295,6 +306,7 @@ def pytest_arguments(
295306
arguments = ["-n", str(workers), "--durations=10", entry.selector]
296307
if entry.keyword:
297308
arguments.extend(("-k", entry.keyword))
309+
arguments.extend(("--randomly-seed", str(_shard_seed())))
298310
if entry.splits:
299311
arguments.extend(
300312
(

benchmarks/validation/test_host_validation.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,48 @@ def test_full_shard_uses_ci_timing_shape_and_least_duration(tmp_path: Path) -> N
7575
assert "--store-durations" not in arguments
7676

7777

78+
def test_all_shards_receive_same_deterministic_seed(tmp_path: Path) -> None:
79+
"""Every full-host shard must get the same pytest-randomly seed so that
80+
pytest-split sees an identical collection order across CI runners."""
81+
82+
entries = full_host_validation()
83+
seeds: set[str] = set()
84+
for entry in entries:
85+
arguments = pytest_arguments(
86+
entry,
87+
timing_path=tmp_path / "timings.json",
88+
workers=2,
89+
store_durations=False,
90+
)
91+
seed_index = arguments.index("--randomly-seed")
92+
seeds.add(arguments[seed_index + 1])
93+
assert len(seeds) == 1
94+
assert seeds.pop() == "0"
95+
96+
97+
def test_keyword_filtered_entry_also_receives_deterministic_seed(
98+
tmp_path: Path,
99+
) -> None:
100+
from benchmarks.tooling.validation_plan import task_host_validation
101+
102+
entries = task_host_validation(
103+
Path(__file__).resolve().parents[2],
104+
"mathematical-benchmarks-v1",
105+
"algebraic-independence-transfer-audit",
106+
)
107+
assert entries
108+
for entry in entries:
109+
arguments = pytest_arguments(
110+
entry,
111+
timing_path=tmp_path / "timings.json",
112+
workers=1,
113+
store_durations=False,
114+
)
115+
assert "--randomly-seed" in arguments
116+
seed_index = arguments.index("--randomly-seed")
117+
assert arguments[seed_index + 1] == "0"
118+
119+
78120
def test_local_full_run_uses_empty_timing_fallback_and_writes_bound_receipts(
79121
tmp_path: Path,
80122
) -> None:

0 commit comments

Comments
 (0)