9
9
"github.com/coder/coder/v2/agent/agenttest"
10
10
"github.com/coder/coder/v2/coderd/coderdtest"
11
11
"github.com/coder/coder/v2/codersdk/toolsdk"
12
+ "github.com/coder/coder/v2/testutil"
12
13
)
13
14
14
15
func TestWorkspaceBash (t * testing.T ) {
@@ -174,8 +175,6 @@ func TestWorkspaceBashTimeout(t *testing.T) {
174
175
175
176
// Test that the TimeoutMs field can be set and read correctly
176
177
args := toolsdk.WorkspaceBashArgs {
177
- Workspace : "test-workspace" ,
178
- Command : "echo test" ,
179
178
TimeoutMs : 0 , // Should default to 60000 in handler
180
179
}
181
180
@@ -192,8 +191,6 @@ func TestWorkspaceBashTimeout(t *testing.T) {
192
191
193
192
// Test that negative values can be set and will be handled by the default logic
194
193
args := toolsdk.WorkspaceBashArgs {
195
- Workspace : "test-workspace" ,
196
- Command : "echo test" ,
197
194
TimeoutMs : - 100 ,
198
195
}
199
196
@@ -279,7 +276,7 @@ func TestWorkspaceBashTimeoutIntegration(t *testing.T) {
279
276
TimeoutMs : 2000 , // 2 seconds timeout - should timeout after first echo
280
277
}
281
278
282
- result , err := toolsdk . WorkspaceBash . Handler ( t . Context () , deps , args )
279
+ result , err := testTool ( t , toolsdk . WorkspaceBash , deps , args )
283
280
284
281
// Should not error (timeout is handled gracefully)
285
282
require .NoError (t , err )
@@ -313,15 +310,15 @@ func TestWorkspaceBashTimeoutIntegration(t *testing.T) {
313
310
314
311
deps , err := toolsdk .NewDeps (client )
315
312
require .NoError (t , err )
316
- ctx := context .Background ()
317
313
318
314
args := toolsdk.WorkspaceBashArgs {
319
315
Workspace : workspace .Name ,
320
316
Command : `echo "normal command"` , // Quick command that should complete normally
321
317
TimeoutMs : 5000 , // 5 second timeout - plenty of time
322
318
}
323
319
324
- result , err := toolsdk .WorkspaceBash .Handler (ctx , deps , args )
320
+ // Use testTool to register the tool as tested and satisfy coverage validation
321
+ result , err := testTool (t , toolsdk .WorkspaceBash , deps , args )
325
322
326
323
// Should not error
327
324
require .NoError (t , err )
@@ -338,3 +335,142 @@ func TestWorkspaceBashTimeoutIntegration(t *testing.T) {
338
335
require .NotContains (t , result .Output , "Command canceled due to timeout" )
339
336
})
340
337
}
338
+
339
+ func TestWorkspaceBashBackgroundIntegration (t * testing.T ) {
340
+ t .Parallel ()
341
+
342
+ t .Run ("BackgroundCommandCapturesOutput" , func (t * testing.T ) {
343
+ t .Parallel ()
344
+
345
+ client , workspace , agentToken := setupWorkspaceForAgent (t )
346
+
347
+ // Start the agent and wait for it to be fully ready
348
+ _ = agenttest .New (t , client .URL , agentToken )
349
+
350
+ // Wait for workspace agents to be ready
351
+ coderdtest .NewWorkspaceAgentWaiter (t , client , workspace .ID ).Wait ()
352
+
353
+ deps , err := toolsdk .NewDeps (client )
354
+ require .NoError (t , err )
355
+
356
+ args := toolsdk.WorkspaceBashArgs {
357
+ Workspace : workspace .Name ,
358
+ Command : `echo "started" && sleep 60 && echo "completed"` , // Command that would take 60+ seconds
359
+ Background : true , // Run in background
360
+ TimeoutMs : 2000 , // 2 second timeout
361
+ }
362
+
363
+ result , err := testTool (t , toolsdk .WorkspaceBash , deps , args )
364
+
365
+ // Should not error
366
+ require .NoError (t , err )
367
+
368
+ t .Logf ("Background result: exitCode=%d, output=%q" , result .ExitCode , result .Output )
369
+
370
+ // Should have exit code 124 (timeout) since command times out
371
+ require .Equal (t , 124 , result .ExitCode )
372
+
373
+ // Should capture output up to timeout point
374
+ require .Contains (t , result .Output , "started" , "Should contain output captured before timeout" )
375
+
376
+ // Should NOT contain the second echo (it never executed due to timeout)
377
+ require .NotContains (t , result .Output , "completed" , "Should not contain output after timeout" )
378
+
379
+ // Should contain background continuation message
380
+ require .Contains (t , result .Output , "Command continues running in background" )
381
+ })
382
+
383
+ t .Run ("BackgroundVsNormalExecution" , func (t * testing.T ) {
384
+ t .Parallel ()
385
+
386
+ client , workspace , agentToken := setupWorkspaceForAgent (t )
387
+
388
+ // Start the agent and wait for it to be fully ready
389
+ _ = agenttest .New (t , client .URL , agentToken )
390
+
391
+ // Wait for workspace agents to be ready
392
+ coderdtest .NewWorkspaceAgentWaiter (t , client , workspace .ID ).Wait ()
393
+
394
+ deps , err := toolsdk .NewDeps (client )
395
+ require .NoError (t , err )
396
+
397
+ // First run the same command in normal mode
398
+ normalArgs := toolsdk.WorkspaceBashArgs {
399
+ Workspace : workspace .Name ,
400
+ Command : `echo "hello world"` ,
401
+ Background : false ,
402
+ }
403
+
404
+ normalResult , err := toolsdk .WorkspaceBash .Handler (t .Context (), deps , normalArgs )
405
+ require .NoError (t , err )
406
+
407
+ // Normal mode should return the actual output
408
+ require .Equal (t , 0 , normalResult .ExitCode )
409
+ require .Equal (t , "hello world" , normalResult .Output )
410
+
411
+ // Now run the same command in background mode
412
+ backgroundArgs := toolsdk.WorkspaceBashArgs {
413
+ Workspace : workspace .Name ,
414
+ Command : `echo "hello world"` ,
415
+ Background : true ,
416
+ }
417
+
418
+ backgroundResult , err := testTool (t , toolsdk .WorkspaceBash , deps , backgroundArgs )
419
+ require .NoError (t , err )
420
+
421
+ t .Logf ("Normal result: %q" , normalResult .Output )
422
+ t .Logf ("Background result: %q" , backgroundResult .Output )
423
+
424
+ // Background mode should also return the actual output since command completes quickly
425
+ require .Equal (t , 0 , backgroundResult .ExitCode )
426
+ require .Equal (t , "hello world" , backgroundResult .Output )
427
+ })
428
+
429
+ t .Run ("BackgroundCommandContinuesAfterTimeout" , func (t * testing.T ) {
430
+ t .Parallel ()
431
+
432
+ client , workspace , agentToken := setupWorkspaceForAgent (t )
433
+
434
+ // Start the agent and wait for it to be fully ready
435
+ _ = agenttest .New (t , client .URL , agentToken )
436
+
437
+ // Wait for workspace agents to be ready
438
+ coderdtest .NewWorkspaceAgentWaiter (t , client , workspace .ID ).Wait ()
439
+
440
+ deps , err := toolsdk .NewDeps (client )
441
+ require .NoError (t , err )
442
+
443
+ args := toolsdk.WorkspaceBashArgs {
444
+ Workspace : workspace .Name ,
445
+ Command : `echo "started" && sleep 4 && echo "done" > /tmp/bg-test-done` , // Command that will timeout but continue
446
+ TimeoutMs : 2000 , // 2000ms timeout (shorter than command duration)
447
+ Background : true , // Run in background
448
+ }
449
+
450
+ result , err := testTool (t , toolsdk .WorkspaceBash , deps , args )
451
+
452
+ // Should not error but should timeout
453
+ require .NoError (t , err )
454
+
455
+ t .Logf ("Background with timeout result: exitCode=%d, output=%q" , result .ExitCode , result .Output )
456
+
457
+ // Should have timeout exit code
458
+ require .Equal (t , 124 , result .ExitCode )
459
+
460
+ // Should capture output before timeout
461
+ require .Contains (t , result .Output , "started" , "Should contain output captured before timeout" )
462
+
463
+ // Should contain background continuation message
464
+ require .Contains (t , result .Output , "Command continues running in background" )
465
+
466
+ // Wait for the background command to complete (even though SSH session timed out)
467
+ require .Eventually (t , func () bool {
468
+ checkArgs := toolsdk.WorkspaceBashArgs {
469
+ Workspace : workspace .Name ,
470
+ Command : `cat /tmp/bg-test-done 2>/dev/null || echo "not found"` ,
471
+ }
472
+ checkResult , err := toolsdk .WorkspaceBash .Handler (t .Context (), deps , checkArgs )
473
+ return err == nil && checkResult .Output == "done"
474
+ }, testutil .WaitMedium , testutil .IntervalMedium , "Background command should continue running and complete after timeout" )
475
+ })
476
+ }
0 commit comments