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
29 changes: 14 additions & 15 deletions tests/cli/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,24 @@
:data:`sys.modules` is process-global, so binding a stand-in over a real module
name is a write that outlives the test unless something undoes it -- and these
stand-ins are *emptier* than what they replace, which is the quiet kind. Issue
#688: this file bound module names there -- the stand-ins above, the bare
#688 is this file binding module names there -- the stand-ins above, the bare
``pcapkit.foundation`` parent they hang from, and ``pcapkit.__main__`` itself
-- and put none of them back. Pairing it with :mod:`tests.project.test_public_api`
gave a run of ``ImportError: cannot import name 'show_flag_values' from
-- and putting none of them back. Paired with :mod:`tests.project.test_public_api`,
that gives a run of ``ImportError: cannot import name 'show_flag_values' from
'pcapkit.utilities.compat'`` errors and one for ``SeekError``, because the real
library's next import found a ``pcapkit.utilities.compat`` carrying exactly one name.
library's next import finds a ``pcapkit.utilities.compat`` carrying exactly one name.

That is the same defect as issues #660 and #674, and it is fixed the same way:
:func:`tests._support.isolate_modules` in ``setUp``, which purges the region on
the way in and restores it exactly on the way out. What this file used to do
instead was roll its own purge loop, which protects this file from whatever ran
before it and promises nothing to whatever runs after -- see
the way in and restores it exactly on the way out. A purge loop of this file's
own would not do: it protects this file from whatever ran before it and promises
nothing to whatever runs after -- see
:func:`tests._support.purge_modules` for why restoration is owed by the code that
binds.

:func:`tests.conftest.restore_module_table` masked the symptom under
:program:`pytest` throughout, which is why it took an audit to find: it surfaces
only under ``--noconftest``, under the stdlib :mod:`unittest` runner, or on any
route that does not load that conftest.
:func:`tests.conftest.restore_module_table` masks the symptom under
:program:`pytest`: it surfaces only under ``--noconftest``, under the stdlib
:mod:`unittest` runner, or on any route that does not load that conftest.
:meth:`CLIMainTests.assert_module_table_restored` is checked in a cleanup
registered *before* the isolation, so it runs after the restore and is not
masked by that fixture -- a regression here fails under a plain
Expand Down Expand Up @@ -408,10 +407,10 @@ def test_layer_defaults_to_none_and_is_case_insensitive(self) -> None:
"""An omitted ``-L``/``-P`` forwards :data:`None`, not a sentinel string.

``Extractor.__init__`` substitutes its own ``'none'``/``'null'`` for an
omitted value, so the CLI has nothing to substitute. It used to pass the
strings ``'None'`` and ``'null'``, which only worked because
``Extractor`` happened to lowercase the former into the sentinel it
wanted.
omitted value, so the CLI has nothing to substitute. Forwarding the
strings ``'None'`` and ``'null'`` instead works only because
``Extractor`` happens to lowercase the former into the sentinel it
wants.

"""
emoji = types.SimpleNamespace(emojize=lambda text: text)
Expand Down
10 changes: 5 additions & 5 deletions tests/corekit/test_bitfield_unnamed_bits_1487_unit.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# -*- coding: utf-8 -*-
"""A BitField packs back the bits its namespace does not name.

GitHub issue #1487: :class:`~pcapkit.corekit.fields.strings.BitField` read only
its named subfields and packed from all zeros, so octets with a bit outside
every subfield set came back with it cleared. The unpacked value now carries
those bits under ``__unnamed__`` when any is set, and a value without the key
packs them as zeros, as before.
GitHub issue #1487 guards against :class:`~pcapkit.corekit.fields.strings.BitField`
reading only its named subfields and packing from all zeros, so that octets with a
bit outside every subfield set come back with it cleared. The unpacked value
carries those bits under ``__unnamed__`` when any is set, and a value without the
key packs them as zeros.

:mod:`pcapkit` is imported inside each test, after
:func:`~tests._support.reimport_once_per_class`, not at module load.
Expand Down
70 changes: 34 additions & 36 deletions tests/corekit/test_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ def _protect_global_exception_state(self) -> None:
``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
(GitHub issue #719). Left unrestored, that process-global corrupts every
test that runs afterwards in the same process -- the #981 shape, whether
or not it shows as a failure here. It is restored the way
``isolate_modules`` addresses its own restore: via ``addCleanup``, so it
still runs if the rest of ``setUp`` raises partway through.

Expand Down Expand Up @@ -131,9 +129,9 @@ def _demo_schema_for_call_shapes(self):

Shared by the five call-shape tests below, one per row of `#444
<https://github.com/JarryShaw/PyPCAPKit/issues/444>`__'s reproduction
table: ``prepare`` used to subscript ``args[2]``/``args[3]``
unconditionally, so only the three-positional shape worked and every
shorter or keyword call raised ``IndexError`` instead of falling back
table: without the fix, ``prepare`` subscripts ``args[2]``/``args[3]``
unconditionally, so only the three-positional shape works and every
shorter or keyword call raises ``IndexError`` instead of falling back
to the documented ``length=None, packet=None`` defaults.

"""
Expand Down Expand Up @@ -187,20 +185,20 @@ def test_prepare_accepts_keyword_length_and_packet(self) -> None:
self._assert_call_shape_result(DemoSchema.unpack(b'payload', length=7, packet={}))

