carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package moves
2
3import (
4 "errors"
5 "testing"
6
7 "github.com/carlosframework/platform/internal/genselect"
8 "github.com/carlosframework/platform/internal/hostclaim"
9)
10
11func startClaim(gen string, adm ...hostclaim.AdmissionRecord) *hostclaim.Claim {
12 return &hostclaim.Claim{Holder: &hostclaim.Holder{
13 Incarnation: "inc-1", Location: "loc/", Generation: gen, Admissions: adm,
14 }}
15}
16
17func startMarker(op string) *genselect.Marker {
18 return &genselect.Marker{Op: op, Incarnation: "inc-1"}
19}
20
21func TestStartCheckOriginEverywhere(t *testing.T) {
22 if err := StartCheck(startClaim(""), nil, nil, ""); err != nil {
23 t.Fatalf("origin everywhere: %v", err)
24 }
25}
26
27func TestStartCheckMarkerNamesTheHoldersGeneration(t *testing.T) {
28 c := startClaim("g2", hostclaim.AdmissionRecord{Generation: "g2", Predecessor: "g1"})
29 fg := &FilesGen{Op: "g2", Incarnation: "inc-1"}
30 if err := StartCheck(c, startMarker("g2"), fg, "g2"); err != nil {
31 t.Fatalf("all three name the holder's generation: %v", err)
32 }
33}
34
35func TestStartCheckLaggingMarker(t *testing.T) {
36 // The latest admission replaced g1 with g2 and has published nothing.
37 c := startClaim("g2", hostclaim.AdmissionRecord{Generation: "g2", Predecessor: "g1"})
38 if err := StartCheck(c, startMarker("g1"), nil, ""); err != nil {
39 t.Fatalf("marker naming the latest admission's predecessor: %v", err)
40 }
41 // A restarted probe: the fresh generation F restored from the prepared M;
42 // the files still name M.
43 c = startClaim("F", hostclaim.AdmissionRecord{Generation: "M", Predecessor: "g0"},
44 hostclaim.AdmissionRecord{Generation: "F", Predecessor: "M"})
45 fg := &FilesGen{Op: "M", Incarnation: "inc-1"}
46 if err := StartCheck(c, startMarker("M"), fg, "F"); err != nil {
47 t.Fatalf("restarted attempt: %v", err)
48 }
49}
50
51func TestStartCheckChainOfAdmissionsWithoutMarkers(t *testing.T) {
52 c := startClaim("g3",
53 hostclaim.AdmissionRecord{Generation: "g2", Predecessor: "g1"},
54 hostclaim.AdmissionRecord{Generation: "g3", Predecessor: "g2"})
55 for _, op := range []string{"g3", "g2", "g1"} {
56 if err := StartCheck(c, startMarker(op), nil, ""); err != nil {
57 t.Errorf("marker %s through the chain: %v", op, err)
58 }
59 }
60 if err := StartCheck(c, startMarker("g0"), nil, ""); !errors.Is(err, ErrStartMarker) {
61 t.Errorf("marker beyond the chain = %v, want ErrStartMarker", err)
62 }
63 // The chain is by predecessor link, not by position: an admission whose
64 // generation the chain never reaches adds nothing.
65 c = startClaim("g3",
66 hostclaim.AdmissionRecord{Generation: "x2", Predecessor: "x1"},
67 hostclaim.AdmissionRecord{Generation: "g3", Predecessor: "g2"})
68 if err := StartCheck(c, startMarker("x1"), nil, ""); !errors.Is(err, ErrStartMarker) {
69 t.Errorf("marker off the chain = %v, want ErrStartMarker", err)
70 }
71}
72
73func TestStartCheckChainTerminatesOnACycle(t *testing.T) {
74 // A damaged record whose predecessors loop must still end.
75 c := startClaim("g2",
76 hostclaim.AdmissionRecord{Generation: "g1", Predecessor: "g2"},
77 hostclaim.AdmissionRecord{Generation: "g2", Predecessor: "g1"})
78 if err := StartCheck(c, startMarker("g1"), nil, ""); err != nil {
79 t.Errorf("marker in the cycle: %v", err)
80 }
81 if err := StartCheck(c, startMarker("g9"), nil, ""); !errors.Is(err, ErrStartMarker) {
82 t.Errorf("marker outside the cycle = %v, want ErrStartMarker", err)
83 }
84}
85
86func TestStartCheckRejectsFilesBeforePreparedGeneration(t *testing.T) {
87 // M was prepared over S; F is the restarted attempt restored from M.
88 // Files from before the preparation are stale, even though S is an
89 // ancestor of the holder's generation.
90 c := startClaim("F",
91 hostclaim.AdmissionRecord{Generation: "M", Predecessor: "S", Mode: "cold"},
92 hostclaim.AdmissionRecord{Generation: "F", Predecessor: "M", Mode: "cold"})
93 if err := StartCheck(c, startMarker("M"), &FilesGen{Op: "S", Incarnation: "inc-1"}, "F"); !errors.Is(err, ErrStartFiles) {
94 t.Errorf("files naming S = %v, want ErrStartFiles", err)
95 }
96 if err := StartCheck(c, startMarker("M"), &FilesGen{Op: "M", Incarnation: "inc-1"}, "F"); err != nil {
97 t.Errorf("files naming M: %v", err)
98 }
99 if err := StartCheck(c, startMarker("M"), &FilesGen{Op: "F", Incarnation: "inc-1"}, "F"); err != nil {
100 t.Errorf("files naming F: %v", err)
101 }
102}
103
104func TestStartCheckLaggingFilesAfterWarmAdmission(t *testing.T) {
105 // Cold F from M, then a warm re-admission of F (F from F): the files
106 // still name M.
107 c := startClaim("F",
108 hostclaim.AdmissionRecord{Generation: "F", Predecessor: "M", Mode: "cold"},
109 hostclaim.AdmissionRecord{Generation: "F", Predecessor: "F", Mode: "warm"})
110 if err := StartCheck(c, startMarker("F"), &FilesGen{Op: "M", Incarnation: "inc-1"}, "F"); err != nil {
111 t.Errorf("files naming M after a warm admission: %v", err)
112 }
113 if err := StartCheck(c, startMarker("F"), &FilesGen{Op: "other", Incarnation: "inc-1"}, "F"); !errors.Is(err, ErrStartFiles) {
114 t.Errorf("files naming another generation = %v, want ErrStartFiles", err)
115 }
116}
117
118func TestStartCheckLaggingMarkerAfterWarmAdmission(t *testing.T) {
119 c := startClaim("F",
120 hostclaim.AdmissionRecord{Generation: "F", Predecessor: "M", Mode: "cold"},
121 hostclaim.AdmissionRecord{Generation: "F", Predecessor: "F", Mode: "warm"})
122 if err := StartCheck(c, startMarker("M"), nil, ""); err != nil {
123 t.Errorf("marker naming M after a warm admission: %v", err)
124 }
125}
126
127func TestStartCheckOriginAfterAFirstAdmission(t *testing.T) {
128 // The first admission left the origin: its predecessor is the origin, so
129 // an absent marker is lagging, not wrong.
130 c := startClaim("g1", hostclaim.AdmissionRecord{Generation: "g1"})
131 if err := StartCheck(c, nil, nil, ""); err != nil {
132 t.Fatalf("absent marker, predecessor the origin: %v", err)
133 }
134 // With no admission reaching the origin, absent is refused.
135 c = startClaim("g2", hostclaim.AdmissionRecord{Generation: "g2", Predecessor: "g1"})
136 if err := StartCheck(c, nil, nil, ""); !errors.Is(err, ErrStartMarker) {
137 t.Errorf("absent marker with a published generation = %v, want ErrStartMarker", err)
138 }
139}
140
141func TestStartCheckMarkerRefusals(t *testing.T) {
142 c := startClaim("g2", hostclaim.AdmissionRecord{Generation: "g2", Predecessor: "g1"})
143 for name, m := range map[string]*genselect.Marker{
144 "unrelated generation": startMarker("other"),
145 "another incarnation": {Op: "g2", Incarnation: "inc-2"},
146 "empty op": {Incarnation: "inc-1"},
147 "root as an op": startMarker(hostclaim.RootGeneration),
148 } {
149 if err := StartCheck(c, m, nil, ""); !errors.Is(err, ErrStartMarker) {
150 t.Errorf("%s = %v, want ErrStartMarker", name, err)
151 }
152 }
153 if err := StartCheck(&hostclaim.Claim{}, nil, nil, ""); !errors.Is(err, ErrStartMarker) {
154 t.Errorf("no holder = %v, want ErrStartMarker", err)
155 }
156 if err := StartCheck(nil, nil, nil, ""); !errors.Is(err, ErrStartMarker) {
157 t.Errorf("nil claim = %v, want ErrStartMarker", err)
158 }
159}
160
161func TestStartCheckFilesGen(t *testing.T) {
162 c := startClaim("g2", hostclaim.AdmissionRecord{Generation: "g2", Predecessor: "g1"})
163 m := startMarker("g2")
164 for name, fg := range map[string]*FilesGen{
165 "another op": {Op: "other", Incarnation: "inc-1"},
166 "another incarnation": {Op: "g2", Incarnation: "inc-2"},
167 "empty": {},
168 } {
169 if err := StartCheck(c, m, fg, ""); !errors.Is(err, ErrStartFiles) {
170 t.Errorf("%s = %v, want ErrStartFiles", name, err)
171 }
172 }
173 // The origin's own files carry no .files-gen, and a stray one is refused.
174 if err := StartCheck(startClaim(""), nil, &FilesGen{Op: "g1", Incarnation: "inc-1"}, ""); !errors.Is(err, ErrStartFiles) {
175 t.Errorf("files-gen on an origin claim = %v, want ErrStartFiles", err)
176 }
177}
178
179func TestStartCheckDBGen(t *testing.T) {
180 c := startClaim("g2", hostclaim.AdmissionRecord{Generation: "g2", Predecessor: "g1"})
181 m := startMarker("g2")
182 // The .gen is plain and exact: a lagging name is a warm database from an
183 // earlier stay, even though the chain would admit it elsewhere.
184 for _, g := range []string{"other", "g1", hostclaim.RootGeneration} {
185 if err := StartCheck(c, m, nil, g); !errors.Is(err, ErrStartDB) {
186 t.Errorf(".gen %q = %v, want ErrStartDB", g, err)
187 }
188 }
189 if err := StartCheck(c, m, nil, "g2"); err != nil {
190 t.Errorf(".gen g2: %v", err)
191 }
192 if err := StartCheck(startClaim(""), nil, nil, "g1"); !errors.Is(err, ErrStartDB) {
193 t.Errorf(".gen on an origin claim = %v, want ErrStartDB", err)
194 }
195}
196
197func TestStartCheckReportsTheFirstFailingMarker(t *testing.T) {
198 c := startClaim("g2")
199 err := StartCheck(c, startMarker("other"), &FilesGen{Op: "other", Incarnation: "inc-1"}, "other")
200 if !errors.Is(err, ErrStartMarker) || errors.Is(err, ErrStartFiles) || errors.Is(err, ErrStartDB) {
201 t.Errorf("err = %v, want only ErrStartMarker", err)
202 }
203}
204
205func TestPreparedCheck(t *testing.T) {
206 h := hostclaim.Holder{Incarnation: "inc-1", Location: "loc/", Generation: "g1"}
207 ok := &FilesGen{Op: "mv-1", Incarnation: "inc-1"}
208 if err := PreparedCheck("mv-1", h, ok, "mv-1"); err != nil {
209 t.Fatalf("both name the prepared generation: %v", err)
210 }
211 cases := []struct {
212 name string
213 prepared string
214 fg *FilesGen
215 db string
216 want error
217 }{
218 {"files-gen absent", "mv-1", nil, "mv-1", ErrStartFiles},
219 {"files-gen another op", "mv-1", &FilesGen{Op: "mv-2", Incarnation: "inc-1"}, "mv-1", ErrStartFiles},
220 {"files-gen another incarnation", "mv-1", &FilesGen{Op: "mv-1", Incarnation: "inc-2"}, "mv-1", ErrStartFiles},
221 {"gen absent", "mv-1", ok, "", ErrStartDB},
222 {"gen another op", "mv-1", ok, "mv-2", ErrStartDB},
223 {"gen the holder's generation", "mv-1", ok, "g1", ErrStartDB},
224 {"no prepared generation", "", &FilesGen{}, "", ErrStartFiles},
225 {"root as prepared", hostclaim.RootGeneration, &FilesGen{Op: hostclaim.RootGeneration, Incarnation: "inc-1"}, hostclaim.RootGeneration, ErrStartFiles},
226 }
227 for _, tc := range cases {
228 if err := PreparedCheck(tc.prepared, h, tc.fg, tc.db); !errors.Is(err, tc.want) {
229 t.Errorf("%s = %v, want %v", tc.name, err, tc.want)
230 }
231 }
232}
233