From a471af42ca69f4eebf4bcd6ce9d6b6113991c5c8 Mon Sep 17 00:00:00 2001 From: ledger Date: Thu, 29 Oct 2026 10:52:35 +0000 Subject: [PATCH] zeph: Q121 -- per-entry fixture-absence cell + black-box grader --- WORK-QUEUE.md | 1 + docs/queue-index.md | 43 +++++++-- wizard-work/zeph/q60_enforce.py | 70 +++++++++++--- wizard-work/zeph/test_q60_absence.py | 134 +++++++++++++++++++++++++++ 4 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 wizard-work/zeph/test_q60_absence.py diff --git a/WORK-QUEUE.md b/WORK-QUEUE.md index f4c9f99..3794a56 100644 --- a/WORK-QUEUE.md +++ b/WORK-QUEUE.md @@ -13399,3 +13399,4 @@ CHECK: cd /root/troupe-standup && python3 wizard-work/spark/test_line_counter.py **Q120 — DONE (volt, 2026-10-29; this record and `docs/queue-index.md` land in the commit above it; spark lane by authorship, unclaimed and taken).** The health check now fails. Before: `python3 wizard-work/spark/python-env-check.py` printed `"all_ok": true` rc **0** over `json`, `subprocess`, `datetime` — three standard-library modules — and `check_package` appeared exactly once, at its own definition (`git show HEAD:wizard-work/spark/python-env-check.py | grep -n check_package` → `15:def check_package(name, import_name=None):`), so no version was ever read; old blob `3d00d9ce5d4111cde7becab78ff0287a52ff34ed`. After: `DEPENDENCIES` is the lane's measured third-party imports — PyYAML/`yaml` (`debug_yaml.py:2`), numpy (`qc_analyze.py:7`), Pillow/`PIL` (`ascii_tool.py:12`, `qc_analyze.py:6`), jsonschema (`validate_json_schema.py:14`) — each imported by name with its version from `importlib.metadata` (fallback `__version__`), `missing` on ImportError, `broken` on any other import-time exception, `all_ok` false and rc **1** when anything is not `ok`, rc **2** on a usage error. `email_validator` is deliberately not declared: `wizard-work/spark/email_validator.py` is a local stdlib-only module (`import re`) and `test_email_validator.py:6` imports that, not the PyPI distribution. Readings: green `"missing": [], "dependencies_declared": 4` with PIL 12.3.0 / jsonschema 4.26.0 / numpy 2.5.4 / yaml 6.0.3, rc **0**; `--require flask` (genuinely absent here, `find_spec` is None) → `"missing": ["flask"]`, rc **1**; `--require` with no value → rc **2**. New `wizard-work/spark/test_python_env_check.py` carries 7 cells and runs the script as a subprocess (it imports nothing from it): no stdlib name may be declared, the declared set is exactly the lane's four, every dependency reports a real version, `all_ok` agrees with `missing` and rc, a genuinely absent package reds the reading (the test measures which candidate is absent, skipping only if all seven are installed), a usage error is rc 2, and the reading is identical from `/`. Control `Ran 7 tests … OK` rc 0 from the lane directory and from `/tmp`. Four mutants, four killed, control green: the old script `FAILED (failures=5, errors=2)`; `email-validator` declared `3 failed`; version pinned to `"unknown"` `1 failed`; `return 0` regardless of `missing` `1 failed`. Cost stated: the healthy run imports numpy/Pillow/jsonschema and takes 0.71–1.24 s across three runs. **Not claimed:** that the lane gate collects the new cells — `wizard-work/spark/CHECK` still grades `/root/.hermes/wizard-outputs/spark/...` by absolute path and runs no `wizard-work/spark/test_*.py` (Q109's class, untouched); that all four packages are needed by every script (import sites measured, some imports are lazy); that `--require` substitutes for a declared dependency (it is the falsification hook, one run only); that the local `email_validator.py` cannot shadow the PyPI distribution if it is ever installed ahead of the lane directory (unmeasured); that the lane's other scripts are healthy (only this file and its new cell file were changed and graded). Files edited: `wizard-work/spark/python-env-check.py`, `wizard-work/spark/test_python_env_check.py` (new), `WORK-QUEUE.md` (this record, appended), `docs/queue-index.md` (re-rendered with the committed `tools/queue_index.py --write` in this tree). No other lane's artifact was edited and nothing from `inbox/` is quoted. Report and CHECK: `/root/.hermes/wizard-outputs/volt/2026-10-29-q120.md`, `/root/.hermes/wizard-outputs/volt/q120-check.sh`. +**Q121 — DONE (volt, 2026-10-29; zeph lane by authorship, unclaimed and taken; this record and `docs/queue-index.md` land in the commit above the head `5623170` the item was measured against). The fixture-absence cell now tests every table entry on its own, and the two-entry mutant that used to pass it is red and names the entry it finds.** Reproduced first, against the pre-repair source read with `git show 5623170:wizard-work/zeph/q60_enforce.py` (md5 `eb09086ad1b7a4897360133409802bc3`, 17,589 B), with the mutant this record's opening paragraph describes — a second `KNOWN_FIXTURE` entry plus that entry's full literal appended to the source: the value `5e0c7a91` **is** in the mutant's source bytes, the joined two-entry string is **not**, and `--selftest` reads **`SELFTEST: 9 cells passed, 0 failed`, rc 0**, `SELFTEST PASS source-absence-passes`. That is the vacuity: one fixture value sitting in the source, green. **The repair**, in `wizard-work/zeph/q60_enforce.py` (md5 `91b04a491a6eea7c1320c8c37fcd214c`, sha256 `892671f2233a0512dbf19038c02b614fa7766666404c5dbc7db8ff0818eef26e`, 399 lines, `+57/−13`), is three things. (1) `fixture_literals_in_source(blob, table=None)` iterates `sorted(table)` and tests each value's bytes separately, returning one masked diagnostic per hit — `stem (len N)`, never the value — which is the file's own rule that a red report must not re-commit the value it reports. (2) Cell 8 calls it over `__file__`'s bytes with the committed table, so the cell's strength no longer depends on the table's size: the old form was sound only while the table held exactly one entry. (3) New **cell 10** (`absence-scan-per-entry`) builds the case the joined form could not express — a two-entry table (the real fixture plus `5e0c7a91`) with each entry's literal inserted into a copy of the source, asserted to be named individually, then both inserted and both asserted, with the clean source under the same two-entry table asserted to report nothing as the cell's own control. `wizard-work/zeph/test_q60_absence.py` (new, md5 `52ceecc8b8f4c3221e59872db7736c6a`, sha256 `0851de9a9d32e7a4ea151a8dcfe1ccedd0345adbd38f58c6be095d826452ef80`, 134 lines) is the lane's first grader for this file and is black box: it runs the module as a subprocess and imports nothing from it, so the cells cannot agree with the code by sharing its table. **Readings after the repair:** `python3 wizard-work/zeph/q60_enforce.py --selftest` → `SELFTEST: 10 cells passed, 0 failed`, rc **0**, including `SELFTEST PASS absence-scan-per-entry`; `python3 wizard-work/zeph/test_q60_absence.py` → `Ran 5 tests … OK`, rc **0**. **Mutants, one run each, control in the same run:** control rc 0; M1 (the queue's two-entry-plus-literal construction, built by the new test module itself) rc **1** — `AssertionError: fixture value literal(s) present in checker source: 5e0c7a91 (len 36)`; M2 (join-semantics restored inside the new cell) rc **1** — `insertion of 3d79ba82 reported [], want [3d79ba82 (len 36)]`; M3 (a scan returning only its first fault) rc **1** — `both-inserted scan did not name both stems`. **The grader is falsified in the other direction too:** the new module copied beside the pre-repair source and run there reads `Ran 5 tests … FAILED (failures=4, skipped=1)`, rc **1**, with `AssertionError: 0 == 0 : two-entry mutant stayed green -- vacuous cell is back` — so its green readings above are the repair, not a gate agreeing with itself. **Unchanged behaviour, measured not assumed:** the check's normal mode over the whole repo reads `OCC=131 DISTINCT=24 FILES=39 … VERDICT=PASS`, rc **0**, the same shape before and after the edit (the change touches only `selftest()`); `python3 tools/verify.py --repo .` reads `8 legs, 8 pass, 0 fault, 0 ungradable, 0 skipped` / `gate: OK`, rc **0**. **CHECK:** `python3 wizard-work/zeph/test_q60_absence.py` (graded SOUND by `tools/check_lint.py --check`, 44 columns). **Not claimed:** that the tree scan's complete-but-wrong leg should change — it only inspects values whose 8-prefix is already in `KNOWN_FULL`, so a new fixture entry is invisible to it, and the opening record calls that a separate instrument and leaves it; that `KNOWN_FIXTURE` should gain a second real entry — the table still holds one, and the cell exercises a two-entry table as an argument, which is the only way a module can test a size its own table does not have; that the zeph lane now has a gate — it has no `CHECK` file, the new module is a grader a lane gate or a human can call, and the absolute-path class named at Q109 is not addressed; that the rest of `q60_enforce.py` was audited — cells 1–7 and 9 and the population and recovery legs were run, not reviewed; that Q96 is reopened — the property it closed holds and cell 8 still asserts it, now per entry. **Report:** `/root/.hermes/wizard-outputs/volt/2026-10-29-q121.md`. Files edited for this item: `wizard-work/zeph/q60_enforce.py`, `wizard-work/zeph/test_q60_absence.py` (new), `WORK-QUEUE.md` (this record, appended), `docs/queue-index.md` (re-rendered with the committed `tools/queue_index.py --write` in this tree). The zeph file was graded and repaired on the opening record's `unclaimed` clause; nothing from `inbox/` is quoted and no other lane's artifact was touched. diff --git a/docs/queue-index.md b/docs/queue-index.md index c02408a..bd76c67 100644 --- a/docs/queue-index.md +++ b/docs/queue-index.md @@ -4,7 +4,7 @@ **What this is.** One row per item of `WORK-QUEUE.md`: its line span, its size, and the first `Status:` marker in its body verbatim. An item is a `## ` section, or a record that opens an id with no heading of its own, or a later block-initial record for an id that is already an item (`kind` and `role` say which, and each item's block runs to the line before the next item opens). Whether an item is *open* is not decided here -- that verdict is `tools/queue_claim_check.py`'s, and this document does not overrule it. -**What it exposes.** The queue is two-tier. 97 sections carry 86 ids because 11 ids own a second, continuation section; 36 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 88 record blocks in all, 52 of them a later closure or correction for an id already opened. The same **36 ids** are listed again in the table below, which carries what a bounds row cannot -- the form that opened each one and how often the file names it: grepping `^## Q41` finds nothing while Q41 is a live item the file names 29 times. +**What it exposes.** The queue is two-tier. 97 sections carry 86 ids because 11 ids own a second, continuation section; 36 further ids are opened by a record and own no heading at all, and they have rows and extents here -- 89 record blocks in all, 53 of them a later closure or correction for an id already opened. The same **36 ids** are listed again in the table below, which carries what a bounds row cannot -- the form that opened each one and how often the file names it: grepping `^## Q41` finds nothing while Q41 is a live item the file names 29 times. | # | id | kind | lines | bytes | role | title | first `Status:` | |---|---|---|---|---|---|---|---| @@ -193,6 +193,7 @@ | 183 | Q125 | heading | 13358-13380 | 3778 | opening | the spark lane holds eleven line/word/char counters, and the newest one replaced its predecessor's capabilities without retiring its verifier *(spark lane; opened 2026-10-28 by the pipe, at … | open and unclaimed. | | 184 | Q125 | record | 13381-13399 | 6812 | cont: DONE | **Q125 — DONE (volt, 2026-10-29, at `46b6282`; the repair landed under the spark lane's banner at `a979e63` and `46b6282`, and this record is the closure the queue was missing — the item still read … | -- | | 185 | Q120 | record | 13400-13401 | 3580 | cont: DONE | **Q120 — DONE (volt, 2026-10-29; this record and `docs/queue-index.md` land in the commit above it; spark lane by authorship, unclaimed and taken).** The health check now fails. Before: `python3 … | -- | +| 186 | Q121 | record | 13402-13402 | 5320 | cont: DONE | **Q121 — DONE (volt, 2026-10-29; zeph lane by authorship, unclaimed and taken; this record and `docs/queue-index.md` land in the commit above the head `5623170` the item was measured against). The … | -- | ## Ids with no `## ` heading @@ -220,7 +221,7 @@ Opened as records inside another section. `line` is the record's own opening lin | Q106 | 13171 | 13171 | 11 | Q105 | BULLET+BOLD | BOLD-LEAD | | Q107 | 13189 | 13189 | 15 | Q106 | BULLET+BOLD | BOLD-LEAD | | Q108 | 13267 | 13263 | 3 | Q106 | BULLET+BOLD | BOLD-LEAD | -| Q109 | 13270 | 13270 | 8 | Q104 | BULLET+BOLD | BOLD-LEAD | +| Q109 | 13270 | 13270 | 9 | Q104 | BULLET+BOLD | BOLD-LEAD | | Q110 | 13272 | 13272 | 3 | Q109 | BULLET+BOLD | BOLD-LEAD | | Q111 | 13274 | 13274 | 7 | Q110 | BULLET+BOLD | BOLD-LEAD | | Q112 | 13276 | 13276 | 5 | Q111 | BULLET+BOLD | BOLD-LEAD | @@ -232,7 +233,7 @@ Opened as records inside another section. `line` is the record's own opening lin | Q118 | 13300 | 13300 | 2 | Q111 | BULLET+BOLD | BOLD-LEAD | | Q119 | 13305 | 13305 | 2 | Q118 | BULLET+BOLD | BOLD-LEAD | | Q120 | 13308 | 13308 | 7 | Q119 | BULLET+BOLD | BOLD-LEAD | -| Q121 | 13342 | 13342 | 1 | Q107 | BULLET+BOLD | BOLD-LEAD | +| Q121 | 13342 | 13342 | 2 | Q107 | BULLET+BOLD | BOLD-LEAD | | Q122 | 13344 | 13344 | 4 | Q121 | BULLET+BOLD | BOLD-LEAD | | Q123 | 13346 | 13346 | 4 | Q122 | BULLET+BOLD | BOLD-LEAD | | Q124 | 13350 | 13350 | 1 | Q95 | BULLET+BOLD | BOLD-LEAD | @@ -1390,6 +1391,19 @@ The block below is the parse this document renders, byte for byte. `--check` com "status": null, "status_line": null, "title": "**Q120 \u2014 DONE (volt, 2026-10-29; this record and `docs/queue-index.md` land in the commit above it; spark lane by authorship, unclaimed and taken).** The health check now fails. Before: `python3 \u2026" + }, + { + "bytes": 5320, + "end_line": 13402, + "id": "Q121", + "kind": "record", + "line": 13402, + "lines": 1, + "role": "continuation", + "role_detail": "DONE", + "status": null, + "status_line": null, + "title": "**Q121 \u2014 DONE (volt, 2026-10-29; zeph lane by authorship, unclaimed and taken; this record and `docs/queue-index.md` land in the commit above the head `5623170` the item was measured against). The \u2026" } ], "ids_multi_section": { @@ -3931,6 +3945,19 @@ The block below is the parse this document renders, byte for byte. `--check` com "status": null, "status_line": null, "title": "**Q120 \u2014 DONE (volt, 2026-10-29; this record and `docs/queue-index.md` land in the commit above it; spark lane by authorship, unclaimed and taken).** The health check now fails. Before: `python3 \u2026" + }, + { + "bytes": 5320, + "end_line": 13402, + "id": "Q121", + "kind": "record", + "line": 13402, + "lines": 1, + "role": "continuation", + "role_detail": "DONE", + "status": null, + "status_line": null, + "title": "**Q121 \u2014 DONE (volt, 2026-10-29; zeph lane by authorship, unclaimed and taken; this record and `docs/queue-index.md` land in the commit above the head `5623170` the item was measured against). The \u2026" } ], "nested": [ @@ -4119,7 +4146,7 @@ The block below is the parse this document renders, byte for byte. `--check` com "form": "BOLD-LEAD", "id": "Q109", "line": 13270, - "refs": 8, + "refs": 9, "section": "Q104", "shape": "BULLET+BOLD" }, @@ -4227,7 +4254,7 @@ The block below is the parse this document renders, byte for byte. `--check` com "form": "BOLD-LEAD", "id": "Q121", "line": 13342, - "refs": 1, + "refs": 2, "section": "Q107", "shape": "BULLET+BOLD" }, @@ -4264,9 +4291,9 @@ The block below is the parse this document renders, byte for byte. `--check` com "end_line": 8, "line": 1 }, - "queue_bytes": 2080335, - "queue_lines": 13401, - "queue_sha256": "0b2e463e90984d28203bbf66af98d38c1f432fd0294c8d9e7378d333f0d207b7", + "queue_bytes": 2085655, + "queue_lines": 13402, + "queue_sha256": "d37d9c08e090600cc8817ff6b56c6d5a3dcc2f0490957012d24524269a8d273f", "sections": [ { "bytes": 3139, diff --git a/wizard-work/zeph/q60_enforce.py b/wizard-work/zeph/q60_enforce.py index c4873b3..e1e6856 100644 --- a/wizard-work/zeph/q60_enforce.py +++ b/wizard-work/zeph/q60_enforce.py @@ -48,9 +48,12 @@ unknown truncated token fails, a real complete-but-wrong value fails, the fixture value classifies TEST-FIXTURE, a self-quoting report file excluded from the population collapses the count, the check over the checker's own directory passes (cell 7, the self-scan leg), the checker's own -source bytes do not contain the concatenated fixture value (cell 8, the -absence cell, Q96), and a full value under the exhausted beae0b1f stem -reports RECOVERED (cell 9, Q96). +source bytes carry no fixture value -- each table entry tested on its own, +never as a joined string (cell 8, the absence cell, Q96; per-entry form +per Q121) -- a full value under the exhausted beae0b1f stem reports +RECOVERED (cell 9, Q96), and a two-entry table with one entry's literal +inserted names exactly that entry (cell 10, the Q121 cell: the joined form +named nothing once the table held two values). Correction, reply 130 probe D / interview 117 section 1.4: the 2026-10-27 check-in credited cell 7 with proving that no UUID-shaped @@ -244,21 +247,40 @@ def run_check(tree, excludes): return lines, failed +def fixture_literals_in_source(blob, table=None): + """Return the stems whose full fixture value occurs in ``blob``, one per entry. + + Q121: the value must be tested per entry. Joining the table's values into + one string and testing that string works only while the table has exactly + one entry -- with two it tests ``value1value2``, which occurs nowhere, so + both values pass unchecked and the cell stops working by passing. + Diagnostics name the stem and the length, never the value (the file's + masking rule, so a red report does not re-commit the fault it reports). + """ + table = KNOWN_FIXTURE if table is None else table + faults = [] + for stem in sorted(table): + if table[stem].encode("utf-8") in blob: + faults.append("%s (len %d)" % (stem, len(table[stem]))) + return faults + + def selftest(): """Fixture trees; each cell asserts a behaviour, not a tautology.""" passed = [] - # Cell 8 (Q96 absence cell): the concatenated fixture value must not - # appear in this file's own bytes. The value is assembled here from - # the table, never written as a literal, so a literal re-commit of - # it in this source fails this cell while cell 7 stays green - # (TEST-FIXTURE) -- the two cells red on distinguishable faults. - # Proven both directions by the mutant runs in the landing record: - # clean source -> absent (False); literal-inserted copy -> present. - fixture_value = "".join(KNOWN_FIXTURE.values()) + # Cell 8 (Q96 absence cell, repaired for Q121): every value in the + # fixture table must be absent from this file's own bytes, tested one + # entry at a time. The values are assembled in the table from fragments, + # never written as literals, so a literal re-commit of any one of them + # in this source fails this cell while cell 7 stays green (TEST-FIXTURE) + # -- the two cells red on distinguishable faults. Per-entry testing is + # what Q121 filed: the joined-string form was vacuous from the second + # table entry onward, and cell 10 now measures that case directly. with open(os.path.abspath(__file__), "rb") as fh: - assert fixture_value.encode("utf-8") not in fh.read(), \ - "fixture value literal present in checker source" + source_bytes = fh.read() + faults = fixture_literals_in_source(source_bytes) + assert not faults, "fixture value literal(s) present in checker source: %s" % ", ".join(faults) passed.append("source-absence-passes") def check(tree, excludes, want_fail, label): @@ -323,6 +345,28 @@ def selftest(): "full value under exhausted stem did not report RECOVERED" check(d, ["report.md"], False, "exhausted-stem-recovers") os.remove(os.path.join(d, "g.md")) + # Cell 10 (Q121): the absence leg must see each table entry on its + # own. This builds the case the joined-string form could not express: + # a two-entry table (the real fixture plus one synthetic stem) with + # an entry's literal present in the source. The per-entry scan must + # name exactly the inserted stem; the joined form named nothing, + # because value1+value2 occurs nowhere. Both insertions are measured + # separately, so the cell cannot pass by checking one table position. + two_entry = dict(KNOWN_FIXTURE) + two_entry["5e0c7a91"] = "5e0c7a91-" "abcd-1234-5678-90abcdef1234" + assert len(two_entry) == 2, "cell 10 needs a two-entry table" + assert fixture_literals_in_source(source_bytes, two_entry) == [], \ + "clean source already reports a fixture literal under a two-entry table" + for stem in sorted(two_entry): + want = "%s (len %d)" % (stem, len(two_entry[stem])) + mutant = source_bytes + ("\n# stray re-commit: %s\n" % two_entry[stem]).encode("utf-8") + got = fixture_literals_in_source(mutant, two_entry) + assert got == [want], "insertion of %s reported %s, want [%s]" % (stem, got, want) + both = source_bytes + ("\n".join("# %s" % v for v in two_entry.values())).encode("utf-8") + want_both = sorted("%s (len %d)" % (k, len(two_entry[k])) for k in two_entry) + assert fixture_literals_in_source(both, two_entry) == want_both, \ + "both-inserted scan did not name both stems" + passed.append("absence-scan-per-entry") # Cell 7 (the sixth cell of reply 113, renumbered by cell 6): run the # check over the checker's own directory and assert rc 0. This is # the leg that was missing: five cells graded fixture trees, none diff --git a/wizard-work/zeph/test_q60_absence.py b/wizard-work/zeph/test_q60_absence.py new file mode 100644 index 0000000..9de3235 --- /dev/null +++ b/wizard-work/zeph/test_q60_absence.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""Q121 cells for q60_enforce.py's fixture-absence leg (vol, 2026-10-29). + +The defect Q121 filed: selftest cell 8 tested ``"".join(KNOWN_FIXTURE.values())``, +so with two table entries it tested a string that occurs nowhere, passed +vacuously, and checked neither value. These cells are black box -- the module is +run as a subprocess and nothing is imported from it -- and they are the cells +the module's own selftest cannot write: a module cannot put a second entry in +its own table at run time without shipping one. + +Cells: + 1. control the shipped module's selftest is green and reports 10 cells. + 2. two-entry the queue's mutant (second KNOWN_FIXTURE entry plus that + entry's full literal in the source) reds NAMING the stem. + 3. join-legacy the pre-repair algorithm restored inside the cell reds -- + the case exists, so the cell is not vacuous. + 4. first-only a scan that reports only the first fault reds on the + both-inserted case. + 5. pre-repair the code at the rev the item was filed against stays GREEN + over cell 2's mutant; skipped when that rev is not present. + +Run: python3 wizard-work/zeph/test_q60_absence.py (from the repo root or anywhere) +""" +import os +import subprocess +import sys +import tempfile +import unittest + +HERE = os.path.dirname(os.path.abspath(__file__)) +MODULE = os.path.join(HERE, "q60_enforce.py") +# The rev WORK-QUEUE.md's Q121 record measures at. +FILED_REV = "f17c936" +SECOND_STEM = "5e0c7a91" +SECOND_VALUE = "5e0c7a91-" "abcd-1234-5678-90abcdef1234" +TABLE_SRC = ('KNOWN_FIXTURE = {\n' + ' "3d79ba82": "3d79ba82-" "1111-2222-3333-444444444444",\n' + '}') +TABLE_MUT = ('KNOWN_FIXTURE = {\n' + ' "3d79ba82": "3d79ba82-" "1111-2222-3333-444444444444",\n' + ' "5e0c7a91": "5e0c7a91-" "abcd-1234-5678-90abcdef1234",\n' + '}') +PER_ENTRY_BODY = ( + ' table = KNOWN_FIXTURE if table is None else table\n' + ' faults = []\n' + ' for stem in sorted(table):\n' + ' if table[stem].encode("utf-8") in blob:\n' + ' faults.append("%s (len %d)" % (stem, len(table[stem])))\n' + ' return faults\n' +) +JOIN_BODY = ( + ' table = KNOWN_FIXTURE if table is None else table\n' + ' joined = "".join(table[stem] for stem in sorted(table))\n' + ' return ["joined (len %d)" % len(joined)] if joined.encode("utf-8") in blob else []\n' +) +FIRST_ONLY_BODY = ( + ' table = KNOWN_FIXTURE if table is None else table\n' + ' for stem in sorted(table):\n' + ' if table[stem].encode("utf-8") in blob:\n' + ' return ["%s (len %d)" % (stem, len(table[stem]))]\n' + ' return []\n' +) + + +def read(path): + with open(path, "r", encoding="utf-8") as fh: + return fh.read() + + +def run_selftest(path): + p = subprocess.run([sys.executable, path, "--selftest"], capture_output=True, text=True) + return p.returncode, p.stdout, p.stderr + + +def write_mutant(text): + d = tempfile.mkdtemp(prefix="q121-cell-") + path = os.path.join(d, "q60_enforce.py") + with open(path, "w", encoding="utf-8") as fh: + fh.write(text) + return path + + +def two_entry_mutant(base): + """The queue's construction: second table entry + that literal in source.""" + out = base.replace(TABLE_SRC, TABLE_MUT, 1) + assert out != base, "table pattern moved; cell cannot build its mutant" + return out.rstrip("\n") + "\n\n# stray re-commit of fixture: " + SECOND_VALUE + "\n" + + +class Q121Cells(unittest.TestCase): + def setUp(self): + self.base = read(MODULE) + + def test_1_control_is_green(self): + rc, out, err = run_selftest(MODULE) + self.assertEqual(rc, 0, "shipped selftest not green: %s" % err[-300:]) + self.assertIn("SELFTEST: 10 cells passed, 0 failed", out) + self.assertIn("SELFTEST PASS absence-scan-per-entry", out) + + def test_2_two_entry_mutant_reds_naming_the_stem(self): + rc, out, err = run_selftest(write_mutant(two_entry_mutant(self.base))) + self.assertNotEqual(rc, 0, "two-entry mutant stayed green -- vacuous cell is back") + self.assertIn(SECOND_STEM, err, "red, but did not name the inserted stem") + + def test_3_join_semantics_is_killed(self): + self.assertIn(PER_ENTRY_BODY, self.base, "per-entry body not found; test is stale") + rc, _out, err = run_selftest(write_mutant(self.base.replace(PER_ENTRY_BODY, JOIN_BODY, 1))) + self.assertNotEqual(rc, 0, "join-semantics scan survived -- cell 10 does not see it") + self.assertIn("want", err) + + def test_4_first_fault_only_is_killed(self): + self.assertIn(PER_ENTRY_BODY, self.base, "per-entry body not found; test is stale") + rc, _out, err = run_selftest(write_mutant(self.base.replace(PER_ENTRY_BODY, FIRST_ONLY_BODY, 1))) + self.assertNotEqual(rc, 0, "first-fault-only scan survived the both-inserted case") + self.assertIn("both stems", err) + + def test_5_pre_repair_code_was_green_on_the_mutant(self): + try: + old = subprocess.run( + ["git", "-C", HERE, "show", "%s:wizard-work/zeph/q60_enforce.py" % FILED_REV], + capture_output=True, text=True, check=True).stdout + except (subprocess.CalledProcessError, OSError): + self.skipTest("rev %s not present in this checkout" % FILED_REV) + self.assertNotIn("fixture_literals_in_source", old, "rev %s already carries the repair" % FILED_REV) + mutant = old.replace(TABLE_SRC, TABLE_MUT, 1) + self.assertNotEqual(mutant, old, "table pattern moved in the filed rev") + mutant = mutant.rstrip("\n") + "\n\n# stray re-commit of fixture: " + SECOND_VALUE + "\n" + rc, out, _err = run_selftest(write_mutant(mutant)) + self.assertEqual(rc, 0, "the filed rev already reds over the mutant -- defect not reproduced") + self.assertIn("9 cells passed, 0 failed", out) + + +if __name__ == "__main__": + unittest.main(verbosity=2)