@@ -23,6 +23,8 @@ import (
23
23
"github.com/coder/coder/v2/coderd/coderdtest/oidctest"
24
24
"github.com/coder/coder/v2/coderd/database"
25
25
"github.com/coder/coder/v2/coderd/database/dbauthz"
26
+ "github.com/coder/coder/v2/coderd/database/dbgen"
27
+ "github.com/coder/coder/v2/coderd/database/dbtestutil"
26
28
"github.com/coder/coder/v2/coderd/database/dbtime"
27
29
"github.com/coder/coder/v2/coderd/externalauth"
28
30
"github.com/coder/coder/v2/coderd/rbac"
@@ -1180,3 +1182,195 @@ func TestPostWorkspaceBuild(t *testing.T) {
1180
1182
require .Len (t , res .Workspaces , 0 )
1181
1183
})
1182
1184
}
1185
+
1186
+ func TestWorkspaceBuildTimings (t * testing.T ) {
1187
+ t .Parallel ()
1188
+
1189
+ // Setup the test environment with a template and version
1190
+ db , pubsub := dbtestutil .NewDB (t )
1191
+ client := coderdtest .New (t , & coderdtest.Options {
1192
+ Database : db ,
1193
+ Pubsub : pubsub ,
1194
+ })
1195
+ owner := coderdtest .CreateFirstUser (t , client )
1196
+ file := dbgen .File (t , db , database.File {
1197
+ CreatedBy : owner .UserID ,
1198
+ })
1199
+ versionJob := dbgen .ProvisionerJob (t , db , pubsub , database.ProvisionerJob {
1200
+ OrganizationID : owner .OrganizationID ,
1201
+ InitiatorID : owner .UserID ,
1202
+ WorkerID : uuid.NullUUID {},
1203
+ FileID : file .ID ,
1204
+ Tags : database.StringMap {
1205
+ "custom" : "true" ,
1206
+ },
1207
+ })
1208
+ version := dbgen .TemplateVersion (t , db , database.TemplateVersion {
1209
+ OrganizationID : owner .OrganizationID ,
1210
+ JobID : versionJob .ID ,
1211
+ CreatedBy : owner .UserID ,
1212
+ })
1213
+ template := dbgen .Template (t , db , database.Template {
1214
+ OrganizationID : owner .OrganizationID ,
1215
+ ActiveVersionID : version .ID ,
1216
+ CreatedBy : owner .UserID ,
1217
+ })
1218
+
1219
+ makeProvisionerTimings := func (build database.WorkspaceBuild , count int ) []database.ProvisionerJobTiming {
1220
+ // Use the database.ProvisionerJobTiming struct to mock timings data instead
1221
+ // of directly creating database.InsertProvisionerJobTimingsParams. This
1222
+ // approach makes the mock data easier to understand, as
1223
+ // database.InsertProvisionerJobTimingsParams requires slices of each field
1224
+ // for batch inserts.
1225
+ timings := make ([]database.ProvisionerJobTiming , count )
1226
+ now := time .Now ()
1227
+ for i := range count {
1228
+ startedAt := now .Add (- time .Hour + time .Duration (i )* time .Minute )
1229
+ endedAt := startedAt .Add (time .Minute )
1230
+ timings [i ] = database.ProvisionerJobTiming {
1231
+ StartedAt : startedAt ,
1232
+ EndedAt : endedAt ,
1233
+ Stage : database .ProvisionerJobTimingStageInit ,
1234
+ Action : string (database .AuditActionCreate ),
1235
+ Source : "source" ,
1236
+ Resource : fmt .Sprintf ("resource[%d]" , i ),
1237
+ }
1238
+ }
1239
+ insertParams := database.InsertProvisionerJobTimingsParams {
1240
+ JobID : build .JobID ,
1241
+ }
1242
+ for _ , timing := range timings {
1243
+ insertParams .StartedAt = append (insertParams .StartedAt , timing .StartedAt )
1244
+ insertParams .EndedAt = append (insertParams .EndedAt , timing .EndedAt )
1245
+ insertParams .Stage = append (insertParams .Stage , timing .Stage )
1246
+ insertParams .Action = append (insertParams .Action , timing .Action )
1247
+ insertParams .Source = append (insertParams .Source , timing .Source )
1248
+ insertParams .Resource = append (insertParams .Resource , timing .Resource )
1249
+ }
1250
+ return dbgen .ProvisionerJobTimings (t , db , insertParams )
1251
+ }
1252
+
1253
+ makeAgentScriptTimings := func (build database.WorkspaceBuild , count int ) []database.WorkspaceAgentScriptTiming {
1254
+ // Create a resource, agent, and script to test the timing of agent scripts
1255
+ resource := dbgen .WorkspaceResource (t , db , database.WorkspaceResource {
1256
+ JobID : build .JobID ,
1257
+ })
1258
+ agent := dbgen .WorkspaceAgent (t , db , database.WorkspaceAgent {
1259
+ ResourceID : resource .ID ,
1260
+ })
1261
+ scripts := dbgen .WorkspaceAgentScripts (t , db , database.InsertWorkspaceAgentScriptsParams {
1262
+ WorkspaceAgentID : agent .ID ,
1263
+ CreatedAt : time .Now (),
1264
+ LogSourceID : []uuid.UUID {
1265
+ uuid .New (),
1266
+ },
1267
+ LogPath : []string {"" },
1268
+ Script : []string {"" },
1269
+ Cron : []string {"" },
1270
+ StartBlocksLogin : []bool {false },
1271
+ RunOnStart : []bool {false },
1272
+ RunOnStop : []bool {false },
1273
+ TimeoutSeconds : []int32 {0 },
1274
+ DisplayName : []string {"" },
1275
+ ID : []uuid.UUID {
1276
+ uuid .New (),
1277
+ },
1278
+ })
1279
+
1280
+ newTimings := make ([]database.InsertWorkspaceAgentScriptTimingsParams , count )
1281
+ now := time .Now ()
1282
+ for i := range count {
1283
+ startedAt := now .Add (- time .Hour + time .Duration (i )* time .Minute )
1284
+ endedAt := startedAt .Add (time .Minute )
1285
+ newTimings [i ] = database.InsertWorkspaceAgentScriptTimingsParams {
1286
+ StartedAt : startedAt ,
1287
+ EndedAt : endedAt ,
1288
+ Stage : database .WorkspaceAgentScriptTimingStageStart ,
1289
+ ScriptID : scripts [0 ].ID ,
1290
+ ExitCode : 0 ,
1291
+ Status : database .WorkspaceAgentScriptTimingStatusOk ,
1292
+ }
1293
+ }
1294
+
1295
+ timings := make ([]database.WorkspaceAgentScriptTiming , 0 )
1296
+ for _ , newTiming := range newTimings {
1297
+ timing := dbgen .WorkspaceAgentScriptTiming (t , db , newTiming )
1298
+ timings = append (timings , timing )
1299
+ }
1300
+
1301
+ return timings
1302
+ }
1303
+
1304
+ // Given
1305
+ testCases := []struct {
1306
+ name string
1307
+ provisionerTimings int
1308
+ actionScriptTimings int
1309
+ }{
1310
+ {name : "with empty provisioner timings" , provisionerTimings : 0 },
1311
+ {name : "with provisioner timings" , provisionerTimings : 5 },
1312
+ {name : "with empty agent script timings" , actionScriptTimings : 0 },
1313
+ {name : "with agent script timings" , actionScriptTimings : 5 },
1314
+ }
1315
+
1316
+ for _ , tc := range testCases {
1317
+ tc := tc
1318
+ t .Run (tc .name , func (t * testing.T ) {
1319
+ t .Parallel ()
1320
+
1321
+ // Create a build to attach provisioner timings
1322
+ ws := dbgen .Workspace (t , db , database.Workspace {
1323
+ OwnerID : owner .UserID ,
1324
+ OrganizationID : owner .OrganizationID ,
1325
+ TemplateID : template .ID ,
1326
+ // Generate unique name for the workspace
1327
+ Name : "test-workspace-" + uuid .New ().String (),
1328
+ })
1329
+ jobID := uuid .New ()
1330
+ job := dbgen .ProvisionerJob (t , db , pubsub , database.ProvisionerJob {
1331
+ ID : jobID ,
1332
+ OrganizationID : owner .OrganizationID ,
1333
+ Type : database .ProvisionerJobTypeWorkspaceBuild ,
1334
+ Tags : database.StringMap {jobID .String (): "true" },
1335
+ })
1336
+ build := dbgen .WorkspaceBuild (t , db , database.WorkspaceBuild {
1337
+ WorkspaceID : ws .ID ,
1338
+ TemplateVersionID : version .ID ,
1339
+ BuildNumber : 1 ,
1340
+ Transition : database .WorkspaceTransitionStart ,
1341
+ InitiatorID : owner .UserID ,
1342
+ JobID : job .ID ,
1343
+ })
1344
+
1345
+ // Generate timings based on test config
1346
+ genProvisionerTimings := makeProvisionerTimings (build , tc .provisionerTimings )
1347
+ genAgentScriptTimings := makeAgentScriptTimings (build , tc .provisionerTimings )
1348
+
1349
+ res , err := client .WorkspaceBuildTimings (context .Background (), build .ID )
1350
+ require .NoError (t , err )
1351
+ require .Len (t , res .ProvisionerTimings , tc .provisionerTimings )
1352
+
1353
+ for i := range res .ProvisionerTimings {
1354
+ timingRes := res .ProvisionerTimings [i ]
1355
+ genTiming := genProvisionerTimings [i ]
1356
+ require .Equal (t , genTiming .Resource , timingRes .Resource )
1357
+ require .Equal (t , genTiming .Action , timingRes .Action )
1358
+ require .Equal (t , string (genTiming .Stage ), timingRes .Stage )
1359
+ require .Equal (t , genTiming .JobID .String (), timingRes .JobID .String ())
1360
+ require .Equal (t , genTiming .Source , timingRes .Source )
1361
+ require .Equal (t , genTiming .StartedAt .UnixMilli (), timingRes .StartedAt .UnixMilli ())
1362
+ require .Equal (t , genTiming .EndedAt .UnixMilli (), timingRes .EndedAt .UnixMilli ())
1363
+ }
1364
+
1365
+ for i := range res .AgentScriptTimings {
1366
+ timingRes := res .AgentScriptTimings [i ]
1367
+ genTiming := genAgentScriptTimings [i ]
1368
+ require .Equal (t , genTiming .ExitCode , timingRes .ExitCode )
1369
+ require .Equal (t , string (genTiming .Status ), timingRes .Status )
1370
+ require .Equal (t , string (genTiming .Stage ), timingRes .Stage )
1371
+ require .Equal (t , genTiming .StartedAt .UnixMilli (), timingRes .StartedAt .UnixMilli ())
1372
+ require .Equal (t , genTiming .EndedAt .UnixMilli (), timingRes .EndedAt .UnixMilli ())
1373
+ }
1374
+ })
1375
+ }
1376
+ }
0 commit comments