@@ -11,7 +11,6 @@ import (
11
11
"time"
12
12
13
13
"github.com/google/uuid"
14
- "github.com/stretchr/testify/assert"
15
14
"github.com/stretchr/testify/require"
16
15
"go.uber.org/goleak"
17
16
"golang.org/x/exp/slices"
@@ -29,26 +28,48 @@ import (
29
28
"github.com/coder/coder/v2/provisionerd/proto"
30
29
"github.com/coder/coder/v2/provisionersdk"
31
30
"github.com/coder/coder/v2/testutil"
31
+ "github.com/coder/quartz"
32
32
)
33
33
34
34
func TestMain (m * testing.M ) {
35
35
goleak .VerifyTestMain (m )
36
36
}
37
37
38
38
// Ensures no goroutines leak.
39
+ //
40
+ //nolint:paralleltest // It uses LockIDDBPurge.
39
41
func TestPurge (t * testing.T ) {
40
- t .Parallel ()
41
- purger := dbpurge .New (context .Background (), slogtest .Make (t , nil ), dbmem .New ())
42
- err := purger .Close ()
43
- require .NoError (t , err )
42
+ ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
43
+ defer cancel ()
44
+
45
+ clk := quartz .NewMock (t )
46
+
47
+ // We want to make sure dbpurge is actually started so that this test is meaningful.
48
+ trapStop := clk .Trap ().TickerStop ()
49
+
50
+ purger := dbpurge .New (context .Background (), slogtest .Make (t , nil ), dbmem .New (), clk )
51
+
52
+ // Wait for the initial nanosecond tick.
53
+ clk .Advance (time .Nanosecond ).MustWait (ctx )
54
+ // Wait for ticker.Stop call that happens in the goroutine.
55
+ trapStop .MustWait (ctx ).Release ()
56
+ // Stop the trap now to avoid blocking further.
57
+ trapStop .Close ()
58
+
59
+ require .NoError (t , purger .Close ())
44
60
}
45
61
46
62
//nolint:paralleltest // It uses LockIDDBPurge.
47
63
func TestDeleteOldWorkspaceAgentStats (t * testing.T ) {
48
- db , _ := dbtestutil . NewDB ( t )
49
- logger := slogtest . Make ( t , & slogtest. Options { IgnoreErrors : true }). Leveled ( slog . LevelDebug )
64
+ ctx , cancel := context . WithTimeout ( context . Background (), testutil . WaitShort )
65
+ defer cancel ( )
50
66
51
67
now := dbtime .Now ()
68
+ // TODO: must refactor DeleteOldWorkspaceAgentStats to allow passing in cutoff
69
+ // before using quarts.NewMock()
70
+ clk := quartz .NewReal ()
71
+ db , _ := dbtestutil .NewDB (t )
72
+ logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true }).Leveled (slog .LevelDebug )
52
73
53
74
defer func () {
54
75
if t .Failed () {
@@ -78,9 +99,6 @@ func TestDeleteOldWorkspaceAgentStats(t *testing.T) {
78
99
}
79
100
}()
80
101
81
- ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
82
- defer cancel ()
83
-
84
102
// given
85
103
// Note: We use increments of 2 hours to ensure we avoid any DST
86
104
// conflicts, verifying DST behavior is beyond the scope of this
@@ -114,7 +132,7 @@ func TestDeleteOldWorkspaceAgentStats(t *testing.T) {
114
132
})
115
133
116
134
// when
117
- closer := dbpurge .New (ctx , logger , db )
135
+ closer := dbpurge .New (ctx , logger , db , clk )
118
136
defer closer .Close ()
119
137
120
138
// then
@@ -139,7 +157,7 @@ func TestDeleteOldWorkspaceAgentStats(t *testing.T) {
139
157
140
158
// Start a new purger to immediately trigger delete after rollup.
141
159
_ = closer .Close ()
142
- closer = dbpurge .New (ctx , logger , db )
160
+ closer = dbpurge .New (ctx , logger , db , clk )
143
161
defer closer .Close ()
144
162
145
163
// then
@@ -177,50 +195,75 @@ func TestDeleteOldWorkspaceAgentLogs(t *testing.T) {
177
195
t .Run ("AgentHasNotConnectedSinceWeek_LogsExpired" , func (t * testing.T ) {
178
196
ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
179
197
defer cancel ()
198
+ clk := quartz .NewMock (t )
199
+ clk .Set (now ).MustWait (ctx )
180
200
181
- // given
182
- agent1 := mustCreateAgentWithLogs (ctx , t , db , user , org , tmpl , tv , now .Add (- 8 * 24 * time .Hour ), t .Name ()+ "-1" )
201
+ // After dbpurge completes, the ticker is reset. Trap this call.
202
+ trapReset := clk .Trap ().TickerReset ()
203
+ defer trapReset .Close ()
183
204
184
- // when
185
- closer := dbpurge .New (ctx , logger , db )
186
- defer closer .Close ()
205
+ // given: an agent with logs older than threshold
206
+ agent := mustCreateAgentWithLogs (ctx , t , db , user , org , tmpl , tv , now .Add (- 8 * 24 * time .Hour ), t .Name ())
187
207
188
- // then
189
- assert .Eventually (t , func () bool {
190
- agentLogs , err := db .GetWorkspaceAgentLogsAfter (ctx , database.GetWorkspaceAgentLogsAfterParams {
191
- AgentID : agent1 ,
192
- })
193
- if err != nil {
194
- return false
195
- }
196
- assert .NoError (t , err )
197
- assert .NotContains (t , agentLogs , t .Name ())
198
- return ! containsAgentLog (agentLogs , t .Name ())
199
- }, testutil .WaitShort , testutil .IntervalFast )
208
+ // when dbpurge runs
209
+ closer := dbpurge .New (ctx , logger , db , clk )
210
+ defer closer .Close ()
211
+ // Wait for the initial nanosecond tick.
212
+ clk .Advance (time .Nanosecond ).MustWait (ctx )
213
+
214
+ trapReset .MustWait (ctx ).Release () // Wait for ticker.Reset()
215
+ d , w := clk .AdvanceNext ()
216
+ require .Equal (t , 10 * time .Minute , d )
217
+
218
+ closer .Close () // doTick() has now run.
219
+ w .MustWait (ctx )
220
+
221
+ // then the logs should be gone
222
+ agentLogs , err := db .GetWorkspaceAgentLogsAfter (ctx , database.GetWorkspaceAgentLogsAfterParams {
223
+ AgentID : agent ,
224
+ CreatedAfter : 0 ,
225
+ })
226
+ require .NoError (t , err )
227
+ require .Empty (t , agentLogs , "expected agent logs to be empty" )
200
228
})
201
229
202
230
//nolint:paralleltest // It uses LockIDDBPurge.
203
231
t .Run ("AgentConnectedSixDaysAgo_LogsValid" , func (t * testing.T ) {
204
232
ctx , cancel := context .WithTimeout (context .Background (), testutil .WaitShort )
205
233
defer cancel ()
234
+ clk := quartz .NewMock (t )
235
+ clk .Set (now ).MustWait (ctx )
206
236
207
- // given
237
+ // After dbpurge completes, the ticker is reset. Trap this call.
238
+ trapReset := clk .Trap ().TickerReset ()
239
+ defer trapReset .Close ()
240
+
241
+ // given: an agent with logs newer than threshold
208
242
agent := mustCreateAgentWithLogs (ctx , t , db , user , org , tmpl , tv , now .Add (- 6 * 24 * time .Hour ), t .Name ())
209
243
210
- // when
211
- closer := dbpurge .New (ctx , logger , db )
244
+ // when dbpurge runs
245
+ closer := dbpurge .New (ctx , logger , db , clk )
212
246
defer closer .Close ()
213
247
214
- // then
215
- require .Eventually (t , func () bool {
216
- agentLogs , err := db .GetWorkspaceAgentLogsAfter (ctx , database.GetWorkspaceAgentLogsAfterParams {
217
- AgentID : agent ,
218
- })
219
- if err != nil {
220
- return false
221
- }
222
- return containsAgentLog (agentLogs , t .Name ())
223
- }, testutil .WaitShort , testutil .IntervalFast )
248
+ // Wait for the initial nanosecond tick.
249
+ clk .Advance (time .Nanosecond ).MustWait (ctx )
250
+
251
+ trapReset .MustWait (ctx ).Release () // Wait for ticker.Reset()
252
+ d , w := clk .AdvanceNext ()
253
+ require .Equal (t , 10 * time .Minute , d )
254
+
255
+ closer .Close () // doTick() has now run.
256
+ w .MustWait (ctx )
257
+
258
+ // then the logs should still be there
259
+ agentLogs , err := db .GetWorkspaceAgentLogsAfter (ctx , database.GetWorkspaceAgentLogsAfterParams {
260
+ AgentID : agent ,
261
+ })
262
+ require .NoError (t , err )
263
+ require .NotEmpty (t , agentLogs )
264
+ for _ , al := range agentLogs {
265
+ require .Equal (t , t .Name (), al .Output )
266
+ }
224
267
})
225
268
}
226
269
@@ -273,14 +316,11 @@ func mustCreateAgent(t *testing.T, db database.Store, user database.User, org da
273
316
})
274
317
}
275
318
276
- func containsAgentLog (daemons []database.WorkspaceAgentLog , output string ) bool {
277
- return slices .ContainsFunc (daemons , func (d database.WorkspaceAgentLog ) bool {
278
- return d .Output == output
279
- })
280
- }
281
-
282
319
//nolint:paralleltest // It uses LockIDDBPurge.
283
320
func TestDeleteOldProvisionerDaemons (t * testing.T ) {
321
+ // TODO: must refactor DeleteOldProvisionerDaemons to allow passing in cutoff
322
+ // before using quartz.NewMock
323
+ clk := quartz .NewReal ()
284
324
db , _ := dbtestutil .NewDB (t , dbtestutil .WithDumpOnFailure ())
285
325
defaultOrg := dbgen .Organization (t , db , database.Organization {})
286
326
logger := slogtest .Make (t , & slogtest.Options {IgnoreErrors : true })
@@ -347,7 +387,7 @@ func TestDeleteOldProvisionerDaemons(t *testing.T) {
347
387
require .NoError (t , err )
348
388
349
389
// when
350
- closer := dbpurge .New (ctx , logger , db )
390
+ closer := dbpurge .New (ctx , logger , db , clk )
351
391
defer closer .Close ()
352
392
353
393
// then
0 commit comments