Skip to content

Commit afbcc91

Browse files
tests: the deep dive's figure refreshes itself on request (#119)
The guard is right and stays strict: a number on the **Verified this pass** line is re-derived by a test, or it does not belong on the line. What was wrong was who paid for it. Every branch that adds or removes a test moves the count, so this file failed that branch until someone hand-edited a figure in a docs file they had no reason to know existed. PR #115 is what that costs. An outside contributor's first change -- four tests, the fan-out example #51 asked for -- sat red for a month on `assert 2151 == 2155`, and nothing in the failure named a fix they could run. The check did its job; the ergonomics did not. `GRAPHARC_UPDATE_FIGURES=1 pytest tests/test_deep_dive.py` now rewrites the line and skips the check that wrote it, so the next run is the one that verifies. The failure message names that command, and CONTRIBUTING.md has a section for the case, because a contributor reads the failure and the contributing guide, not this file's docstring. Strictness is unchanged where it counts. `_updating()` is false for unset, empty, "0", "false" and "no", so a leftover `=0` in a shell profile cannot disarm the guard, and CI sets nothing -- a stale figure still fails there. That property has its own test, because a self-healing check in CI would assert nothing at all. The rewrite is a pure function over the line, tested without touching the real document: only the capture group's span changes, so the comma grouping and every surrounding word stay byte-identical. The version the paragraph says is on PyPI is deliberately not rewritten -- whether a release is published is not something this tree can re-derive, and a note claiming it should be written by whoever released it. Verified: 2156 selected, 13 deselected, ruff clean. Dogfooded -- the figure in this commit was written by the mechanism it adds. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 05be13c commit afbcc91

3 files changed

Lines changed: 168 additions & 4 deletions

File tree

‎CONTRIBUTING.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,26 @@ uv run ruff check . --fix
3737
Both are what CI runs (`.github/workflows/ci.yml`), across Python 3.12, 3.13
3838
and 3.14.
3939

40+
### If you added or removed a test
41+
42+
`docs/deep-dive.md` quotes how many tests the suite selects, and
43+
`tests/test_deep_dive.py` holds that figure against reality — the page's
44+
verified claims are only worth reading if its numbers are real. So a branch
45+
that changes the test count fails that one check until the figure is updated.
46+
47+
Nothing is wrong with your change. Refresh the line and commit it:
48+
49+
```bash
50+
GRAPHARC_UPDATE_FIGURES=1 uv run pytest tests/test_deep_dive.py
51+
```
52+
53+
That rewrites the figure, skips the check that wrote it, and tells you to
54+
re-run. A plain `pytest` never rewrites anything, and CI never sets that
55+
variable, so a stale figure still fails there.
56+
57+
The version the paragraph says is on PyPI is deliberately *not* refreshed this
58+
way: whether a release is published is not something the tree can re-derive.
59+
4060
### The live-marker rule
4161

4262
**A test marked `live` calls a real model backend and spends real money.** Never

‎docs/deep-dive.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ A stable system is not one that claims to have no edges — it is one whose edge
254254
- **`.env` and `grapharc.toml` follow the same discovery rule: the working directory, and nowhere else.** Neither searches parent directories — a run must not be governed by a file you did not know about, and must not be *billed* to one either. **This is a behaviour change:** the credential loader used to walk up to `/`, so a `.env` in an ancestor directory (a `$HOME` one on a shared box, a client project one above a demo checkout) was picked up silently. If you relied on that, move the file into the directory you run from, `export` the variable, or pass `env_file=` to name it explicitly. A real environment variable still beats any file.
255255
- **`grapharc run` has no budget unless you give it one.** Set any of `--max-tokens`, `--max-iterations`, `--max-seconds`, or `--max-concurrency`; without them each dimension is unlimited and the gate admits a topology of any worst-case cost.
256256

257-
**Verified this pass:** `pytest` → green, 2,151 selected and 13 deselected (the live ones); `ruff check .` clean; all eight `grapharc demo` stages green, plus the `trace` / `metrics` / `viz` / `replay` tour against a freshly recorded demo trace; the wheel builds and imports all submodules in a clean virtualenv with `[all]`, and `0.1.7` on PyPI is that wheel. The counts are a snapshot, not a property of the project — `pytest` re-derives them in one command, which is the only reason they are quoted, and `tests/test_deep_dive.py` fails this line rather than letting it drift.
257+
**Verified this pass:** `pytest` → green, 2,156 selected and 13 deselected (the live ones); `ruff check .` clean; all eight `grapharc demo` stages green, plus the `trace` / `metrics` / `viz` / `replay` tour against a freshly recorded demo trace; the wheel builds and imports all submodules in a clean virtualenv with `[all]`, and `0.1.7` on PyPI is that wheel. The counts are a snapshot, not a property of the project — `pytest` re-derives them in one command, which is the only reason they are quoted, and `tests/test_deep_dive.py` fails this line rather than letting it drift.
258258

259259
[ROADMAP.md](../ROADMAP.md) tracks what is built and what is not, item by item.
260260

