|
1 | 1 | # Session log: executing the #130 plan |
2 | 2 |
|
3 | | -**Date:** 2026-08-17 · **Branch:** `fix/130-pytest-config` · **Base:** `master` @ `a9393b7` |
4 | | -**Plan followed:** `notes/handoff_130_pytest_config.md` |
| 3 | +**Date:** 2026-08-17 · **Merged:** PR #134 → `760432c` on `master` · **Issue #130: closed** |
| 4 | +**Base:** `master` @ `a9393b7` · **Plan followed:** `notes/handoff_130_pytest_config.md` |
| 5 | + |
| 6 | +> Read `HANDOFF.md` first if you are a fresh session. This file is the detailed |
| 7 | +> record; `HANDOFF.md` is the entry point. |
5 | 8 |
|
6 | 9 | Every command below was run in a throwaway venv, since the repo has no usable |
7 | 10 | one by default (#110): |
@@ -60,20 +63,34 @@ So the two fixes are each independently sufficient, and the test guards the |
60 | 63 | *combination*. Both are kept as belt and braces, and the table is recorded in |
61 | 64 | the test's docstring. |
62 | 65 |
|
| 66 | +> **Superseded — see the red-team section below.** That table is about one |
| 67 | +> property only (does bare `pytest` run?). It is true as far as it goes and |
| 68 | +> badly misleading as a conclusion: `invocation_params.args` also opens real |
| 69 | +> money-safety bypasses, so the two options were never equivalent. The guard |
| 70 | +> reads `config.args` on `master` today. |
| 71 | +
|
63 | 72 | **`--strict-markers` needs care with `-o addopts=`.** It reaches pytest through |
64 | 73 | `addopts`, so `test_strict_markers_is_active` skips when |
65 | 74 | `config.option.override_ini` contains an `addopts=` entry. Verified against |
66 | 75 | pytest 8.4.2: that attribute holds `['addopts=']`. |
67 | 76 |
|
68 | | -**`pre_push_check.py` ran a bare `pytest`.** Harmless only while no config was |
69 | | -effective. Once `testpaths` went live it resolved to all 1670 tests including |
70 | | -the 224 network-bound ones in `tests/real_world/`, and stopped terminating. It |
| 77 | +**`pre_push_check.py` ran a bare `pytest`.** (Two figures in this paragraph |
| 78 | +were wrong when first written and are corrected here: it was never "harmless", |
| 79 | +and `tests/real_world/` holds 388 tests, not 224 — that was a count of |
| 80 | +decorator lines.) On `master` the bare `pytest` aborted in seconds on the |
| 81 | +f-string SyntaxError and ran **zero** tests, so the gate was permanently red |
| 82 | +and checked nothing. Fixing that error let collection succeed, at which point |
| 83 | +the bare `pytest` resolved to all 1670 tests including the 388 network-bound |
| 84 | +ones in `tests/real_world/`, and stopped terminating. It |
71 | 85 | also left artifacts behind: it modified checked-in files under |
72 | 86 | `tests/real_world/screenshots/` and created an untracked |
73 | 87 | `performance_test_results/`. Now runs `pytest tests/unit/ -m "not real_world"`, |
74 | 88 | matching `.github/workflows/tests.yml`; the generated paths are gitignored. |
75 | 89 |
|
76 | | -## Definition of done — measured |
| 90 | +## Definition of done — measured *at the time the PR was opened* |
| 91 | + |
| 92 | +Superseded by the "Final verified state" block at the end of this file; the |
| 93 | +counts moved once the red-team fixes added tests. Kept for the audit trail. |
77 | 94 |
|
78 | 95 | ``` |
79 | 96 | configfile pyproject.toml |
@@ -110,3 +127,104 @@ https://github.com/ContextLab/clustrix/issues/110#issuecomment-5317453905 |
110 | 127 |
|
111 | 128 | **#117** untouched, as instructed — `tests/real_world/conftest.py:223` still |
112 | 129 | calls `is_dartmouth_network()` at collection time. |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +# Red-team pass (after the PR was opened) |
| 134 | + |
| 135 | +Four parallel subagents were pointed at the branch: guard bypasses, config |
| 136 | +regressions, the new tests, and PR-claim honesty. **Two of the bugs they found |
| 137 | +were mine, introduced by this PR.** Everything below was fixed before merge. |
| 138 | + |
| 139 | +## 1. Step 1 of the plan was actively unsafe (the important one) |
| 140 | + |
| 141 | +Switching the #109 guard to `config.invocation_params.args` unblocked bare |
| 142 | +`pytest`, but `invocation_params.args` is only *what the operator typed*. |
| 143 | +`testpaths`, `-o testpaths=` and `PYTEST_ADDOPTS` all feed `config.args` |
| 144 | +without ever appearing there. Verified against `master`, which **refused** both: |
| 145 | + |
| 146 | +```bash |
| 147 | +PYTEST_ADDOPTS=tests/integration/test_timeout_mechanism.py pytest --co # branch: 2 collected |
| 148 | +pytest --co -o testpaths=tests/integration/test_timeout_mechanism.py # branch: 2 collected |
| 149 | +``` |
| 150 | + |
| 151 | +**Resolution: the fix belonged in the config, not the guard.** The guard reads |
| 152 | +`config.args` again — safe *because* `testpaths = ["tests"]` landed in Step 2. |
| 153 | + |
| 154 | +Also fixed while in there (both pre-existing #109 holes): |
| 155 | + |
| 156 | +- relative paths were resolved only against `rootdir`, so from inside `tests/` |
| 157 | + the path `integration/test_x.py` was unrecognised. All plausible bases are |
| 158 | + tried now. |
| 159 | +- `--pyargs` / `-p` address modules by dotted name and never looked like paths; |
| 160 | + both are translated and checked. |
| 161 | + |
| 162 | +Final state, measured: |
| 163 | + |
| 164 | +| invocation | result | |
| 165 | +|-|-| |
| 166 | +| explicit dir / file / node id | refused | |
| 167 | +| `-o testpaths=` | refused | |
| 168 | +| `PYTEST_ADDOPTS=` | refused | |
| 169 | +| `--pyargs <dotted>` | refused | |
| 170 | +| cwd-relative from `tests/` | refused | |
| 171 | +| bare `pytest` | allowed, **0** integration nodes | |
| 172 | +| `CLUSTRIX_ALLOW_BILLABLE=1` | works | |
| 173 | + |
| 174 | +`-p <dotted>` cannot be blocked *before* the import it triggers — no conftest |
| 175 | +hook runs that early — but the run is refused before any test executes, which |
| 176 | +is where the cost is. It also fails on its own here: `tests/` is not a package. |
| 177 | + |
| 178 | +Covered by `test_indirect_targeting_of_integration_is_refused` (4 params). |
| 179 | +Three of the four fail against the old guard, so they are not decorative. |
| 180 | + |
| 181 | +## 2. Claims of mine that were false |
| 182 | + |
| 183 | +- **"the 224 tests in tests/real_world/"** — wrong, and I had committed it as a |
| 184 | + code comment. 224 counts `@pytest.mark.real_world` *decorator lines*; the |
| 185 | + directory collects **388** tests. |
| 186 | +- **The `pre_push_check.py` rationale** — I wrote that bare `pytest` was |
| 187 | + "harmless while no config applied." It was not: on `master` it aborted in |
| 188 | + seconds on the f-string SyntaxError and ran **zero** tests. Fixing that error |
| 189 | + is what made the runtime problem appear. |
| 190 | +- **`test_no_shadowing_config_file_exists` forbade `tox.ini`/`setup.cfg`** — |
| 191 | + pytest checks both *after* `pyproject.toml` (`_pytest/config/findpaths.py`), |
| 192 | + so neither can shadow it, and the test failed on an ordinary pytest-free |
| 193 | + `setup.cfg` holding flake8 config. Now limited to `pytest.ini`/`.pytest.ini`. |
| 194 | +- **`coverage_detailed_report.txt` in `.gitignore`** — nothing in the repo |
| 195 | + writes it. Entry removed. |
| 196 | +- **"18/18 CI checks passing"** — incomplete. `gh pr checks` omits the Fast CI |
| 197 | + workflow, which was running and failing. |
| 198 | + |
| 199 | +One agent claim was **rejected**: it reported the fresh-venv result as |
| 200 | +unsubstantiated, having inspected `/tmp/v130` (which also has `[test]`). |
| 201 | +`/tmp/fresh130` is genuinely `[dev]`-only. Do not take agent findings on |
| 202 | +trust — two of the four needed verification before acting. |
| 203 | + |
| 204 | +## 3. `fast_ci.yml` — my fix was reverted, diagnosis was wrong |
| 205 | + |
| 206 | +I added `pytest-timeout` and claimed it fixed a job "failing on every run." |
| 207 | +It does not. The workflow is **invalid YAML and has never run a single job** |
| 208 | +— every run, on `master` too, is `conclusion: failure` with `jobs: 0`. |
| 209 | + |
| 210 | +``` |
| 211 | +yaml.scanner.ScannerError: while scanning a simple key |
| 212 | + in ".github/workflows/fast_ci.yml", line 89, column 1 |
| 213 | +``` |
| 214 | + |
| 215 | +Three steps open `python -c "` inside a `run: |` block and write the Python |
| 216 | +body at column 0, ending the block scalar. Reverted to match `master` exactly |
| 217 | +and filed as **#135**; the missing `pytest-timeout` is real but latent. |
| 218 | + |
| 219 | +## Final verified state on `master` @ `760432c` |
| 220 | + |
| 221 | +``` |
| 222 | +configfile pyproject.toml |
| 223 | +bare pytest 1674 collected, 0 integration nodes |
| 224 | +collection errors 0 |
| 225 | +markers registered 7 / 7 |
| 226 | +tests/unit/ 79 passed |
| 227 | +mypy clean |
| 228 | +flake8 91 (master before: 92) -- see #133 |
| 229 | +CI 15 test jobs + docs + integration pass |
| 230 | +``` |
0 commit comments