carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package provision
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10// proposeFixture is a small census: one legacy unit, one authority unit, one
11// envelope, sealed, and a scan that matches it.
12func proposeFixture(t *testing.T) (rootReadScan, rootReadInventory, string) {
13 t.Helper()
14 legacy := classifiedRootRead{rootReadEntry: rootReadEntry{File: "internal/a/a.go", Unit: "Wake", Kinds: "registry:Host@10,registry:DBPath@20", Digest: strings.Repeat("1", 64)}, Class: "legacy", Task: "accepted-legacy", Basis: "accepted-legacy;rev3=J1;basis=root-route"}
15 auth := classifiedRootRead{rootReadEntry: rootReadEntry{File: "internal/a/b.go", Unit: "Route", Kinds: "registry:Host@5", Digest: strings.Repeat("2", 64)}, Class: "authority", Task: "E4", Basis: "root-route"}
16 fp := rootReadLegacyFingerprint(legacy)
17 inv := rootReadInventory{
18 Entries: map[string]classifiedRootRead{"internal/a/a.go:Wake": legacy, "internal/a/b.go:Route": auth},
19 Frozen: map[string]classifiedRootRead{"internal/a/a.go:Wake": legacy},
20 Legacy: map[string]bool{fp: true},
21 Coverage: map[string]string{"internal/a": strings.Repeat("c", 64)},
22 }
23 got := rootReadScan{
24 Entries: []rootReadEntry{legacy.rootReadEntry, auth.rootReadEntry},
25 Declarations: map[string]string{"internal/a/a.go:Wake": legacy.Digest, "internal/a/b.go:Route": auth.Digest},
26 Coverage: map[string]string{"internal/a": strings.Repeat("c", 64)},
27 }
28 return got, inv, rootReadLegacySeal(inv)
29}
30
31// change edits the scan as a code change would: a new digest, and the
32// unit's read sites (kinds) as given.
33func change(got *rootReadScan, key, kinds, digest string) {
34 for i, e := range got.Entries {
35 if e.File+":"+e.Unit == key {
36 got.Entries[i].Kinds, got.Entries[i].Digest = kinds, digest
37 }
38 }
39 got.Declarations[key] = digest
40}
41
42func TestCensusClassifiesEachKindOfChange(t *testing.T) {
43 got, inv, seal := proposeFixture(t)
44 // body: the same reads at moved offsets.
45 change(&got, "internal/a/a.go:Wake", "registry:Host@14,registry:DBPath@24", strings.Repeat("3", 64))
46 // reads: a kind of read the unit did not make.
47 change(&got, "internal/a/b.go:Route", "registry:Host@5,registry:Socket@9", strings.Repeat("4", 64))
48 got.Coverage["internal/a"] = strings.Repeat("d", 64)
49 got.Entries = append(got.Entries, rootReadEntry{File: "internal/a/c.go", Unit: "New", Kinds: "file:Open@1", Digest: strings.Repeat("5", 64)})
50 got.Declarations["internal/a/c.go:New"] = strings.Repeat("5", 64)
51
52 classes := map[string]string{}
53 for _, c := range classifyRootReads(got, inv, seal) {
54 classes[c.Key] = c.Class
55 }
56 want := map[string]string{
57 "internal/a/a.go:Wake": censusBody,
58 "internal/a/b.go:Route": censusReads,
59 "internal/a": censusEnvelope,
60 "internal/a/c.go:New": censusNew,
61 }
62 for k, v := range want {
63 if classes[k] != v {
64 t.Errorf("class of %s = %q, want %q (all: %v)", k, classes[k], v, classes)
65 }
66 }
67 if len(classes) != len(want) {
68 t.Errorf("classes = %v, want exactly %v", classes, want)
69 }
70}
71
72// sites: another read of a kind the unit already makes.
73func TestCensusClassifiesMoreReadsOfAKnownKindAsSites(t *testing.T) {
74 got, inv, seal := proposeFixture(t)
75 change(&got, "internal/a/b.go:Route", "registry:Host@5,registry:Host@9", strings.Repeat("4", 64))
76 cs := classifyRootReads(got, inv, seal)
77 if len(cs) != 1 || cs[0].Class != censusSites {
78 t.Fatalf("changes = %+v, want one sites", cs)
79 }
80}
81
82// The card never shows less than the census: a discrepancy no unit or
83// envelope change explains is an "other" decision with the census's own
84// message (Astra review, census-approvals P2).
85func TestCensusClassifiesEveryOtherDiscrepancy(t *testing.T) {
86 got, inv, seal := proposeFixture(t)
87 for name, tc := range map[string]struct {
88 edit func(*rootReadScan, *rootReadInventory) string
89 want string
90 }{
91 "a wrong seal": {
92 edit: func(*rootReadScan, *rootReadInventory) string { return strings.Repeat("e", 64) },
93 want: "changed legacy freeze",
94 },
95 "a removed declaration back in the tree": {
96 edit: func(g *rootReadScan, i *rootReadInventory) string {
97 i.Removed = map[string]string{"internal/a/z.go:Gone": "E4;census-ruling-x"}
98 g.Declarations["internal/a/z.go:Gone"] = strings.Repeat("6", 64)
99 return seal
100 },
101 want: "internal/a/z.go:Gone",
102 },
103 } {
104 t.Run(name, func(t *testing.T) {
105 g, i := got, cloneRootReadInventory(inv)
106 g.Declarations = cloneMap(got.Declarations)
107 s := tc.edit(&g, &i)
108 cs := classifyRootReads(g, i, s)
109 if len(cs) == 0 {
110 t.Fatal("classified nothing")
111 }
112 for _, c := range cs {
113 if c.Class != censusOther || !strings.Contains(c.Key, tc.want) {
114 t.Fatalf("changes = %+v, want an other carrying %q", cs, tc.want)
115 }
116 }
117 if card := rootReadCard("x", cs, nil, s, ""); strings.Contains(card, "Nothing to approve") || !strings.Contains(card, tc.want) {
118 t.Errorf("card:\n%s", card)
119 }
120 })
121 }
122}
123
124func TestCensusClassifiesARemovedUnit(t *testing.T) {
125 got, inv, seal := proposeFixture(t)
126 got.Entries = got.Entries[:1]
127 delete(got.Declarations, "internal/a/b.go:Route")
128 cs := classifyRootReads(got, inv, seal)
129 if len(cs) != 1 || cs[0].Class != censusRemoved || cs[0].Key != "internal/a/b.go:Route" {
130 t.Fatalf("changes = %+v, want one removed", cs)
131 }
132}
133
134// The proposal is the whole job: loaded onto the inventory it leaves no
135// discrepancy, under the seal it reports, and every row names the ruling.
136func TestCensusProposalClosesEveryApprovableChange(t *testing.T) {
137 got, inv, seal := proposeFixture(t)
138 change(&got, "internal/a/a.go:Wake", "registry:Host@14,registry:DBPath@24", strings.Repeat("3", 64))
139 change(&got, "internal/a/b.go:Route", "registry:Host@5,registry:Host@9", strings.Repeat("4", 64))
140 got.Coverage["internal/a"] = strings.Repeat("d", 64)
141 if len(censusDiscrepancies(got, inv, seal)) == 0 {
142 t.Fatal("fixture change made no discrepancy")
143 }
144 claims := map[string]string{
145 "internal/a/a.go:Wake": "logs the wake's timing; no read added",
146 "internal/a/b.go:Route": "reads the route's host again to log it",
147 }
148 p, err := proposeRootReads(got, inv, seal, "wake-timing", claims)
149 if err != nil {
150 t.Fatal(err)
151 }
152 path := filepath.Join(t.TempDir(), "overlay.tsv")
153 if err := os.WriteFile(path, []byte(p.Overlay), 0o644); err != nil {
154 t.Fatal(err)
155 }
156 after := cloneRootReadInventory(inv)
157 if err := loadRootReadInventory(path, &after, true); err != nil {
158 t.Fatal(err)
159 }
160 if errs := censusDiscrepancies(got, after, p.Seal); len(errs) != 0 {
161 t.Fatalf("after the proposal: %v\n%s", errs, p.Overlay)
162 }
163 if rootReadLegacySeal(inv) != seal {
164 t.Error("proposing changed the inventory it was given")
165 }
166 for _, line := range strings.Split(p.Overlay, "\n") {
167 if line != "" && !strings.HasPrefix(line, "#") && !strings.HasPrefix(line, "@coverage") && !strings.Contains(line, "rev3=census-ruling-wake-timing;basis=") {
168 t.Errorf("row does not name the ruling: %s", line)
169 }
170 }
171 for _, want := range []string{"census-ruling-wake-timing", "internal/a/a.go", "body", "sites", "+registry:Host", "internal/a", seal, p.Seal, "logs the wake's timing"} {
172 if !strings.Contains(p.Card, want) {
173 t.Errorf("card lacks %q:\n%s", want, p.Card)
174 }
175 }
176}
177
178func TestCensusProposalRefuses(t *testing.T) {
179 for name, tc := range map[string]struct {
180 edit func(*rootReadScan)
181 claims map[string]string
182 want string
183 }{
184 "a change without a claim": {
185 edit: func(g *rootReadScan) {
186 change(g, "internal/a/a.go:Wake", "registry:Host@14,registry:DBPath@24", strings.Repeat("3", 64))
187 },
188 claims: map[string]string{},
189 want: "no claim",
190 },
191 "a claim for no change": {
192 edit: func(g *rootReadScan) {
193 change(g, "internal/a/a.go:Wake", "registry:Host@14,registry:DBPath@24", strings.Repeat("3", 64))
194 },
195 claims: map[string]string{"internal/a/a.go:Wake": "x", "internal/a/z.go:Typo": "y"},
196 want: "not a change",
197 },
198 "a new kind of read": {
199 edit: func(g *rootReadScan) {
200 change(g, "internal/a/b.go:Route", "registry:Host@5,file:Open@9", strings.Repeat("4", 64))
201 },
202 claims: map[string]string{"internal/a/b.go:Route": "opens a file"},
203 want: "decision",
204 },
205 "a new unit": {
206 edit: func(g *rootReadScan) {
207 g.Entries = append(g.Entries, rootReadEntry{File: "internal/a/c.go", Unit: "New", Kinds: "file:Open@1", Digest: strings.Repeat("5", 64)})
208 g.Declarations["internal/a/c.go:New"] = strings.Repeat("5", 64)
209 },
210 claims: map[string]string{"internal/a/c.go:New": "opens a file"},
211 want: "decision",
212 },
213 "a claim with a tab": {
214 edit: func(g *rootReadScan) {
215 change(g, "internal/a/a.go:Wake", "registry:Host@14,registry:DBPath@24", strings.Repeat("3", 64))
216 },
217 claims: map[string]string{"internal/a/a.go:Wake": "a\tb"},
218 want: "tab",
219 },
220 "nothing to propose": {
221 edit: func(*rootReadScan) {},
222 claims: map[string]string{},
223 want: "nothing",
224 },
225 } {
226 t.Run(name, func(t *testing.T) {
227 got, inv, seal := proposeFixture(t)
228 tc.edit(&got)
229 _, err := proposeRootReads(got, inv, seal, "x", tc.claims)
230 if err == nil || !strings.Contains(err.Error(), tc.want) {
231 t.Fatalf("err = %v, want one mentioning %q", err, tc.want)
232 }
233 })
234 }
235}
236
237func TestCensusClaimsFile(t *testing.T) {
238 path := filepath.Join(t.TempDir(), "claims")
239 if err := os.WriteFile(path, []byte("# what changed\n\ninternal/a/a.go:Wake\tlogs the timing; no read added\n"), 0o644); err != nil {
240 t.Fatal(err)
241 }
242 claims, err := readCensusClaims(path)
243 if err != nil || claims["internal/a/a.go:Wake"] != "logs the timing; no read added" || len(claims) != 1 {
244 t.Fatalf("claims = %v, %v", claims, err)
245 }
246 if err := os.WriteFile(path, []byte("internal/a/a.go:Wake no tab\n"), 0o644); err != nil {
247 t.Fatal(err)
248 }
249 if _, err := readCensusClaims(path); err == nil {
250 t.Fatal("a line without a tab was accepted")
251 }
252}
253
254// writeFixtureRoot is a tree with an empty chain manifest and the notes dir.
255func writeFixtureRoot(t *testing.T) string {
256 t.Helper()
257 root := t.TempDir()
258 for _, d := range []string{"internal/provision/testdata", "docs/superpowers/notes"} {
259 if err := os.MkdirAll(filepath.Join(root, d), 0o755); err != nil {
260 t.Fatal(err)
261 }
262 }
263 if err := os.WriteFile(filepath.Join(root, rootReadChainManifest), []byte("# manifest\n"), 0o644); err != nil {
264 t.Fatal(err)
265 }
266 return root
267}
268
269func readFile(t *testing.T, path string) string {
270 t.Helper()
271 raw, err := os.ReadFile(path)
272 if err != nil {
273 t.Fatal(err)
274 }
275 return string(raw)
276}
277
278func TestCensusProposalWritesAndReplacesItsOwnOverlay(t *testing.T) {
279 root := writeFixtureRoot(t)
280 first := rootReadProposal{Overlay: "# Census overlay x, " + censusProposeMark + ".\nrow1\n", Seal: strings.Repeat("a", 64), Card: "card 1"}
281 if err := writeRootReadProposal(root, "x", first); err != nil {
282 t.Fatal(err)
283 }
284 second := rootReadProposal{Overlay: "# Census overlay x, " + censusProposeMark + ".\nrow2\n", Seal: strings.Repeat("b", 64), Card: "card 2"}
285 if err := writeRootReadProposal(root, "x", second); err != nil {
286 t.Fatal(err)
287 }
288 if got, want := readFile(t, filepath.Join(root, rootReadChainManifest)), "# manifest\nplan14-root-reads-x.tsv\t"+strings.Repeat("b", 64)+"\n"; got != want {
289 t.Errorf("manifest = %q, want %q", got, want)
290 }
291 if got := readFile(t, filepath.Join(root, "internal/provision/testdata/plan14-root-reads-x.tsv")); got != second.Overlay {
292 t.Errorf("overlay = %q", got)
293 }
294 if got := readFile(t, filepath.Join(root, "docs/superpowers/notes/census-proposal-x.md")); got != "card 2" {
295 t.Errorf("card = %q", got)
296 }
297}
298
299func TestCensusProposalRefusesWhatItDidNotWrite(t *testing.T) {
300 for name, setup := range map[string]func(t *testing.T, root string){
301 "a hand-written overlay of that name": func(t *testing.T, root string) {
302 if err := os.WriteFile(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-x.tsv"), []byte("# Census overlay x (by hand)\n"), 0o644); err != nil {
303 t.Fatal(err)
304 }
305 },
306 "its overlay followed by another": func(t *testing.T, root string) {
307 mine := rootReadProposal{Overlay: "# " + censusProposeMark + "\n", Seal: strings.Repeat("a", 64)}
308 if err := writeRootReadProposal(root, "x", mine); err != nil {
309 t.Fatal(err)
310 }
311 if err := writeRootReadProposal(root, "y", mine); err != nil {
312 t.Fatal(err)
313 }
314 },
315 } {
316 t.Run(name, func(t *testing.T) {
317 root := writeFixtureRoot(t)
318 setup(t, root)
319 manifest := readFile(t, filepath.Join(root, rootReadChainManifest))
320 overlay, _ := os.ReadFile(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-x.tsv"))
321 card, _ := os.ReadFile(filepath.Join(root, "docs/superpowers/notes/census-proposal-x.md"))
322 p := rootReadProposal{Overlay: "# " + censusProposeMark + "\nnew\n", Seal: strings.Repeat("c", 64), Card: "card"}
323 if err := writeRootReadProposal(root, "x", p); err == nil {
324 t.Fatal("replaced an overlay it should not have")
325 }
326 if got := readFile(t, filepath.Join(root, rootReadChainManifest)); got != manifest {
327 t.Errorf("manifest changed: %q", got)
328 }
329 if got, _ := os.ReadFile(filepath.Join(root, "internal/provision/testdata/plan14-root-reads-x.tsv")); string(got) != string(overlay) {
330 t.Errorf("overlay changed: %q", got)
331 }
332 if got, _ := os.ReadFile(filepath.Join(root, "docs/superpowers/notes/census-proposal-x.md")); string(got) != string(card) {
333 t.Errorf("card changed: %q", got)
334 }
335 })
336 }
337}
338
339// Keys may contain spaces, and a directory is a prefix of its units' keys.
340func TestCensusNamesWholeKeys(t *testing.T) {
341 for _, tc := range []struct {
342 msg, key string
343 want bool
344 }{
345 {"changed root read internal/h/k.go:type gen; review class", "internal/h/k.go:type gen", true},
346 {"changed legacy declaration internal/h/k.go:type gen", "internal/h/k.go:type gen", true},
347 {"changed root read internal/h/k.go:type general; review", "internal/h/k.go:type gen", false},
348 {"changed root read internal/a/x.go:Foo; review", "internal/a", false},
349 {"changed root source envelope internal/a", "internal/a", true},
350 {"stale root read internal/a/x.go:Foo", "internal/a/x.go:Foo", true},
351 } {
352 if got := censusNames(tc.msg, tc.key); got != tc.want {
353 t.Errorf("censusNames(%q, %q) = %v, want %v", tc.msg, tc.key, got, tc.want)
354 }
355 }
356}
357
358// A body change to a unit whose key has a space is still an approval.
359func TestCensusClassifiesAKeyWithASpace(t *testing.T) {
360 got, inv, seal := proposeFixture(t)
361 key := "internal/a/t.go:type gen"
362 e := classifiedRootRead{rootReadEntry: rootReadEntry{File: "internal/a/t.go", Unit: "type gen", Kinds: "registry:Host@3", Digest: strings.Repeat("7", 64)}, Class: "legacy", Task: "accepted-legacy", Basis: "accepted-legacy;rev3=J1;basis=root-route"}
363 inv.Entries[key], inv.Frozen[key] = e, e
364 inv.Legacy[rootReadLegacyFingerprint(e)] = true
365 seal = rootReadLegacySeal(inv)
366 got.Entries = append(got.Entries, e.rootReadEntry)
367 got.Declarations[key] = e.Digest
368 change(&got, key, "registry:Host@4", strings.Repeat("8", 64))
369 cs := classifyRootReads(got, inv, seal)
370 if len(cs) != 1 || cs[0].Class != censusBody || cs[0].Key != key {
371 t.Fatalf("changes = %+v, want one body for %q", cs, key)
372 }
373}
374