Skip to content

Assert the door the refresh reads the roster through - #208

Merged
iderex merged 1 commit into
mainfrom
tests/the-door-the-refresh-reads-the-roster-through
Aug 27, 2026
Merged

Assert the door the refresh reads the roster through#208
iderex merged 1 commit into
mainfrom
tests/the-door-the-refresh-reads-the-roster-through

Conversation

@iderex

@iderex iderex commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

What was wrong

roster.Named carried no test. It is the second door on the roster file, the one
the refresh reads through before anything can be asked about the repositories a
row names, and its own comment says why it exists as a separate door: it applies
no rule and judges nothing, so a caller that used it to render a page would be
rendering a file nobody checked.

It does refuse three things, and all three were reached by nothing:

git grep -n 'func TestTheOtherDoor\|Named(' -- internal/roster/roster_test.go ; echo "exit=$?"
exit=1
git grep -n 'roster.Named' -- . 
main.go:134:            named, err := roster.Named(body)

Run 2026-08-27 against 3db5bc8. One caller, no case.

I found it by reading the uncovered list of the mutation run #44 records rather
than its score. Eight mutants in this package are reported NOT COVERED, and I
applied every one of them by hand at 3db5bc8 in a clone outside the working
tree, because a tool that cannot see a site is not the same statement as a suite
that does not assert it. The eight split cleanly in two:

internal/roster/roster.go:176:25  owner == ""      -> owner != ""      SUITE RED
internal/roster/roster.go:176:39  name == ""       -> name != ""       SUITE RED
internal/roster/roster.go:178:14  name != want     -> name == want     SUITE RED
internal/roster/roster.go:188:13  err != nil       -> err == nil       SUITE RED
internal/roster/roster.go:217:48  err != nil       -> err == nil       SUITE GREEN
internal/roster/roster.go:224:38  == ""            -> != ""            SUITE GREEN
internal/roster/roster.go:226:111 i+1              -> i-1              SUITE GREEN
internal/roster/roster.go:231:16  len(names) == 0  -> len(names) != 0  SUITE GREEN

Run 2026-08-27, each mutation applied alone with the tree restored between.

The four that survive are all of Named, and they are this change. The four that
die are all case expressions of an expression switch inside Parse, which the
suite does assert; Go's coverage counter sits on the case body rather than on the
case expression, so the tool reads the position as uncovered and never mutates
it. That half is a bound on the figure rather than a gap in the suite, and it is
written into #44 rather than repaired here.

What this does

Four cases over Named, in the shape the table beside them already uses.

One per refusal, each tripping exactly it: a file that is not the array of rows,
a row naming no repository, a row whose repository is only spaces, and a file
holding no row. The reason is compared against its whole opening rather than a
fragment, so the row number is part of what is asserted.

Two neighbours. A file breaking nothing reads into the three repositories it
declares, in order. And a file the parser refuses three times over still comes
back out of this door, with the parser refusing the same bytes asserted in the
same case, which is the property that makes it a separate door rather than a flag
on the first one.

What it prevents is Named losing a refusal in silence. The list it hands back
goes into the refresh that writes the release record, and the build then reads
that record as an answered question.

Closes

Nothing. It belongs to #44 and does not close it: what that issue waits on is
where a mutation run lives, which is a call rather than a measurement and is not
touched here.

What was run

The gate, at the commit being pushed:

go run . ci
gate: 7 legs, in order: format, vet, test, build, links, sitemap, invariants
  needs-network was not asked for. Asking costs a request to the public name from whatever machine runs it, and a verdict that moves when somebody else's service does rather than when this tree changes. Ask with: go run ./harness/needs-network
  format: ok, 74 file(s)
  vet: ok
  test: ok, 37 test file(s)
  build: ok, 22 file(s)
  links: every reference that stays inside this site resolves to a file the build wrote
  sitemap: every page the build wrote is listed once, and every entry has a page behind it
  invariants: ok, 39 rule(s) decided, 1 owed and not decided
7 of 7 legs ran. None was skipped.

needs-network was not asked for and nothing here needs it: every case in this
change reads bytes in the test file.

Each of the four mutations these cases exist for, applied again by hand with the
cases in, one at a time with the tree restored between:

internal/roster/roster.go:217  err != nil -> err == nil
--- FAIL: TestTheOtherDoorReadsTheRepositoriesARosterNames
    a roster that breaks nothing was refused: the file is not the array of rows this roster has to be: <nil>
