carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "bytes"
5 "os"
6 "path/filepath"
7 "testing"
8 "time"
9)
10
11const (
12 nsHost = "held.example.com"
13 nsOp = "mv_abcdefghijklmnopqrstuvwxyz"
14)
15
16func adoptionRefusalFor(host string) Refusal {
17 return Refusal{Host: host, App: "console", Version: "v0", SHA256: "aa", Reason: "did not come up",
18 PointerPromotedAt: time.Date(2026, 10, 1, 0, 0, 0, 0, time.UTC), RefusedAt: time.Date(2026, 10, 1, 0, 1, 0, 0, time.UTC)}
19}
20
21func heldRefusalFor(host string) Refusal {
22 return Refusal{Host: host, App: "console", Version: "v1", SHA256: "bb", Reason: "not proven: staged sha256 bb",
23 RefusedAt: time.Date(2026, 10, 7, 0, 0, 0, 0, time.UTC), Kind: RefusalNotProven, Recovered: true}
24}
25
26// Final review finding 3: held cleanup reads a record and then removes a
27// path. While held and adoption refusals shared refusals/<host>.json, an
28// adoption refusal written between the two (adoption does not take the
29// root adopt lock) was removed with it. Held refusals now live in their own
30// namespace, so the adoption refusal survives. The hook makes the
31// interleaving deterministic.
32func TestHeldCleanupLeavesAConcurrentAdoptionRefusal(t *testing.T) {
33 bake := t.TempDir()
34 if err := heldRefusalWriter(bake, nsOp)(heldRefusalFor(nsHost)); err != nil {
35 t.Fatal(err)
36 }
37 prev := heldRetireBeforeClear
38 t.Cleanup(func() { heldRetireBeforeClear = prev })
39 raced := false
40 heldRetireBeforeClear = func(host string) {
41 raced = true
42 if err := WriteRefusal(bake, adoptionRefusalFor(host)); err != nil {
43 t.Fatal(err)
44 }
45 }
46 retired, err := RetireHeldRefusals(bake, func(Refusal) bool { return false })
47 if err != nil || len(retired) != 1 || !raced {
48 t.Fatalf("retired %v, %v (raced %v)", retired, err, raced)
49 }
50 r, err := ReadRefusal(bake, nsHost)
51 if err != nil || r == nil || r.MoveOp != "" || r.Reason != "did not come up" {
52 t.Fatalf("the adoption refusal written during held cleanup: %+v, %v; want it kept", r, err)
53 }
54 if held, _ := ListHeldRefusals(bake); len(held) != 0 {
55 t.Fatalf("held refusals left: %+v", held)
56 }
57}
58
59// The held writer never touches adoption's record: with an adoption
60// refusal standing for the host, the held one is still recorded, beside
61// it, and the adoption file is byte-identical. Adoption's readers
62// (ReadRefusal, ListRefusals) never see a held record.
63func TestHeldRefusalsHaveTheirOwnNamespace(t *testing.T) {
64 bake := t.TempDir()
65 if err := WriteRefusal(bake, adoptionRefusalFor(nsHost)); err != nil {
66 t.Fatal(err)
67 }
68 path := filepath.Join(RefusalDir(bake), nsHost+".json")
69 before, err := os.ReadFile(path)
70 if err != nil {
71 t.Fatal(err)
72 }
73 if err := heldRefusalWriter(bake, nsOp)(heldRefusalFor(nsHost)); err != nil {
74 t.Fatalf("the held refusal was not recorded beside an adoption one: %v", err)
75 }
76 if after, _ := os.ReadFile(path); !bytes.Equal(before, after) {
77 t.Fatalf("the adoption refusal changed:\n%s\nwant\n%s", after, before)
78 }
79 held, err := ListHeldRefusals(bake)
80 if err != nil || len(held) != 1 || held[0].MoveOp != nsOp || held[0].Host != nsHost {
81 t.Fatalf("held refusals %+v, %v", held, err)
82 }
83 // Adoption's readers see only adoption's record, on this host and on
84 // one with only a held refusal.
85 other := "only-held.example.com"
86 if err := heldRefusalWriter(bake, nsOp)(heldRefusalFor(other)); err != nil {
87 t.Fatal(err)
88 }
89 if r, _ := ReadRefusal(bake, other); r != nil {
90 t.Fatalf("adoption reads a held refusal: %+v", r)
91 }
92 recs, _ := ListRefusals(bake)
93 if len(recs) != 1 || recs[0].MoveOp != "" {
94 t.Fatalf("adoption's list holds %+v", recs)
95 }
96 // Retiring the held one leaves adoption's.
97 if err := RetireHeldRefusal(bake, nsHost, nsOp); err != nil {
98 t.Fatal(err)
99 }
100 if after, _ := os.ReadFile(path); !bytes.Equal(before, after) {
101 t.Fatal("retiring the held refusal touched the adoption one")
102 }
103}
104