moves tests: the park fakes use claim Plan 12's real RunHeld and lease set (M5, M6, Q10)
testRunHeld, borrowedLeases and the allowing local-authorizer stub stood in
for claim Plan 12's pieces. They go:
- armParkFakes and armStandIns arm ParkRunHeld with movesRunHeld
(park.Engine.RunHeld) and ParkLeases with the claim's
hostbox.AggregateLeases over hostbox.ReplicaParkLeaseSet, wrapped around
the tests' lease table; fakeHeldLease gains LeaseScope so the claim's
AcquireHeld can lend it, as it lends renewingHeld;
- allowLocalAuthorize is claimMoveLocalAuthorizer over an allowing owner,
so it refuses anything but a park-local, as the claim's does.
TestLandingResidueParkUnderItsOwnLease now runs the landing with a
RenewingLeases own lease and AggregateLeases{ReplicaParkLeaseSet} park
leases over a table behind a fake Open, and checks that the residue parks
borrowed the landing's lease through the claim's AcquireHeld (RED against
the old testRunHeld, which bypassed the claim's leases). The Engine.Run
subtest still shows the park would wait on the landing's own lock.
Stale "claim Plan 12" names in these test files are rewritten too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 files changed,
+92
−76
internal/host/movescutguard_test.go+6 −4internal/host/movesland_test.go+23 −13internal/host/movesparkfakes_test.go+22 −9internal/host/movessources_test.go+41 −50
diff --git a/internal/host/movescutguard_test.go b/internal/host/movescutguard_test.go| index 85f59a381..e64b88f9e 100644 |
| --- a/internal/host/movescutguard_test.go |
| +++ b/internal/host/movescutguard_test.go |
| @@ -24,7 +24,8 @@ import ( |
| // the claim is sealed, and the move stays ready with the reason recorded. |
| // errStandIn is what a harness stand-in answers. The stand-ins arm a box |
| -// so that the arming predicates hold, as they do once claim Plan 12 lands; |
| +// so that the arming predicates hold, as they do once the wake-source gate |
| +// opens (movesWakeSourceRegistered, Plan 5 Task 9b); |
| // a test that reaches one has run a step its harness never wired. |
| var errStandIn = errors.New("harness stand-in: this hook is not wired in this harness") |
| @@ -42,7 +43,7 @@ func armStandIns(c *moveCfg, s release.Store, role string) { |
| c.Local = park.Local{DataDir: c.DataDir, Box: c.Box} |
| } |
| if c.ParkLeases == nil { |
| - c.ParkLeases = &fakeParkLeases{} |
| + c.ParkLeases = claimParkLeases(&fakeParkLeases{}) |
| } |
| if c.ParkView == nil { |
| c.ParkView = recordView(s, c.Region, role) |
| @@ -51,7 +52,7 @@ func armStandIns(c *moveCfg, s release.Store, role string) { |
| c.ParkAuthorize = allowParkAuthorize |
| } |
| if c.ParkRunHeld == nil { |
| - c.ParkRunHeld = testRunHeld |
| + c.ParkRunHeld = movesRunHeld |
| } |
| if c.ParkLocalAuthorize == nil { |
| c.ParkLocalAuthorize = allowLocalAuthorize |
| @@ -121,7 +122,8 @@ func armStandIns(c *moveCfg, s release.Store, role string) { |
| } |
| // unarmedTarget is the cut harness's target with the landing or the |
| -// rollback left unwired, as every box is in production until claim Plan 12. |
| +// rollback left unwired, as every box is in production until the |
| +// wake-source gate opens (Plan 5 Task 9b). |
| func (h *cutHarness) unarmedTarget(t *testing.T, unarm func(*moveCfg)) moveCfg { |
| t.Helper() |
| c := h.target() |
diff --git a/internal/host/movesland_test.go b/internal/host/movesland_test.go| index 688694ed3..35d7e21d8 100644 |
| --- a/internal/host/movesland_test.go |
| +++ b/internal/host/movesland_test.go |
| @@ -47,9 +47,9 @@ import ( |
| // fakeParkLeases is one lease table for the landing's own-location lease |
| // and the park engine's: a second acquisition of a held location is |
| // refused. The landing holds its lease from checking through published |
| -// (spec §7.6), so its residue parks run under it through claim Plan 12's |
| -// RunHeld (testRunHeld, M5); an Engine.Run that took the lock again would |
| -// fail the test. |
| +// (spec §7.6), so its residue parks run under it through the claim's |
| +// AggregateLeases and park.Engine.RunHeld (M5); an Engine.Run that took the |
| +// lock again would fail the test. |
| type landingHarness struct { |
| *cutHarness |
| @@ -100,10 +100,12 @@ type landingHarness struct { |
| // the target's parks use the allowing fake behind their own checks |
| // (Plan 5 Task 3). |
| ownAuthorize bool |
| - // parkLeases and runHeld, when set, replace the park fakes' leases and |
| - // claim Plan 12's RunHeld (Plan 5 Task 9a, M5). |
| + // parkLeases, runHeld and ownLease, when set, replace the park fakes' |
| + // leases, the claim's RunHeld (park.Engine.RunHeld, M5) and the |
| + // landing's own-location lease. |
| parkLeases park.Leases |
| runHeld func(ctx context.Context, e park.Engine, w park.Work, held park.HeldLease) error |
| + ownLease func(ctx context.Context, bucket, location string) (park.HeldLease, error) |
| } |
| // fakeParkLeases refuses a second acquisition of a held location. |
| @@ -114,16 +116,21 @@ type fakeParkLeases struct { |
| acquired []string |
| } |
| -// fakeHeldLease is a park.HeldLease with the Context method hostbox's |
| -// renewingHeld has: a context derived from it is cancelled when the lease |
| -// is lost or released. |
| +// fakeHeldLease is a park.HeldLease with the Context and LeaseScope methods |
| +// hostbox's renewingHeld has: a context derived from it is cancelled when |
| +// the lease is lost or released, and the claim's AggregateLeases lends it to |
| +// RunHeld for its own bucket and scope. |
| type fakeHeldLease struct { |
| - l *fakeParkLeases |
| - key string |
| - ctx context.Context |
| - cancel context.CancelFunc |
| + l *fakeParkLeases |
| + key string |
| + bucket, scope string |
| + ctx context.Context |
| + cancel context.CancelFunc |
| } |
| +// LeaseScope is renewingHeld.LeaseScope: the bucket and scope it holds. |
| +func (h *fakeHeldLease) LeaseScope() (string, string) { return h.bucket, h.scope } |
| + |
| func (l *fakeParkLeases) Acquire(_ context.Context, bucket, scope string) (park.HeldLease, error) { |
| l.mu.Lock() |
| defer l.mu.Unlock() |
| @@ -137,7 +144,7 @@ func (l *fakeParkLeases) Acquire(_ context.Context, bucket, scope string) (park. |
| l.held[key] = true |
| l.acquired = append(l.acquired, key) |
| ctx, cancel := context.WithCancel(context.Background()) |
| - h := &fakeHeldLease{l: l, key: key, ctx: ctx, cancel: cancel} |
| + h := &fakeHeldLease{l: l, key: key, bucket: bucket, scope: scope, ctx: ctx, cancel: cancel} |
| if l.leases == nil { |
| l.leases = map[string]*fakeHeldLease{} |
| } |
| @@ -365,6 +372,9 @@ func (h *landingHarness) landingCfg(t *testing.T) moveCfg { |
| c.ParkRunHeld = h.runHeld |
| } |
| c.OwnLease = h.leases.Acquire |
| + if h.ownLease != nil { |
| + c.OwnLease = h.ownLease |
| + } |
| if h.store != nil { |
| c.Versioned = func(context.Context, string, string) (lease.VersionedStore, error) { return h.store, nil } |
| } |
diff --git a/internal/host/movesparkfakes_test.go b/internal/host/movesparkfakes_test.go| index 09db434e7..e60ea87e9 100644 |
| --- a/internal/host/movesparkfakes_test.go |
| +++ b/internal/host/movesparkfakes_test.go |
| @@ -4,6 +4,7 @@ import ( |
| "context" |
| "strings" |
| + "github.com/carlosframework/platform/internal/hostbox" |
| "github.com/carlosframework/platform/internal/hostclaim" |
| "github.com/carlosframework/platform/internal/park" |
| "github.com/carlosframework/platform/internal/provision" |
| @@ -11,9 +12,11 @@ import ( |
| ) |
| // The park fakes the moves tests inject (Plan 5 Task 9a). Production has no |
| -// stand-in for the claim's view or authorizer; these are the rules the |
| +// stand-in for the claim's view or authorizers; these are the rules the |
| // stand-ins had, kept for the tests that do not exercise the real view |
| -// (movesParkView, movessources_test.go). |
| +// (movesParkView, movessources_test.go). The rest is the claim's own, as |
| +// claim Plan 12 landed it (Q10): the claim's lease set and AggregateLeases over the tests' lease |
| +// table, park.Engine.RunHeld, and the MoveParks local authorizer's wrapper. |
| // standInView is the tests' park view: the location's objects classified by |
| // key, seen from region, and the other side of the move authoritative, which |
| @@ -61,16 +64,26 @@ func recordView(s release.Store, region, role string) func(context.Context, host |
| // so each arm's own operation check (movesArmAuthorize) decides alone. |
| func allowParkAuthorize(context.Context, hostclaim.Observed, hostclaim.Obligation) error { return nil } |
| -// allowLocalAuthorize is the tests' claim Plan 12 local authorizer (M9). |
| -func allowLocalAuthorize(context.Context, *hostclaim.Claim, hostclaim.Obligation) error { return nil } |
| +// allowLocalAuthorize is the claim's MoveParks local authorizer |
| +// (claimMoveLocalAuthorizer, M9) over an identity check that passes and the |
| +// allowing owner: it allows every park-local and refuses anything else. |
| +var allowLocalAuthorize = claimMoveLocalAuthorizer(func(context.Context) error { return nil }, allowParkAuthorize) |
| -// armParkFakes arms c's park hooks with the tests' fakes: leases, the |
| -// record-backed stand-in view for region and role, the allowing claim |
| -// authorizer and claim Plan 12's pieces (testRunHeld, allowLocalAuthorize). |
| +// claimParkLeases is the claim's park leases over the tests' lease table |
| +// leases: AggregateLeases with the replica park lease set (M6), as the |
| +// claim's MoveParks engine resolves them, lending a held lease to RunHeld. |
| +func claimParkLeases(leases park.Leases) park.Leases { |
| + return hostbox.AggregateLeases{Leases: leases, Resolve: hostbox.ReplicaParkLeaseSet} |
| +} |
| + |
| +// armParkFakes arms c's park hooks: the claim's leases over the tests' |
| +// lease table, the record-backed stand-in view for region and role, the |
| +// allowing claim authorizer, park.Engine.RunHeld (movesRunHeld, M5) and the |
| +// claim's local authorizer over the allowing owner. |
| func armParkFakes(c *moveCfg, s release.Store, leases park.Leases, region, role string) { |
| - c.ParkLeases = leases |
| + c.ParkLeases = claimParkLeases(leases) |
| c.ParkView = recordView(s, region, role) |
| c.ParkAuthorize = allowParkAuthorize |
| - c.ParkRunHeld = testRunHeld |
| + c.ParkRunHeld = movesRunHeld |
| c.ParkLocalAuthorize = allowLocalAuthorize |
| } |
diff --git a/internal/host/movessources_test.go b/internal/host/movessources_test.go| index 1813d7b2d..17560326c 100644 |
| --- a/internal/host/movessources_test.go |
| +++ b/internal/host/movessources_test.go |
| @@ -678,32 +678,6 @@ func TestMovesParksRefuseUntilTheWakeSourceRegisters(t *testing.T) { |
| }) |
| } |
| -// testRunHeld is the agreed shape of claim Plan 12's Engine.RunHeld (M5): |
| -// the chore runs under the lease the caller holds, which must be the |
| -// scope's own lock (the lease set, M6); it is neither acquired again, nor |
| -// released, nor closed by the run. |
| -func testRunHeld(ctx context.Context, e park.Engine, w park.Work, held park.HeldLease) error { |
| - if held == nil { |
| - return park.ErrUnsafe |
| - } |
| - e.Leases = borrowedLeases{held} |
| - return e.Run(ctx, w) |
| -} |
| - |
| -type borrowedLeases struct{ held park.HeldLease } |
| - |
| -func (b borrowedLeases) Acquire(_ context.Context, bucket, scope string) (park.HeldLease, error) { |
| - if f, ok := b.held.(*fakeHeldLease); ok && f.key != bucket+"/"+scope { |
| - return nil, errors.New("the held lease is " + f.key + ", not " + bucket + "/" + scope) |
| - } |
| - return borrowedLease{b.held}, nil |
| -} |
| - |
| -type borrowedLease struct{ park.HeldLease } |
| - |
| -func (borrowedLease) Release(context.Context) error { return nil } |
| -func (borrowedLease) Close() {} |
| - |
| // TestCommittedLaneViewNamesTheTargetWithItsMarker (the Task 3 carry): after |
| // the commit the target's lane park runs under the real view, which names |
| // the target's region authoritative and supplies the marker the target's |
| @@ -863,31 +837,34 @@ func TestParkViewRegionIsTheRosters(t *testing.T) { |
| } |
| } |
| -// TestLandingResidueParkUnderItsOwnLease (M5): the landing takes its lease on |
| -// the location at checking and holds it through published (spec §7.6). Over |
| -// the real AggregateLeases and the moves lease set, the residue park's |
| -// scope resolves to that same lock, so Engine.Run would wait on the landing |
| -// itself; claim Plan 12's RunHeld runs the chores under the held lease and |
| -// the landing goes on to restored. |
| +// TestLandingResidueParkUnderItsOwnLease (M5, M6): the landing takes its |
| +// lease on the location at checking and holds it through published (spec |
| +// §7.6). The leases are the claim's own shape over a table behind |
| +// RenewingLeases' Open: the landing's own lease is a RenewingLeases one (as |
| +// armPhase1's OwnLease is) and the park leases are AggregateLeases over the |
| +// replica park lease set (as the claim's MoveParks engine's are). The |
| +// residue park's scope resolves to the landing's own lock, so Engine.Run |
| +// would wait on the landing itself; park.Engine.RunHeld borrows the held |
| +// lease through AcquireHeld and LeaseScope, and the landing goes on to |
| +// restored. |
| func TestLandingResidueParkUnderItsOwnLease(t *testing.T) { |
| location := "data-uk/" + cutRoot |
| t.Run("under RunHeld", func(t *testing.T) { |
| h := newLandingHarness(t) |
| h.flip(t) |
| keys := h.residue(t) |
| - h.parkLeases = hostbox.AggregateLeases{Leases: h.leases, Resolve: hostbox.ReplicaParkLeaseSet} |
| - runs := 0 |
| - h.runHeld = func(ctx context.Context, e park.Engine, w park.Work, held park.HeldLease) error { |
| - runs++ |
| - if got := h.leases.holding(); len(got) != 1 || got[0] != location { |
| - t.Errorf("a residue park ran while the landing held %v, want %s", got, location) |
| - } |
| - return testRunHeld(ctx, e, w, held) |
| - } |
| + table := &renewTable{} |
| + lender := &countingLender{AggregateLeases: table.leases()} |
| + h.ownLease, h.parkLeases = table.ownLease, lender |
| h.land(t) |
| h.installed(t) |
| - if runs == 0 { |
| - t.Fatal("no residue park ran") |
| + if lender.held == 0 { |
| + t.Fatal("no residue park borrowed the landing's lease through the claim's leases") |
| + } |
| + for _, o := range table.opens { |
| + if o != location { |
| + t.Fatalf("a lease other than the landing's own was opened: %v", table.opens) |
| + } |
| } |
| for _, k := range keys { |
| if _, ok := h.f.buckets["data-uk"][k]; ok && !strings.HasSuffix(k, "/generation.json") { |
| @@ -899,7 +876,8 @@ func TestLandingResidueParkUnderItsOwnLease(t *testing.T) { |
| h := newLandingHarness(t) |
| h.flip(t) |
| keys := h.residue(t) |
| - h.parkLeases = hostbox.AggregateLeases{Leases: h.leases, Resolve: hostbox.ReplicaParkLeaseSet} |
| + table := &renewTable{} |
| + h.ownLease, h.parkLeases = table.ownLease, table.leases() |
| h.runHeld = func(ctx context.Context, e park.Engine, w park.Work, _ park.HeldLease) error { return e.Run(ctx, w) } |
| for i := 0; i < 3; i++ { |
| h.land(t) |
| @@ -907,7 +885,7 @@ func TestLandingResidueParkUnderItsOwnLease(t *testing.T) { |
| if m := h.read(t, p1Host).Move; m.LandingPhase != provision.LandParking { |
| t.Fatalf("the landing went past parking without RunHeld: %+v", m) |
| } |
| - if got := h.leases.holding(); len(got) != 1 || got[0] != location { |
| + if got := table.holding(); len(got) != 1 || got[0] != location { |
| t.Fatalf("the landing holds %v", got) |
| } |
| for _, k := range keys { |
| @@ -918,10 +896,23 @@ func TestLandingResidueParkUnderItsOwnLease(t *testing.T) { |
| }) |
| } |
| +// countingLender is the claim's AggregateLeases, counting the held leases |
| +// it lends (AcquireHeld). |
| +type countingLender struct { |
| + hostbox.AggregateLeases |
| + held int |
| +} |
| + |
| +func (l *countingLender) AcquireHeld(ctx context.Context, bucket, scope string, own park.HeldLease) (park.HeldLease, error) { |
| + l.held++ |
| + return l.AggregateLeases.AcquireHeld(ctx, bucket, scope, own) |
| +} |
| + |
| // TestNoArmRunsWithoutTheThreeParkHooks (M17): every moves arm requires the |
| // claim's park leases, view and authorizer, the landing (and the recovery's |
| -// install) claim Plan 12's RunHeld too, the finalization its local |
| -// authorizer; production's own wiring sets none of them. |
| +// install) RunHeld too, the finalization the claim's local authorizer; |
| +// production's own wiring sets none of them until the wake-source gate |
| +// opens. |
| func TestNoArmRunsWithoutTheThreeParkHooks(t *testing.T) { |
| three := map[string]func(*moveCfg){ |
| "no park leases": func(c *moveCfg) { c.ParkLeases = nil }, |
| @@ -936,11 +927,11 @@ func TestNoArmRunsWithoutTheThreeParkHooks(t *testing.T) { |
| armed func(moveCfg) bool |
| extra map[string]func(*moveCfg) |
| }{ |
| - {"landing", func(t *testing.T) moveCfg { return newLandingHarness(t).lcfg }, landingArmed, map[string]func(*moveCfg){"no claim Plan 12 RunHeld": runHeld}}, |
| + {"landing", func(t *testing.T) moveCfg { return newLandingHarness(t).lcfg }, landingArmed, map[string]func(*moveCfg){"no RunHeld": runHeld}}, |
| {"rollback target", func(t *testing.T) moveCfg { h := newRollbackHarness(t); return h.rollbackCfg(t) }, rollbackTargetArmed, nil}, |
| {"lane park (revert, abort, committed)", func(t *testing.T) moveCfg { return newRevertHarness(t).laneCfg() }, revertLaneArmed, nil}, |
| - {"recovery", func(t *testing.T) moveCfg { h := newRecoverHarness(t, true); return h.sourceCfg(t) }, recoverySourceArmed, map[string]func(*moveCfg){"no claim Plan 12 RunHeld": runHeld}}, |
| - {"source finalization", func(t *testing.T) moveCfg { return newFinalHarness(t).cfg }, sourceFinalArmed, map[string]func(*moveCfg){"no claim Plan 12 local authorizer": local}}, |
| + {"recovery", func(t *testing.T) moveCfg { h := newRecoverHarness(t, true); return h.sourceCfg(t) }, recoverySourceArmed, map[string]func(*moveCfg){"no RunHeld": runHeld}}, |
| + {"source finalization", func(t *testing.T) moveCfg { return newFinalHarness(t).cfg }, sourceFinalArmed, map[string]func(*moveCfg){"no claim local authorizer": local}}, |
| } |
| for _, a := range arms { |
| t.Run(a.name, func(t *testing.T) { |