|
1 | | -"""Shared pytest configuration for the ShapePipe test suite.""" |
| 1 | +"""Shared pytest configuration for the ShapePipe test suite. |
| 2 | +
|
| 3 | +Markers, environment detection, and the candide skip policy live here so |
| 4 | +every test module — wherever it sits in the tree — sees the same rules. |
| 5 | +
|
| 6 | +Markers (also declared in ``pyproject.toml`` so ``--strict-markers`` is on): |
| 7 | +
|
| 8 | +* ``slow`` — heavy compute (minutes), not part of the fast inner loop. |
| 9 | +* ``candide`` — needs the candide cluster and/or its real on-disk data; |
| 10 | + meaningless (and auto-skipped) anywhere else. |
| 11 | +
|
| 12 | +A ``candide``-marked test only runs on a candide node. Everywhere else it |
| 13 | +is skipped with a clear reason, so the same suite is green on a laptop, in |
| 14 | +CI, and on the cluster — the cluster-only tests simply do not fire off it. |
| 15 | +""" |
2 | 16 |
|
3 | 17 | import os |
| 18 | +import re |
| 19 | +import socket |
| 20 | + |
| 21 | +import pytest |
4 | 22 |
|
5 | 23 | from hypothesis import settings |
6 | 24 |
|
7 | 25 |
|
8 | 26 | settings.register_profile("ci", derandomize=True, max_examples=50) |
9 | 27 | settings.register_profile("dev", max_examples=200) |
10 | 28 | settings.load_profile(os.environ.get("HYPOTHESIS_PROFILE", "ci")) |
| 29 | + |
| 30 | + |
| 31 | +# --------------------------------------------------------------------------- # |
| 32 | +# Candide detection |
| 33 | +# --------------------------------------------------------------------------- # |
| 34 | + |
| 35 | +# Candide compute / login nodes are named c01, c03, n22..n36, etc. The login |
| 36 | +# host this suite is most often driven from is ``c03``. We match the candide |
| 37 | +# node-name families rather than a fixed list so new nodes are covered, and |
| 38 | +# allow an explicit override for CI or odd hostnames. |
| 39 | +_CANDIDE_HOST_RE = re.compile(r"^(c\d|n\d{2})", re.IGNORECASE) |
| 40 | + |
| 41 | + |
| 42 | +def on_candide(): |
| 43 | + """Return True when running on a candide node. |
| 44 | +
|
| 45 | + The check is, in order: an explicit ``SHAPEPIPE_ON_CANDIDE`` override |
| 46 | + (``1``/``0``), then the hostname against the candide node-name families |
| 47 | + (``c0x`` login, ``nXX`` compute). Cheap, import-safe, no cluster calls. |
| 48 | + """ |
| 49 | + override = os.environ.get("SHAPEPIPE_ON_CANDIDE") |
| 50 | + if override is not None: |
| 51 | + return override == "1" |
| 52 | + return bool(_CANDIDE_HOST_RE.match(socket.gethostname())) |
| 53 | + |
| 54 | + |
| 55 | +# --------------------------------------------------------------------------- # |
| 56 | +# Marker registration + skip policy |
| 57 | +# --------------------------------------------------------------------------- # |
| 58 | + |
| 59 | + |
| 60 | +def pytest_configure(config): |
| 61 | + config.addinivalue_line( |
| 62 | + "markers", |
| 63 | + "slow: heavy compute (minutes); excluded from the fast inner loop.", |
| 64 | + ) |
| 65 | + config.addinivalue_line( |
| 66 | + "markers", |
| 67 | + "candide: needs the candide cluster and/or its real data; " |
| 68 | + "auto-skipped elsewhere.", |
| 69 | + ) |
| 70 | + |
| 71 | + |
| 72 | +def pytest_collection_modifyitems(config, items): |
| 73 | + """Skip ``candide`` tests off-cluster. |
| 74 | +
|
| 75 | + Collection still happens everywhere — so ``pytest --collect-only`` shows |
| 76 | + the cluster tests exist — they are just marked skipped at run time when |
| 77 | + not on candide. |
| 78 | + """ |
| 79 | + if on_candide(): |
| 80 | + return |
| 81 | + skip_candide = pytest.mark.skip( |
| 82 | + reason="needs candide (set SHAPEPIPE_ON_CANDIDE=1 to force)" |
| 83 | + ) |
| 84 | + for item in items: |
| 85 | + if "candide" in item.keywords: |
| 86 | + item.add_marker(skip_candide) |
| 87 | + |
| 88 | + |
| 89 | +# --------------------------------------------------------------------------- # |
| 90 | +# Shared fixtures |
| 91 | +# --------------------------------------------------------------------------- # |
| 92 | + |
| 93 | + |
| 94 | +@pytest.fixture(scope="session") |
| 95 | +def artifacts_dir(): |
| 96 | + """Directory where guardrail tests drop plots + status summaries. |
| 97 | +
|
| 98 | + The artifacts SEAM: a later GitHub Pages / status step publishes from |
| 99 | + here. Created on demand so a clean checkout has nothing to commit until |
| 100 | + a test actually emits. |
| 101 | + """ |
| 102 | + from pathlib import Path |
| 103 | + |
| 104 | + path = Path(__file__).parent / "tests" / "_artifacts" |
| 105 | + path.mkdir(parents=True, exist_ok=True) |
| 106 | + return path |
0 commit comments