def test_prepare_raises_typeerror_for_extra_positional_and_keyword_arguments(self) -> None:
"""Extras used to vanish silently instead of being forwarded; see #454.
"""Extras raise :exc:`TypeError` rather than vanishing silently; see #454.

``prepare``'s own docstring promised the decorated function receives
``*args, **kwargs``, but the wrapper never populated either --
reproduced from the issue::
Without the fix, ``prepare``'s own docstring promises the decorated
function receives ``*args, **kwargs``, but the wrapper populates neither
-- reproduced from the issue::

Probe.unpack(b'\\x07\\x08', 2, {}, 'EXTRA_POSITIONAL', extra_kw='EXTRA_KW')
-> a=7 b=8 # both extras silently gone, no error

The chosen fix removes the promise (nothing in the tree ever passed
extras, and ``@prepare`` decorates exactly one function, whose real
signature never had room for them) and rejects extras instead of
forwarding them, so a misspelled or unsupported argument is a
``TypeError`` rather than a parse that silently ignored it.
``prepare`` makes no such promise (nothing in the tree passes extras, and
the real signature of the function ``@prepare`` decorates has no room for
them) and rejects extras instead of forwarding them, so a misspelled or
unsupported argument is a ``TypeError`` rather than a parse that silently
ignores it.

"""
DemoSchema = self._demo_schema_for_call_shapes()
Expand All @@ -211,11 +209,11 @@ def test_prepare_raises_typeerror_for_extra_positional_and_keyword_arguments(sel
def test_prepare_raises_typeerror_for_length_given_both_positionally_and_by_keyword(self) -> None:
"""The narrower case #454 calls out.

Passing ``length`` both positionally and by keyword used to silently
keep the positional value and drop the keyword one -- the keyword
never reached ``kwargs.pop``, since that branch only runs when the
positional slot was *not* supplied. It is now a caller error like any
other unconsumed argument.
Passing ``length`` both positionally and by keyword is a caller error
like any other unconsumed argument. Without the fix, the positional
value is silently kept and the keyword one dropped -- the keyword never
reaches ``kwargs.pop``, since that branch only runs when the positional
slot is *not* supplied.

"""
DemoSchema = self._demo_schema_for_call_shapes()
Expand All @@ -241,13 +239,13 @@ def unpack(cls, data, length=None, packet=None):
DemoSchema.unpack(b'', None, None)

def test_prepare_raises_stream_eof_error_not_a_bare_eof_error(self) -> None:
"""The genuinely-exhausted case now raises an in-library exception.
"""The genuinely-exhausted case raises an in-library exception.

A caller could not previously tell a truncated capture apart from any
other ``EOFError``. ``StreamEOFError`` still *is* an ``EOFError`` --
so ``pcapkit.foundation.extraction.Extractor``'s existing ``except
(EOFError, StopIteration)`` keeps working unchanged -- but it can now
be caught specifically via
That lets a caller tell a truncated capture apart from any other
``EOFError``. ``StreamEOFError`` *is* an ``EOFError`` -- so
``pcapkit.foundation.extraction.Extractor``'s ``except
(EOFError, StopIteration)`` catches it -- but it can also be caught
specifically via
:class:`pcapkit.utilities.exceptions.StreamEOFError`.

"""
Expand Down Expand Up @@ -299,7 +297,7 @@ def test_prepare_accepts_a_declared_zero_length_schema(self) -> None:
class Empty(Schema):
pass
Empty.unpack(b'', 0, {})
-> EOFError: EOFError() # before the fix
-> EOFError: EOFError() # without the fix

