feat(audit): declare a test suite intentionally not run in CI (reason + expiry), so deliberate gaps stop showing as uncovered - #404
Merged
Conversation
… + expiry), so deliberate gaps stop showing as uncovered
[[project.not_run_in_ci]] { target, reason, until } lets a project say a
test target runs in no CI job on purpose. momfriend's MomFriendCoreTests is
the case: it needs on-device models, so CI builds it and never runs it, and
none of the fixes the uncovered report offers fit.
- config: all three fields required. Rejected at load: an unknown key (plain
struct, so the manifestTables reflection covers it), an empty reason, a
missing or malformed until, a duplicate target, a target also declared in
covered_elsewhere, and a manifest with no xcodeproj (the declaration would
never be evaluated).
- testtargets.Deliberate, after Apply: an in-term declaration moves its
target out of Uncovered/Unchecked onto its own printed line. Past until
(the whole day inclusive), the target stays reported and the audit exits
4. A declaration is stale if the target is missing, or a selector, a
verified covered_elsewhere or a workflow now runs it.
- audit (main.go, audit section only): wires it in and adds expired
declarations to the exit-4 case. They are still evaluated when the
.xcodeproj is absent, so an expiry cannot silently stop firing.
- fleet: Report.Blocking() blocks on an expired declaration, using the same
testtargets.Expired, so fleet and audit agree (see #358 for the orphan
divergence, not addressed here).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
#400 made
lacquer auditreport local-package suites no CI job runs. One real case is deliberate: momfriend'sMomFriendCoreTestsneeds on-device models, so CI builds it (swift build --build-tests) and never runs it. None of the report's fixes fit (a selector, aswift teststep,covered_elsewhere), so the entry stayed reported forever, which teaches people to skip the report.This adds an honest, reasoned and time-boxed declaration:
Spelling.
[[project.not_run_in_ci]]sits beside[[project.covered_elsewhere]], which makes the opposite claim ("a workflow I own runs it"). I kept the suggested spelling. It uses the same array-of-tables form, under[project], where the audit's test-target inputs already live.Behaviour
deliberately not run in CI: <target> — <reason> (until <date>).until(the whole day is inclusive, same construction asdepignore/exclusion): the target stays reported, a "declarations that have EXPIRED" block names it, andauditexits 4, the code an expireddependabot_ignore/excludeuses..xcodeprojis absent (multimeter's state), so an expiry can't silently stop being checked.covered_elsewhere, or a workflow the audit sees running it. The message names which one.Validation (
internal/config)until(e.g.2026-02-30), and an unquoted TOML datecovered_elsewhere. At most one of the two can be true.[project].xcodeproj. The audit reads targets from it, so the declaration and its date would never be evaluated.sync/fixkeep working. Expiry is the audit's job, as it is for[baseline.relax].lacquer fleet:Report.Blocking()now blocks on an expired declaration, using the sametesttargets.Expiredthe audit uses, so the two can't disagree about the day it lapses. It also appears in the notes and on the expiry horizon.Blocking()drifting from audit's exit codes. This PR adds no new drift and leaves fix(fleet): Blocking() claims to mirror audit's exit codes and does not check orphans #358's orphan part alone.cmd/lacquer/main.go: audit section only (passes the declarations in, and adds them to the exit-4 case). No console code touched.Docs:
site/src/content/docs/reference/commands.mdhas a new "A suite deliberately not run in CI" section, and the audit exit-code row and the package-suite paragraph now mention it.guides/ios-rules.mdis a docsmirror ofprofiles/ios/CLAUDE.ios.mdand is outside this change.Rollout note: a lacquer older than this release rejects a manifest carrying the key ("unknown key", exit 1), as measured below. So momfriend should add its declaration only once its CI's pinned lacquer includes this release.
Tests (written first, watched fail)
internal/config/not_run_in_ci_test.go: loads; the reflected table isreason,target,until; each validation error; past-until still loads.internal/testtargets/notrun_test.go: a momfriend-shaped fixture (ios/MomFriend.xcodeproj+ local packageios/MomFriendCore, CI builds it and never runs it).covered_elsewherecmd/lacquer/not_run_in_ci_test.go(wiring throughrun([]string{"audit"})): undeclared → exit 0 and uncovered; in term → its own line, exit 0; expired → exit 4; expired with the.xcodeprojabsent → exit 4.internal/fleet/fleet_test.go: expired →Blocking(), named in notes; in term (the until day itself) → not blocking, and on the horizon.Local run of this repo's CI bar:
gofmtclean,go vet ./...,go build ./...,go test -raceon all packages exceptinternal/shipped(all ok), andgo test ./internal/shipped/(ok, 100s).Mutation testing (CLAUDE.md rule 2)
This ran on a separate copy of the tree. Each mutation was applied, the package's tests were run, and the file was restored and byte-verified. 33 mutations; every one is killed by a named test. The first pass had one survivor: dropping
len(r.NotRun) == 0from Format's empty check. A report whose only content was a deliberate line printed nothing, so the exception was invisible.TestADeclarationAloneIsStillPrintedwas added and kills it. Six first-pass mutations didn't compile (Go unused-variable errors), so they proved nothing. They were rewritten to compile and re-run, and all six are killed.now.After(d))-1ns+2daysAfter→BeforeBlocking()ignores itFleet dry-run
lacquer-beforebuilt fromorigin/main(654e0c8, v1.41.1);lacquer-afterbuilt from this branch.LACQUER_ROOT(agit archiveoforigin/main) for both.--depth 1clones of all 15 projects infleet-ops/fleet.tomlthat have a repo, cloned today.git status --porcelainwas empty in every clone afterwards. No live checkout was touched, and nothing was pushed anywhere.not_run_in_ci, so no finding moved. The only change is the two new advice lines, in the 7 repos that have an uncovered-targets section. Zero lines were removed in any repo.Every changed line, all 15 repos (each line appears in 7 repos: dailybread, flare, kit, momfriend, ShelfLife, steps, Windsock):
Positive control: momfriend, with the declaration added locally
This was a scratch copy of the momfriend clone with the exact
[[project.not_run_in_ci]]block above appended to.lacquer.toml. Today is 2026-09-18.lacquer-before, declaration presentload manifest: … has unknown key(s)(hence the rollout note above)lacquer-after,until = "2026-12-31"(in term)lacquer-after,until = "2026-09-17"(expired)lacquer-after fleetover the expired copy + the pristine cloneFAIL momfriend-positive EXPIRED not_run_in_ci MomFriendCoreTests (2026-09-17)In term, compared with the undeclared audit of the same commit (the lines that differ only by the temp-dir path are omitted):
(momfriend's other uncovered target stays listed. Only the package paragraph goes, because the declared suite was the only package suite left.)
Expired (
until = "2026-09-17"): the uncovered list is unchanged from undeclared, plus: