Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions docs/source/pcapkit/utilities/exceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ Loud and Quiet Errors
Raising a :class:`~pcapkit.utilities.exceptions.BaseError` is, by default, a
**loud** act: the error is logged once at :data:`logging.CRITICAL` on the
:data:`~pcapkit.utilities.logging.logger` logger, and outside development mode
:data:`sys.tracebacklimit` is set to ``0``, which suppresses the traceback frames
entirely so the user sees the exception line rather than a walk through
:mod:`pcapkit`'s internals.
an exception hook is installed on :data:`sys.excepthook` and
:data:`threading.excepthook`, the first time a loud error needs it, so the user
sees the exception line rather than a walk through :mod:`pcapkit`'s internals.
That hook only shortens the printing of a
:class:`~pcapkit.utilities.exceptions.BaseError` itself; every other exception
is handed on to whichever hook was previously installed, unchanged.

``quiet=True`` marks an error that :mod:`pcapkit` raises as **internal control
flow** and expects to catch itself -- the
Expand All @@ -42,8 +45,8 @@ It is still an ordinary exception carrying its message, so ``except`` clauses an
:show-inheritance:

:param quiet: If :data:`True`, the error is neither logged nor allowed to
alter :data:`sys.tracebacklimit`; it is raised silently, as internal
control flow.
install the exception hook; it is raised silently, as internal control
flow.
:param \*args: Arbitrary positional arguments.
:param \*\*kwargs: Arbitrary keyword arguments.

Expand Down
269 changes: 250 additions & 19 deletions pcapkit/utilities/exceptions.py

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions tests/utilities/test_decorators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import io
import sys
import unittest

from tests._support import (bootstrap_core_modules, install_fake_payload_protocols,
Expand All @@ -13,10 +14,41 @@ def setUp(self) -> None:
# binds partially-initialised real modules under ``pcapkit.*`` names, and
# ``_payload_stand_ins`` binds outright stand-ins. See #660.
isolate_modules(self)
self._protect_global_exception_state()
modules = bootstrap_core_modules()
self.decorators = modules['decorators']
self.exceptions = modules['exceptions']

def _protect_global_exception_state(self) -> None:
"""Restore ``sys.excepthook`` (and, defensively, ``sys.tracebacklimit``).

Several tests below construct a loud ``BaseError`` directly --
:meth:`test_beholder_wraps_struct_eof_with_no_payload`'s
``StructError('unexpected eof', eof=True)`` does not pass ``quiet=True``,
for one -- and a loud error outside development mode installs
:mod:`pcapkit.utilities.exceptions`'s own exception hook on first use
(GitHub issue #719). This file had no ``tearDown`` before that issue and
never needed one: the equivalent side effect at the time,
``sys.tracebacklimit = 0``, was already left unrestored, corrupting
every test that ran afterwards in the same process -- the #981 shape,
just not previously visible as a failure here. Addressed the way
``isolate_modules`` addresses its own restore: via ``addCleanup``, so it
still runs if the rest of ``setUp`` raises partway through.

"""
saved_hook = sys.excepthook
had_limit = hasattr(sys, 'tracebacklimit')
saved_limit = getattr(sys, 'tracebacklimit', None)

def _restore() -> None:
sys.excepthook = saved_hook
if had_limit:
sys.tracebacklimit = saved_limit
elif hasattr(sys, 'tracebacklimit'):
del sys.tracebacklimit

self.addCleanup(_restore)

def test_seekset_restores_original_offset(self) -> None:
class DemoProtocol:
def __init__(self) -> None:
Expand Down
Loading
Loading