Skip to content

Commit 717c211

Browse files
authored
Merge pull request #400 from CCPBioSim/398-bug-regression-tests-intermittently-fail
Improve Regression Test Reproducibility in CI
2 parents 86b9529 + 746db3b commit 717c211

8 files changed

Lines changed: 35 additions & 50 deletions

File tree

‎.github/workflows/pr.yaml‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,7 @@ jobs:
9393
run: |
9494
python -m pytest tests/regression \
9595
-m "not slow" \
96-
-n auto \
97-
--dist=loadscope \
98-
-k "${{ matrix.system }}" \
96+
--system "${{ matrix.system }}" \
9997
-vv \
10098
--durations=20
10199

‎.github/workflows/weekly-regression.yaml‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,9 @@ jobs:
6464
6565
- name: Run regression tests (slow, per system)
6666
run: |
67-
pytest tests/regression \
68-
-k "${{ matrix.system }}" \
67+
python -m pytest tests/regression \
68+
--system "${{ matrix.system }}" \
6969
--run-slow \
70-
-n auto \
71-
--dist=loadscope \
7270
-vv \
7371
--durations=20
7472

‎CodeEntropy/levels/nodes/accumulators.py‎

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,17 +86,24 @@ def run(self, shared_data: dict[str, Any]) -> dict[str, Any]:
8686

8787
@staticmethod
8888
def _build_group_index(groups: dict[int, Any]) -> GroupIndex:
89-
"""Build group id <-> index mappings.
89+
"""Build deterministic mappings between group IDs and contiguous indices.
90+
91+
Group IDs are sorted before indices are assigned, ensuring that the
92+
resulting mappings do not depend on dictionary insertion order.
9093
9194
Args:
92-
groups: Mapping of group id to group members.
95+
groups: Mapping of group IDs to their members.
9396
9497
Returns:
95-
GroupIndex mapping object.
98+
Bidirectional mappings between group IDs and contiguous indices.
9699
"""
97-
group_ids = list(groups.keys())
98-
gid2i = {gid: i for i, gid in enumerate(group_ids)}
99-
return GroupIndex(group_id_to_index=gid2i, index_to_group_id=list(group_ids))
100+
group_ids = sorted(groups)
101+
gid2i = {gid: index for index, gid in enumerate(group_ids)}
102+
103+
return GroupIndex(
104+
group_id_to_index=gid2i,
105+
index_to_group_id=group_ids,
106+
)
100107

101108
@staticmethod
102109
def _build_accumulators(n_groups: int) -> CovarianceAccumulators:

‎conda-recipe/meta.yaml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ requirements:
3838
- dask >=2026.1.2,<2026.2.0
3939
- distributed >=2026.1.2,<2026.2.0
4040
- dask-jobqueue >=0.9,<0.10
41-
- pytest-xdist >=3.8, <3.9
4241
- numba >=0.65.1, <0.70
4342

4443
test:

‎pyproject.toml‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ testing = [
6464
"pytest>=9.0,<10.0",
6565
"pytest-cov>=7.0,<8.0",
6666
"pytest-sugar>=1.1,<2.0",
67-
"pytest-xdist"
6867
]
6968
pre-commit = [
7069
"pre-commit>=4.5,<5.0",
@@ -89,7 +88,7 @@ CodeEntropy = "CodeEntropy.cli:main"
8988
[tool.pytest.ini_options]
9089
testpaths = ["tests"]
9190

92-
addopts = "-n auto -vv -ra"
91+
addopts = "-vv -ra"
9392

9493
markers = [
9594
"regression: end-to-end regression tests against baselines",

‎tests/regression/conftest.py‎

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
from __future__ import annotations
22

3-
import os
4-
import random
5-
6-
import numpy as np
73
import pytest
84

95

@@ -39,17 +35,11 @@ def pytest_addoption(parser: pytest.Parser) -> None:
3935

4036
def pytest_configure(config: pytest.Config) -> None:
4137
"""
42-
Register markers and enforce deterministic behavior.
38+
Register regression test markers.
4339
"""
4440
config.addinivalue_line("markers", "regression: end-to-end regression tests")
4541
config.addinivalue_line("markers", "slow: long-running tests (20-30+ minutes)")
4642

47-
seed = 0
48-
random.seed(seed)
49-
np.random.seed(seed)
50-
51-
os.environ["PYTHONHASHSEED"] = "0"
52-
5343

5444
def pytest_collection_modifyitems(
5545
config: pytest.Config, items: list[pytest.Item]

‎tests/regression/helpers.py‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import json
44
import os
55
import subprocess
6+
import sys
67
import tarfile
78
import urllib.request
89
from dataclasses import dataclass
@@ -366,12 +367,25 @@ def run_codeentropy_with_config(*, workdir: Path, config_src: Path) -> RunResult
366367

367368
(workdir / "config.yaml").write_text(yaml.safe_dump(cooked, sort_keys=False))
368369

370+
env = os.environ.copy()
371+
env.update(
372+
{
373+
"PYTHONHASHSEED": "0",
374+
"OMP_NUM_THREADS": "1",
375+
"OPENBLAS_NUM_THREADS": "1",
376+
"MKL_NUM_THREADS": "1",
377+
"NUMEXPR_NUM_THREADS": "1",
378+
"BLIS_NUM_THREADS": "1",
379+
"VECLIB_MAXIMUM_THREADS": "1",
380+
}
381+
)
382+
369383
proc = subprocess.run(
370-
["python", "-m", "CodeEntropy"],
384+
[sys.executable, "-m", "CodeEntropy"],
371385
cwd=str(workdir),
372386
capture_output=True,
373387
text=True,
374-
env={**os.environ},
388+
env=env,
375389
)
376390

377391
(workdir / "codeentropy_stdout.txt").write_text(proc.stdout or "")

‎tests/regression/test_regression.py‎

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,6 @@
1010
from tests.regression.cases import discover_cases
1111
from tests.regression.helpers import run_codeentropy_with_config
1212

13-
CASES = discover_cases()
14-
15-
16-
def pytest_generate_tests(metafunc: pytest.Metafunc) -> None:
17-
"""
18-
Dynamically parametrize regression test cases.
19-
20-
This hook enables pytest-xdist to distribute individual regression cases
21-
across multiple workers by generating test parametrization at collection time.
22-
23-
Args:
24-
metafunc (pytest.Metafunc): Pytest metafunction object used to inspect
25-
and modify test function parametrization.
26-
"""
27-
if "case" in metafunc.fixturenames:
28-
metafunc.parametrize(
29-
"case",
30-
CASES,
31-
ids=[c.id for c in CASES],
32-
)
33-
3413

3514
def _group_index(payload: dict[str, Any]) -> dict[str, dict[str, Any]]:
3615
"""
@@ -139,6 +118,7 @@ def _compare_grouped(
139118

140119

141120
@pytest.mark.regression
121+
@pytest.mark.parametrize("case", discover_cases())
142122
def test_regression_matches_baseline(
143123
tmp_path: Path,
144124
case,

0 commit comments

Comments
 (0)