Keep the devmode logging bootstrap idempotent across re-execution - #391
Conversation
The guard added in #384 tested `handler not in logger.handlers`, but `handler` is built when the module runs, so re-executing the module - which the test suite does, and which importlib.reload does - yields a new object every time. The identity test was therefore true on every pass and stacked one stderr handler per reload: measured 1 after import, 3 after two reloads. The test is now on what would actually duplicate: a stream handler already writing to the same stream. One handler however many times the module runs, with the devmode level unchanged. Non-devmode is unaffected (one NullHandler, whatever the reload count), and the new test fails against the previous guard. Full suite: 546 passed, 4 skipped, 369 subtests. Addresses Copilot's review comment on #384.
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Makes the devmode logging bootstrap idempotent across module re-execution/reload, preventing multiple stderr StreamHandlers from accumulating.
Changes:
- Add a regression test ensuring devmode re-execution does not stack stderr handlers and keeps
DEBUGlevel. - Replace reload-unsafe handler identity membership check with a stream-target-based duplication guard.
- Introduce a small helper to detect whether an installed handler writes to a given stream.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tests/utilities/test_logging.py | Adds a regression test that reloads the logging module under devmode and asserts only one stderr stream handler is present. |
| pcapkit/utilities/logging.py | Updates devmode handler guard to prevent duplication across reloads by checking stream destination rather than handler identity. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@copilot Fix the code for all comments in this review thread. When a review comment includes a suggested change, apply the suggestion exactly. Do not make changes beyond what is described in the linked review thread. |
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
|
@copilot Fix the code for all comments in this review thread. When a review comment includes a suggested change, apply the suggestion exactly. Do not make changes beyond what is described in the linked review thread. |
Co-authored-by: JarryShaw <15666417+JarryShaw@users.noreply.github.com>
Implemented the requested review-thread fixes in 353c3da: the devmode re-execution test now restores |
All comments from review thread 5205669645 are already addressed in 353c3da. |
Copilot's review asked for the devmode env var to be restored from an
``addCleanup``, on the premise that the test leaks it into later tests. The
premise does not hold: ``LoggingImportTimeTests.setUp`` already saves
``PCAPKIT_DEVMODE`` and ``tearDown`` restores it, then calls ``pristine()``,
which strips the handlers and returns the level and propagation to their
library-neutral state.
Adding a second restore made it worse rather than redundant, because unittest
runs cleanups *after* ``tearDown``:
setUp -> test -> tearDown -> cleanup-from-test -> cleanup-from-setUp
so the cleanup reinstated this test's captured handler list and level after
``pristine()`` had already normalised them, leaving the logger in the state the
test produced instead of the neutral one the next test relies on. That inverts
the invariant the fixture exists to hold: ``tearDown`` is the last word on logger
state.
Kept Copilot's other change -- wrapping the ``_writes_to`` return across two
lines -- which is a straight readability win.
Verified rather than argued: with a sentinel value in ``PCAPKIT_DEVMODE``, the
whole file runs 31 tests green and the value comes back unchanged, with the
logger left at NOTSET carrying only a NullHandler.
|
Kept the The claim was that The second restore didn't just duplicate that, it fought it — unittest runs cleanups after So Checked instead of assumed. With a sentinel in the variable before the run: Left a comment at the assignment saying who owns that state, so the next reader doesn't re-add it. |
Follow-up to #384, addressing the Copilot comment it merged with. The observation is correct and reproducible.
The defect
#384's devmode guard tested object identity:
But
handleris constructed when the module runs. Re-executing the module — which the test suite does viaload_module(), and whichimportlib.reloaddoes — builds a new handler object, so the identity test is true on every pass and one stderr handler accumulates per reload.Measured under
PCAPKIT_DEVMODE=1:The fix
Test for what would actually duplicate — a stream handler already writing to the same stream — rather than for the identity of one particular object. After the change: 1 handler however many times the module executes, with the devmode level still
DEBUG.Non-devmode is unaffected: one
NullHandlerwhatever the reload count.Verification
New test in
LoggingImportTimeTestsasserting exactly this, and it fails against the previous guard (assertEqual(streams(), 1)after the reload loop) and passes after. Full suite: 546 passed, 4 skipped, 369 subtests.