fix(utilities): stop truncating every traceback for a loud BaseError - #983
Conversation
|
A premise of my own brief was wrong, and it is worth correcting before this is reviewed. When I recommended against the excepthook I said an exception hook installed merely by importing the ROOT = os.path.dirname(os.path.realpath(__file__))
tbtrim.set_trim_rule(lambda filename: ROOT in os.path.realpath(filename),
exception=BaseError, strict=False)
A second finding, independent of this change. That is #981's shape again, in a second file, and it broke the new test under a full-directory run Also now stale, and not this change's to fix: Labelled |
cd1a271 to
3f1d505
Compare
|
NEEDS CHANGES at 1. The hook does not reproduce This is the common shape, not a corner: 42 raise-inside-except sites across 22 files under 2. A loud error on a worker thread regresses from one line to twelve, and my earlier "strictly better I repeated the PR body's framing when I labelled this PR. It is accurate for Also found, non-blocking. One new test is vacuous — One fact worth recording. Verified sound: the simple uncaught case is byte-identical to before; devmode unchanged; the quiet path |
|
I think we can refer to |
|
NEEDS CHANGES at 1. Both blocking findings survive this head, measured here. Chained exceptions still print the 2. The description now contradicts its own code. The body says "Installing twice, or from a 3. A test proves something the real world cannot produce. The docstring names "a genuine 4. The vacuous test is still vacuous. It ran the test's verbatim body against the pre-fix tree: 5. The stale prose was not taken — the diffstat is still 5 files, none under Test hygiene passes, and the check was the right one: 64 tests across the touched modules run twice It also corrected round 1's reload nit: in a genuine reload tbtrim's trimming is not lost, because UNVERIFIED by it: the full directory double-run (it substituted 64 tests after the first attempt |
|
Looked at it, and tbtrim cannot produce the one-line output — but what it does instead may be what you Why dropping the exemption does not give terse output. Two things worth knowing before deciding. That opens a third option I had not put to you, and I think it is worth a moment's thought rather than The cost is that it is not the behaviour you asked to preserve. One line becomes about seven, and No change made. If you want the terse form kept, the current approach stands and the worker fixes |
…719) A loud BaseError set sys.tracebacklimit = 0 outside development mode and never restored it. The attribute is process-global, so one pcapkit error silently truncated every subsequent traceback for the rest of the process, including exceptions pcapkit had nothing to do with. Measured: an unrelated IndexError raised after a loud IntError came back with traceback.extract_tb() returning 0 frames. Removed the assignment outright. The same terse output for a loud error now comes from exception hooks installed lazily on sys.excepthook and threading.excepthook, the first time a loud error outside development mode actually needs them -- never at import time. A hook prints limit=0 against the exception's real traceback, not tb=None, which only discards the top exception's own frames and leaves a chained cause's traceback printed in full -- cross-review caught this; tests now cover an explicit `raise ... from err` and an implicit context chain. Everything else is delegated, unchanged, to whatever hook was previously installed. Install is idempotent by identity against this module instance's own hook function, not a marker shared across every reloaded copy of it -- CI caught the marker version under `pytest -n auto`. A re-entrant call falls back to the interpreter's default; the thread-side guard is per-thread (threading.local), since two worker threads can legitimately raise concurrently without being each other's re-entrancy. threading.excepthook is now set, reversing an earlier decision to decline it. The interpreter's default thread hook consults sys.tracebacklimit itself, so a loud BaseError on a worker thread was terse only as a side effect of the global this removes; leaving it unset regressed that path from 1 line to 12 (measured). That gap is closed, including the thread-identifying header line the default hook always prints first. tests/utilities/test_decorators.py gained a tearDown this leak itself needed: constructing a loud StructError there with no quiet=True already left sys.tracebacklimit = 0 set for every later test in the process on current main -- the same #981 shape, just not previously visible as a failure. Development mode is unchanged. Neither hook fires for a traceback a caller formats itself with traceback.* rather than letting it reach the top level uncaught -- strictly better than today regardless, since sys.tracebacklimit truncated that path too, for every caller in the process.
3f1d505 to
afa7435
Compare
|
All five round-2 items addressed at Chained exceptions fixed and now tested. It reversed itself on It then edited the PR body itself to narrow the "strictly better than today" claim to caller-formatted The vacuous test now bites. It asserts the hook is installed before checking delegation, and against Stale prose taken where I scoped it — Verified: Still gated on your terse-versus-trimmed call. A fresh cross-review goes out once that is settled — |
|
The red mark on The two failures are What I could not establish: whether any test had already failed before the cancellation. The This does not change the PR's state: it is still held at |
|
What exactly needs my decision and what's your recommendation? |
|
One decision here, and my recommendation is: change nothing — keep the terse output. The question. Why I recommend against it. Measured against
Two further things against it: I raised trimmed only because I had not put it to you, and I am not advocating it. If you say "keep terse" One thing you should know before deciding, because it is not clean. The two red marks on The other three decisions are on #982, with recommendations. |
|
keep terse |
|
GOOD TO GO at The red marks: nothing had failed before the eviction. I re-derived this myself from the job log — A correction to what I relayed earlier. I told you the worker "proved it empirically: swapping in shared The install guard is load-bearing, proven by revert. Reverting to the old marker form The tbtrim composition resolves correctly, against the real thing. tbtrim assigns both hooks No process-global residue: two rounds in one interpreter, One real gap it names, not a blocker: no test asserts end-to-end that a non- I am not calling this ready to merge, because the two red marks are still unresolved on the revision UNVERIFIED: the |
|
Unit tests failed? |
|
READY TO MERGE at Your merge of The verdict carries to the new head, and here is why rather than just the assertion. Unpublished and otherwise untouched. Yours. |
…issue (#719) - Nine citations in tests/ called a pull request "GitHub issue" (#921, #764, #906, #815, #936, #721, #501, #983) or lumped PR #428 in with issue #425; each now names the right kind. - test_dispatch_default_resolution_unit: issue #425 reported the registry leak and PR #428 fixed it, so the sentence says "reported" and "fixed" instead of crediting the issue with the fix. - Prose only: docstrings and comments, no assertion or logic touched.
make pylint,make mypy,make isortpass clean onpcapkit/utilities/exceptions.py)make testpasses (rantests/utilities/under both pytest and plainunittest), and a test case covers the changedocs/source/changelog/and regeneratedCHANGELOG.md-- N/A — centralised in docs(changelog): shared 1.5.0 changelog — long-lived, merges last (#610, #616, #617, #618, #620) #657What is the purpose of your pull request?
fix— corrects a defectDescription of your pull request and other information
Issue #719: a loud
BaseErrorsetsys.tracebacklimit = 0outside development mode and never restored it. That attribute is process-global, so one pcapkit error silently truncated every subsequent traceback in the process, including exceptions pcapkit had nothing to do with. Measured: an unrelatedIndexErrorraised after a loudIntErrorcame back withtraceback.extract_tb()returning 0 frames.The fix removes the assignment outright. The same terse output now comes from exception hooks installed lazily on
sys.excepthookandthreading.excepthook, the first time a loud error outside development mode actually needs them. A hook printslimit=0against the exception's real traceback, nottb=None— the latter only discards the top exception's own frames and leaves a chained cause's traceback printed in full; fixed after cross-review caught it, with tests covering an explicitraise ... from errand an implicit context chain. Everything else is delegated, unchanged, to whatever hook was previously installed. Install is idempotent by identity against this module instance's own hook function, not a marker shared across every reloaded copy of it — the first version of this fix used the marker and CI caught the difference underpytest -n auto. A re-entrant call falls back to the interpreter's default instead of recursing; the thread-side guard is per-thread (threading.local), since two worker threads can legitimately raise concurrently without being each other's re-entrancy.I now set
threading.excepthook, reversing an earlier decision to decline it. The interpreter's default thread hook itself consultssys.tracebacklimit, so a loudBaseErroron a worker thread used to be terse only as a side effect of the global this PR removes — leaving it unset regressed that path from 1 line to 12 (measured), which my first write-up here wrongly called acceptable. That gap is now closed, including the thread-identifying header line the default hook always prints first.tests/utilities/test_decorators.pygained atearDownthis leak itself needed: constructing a loudStructErrorthere with noquiet=Truewas already leavingsys.tracebacklimit = 0set for every later test in the process on currentmain— the same #981 shape, just not previously visible as a failure.Remaining limitation: neither hook fires for a traceback a caller formats itself with
traceback.*instead of letting it reach the top level uncaught. Strictly better than today regardless, sincesys.tracebacklimittruncated that path too, for every caller in the process.