carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "testing"
5 "time"
6)
7
8// The edges file schedule (#174): a pass within one watch tick of a registry
9// change, the old timer kept, and a failed change pass retried.
10
11func newTestSchedule(t0 time.Time) *edgesFileSchedule {
12 return newEdgesFileSchedule(t0, func() time.Duration { return 5 * time.Minute })
13}
14
15// TestEdgesFileScheduleFirstFingerprintOnlyBaselines — arming must not
16// publish in the loop's first two seconds; the first timed pass stays a
17// minute after arm, as it was before the change trigger existed.
18func TestEdgesFileScheduleFirstFingerprintOnlyBaselines(t *testing.T) {
19 t0 := time.Unix(1_000_000, 0)
20 s := newTestSchedule(t0)
21 if s.due(t0.Add(2*time.Second), "fp-a", true) {
22 t.Fatal("the first fingerprint triggered a pass; it should only baseline")
23 }
24 if s.due(t0.Add(30*time.Second), "fp-a", true) {
25 t.Fatal("an unchanged fingerprint triggered a pass before the timer")
26 }
27 if !s.due(t0.Add(time.Minute), "fp-a", true) {
28 t.Fatal("the first timed pass did not come due a minute after arm")
29 }
30}
31
32// TestEdgesFileScheduleChangeTriggersAPass is the headline: a route written
33// to the registry is published on the next tick, not up to six minutes
34// later.
35func TestEdgesFileScheduleChangeTriggersAPass(t *testing.T) {
36 t0 := time.Unix(1_000_000, 0)
37 s := newTestSchedule(t0)
38 s.due(t0.Add(2*time.Second), "fp-a", true)
39 now := t0.Add(4 * time.Second)
40 if !s.due(now, "fp-b", true) {
41 t.Fatal("a fingerprint change did not trigger a pass")
42 }
43 s.done(now, true)
44 if s.due(t0.Add(6*time.Second), "fp-b", true) {
45 t.Fatal("the same fingerprint triggered a second pass after a good one")
46 }
47 // A change pass does not move the timer: it is still due at arm + 1min.
48 if !s.due(t0.Add(time.Minute), "fp-b", true) {
49 t.Fatal("a change pass pushed the timer back")
50 }
51}
52
53// TestEdgesFileScheduleFailedChangePassIsRetried — the baseline advances only
54// on a successful pass, so a failed bucket write is retried after the first
55// backoff step (2s) instead of waiting for the timer.
56func TestEdgesFileScheduleFailedChangePassIsRetried(t *testing.T) {
57 t0 := time.Unix(1_000_000, 0)
58 s := newTestSchedule(t0)
59 s.due(t0.Add(2*time.Second), "fp-a", true)
60 s.due(t0.Add(4*time.Second), "fp-b", true)
61 s.done(t0.Add(4*time.Second), false)
62 if !s.due(t0.Add(6*time.Second), "fp-b", true) {
63 t.Fatal("a change whose pass failed was not retried on the next tick")
64 }
65 s.done(t0.Add(6*time.Second), true)
66 if s.due(t0.Add(8*time.Second), "fp-b", true) {
67 t.Fatal("a change was passed again after its retry succeeded")
68 }
69}
70
71// TestEdgesFileScheduleTimerSurvivesAnUnreadableRegistry — a fingerprint
72// error never triggers a change pass, and never stops the timer.
73func TestEdgesFileScheduleTimerSurvivesAnUnreadableRegistry(t *testing.T) {
74 t0 := time.Unix(1_000_000, 0)
75 s := newTestSchedule(t0)
76 s.due(t0.Add(2*time.Second), "fp-a", true)
77 if s.due(t0.Add(4*time.Second), "", false) {
78 t.Fatal("an unreadable fingerprint triggered a pass")
79 }
80 now := t0.Add(time.Minute)
81 if !s.due(now, "", false) {
82 t.Fatal("the timer did not fire while the fingerprint was unreadable")
83 }
84 s.done(now, true)
85 // The timed pass could not vouch for a fingerprint, so the baseline is
86 // still fp-a: a later read of fp-a is not a change, fp-b is.
87 if s.due(now.Add(2*time.Second), "fp-a", true) {
88 t.Fatal("the baseline moved on a pass that saw no fingerprint")
89 }
90 if !s.due(now.Add(4*time.Second), "fp-b", true) {
91 t.Fatal("a change after an unreadable spell was missed")
92 }
93}
94
95// TestEdgesFileScheduleTimerAdvancesOnFailure — a failed timed pass moves the
96// timer on by the cadence, exactly as the old sleep loop did.
97func TestEdgesFileScheduleTimerAdvancesOnFailure(t *testing.T) {
98 t0 := time.Unix(1_000_000, 0)
99 s := newTestSchedule(t0)
100 now := t0.Add(time.Minute)
101 if !s.due(now, "fp-a", true) {
102 t.Fatal("timer not due")
103 }
104 s.done(now, false)
105 if s.due(now.Add(2*time.Minute), "fp-a", true) {
106 t.Fatal("a failed timed pass did not move the timer on")
107 }
108 if !s.due(now.Add(5*time.Minute), "fp-a", true) {
109 t.Fatal("the timer did not come due one cadence after the failed pass")
110 }
111}
112
113// TestEdgesFileScheduleFailedChangePassesBackOff — a bucket refusing every
114// write is retried on a doubling backoff, not every tick, and a success
115// resets it.
116func TestEdgesFileScheduleFailedChangePassesBackOff(t *testing.T) {
117 t0 := time.Unix(1_000_000, 0)
118 s := newTestSchedule(t0)
119 s.due(t0, "fp-a", true)
120 now := t0.Add(2 * time.Second)
121 if !s.due(now, "fp-b", true) {
122 t.Fatal("change not due")
123 }
124 s.done(now, false) // retry no sooner than +2s
125 if s.due(now.Add(time.Second), "fp-b", true) {
126 t.Fatal("a failed change pass was retried inside its backoff")
127 }
128 now = now.Add(2 * time.Second)
129 if !s.due(now, "fp-b", true) {
130 t.Fatal("not retried once the 2s backoff passed")
131 }
132 s.done(now, false) // now +4s
133 if s.due(now.Add(3*time.Second), "fp-b", true) {
134 t.Fatal("the backoff did not double")
135 }
136 now = now.Add(4 * time.Second)
137 if !s.due(now, "fp-b", true) {
138 t.Fatal("not retried once the 4s backoff passed")
139 }
140 s.done(now, true)
141 s.due(now.Add(2*time.Second), "fp-b", true)
142 later := now.Add(4 * time.Second)
143 if !s.due(later, "fp-c", true) {
144 t.Fatal("a success did not reset the backoff")
145 }
146 for i := 0; i < 20; i++ {
147 s.done(later, false)
148 }
149 if s.backoff > edgesFileRetryMax {
150 t.Fatalf("backoff %s grew past its cap %s", s.backoff, edgesFileRetryMax)
151 }
152}
153