diff --git a/fleetd/src/test/java/dev/ltms/fleet/inject/StatusPollerWatchdogTest.java b/fleetd/src/test/java/dev/ltms/fleet/inject/StatusPollerWatchdogTest.java index e575305..fdfb7bb 100644 --- a/fleetd/src/test/java/dev/ltms/fleet/inject/StatusPollerWatchdogTest.java +++ b/fleetd/src/test/java/dev/ltms/fleet/inject/StatusPollerWatchdogTest.java @@ -84,6 +84,24 @@ class StatusPollerWatchdogTest { "stop() must report STOPPED, never STALLED, however stale the last round looks"); } + @Test + void aRestartedLoopReportsRunningAgainNotStoppedForever() throws Exception { + // fleetd #544 review (issue comment #16944): stoppedByCaller is sticky, and reset() — + // called only from start() — is the sole thing that clears it. start() is documented + // idempotent and loop()'s own error line says "it can be restarted", so stop() followed + // by start() is an anticipated path. Without the reset() call in start(), health() would + // report STOPPED forever after a restart even though the loop is genuinely running again. + AtomicLong clock = new AtomicLong(0); + StatusPoller poller = new StatusPoller(new AgentControl(new IdleHerdr()), new Injector(new AgentControl(new IdleHerdr())), + new StatusRefiner(new AgentControl(new IdleHerdr())), INTERVAL_MILLIS, clock::get); + poller.start(); + poller.stop(); + poller.start(); + + assertEquals(LoopWatchdog.State.RUNNING, poller.health(), + "an intentional stop must not outlive the restart that follows it"); + } + @Test void aHealthyLoopReportsRunning() throws Exception { // Real elapsed time on purpose, unlike the other tests here: a clock frozen at 0 would read diff --git a/fleetd/src/test/java/dev/ltms/fleet/session/SessionReaperWatchdogTest.java b/fleetd/src/test/java/dev/ltms/fleet/session/SessionReaperWatchdogTest.java index 9993db1..afaa622 100644 --- a/fleetd/src/test/java/dev/ltms/fleet/session/SessionReaperWatchdogTest.java +++ b/fleetd/src/test/java/dev/ltms/fleet/session/SessionReaperWatchdogTest.java @@ -88,6 +88,23 @@ class SessionReaperWatchdogTest { "stop() must report STOPPED, never STALLED, however stale the last round looks"); } + @Test + void aRestartedLoopReportsRunningAgainNotStoppedForever() throws Exception { + // fleetd #544 review (issue comment #16944): stoppedByCaller is sticky, and reset() — + // called only from start() — is the sole thing that clears it. start() is documented + // idempotent and loop()'s own error line says "it can be restarted", so stop() followed + // by start() is an anticipated path. Without the reset() call in start(), health() would + // report STOPPED forever after a restart even though the loop is genuinely running again. + AtomicLong watchdogClock = new AtomicLong(0); + SessionReaper reaper = new SessionReaper(sessionManager(System::nanoTime), 60, INTERVAL_MILLIS, watchdogClock::get); + reaper.start(); + reaper.stop(); + reaper.start(); + + assertEquals(LoopWatchdog.State.RUNNING, reaper.health(), + "an intentional stop must not outlive the restart that follows it"); + } + @Test void aHealthyLoopReportsRunning() throws Exception { // Real elapsed time on purpose, unlike the other tests here: a clock frozen at 0 would read