Repository navigation
Per-method test coverage: a unit test and a functional test for every provider method #635
Description
Epic: #444 — The resource model.
Every provider method gets a Go unit test and a devlore-test functional test; every compensation companion gets the same. One PR per provider, starting with file.Provider.
Why now
The resource-construction campaign and its successors have repeatedly found defects that only a test at the right altitude would have caught, and several were found by accident:
SymbolicLink.Existsfollowed the link, so a deployed link verified as its target (Plan-time claiming: pending entries for string-valued resource parameters; promises recorded; products excluded #585 PR C).Remove's*Regulartype contradicted its own documentation, andremove_all's*Directoryclaim was the only thing keepingrm -rfoff a file (AnyEntry: claims that assert existence, not kind #616's audit).file.Anywas never enrolled in pre-flight's verification gate, so its claims were silently skipped (file.move unifies: any kind, no policy, move_directory retires #620 — caught by an acceptance scenario, not by a unit test).- Unexporting a base silently removed every promoted field from the Starlark surface (The taxonomy reshape: unexported base, AnyEntry variant #617 — caught by an unrelated extension test).
The pattern: behavior that no test names is behavior nobody notices changing. A per-method matrix turns those from lucky catches into guaranteed ones.
The shape
For each provider, per method:
- A Go unit test exercising the method directly — the contract at its own altitude, including the failure directions.
- A devlore-test functional test exercising it through a planned graph — the contract as an author meets it.
- The compensation companion, where one exists: forward, then undo, then the state assertion that proves the undo was real.
For file.Provider specifically: every method that accepts file.Resource must be tested against all three kinds — regular, directory, and symbolic link. That interface is new (#616), and the whole point of it is that kind is the disk's business rather than the author's; a method that quietly misbehaves on one kind is exactly the gotcha the type invites. The link cases matter most, since lstat-versus-stat is where this codebase has already been bitten twice.
Sequencing
file.Provider first — it is the largest surface, the one with a kind axis, and the one every other provider's pattern will be judged against. Then one PR per provider.
A gap analysis comes first within each PR: 3.5.x's status files already carry per-method test matrices, so the work starts by diffing that matrix against the tree rather than by writing tests blind.
Activity
David-Noble-at-work commentedon Aug 24, 2026
Concrete gaps found by measuring #616's coverage (2026-08-24) — the feature is done; these are the holes a per-method matrix would have closed, recorded here rather than lost.
A correctness item, not just coverage.
file.resource.Exists(the unexported base) is at 0% coverage and is kind-blind — it uses a followingStat:All four variants override it with lstat-plus-kind (#617/#621), so this is the pre-kind-honest predicate surviving in the one corner nothing tests. It stays reachable: the base's own
UnmarshalJSON/UnmarshalText/UnmarshalYAMLbuild a bare*resourceviadiscoverResource. Decide whether a non-taxonomy base should answerExistsat all — it is not anEntry, so arguably it should not — or make it honest.Predicates with no test at all:
Regular.MismatchesKindandDirectory.MismatchesKind— 0%. OnlySymbolicLink's is pinned. These two are what protect a deployed copy replaced by a directory, the case the oldunlinkcheck never covered and The rest of the mutator surface: remove, unlink retirement, remove_all, backup #621 extended protection to.ResourceKind'sMarshalJSON,MarshalYAML,UnmarshalJSON,UnmarshalYAML— 0%. OnlyUnmarshalTextis pinned, yet the enum is serialized into graph documents, so the JSON/YAML paths are the ones a saved plan actually travels through.Functional-only, no unit test:
file.discoverandfile.resolvereport 0% becausemake cover's per-package profile cannot see cross-package coverage — 9 devlore-test scenarios exercise them, and zero Go unit tests do. That asymmetry is precisely what this feature's "unit and functional" rule is for, and it means the reported number understates real coverage while still naming a real gap.Partial, worth a look when
file.Provideris swept:bindAuthoredValue62%,Move74%,DiscoverAny75%,checkVariableResourceSlots77%,admits83%,removeEntry89%,verifyClaimBinding87%.