--- FAIL: TestTheOtherDoorJudgesNothingAboutWhatItReads
    a file the parser refuses was refused by the door that applies no rule

internal/roster/roster.go:224  == "" -> != ""
--- FAIL: TestTheOtherDoorReadsTheRepositoriesARosterNames
    a roster that breaks nothing was refused: row 1 names no repository

internal/roster/roster.go:226  i+1 -> i-1
--- FAIL: TestEachRefusalOfTheOtherDoorIsTrippedByItsOwnFixture
    a row naming no repository was refused with "row 1 names no repository, ...", which does not open with "row 3 names no repository"
    a row whose repository is spaces was refused with "row 1 names no repository, ...", which does not open with "row 3 names no repository"

internal/roster/roster.go:231  len(names) == 0 -> len(names) != 0
--- FAIL: TestTheOtherDoorReadsTheRepositoriesARosterNames
    a roster that breaks nothing was refused: the file holds no row

All run 2026-08-27. Each is red for the refusal it removed rather than for
something downstream.

The tool agrees, in a clone outside the working tree at the two commits:

gremlins --version
gremlins version dev windows/amd64
gremlins unleash --timeout-coefficient=20 --workers=2 ./internal/roster

at 3db5bc8   Killed: 23, Lived: 0, Not covered: 8, Timed out: 0
             Test efficacy: 100.00%   Mutator coverage: 74.19%
at e985bc5   Killed: 27, Lived: 0, Not covered: 4, Timed out: 0
             Test efficacy: 100.00%   Mutator coverage: 87.10%

Both run 2026-08-27, at the coefficient #44 argues belongs beside any score. The
four still reported uncovered are the four case expressions above, which are the
ones the reading at the top shows the suite already asserts.

No test was skipped. Nothing here needs a display, elevation, a socket or a
network.

The means

Go test cases in the package they judge, which is what every other suite in this
tree is made of. It adds no language, no runtime and no dependency: go test
already runs it as the third leg of the gate, and the mutation figures above come
from a tool that is run by hand outside the gate and is not made a dependency of
it by this change.

It carries a rule a machine refuses, in the only form this repository has for a
rule about a suite: each case is shown to bite by removing the thing it exists
for and watching the run go red naming that thing, which is the four readings
above. Every claim in this body carries the command that produced it.

Who read it

Nobody else. This change carries no second reader, and the evidence above stands
in place of one: the eight-way hand reading that found the gap, the four
mutations replayed against the cases, and the two tool runs at the two commits.
The ruleset on this branch requires no approving review, so the merge is not
evidence of one either.

roster.Named carried no test. It is the second door on the roster file, the
one the refresh reads through before anything can be asked about the
repositories a row names, and all three of its refusals were reached by
nothing: a file that is not the array of rows, a row naming no repository,
and a file holding no row at all. Every one of them could have stopped
refusing with the suite green, and what the door hands back goes into the
refresh that produces the record the build later reads as answered.

Found by reading the survivor and uncovered lists of the mutation run this
issue records rather than its score. Of the eight mutants the tool reports as
NOT COVERED in this package, four sit in Named. Applied by hand at 3db5bc8,
in a clone outside the working tree, each one leaves the suite green:

    internal/roster/roster.go:217  err != nil       -> err == nil
    internal/roster/roster.go:224  == ""            -> != ""
    internal/roster/roster.go:226  i+1              -> i-1
    internal/roster/roster.go:231  len(names) == 0  -> len(names) != 0

The four cases here are one per refusal plus two neighbours: a file breaking
nothing reads into the three repositories it declares in order, and a file
the parser refuses three times over still comes back out of this door, which
is the property that makes it a separate door rather than a flag on the first
one. Each of the four mutations above was then applied again with these cases
in and produced a red run naming the refusal it removed.

The row number is compared against the whole opening of the reason rather
than a fragment of it, which is the rule the table beside this one already
follows: a reason saying row 1 where the fixture broke the third row still
reads as a refusal about a row and sends somebody to the wrong line. The
fixture breaks the third row, so counting from zero and counting the wrong
row are both separated from the right answer.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex
iderex merged commit 0112a59 into main Aug 27, 2026
17 checks passed
@iderex
iderex deleted the tests/the-door-the-refresh-reads-the-roster-through branch August 27, 2026 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant