From 2af037ce02e620b798202e898476b8b318ac6709 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 29 May 2026 11:25:06 +0300 Subject: [PATCH] fix(cli): use quartz mock clock in PausedDuringWaitForReady test (#25811) PausedDuringWaitForReady used the real clock, so the 5s poll in waitForTaskIdle could race with an in-flight stop build. The SQL view (tasks_with_status) returns "unknown" for stop builds with job_status != "succeeded" because the build_status CASE has no branch for (stop, pending) or (stop, running). On macOS CI, where the provisioner is slower, the poll fires during this transient window and hits the TaskStatusUnknown case instead of TaskStatusPaused, failing with "task entered unknown state" rather than the expected "was paused". Convert to the same quartz mock clock pattern that PR #25648 applied to WaitsForWorkingAppState: inject a mock clock via NewWithClock, trap ticker creation and reset, then advance time deterministically so the poll fires after the stop build completes. Closes CODAGT-482 --- cli/task_send_test.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/cli/task_send_test.go b/cli/task_send_test.go index e545da80d1..c90cb335cc 100644 --- a/cli/task_send_test.go +++ b/cli/task_send_test.go @@ -245,8 +245,15 @@ func Test_TaskSend(t *testing.T) { pauseTask(setupCtx, t, setup.userClient, setup.task) resumeTask(setupCtx, t, setup.userClient, setup.task) + // Set up mock clock and traps before starting the command. + // Without a mock clock the poll can race with the stop build + // and see a transient 'unknown' status instead of 'paused'. + mClock := quartz.NewMock(t) + tickTrap := mClock.Trap().NewTicker("task_send", "poll") + resetTrap := mClock.Trap().TickerReset("task_send", "poll") + // When: We attempt to send input to the initializing task. - inv, root := clitest.New(t, "task", "send", setup.task.Name, "some task input") + inv, root := clitest.NewWithClock(t, mClock, "task", "send", setup.task.Name, "some task input") clitest.SetupConfig(t, setup.userClient, root) ctx := testutil.Context(t, testutil.WaitLong) @@ -259,11 +266,29 @@ func Test_TaskSend(t *testing.T) { // of waitForTaskIdle. pty.ExpectMatchContext(ctx, "Waiting for task to become idle") + // Wait for ticker creation and release it. + tickCall := tickTrap.MustWait(ctx) + tickCall.MustRelease(ctx) + tickTrap.Close() + + // Fire the immediate first poll (time.Nanosecond initial interval). + // This poll sees 'initializing' because no agent is connected. + mClock.Advance(time.Nanosecond).MustWait(ctx) + + // Wait for Reset (confirms first poll completed). + resetCall := resetTrap.MustWait(ctx) + resetCall.MustRelease(ctx) + resetTrap.Close() + // Pause the task while waitForTaskIdle is polling. Since // no agent is connected, the task stays initializing until // we pause it, at which point the status becomes paused. pauseTask(ctx, t, setup.userClient, setup.task) + // Fire second poll at the regular 5s interval. The stop + // build has completed, so the poll sees 'paused'. + mClock.Advance(5 * time.Second).MustWait(ctx) + // Then: The command should fail because the task was paused. err := w.Wait() require.Error(t, err)