@@ -10,9 +10,9 @@ import (
10
10
"net/http/httptest"
11
11
"net/url"
12
12
"testing"
13
+ "testing/synctest"
13
14
"time"
14
15
15
- "github.com/benbjohnson/clock"
16
16
"github.com/stretchr/testify/assert"
17
17
"github.com/stretchr/testify/require"
18
18
"go.uber.org/zap/zaptest"
@@ -22,8 +22,6 @@ import (
22
22
)
23
23
24
24
func TestProbeHTTP (t * testing.T ) {
25
- t .Parallel ()
26
-
27
25
server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
28
26
w .WriteHeader (http .StatusOK )
29
27
}))
@@ -43,7 +41,7 @@ func TestProbeHTTP(t *testing.T) {
43
41
},
44
42
}
45
43
46
- ctx , cancel := context .WithTimeout (t . Context (), 3 * time .Second )
44
+ ctx , cancel := context .WithTimeout (context . Background (), 3 * time .Second )
47
45
t .Cleanup (cancel )
48
46
49
47
notifyCh := make (chan probe.Notification )
@@ -80,73 +78,76 @@ func TestProbeHTTP(t *testing.T) {
80
78
}
81
79
82
80
func TestProbeConsecutiveFailures (t * testing.T ) {
83
- t .Parallel ()
84
-
85
- server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
86
- w .WriteHeader (http .StatusOK )
87
- }))
88
- t .Cleanup (server .Close )
89
-
90
- u , err := url .Parse (server .URL )
91
- require .NoError (t , err )
92
-
93
- mockClock := clock .NewMock ()
94
-
95
- p := probe.Runner {
96
- ID : "consecutive-failures" ,
97
- Spec : network.ProbeSpecSpec {
98
- Interval : 10 * time .Millisecond ,
99
- FailureThreshold : 3 ,
100
- TCP : network.TCPProbeSpec {
101
- Endpoint : u .Host ,
102
- Timeout : time .Second ,
81
+ // Use synctest.Test to run the test in a controlled time bubble.
82
+ // This allows us to test time-dependent behavior without actual delays,
83
+ // making the test both faster and more deterministic.
84
+ synctest .Test (t , func (t * testing.T ) {
85
+ server := httptest .NewServer (http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
86
+ w .WriteHeader (http .StatusOK )
87
+ }))
88
+ defer server .Close ()
89
+
90
+ u , err := url .Parse (server .URL )
91
+ require .NoError (t , err )
92
+
93
+ p := probe.Runner {
94
+ ID : "consecutive-failures" ,
95
+ Spec : network.ProbeSpecSpec {
96
+ Interval : 10 * time .Millisecond ,
97
+ FailureThreshold : 3 ,
98
+ TCP : network.TCPProbeSpec {
99
+ Endpoint : u .Host ,
100
+ Timeout : time .Second ,
101
+ },
103
102
},
104
- },
105
- Clock : mockClock ,
106
- }
103
+ }
107
104
108
- ctx , cancel := context .WithTimeout (t . Context (), 3 * time .Second )
109
- t . Cleanup ( cancel )
105
+ ctx , cancel := context .WithTimeout (context . Background (), 3 * time .Second )
106
+ defer cancel ( )
110
107
111
- notifyCh := make (chan probe.Notification )
108
+ notifyCh := make (chan probe.Notification )
112
109
113
- p .Start (ctx , notifyCh , zaptest .NewLogger (t ))
114
- t .Cleanup (p .Stop )
115
-
116
- // first iteration should succeed
117
- assert .Equal (t , probe.Notification {
118
- ID : "consecutive-failures" ,
119
- Status : network.ProbeStatusSpec {
120
- Success : true ,
121
- },
122
- }, <- notifyCh )
110
+ p .Start (ctx , notifyCh , zaptest .NewLogger (t ))
111
+ defer p .Stop ()
123
112
124
- // stop the test server, probe should fail
125
- server .Close ()
126
-
127
- for range p .Spec .FailureThreshold - 1 {
128
- // probe should fail, but no notification should be sent yet (failure threshold not reached)
129
- mockClock .Add (p .Spec .Interval )
113
+ // first iteration should succeed
114
+ assert .Equal (t , probe.Notification {
115
+ ID : "consecutive-failures" ,
116
+ Status : network.ProbeStatusSpec {
117
+ Success : true ,
118
+ },
119
+ }, <- notifyCh )
130
120
131
- select {
132
- case ev := <- notifyCh :
133
- require .Fail (t , "unexpected notification" , "got: %v" , ev )
134
- case <- time .After (100 * time .Millisecond ):
121
+ // stop the test server, probe should fail
122
+ server .Close ()
123
+
124
+ for range p .Spec .FailureThreshold - 1 {
125
+ // probe should fail, but no notification should be sent yet (failure threshold not reached)
126
+ // synctest.Wait() waits until all goroutines in the bubble are durably blocked,
127
+ // which happens when the ticker in the probe runner is waiting for the next interval
128
+ synctest .Wait ()
129
+
130
+ select {
131
+ case ev := <- notifyCh :
132
+ require .Fail (t , "unexpected notification" , "got: %v" , ev )
133
+ default :
134
+ // Expected: no notification yet
135
+ }
135
136
}
136
- }
137
137
138
- // advance clock to trigger another failure(s)
139
- mockClock . Add ( p . Spec . Interval )
138
+ // wait for next interval to trigger failure notification
139
+ synctest . Wait ( )
140
140
141
- notify := <- notifyCh
142
- assert .Equal (t , "consecutive-failures" , notify .ID )
143
- assert .False (t , notify .Status .Success )
144
- assert .Contains (t , notify .Status .LastError , "connection refused" )
141
+ notify := <- notifyCh
142
+ assert .Equal (t , "consecutive-failures" , notify .ID )
143
+ assert .False (t , notify .Status .Success )
144
+ assert .Contains (t , notify .Status .LastError , "connection refused" )
145
145
146
- // advance clock to trigger another failure(s)
147
- mockClock . Add ( p . Spec . Interval )
146
+ // wait for next interval to trigger another failure notification
147
+ synctest . Wait ( )
148
148
149
- notify = <- notifyCh
150
- assert .Equal (t , "consecutive-failures" , notify .ID )
151
- assert .False (t , notify .Status .Success )
149
+ notify = <- notifyCh
150
+ assert .Equal (t , "consecutive-failures" , notify .ID )
151
+ assert .False (t , notify .Status .Success )
152
+ })
152
153
}
0 commit comments