testharness-fun/ ├─ src/fwvault/ the specimen. no runtime dependencies. │ ├─ parse.py 303 UF2 + ELF walkers, and serialize_uf2 │ ├─ policy.py 118 9 rules, precedence-ordered │ ├─ signing.py 158 outbound client. transport is a PARAM │ ├─ store.py 116 content-addressed, atomic writes │ ├─ app.py 220 raw ASGI3. no framework. │ ├─ cli.py 168 argv in, exit code out │ ├─ errors.py 55 the taxonomy the design hangs on │ ├─ clock.py 47 time as a dependency │ └─ testing.py 158 builders + doubles, SHIPPED │ ├─ tests/ one file per technique family │ ├─ conftest.py isolation, specimens, plugin hooks │ ├─ mechanics parse, fixtures, parametrize, markers, │ │ assertions, time, cli, store │ ├─ doubles doubles, monkeypatch, transport, │ │ asgi_raw, asgi_clients │ ├─ evidence hostile, property, metamorphic, │ │ differential, policy, oracle_independence │ └─ meta suite_hygiene, contract, lean_install, │ mutation, ai_authored, cards │ ├─ terms.py 67 terms. every glossary generates from it ├─ frag.html deep dive source. the page is built. ├─ card_content*.py the two printed cards └─ build_*.py gen_*.py generators. see the COMMANDS tab.
POST /artifact ← 1024 bytes of UF2 │ ├─ parse(blob) sniff magic, walk 512-byte │ blocks, fold to an Image │ ├─ digest_of(blob) sha256, computed by US, │ never taken from the client │ ├─ client.verify(digest) GET oracle/keys/<digest> │ → Verdict, or VaultUnavailable │ ├─ policy.enforce(...) 9 rules, precedence order, │ raises the first hit │ └─ store.put(blob, m) → 201 {"digest": ...}
| seam | enabling point | contract | what it buys |
|---|---|---|---|
| transport | SigningClient(transport=) | .request(method,url,body,headers) | retries, URLs, calls that did not happen |
| clock | SigningClient(clock=) | .now(), .sleep(s) | assert clock.slept == [0.5,1.0,2.0] in zero time |
| cache | SigningClient(cache=) | a dict | stale-verdict behaviour, prepopulated |
| store | create_app(store=) | .put/.get/.has/__len__ | assert len(vault) == 0 after a refusal |
| client | create_app(client=) | .verify(digest) | the 503 path, via a 3-line DeadClient |
| policy | create_app(policy=) | frozen dataclass, 9 fields | every rule via replace(policy, ...) |
| body limit | create_app(max_body=) | int | 413 without generating 8 MB |
| argv + streams | cli.main(argv,stdout,stderr) | files | the whole CLI in microseconds |
| vault root | Store(root) | a path | tmp_path, so tests never collide |
app fixture is one line.STRUCTURAL split a function so both halves are reachable walk_uf2() generator, yields Block records parse_uf2() folds that stream into an Image assert on block 10 of 900 without building a manifest; test the fold with hand-built records and no bytes at all. INVERSE serialize_uf2() is walk_uf2 backwards buys the round-trip relation, which needs no oracle at all. see the EVIDENCE tab. IMPORT sys.modules[name] = None makes `import name` raise. tests the dependency-absent path on a machine that has it. ENVIRONMENT FWVAULT_WALKER_RAISE a walker bug is a warning in production and a traceback in the suite. both states tested.
| test file | surfaces | what it is really about |
|---|---|---|
| test_parse.py | parser | the assertion vocabulary, parametrize and ids |
| test_hostile.py | parser | truncation sweeps, bit flips, seeded fuzz |
| test_property.py | parser, policy, store | hypothesis: round trip, invariant, never-crash |
| test_metamorphic.py | parser | relations that need no oracle |
| test_differential.py | parser | two independent implementations over one corpus |
| test_policy.py | policy | proving a refusal, for the right reason |
| test_store.py | store | tmp_path, atomicity, path traversal by regeneration |
| test_transport.py | transport | retry schedules, cache staleness, caplog |
| test_asgi_raw.py | HTTP | a 20-line ASGI client, and what only it can do |
| test_asgi_clients.py | HTTP, transport | the same app through httpx and TestClient |
| test_cli.py | CLI | in-process with streams, plus four real processes |
| test_doubles.py | transport, policy | the five doubles, each with a working example |
| test_monkeypatch.py | most | every method, four traps, and when not to patch |
| test_fixtures.py | store | scope, teardown order, factories, the anti-pattern |
| test_parametrize.py | policy, store | indirect, generate_tests, stacking limits |
| test_markers.py | parser | skip vs xfail, strict, pytest.param, flaky policy |
| test_time.py | transport | inject a clock, never sleep in a test |
| test_contract.py | all | the published shape: names, codes, routes, schema |
| test_lean_install.py | all | the dependency boundary, statically and behaviourally |
| test_suite_hygiene.py | the suite | does every test still assert anything |
| test_mutation.py | the suite | 8 mutants, each paired with its catcher |
| test_ai_authored.py | the suite | generated-shaped suite vs asserting, measured |
| test_oracle_independence.py | the suite | why the author cannot grade its own homework |
| test_cards.py | the docs | generated files, cross-refs, page fit |
| files | test_*.py or *_test.py |
| classes | Test*, and no __init__ |
| functions | test* not test_* |
# a helper named tests_in() IS COLLECTED as a test. # if it is a generator, the whole FILE dies: 'yield' keyword is allowed in fixtures, but not in tests
test_suite_hygiene.py, once in test_oracle_independence.py
with a helper called tests_from_belief. Name helpers
find_* or suite_*.visibility follows the DIRECTORY tree. nearest wins. no import needed, anywhere. repo/conftest.py everything below tests/conftest.py tests/ and below tests/test_x.py a fixture here SHADOWS both
1. isolate the process (autouse) @pytest.fixture(autouse=True) def isolate_environment(monkeypatch, tmp_path_factory): home = tmp_path_factory.mktemp("fwvault_home") monkeypatch.setenv("HOME", str(home)) monkeypatch.setenv("USERPROFILE", str(home)) # windows monkeypatch.setenv("FWVAULT_WALKER_RAISE", "1") 2. supply specimens and collaborators 3. extend pytest itself def pytest_addoption(parser): parser.addoption("--runnet", action="store_true") def pytest_collection_modifyitems(config, items): # where a marker becomes behaviour
expanduser("~") and the suite writes into a real home
directory. Set a fake one, and set both HOME and USERPROFILE.[tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["src"] # runs without installing addopts = "-rs --strict-markers" xfail_strict = true markers = [ "slow: takes real wall-clock time", "net: touches a real network", ] filterwarnings = [ # YOUR deprecations are errors. SCOPE it, or # someone else's release breaks your build. "error::DeprecationWarning:yourpkg.*", ]
| -rs | print every skip reason |
| --strict-markers | a typo'd marker is an error |
| xfail_strict | an xfail that passes is a failure |
| pythonpath | a fresh clone runs immediately |
build order is DEPENDENCY order. teardown is its exact reverse. outer:setup → inner:setup → TEST → inner:teardown → outer:teardown code after `yield` runs even if the test FAILS. it does NOT run if setup before the yield raised. → put what CAN fail after what MUST be cleaned up.
| scope | built | safe when |
|---|---|---|
| function | per test | always. the default |
| class | per class | a class groups a scenario |
| module | per file | expensive AND immutable |
| package | per directory | rare |
| session | per run | never mutable |
FACTORY: the test gets the FUNCTION @pytest.fixture def make_uf2(): return build_uf2 beats forty fixtures named after their defects PARAMETRIZED: requester runs once per case @pytest.fixture(params=["uf2","elf"], ids=[...]) def any_artifact(request): ... NAMED: function name is not the fixture name @pytest.fixture(name="digest") def _make_digest(uf2): ... OVERRIDE: a module-level fixture shadows conftest silently. nothing announces it. CONDITIONAL CLEANUP: addfinalizer beats yield request.addfinalizer(t.unlink) # only now that # it exists
@pytest.mark.parametrize("fam,want",
[(RP2040,"RP2040"), (NRF,"NRF52840")],
ids=["rp2040","nrf52840"]) # explicit
ids=lambda n: "block%d" % n # callable
ids=repr # byte specimens
STACKED = cartesian product. 2x2 = 4 tests.
multiplies fast: a 4th axis of 5 is 40 tests.
pytest.param = per-case id AND marks
pytest.param(x, id="unknown",
marks=pytest.mark.xfail(strict=True, reason="..."))
indirect: the param goes to a FIXTURE first,
so each case gets setup AND teardown
@parametrize("vault",[0,1,5], indirect=True)
pytest_generate_tests(metafunc): build the case
list at collection, from the source of truth
test_family[3-2] tells you nothing.
test_family[nrf52840] tells you everything. You read these in CI, in
a bisect, and in a message from someone without the repo open.| skip | does not apply here |
| skipif(cond) | same, decided at collection |
| xfail | this is broken and we know |
| xfail(strict=True) | and tell me when it stops |
| xfail(raises=E) | fails FOR THIS REASON |
@pytest.mark.xfail(reason="bug FW-118") def test_the_bug(): assert True # someone fixed it # → XPASS. exit 0. GREEN. the marker sits there # for two years and the test asserts nothing.
@pytest.mark.xfail(strict=True, reason="FW-118") # → FAILED: "delete this marker."
raises=, an xfail absorbs every
failure, including the ImportError you added this morning.with pytest.raises(ParseError, match=r"bad magic"):
parse(blob)
match is re.SEARCH, not fullmatch.
ESCAPE YOUR METACHARACTERS. "offset (0x1fc)"
is a group. a match that matches NOTHING still
passes if the exception type is right.
with pytest.raises(TruncatedError) as ei: ...
assert ei.value.needed == 32 # the OBJECT, not
# just the class
| capsys | stdout/stderr at the Python level |
| capfd | at the fd level. C ext, subprocess |
| caplog | log records. set the level or capture nothing |
| tmp_path | fresh dir per test, last 3 runs kept |
| tmp_path_factory | the session-scoped variant |
caplog.records, not
caplog.text. Records carry .levelname,
.name, .getMessage(). Text is whatever the formatter
felt like, and nobody thinks of a format string as an interface.A flaky test is a bug report, against the test or the code. Never noise. Something is genuinely non-deterministic and you have found it.
| cause | fix |
|---|---|
| unseeded random | fix the seed |
| real clock / sleep | inject a clock |
| shared state | narrow the scope |
fixed path + -n | tmp_path |
| real network | a marker plus a fake |
| dict / set order | sort, or compare sets |
--reruns 3 on your own code turns a real bug
into a slower green run, permanently.DUMMY fills a signature, never used STUB canned answers, no state FAKE a real, working, SIMPLIFIED impl ← this one SPY a stub that records its calls MOCK a spy that asserts on ITSELF ← almost never
- Fake is the only kind that can be wrong in a way a test notices. A stub returning 200 forever cannot fail to model a 404, because it was never modelling anything.
- Spy for the negative assertion. "the cache was used" is invisible in a return value and obvious in a call log.
- Mock fails inside the double, so the message describes a call that did not happen, not a behaviour that is wrong.
loose = Mock() # no spec= loose.method_that_does_not_exist() # passes. spec= is not optional.
SigningClient │ .verify(digest) ▼ transport.request(method, url, body, headers) │ → Response ├── UrllibTransport real. stdlib. net-marked only ├── RecordingTransport routes + a call log (spy) ├── ScriptedTransport one answer per call (sequence) └── FakeOracle a working oracle in RAM (fake) The narrower the seam, the cheaper the fake. `Response` is a 3-field dataclass, deliberately NOT an httpx.Response.
A library whose only seam is a protocol owes its users a working fake for it,
the way httpx ships MockTransport. Otherwise every downstream user
writes their own and each gets a detail wrong.
# fwvault/testing.py, part of the package build_uf2(blocks=3, bad_end_magic=1) # named defect build_elf(machine=0x28, bit64=True) signed_body(signer="ci-builder") RecordingTransport(routes={...}) # spy ScriptedTransport([err, err, Response(200, ...)]) flaky(2, Response(200, body)) # the idiom
# running off the end of a script is a HARD FAILURE AssertionError: ScriptedTransport exhausted: call 4 was not scripted (GET https://...) # a double that keeps answering makes "retried 3 # times" and "retried 300 times" the same test.
| setattr(o,n,v) | raises if n is absent. the guardrail |
| delattr(o,n) | |
| setitem(d,k,v) | sys.modules, os.environ, any table |
| delitem(d,k) | raising=False for cleanup |
| setenv / delenv | strings only |
| syspath_prepend | + invalidates importlib caches |
| chdir(p) | and puts it back |
| context() | undo EARLY, mid-test |
patch where the name is LOOKED UP: from . import families; families.FAMILIES[x] read at CALL time → patchable from .families import FAMILIES; FAMILIES[x] bound at IMPORT time into a SECOND namespace → patching does NOTHING, and the test PASSES
with pytest.MonkeyPatch.context() as m:1 raising=False disables your only signal a patch aimed at a typo'd name is a patch aimed at nothing, and it passes. 2 patching a constant does not move an already-bound default @dataclass x: int = CONST ← evaluated ONCE at class creation. same for def f(x=CONST) and `from mod import CONST`. 3 the string form walks getattr "pkg.mod.CONST" imports pkg then getattrs along. if pkg re-exported a FUNCTION over its own submodule name, the walk dies there. `import pkg.mod as m` does NOT save you. importlib.import_module() reads sys.modules. 4 sys.modules[n] = None makes `import n` raise delitem FIRST, or the None assignment is what takes effect and you pass for the wrong reason.
INJECT what you DO own your transport, clock, store, policy, streams, argv, the vault root PATCH what you do NOT own urllib, datetime, os.replace, a vendor lib SPLIT when there is nothing to pass a generator that WALKS plus a function that FOLDS is a seam too, and often a better one
walk_uf2(blob) generator → Block records parse_uf2(blob) folds → Image serialize_uf2(bs) the inverse
| hard because | the input space is adversarial by definition |
| the state machine is deep, so a wrong branch still returns something plausible | |
| a length field is a trust boundary, not a style issue | |
| easy because | a real oracle usually exists. almost no other code has one |
Driven by: test_parse, test_hostile,
test_property, test_metamorphic,
test_differential.
evaluate(image, verdict, policy, flags) → EVERY rejection, in precedence order enforce(...) → raises the FIRST one
Returning every hit rather than the first is what makes the rules testable without combinatorial fixtures. One hostile artifact that trips four rules gives four codes in one assertion, so a rule that silently stopped firing cannot hide behind one that fires earlier.
PRECEDENCE = (MALFORMED, OVERSIZE, EMPTY_PAYLOAD, UNKNOWN_FAMILY, DENIED_MACHINE, NOT_MAIN_FLASH, REVOKED_KEY, UNSIGNED, TOO_MANY_WARNINGS) codes are a PUBLIC CONTRACT. clients branch on `code`, never on `detail`, so codes are frozen in test_contract.py while the wording stays free.
Store(root).put(blob, manifest) → (digest, created)
content-addressed. atomic. two-level fan-out.
- Atomic: temp file in the SAME directory plus
os.replace. A temp in the system temp dir and a cross-device move is a copy, and a copy is the torn write you were avoiding. - Idempotent: the same bytes twice is a no-op, not a rewrite.
- Path safety by regeneration: the key is not sanitised, it is hashed from the bytes. A traversal string is just a miss.
# the traversal test everyone writes: "../../etc/passwd" "..%2f..%2fetc" "....//" this list can never be complete. # so the design does not rely on it. the sweep # documents the threat; regeneration IS the defence.
tmp_path. Never a fixed path:
two workers under -n 4 will race for it.async def app(scope, receive, send) scope dict describing the connection receive await it to pull events IN send await it to push events OUT that is the whole protocol.
| raw harness | chunk boundaries, disconnects, malformed scopes, visible lifespan |
| ASGITransport | substitutes the server. real header handling |
| MockTransport | substitutes what you call. opposite end of the wire |
| TestClient | wraps ANY asgi app, lifespan via with |
lifespan or a test client hangs at fixture
time, and it looks like the framework is broken rather than your app.def main(argv=None, stdout=None, stderr=None) -> int takes argv. returns an int. writes to streams it was HANDED. the highest-leverage decision in a CLI's design, and it costs nothing.
| in-process | microseconds, every branch. the coverage lives here |
| subprocess | four cases, but the ones in-process cannot see |
- importable from a clean interpreter
- the exit code as the shell sees it
- buffering, encoding, line endings
- a stray
print()in the import path corrupting your JSON
what is worth asserting is not "it returned a Verdict". it is WHEN, HOW OFTEN, and WHAT IT DID NOT DO. none of those show in a return value. assert clock.slept == [0.5, 1.0, 2.0] schedule assert len(transport.calls) == 1 no retry assert verdict.stale is True honesty
| oracle said | means |
|---|---|
| 200 | an answer |
| 404 | an answer. NOT retried |
| 4xx | OUR credentials are wrong → unavailable |
| 5xx / reset | retry, then unavailable |
fwvault has no container in the loop, so nothing here is demonstrated by a running test. The pattern is the one this repo already uses for the single real HTTP call, and it generalises unchanged.
# the shape: a marker, deselected by default @pytest.mark.net # or .docker, .integration def test_against_the_real_thing(): ... # conftest turns the marker into behaviour def pytest_collection_modifyitems(config, items): if config.getoption("--runnet"): return skip = pytest.mark.skip(reason="needs a real network; pass --runnet") for item in items: if "net" in item.keywords: item.add_marker(skip)
bare
job: an environment that can disprove a claim the others cannot.A surface most projects never test. These read the test tree as data.
- every test asserts something
- every skip explains itself
- no fixed paths, no path outside the repo
- nothing silently disabled: a bare
returnat the top leaves no skip line at all - no duplicate test names across files
- no mutated module-level state
- the published shape is pinned: names, codes, routes, schema version
- the dependency boundary, statically and behaviourally
| layer | proves | cannot prove | needs you to know | where |
|---|---|---|---|---|
| example | this input gives that output | anything about the inputs you did not write | the answer | test_parse |
| contract | the published shape has not moved | that anything behind it works | what you promised | test_contract |
| guardrail | it refuses, with this code | that the rule is the right rule | the policy | test_policy |
| hostile | nothing escapes the declared error | that it does anything useful | the error contract | test_hostile |
| property | a rule holds across generated inputs | that the rule is complete | a rule | test_property |
| metamorphic | two runs relate as they must | that either run is correct | nothing | test_metamorphic |
| differential | two implementations agree | that both are not wrong the same way | nothing, if independent | test_differential |
| mutation | the suite notices a break | that it notices the breaks nobody wrote | plausible breaks | test_mutation |
| meta | the suite is still a suite | anything about the code | what rot looks like | test_suite_hygiene |
lines covered mutants killed ai-authored 93 0 / 5 asserting 93 5 / 5 identical coverage. a --cov gate cannot tell these two files apart.
Two suites over the same twelve specimens. One written in the never-crash
shape, one with positive assertions. Coverage counted by a twenty-line
sys.settrace tracer so the number owes nothing to a plugin.
| line coverage | did this line execute |
| branch coverage | did BOTH sides of the if |
| mutation score | if I BREAK it, does anything go red |
# every assertion in test_hostile.py passes # against this: def parse(b): raise ParseError("no") # that is a truncation sweep, twelve bit flips # and twenty seeds. all green. against a parser # that does nothing.
The same trap reappears wherever assertions describe relationships instead of values. Every metamorphic relation in this repo is satisfied by a parser that ignores its input and returns a constant.
# the companion, in the SAME file so the pairing # is visible: def test_hostile_specimens_do_not_all_look_alike(): assert parse(VALID).block_count == 3
ROUND TRIP: the strongest, when an inverse exists assert serialize_uf2(walk_uf2(blob)) == blob INVARIANCE: changing X must NOT change Y scrambled = every payload byte randomised assert parse(scrambled) == parse(blob) EQUIVARIANCE: change X, predict the change in Y assert after.payload_bytes == before.payload_bytes + 200 * extra IDEMPOTENCE: twice is once assert serialize_uf2(walk_uf2(once)) == once
payloadSize can never lower the total, and past some point it stops
moving. That states the clamp without repeating 476, so the test cannot simply
agree with the implementation.specimen ─┬─► walk_uf2() ─┐
│ fast, subtle ├─► == ?
└─► _reference_walk() ─┘
naive, obvious, INDEPENDENT
- The reference must be independent. Shared helpers mean a shared bug cancels out and both agree. Asserted structurally here: the reference borrows no constant, import or type from the implementation.
- Compare outcomes, not just outputs. Agreeing on the result is not enough if one raises and the other does not.
- The corpus needs a guard. If every specimen raises, both agree everything is broken and the test is vacuous.
- One test per specimen, named by specimen. Adding a corpus entry adds a test with no assertion to write. That is the economics.
A mutation score is never 100%, and the reason has a name: some edits cannot be detected because they change no behaviour.
# the first attempt at "PRECEDENCE gets alphabetised" # SURVIVED, and it was not a hole: real: MALFORMED, OVERSIZE, EMPTY_PAYLOAD, ... alphabetical: DENIED_MACHINE, EMPTY_PAYLOAD, ... OVERSIZE # for the specimen chosen, OVERSIZE came first in # BOTH orders. the mutation changed nothing # observable. OVERSIZE + EMPTY_PAYLOAD is the pair that actually distinguishes them.
# nothing but pytest. this is the claim the repo makes. pip install pytest python -m pytest -q # 547 passed # plus hypothesis, httpx, starlette pip install -e ".[test]" python -m pytest -q # 557 passed # the deselected-by-default tests python -m pytest --runnet # no install at all: pythonpath is set in pyproject git clone ... && cd testharness-fun && pytest
pythonpath =
["src"] costs one line.# what WOULD run. the first thing to try when a # test is not running. pytest --collect-only -q pytest --collect-only -q | wc -l # every fixture visible here, and where it is from pytest --fixtures pytest --fixtures-per-test tests/test_policy.py # which markers exist and what they mean pytest --markers # the fixture setup plan, without executing tests pytest --setup-plan tests/test_asgi_raw.py # what a marker expression selects pytest --collect-only -q -m "net" pytest --collect-only -q -k "metamorphic and not round"
--setup-plan is the one people never learn. It
prints the fixture tree per test and runs nothing, which is how you find out
that a session fixture is being rebuilt.# every injectable collaborator in the package grep -rn "def __init__\|def create_app\|def main" src/ \ | grep "=" # the same, structurally python - <<'PY' import inspect, fwvault.signing as s, fwvault.app as a print(inspect.signature(s.SigningClient.__init__)) print(inspect.signature(a.create_app)) PY # what does this module import at MODULE level? # (a lazy import inside a function is the contract) python -c "import ast,sys;print([n.names[0].name for n in ast.parse(open(sys.argv[1]).read()).body if isinstance(n,ast.Import)])" src/fwvault/app.py # who patches, and who injects? grep -rln "monkeypatch" tests/ grep -rn "transport=" tests/ | head
# one surface at a time pytest tests/test_parse.py tests/test_hostile.py \ tests/test_metamorphic.py tests/test_differential.py # by name expression, across files pytest -k "parse or walk or serialize" pytest -k "policy or reject" pytest -k "asgi or transport" # the meta tier only pytest tests/test_suite_hygiene.py tests/test_contract.py \ tests/test_lean_install.py tests/test_cards.py # everything EXCEPT the slow generated ones pytest -k "not property and not metamorphic" # what changed, if you use git pytest $(git diff --name-only HEAD~1 | grep ^tests/)
pytest --lf # only what failed last time pytest --lf -x --tb=short # the shortest useful loop pytest --ff # failed first, then the rest # one test, exactly pytest tests/test_policy.py::test_oversize pytest "tests/test_parse.py::test_family_naming[rp2040]" # see prints and live logs pytest -s pytest --log-cli-level=DEBUG # locals in the traceback, then a debugger pytest -l pytest --pdb pytest --trace # break at the START of the test # why is this fixture what it is pytest --setup-show tests/test_fixtures.py
--lf. It changes
the edit-run loop more than anything else here.# coverage as a MAP, not a score pytest --cov=fwvault --cov-branch --cov-report=term-missing # the branch list only, which is what you act on pytest --cov=fwvault --cov-branch --cov-report=term-missing \ | grep -E "^src.*[0-9]+%" | sort -t% -k1 # does the suite notice a break? (slow, occasional) pip install mutmut && mutmut run --paths-to-mutate src/fwvault # the cheap in-repo version, milliseconds pytest tests/test_mutation.py tests/test_ai_authored.py -v # order dependence, the cheapest detector you own pytest -p xdist -n 4 pytest -p no:randomly # baseline to compare # slowest ten. run it monthly. pytest --durations=10
# after editing terms.py python gen_glossary.py # → GLOSSARY.md, cards, frag # after editing card_content*.py python build_card.py # the 3-sheet reference python build_basics.py # the 1-sheet beginner card python build_console.py # this page # layout without writing anything python build_card.py --check python build_console.py --check # the deep dive: edit frag.html, NEVER the built page build.py frag.html testharness_deep-dive.html "<title>" # the check that catches a stale artifact pytest tests/test_cards.py
test_cards.py fails if a generated file is
stale, if a cross-reference cites a section number that moved, or if a card
column would overflow its page. A bad rebuild fails locally, not on the site.jobs: bare: pip install pytest # and NOTHING else # the only environment that can DISPROVE a # no-dependencies claim. full: matrix [ubuntu, windows] x [3.11, 3.13] fail-fast: false # one red cell must not # hide the others cov: --cov=fwvault --cov-branch --cov-report=term-missing # no --cov-fail-under. a threshold is a number # people game, and the cheapest way to game it # is a test with no assertions.
expanduser, path separators and os.replace semantics all
differ, and every one has produced a green local run and a red CI run.Su lights-out codebases: code no human reads. volume makes review impossible, and the chess analogy says trying will look like a category error. Venturini the better version. you do not read your compiler's output, and NOT because compilers are trusted blindly. they have shipped catastrophic bugs. it is because an apparatus was built that makes reading unnecessary: behavioural tests, type systems, reproducible builds, fuzzers, sanitizers, formal methods. you trust the process, not the artifact. his diagnosis: that apparatus does not exist for agents yet, so the fear is rational rather than nostalgic.
| compiler | agent | |
|---|---|---|
| determinism | same source, same binary | same prompt, different code. nothing to pin or bisect |
| the spec | the source IS the spec, and a human wrote it | the prompt is the spec, and inventing the rest is the job |
| verification | CompCert, Alive2, Csmith. decades of formal methods | a suite somebody wrote, often the same somebody |
| trusting trust | a compiler can backdoor itself and hide it in its own source | one model writing both code and tests has that exact structure |
| what | finding | source |
|---|---|---|
| coverage vs mutation | a suite can reach 100% coverage at a 4% mutation score. quoted in MUTGEN's abstract as the motivating example | arXiv 2506.02954 |
| the benchmark gap | on 3,909 real-world Python functions: 41.3% accuracy, 45.1% statement, 30.2% branch, 40.2% mutation. the same models on TestEval: 91.8 / 92.2 / 82.0 / 49.7 | arXiv 2508.00408 |
| metric validity | whether coverage and mutation predict real fault detection is context-dependent: usable in regression scenarios, unreliable when the code may already be buggy | arXiv 2607.22880 |
| self-preference | evaluators recognise and favour their own generations, above what human annotators give them | arXiv 2404.13076 |
| why | the bias tracks perplexity. familiar-looking, not better | arXiv 2410.21819 |
| scale does not fix it | advanced capability is uncorrelated, sometimes negatively correlated, with low bias | arXiv 2604.22891 |
| self-correction | models struggle to self-correct without external feedback, and sometimes degrade | arXiv 2310.01798 |
| provenance effect | models correct external errors but not the same ones in their own traces. relabelling identical content as external restores it | arXiv 2606.05976 |
self-verification provenance separation model ──► code model A ──► code │ ▲ ▲ └──► tests ┘ model B ──► tests ┘ │ │ ▼ ▼ GREEN independent oracle and it means nothing: still not sufficient, but both halves share the a wrong belief now has to same wrong belief be held twice
test_oracle_independence.py runs exactly this, with
no model involved. A parser and its tests built on one plausible wrong
number: self-consistent, green, and wrong. Only the spec catches it.
| mutation gate | a test that kills no mutant is not a test |
| positive companion | or a refusing parser passes the file |
| provenance separation | the author does not grade it |
| differential | an independent implementation, not an expectation |
| properties over examples | review five, not five hundred |
| meta-tests | suites rot toward passing, and a generator helps |
| metamorphic | for when you cannot state the answer |
| equivalent mutants | the manual cost, stated honestly |
a test can check the code does what the test says. nothing can check the test says what was meant. spec ──► tests ──► code ▲ └─ a human wrote this, and a human has to read it every mitigation lives to the RIGHT of the spec. all of them assume the tests encode the intent. none can check that they do. the only defence is that the spec is small enough to be read.
terms.py, the same source as GLOSSARY.md, the printed card and section 02 of the deep dive. One line is the budget: a meaning needing two lines is an argument, and arguments live in the deep dive.| test | a function whose name starts with test. It passes if it does not raise |
| assertion | a line stating what must be true. assert x == y |
| test suite | all your tests together |
| test runner | the tool that finds and runs them. pytest is one |
| collection | pytest finding your tests, before running any |
| fail vs error | fail = an assertion was false. error = it blew up before reaching one |
| AAA | arrange, act, assert. The three parts of most tests, in that order |
| TDD | write the failing test first, then the code that makes it pass |
| fixture | named setup a test asks for by putting its name in the arguments |
| factory fixture | a fixture returning a function, so a test can make several things |
| setup / teardown | before and after. In pytest, teardown is whatever follows yield |
| scope | how often a fixture is rebuilt: function, class, module, package, session |
| autouse | a fixture applied to every test without being asked for |
| conftest.py | shared fixtures for a folder and everything under it. No import needed |
| override | a fixture in a test file shadowing one of the same name from conftest |
| tmp_path | built in: an empty folder, new per test, cleaned up for you |
| capsys / capfd | built in: captured output. capfd when the write bypasses Python |
| caplog | built in: log records. Set the level or you capture nothing |
| monkeypatch | built in: change something temporarily, put back automatically |
| test id | the name in the report, e.g. test_naming[2-many] |
| parametrize | run one test many times with different inputs, each reported separately |
| indirect | send a parametrize value to a fixture, so each case gets setup and teardown |
| marker | a label on a test: @pytest.mark.slow. Select with -m |
| skip / skipif | does not apply here. skipif decides at collection |
| xfail | known broken. Needs strict=True or it stays green once fixed |
| xpass | an xfail that unexpectedly passed. Usually means delete the marker |
| deselect | excluded from this run by -k or -m. Not the same as skipped |
| test double | any stand-in used instead of the real thing |
| dummy | passed only to fill a signature. Never actually used |
| stub | always returns the same canned answer |
| fake | a real but simplified implementation. A dict instead of a database |
| spy | works, and records how it was called |
| mock | a spy that also asserts it was called correctly |
| patching | replacing something in place, by name |
| injection | passing the collaborator in, rather than the code fetching it itself |
| seam | a place you can change behaviour without editing there. An argument is one |
| mock transport | a fake put in the place where code would talk to the network |
| unit | one function or class, nothing real underneath it |
| integration | several pieces together, often with a real file or database |
| end-to-end | the whole system as a user meets it. Slow, valuable, few |
| regression test | written to pin a bug you just fixed, so it cannot come back |
| smoke test | does it start at all |
| property-based | assert a rule for all inputs and let a tool hunt counterexamples |
| fuzzing | throw generated input at it and check nothing escapes the contract |
| differential | run two implementations over one input; whichever disagrees is wrong |
| oracle | differential testing where the reference is a trusted external tool |
| golden / snapshot | compare output against a stored known-good file |
| contract test | pin the shape you publish: names, codes, status codes, schema version |
| meta-test | a test about the test suite, e.g. does every test still assert |
| coverage | which lines ran. Not whether anything checked them |
| branch coverage | did both sides of each if run |
| mutation testing | break the code deliberately and see whether the suite notices |
| equivalent mutant | a deliberate break that changes no behaviour, so nothing can catch it |
| mutation gate | admit a generated suite only if it kills a threshold of mutants |
| flaky | passes and fails without the code changing. Always a bug, never noise |
| quarantine | move a flaky test behind a deselected marker with a ticket |
| test smell | a sign the test or code is wrong. More patching than assertion is one |
| apparatus | the machinery that makes reading generated output unnecessary: tests, types, sanitizers, canaries |
| provenance separation | whatever wrote the code does not grade it, and ideally is not the same family |
| self-preference bias | an evaluator scoring its own generations higher than a human would |
| intrinsic self-correction | a model revising its own output with no external signal. Does not reliably work |
| external feedback | a signal from outside the model. A test run is one, which is the point |
| characterization test | assertions transcribed from what the code currently does. Pins change, not correctness |
| the oracle problem | knowing what the right answer is, independently of the thing being tested |
| metamorphic relation | a property linking two runs when you cannot state the answer for either |
| translation validation | proving one output matches its input for this run, rather than proving the tool |
| the intent gap | tests cannot check that they encode what was actually wanted. No technical fix |