carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "fmt"
5 "go/ast"
6 "go/parser"
7 "go/token"
8 "io/fs"
9 "os"
10 "path/filepath"
11 "regexp"
12 "sort"
13 "strconv"
14 "strings"
15 "testing"
16)
17
18// Moves into the home region, part 2, Task 3: the static half of Review
19// Focus 1 (supplied regions are @home). This is a pin, not a proof. It
20// lists every production call site that hands the claim a region on a
21// move's path, with the argument expression text a reviewer saw and the
22// runtime witness that drives the site with a home target from clean claim
23// state, or "named-only" with the guard that keeps a home target away. The
24// proof is the runtime one (TestHomeTargetFreshStateShowsOnlyTheHomeToken
25// in internal/host, where a wrong spelling cannot hide). This test
26// classifies nothing as safe by its shape: a new site, an edited argument
27// or a vanished site fails until a reviewer updates the row and names a
28// witness.
29
30// homeRegionSite is one inventoried site. Callee is the call with the
31// region parameter in parentheses, the composite type with the field in
32// braces, or the assigned field; Arg is the argument expression as
33// go/printer prints it.
34type homeRegionSite struct {
35 File, Func, Callee, Arg string
36 // Witness is the runtime test that drives the site with a home target
37 // from clean claim state, or "named-only: <the guard>".
38 Witness string
39 // Pending names the task that writes Witness while it is not written
40 // yet; the test logs the row instead of requiring the test.
41 Pending string
42 // Note says why the row's argument is what it is, where that is not
43 // the placement-derived @home a reviewer would expect.
44 Note string
45}
46
47// The witnesses.
48const (
49 wFresh = "TestHomeTargetFreshStateShowsOnlyTheHomeToken"
50 wBegin = "TestPlan13BeginClaimMoveHomeAndNamed"
51 wConsoleCommt = "TestMoveRecoveryCommitIntoHome"
52 wRevert = "TestMoveIntoHomePreFlipRevert"
53 wAbort = "TestMoveIntoHomeAbortParksTheHomeLane"
54 wClean = "TestMoveIntoHomeCleanRollback"
55 wDirty = "TestMoveIntoHomeDirtyReturn"
56 wFenced = "TestMoveIntoHomeFencedTargetRelease"
57 wLost = "TestMoveIntoHomeDeclareLost"
58 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"
59)
60
61// homeRegionCallSites is the golden inventory, sorted by file, function and
62// callee.
63var homeRegionCallSites = []homeRegionSite{
64 {File: "internal/console/apiinstancemove.go", Func: "Server.handleAPIInstanceMoveRequest", Callee: "hostclaim.BeginMove(targetRegion)", Arg: "to",
65 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"},
66 {File: "internal/host/movesabort.go", Func: "movesWorker.abortClaim", Callee: "hostclaim.Abort(targetRegion)", Arg: "park.PlacementRegion(rec.Move.To)", Witness: wAbort},
67 {File: "internal/host/movescut.go", Func: "movesWorker.revertClaim", Callee: "hostclaim.Revert(targetRegion)", Arg: "park.PlacementRegion(rec.Move.To)", Witness: wRevert},
68 {File: "internal/host/movesinstall.go", Func: "installRun.parking", Callee: "hostclaim.QueueResidueParks(region)", Arg: "cfg.parkRegion()", Witness: wFresh},
69 {File: "internal/host/movesland.go", Func: "landingRun.checking", Callee: "hostclaim.Touch(regions)", Arg: "nil", Witness: wFresh,
70 Note: "the landing touches its own box only; the source round touched the region"},
71 {File: "internal/host/moveslandcommit.go", Func: "landingRun.commitClaim", Callee: "hostclaim.Commit(sourceRegion)", Arg: "park.PlacementRegion(l.rec.Move.From)", Witness: wFresh},
72 {File: "internal/host/moveslandcommit.go", Func: "landingRun.commitClaim", Callee: "hostclaim.Commit(targetRegion)", Arg: "cfg.parkRegion()", Witness: wFresh},
73 {File: "internal/host/movespass.go", Func: "stagingLeaseKey", Callee: "moves.StagingLeasePrefix(targetRegion)", Arg: "r", Witness: wFresh,
74 Note: "r is park.PlacementRegion(targetRegion), the lane's record code"},
75 {File: "internal/host/movesphase1.go", Func: "movesWorker.sourceRound", Callee: "hostclaim.Touch(regions)", Arg: "[]string{park.PlacementRegion(rec.Move.To)}", Witness: wFresh},
76 {File: "internal/host/movesrecover.go", Func: "recoverRun.probed", Callee: "hostclaim.QueueReturnLanePark(sourceRegion)", Arg: "cfg.parkRegion()", Witness: wDirty,
77 Note: "runs on the named source; its region is the source's"},
78 {File: "internal/host/movesrollback.go", Func: "rollbackRun.claimReleased", Callee: "hostclaim.TargetReleased(targetRegion)", Arg: "cfg.parkRegion()", Witness: wClean},
79 {File: "internal/host/movesrollback.go", Func: "rollbackRun.remote", Callee: "hostclaim.QueueStagingPark(region)", Arg: "cfg.parkRegion()", Witness: wClean},
80 {File: "internal/hostops/move_enrollment.go", Func: "BeginClaimMove", Callee: "hostclaim.BeginAdmittedMove(targetRegion)", Arg: "park.PlacementRegion(to)", Witness: wBegin},
81 {File: "internal/hostops/move_fence_release.go", Func: "MoveRecoveryGrant.releaseFencedTarget", Callee: "hostclaim.FencedTargetRelease{SourceRegion}", Arg: "park.PlacementRegion(m.From)", Witness: wFenced},
82 {File: "internal/hostops/move_fence_release.go", Func: "MoveRecoveryGrant.releaseFencedTarget", Callee: "hostclaim.FencedTargetRelease{TargetRegion}", Arg: "park.PlacementRegion(m.To)", Witness: wFenced},
83 {File: "internal/hostops/move_fence_release.go", Func: "MoveRecoveryGrant.releaseFencedTarget", Callee: "hostclaim.ReleaseFencedTarget(r)", Arg: "r", Witness: wFenced},
84 {File: "internal/hostops/move_loss_grant.go", Func: "lossScopedStore.claim", Callee: "hostclaim.ReleaseLostTarget(r)", Arg: "rel", Witness: wLost,
85 Note: "rel is moveLossRelease over the intent read back from the signed loss record (lossIntentOf), not a new derivation"},
86 {File: "internal/hostops/move_loss_owner.go", Func: "claimLossIntent", Callee: "moveLossIntent{TargetRegion}", Arg: `op.Intent["to"]`, Witness: wLost, Note: lossNote},
87 {File: "internal/hostops/move_loss_owner.go", Func: "moveLossOwner.FinishLoss", Callee: "hostclaim.ReleaseLostTarget(r)", Arg: "moveLossRelease(in, durable, c.Holder, loss.Host)", Witness: wLost},
88 {File: "internal/hostops/move_loss_owner.go", Func: "moveLossRelease", Callee: "hostclaim.LostTargetRelease{SourceRegion}", Arg: "in.SourceRegion", Witness: wLost, Note: lossNote},
89 {File: "internal/hostops/move_loss_owner.go", Func: "moveLossRelease", Callee: "hostclaim.LostTargetRelease{TargetRegion}", Arg: "in.TargetRegion", Witness: wLost, Note: lossNote},
90 {File: "internal/hostops/move_loss_owner.go", Func: "recordLossIntent", Callee: "in.SourceRegion =", Arg: "park.PlacementRegion(m.From)", Witness: wLost, Note: lossNote},
91 {File: "internal/hostops/move_recovery_grant.go", Func: "MoveRecoveryGrant.Claim", Callee: "hostclaim.Commit(sourceRegion)", Arg: "park.PlacementRegion(m.From)", Witness: wConsoleCommt},
92 {File: "internal/hostops/move_recovery_grant.go", Func: "MoveRecoveryGrant.Claim", Callee: "hostclaim.Commit(targetRegion)", Arg: "park.PlacementRegion(m.To)", Witness: wConsoleCommt},
93}
94
95// homeRegionCallees are the claim transitions and the lease key that take a
96// region, by package, with the index and name of each region parameter.
97var homeRegionCallees = map[string]map[string][]homeRegionParam{
98 "internal/hostclaim": {
99 "Touch": {{1, "regions"}},
100 "Abort": {{1, "targetRegion"}},
101 "Revert": {{2, "targetRegion"}},
102 "Commit": {{1, "sourceRegion"}, {3, "targetRegion"}},
103 "QueueLocationPark": {{1, "region"}},
104 "QueueStagingPark": {{1, "region"}},
105 "QueueResidueParks": {{1, "region"}},
106 "QueueReturnLanePark": {{1, "sourceRegion"}},
107 "TargetReleased": {{2, "targetRegion"}},
108 "BeginMove": {{4, "targetRegion"}},
109 "BeginAdmittedMove": {{4, "targetRegion"}},
110 "ReleaseFencedTarget": {{0, "r"}},
111 "ReleaseLostTarget": {{0, "r"}},
112 },
113 "internal/moves": {
114 "StagingLeasePrefix": {{0, "targetRegion"}},
115 },
116}
117
118type homeRegionParam struct {
119 index int
120 name string
121}
122
123// homeRegionLiterals are the composite types whose region fields are
124// inventoried: the claim's two release literals, and the loss owner's own
125// intent (claimLossIntent builds it). The empty package is a type of the
126// scanned file's own package.
127var homeRegionLiterals = map[string]map[string]bool{
128 "internal/hostclaim": {"FencedTargetRelease": true, "LostTargetRelease": true},
129 "": {"moveLossIntent": true},
130}
131
132// homeRegionFields are those literals' region fields. An assignment to one
133// (recordLossIntent's in.SourceRegion) is a site too.
134var homeRegionFields = map[string]bool{"TargetRegion": true, "SourceRegion": true}
135
136var homeRegionDirs = []string{"internal/host", "internal/hostops", "internal/console"}
137
138const homeRegionModule = "github.com/carlosframework/platform/"
139
140// scanHomeRegionSites lists the sites in every non-test Go file under the
141// scanned directories. overlay replaces a file's source by its
142// slash-separated path from root ("" removes the file) or adds a file.
143func scanHomeRegionSites(root string, overlay map[string]string) ([]homeRegionSite, error) {
144 files := map[string]bool{}
145 for _, dir := range homeRegionDirs {
146 err := filepath.WalkDir(filepath.Join(root, dir), func(path string, d fs.DirEntry, err error) error {
147 if err != nil {
148 return err
149 }
150 if d.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
151 return nil
152 }
153 rel, err := filepath.Rel(root, path)
154 if err != nil {
155 return err
156 }
157 files[filepath.ToSlash(rel)] = true
158 return nil
159 })
160 if err != nil {
161 return nil, err
162 }
163 }
164 for path := range overlay {
165 files[path] = true
166 }
167 var names []string
168 for f := range files {
169 names = append(names, f)
170 }
171 sort.Strings(names)
172 var out []homeRegionSite
173 for _, name := range names {
174 var src any
175 if body, ok := overlay[name]; ok {
176 if body == "" {
177 continue
178 }
179 src = body
180 }
181 f, err := parser.ParseFile(token.NewFileSet(), filepath.Join(root, filepath.FromSlash(name)), src, parser.SkipObjectResolution)
182 if err != nil {
183 return nil, err
184 }
185 out = append(out, homeRegionFileSites(name, f)...)
186 }
187 return out, nil
188}
189
190// homeRegionFileSites lists one file's sites, each under the function (or
191// method, by receiver type) that encloses it; a function literal's sites
192// are its enclosing declaration's.
193func homeRegionFileSites(name string, f *ast.File) []homeRegionSite {
194 // The file's local names for the scanned packages.
195 local := map[string]string{}
196 for _, imp := range f.Imports {
197 path, _ := strconv.Unquote(imp.Path.Value)
198 rel, ok := strings.CutPrefix(path, homeRegionModule)
199 if !ok {
200 continue
201 }
202 if homeRegionCallees[rel] == nil && homeRegionLiterals[rel] == nil {
203 continue
204 }
205 n := filepath.Base(rel)
206 if imp.Name != nil {
207 n = imp.Name.Name
208 }
209 local[n] = rel
210 }
211 resolve := func(e ast.Expr) (rel, sel string, ok bool) {
212 switch x := e.(type) {
213 case *ast.SelectorExpr:
214 id, isID := x.X.(*ast.Ident)
215 if !isID {
216 return "", "", false
217 }
218 rel, ok = local[id.Name]
219 return rel, x.Sel.Name, ok
220 case *ast.Ident:
221 return "", x.Name, true
222 }
223 return "", "", false
224 }
225 var out []homeRegionSite
226 visit := func(fn string, root ast.Node) {
227 ast.Inspect(root, func(n ast.Node) bool {
228 switch x := n.(type) {
229 case *ast.CallExpr:
230 rel, callee, ok := resolve(x.Fun)
231 if !ok || rel == "" {
232 return true
233 }
234 for _, p := range homeRegionCallees[rel][callee] {
235 arg := "<missing>"
236 if p.index < len(x.Args) {
237 arg = plan13Expr(x.Args[p.index])
238 }
239 out = append(out, homeRegionSite{File: name, Func: fn, Callee: filepath.Base(rel) + "." + callee + "(" + p.name + ")", Arg: arg})
240 }
241 case *ast.CompositeLit:
242 rel, typ, ok := resolve(x.Type)
243 if !ok || !homeRegionLiterals[rel][typ] {
244 return true
245 }
246 if rel != "" {
247 typ = filepath.Base(rel) + "." + typ
248 }
249 for _, el := range x.Elts {
250 kv, isKV := el.(*ast.KeyValueExpr)
251 if !isKV {
252 continue
253 }
254 if key, isID := kv.Key.(*ast.Ident); isID && homeRegionFields[key.Name] {
255 out = append(out, homeRegionSite{File: name, Func: fn, Callee: typ + "{" + key.Name + "}", Arg: plan13Expr(kv.Value)})
256 }
257 }
258 case *ast.AssignStmt:
259 for i, lhs := range x.Lhs {
260 sel, isSel := lhs.(*ast.SelectorExpr)
261 if !isSel || !homeRegionFields[sel.Sel.Name] {
262 continue
263 }
264 arg := "<multi-value>"
265 if len(x.Rhs) == len(x.Lhs) {
266 arg = plan13Expr(x.Rhs[i])
267 }
268 out = append(out, homeRegionSite{File: name, Func: fn, Callee: plan13Expr(sel) + " =", Arg: arg})
269 }
270 }
271 return true
272 })
273 }
274 for _, d := range f.Decls {
275 switch x := d.(type) {
276 case *ast.FuncDecl:
277 fn := x.Name.Name
278 if x.Recv != nil && len(x.Recv.List) == 1 {
279 t := x.Recv.List[0].Type
280 if star, ok := t.(*ast.StarExpr); ok {
281 t = star.X
282 }
283 if ix, ok := t.(*ast.IndexExpr); ok {
284 t = ix.X
285 }
286 fn = plan13Expr(t) + "." + fn
287 }
288 if x.Body != nil {
289 visit(fn, x.Body)
290 }
291 case *ast.GenDecl:
292 visit("<package>", x)
293 }
294 }
295 return out
296}
297
298// homeRegionDiff compares the sites found with the inventory. A site is
299// keyed by its file, function and callee; within one key the argument
300// texts are matched as a multiset. What is left over is a changed site
301// (one found and one row unmatched under the same key), an unknown site (a
302// found site with no row left), or a stale row (a row with no site left).
303func homeRegionDiff(found, inventory []homeRegionSite) []string {
304 type key struct{ file, fn, callee string }
305 want := map[key][]string{}
306 for _, r := range inventory {
307 k := key{r.File, r.Func, r.Callee}
308 want[k] = append(want[k], r.Arg)
309 }
310 got := map[key][]string{}
311 for _, s := range found {
312 k := key{s.File, s.Func, s.Callee}
313 got[k] = append(got[k], s.Arg)
314 }
315 keys := map[key]bool{}
316 for k := range want {
317 keys[k] = true
318 }
319 for k := range got {
320 keys[k] = true
321 }
322 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"
323 var out []string
324 for k := range keys {
325 rows := append([]string(nil), want[k]...)
326 var sites []string
327 for _, a := range got[k] {
328 if i := indexOf(rows, a); i >= 0 {
329 rows = append(rows[:i], rows[i+1:]...)
330 continue
331 }
332 sites = append(sites, a)
333 }
334 n := min(len(rows), len(sites))
335 for i := 0; i < n; i++ {
336 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))
337 }
338 for _, a := range sites[n:] {
339 out = append(out, fmt.Sprintf("unknown site: %s %s %s passes %s%s", k.file, k.fn, k.callee, a, fix))
340 }
341 for _, a := range rows[n:] {
342 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))
343 }
344 }
345 sort.Strings(out)
346 return out
347}
348
349func indexOf(xs []string, x string) int {
350 for i, s := range xs {
351 if s == x {
352 return i
353 }
354 }
355 return -1
356}
357
358// homeRegionTestNames is every top-level test function name in the
359// repository's test files.
360func homeRegionTestNames(t *testing.T) map[string]bool {
361 t.Helper()
362 re := regexp.MustCompile(`(?m)^func (Test\w+)\(t \*testing\.T\)`)
363 out := map[string]bool{}
364 err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
365 if err != nil {
366 return err
367 }
368 if d.IsDir() {
369 if path != "." && (strings.HasPrefix(d.Name(), ".") || d.Name() == "node_modules" || d.Name() == "testdata") {
370 return filepath.SkipDir
371 }
372 return nil
373 }
374 if !strings.HasSuffix(path, "_test.go") {
375 return nil
376 }
377 body, err := os.ReadFile(path)
378 if err != nil {
379 return err
380 }
381 for _, m := range re.FindAllStringSubmatch(string(body), -1) {
382 out[m[1]] = true
383 }
384 return nil
385 })
386 if err != nil {
387 t.Fatal(err)
388 }
389 return out
390}
391
392// homeRegionOverlay is path's source with each old, new pair applied: old
393// must be there exactly once.
394func homeRegionOverlay(t *testing.T, path string, pairs ...string) map[string]string {
395 t.Helper()
396 raw, err := os.ReadFile(filepath.FromSlash(path))
397 if err != nil {
398 t.Fatal(err)
399 }
400 body := string(raw)
401 for i := 0; i+1 < len(pairs); i += 2 {
402 if n := strings.Count(body, pairs[i]); n != 1 {
403 t.Fatalf("fixture: %s holds %q %d times, want once", path, pairs[i], n)
404 }
405 body = strings.Replace(body, pairs[i], pairs[i+1], 1)
406 }
407 return map[string]string{path: body}
408}
409
410// homeRegionWant fails unless problems hold one starting with kind and
411// containing every part.
412func homeRegionWant(t *testing.T, problems []string, kind string, parts ...string) {
413 t.Helper()
414 for _, p := range problems {
415 if !strings.HasPrefix(p, kind) {
416 continue
417 }
418 all := true
419 for _, part := range parts {
420 all = all && strings.Contains(p, part)
421 }
422 if all {
423 return
424 }
425 }
426 t.Fatalf("no %q naming %q among %q", kind, parts, problems)
427}
428
429func TestMovesHomeRegionCallSiteInventory(t *testing.T) {
430 found, err := scanHomeRegionSites(".", nil)
431 if err != nil {
432 t.Fatal(err)
433 }
434
435 t.Run("the tree matches the inventory", func(t *testing.T) {
436 if problems := homeRegionDiff(found, homeRegionCallSites); len(problems) != 0 {
437 for _, s := range found {
438 t.Logf("found {File: %q, Func: %q, Callee: %q, Arg: %q}", s.File, s.Func, s.Callee, s.Arg)
439 }
440 for _, p := range problems {
441 t.Error(p)
442 }
443 }
444 })
445
446 t.Run("every row names a witness", func(t *testing.T) {
447 tests := homeRegionTestNames(t)
448 for _, r := range homeRegionCallSites {
449 switch {
450 case strings.HasPrefix(r.Witness, "named-only: ") && len(r.Witness) > len("named-only: "):
451 case r.Witness == "":
452 t.Errorf("%s %s %s names no witness", r.File, r.Func, r.Callee)
453 case r.Pending != "":
454 if tests[r.Witness] {
455 t.Errorf("%s %s %s: %s is written; clear the row's Pending (%s)", r.File, r.Func, r.Callee, r.Witness, r.Pending)
456 }
457 t.Logf("pending (%s): %s %s %s, witness %s", r.Pending, r.File, r.Func, r.Callee, r.Witness)
458 case !tests[r.Witness]:
459 t.Errorf("%s %s %s: the witness %s is not a test in this repository", r.File, r.Func, r.Callee, r.Witness)
460 }
461 }
462 })
463
464 // The fixtures: each edit the inventory exists to catch.
465 t.Run("a new Commit site supplying a roster region is unknown", func(t *testing.T) {
466 src := "package host\n\nimport \"github.com/carlosframework/platform/internal/hostclaim\"\n\n" +
467 "func homeRegionFixture(rr regionRow) error {\n\treturn hostclaim.Commit(id, from, src, rr.row.Region, scope, staging, newID)(c)\n}\n"
468 found, err := scanHomeRegionSites(".", map[string]string{"internal/host/zz_home_region_fixture.go": src})
469 if err != nil {
470 t.Fatal(err)
471 }
472 homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "unknown site", "internal/host/zz_home_region_fixture.go", "hostclaim.Commit(targetRegion)", "rr.row.Region")
473 })
474 t.Run("home_region handed to QueueStagingPark at a new site is unknown", func(t *testing.T) {
475 src := "package host\n\nimport (\n\t\"github.com/carlosframework/platform/internal/hostclaim\"\n\t\"github.com/carlosframework/platform/internal/park\"\n)\n\n" +
476 "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"
477 found, err := scanHomeRegionSites(".", map[string]string{"internal/host/zz_home_region_fixture.go": src})
478 if err != nil {
479 t.Fatal(err)
480 }
481 homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "unknown site", "hostclaim.QueueStagingPark(region)", "park.PlacementRegion(to)")
482 })
483 t.Run("home_region handed to QueueStagingPark at an inventoried site is changed", func(t *testing.T) {
484 overlay := homeRegionOverlay(t, "internal/host/movesrollback.go",
485 "scope := moves.StagingScope(host, id, obs.Claim.Holder.Incarnation, staged.Bound())",
486 "scope := moves.StagingScope(host, id, obs.Claim.Holder.Incarnation, staged.Bound())\n\t\tto := obs.Claim.Op.Intent[\"home_region\"]",
487 "hostclaim.QueueStagingPark(id, cfg.parkRegion(), scope, obligationID)",
488 "hostclaim.QueueStagingPark(id, park.PlacementRegion(to), scope, obligationID)")
489 found, err := scanHomeRegionSites(".", overlay)
490 if err != nil {
491 t.Fatal(err)
492 }
493 homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "changed site", "rollbackRun.remote", "hostclaim.QueueStagingPark(region)", "park.PlacementRegion(to)")
494 })
495 t.Run("a FencedTargetRelease naming home_region is changed", func(t *testing.T) {
496 found, err := scanHomeRegionSites(".", homeRegionOverlay(t, "internal/hostops/move_fence_release.go", "TargetRegion: park.PlacementRegion(m.To)",
497 `TargetRegion: o.Claim.Op.Intent["home_region"]`))
498 if err != nil {
499 t.Fatal(err)
500 }
501 homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "changed site", "hostclaim.FencedTargetRelease{TargetRegion}", `o.Claim.Op.Intent["home_region"]`)
502 })
503 t.Run("a site that no longer exists leaves a stale row", func(t *testing.T) {
504 found, err := scanHomeRegionSites(".", homeRegionOverlay(t, "internal/host/movespass.go", "return moves.StagingLeasePrefix(r, host), nil",
505 `return "control/staging-leases/" + r + "/" + host, nil`))
506 if err != nil {
507 t.Fatal(err)
508 }
509 homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "stale row", "stagingLeaseKey", "moves.StagingLeasePrefix(targetRegion)")
510 })
511 t.Run("an aliased import is still found", func(t *testing.T) {
512 src := "package host\n\nimport hc \"github.com/carlosframework/platform/internal/hostclaim\"\n\n" +
513 "func homeRegionFixture() error {\n\treturn hc.Touch(id, []string{\"ie\"}, nil)(c)\n}\n"
514 found, err := scanHomeRegionSites(".", map[string]string{"internal/console/zz_home_region_fixture.go": src})
515 if err != nil {
516 t.Fatal(err)
517 }
518 homeRegionWant(t, homeRegionDiff(found, homeRegionCallSites), "unknown site", "hostclaim.Touch(regions)", `[]string{"ie"}`)
519 })
520}
521