@@ -26,6 +26,7 @@ import (
2626"golang.org/x/xerrors"
2727
2828"cdr.dev/slog"
29+ "cdr.dev/slog/sloggers/sloghuman"
2930"cdr.dev/slog/sloggers/slogtest"
3031"github.com/coder/coder/v2/agent/agentcontainers"
3132"github.com/coder/coder/v2/agent/agentcontainers/acmock"
@@ -342,6 +343,103 @@ func (f *fakeExecer) getLastCommand() *exec.Cmd {
342343func TestAPI (t * testing.T ) {
343344t .Parallel ()
344345
346+ t .Run ("NoUpdaterLoopLogspam" ,func (t * testing.T ) {
347+ t .Parallel ()
348+
349+ var (
350+ ctx = testutil .Context (t ,testutil .WaitShort )
351+ logbuf strings.Builder
352+ logger = slogtest .Make (t ,& slogtest.Options {IgnoreErrors :true }).Leveled (slog .LevelDebug ).AppendSinks (sloghuman .Sink (& logbuf ))
353+ mClock = quartz .NewMock (t )
354+ tickerTrap = mClock .Trap ().TickerFunc ("updaterLoop" )
355+ firstErr = xerrors .New ("first error" )
356+ secondErr = xerrors .New ("second error" )
357+ fakeCLI = & fakeContainerCLI {
358+ listErr :firstErr ,
359+ }
360+ )
361+
362+ api := agentcontainers .NewAPI (logger ,
363+ agentcontainers .WithClock (mClock ),
364+ agentcontainers .WithContainerCLI (fakeCLI ),
365+ )
366+ api .Start ()
367+ defer api .Close ()
368+
369+ // Make sure the ticker function has been registered
370+ // before advancing the clock.
371+ tickerTrap .MustWait (ctx ).MustRelease (ctx )
372+ tickerTrap .Close ()
373+
374+ logbuf .Reset ()
375+
376+ // First tick should handle the error.
377+ _ ,aw := mClock .AdvanceNext ()
378+ aw .MustWait (ctx )
379+
380+ // Verify first error is logged.
381+ got := logbuf .String ()
382+ t .Logf ("got log: %q" ,got )
383+ require .Contains (t ,got ,"updater loop ticker failed" ,"first error should be logged" )
384+ require .Contains (t ,got ,"first error" ,"should contain first error message" )
385+ logbuf .Reset ()
386+
387+ // Second tick should handle the same error without logging it again.
388+ _ ,aw = mClock .AdvanceNext ()
389+ aw .MustWait (ctx )
390+
391+ // Verify same error is not logged again.
392+ got = logbuf .String ()
393+ t .Logf ("got log: %q" ,got )
394+ require .Empty (t ,got ,"same error should not be logged again" )
395+
396+ // Change to a different error.
397+ fakeCLI .listErr = secondErr
398+
399+ // Third tick should handle the different error and log it.
400+ _ ,aw = mClock .AdvanceNext ()
401+ aw .MustWait (ctx )
402+
403+ // Verify different error is logged.
404+ got = logbuf .String ()
405+ t .Logf ("got log: %q" ,got )
406+ require .Contains (t ,got ,"updater loop ticker failed" ,"different error should be logged" )
407+ require .Contains (t ,got ,"second error" ,"should contain second error message" )
408+ logbuf .Reset ()
409+
410+ // Clear the error to simulate success.
411+ fakeCLI .listErr = nil
412+
413+ // Fourth tick should succeed.
414+ _ ,aw = mClock .AdvanceNext ()
415+ aw .MustWait (ctx )
416+
417+ // Fifth tick should continue to succeed.
418+ _ ,aw = mClock .AdvanceNext ()
419+ aw .MustWait (ctx )
420+
421+ // Verify successful operations are logged properly.
422+ got = logbuf .String ()
423+ t .Logf ("got log: %q" ,got )
424+ gotSuccessCount := strings .Count (got ,"containers updated successfully" )
425+ require .GreaterOrEqual (t ,gotSuccessCount ,2 ,"should have successful update got" )
426+ require .NotContains (t ,got ,"updater loop ticker failed" ,"no errors should be logged during success" )
427+ logbuf .Reset ()
428+
429+ // Reintroduce the original error.
430+ fakeCLI .listErr = firstErr
431+
432+ // Sixth tick should handle the error after success and log it.
433+ _ ,aw = mClock .AdvanceNext ()
434+ aw .MustWait (ctx )
435+
436+ // Verify error after success is logged.
437+ got = logbuf .String ()
438+ t .Logf ("got log: %q" ,got )
439+ require .Contains (t ,got ,"updater loop ticker failed" ,"error after success should be logged" )
440+ require .Contains (t ,got ,"first error" ,"should contain first error message" )
441+ })
442+
345443// List tests the API.getContainers method using a mock
346444// implementation. It specifically tests caching behavior.
347445t .Run ("List" ,func (t * testing.T ) {