fleetd #525: restore the logger level, not just the appender, in SessionManagerTest #527
Reference in New Issue
Block a user
Delete Branch "worker/525-logger-level-leak-1b4eb0-6"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
fleetd #525: the shared SessionManager logger's level leaked past setLevel(WARN)
The defect
SessionManagerTest.onTurnFailedIsLoggedAtWarnWithThePriorState'sfinallyblock onlycalled
sessionLog.detachAppender(appender). It never restored the level thatsessionLog.setLevel(Level.WARN)had pinned.ch.qos.logback.classic.Loggerinstancesare cached per class and shared across the whole JVM, so every test that ran after this
one — in this class or any other, in the same surefire fork (this project reuses forks
by default) — saw the
SessionManagerlogger pinned at WARN. PR #522 already hit thislive: its new
log.infoassertion passed alone and silently saw nothing when the fullclass ran, and its local fix (
setLevel(null)infinally, per test) is onmainuntouched by this change.
Fix — three parts, as scoped
1. Restore the level wherever it's set, in this file. Swept the whole file: found and
fixed 5 leaking
setLevelcall sites (the reported one plus 4 more, one of them onMemberRegistry.classrather thanSessionManager.class):onTurnFailedIsLoggedAtWarnWithThePriorState(WARN,SessionManager)aSecondArchitectOnAnAlreadyBoundProfileIsHeldAsDevNotArchitectAndWarnsLoudly(WARN,MemberRegistry)releasePreservesWorktreeWhenDirtyCheckThrows(WARN,SessionManager)reapIdleCountsAllThreeSessionsWhenOnlyItsWorktreeRemovalFails(WARN,SessionManager)reapIdleSurvivesOneSessionWhoseLauncherStopFails(WARN,SessionManager)2. Made the shape hard to get wrong. Added
CapturedLog, a privateAutoCloseablethat captures a logger's level and attaches a
ListAppendertogether, then restoresboth on
close(). Converted all 7addAppender/setLevelcall sites in this file toit (the 5 leaks above, plus the one call with no
setLevelat all, plus the 2 sites#522 already fixed by hand with per-test
setLevel(null)). The 2 already-fixed siteskeep their explicit per-test pinning as belt-and-braces, per the ticket — a later change
to the sweep must not be able to make those two vacuous again.
3. Swept wider — report only, nothing else changed. Same shape (mutates a shared
logger's level via
setLevel, doesn't restore it infinally/@AfterEach) foundelsewhere in
fleetd/src/test:dev/ltms/fleet/FleetdAwaitHerdrTest.java:200-211—attach()/detach()helpers pinFleetd.classto DEBUG;detach()only detaches.dev/ltms/fleet/FleetdReplyInboxSelectionTest.java:53-65— same shape, same sharedFleetd.classlogger, pinned to INFO instead — these two files leak into each other.dev/ltms/fleet/auth/AuditLogTest.java:30-44—@BeforeEach/@AfterEachpin theshared
"audit"logger to INFO;@AfterEachonly detaches.dev/ltms/fleet/inject/CompletionResolverTest.java:473-499(
failIsLoggedAtWarnWithTheReason) — pinsCompletionResolver.classto WARN;finallyonly detaches.dev/ltms/fleet/inject/InjectorTest.java— 4 tests, same shape, pinInjector.classto WARN and never restore: lines 504/522, 543/561, 594/613, 657/673.
dev/ltms/fleet/lead/LeadRolloverTest.java:865-876—attachLog()/detachLog()helpers pin
LeadRollover.classto DEBUG;detachLog()only detaches.dev/ltms/fleet/msg/AmqpConnectionFailureLoggerTest.java:106-117—attach(Class<?> owner)/detach(...)pin whatever logger class is passed in toDEBUG;
detach()only detaches.dev/ltms/fleet/session/GitWorktreesTest.java:497-511—@BeforeEachre-pinsGitWorktrees.classto WARN before every test (self-heals within the class), butseveral tests (lines 815, 830, 1438, 1455, 1475, 1498, 1686) re-pin it to INFO
mid-test and neither those tests nor
@AfterEachrestore it, so the level this classleaves behind for the next class in the same fork depends on which test ran last.
dev/ltms/fleet/session/WorktreeSessionManagerTest.java:257-291(
releasePreservesDirtyWorktreeAndLogsWarn) — same shape, on the same sharedSessionManager.classlogger this PR fixes inSessionManagerTest; pins WARN,finallyonly detaches. Out of this ticket's scope (onlySessionManagerTest.javalogger-level leaks were to be fixed) but worth flagging since it touches the same
logger.
None of these were changed — report only, per the ticket.
Acceptance
mvn -f fleetd/pom.xml clean installexits 0.Tests run: 1699, Failures: 0, Errors: 0, Skipped: 0—BUILD SUCCESS.(1698 on
mainat37dcefa+ 1 new proving test.)Proving test. Added
sharedSessionManagerLoggerLevelIsRestoredAfterOnTurnFailedPinsWarnat@Order(2),running right after the fixed
onTurnFailedIsLoggedAtWarnWithThePriorStateat@Order(1)(@TestMethodOrder(MethodOrderer.OrderAnnotation.class)on the class;every other, unannotated test keeps running after both, in whatever order it already
ran in). A class-level
@BeforeAllforces theSessionManagerlogger to adistinctive baseline (
Level.DEBUG) before any test in the class can touch it, so theproving test can tell "restored" apart from "happens to already be WARN because
WorktreeSessionManagerTest's unfixed leak (see above) left it there" — a real risk,since surefire reuses JVM forks by default.
@AfterAllputs the pre-class level back.Shown failing before the fix: reintroduced the leak (removed
logger.setLevel(originalLevel);fromCapturedLog.close()) and re-ranmvn -Dtest=SessionManagerTest test:exit code 1.
Mutation proof.
logger.setLevel(originalLevel);fromCapturedLog.close().grep -c 'MUTATION fleetd #525'→ 1 present;grep -c 'logger.setLevel(originalLevel);'→ 0 absent (both confirm the mutant, not a no-op).shasum -a 256on the file matched the pre-mutation hashbyte-for-byte (
0228f78424baa4a1fdd6d372ecc4eaa3380e5dde5103c8cb6a3de4e7a42ee345).grep -c 'MUTATION fleetd #525'→ 0 (mutant gone);grep -c 'logger.setLevel(originalLevel);'→ 1 (original back);grep -nre-read of the line confirmedlogger.setLevel(originalLevel);at itsoriginal spot.
Tests run: 72, Failures: 0, Errors: 0, Skipped: 0, exit 0.#522's per-test pinning kept.
drainAllLogsACompletionLineWithTheRealCountsOnACleanDrainand
drainAllLogsANonZeroAbandonedCountForASessionStillBusyAtTheDeadlinestill pinLevel.INFOexplicitly per test (now viaCapturedLog.at(SessionManager.class, Level.INFO)instead of the hand-rolledsetLevel/setLevel(null)pair), asbelt-and-braces on top of the class-wide fix.
Files changed
fleetd/src/test/java/dev/ltms/fleet/session/SessionManagerTest.java(only file touched)Correction to my own merge message: its first paragraph garbled the test file's hash into
0228f78424boa..., which is not a hash. I then made it worse by retyping from memory in the first version of this comment instead of re-running the command.Measured, at commit
d8985719ebfd645afb6052279616d9ae3d368d65:That matches the value the PR body records in its section 3.
Nothing about the acceptance result changes. The hash was only used to confirm the file was byte-identical before and after the mutation experiments, and that check passed: the mutation applied, it killed the new proving test with
expected: <DEBUG> but was: <WARN>, and the restore returned the file to these exact bytes before the green control run.