For reading and verification only. You may not copy, use, modify or redistribute this code. Full notice on the main page.
tunnel/clock_test.go
| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "sort" |
| 7 | "sync" |
| 8 | "testing" |
| 9 | "time" |
| 10 | ) |
| 11 | |
| 12 | // fakeClock is a clock the tests move by hand: Advance sets the time forward and runs every timer that came due, each in its own goroutine (as |
| 13 | // time.AfterFunc does), in the order of their moments. A timer asked for a moment that is already past runs at once. Nothing else in the node reads |
| 14 | // it: socket deadlines stay on the real clock. |
| 15 | type fakeClock struct { |
| 16 | mu sync.Mutex |
| 17 | now time.Time |
| 18 | timers []*fakeTimer |
| 19 | fired int // timers run so far (a test waits for them) |
| 20 | } |
| 21 | |
| 22 | type fakeTimer struct { |
| 23 | when time.Time |
| 24 | f func() |
| 25 | stopped bool |
| 26 | done bool |
| 27 | } |
| 28 | |
| 29 | func newFakeClock() *fakeClock { return &fakeClock{now: time.Now()} } |
| 30 | |
| 31 | func (c *fakeClock) Now() time.Time { |
| 32 | c.mu.Lock() |
| 33 | defer c.mu.Unlock() |
| 34 | return c.now |
| 35 | } |
| 36 | |
| 37 | func (c *fakeClock) AfterFunc(d time.Duration, f func()) func() bool { |
| 38 | c.mu.Lock() |
| 39 | t := &fakeTimer{when: c.now.Add(d), f: f} |
| 40 | if d <= 0 { |
| 41 | t.done = true |
| 42 | c.fired++ |
| 43 | c.mu.Unlock() |
| 44 | go f() |
| 45 | return func() bool { return false } |
| 46 | } |
| 47 | c.timers = append(c.timers, t) |
| 48 | c.mu.Unlock() |
| 49 | return func() bool { |
| 50 | c.mu.Lock() |
| 51 | defer c.mu.Unlock() |
| 52 | was := !t.stopped && !t.done |
| 53 | t.stopped = true |
| 54 | return was |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | // Advance moves the clock forward by d and runs what came due. |
| 59 | func (c *fakeClock) Advance(d time.Duration) { |
| 60 | c.mu.Lock() |
| 61 | c.now = c.now.Add(d) |
| 62 | var due []*fakeTimer |
| 63 | rest := c.timers[:0] |
| 64 | for _, t := range c.timers { |
| 65 | switch { |
| 66 | case t.stopped || t.done: |
| 67 | case !t.when.After(c.now): |
| 68 | t.done = true |
| 69 | c.fired++ |
| 70 | due = append(due, t) |
| 71 | default: |
| 72 | rest = append(rest, t) |
| 73 | } |
| 74 | } |
| 75 | c.timers = rest |
| 76 | c.mu.Unlock() |
| 77 | sort.SliceStable(due, func(i, j int) bool { return due[i].when.Before(due[j].when) }) |
| 78 | for _, t := range due { |
| 79 | go t.f() |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | // pending is the number of timers waiting. |
| 84 | func (c *fakeClock) pending() int { |
| 85 | c.mu.Lock() |
| 86 | defer c.mu.Unlock() |
| 87 | n := 0 |
| 88 | for _, t := range c.timers { |
| 89 | if !t.stopped && !t.done { |
| 90 | n++ |
| 91 | } |
| 92 | } |
| 93 | return n |
| 94 | } |
| 95 | |
| 96 | // ---------------------------------------------------------------------------------------------------------------------------- the helpers of clock.go |
| 97 | |
| 98 | func TestClock_TheFakeClockRunsDueTimersInOrderStopsOnRequestAndRunsAPastOneAtOnce(t *testing.T) { |
| 99 | c := newFakeClock() |
| 100 | var mu sync.Mutex |
| 101 | var order []string |
| 102 | note := func(s string) func() { return func() { mu.Lock(); order = append(order, s); mu.Unlock() } } |
| 103 | c.AfterFunc(20*time.Second, note("b")) |
| 104 | c.AfterFunc(10*time.Second, note("a")) |
| 105 | stopped := c.AfterFunc(15*time.Second, note("stopped")) |
| 106 | c.AfterFunc(40*time.Second, note("later")) |
| 107 | if !stopped() { |
| 108 | t.Fatal("a pending timer was not stopped") |
| 109 | } |
| 110 | if stopped() { |
| 111 | t.Fatal("a timer was stopped twice") |
| 112 | } |
| 113 | c.Advance(25 * time.Second) |
| 114 | eventually(t, "the two due timers", time.Second, func() bool { mu.Lock(); defer mu.Unlock(); return len(order) == 2 }) |
| 115 | if c.pending() != 1 { |
| 116 | t.Fatalf("pending %d", c.pending()) |
| 117 | } |
| 118 | c.AfterFunc(-time.Second, note("past")) // a moment already gone runs at once |
| 119 | eventually(t, "a past timer", time.Second, func() bool { mu.Lock(); defer mu.Unlock(); return len(order) == 3 }) |
| 120 | mu.Lock() |
| 121 | defer mu.Unlock() |
| 122 | if order[0] != "a" && order[0] != "b" || order[2] != "past" { |
| 123 | t.Fatalf("order %v", order) |
| 124 | } |
| 125 | } |
| 126 | |
| 127 | func TestClock_SleepEndsWithTheClockOrWithTheContext(t *testing.T) { |
| 128 | c := newFakeClock() |
| 129 | clk := newClock(c.Now, c.AfterFunc) |
| 130 | done := make(chan bool, 1) |
| 131 | go func() { done <- clk.sleep(context.Background(), time.Minute) }() |
| 132 | eventually(t, "the sleeper", time.Second, func() bool { return c.pending() == 1 }) |
| 133 | c.Advance(time.Minute) |
| 134 | if !<-done { |
| 135 | t.Fatal("a sleep that ran to its end reported that it was cut") |
| 136 | } |
| 137 | ctx, cancel := context.WithCancel(context.Background()) |
| 138 | go func() { done <- clk.sleep(ctx, time.Hour) }() |
| 139 | eventually(t, "the sleeper", time.Second, func() bool { return c.pending() == 1 }) |
| 140 | cancel() |
| 141 | if <-done { |
| 142 | t.Fatal("a cancelled sleep reported that it ran to its end") |
| 143 | } |
| 144 | if c.pending() != 0 { |
| 145 | t.Fatal("a cancelled sleep left its timer behind") |
| 146 | } |
| 147 | if clk.sleep(ctx, time.Second) { |
| 148 | t.Fatal("a sleep started with a cancelled context") |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | func TestClock_BackoffDoublesFromThePolicysMinimumToItsMaximumAndJitterStaysInItsBand(t *testing.T) { |
| 153 | want := []time.Duration{1, 2, 4, 8, 16, 32, 60, 60} |
| 154 | for i, w := range want { |
| 155 | if got := backoff(i + 1); got != w*time.Second { |
| 156 | t.Errorf("backoff(%d) = %s, want %s", i+1, got, w*time.Second) |
| 157 | } |
| 158 | } |
| 159 | if backoff(0) != secs(policyData.Timing.BackoffMinS) || backoff(1000) != secs(policyData.Timing.BackoffMaxS) { |
| 160 | t.Fatal("backoff out of its bounds") |
| 161 | } |
| 162 | for i := 0; i < 500; i++ { |
| 163 | if got := jitter(100*time.Second, 10); got < 90*time.Second || got > 110*time.Second { |
| 164 | t.Fatalf("jitter %s", got) |
| 165 | } |
| 166 | } |
| 167 | if jitter(time.Second, 0) != time.Second || jitter(0, 10) != 0 { |
| 168 | t.Fatal("jitter of nothing") |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | func TestHealth_AnAnswerOfAnyKindIsReachabilityAndDownNeedsAFailureAfterItAndQuiet(t *testing.T) { |
| 173 | var h cpHealth |
| 174 | now := time.Now() |
| 175 | if h.down(now, time.Second) { |
| 176 | t.Fatal("down before anything was seen") |
| 177 | } |
| 178 | h.record(now, nil) |
| 179 | h.record(now.Add(time.Second), &ControlError{Status: 402, Code: codeLicenseInactive}) // a refusal is an answer |
| 180 | h.record(now.Add(2*time.Second), &ControlError{Status: 404, Code: "not_enrolled"}) |
| 181 | if h.down(now.Add(time.Hour), time.Second) { |
| 182 | t.Fatal("a control plane that said no is down") |
| 183 | } |
| 184 | for name, err := range map[string]error{"a 503": &ControlError{Status: 503, Code: "end_to_end_unavailable"}, "a 429": &ControlError{Status: 429, Code: "busy"}, |
| 185 | "a timeout": context.DeadlineExceeded, "a transport error": errors.New("connection refused"), "an unreadable answer": &ControlError{Status: 502, Code: "bad_response"}} { |
| 186 | var g cpHealth |
| 187 | g.record(now, nil) |
| 188 | g.record(now.Add(time.Second), err) |
| 189 | if g.down(now.Add(2*time.Second), time.Minute) { |
| 190 | t.Errorf("%s: down before the quiet period", name) |
| 191 | } |
| 192 | if !g.down(now.Add(2*time.Minute), time.Minute) { |
| 193 | t.Errorf("%s: not down after the quiet period", name) |
| 194 | } |
| 195 | g.record(now.Add(3*time.Minute), nil) |
| 196 | if g.down(now.Add(10*time.Minute), time.Minute) { |
| 197 | t.Errorf("%s: still down after an answer", name) |
| 198 | } |
| 199 | } |
| 200 | } |