master
go 77 lines 1.93 KB
Raw
1 package shutdown
2
3 import (
4 "sync/atomic"
5 "testing"
6 "time"
7 )
8
9 // resetForTest clears the package-level state. Tests in this file mutate
10 // global state, so they cannot run in parallel.
11 func resetForTest(t *testing.T) {
12 t.Helper()
13 startedAt.Store(0)
14 }
15
16 func TestInProgressInitiallyFalse(t *testing.T) {
17 resetForTest(t)
18 if InProgress() {
19 t.Fatal("InProgress() should be false before MarkStarted")
20 }
21 if !StartedAt().IsZero() {
22 t.Fatal("StartedAt() should be zero time before MarkStarted")
23 }
24 }
25
26 func TestMarkStartedFirstCallWins(t *testing.T) {
27 resetForTest(t)
28 if !MarkStarted() {
29 t.Fatal("first MarkStarted() should return true")
30 }
31 if MarkStarted() {
32 t.Fatal("second MarkStarted() should return false")
33 }
34 if !InProgress() {
35 t.Fatal("InProgress() should be true after MarkStarted")
36 }
37 if StartedAt().IsZero() {
38 t.Fatal("StartedAt() should be non-zero after MarkStarted")
39 }
40 }
41
42 func TestMarkStartedPreservesFirstTimestamp(t *testing.T) {
43 resetForTest(t)
44 MarkStarted()
45 first := StartedAt()
46 // Sleep is intentional: it forces time.Now() to advance between the
47 // two MarkStarted calls so a regression that replaces the CAS with a
48 // plain Store would change StartedAt() and fail the assertion below.
49 // Without the gap, both calls could land in the same nanosecond on
50 // coarse-resolution clocks and mask the bug.
51 time.Sleep(2 * time.Millisecond)
52 MarkStarted() // second call must not overwrite
53 if !StartedAt().Equal(first) {
54 t.Fatalf("StartedAt() changed after second MarkStarted: %v != %v", StartedAt(), first)
55 }
56 }
57
58 func TestMarkStartedConcurrent(t *testing.T) {
59 resetForTest(t)
60 const goroutines = 64
61 var winners atomic.Int32
62 done := make(chan struct{})
63 for range goroutines {
64 go func() {
65 if MarkStarted() {
66 winners.Add(1)
67 }
68 done <- struct{}{}
69 }()
70 }
71 for range goroutines {
72 <-done
73 }
74 if got := winners.Load(); got != 1 {
75 t.Fatalf("expected exactly 1 winner across %d goroutines, got %d", goroutines, got)
76 }
77 }