package main import ( "fmt" "go/ast" "go/parser" "go/token" "io/fs" "os" "path/filepath" "regexp" "sort" "strconv" "strings" "testing" ) // Moves into the home region, part 2, Task 3: the static half of Review // Focus 1 (supplied regions are @home). This is a pin, not a proof. It // lists every production call site that hands the claim a region on a // move's path, with the argument expression text a reviewer saw and the // runtime witness that drives the site with a home target from clean claim // state, or "named-only" with the guard that keeps a home target away. The // proof is the runtime one (TestHomeTargetFreshStateShowsOnlyTheHomeToken // in internal/host, where a wrong spelling cannot hide). This test // classifies nothing as safe by its shape: a new site, an edited argument // or a vanished site fails until a reviewer updates the row and names a // witness. // homeRegionSite is one inventoried site. Callee is the call with the // region parameter in parentheses, the composite type with the field in // braces, or the assigned field; Arg is the argument expression as // go/printer prints it. type homeRegionSite struct { File, Func, Callee, Arg string // Witness is the runtime test that drives the site with a home target // from clean claim state, or "named-only: ". Witness string // Pending names the task that writes Witness while it is not written // yet; the test logs the row instead of requiring the test. Pending string // Note says why the row's argument is what it is, where that is not // the placement-derived @home a reviewer would expect. Note string } // The witnesses. const ( wFresh = "TestHomeTargetFreshStateShowsOnlyTheHomeToken" wBegin = "TestPlan13BeginClaimMoveHomeAndNamed" wConsoleCommt = "TestMoveRecoveryCommitIntoHome" wRevert = "TestMoveIntoHomePreFlipRevert" wAbort = "TestMoveIntoHomeAbortParksTheHomeLane" wClean = "TestMoveIntoHomeCleanRollback" wDirty = "TestMoveIntoHomeDirtyReturn" wFenced = "TestMoveIntoHomeFencedTargetRelease" wLost = "TestMoveIntoHomeDeclareLost" lossNote = "retained, validated intent: claimLossIntent copies TargetRegion from op.intent.to (@home, written by the start CAS) and the loss record signs it; SourceRegion is the record's From through park.PlacementRegion" ) // homeRegionCallSites is the golden inventory, sorted by file, function and // callee. var homeRegionCallSites = []homeRegionSite{ {File: "internal/console/apiinstancemove.go", Func: "Server.handleAPIInstanceMoveRequest", Callee: "hostclaim.BeginMove(targetRegion)", Arg: "to", Witness: "named-only: without host claims the door refuses a home target before the plain path's BeginMove (MV-N510, Ruling R1; TestMoveDoorPlainPathRefusesHomeTarget); a host-claim console starts through BeginClaimMove"}, {File: "internal/host/movesabort.go", Func: "movesWorker.abortClaim", Callee: "hostclaim.Abort(targetRegion)", Arg: "park.PlacementRegion(rec.Move.To)", Witness: wAbort}, {File: "internal/host/movescut.go", Func: "movesWorker.revertClaim", Callee: "hostclaim.Revert(targetRegion)", Arg: "park.PlacementRegion(rec.Move.To)", Witness: wRevert}, {File: "internal/host/movesinstall.go", Func: "installRun.parking", Callee: "hostclaim.QueueResidueParks(region)", Arg: "cfg.parkRegion()", Witness: wFresh}, {File: "internal/host/movesland.go", Func: "landingRun.checking", Callee: "hostclaim.Touch(regions)", Arg: "nil", Witness: wFresh, Note: "the landing touches its own box only; the source round touched the region"}, {File: "internal/host/moveslandcommit.go", Func: "landingRun.commitClaim", Callee: "hostclaim.Commit(sourceRegion)", Arg: "park.PlacementRegion(l.rec.Move.From)", Witness: wFresh}, {File: "internal/host/moveslandcommit.go", Func: "landingRun.commitClaim", Callee: "hostclaim.Commit(targetRegion)", Arg: "cfg.parkRegion()", Witness: wFresh}, {File: "internal/host/movespass.go", Func: "stagingLeaseKey", Callee: "moves.StagingLeasePrefix(targetRegion)", Arg: "r", Witness: wFresh, Note: "r is park.PlacementRegion(targetRegion), the lane's record code"}, {File: "internal/host/movesphase1.go", Func: "movesWorker.sourceRound", Callee: "hostclaim.Touch(regions)", Arg: "[]string{park.PlacementRegion(rec.Move.To)}", Witness: wFresh}, {File: "internal/host/movesrecover.go", Func: "recoverRun.probed", Callee: "hostclaim.QueueReturnLanePark(sourceRegion)", Arg: "cfg.parkRegion()", Witness: wDirty, Note: "runs on the named source; its region is the source's"}, {File: "internal/host/movesrollback.go", Func: "rollbackRun.claimReleased", Callee: "hostclaim.TargetReleased(targetRegion)", Arg: "cfg.parkRegion()", Witness: wClean}, {File: "internal/host/movesrollback.go", Func: "rollbackRun.remote", Callee: "hostclaim.QueueStagingPark(region)", Arg: "cfg.parkRegion()", Witness: wClean}, {File: "internal/hostops/move_enrollment.go", Func: "BeginClaimMove", Callee: "hostclaim.BeginAdmittedMove(targetRegion)", Arg: "park.PlacementRegion(to)", Witness: wBegin}, {File: "internal/hostops/move_fence_release.go", Func: "MoveRecoveryGrant.releaseFencedTarget", Callee: "hostclaim.FencedTargetRelease{SourceRegion}", Arg: "park.PlacementRegion(m.From)", Witness: wFenced}, {File: "internal/hostops/move_fence_release.go", Func: "MoveRecoveryGrant.releaseFencedTarget", Callee: "hostclaim.FencedTargetRelease{TargetRegion}", Arg: "park.PlacementRegion(m.To)", Witness: wFenced}, {File: "internal/hostops/move_fence_release.go", Func: "MoveRecoveryGrant.releaseFencedTarget", Callee: "hostclaim.ReleaseFencedTarget(r)", Arg: "r", Witness: wFenced}, {File: "internal/hostops/move_loss_grant.go", Func: "lossScopedStore.claim", Callee: "hostclaim.ReleaseLostTarget(r)", Arg: "rel", Witness: wLost, Note: "rel is moveLossRelease over the intent read back from the signed loss record (lossIntentOf), not a new derivation"}, {File: "internal/hostops/move_loss_owner.go", Func: "claimLossIntent", Callee: "moveLossIntent{TargetRegion}", Arg: `op.Intent["to"]`, Witness: wLost, Note: lossNote}, {File: "internal/hostops/move_loss_owner.go", Func: "moveLossOwner.FinishLoss", Callee: "hostclaim.ReleaseLostTarget(r)", Arg: "moveLossRelease(in, durable, c.Holder, loss.Host)", Witness: wLost}, {File: "internal/hostops/move_loss_owner.go", Func: "moveLossRelease", Callee: "hostclaim.LostTargetRelease{SourceRegion}", Arg: "in.SourceRegion", Witness: wLost, Note: lossNote}, {File: "internal/hostops/move_loss_owner.go", Func: "moveLossRelease", Callee: "hostclaim.LostTargetRelease{TargetRegion}", Arg: "in.TargetRegion", Witness: wLost, Note: lossNote}, {File: "internal/hostops/move_loss_owner.go", Func: "recordLossIntent", Callee: "in.SourceRegion =", Arg: "park.PlacementRegion(m.From)", Witness: wLost, Note: lossNote}, {File: "internal/hostops/move_recovery_grant.go", Func: "MoveRecoveryGrant.Claim", Callee: "hostclaim.Commit(sourceRegion)", Arg: "park.PlacementRegion(m.From)", Witness: wConsoleCommt}, {File: "internal/hostops/move_recovery_grant.go", Func: "MoveRecoveryGrant.Claim", Callee: "hostclaim.Commit(targetRegion)", Arg: "park.PlacementRegion(m.To)", Witness: wConsoleCommt}, } // homeRegionCallees are the claim transitions and the lease key that take a // region, by package, with the index and name of each region parameter. var homeRegionCallees = map[string]map[string][]homeRegionParam{ "internal/hostclaim": { "Touch": {{1, "regions"}}, "Abort": {{1, "targetRegion"}}, "Revert": {{2, "targetRegion"}}, "Commit": {{1, "sourceRegion"}, {3, "targetRegion"}}, "QueueLocationPark": {{1, "region"}}, "QueueStagingPark": {{1, "region"}}, "QueueResidueParks": {{1, "region"}}, "QueueReturnLanePark": {{1, "sourceRegion"}}, "TargetReleased": {{2, "targetRegion"}}, "BeginMove": {{4, "targetRegion"}}, "BeginAdmittedMove": {{4, "targetRegion"}}, "ReleaseFencedTarget": {{0, "r"}}, "ReleaseLostTarget": {{0, "r"}}, }, "internal/moves": { "StagingLeasePrefix": {{0, "targetRegion"}}, }, } type homeRegionParam struct { index int name string } // homeRegionLiterals are the composite types whose region fields are // inventoried: the claim's two release literals, and the loss owner's own // intent (claimLossIntent builds it). The empty package is a type of the // scanned file's own package. var homeRegionLiterals = map[string]map[string]bool{ "internal/hostclaim": {"FencedTargetRelease": true, "LostTargetRelease": true}, "": {"moveLossIntent": true}, } // homeRegionFields are those literals' region fields. An assignment to one // (recordLossIntent's in.SourceRegion) is a site too. var homeRegionFields = map[string]bool{"TargetRegion": true, "SourceRegion": true} var homeRegionDirs = []string{"internal/host", "internal/hostops", "internal/console"} const homeRegionModule = "github.com/carlosframework/platform/" // scanHomeRegionSites lists the sites in every non-test Go file under the // scanned directories. overlay replaces a file's source by its // slash-separated path from root ("" removes the file) or adds a file. func scanHomeRegionSites(root string, overlay map[string]string) ([]homeRegionSite, error) { files := map[string]bool{} for _, dir := range homeRegionDirs { err := filepath.WalkDir(filepath.Join(root, dir), func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { return nil } rel, err := filepath.Rel(root, path) if err != nil { return err } files[filepath.ToSlash(rel)] = true return nil }) if err != nil { return nil, err } } for path := range overlay { files[path] = true } var names []string for f := range files { names = append(names, f) } sort.Strings(names) var out []homeRegionSite for _, name := range names { var src any if body, ok := overlay[name]; ok { if body == "" { continue } src = body } f, err := parser.ParseFile(token.NewFileSet(), filepath.Join(root, filepath.FromSlash(name)), src, parser.SkipObjectResolution) if err != nil { return nil, err } out = append(out, homeRegionFileSites(name, f)...) } return out, nil } // homeRegionFileSites lists one file's sites, each under the function (or // method, by receiver type) that encloses it; a function literal's sites // are its enclosing declaration's. func homeRegionFileSites(name string, f *ast.File) []homeRegionSite { // The file's local names for the scanned packages. local := map[string]string{} for _, imp := range f.Imports { path, _ := strconv.Unquote(imp.Path.Value) rel, ok := strings.CutPrefix(path, homeRegionModule) if !ok { continue } if homeRegionCallees[rel] == nil && homeRegionLiterals[rel] == nil { continue } n := filepath.Base(rel) if imp.Name != nil { n = imp.Name.Name } local[n] = rel } resolve := func(e ast.Expr) (rel, sel string, ok bool) { switch x := e.(type) { case *ast.SelectorExpr: id, isID := x.X.(*ast.Ident) if !isID { return "", "", false } rel, ok = local[id.Name] return rel, x.Sel.Name, ok case *ast.Ident: return "", x.Name, true } return "", "", false } var out []homeRegionSite visit := func(fn string, root ast.Node) { ast.Inspect(root, func(n ast.Node) bool { switch x := n.(type) { case *ast.CallExpr: rel, callee, ok := resolve(x.Fun) if !ok || rel == "" { return true } for _, p := range homeRegionCallees[rel][callee] { arg := "" if p.index < len(x.Args) { arg = plan13Expr(x.Args[p.index]) } out = append(out, homeRegionSite{File: name, Func: fn, Callee: filepath.Base(rel) + "." + callee + "(" + p.name + ")", Arg: arg}) } case *ast.CompositeLit: rel, typ, ok := resolve(x.Type) if !ok || !homeRegionLiterals[rel][typ] { return true } if rel != "" { typ = filepath.Base(rel) + "." + typ } for _, el := range x.Elts { kv, isKV := el.(*ast.KeyValueExpr) if !isKV { continue } if key, isID := kv.Key.(*ast.Ident); isID && homeRegionFields[key.Name] { out = append(out, homeRegionSite{File: name, Func: fn, Callee: typ + "{" + key.Name + "}", Arg: plan13Expr(kv.Value)}) } } case *ast.AssignStmt: for i, lhs := range x.Lhs { sel, isSel := lhs.(*ast.SelectorExpr) if !isSel || !homeRegionFields[sel.Sel.Name] { continue } arg := "" if len(x.Rhs) == len(x.Lhs) { arg = plan13Expr(x.Rhs[i]) } out = append(out, homeRegionSite{File: name, Func: fn, Callee: plan13Expr(sel) + " =", Arg: arg}) } } return true }) } for _, d := range f.Decls { switch x := d.(type) { case *ast.FuncDecl: fn := x.Name.Name if x.Recv != nil && len(x.Recv.List) == 1 { t := x.Recv.List[0].Type if star, ok := t.(*ast.StarExpr); ok { t = star.X } if ix, ok := t.(*ast.IndexExpr); ok { t = ix.X } fn = plan13Expr(t) + "." + fn } if x.Body != nil { visit(fn, x.Body) } case *ast.GenDecl: visit("", x) } } return out } // homeRegionDiff compares the sites found with the inventory. A site is // keyed by its file, function and callee; within one key the argument // texts are matched as a multiset. What is left over is a changed site // (one found and one row unmatched under the same key), an unknown site (a // found site with no row left), or a stale row (a row with no site left). func homeRegionDiff(found, inventory []homeRegionSite) []string { type key struct{ file, fn, callee string } want := map[key][]string{} for _, r := range inventory { k := key{r.File, r.Func, r.Callee} want[k] = append(want[k], r.Arg) } got := map[key][]string{} for _, s := range found { k := key{s.File, s.Func, s.Callee} got[k] = append(got[k], s.Arg) } keys := map[key]bool{} for k := range want { keys[k] = true } for k := range got { keys[k] = true } const fix = "; add or update its row in homeRegionCallSites (home_region_args_test.go) and name the runtime witness that drives it with a home target from clean claim state" var out []string for k := range keys { rows := append([]string(nil), want[k]...) var sites []string for _, a := range got[k] { if i := indexOf(rows, a); i >= 0 { rows = append(rows[:i], rows[i+1:]...) continue } sites = append(sites, a) } n := min(len(rows), len(sites)) for i := 0; i < n; i++ { out = append(out, fmt.Sprintf("changed site: %s %s %s passes %s, its row says %s%s", k.file, k.fn, k.callee, sites[i], rows[i], fix)) } for _, a := range sites[n:] { out = append(out, fmt.Sprintf("unknown site: %s %s %s passes %s%s", k.file, k.fn, k.callee, a, fix)) } for _, a := range rows[n:] { out = append(out, fmt.Sprintf("stale row: %s %s %s passing %s is no longer there%s", k.file, k.fn, k.callee, a, fix)) } } sort.Strings(out) return out } func indexOf(xs []string, x string) int { for i, s := range xs { if s == x { return i } } return -1 } // homeRegionTestNames is every top-level test function name in the // repository's test files. func homeRegionTestNames(t *testing.T) map[string]bool { t.Helper() re := regexp.MustCompile(`(?m)^func (Test\w+)\(t \*testing\.T\)`) out := map[string]bool{} err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { if path != "." && (strings.HasPrefix(d.Name(), ".") || d.Name() == "node_modules" || d.Name() == "testdata") { return filepath.SkipDir } return nil } if !strings.HasSuffix(path, "_test.go") { return nil } body, err := os.ReadFile(path) if err != nil { return err } for _, m := range re.FindAllStringSubmatch(string(body), -1) { out[m[1]] = true } return nil }) if err != nil { t.Fatal(err) } return out } // homeRegionOverlay is path's source with each old, new pair applied: old // must be there exactly once. func homeRegionOverlay(t *testing.T, path string, pairs ...string) map[string]string { t.Helper() raw, err := os.ReadFile(filepath.FromSlash(path)) if err != nil { t.Fatal(err) } body := string(raw) for i := 0; i+1 < len(pairs); i += 2 { if n := strings.Count(body, pairs[i]); n != 1 { t.Fatalf("fixture: %s holds %q %d times, want once", path, pairs[i], n) } body = strings.Replace(body, pairs[i], pairs[i+1], 1) } return map[string]string{path: body} } // homeRegionWant fails unless problems hold one starting with kind and // containing every part. func homeRegionWant(t *testing.T, problems []string, kind string, parts ...string) { t.Helper() for _, p := range problems { if !strings.HasPrefix(p, kind) { continue } all := true for _, part := range parts { all = all && strings.Contains(p, part) } if all { return } } t.Fatalf("no %q naming %q among %q", kind, parts, problems) } func TestMovesHomeRegionCallSiteInventory(t *testing.T) { found, err := scanHomeRegionSites(".", nil) if err != nil { t.Fatal(err) } t.Run("the tree matches the inventory", func(t *testing.T) { if problems := homeRegionDiff(found, homeRegionCallSites); len(problems) != 0 { for _, s := range found { t.Logf("found {File: %q, Func: %q, Callee: %q, Arg: %q}", s.File, s.Func, s.Callee, s.Arg) } for _, p := range problems { t.Error(p) } } }) t.Run("every row names a witness", func(t *testing.T) { tests := homeRegionTestNames(t) for _, r := range homeRegionCallSites { switch { case strings.HasPrefix(r.Witness, "named-only: ") && len(r.Witness) > len("named-only: "): case r.Witness == "": t.Errorf("%s %s %s names no witness", r.File, r.Func, r.Callee) case r.Pending != "": if tests[r.Witness] { t.Errorf("%s %s %s: %s is written; clear the row's Pending (%s)", r.File, r.Func, r.Callee, r.Witness, r.Pending) } t.Logf("pending (%s): %s %s %s, witness %s", r.Pending, r.File, r.Func, r.Callee, r.Witness) case !tests[r.Witness]: t.Errorf("%s %s %s: the witness %s is not a test in this repository", r.File, r.Func, r.Callee, r.Witness) } } }) // The fixtures: each edit the inventory exists to catch. t.Run("a new Commit site supplying a roster region is unknown", func(t *testing.T) { src := "package host\n\nimport \"github.com/carlosframework/platform/internal/hostclaim\"\n\n" + "func homeRegionFixture(rr regionRow) error {\n\treturn hostclaim.Commit(id, from, src, rr.row.Region, scope, staging, newID)(c)\n}\n" found, err := scanHomeRegionSites(".", map[string]string{"internal/host/zz_home_region_fixture.go": src}) if err != nil { t.Fatal(err) } homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "unknown site", "internal/host/zz_home_region_fixture.go", "hostclaim.Commit(targetRegion)", "rr.row.Region") }) t.Run("home_region handed to QueueStagingPark at a new site is unknown", func(t *testing.T) { src := "package host\n\nimport (\n\t\"github.com/carlosframework/platform/internal/hostclaim\"\n\t\"github.com/carlosframework/platform/internal/park\"\n)\n\n" + "func homeRegionFixture(c *hostclaim.Claim) error {\n\tto := c.Op.Intent[\"home_region\"]\n\treturn hostclaim.QueueStagingPark(id, park.PlacementRegion(to), scope, newID)(c)\n}\n" found, err := scanHomeRegionSites(".", map[string]string{"internal/host/zz_home_region_fixture.go": src}) if err != nil { t.Fatal(err) } homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "unknown site", "hostclaim.QueueStagingPark(region)", "park.PlacementRegion(to)") }) t.Run("home_region handed to QueueStagingPark at an inventoried site is changed", func(t *testing.T) { overlay := homeRegionOverlay(t, "internal/host/movesrollback.go", "scope := moves.StagingScope(host, id, obs.Claim.Holder.Incarnation, staged.Bound())", "scope := moves.StagingScope(host, id, obs.Claim.Holder.Incarnation, staged.Bound())\n\t\tto := obs.Claim.Op.Intent[\"home_region\"]", "hostclaim.QueueStagingPark(id, cfg.parkRegion(), scope, obligationID)", "hostclaim.QueueStagingPark(id, park.PlacementRegion(to), scope, obligationID)") found, err := scanHomeRegionSites(".", overlay) if err != nil { t.Fatal(err) } homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "changed site", "rollbackRun.remote", "hostclaim.QueueStagingPark(region)", "park.PlacementRegion(to)") }) t.Run("a FencedTargetRelease naming home_region is changed", func(t *testing.T) { found, err := scanHomeRegionSites(".", homeRegionOverlay(t, "internal/hostops/move_fence_release.go", "TargetRegion: park.PlacementRegion(m.To)", `TargetRegion: o.Claim.Op.Intent["home_region"]`)) if err != nil { t.Fatal(err) } homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "changed site", "hostclaim.FencedTargetRelease{TargetRegion}", `o.Claim.Op.Intent["home_region"]`) }) t.Run("a site that no longer exists leaves a stale row", func(t *testing.T) { found, err := scanHomeRegionSites(".", homeRegionOverlay(t, "internal/host/movespass.go", "return moves.StagingLeasePrefix(r, host), nil", `return "control/staging-leases/" + r + "/" + host, nil`)) if err != nil { t.Fatal(err) } homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "stale row", "stagingLeaseKey", "moves.StagingLeasePrefix(targetRegion)") }) t.Run("an aliased import is still found", func(t *testing.T) { src := "package host\n\nimport hc \"github.com/carlosframework/platform/internal/hostclaim\"\n\n" + "func homeRegionFixture() error {\n\treturn hc.Touch(id, []string{\"ie\"}, nil)(c)\n}\n" found, err := scanHomeRegionSites(".", map[string]string{"internal/console/zz_home_region_fixture.go": src}) if err != nil { t.Fatal(err) } homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "unknown site", "hostclaim.Touch(regions)", `[]string{"ie"}`) }) }