textmachine/backend/internal/archguard/shippingtree_test.go

114 lines
4.8 KiB
Go

package archguard
import (
"os"
"path/filepath"
"strings"
"testing"
"golang.org/x/tools/go/analysis/checker"
"golang.org/x/tools/go/packages"
)
// packageFloor is the smallest number of packages a healthy load returns. Without it, a run that loads
// NOTHING — a bad pattern, a rename, a broken module — is indistinguishable from a clean tree and the
// guard silently becomes decoration. `go vet` is loud about a package that fails to build, but it is
// perfectly quiet about analyzing zero of them, so the floor is asserted rather than assumed. The module
// holds ~20 packages (~40 with test variants); the floor catches ZERO, it does not track the count.
const packageFloor = 15
// TestInvariantsHoldInTheShippingTree runs the analyzers over the REAL module from an ordinary `go test`.
//
// The invariants moved out of _test.go files into go/analysis, which made them depend on somebody typing
// `make vet` — `go test -vet=` refuses a custom analyzer, so the ordinary battery stopped carrying them.
// This puts them back without a second implementation: the same Analyzers() the vet tool wires up, driven
// here through the checker API instead of unitchecker.
func TestInvariantsHoldInTheShippingTree(t *testing.T) {
// Every analyzer, from the same slice cmd/tmvet uses: dropping one from Analyzers() must fail HERE,
// which is the property the old walk's file floor used to provide for its own coverage.
if got := len(Analyzers()); got != 3 {
t.Fatalf("Analyzers() returns %d analyzers, want 3 (stageseam, requestseam, egressseam) — an "+
"invariant that is not in this slice is enforced by nothing", got)
}
cfg := &packages.Config{Mode: packages.LoadAllSyntax, Tests: true, Dir: "../.."}
pkgs, err := packages.Load(cfg, "textmachine/backend/...")
if err != nil {
t.Fatalf("load the module: %v", err)
}
if len(pkgs) < packageFloor {
t.Fatalf("loaded only %d packages (floor %d) — the pattern matches nothing or the module is "+
"broken, which makes this guard decoration rather than a guard", len(pkgs), packageFloor)
}
var loadErrs []string
packages.Visit(pkgs, nil, func(p *packages.Package) {
for _, e := range p.Errors {
loadErrs = append(loadErrs, e.Error())
}
})
if len(loadErrs) > 0 {
// A package that does not type-check is analyzed as nothing at all — silently, unless said here.
t.Fatalf("packages failed to load, so they were NOT analyzed:\n %s", strings.Join(loadErrs, "\n "))
}
graph, err := checker.Analyze(Analyzers(), pkgs, nil)
if err != nil {
t.Fatalf("run the analyzers: %v", err)
}
var found []string
for act := range graph.All() {
if act.Err != nil {
found = append(found, act.Analyzer.Name+" errored on "+act.Package.PkgPath+": "+act.Err.Error())
}
for _, d := range act.Diagnostics {
found = append(found, act.Package.Fset.Position(d.Pos).String()+": "+d.Message)
}
}
if len(found) > 0 {
t.Fatalf("architectural invariants violated in the shipping tree:\n %s", strings.Join(found, "\n "))
}
assertGuardVocabularyIsLive(t, pkgs)
}
// assertGuardVocabularyIsLive fails when the guards name something that no longer exists.
//
// Both halves of this analyzer set are keyed on STRINGS — the exempt lists name files, and the egress
// shape names two types. A rename on either side does not break a build and does not fail a test: the
// exemption simply stops matching (a legitimate file starts being reported, which is loud) or, far
// worse, the shape half stops matching anything at all and the guard goes quietly decorative. That
// second direction is exactly the failure mode this whole package was built to end, so it is asserted
// rather than trusted.
func assertGuardVocabularyIsLive(t *testing.T, pkgs []*packages.Package) {
t.Helper()
const modulePrefix = "textmachine/backend/"
for _, list := range [][]string{stageSeam.exempt, requestSeam.exempt, egressExempt} {
for _, f := range list {
rel, ok := strings.CutPrefix(f, modulePrefix)
if !ok {
t.Errorf("exemption %q is not inside this module — it can never match", f)
continue
}
if _, err := os.Stat(filepath.Join("../..", rel)); err != nil {
t.Errorf("exemption %q names a file that does not exist (%v) — a rename left it behind, "+
"and an exemption nobody can see is how a seam re-opens", f, err)
}
}
}
var llmPkgFound bool
for _, p := range pkgs {
if p.PkgPath != llmPkg || p.Types == nil {
continue
}
llmPkgFound = true
for _, name := range []string{llmRequestType, llmResponseType} {
if p.Types.Scope().Lookup(name) == nil {
t.Errorf("the egress shape is keyed on %s.%s, which no longer exists — renaming it "+
"silences the name-independent half of the guard without failing anything else",
llmPkg, name)
}
}
}
if !llmPkgFound {
t.Errorf("package %s was not loaded, so the egress vocabulary could not be checked", llmPkg)
}
}