‎tests/test_deep_dive.py‎

Lines changed: 147 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,21 @@
1515
tests `pytest` selects, and how many it holds back as `live` — rather than as
1616
a pass count, which cannot be re-derived without running the suite from inside
1717
itself. A green suite is asserted by the suite being green.
18+
19+
**Keeping the figure honest must not be a newcomer's problem.** Any PR that
20+
adds or removes a test moves these counts, so this file failed *every* such
21+
branch until someone hand-edited a number in a docs file they had no reason to
22+
know existed. That is what happened to PR #115: an outside contributor's first
23+
change sat red for a month over "2,151 selected", and nothing in the failure
24+
pointed at a fix they could run. The check is unchanged and still strict --
25+
`GRAPHARC_UPDATE_FIGURES=1 pytest tests/test_deep_dive.py` now re-derives the
26+
line and writes it back, and the failure message says so. CI never sets that
27+
variable, so a stale figure still fails there, which is the whole point.
1828
"""
1929

2030
from __future__ import annotations
2131

32+
import os
2233
import re
2334
import subprocess
2435
import sys
@@ -32,6 +43,54 @@
3243
DEEP_DIVE = ROOT / "docs" / "deep-dive.md"
3344
MARKER = "**Verified this pass:**"
3445

46+
#: Opt in to rewriting the line instead of failing on it. Deliberately an
47+
#: environment variable and not a pytest flag: `--strict-config` means an
48+
#: unknown flag is an error, and a contributor reading a failure message can
49+
#: paste an env var in front of the command they already ran.
50+
UPDATE_ENV = "GRAPHARC_UPDATE_FIGURES"
51+
52+
53+
def _updating() -> bool:
54+
"""Whether this run may rewrite the paragraph.
55+
56+
Off for unset, empty, "0" and "false", so a leftover `=0` in a shell
57+
profile cannot quietly turn the guard into a no-op. CI sets nothing, which
58+
is what keeps a stale figure red there.
59+
"""
60+
return os.environ.get(UPDATE_ENV, "").strip().lower() not in ("", "0", "false", "no")
61+
62+
63+
def _with_figure(line: str, pattern: str, value: int) -> str:
64+
"""`line` with the one figure `pattern` captures replaced by `value`.
65+
66+
Pure, so the rewrite is tested without touching the real document: only
67+
group 1's span changes, which keeps the surrounding prose and the comma
68+
grouping the page uses byte-identical everywhere else.
69+
"""
70+
match = re.search(pattern, line)
71+
assert match, f"cannot rewrite {pattern!r}: it does not match:\n{line}"
72+
start, end = match.span(1)
73+
return line[:start] + f"{value:,}" + line[end:]
74+
75+
76+
def _rewrite(pattern: str, value: int) -> None:
77+
"""Write `value` into the marker line in place."""
78+
lines = DEEP_DIVE.read_text(encoding="utf-8").splitlines(keepends=True)
79+
for index, raw in enumerate(lines):
80+
if raw.startswith(MARKER):
81+
lines[index] = _with_figure(raw, pattern, value)
82+
DEEP_DIVE.write_text("".join(lines), encoding="utf-8")
83+
return
84+
raise AssertionError(f"{DEEP_DIVE.name} has no line starting with {MARKER!r}")
85+
86+
87+
def _remedy(pattern: str, value: int) -> str:
88+
"""The failure message's second half: what to run, or what to edit."""
89+
return (
90+
f"\n\nRe-derive it: {UPDATE_ENV}=1 pytest tests/test_deep_dive.py\n"
91+
f"or edit the line by hand — the figure should read {value:,}."
92+
)
93+
3594
# The recount runs pytest in a subprocess rather than calling `pytest.main`
3695
# in-process: this module is itself collected by the session doing the asking,
3796
# and re-entering the collector from inside it is not a supported thing to do.
@@ -101,21 +160,33 @@ def _quoted(pattern: str) -> str:
101160

102161
def test_the_quoted_selection_is_what_pytest_selects(recount):
103162
selected, _ = recount
104-
quoted = int(_quoted(r"([\d,]+) selected").replace(",", ""))
163+
pattern = r"([\d,]+) selected"
164+
quoted = int(_quoted(pattern).replace(",", ""))
165+
166+
if quoted != selected and _updating():
167+
_rewrite(pattern, selected)
168+
pytest.skip(f"refreshed: {quoted:,} -> {selected:,} selected. Re-run to verify.")
105169

106170
assert quoted == selected, (
107171
f"update the **Verified this pass** paragraph in {DEEP_DIVE.name}: it "
108172
f"says {quoted:,} selected, this tree has {selected:,}"
173+
+ _remedy(pattern, selected)
109174
)
110175

111176

