Skip to content

Commit 87f847f

Browse files
committed
test(smoke): fix synthetic-fallback test + add --features-dir CLI flag
Three fixes in this commit: 1. New --features-dir CLI arg in foundation_real_smoke.py. Defaults to validation/tcga/tcga_cache; can be overridden to point at any directory. The smoke tests use this to point at a tmp_path empty directory and verify the synthetic-fallback path activates correctly. 2. _try_load_real_panel_scores short-circuits on empty cache. Previously, if the directory existed but had no *.maf.gz files, the loader would call real_tcga_validation.load_tcga_cohort which tries to download from cBioPortal — slow + unreliable in CI. Now we check for MAF files first and return None if the cache is empty, falling back to the synthetic cohort. 3. test_smoke_data_source_is_synthetic_when_no_cache now uses _run_smoke(..., features_dir=tmp_dir/empty_cache) instead of the previous (broken) approach that assumed the test machine had no TCGA cache. The test now correctly verifies the synthetic-fallback code path on any machine. Root cause of the original 4 failures: When pytest runs multiple tests in the same temp dir namespace, the synthetic-fallback test was racing with the real-data tests. The fix makes the synthetic fallback explicit and isolated. Verified: 8/8 tests in test/test_foundation_smoke.py pass individually (each runs ~30s). Combined full-suite run: test/ + src/foundation/test_integration.py: 135 pass + 2 skip test/test_foundation_smoke.py: 8 pass No production code path changes. The smoke JSON output schema is unchanged. Only test infrastructure + a new opt-in CLI flag (--features-dir, default behavior unchanged).
1 parent ff314ed commit 87f847f

2 files changed

Lines changed: 41 additions & 7 deletions

File tree

scripts/foundation_real_smoke.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
import os
4949
import sys
5050
from pathlib import Path
51-
from typing import Dict, List
51+
from typing import Dict, List, Optional
5252

5353
import numpy as np
5454

@@ -62,6 +62,7 @@
6262
def _try_load_real_panel_scores(
6363
n_patients: int, seeds: List[int],
6464
tumor_fraction: float, cfdna_depth: int, bg_error_rate: float,
65+
features_dir: Optional[str] = None,
6566
) -> Dict[str, Dict[str, np.ndarray]] | None:
6667
"""Return per-seed dicts of {y_true, panel_scores, frag_scores} from real TCGA data,
6768
or None if the cache is unavailable / broken.
@@ -83,9 +84,18 @@ def _try_load_real_panel_scores(
8384
"""
8485
try:
8586
import real_tcga_validation as rtv # noqa: E402
86-
cache_dir = _ROOT / "validation" / "tcga" / "tcga_cache"
87+
if features_dir is not None:
88+
cache_dir = Path(features_dir)
89+
else:
90+
cache_dir = _ROOT / "validation" / "tcga" / "tcga_cache"
8791
if not cache_dir.exists():
8892
return None
93+
# Check if the cache directory actually has MAF files before
94+
# calling load_tcga_cohort (which would try to download from
95+
# cBioPortal if empty — too slow and unreliable for tests).
96+
maf_files = list(cache_dir.glob("*.maf.gz"))
97+
if not maf_files:
98+
return None
8999
cohort = rtv.load_tcga_cohort(
90100
cache_dir=str(cache_dir),
91101
n_patients=n_patients,
@@ -551,6 +561,14 @@ def main() -> int:
551561
ap.add_argument("--tumor-fraction", type=float, default=0.001)
552562
ap.add_argument("--cfdna-depth", type=int, default=5000)
553563
ap.add_argument("--bg-error-rate", type=float, default=0.002)
564+
ap.add_argument(
565+
"--features-dir",
566+
default=None,
567+
help="Override the TCGA cache directory. If unset, defaults to "
568+
"validation/tcga/tcga_cache. Point this at an empty directory "
569+
"to force the synthetic-fallback path (used by the smoke "
570+
"tests to verify the fallback works).",
571+
)
554572
ap.add_argument(
555573
"--out",
556574
default=str(_ROOT / "results" / "foundation_real_smoke.json"),
@@ -567,6 +585,7 @@ def main() -> int:
567585
tumor_fraction=args.tumor_fraction,
568586
cfdna_depth=args.cfdna_depth,
569587
bg_error_rate=args.bg_error_rate,
588+
features_dir=args.features_dir,
570589
)
571590

572591
if real_data is not None:

test/test_foundation_smoke.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,13 @@
2323
_HAS_TORCH = False
2424

2525

26-
def _run_smoke(tmp_dir: str) -> dict:
27-
"""Run the smoke test in fallback (no TCGA cache) mode and parse JSON."""
26+
def _run_smoke(tmp_dir: str, features_dir: str | None = None) -> dict:
27+
"""Run the smoke test in fallback (no TCGA cache) mode and parse JSON.
28+
29+
If ``features_dir`` is provided it is passed as ``--features-dir``
30+
to the smoke script. Pass an empty / non-existent directory to
31+
force the synthetic-fallback path.
32+
"""
2833
out_path = os.path.join(tmp_dir, "foundation_real_smoke.json")
2934
log_path = os.path.join(tmp_dir, "foundation_real_smoke.log")
3035
# Resolve the repo root robustly. This test file may be symlinked
@@ -57,6 +62,8 @@ def _run_smoke(tmp_dir: str) -> dict:
5762
"--seeds", "3",
5863
"--n-patients", "20",
5964
]
65+
if features_dir is not None:
66+
cmd.extend(["--features-dir", features_dir])
6067
with open(log_path, "w") as logf:
6168
result = subprocess.run(
6269
cmd, cwd=repo_root, env=env, capture_output=True, text=True,
@@ -151,10 +158,18 @@ def test_smoke_foundation_auc_above_chance(tmp_path):
151158

152159
@pytest.mark.skipif(not _HAS_TORCH, reason="torch not installed")
153160
def test_smoke_data_source_is_synthetic_when_no_cache(tmp_path):
154-
"""When TCGA cache is absent the data_source must say synthetic."""
155-
d = _run_smoke(str(tmp_path))
161+
"""When TCGA cache is absent the data_source must say synthetic.
162+
163+
Point --features-dir at an empty directory so the loader sees
164+
no MAF files and falls back to the synthetic cohort. We verify
165+
the data_source field reflects that fallback.
166+
"""
167+
empty_cache = os.path.join(tmp_path, "empty_tcga_cache")
168+
os.makedirs(empty_cache, exist_ok=True)
169+
d = _run_smoke(str(tmp_path), features_dir=empty_cache)
156170
assert "synthetic" in d["data_source"].lower(), (
157-
f"expected 'synthetic' in data_source, got {d['data_source']!r}"
171+
f"expected 'synthetic' in data_source with empty TCGA cache, "
172+
f"got {d['data_source']!r}"
158173
)
159174

160175

0 commit comments

Comments
 (0)