Distinguishing this from the truncated-stream case above is what
``length is not None`` (a *declared* length, even zero) checks for.
Expand Down Expand Up @@ -333,12 +331,12 @@ def test_prepare_leaves_the_schema_clean_after_post_process(self) -> None:

``Schema.unpack`` clears ``__updated__`` before returning, but
``post_process`` runs after it and every field it assigns sets the flag
again. The schema was therefore left dirty, and the next
``bytes(schema)`` re-packed it -- which calls ``post_process`` a second
again. Without the fix the schema is therefore left dirty, and the next
``bytes(schema)`` re-packs it -- which calls ``post_process`` a second
time with a packet context rebuilt from the schema's own fields, so any
value ``post_process`` had derived from the *enclosing* layer's context
was overwritten with the fallback. ``Schema.pack`` clears the flag after
``post_process``, and this asserts the unpacking path now matches it.
value ``post_process`` derived from the *enclosing* layer's context is
overwritten with the fallback. ``Schema.pack`` clears the flag after
``post_process``, and this asserts the unpacking path matches it.

"""
class DemoSchema:
Expand Down Expand Up @@ -496,8 +494,8 @@ def test_beholder_uses_the_protocol_payload_accessor_not_the_schema(self) -> Non
so neither header schema has a ``payload`` field: reaching for the schema
raises ``ProtocolUnbound('unknown field: payload')`` *from the recovery
path*, turning a next-layer failure that should have degraded to
:class:`Raw` into a crash. It was unreachable until something was
registered on an SCTP payload protocol identifier, which NGAP now is.
:class:`Raw` into a crash. It is reachable once something is registered
on an SCTP payload protocol identifier, as NGAP is.

"""
exceptions = self.exceptions
Expand Down
11 changes: 6 additions & 5 deletions tests/corekit/test_decorators_path_1735_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@

GitHub issue #1735, applying #1712's placement rule. ``seekset``, ``beholder``
and ``prepare`` act on a protocol's file and the schema it unpacks, and
``beholder`` falls back to protocol classes, so they moved from
``pcapkit.utilities.decorators`` to :mod:`pcapkit.corekit.decorators`. Per the
ruling on #1715 nothing is left at the old path, and :mod:`pcapkit.utilities`
no longer exports the three names. This pins both halves of that: the old path
``beholder`` falls back to protocol classes, so they live in
:mod:`pcapkit.corekit.decorators` rather than ``pcapkit.utilities.decorators``.
Per the ruling on #1715 nothing is left at the old path, and
:mod:`pcapkit.utilities` does not export those names. This pins both halves of
that: the old path
and the old exports are gone, and every importer uses the new module.

"""
Expand All @@ -23,7 +24,7 @@
#: Where the decorators live.
NEW_PATH = 'pcapkit.corekit.decorators'

#: Where they lived before #1735; removed, not re-exported.
#: The path they moved from (#1735); nothing is left there, not even a re-export.
OLD_PATH = 'pcapkit.utilities.decorators'

#: The three decorators.
Expand Down
8 changes: 3 additions & 5 deletions tests/corekit/test_deferred_packet_1516_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
"""GitHub issue #1516: one :class:`~pcapkit.corekit.packet.DeferredPacket` for
reassembly and flow tracing.

There used to be two copies of the mixin, in
``pcapkit/foundation/reassembly/data/data.py`` and
``pcapkit/foundation/traceflow/data/data.py``. Their method bodies were
AST-identical, and the one thing that differed was which module-level
``Deferred`` the ``isinstance`` test named. The merged class takes that
The mixin serves the models of both ``pcapkit/foundation/reassembly/data/data.py``
and ``pcapkit/foundation/traceflow/data/data.py``, which differ only in which
module-level ``Deferred`` the ``isinstance`` test names. The class takes that
difference as the ``__deferred__`` hook each subclass sets, so a model resolves
its own placeholder class and nothing else.

Expand Down
Loading
Loading