112177
def test_the_quoted_deselection_is_what_pytest_holds_back(recount):
113178
_, live = recount
114-
quoted = int(_quoted(r"([\d,]+) deselected").replace(",", ""))
179+
pattern = r"([\d,]+) deselected"
180+
quoted = int(_quoted(pattern).replace(",", ""))
181+
182+
if quoted != live and _updating():
183+
_rewrite(pattern, live)
184+
pytest.skip(f"refreshed: {quoted:,} -> {live:,} deselected. Re-run to verify.")
115185

116186
assert quoted == live, (
117187
f"update the **Verified this pass** paragraph in {DEEP_DIVE.name}: it "
118188
f"says {quoted:,} deselected, this tree marks {live:,} `live`"
189+
+ _remedy(pattern, live)
119190
)
120191

121192

@@ -129,7 +200,10 @@ def test_the_quoted_published_version_is_the_packaged_one():
129200

130201
assert quoted == packaged, (
131202
f"update the **Verified this pass** paragraph in {DEEP_DIVE.name}: it "
132-
f"says {quoted} is on PyPI, pyproject says {packaged}"
203+
f"says {quoted} is on PyPI, pyproject says {packaged}. Deliberately "
204+
f"not rewritten by {UPDATE_ENV}: whether a version is *published* is "
205+
f"not something this tree can re-derive, and a release note that "
206+
f"claims it should be written by whoever released it."
133207
)
134208

135209

@@ -170,3 +244,73 @@ def test_the_paragraph_quotes_no_figure_that_nothing_re_derives():
170244
f"nothing: {stray}. Either add a check for them here or take them off "
171245
f"the line — that is the rot this file exists to stop."
172246
)
247+
248+
249+
# -- the update mode --------------------------------------------------------
250+
#
251+
# The guard's value is that it is strict; its cost was that a contributor could
252+
# not tell what to do about it. These cover both halves: the rewrite is correct,
253+
# and it cannot happen unless someone asked for it.
254+
255+
_SAMPLE = (
256+
"**Verified this pass:** `pytest` -> green, 2,151 selected and 13 deselected "
257+
"(the live ones); `ruff check .` clean; `0.1.7` on PyPI is that wheel.\n"
258+
)
259+
260+
261+
def test_the_rewrite_changes_the_figure_and_nothing_else():
262+
"""Comma grouping and every surrounding word survive, because the span of
263+
one capture group is all that is replaced."""
264+
updated = _with_figure(_SAMPLE, r"([\d,]+) selected", 2171)
265+
266+
assert "2,171 selected" in updated
267+
assert "2,151" not in updated
268+
# Untouched: the other figure, the prose, the trailing newline.
269+
assert "13 deselected" in updated
270+
assert "`ruff check .` clean" in updated
271+
assert updated.endswith("\n")
272+
assert updated.replace("2,171", "2,151") == _SAMPLE
273+
274+
275+
def test_the_rewrite_groups_thousands_like_the_page_does():
276+
"""A bare "2171" beside "2,151" would read as a typo, and the next reader
277+
would 'fix' it back."""
278+
assert "10,000 selected" in _with_figure(_SAMPLE, r"([\d,]+) selected", 10_000)
279+
# Under a thousand takes no separator.
280+
assert "999 selected" in _with_figure(_SAMPLE, r"([\d,]+) selected", 999)
281+
282+
283+
def test_the_rewrite_refuses_a_line_it_cannot_find_the_figure_in():
284+
"""Silently writing nothing would leave a stale figure looking refreshed."""
285+
with pytest.raises(AssertionError):
286+
_with_figure("no figures here\n", r"([\d,]+) selected", 5)
287+
288+
289+
def test_the_rewrite_reaches_the_real_document(tmp_path, monkeypatch):
290+
"""`_rewrite` finds the marker line among others and leaves them alone."""
291+
document = tmp_path / "deep-dive.md"
292+
document.write_text("# Title\n\nsome prose\n\n" + _SAMPLE + "\nafter\n", encoding="utf-8")
293+
monkeypatch.setattr(sys.modules[__name__], "DEEP_DIVE", document)
294+
295+
_rewrite(r"([\d,]+) selected", 2171)
296+
297+
written = document.read_text(encoding="utf-8")
298+
assert "2,171 selected" in written
299+
assert written.startswith("# Title\n\nsome prose\n")
300+
assert written.endswith("\nafter\n")
301+
302+
303+
def test_nothing_is_rewritten_unless_it_was_asked_for(monkeypatch):
304+
"""The load-bearing half. CI sets nothing, so the guard must be strict on an
305+
unset variable — a self-healing check in CI would assert nothing at all."""
306+
monkeypatch.delenv(UPDATE_ENV, raising=False)
307+
assert _updating() is False
308+
309+
# A leftover `=0` or `=false` in a shell profile must not disarm it either.
310+
for off in ("", "0", "false", "FALSE", "no", " "):
311+
monkeypatch.setenv(UPDATE_ENV, off)
312+
assert _updating() is False, off
313+
314+
for on in ("1", "true", "yes", "please"):
315+
monkeypatch.setenv(UPDATE_ENV, on)
316+
assert _updating() is True, on

0 commit comments

Comments
 (0)