carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package hostclaim
2
3import (
4 "reflect"
5 "testing"
6)
7
8// TestRollbackBaselineStaysUncoveredUntilCommit ports the claim half of the
9// model's wRollbackBaselinePreserved (claim rev 6.14 item 5, R12-3; moves
10// spec §5.1 step 9 and §6.4): a landing's probe publishes the target's
11// generation in the target's region, and PublishGeneration, as it stands on
12// main, does not queue the predecessor, which is the source's generation in
13// the source's region, for a park. The target's admission record stays
14// uncovered and no chore is queued into the source's region, at either
15// probing phase and when the publication is replayed, so a rollback still
16// has the generation it restores from. Only Commit covers the record, in
17// the CAS that queues the source region's whole-location park
18// (wCommitCoverage); a rollback, the target's release and a clean return
19// never do. The source's own claim sequence, RollBack then TargetReleased
20// then ReturnToSource(clean), ends with holder.generation naming the
21// source's generation again, which the host half
22// (TestRollbackBaselineSurvivesTheCleanReturn) finds still in the bucket.
23func TestRollbackBaselineStaysUncoveredUntilCommit(t *testing.T) {
24 failID := func(t *testing.T) func() string {
25 return func() string {
26 t.Error("the target's publication allocated an obligation id for the source's generation")
27 return "id"
28 }
29 }
30 publish := func(t *testing.T, c *Claim) {
31 t.Helper()
32 if err := PublishGeneration(c.Op.ID, c.Op.Phase, c.Holder.Session, c.Holder.Generation, "uk", failID(t))(c); err != nil {
33 t.Fatalf("publish at %s: %v", c.Op.Phase, err)
34 }
35 }
36 // baselineKept is the invariant: the admission record's predecessor is
37 // the source's generation in ie, nothing is queued into ie, and the
38 // record is uncovered.
39 baselineKept := func(t *testing.T, c *Claim, where string) {
40 t.Helper()
41 srcGen := c.Op.Journal[JournalSrcGen]
42 if srcGen == "" || len(c.Holder.Admissions) != 1 {
43 t.Fatalf("%s: fixture: src_gen %q, admissions %+v", where, srcGen, c.Holder.Admissions)
44 }
45 a := c.Holder.Admissions[0]
46 if a.Box != "uk-box" || a.PredecessorRegion != "ie" || a.Predecessor != srcGen {
47 t.Fatalf("%s: the target's record does not name the source's generation %q in ie: %+v", where, srcGen, a)
48 }
49 if a.Covered {
50 t.Fatalf("%s: the target's record is covered: %+v", where, a)
51 }
52 for _, o := range c.Obligations {
53 if o.Region == "ie" || o.Box == "ie-box" {
54 t.Fatalf("%s: an obligation reaches the source's region: %+v", where, o)
55 }
56 if o.Kind == "park-generation" {
57 t.Fatalf("%s: a generation park was queued: %+v", where, o)
58 }
59 }
60 }
61
62 for _, from := range []string{"probing", "probed"} {
63 t.Run("publication at "+from, func(t *testing.T) {
64 c := targetWroteLanding(t)
65 c.Op.Phase = from
66 publish(t, c)
67 baselineKept(t, c, "after the publication")
68 // A replay changes nothing.
69 before := mustJSON(t, c)
70 publish(t, c)
71 if string(mustJSON(t, c)) != string(before) {
72 t.Fatalf("the replayed publication changed the claim:\nbefore %s\nafter %s", before, mustJSON(t, c))
73 }
74 validAfterWrite(t, c)
75 })
76 }
77
78 t.Run("the rollback and the clean return", func(t *testing.T) {
79 c := targetWroteLanding(t)
80 publish(t, c)
81 baselineKept(t, c, "after the publication")
82 srcGen := c.Op.Journal[JournalSrcGen]
83 if err := RollBack(moveA, rbA)(c); err != nil {
84 t.Fatal(err)
85 }
86 baselineKept(t, c, "after the rollback")
87 if err := TargetReleased(moveA, "uk-box", "uk", stoppedTarget, releaseScope(c), counter())(c); err != nil {
88 t.Fatal(err)
89 }
90 // The release queues the target region's own park and covers only
91 // records whose predecessor lives there.
92 baselineKept(t, c, "after the target's release")
93 if k := obligationKinds(c); k["park-replica/uk"] != 1 || len(c.Obligations) != 1 {
94 t.Fatalf("the release owes %+v", c.Obligations)
95 }
96 if err := ReturnToSource(moveA, "ie-box", true, ReturnFence{})(c); err != nil {
97 t.Fatal(err)
98 }
99 baselineKept(t, c, "after the clean return")
100 if c.Op.Kind != KindRestoring || c.Op.Phase != PhaseClaimed || c.Op.Exec != "ie-box" {
101 t.Fatalf("op: %+v", c.Op)
102 }
103 if c.Holder.Generation != srcGen || c.Holder.Stamp != 3 {
104 t.Fatalf("the return names generation %q stamp %d, want the source's %q and 3", c.Holder.Generation, c.Holder.Stamp, srcGen)
105 }
106 validAfterWrite(t, c)
107 })
108
109 t.Run("only the commit covers the record", func(t *testing.T) {
110 c := targetWroteLanding(t)
111 publish(t, c)
112 baselineKept(t, c, "after the publication")
113 src, stg := commitScopes(c)
114 if err := Commit(moveA, "ie", "ie-box", "uk", src, stg, counter())(c); err != nil {
115 t.Fatal(err)
116 }
117 if !c.Holder.Admissions[0].Covered {
118 t.Fatalf("the commit left the record uncovered: %+v", c.Holder.Admissions[0])
119 }
120 if k := obligationKinds(c); k["park-replica/ie"] != 1 {
121 t.Fatalf("the commit queued %+v, want the source region's whole-location park", c.Obligations)
122 }
123 for _, o := range c.Obligations {
124 if o.Kind == "park-generation" {
125 t.Fatalf("the commit queued a generation park: %+v", o)
126 }
127 }
128 // A publication after the commit changes nothing more.
129 before := mustJSON(t, c)
130 publish2 := PublishGeneration(c.Op.ID, c.Op.Phase, c.Holder.Session, c.Holder.Generation, "uk", failID(t))
131 if err := publish2(c); err != nil || !reflect.DeepEqual(mustJSON(t, c), before) {
132 t.Fatalf("a publication after the commit: %v", err)
133 }
134 })
135}
136