carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package boxchoice
2
3import (
4 "reflect"
5 "testing"
6 "time"
7)
8
9var policyNow = time.Date(2026, 10, 16, 12, 0, 0, 0, time.UTC)
10
11// roomy is a candidate every §7 rule passes at policyNow.
12func roomy(id string, memFree int64) Candidate {
13 return Candidate{BoxID: id, Region: "eu-west-2", MemFreeMiB: memFree, MemTotalMiB: 8000, DiskFreeMiB: 50000, DiskTotalMiB: 100000,
14 Generation: policyNow.Add(-5 * time.Minute).Format(time.RFC3339)}
15}
16
17func TestPlaceableAppliesEachRule(t *testing.T) {
18 gen := func(d time.Duration) string { return policyNow.Add(d).Format(time.RFC3339) }
19 cases := []struct {
20 name string
21 mut func(*Candidate)
22 role string
23 want bool
24 }{
25 {"every rule holds", nil, "", true},
26 {"memory exactly 15%", func(c *Candidate) { c.MemFreeMiB = 1200 }, "", true},
27 {"memory under 15%", func(c *Candidate) { c.MemFreeMiB = 1199 }, "", false},
28 {"no memory total", func(c *Candidate) { c.MemTotalMiB = 0 }, "", false},
29 {"disk exactly 10%", func(c *Candidate) { c.DiskFreeMiB = 10000 }, "", true},
30 {"disk under 10%", func(c *Candidate) { c.DiskFreeMiB = 9999 }, "", false},
31 {"no disk total", func(c *Candidate) { c.DiskTotalMiB = 0 }, "", false},
32 {"edges file exactly 15 minutes old", func(c *Candidate) { c.Generation = gen(-15 * time.Minute) }, "", true},
33 {"edges file older than 15 minutes", func(c *Candidate) { c.Generation = gen(-15*time.Minute - time.Second) }, "", false},
34 {"edges file a minute in the future", func(c *Candidate) { c.Generation = gen(time.Minute) }, "", true},
35 {"edges file further in the future", func(c *Candidate) { c.Generation = gen(time.Minute + time.Second) }, "", false},
36 {"no generation", func(c *Candidate) { c.Generation = "" }, "", false},
37 {"unreadable generation", func(c *Candidate) { c.Generation = "yesterday" }, "", false},
38 {"legacy role", nil, "legacy", false},
39 {"platform role", nil, "platform", false},
40 {"a role this build does not know", nil, "draining", false},
41 }
42 for _, tc := range cases {
43 t.Run(tc.name, func(t *testing.T) {
44 c := roomy("i-0aaa", 4000)
45 if tc.mut != nil {
46 tc.mut(&c)
47 }
48 got := Placeable([]Candidate{c}, map[string]string{"i-0aaa": tc.role}, policyNow)
49 if (len(got) == 1) != tc.want {
50 t.Fatalf("placeable = %v, want %v", ids(got), tc.want)
51 }
52 })
53 }
54}
55
56func TestExcludeRolesKeepsOrderAndDefaults(t *testing.T) {
57 cands := []Candidate{roomy("c", 1), roomy("a", 1), roomy("b", 1)}
58 got := ExcludeRoles(cands, map[string]string{"a": "legacy", "b": ""})
59 if !reflect.DeepEqual(ids(got), []string{"c", "b"}) {
60 t.Fatalf("got %v", ids(got))
61 }
62 if got := ExcludeRoles(cands, nil); len(got) != 3 {
63 t.Fatalf("no roles excluded %v", ids(got))
64 }
65}
66
67func TestChooseRoomiestSpreadsThenMostMemoryThenHash(t *testing.T) {
68 running := map[string]Load{"a": {Hosts: 1, Apps: map[string]int{"bab/jam": 1}}}
69 // a has the most memory but already runs the app: spread wins.
70 got, ok := ChooseRoomiest("h.example", []Candidate{roomy("a", 7000), roomy("b", 2000), roomy("c", 3000)}, running, "bab/jam")
71 if !ok || got.BoxID != "c" {
72 t.Fatalf("spread then memory: got %q", got.BoxID)
73 }
74 // Spread never refuses: every box runs the app, so memory decides.
75 all := map[string]Load{"a": running["a"], "b": running["a"]}
76 got, _ = ChooseRoomiest("h.example", []Candidate{roomy("a", 1000), roomy("b", 2000)}, all, "bab/jam")
77 if got.BoxID != "b" {
78 t.Fatalf("all run the app: got %q", got.BoxID)
79 }
80 // Fewer hosts is no longer a rule: memory beats it.
81 busy := map[string]Load{"a": {Hosts: 50}, "b": {Hosts: 1}}
82 got, _ = ChooseRoomiest("h.example", []Candidate{roomy("a", 5000), roomy("b", 4000)}, busy, "bab/jam")
83 if got.BoxID != "a" {
84 t.Fatalf("memory over host count: got %q", got.BoxID)
85 }
86 // Equal memory: the P5-D2 hash, whatever the order.
87 one, _ := ChooseRoomiest("h.example", []Candidate{roomy("a", 1000), roomy("b", 1000)}, nil, "bab/jam")
88 two, _ := ChooseRoomiest("h.example", []Candidate{roomy("b", 1000), roomy("a", 1000)}, nil, "bab/jam")
89 if one.BoxID != two.BoxID {
90 t.Fatalf("order changed the answer: %q %q", one.BoxID, two.BoxID)
91 }
92 want, _ := Choose("h.example", []Candidate{roomy("a", 1000), roomy("b", 1000)}, nil, "bab/jam")
93 if one.BoxID != want.BoxID {
94 t.Fatalf("hash tie-break differs from spec 1's: %q, want %q", one.BoxID, want.BoxID)
95 }
96 if _, ok := ChooseRoomiest("h.example", nil, nil, "bab/jam"); ok {
97 t.Fatal("chose from nothing")
98 }
99}
100
101func TestEligibleCarriesTotalsAndGeneration(t *testing.T) {
102 body := haBox("i-0aaa", func(m map[string]any) {
103 m["headroom"] = map[string]any{"mem_free_mib": 1000, "disk_free_mib": 2000, "mem_total_mib": 8000, "disk_total_mib": 40000, "awake": 1}
104 m["generation"] = "2026-10-16T11:55:00Z"
105 })
106 got := Eligible(views(t, body), rosterFor("i-0aaa"), "uk", "eu-west-2", "ie", testKeyFP, nil)
107 want := Candidate{BoxID: "i-0aaa", Region: "eu-west-2", MemFreeMiB: 1000, DiskFreeMiB: 2000, MemTotalMiB: 8000, DiskTotalMiB: 40000, Awake: 1, Generation: "2026-10-16T11:55:00Z"}
108 if len(got) != 1 || got[0] != want {
109 t.Fatalf("got %+v, want %+v", got, want)
110 }
111}
112