diff --git a/tests/_dependency_gates.py b/tests/_dependency_gates.py index 265e2951fa..1420d37881 100644 --- a/tests/_dependency_gates.py +++ b/tests/_dependency_gates.py @@ -2,9 +2,9 @@ """Which CI job installs the dependency each ``HAS_*`` gate asks for. :mod:`tests._tiers` answers "may this module read that capture". This module -answers the neighbouring question that nothing asked before: **when a test is -gated on an optional dependency, does the job whose selection reaches that test -actually install it?** +answers the neighbouring question: **when a test is gated on an optional +dependency, does the job whose selection reaches that test actually install +it?** The reason it needs asking is that the failure mode is silence. A ``@unittest.skipUnless(HAS_CRYPTO, ...)`` whose dependency nobody installs does @@ -740,8 +740,8 @@ def _top_level_providers(module: 'str') -> 'tuple[str, ...]': unrecognised one is the worst answer available; #779 is what that cost, and :func:`_disqualified_providers` below is the message shape this follows. - Unchanged for a mapped module: the same tuple the subscript returned, so the - lookup is a diagnostic improvement and not a semantic one. + For a mapped module it is the same tuple a subscript returns, so only the + failure for an unmapped one differs from a bare lookup. """ top_level = module.partition('.')[0] @@ -762,14 +762,13 @@ def _top_level_providers(module: 'str') -> 'tuple[str, ...]': def module_providers(module: 'str') -> 'frozenset[str]': """Every distribution (or :data:`CORE`) :data:`MODULE_PROVIDERS` says could ship ``module``. - Unlike :func:`extras_providing`, which this deliberately does not touch, - keyed on the dotted path *exactly as given* when :data:`MODULE_PROVIDERS` - has a dedicated entry for it -- ``pcap._pcap`` resolves to ``('pcap-ct',)`` - alone rather than falling back to plain ``pcap``'s two-wide entry. Falls - back to the top-level package otherwise, the same rule - :func:`extras_providing` always uses, which is what makes an unmapped - module a failure there rather than a silent pass here too (the fallback goes - through :func:`_top_level_providers`, so it raises the same + Unlike :func:`extras_providing`, keyed on the dotted path *exactly as given* + when :data:`MODULE_PROVIDERS` has a dedicated entry for it -- ``pcap._pcap`` + resolves to ``('pcap-ct',)`` alone rather than falling back to plain + ``pcap``'s two-wide entry. Falls back to the top-level package otherwise, + the same rule :func:`extras_providing` always uses, which is what makes an + unmapped module a failure there rather than a silent pass here too (the + fallback goes through :func:`_top_level_providers`, so it raises the same :exc:`AssertionError` naming the module that that function's own lookup does). @@ -886,9 +885,8 @@ def _probed_modules(node: 'ast.AST', names: 'dict[str, tuple[str, ...]]', cannot tell them apart -- it would report ``HAS_PYPCAP`` as needing a module whose presence makes it false. - ``want_negated`` picks which side this call collects: :data:`False` (the - default, and every call site before :func:`module_flag_exclusions` existed) - yields the positively-required modules, as before. :data:`True` yields the + ``want_negated`` picks which side this call collects: :data:`False`, the + default, yields the positively-required modules. :data:`True` yields the mirror image -- the modules a negated probe asks to be absent -- which is what :func:`module_flag_exclusions` asks for. A module is never yielded by both calls on the same expression: exactly one of ``negated == want_negated`` @@ -1228,11 +1226,9 @@ def _engine_matrix_variants(name: 'str', section: 'str') -> 'Optional[tuple[Job, This also, separately, asserts that the workflow's own ``engine:`` matrix list agrees with the ``case "$ENGINE" in ...`` arms it is supposed to - describe: earlier versions of this function built :class:`Job` entries - purely from the case arms and never looked at the ``engine:`` list at - all, so deleting a cell from that list (the ordinary way to retire one) - left this guard still crediting the retired engine with coverage it no - longer has, silently. + describe: built purely from the case arms, the :class:`Job` entries would + go on crediting an engine deleted from that list (the ordinary way to + retire a cell) with coverage it does not have, silently. """ code = '\n'.join(line for line in section.splitlines() if not line.strip().startswith('#')) @@ -1570,8 +1566,8 @@ def ambiguous_satisfactions( This is the check that notices, scoped to exactly the names :data:`MUTUALLY_EXCLUSIVE_IMPORTS` declares genuinely contested -- deliberately *not* every name :data:`MODULE_PROVIDERS` lists more than one - requirement string for for. ``html5lib`` also has two entries there, and - is not this: both name the same one distribution reached two ways, not two + requirement string for. ``html5lib`` also has two entries there, and is + not this: both name the same one distribution reached two ways, not two distributions that could each independently win the import, so scoping on ``len(MODULE_PROVIDERS[...]) > 1`` instead would false-positive on it the moment some job gained the ``vendor`` extra. @@ -1615,8 +1611,7 @@ def ambiguous_satisfactions( # not for every gate this job reaches: an excluded module with # no MODULE_PROVIDERS entry of its own raises here (see # _disqualified_providers), and a flag with nothing to do with - # a contested name must never pay for that -- see this - # function's own history (#762 round 3). + # a contested name must never pay for that (#762). excluded_modules = exclusions.get((gate.module, gate.flag), frozenset()) disqualified = _disqualified_providers(excluded_modules) diff --git a/tests/_support.py b/tests/_support.py index 2a20b1b45e..96fcb526bd 100644 --- a/tests/_support.py +++ b/tests/_support.py @@ -400,8 +400,8 @@ def load_module(module_name: str, relative_path: str, Both halves of that write to :data:`sys.modules` -- the stub parents, and ``module_name`` itself -- so both are taken back off again when ``test`` - finishes, via :func:`restore_modules_after`. Before issue #674 they were not, - and test modules left a bare ``pcapkit`` bound for whatever ran next. + finishes, via :func:`restore_modules_after`. Without that, a test module + leaves a bare ``pcapkit`` bound for whatever runs next (issue #674). Args: module_name: Dotted name to execute the file under, e.g. @@ -587,8 +587,8 @@ class objects, and their creation churns the C-level ``_abc_impl`` caches :func:`abc._reset_caches` is a CPython internal (present on both the C ``_abc`` and pure-python ``_py_abc`` backends); if a future runtime drops - it this degrades to the previous, occasionally-flaky behaviour rather than - erroring. + it this degrades to skipping the reset, which is occasionally flaky, + rather than erroring. """ reset = getattr(abc, '_reset_caches', None) if reset is None: # pragma: no cover @@ -748,9 +748,9 @@ def _running_test_case() -> 'Optional[unittest.TestCase]': same frame, so one handed some other, finished :class:`~unittest.TestCase` would otherwise win the walk -- and a cleanup registered on a test that has already finished is never run, which is the original leak with an extra step - and no error to show for it. Measured before the check was added: a ten-name - leak, silently. Skipping the idle candidate lets the walk continue to the - method frame above, which is the running test. + and no error to show for it. Measured without the check: a ten-name leak, + silently. Skipping the idle candidate lets the walk continue to the method + frame above, which is the running test. Returns: The running test, or :data:`None` when the caller is not inside a running diff --git a/tests/_tiers.py b/tests/_tiers.py index 87a7c7ed7e..c75341b300 100644 --- a/tests/_tiers.py +++ b/tests/_tiers.py @@ -81,21 +81,22 @@ check sees the name however it was computed, e.g. ``sample_path(sample)`` in a parametrised loop, but only once the call is reached. -What neither catches, deliberately: a unit-tier module that opens a capture -without going through :func:`~tests._support.sample_path` at all, e.g. by joining +What neither catches, deliberately: a unit-tier module that opens a capture without +going through :func:`~tests._support.sample_path` at all, e.g. by joining :file:`examples/captures/` and a name itself. No *unit-tier* module does this: -:file:`tests/protocols/misc/test_pcapng_unit.py` was the last one, and -reads ``sample_path('dhcp_big_endian.pcapng')`` inside the ``try``/``except -FileNotFoundError`` idiom :func:`explain` recommends. Only the *unit* tier is -claimed there, deliberately: fixture-tier modules join the directory freely and -are entitled to, e.g. :file:`tests/protocols/test_option_coverage_runtime.py`, -which imports :data:`SAMPLE_ROOT` from here and joins names onto it -- legal, -because that tier runs only once the fixtures exist. The one unit-tier module that -touches :data:`SAMPLE_ROOT` at all, :file:`tests/test_tier_guard.py`, stats -:file:`in.pcap` directly -- which is committed -- and separately loops over every -name :func:`committed_captures` reports, each committed by construction, since that -is git's own tracked listing rather than a name the test made up. But nothing stops -the next unit-tier module from opening a *generated* capture that way, and the shape +:file:`tests/protocols/misc/test_pcapng_unit.py`, for one, reads +``sample_path('dhcp_big_endian.pcapng')`` inside the +``try``/``except FileNotFoundError`` idiom :func:`explain` recommends. Only the +*unit* tier is claimed there, deliberately: fixture-tier modules join the directory +freely and are entitled to, e.g. +:file:`tests/protocols/test_option_coverage_runtime.py`, which imports +:data:`SAMPLE_ROOT` from here and joins names onto it -- legal, because that tier +runs only once the fixtures exist. The one unit-tier module that touches +:data:`SAMPLE_ROOT` at all, :file:`tests/test_tier_guard.py`, stats :file:`in.pcap` +directly -- which is committed -- and separately loops over every name +:func:`committed_captures` reports, each committed by construction, since that is +git's own tracked listing rather than a name the test made up. But nothing stops the +next unit-tier module from opening a *generated* capture that way, and the shape stays invisible here. Flagging it in general was considered and rejected, with a measurement behind the @@ -1729,8 +1730,7 @@ def fixture_tier_paths(tier: 'Optional[str]' = None) -> 'tuple[str, ...]': :func:`skip_idiom_modules` directly, so that pulling in one skip-idiom test does not also pull in every unrelated method the same module happens to hold. Without that, the skip-idiom modules would add every one of their - tests back into this selection -- well over a hundred -- to reach the - handful that actually need it. + tests back into this selection to reach the handful that actually need it. This is deliberately *not* "the complement of :func:`is_unit_tier`" expressed as a single predicate the way :func:`is_unit_tier` itself is: the diff --git a/tests/cli/test_main.py b/tests/cli/test_main.py index bbc7428431..8a115b4ed5 100644 --- a/tests/cli/test_main.py +++ b/tests/cli/test_main.py @@ -59,10 +59,10 @@ #: Wider than :data:`tests._support.ISOLATED_PREFIXES` by ``'emoji'``, which is #: the one stand-in here that is not part of :mod:`pcapkit`. #: :func:`tests.conftest.restore_module_table` covers only the default prefixes, -#: so the faked ``emoji`` was not merely unrestored but *unmasked* as well: it -#: survived even a normal :program:`pytest` run, leaving whatever imported -#: :mod:`emoji` next with a :class:`~types.SimpleNamespace` carrying nothing but -#: ``emojize`` -- or, from the last test here, a class whose ``emojize`` raises +#: so a faked ``emoji`` left unrestored is *unmasked* as well: it survives even +#: a normal :program:`pytest` run, leaving whatever imports :mod:`emoji` next +#: with a :class:`~types.SimpleNamespace` carrying nothing but ``emojize`` -- +#: or, from the last test here, a class whose ``emojize`` raises #: :exc:`UnicodeEncodeError`. ISOLATED = ISOLATED_PREFIXES + ('emoji',) diff --git a/tests/conftest.py b/tests/conftest.py index 129889ec34..ec2cb3725c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -286,21 +286,21 @@ def restore_module_table() -> 'Iterator[None]': :meth:`~unittest.TestCase.setUpClass` runs *before* this fixture's first entry for the class and a class-level cleanup runs *after* its last exit, so either can plant exactly the kind of leak the paragraphs above describe - without this fixture's old, entry-scoped restore ever seeing it -- an - entry-scoped snapshot faithfully preserves whatever a ``setUpClass`` purge - already did, rather than undoing it, and a class-level cleanup that purges - again has no later exit here to be caught by at all. See - :func:`_pin_module_snapshot` for why the fix is to restore to a fixed target - instead, both before the test and after it, rather than to change what is - snapshotted and when: doing it on both sides means neither a ``setUpClass`` - purge nor a class-exit purge survives past the next test's own entry, - regardless of which one planted it or whether the class in between reimports - anything. This is the same "leaning on a neighbour" trap the paragraph above - already rejects, generalised: healing a class-level leak by restoring before - the *next* test runs is the guard doing its own job on a fixed schedule, not - an accident of collection order -- unlike relying on some *other* class's own - purge to happen to reimport first, which is exactly the accident #720's - reproduction shows failing half the time. + without an entry-scoped restore ever seeing it -- an entry-scoped snapshot + faithfully preserves whatever a ``setUpClass`` purge already did, rather than + undoing it, and a class-level cleanup that purges again has no later exit + here to be caught by at all. See :func:`_pin_module_snapshot` for why the fix + is to restore to a fixed target instead, both before the test and after it, + rather than to change what is snapshotted and when: doing it on both sides + means neither a ``setUpClass`` purge nor a class-exit purge survives past the + next test's own entry, regardless of which one planted it or whether the + class in between reimports anything. This is the same "leaning on a + neighbour" trap the paragraph above already rejects, generalised: healing a + class-level leak by restoring before the *next* test runs is the guard doing + its own job on a fixed schedule, not an accident of collection order -- + unlike relying on some *other* class's own purge to happen to reimport first, + which is exactly the accident #720's reproduction shows failing half the + time. Yields: Nothing; the region is already restored by the time control passes to @@ -476,9 +476,10 @@ def pytest_collection_modifyitems(session: 'pytest.Session', config: 'pytest.Con # pytest-xdist 3.8.0/pytest 9.1.1: setting `shouldfail` first is what the # controller already knows how to shut a worker down on cleanly, no # crash-item assertion, and the message still comes through -- as - # ``xdist.dsession.Interrupted: `` rather than ``ERROR: - # ``, which is the one visible difference from the serial path. - # A no-op outside a worker, so serial behaviour is unchanged. + # ``xdist.dsession.Interrupted: `` rather than + # ``ERROR: ``, which is the one visible difference from the + # serial path. A no-op outside a worker, where the ``UsageError`` alone + # ends the run. if hasattr(config, 'workerinput'): session.shouldfail = message raise pytest.UsageError(message) @@ -629,11 +630,11 @@ def _tier_guard_lock(exclusive: 'bool') -> 'Iterator[None]': has to poll (see :data:`TIER_GUARD_LOCK_POLLS`), they do (#1977). A second lock file, the gate, gives the writer priority: a writer holds the gate exclusively while it waits for the lock, and a reader passes through the - gate, shared and released at once, before asking for the lock. Readers - hold the gate only for that instant, so the writer gets it almost at once, - and the readers already past it then drain. The kernel drops both locks - with a dying process, so no crash can leave the gate shut. Every wait is - unbounded, as before: proceeding without the lock would reopen the race. + gate, shared and released at once, before asking for the lock. Readers hold + the gate only for that instant, so the writer gets it almost at once, and + the readers already past it then drain. The kernel drops both locks with a + dying process, so no crash can leave the gate shut. Every wait is + unbounded: proceeding without the lock would reopen the race. Args: exclusive: :data:`True` for the writer's hold, :data:`False` for a diff --git a/tests/corekit/test_decorators.py b/tests/corekit/test_decorators.py index bc6478eb78..71222aff0d 100644 --- a/tests/corekit/test_decorators.py +++ b/tests/corekit/test_decorators.py @@ -127,7 +127,7 @@ def unpack(cls, data, length=None, packet=None): def _demo_schema_for_call_shapes(self): """A ``DemoSchema`` whose ``unpack`` records what ``prepare`` bound. - Shared by the five call-shape tests below, one per row of `#444 + Shared by the call-shape tests below, one per row of `#444 `__'s reproduction table: without the fix, ``prepare`` subscripts ``args[2]``/``args[3]`` unconditionally, so only the three-positional shape works and every @@ -170,7 +170,7 @@ def test_prepare_accepts_data_and_positional_length(self) -> None: self._assert_call_shape_result(DemoSchema.unpack(b'payload', 7)) def test_prepare_accepts_data_length_and_packet_positionally(self) -> None: - """``unpack(data, length, packet)`` -- the one shape that already worked.""" + """``unpack(data, length, packet)`` -- the shape that never needed the fix.""" DemoSchema = self._demo_schema_for_call_shapes() self._assert_call_shape_result(DemoSchema.unpack(b'payload', 7, {})) @@ -267,9 +267,8 @@ def unpack(cls, data, length=None, packet=None): def test_prepare_still_raises_eof_for_a_truncated_stream(self) -> None: """A file-like stream already exhausted, with no declared length, is - the frame reader's genuine end-of-capture case and must still raise -- - confirming the #458 fix only changes the *declared*-zero case below, - not this one. + the frame reader's genuine end-of-capture case and must still raise: + only the *declared*-zero case below unpacks (#458). """ class DemoSchema: @@ -397,7 +396,7 @@ def unpack(cls, data, length=None, packet=None): # ``self.__header__.get_payload()``, because that is what # ``ProtocolBase._get_payload`` is -- a wrapper over the schema's accessor. # ``beholder`` goes through the wrapper rather than the schema directly, so - # that the two protocols which override it (SCTP, PCAP-NG) recover from a + # that the protocols which override it, SCTP and PCAP-NG, recover from a # next-layer failure at all; see # ``test_beholder_uses_the_protocol_payload_accessor_not_the_schema``. ########################################################################## @@ -472,8 +471,8 @@ def test_beholder_forwards_the_protocol_number_as_an_alias(self) -> None: The success path in ``_import_next_layer`` passes ``alias=proto``, so a payload that reached :class:`Raw` because nothing was registered on its number is still labelled with that number. Omitting it from the failure - path made *registering* a protocol produce less informative output than - leaving the number unregistered. + path would make *registering* a protocol produce less informative + output than leaving the number unregistered. """ Raw, _ = self._payload_stand_ins() diff --git a/tests/corekit/test_deferred_packet_1516_unit.py b/tests/corekit/test_deferred_packet_1516_unit.py index eb7fa2a83d..7932a03477 100644 --- a/tests/corekit/test_deferred_packet_1516_unit.py +++ b/tests/corekit/test_deferred_packet_1516_unit.py @@ -211,9 +211,9 @@ def test_every_model_shares_the_one_class_mixin_first(self) -> 'None': self.assertIs(cls.__deferred__, importlib.import_module(deferred_module).Deferred) def test_the_old_import_paths_are_gone(self) -> 'None': - """The name was public at all three before the move; GitHub issue #1744 - dropped those re-exports (#1715: a moved name keeps no re-export at its - old path), so :mod:`pcapkit.corekit.packet` is the one home. + """None of the three paths the name was public at keeps a re-export + (GitHub issue #1744; #1715: a moved name keeps no re-export at its old + path), so :mod:`pcapkit.corekit.packet` is the one home. """ from pcapkit.corekit.packet import DeferredPacket diff --git a/tests/corekit/test_enum_get_exception_provenance_923_unit.py b/tests/corekit/test_enum_get_exception_provenance_923_unit.py index 2503bb0f72..4b399ad40b 100644 --- a/tests/corekit/test_enum_get_exception_provenance_923_unit.py +++ b/tests/corekit/test_enum_get_exception_provenance_923_unit.py @@ -194,7 +194,7 @@ def test_a_usable_default_still_raises_nothing(self) -> 'None': self.assertIs(_Closed.get(99, 2), _Closed.two) def test_old_call_sites_keep_catching(self) -> 'None': - """The whole reason the shape is preserved: the six in-library + """The whole reason the shape is preserved: the in-library ``except KeyError`` sites, and any caller's own, keep working.""" try: _Closed.get('nosuch') @@ -254,8 +254,9 @@ class BaseGetQuietnessTests(unittest.TestCase): inline: *"A pcapkit exception here would log at CRITICAL for something that is handled two lines later."* - Since GitHub issue #1845 the value miss is quiet as well, so the control - that shows the recorder can see a loud error constructs one directly. + The value miss is quiet as well (GitHub issue #1845), so the control + that shows the recorder can see a loud error constructs one + directly. """ def setUp(self) -> 'None': @@ -489,7 +490,7 @@ def test_a_value_miss_still_goes_through_missing(self) -> 'None': class MobilityHeaderNameMissTests(unittest.TestCase): - """The two :rfc:`5568` inline enumerations found by #923's own census. + """The :rfc:`5568` inline enumerations #923's own census found. Both raise from :mod:`pcapkit.utilities.exceptions` on a name miss -- the right provenance. Choosing ``EnumValueError`` for it, *"so the two ways of diff --git a/tests/corekit/test_enum_get_quiet_fallback_1845_unit.py b/tests/corekit/test_enum_get_quiet_fallback_1845_unit.py index 8c657e2957..02ccfe19b3 100644 --- a/tests/corekit/test_enum_get_quiet_fallback_1845_unit.py +++ b/tests/corekit/test_enum_get_quiet_fallback_1845_unit.py @@ -1,23 +1,23 @@ # -*- coding: utf-8 -*- """GitHub issue #1845: ``get`` is quiet for a value it cannot resolve; ``register`` is loud. -#1823 gave every generated registry a ``_validate_value`` override raising a loud -:exc:`~pcapkit.utilities.exceptions.EnumValueError`, and +Every generated registry has a ``_validate_value`` override raising a loud +:exc:`~pcapkit.utilities.exceptions.EnumValueError` (#1823), and :meth:`~pcapkit.corekit.enum.EnumLookup.get` calls that hook before ``cls(key)``. -So ``get(out_of_range, default)`` returned ``default`` but logged a -:data:`logging.CRITICAL` record and installed :mod:`pcapkit`'s exception hooks -on the way. The owner ruled ``get`` quiet for such a value, with a usable -``default`` or without one -- without, the miss is still raised, just not -reported -- and kept ``register`` loud. The quiet path lives in -:mod:`pcapkit.corekit`, so the generated overrides are untouched: ``get`` runs -them inside a :class:`~pcapkit.utilities.exceptions.CaughtByCaller` block, which -makes a :exc:`ValueError`-derived :class:`~pcapkit.utilities.exceptions.BaseError` +So without the fix ``get(out_of_range, default)`` returns ``default`` but logs a +:data:`logging.CRITICAL` record and installs :mod:`pcapkit`'s exception hooks on +the way. The owner ruled ``get`` quiet for such a value, with a usable ``default`` +or without one -- without, the miss is still raised, just not reported -- and kept +``register`` loud. The quiet path lives in :mod:`pcapkit.corekit`, so the +generated overrides stay loud: ``get`` runs them inside a +:class:`~pcapkit.utilities.exceptions.CaughtByCaller` block, which makes a +:exc:`ValueError`-derived :class:`~pcapkit.utilities.exceptions.BaseError` constructed there quiet. The generated registry under test is :class:`~pcapkit.const.ipv4.tos_pre.ToSPrecedence`, whose override accepts -``0``-``7`` only. ``LLACode``, the registry the issue measured on, is avoided -because one of its members is being renamed in parallel (#1848). +``0``-``7`` only. ``LLACode``, the registry the issue measured on, is not +used here: #1848 renamed one of its members. """ diff --git a/tests/corekit/test_enum_lookup_base_unit.py b/tests/corekit/test_enum_lookup_base_unit.py index f4b8fc06c5..38eee9db95 100644 --- a/tests/corekit/test_enum_lookup_base_unit.py +++ b/tests/corekit/test_enum_lookup_base_unit.py @@ -70,7 +70,7 @@ MUTATING = ('register', 'register_alias', 'register_aliases', '_extend', '_unregistered_member') -#: The lookup half, which must move to :class:`~pcapkit.corekit.enum.EnumLookup`. +#: The lookup half, which must live on :class:`~pcapkit.corekit.enum.EnumLookup`. LOOKUP = ('get', 'get_all', '_validate_value') @@ -171,7 +171,7 @@ def test_the_split_is_an_exact_partition(self) -> 'None': """Each method is defined on exactly one tier, and on the right one. Asserted against ``__dict__`` rather than with :func:`hasattr`, which - cannot tell "defined here" from "inherited": the mutating five are + cannot tell "defined here" from "inherited": the mutating methods are reachable from :class:`EnumLookup`'s subclasses either way, so presence alone would pass on a single-tier hierarchy. @@ -460,7 +460,7 @@ def _validate_value(cls, value: 'Any') -> 'None': class UnassignedAsymmetryTests(unittest.TestCase): - """The declared-but-unassigned ``str`` gap, unchanged on the new base. + """The declared-but-unassigned ``str`` gap, which holds on the base too. :meth:`~pcapkit.corekit.enum.EnumLookup.get`'s own docstring calls this deliberate: closing it would mean calling ``cls(key)`` for a ``str`` value, @@ -540,7 +540,7 @@ def test_flag_registry_member_round_trips(self) -> 'None': self.assertRoundTrips(_PickleFlag.solo) def test_bare_base_members_round_trip_too(self) -> 'None': - """And a closed set on the base alone, which is what phase 2 will create.""" + """And a closed set on the base alone, the shape phase 2's enumerations take.""" self.assertRoundTrips(_Int.one) self.assertRoundTrips(_Str.plain) self.assertRoundTrips(_Flag.first) diff --git a/tests/corekit/test_enum_lookup_reparent_877_unit.py b/tests/corekit/test_enum_lookup_reparent_877_unit.py index 0967713f0f..40b75cf44d 100644 --- a/tests/corekit/test_enum_lookup_reparent_877_unit.py +++ b/tests/corekit/test_enum_lookup_reparent_877_unit.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -"""Phase 2 of GitHub issue #877, the unblocked half: re-parenting 11 helper -enumerations across 8 files onto :class:`~pcapkit.corekit.enum.EnumLookup`. +"""Phase 2 of GitHub issue #877, the unblocked half: re-parenting the +helper enumerations onto :class:`~pcapkit.corekit.enum.EnumLookup`. The owner's last word on #877 was to re-parent every non-registry enumeration onto an in-house base class so that they all share the same contracts; it @@ -20,7 +20,7 @@ :class:`PacketReception `, :class:`WireGuardKeyLabel `, and :class:`FrameType.Flags -` plus its six +` plus its concrete per-frame subclasses. :class:`TransportProtocol` defines its own ``get``, as a :class:`classmethod` @@ -158,7 +158,7 @@ def test_wireguard_key_label(self) -> None: class HttpV2FlagsHierarchyTests(unittest.TestCase): - """``FrameType.Flags`` plus its six concrete per-frame subclasses -- one + """``FrameType.Flags`` plus its concrete per-frame subclasses -- one hierarchy, re-parented at the root only. Each concrete subclass declares ``class Flags(FrameType.Flags):`` with no @@ -254,8 +254,8 @@ def test_wireguard_key_label_get(self) -> None: self.assertIs(WireGuardKeyLabel.get('PRESHARED_KEY'), WireGuardKeyLabel.PRESHARED_KEY) def test_a_pure_reparent_still_refuses_an_unknown_name(self) -> None: - """The base's own contract -- raise, never mint -- reaches these nine - for free, exactly as it does the registries.""" + """The base's own contract -- raise, never mint -- reaches these for + free, exactly as it does the registries.""" from pcapkit.corekit.infoclass import FinalisedState with self.assertRaises(KeyError): @@ -418,9 +418,10 @@ def test_unresolved_value_still_raises_via_missing(self) -> None: self.assertIn('3', str(caught.exception)) def test_default_is_honoured_by_the_inherited_base(self) -> None: - """The one thing that could have made GitHub issue #923's deletion of - this override unsafe. The override forwarded ``default`` verbatim, so - the inherited base has to answer identically: a member-valued default + """The one thing that could make relying on the inherited base, with + no override (GitHub issue #923), unsafe. An override would forward + ``default`` verbatim, so the base has to answer identically: a + member-valued default resolves, and one naming no registered value is not honoured.""" from pcapkit.protocols.application.ngap import Criticality @@ -433,7 +434,7 @@ def test_default_is_honoured_by_the_inherited_base(self) -> None: class NoMintingTests(unittest.TestCase): - """None of the eleven re-parented classes mint on a lookup -- each is a + """None of the re-parented classes mint on a lookup -- each is a closed set on the bare lookup tier, not the mutating :class:`~pcapkit.corekit.enum.EnumRegistry` one. Measured per class in a throwaway subprocess, so a lookup made by an *earlier* assertion in this diff --git a/tests/corekit/test_enum_lookup_reparent_930_unit.py b/tests/corekit/test_enum_lookup_reparent_930_unit.py index f02b478264..8f9365e246 100644 --- a/tests/corekit/test_enum_lookup_reparent_930_unit.py +++ b/tests/corekit/test_enum_lookup_reparent_930_unit.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """GitHub issue #930, the blocked half of #877's phase 2: re-parenting the last -seven non-registry enumerations onto :class:`~pcapkit.corekit.enum.EnumLookup`. +non-registry enumerations onto :class:`~pcapkit.corekit.enum.EnumLookup`. GitHub pull request #921 re-parented every other non-registry enumeration and left these -seven alone, because the files holding them were open under other pull requests: +alone, because the files holding them were open under other pull requests: :mod:`pcapkit.const.ftp.command` (``CommandType``, ``ConformanceRequirement``) under #913, and :mod:`pcapkit.protocols.internet.esp` (``ESPStatus``) plus :mod:`pcapkit.protocols.internet.mh` (``FastBindingAcknowledgmentStatus``, @@ -11,7 +11,7 @@ module pins that they derive from the base, so that every non-registry enumeration is inside the hierarchy and none is outside it. -None of the seven declares a ``get`` of its own: ``get`` and ``get_all`` come from the base +None of these declares a ``get`` of its own: ``get`` and ``get_all`` come from the base alone. For :class:`FastBindingAcknowledgmentStatus` and :class:`IPv6AddressPrefixCode` that is GitHub issue #935's ruling, given on GitHub pull request #940: a hand-rolled override there resolves by :class:`int` or by name exactly as @@ -29,7 +29,7 @@ :exc:`~pcapkit.utilities.exceptions.EnumKeyError`, while the base, branching on ``isinstance(key, str)`` instead, answers with an :exc:`~pcapkit.utilities.exceptions.EnumValueError`. -:class:`NonCanonicalKeyConvergenceTests` pins that all seven answer such a key alike. +:class:`NonCanonicalKeyConvergenceTests` pins that they all answer such a key alike. :meth:`~pcapkit.corekit.enum.EnumLookup.get_all`, inherited from the base, calls ``get`` internally, so a name miss reached through it is as quiet as one reached through ``get``. @@ -92,12 +92,12 @@ class ReparentedBasesTests(unittest.TestCase): - """Every one of the seven gained :class:`EnumLookup` as a base, mixed in - *ahead of* its enum base so ``_member_type_`` still resolves to - :class:`int`, and none grew a member in the process -- this issue's own - brief is explicit that a re-parent minting one silently is exactly the - defect this programme exists to prevent, so each test below pins both in - one place rather than trusting that a passing base-tuple assertion says + """Each of these has :class:`EnumLookup` as a base, mixed in *ahead of* + its enum base so ``_member_type_`` still resolves to :class:`int`, and + none has a member the re-parent minted -- this issue's own brief is + explicit that a re-parent minting one silently is exactly the defect + this programme exists to prevent, so each test below pins both in one + place rather than trusting that a passing base-tuple assertion says anything about the member table. ``__members__`` (which counts aliases) and ``list(cls)`` (which does not) are pinned separately, since the two diverge on :class:`CommandType`: its ``undefined = 0`` member is a real, @@ -141,7 +141,7 @@ def test_esp_status(self) -> None: def test_fast_binding_acknowledgment_status(self) -> None: """No ``get`` override to pin here (GitHub pull request #940) -- :class:`AllSevenInheritTheBareClassmethodTests` covers this class - alongside the other six.""" + alongside the others.""" from aenum import IntEnum from pcapkit.corekit.enum import EnumLookup @@ -188,17 +188,17 @@ def test_lma_address_code(self) -> None: class NoMintingTests(unittest.TestCase): - """None of the seven mints on a miss, exercised through ``get`` itself + """None of these mints on a miss, exercised through ``get`` itself rather than only through the static counts above.""" def test_a_battery_of_lookups_does_not_grow_any_of_the_seven(self) -> None: - """None of the seven mints on a miss -- each is a closed set on the + """None of these mints on a miss -- each is a closed set on the bare lookup tier, not the mutating :class:`~pcapkit.corekit.enum.EnumRegistry` one. Exercises every ``get`` (name hit, value hit, name miss, value miss) and then re-checks every size against the values pinned above, in the same process and without subprocess isolation: unlike a registry whose - ``_missing_`` mints a placeholder, every one of these seven either + ``_missing_`` mints a placeholder, every one of these either resolves an existing member or raises, so there is nothing for an earlier assertion in this method to leave behind for a later one to mistake for growth. @@ -232,7 +232,7 @@ def test_a_battery_of_lookups_does_not_grow_any_of_the_seven(self) -> None: class GetByNameAndValueTests(unittest.TestCase): - """``get`` resolves by name and by value on each of the seven, uniformly + """``get`` resolves by name and by value on each of these, uniformly through the base's own inherited implementation -- none of them carries an override (GitHub issue #935).""" @@ -286,10 +286,10 @@ def test_lma_address_code(self) -> None: class FailedLookupTests(unittest.TestCase): - """What a miss raises on each of the seven -- a name miss is + """What a miss raises on each of these -- a name miss is :exc:`KeyError`-derived and a value miss :exc:`ValueError`-derived, matching stdlib :class:`~enum.Enum`'s own shape (GitHub issue #923) on - every one of the seven, uniformly through the base's own ``get`` (GitHub + every one of them, uniformly through the base's own ``get`` (GitHub issue #935). """ @@ -487,16 +487,16 @@ def test_get_with_unusable_default_is_quiet_too(self) -> None: class AllSevenInheritTheBareClassmethodTests(unittest.TestCase): - """All seven inherit the base's ``classmethod`` outright, none + """Each inherits the base's ``classmethod`` outright, none declaring a ``get`` of its own. For :class:`FastBindingAcknowledgmentStatus` and :class:`IPv6AddressPrefixCode` that is the owner's ruling on GitHub pull request #940 -- no override rather than one widened to accept - ``default`` -- so the seven are one uniform case. A test that only - checked the decorator would pass identically whether an override were - kept or converted, so this also asserts that ``get`` is absent from each - class's own ``__dict__``. + ``default`` -- so they are one uniform case. A test that only checked + the decorator would pass identically whether an override were kept or + converted, so this also asserts that ``get`` is absent from each class's + own ``__dict__``. """ def test_all_seven_inherit_the_bare_classmethod(self) -> None: @@ -512,13 +512,13 @@ def test_all_seven_inherit_the_bare_classmethod(self) -> None: with self.subTest(cls=cls.__name__): self.assertIsInstance(inspect.getattr_static(cls, 'get'), classmethod) # Inherited, not redeclared: the class's own ``__dict__`` carries - # no ``get`` of its own -- which holds for all seven alike. + # no ``get`` of its own -- which holds for each of them alike. self.assertNotIn('get', vars(cls)) class NonCanonicalKeyConvergenceTests(unittest.TestCase): """The point of GitHub issue #935's final ruling, made literally true: - ``get(None)`` and ``get(1.5)`` answer alike on all seven. + ``get(None)`` and ``get(1.5)`` answer alike on every one of them. An override branching on ``isinstance(key, int)`` routes every other type through its own *name* path -- so a key that is neither an @@ -530,9 +530,9 @@ class NonCanonicalKeyConvergenceTests(unittest.TestCase): :meth:`~pcapkit.corekit.enum.EnumLookup._validate_value` and the constructor, reaching :meth:`_missing_` and answering with an :exc:`~pcapkit.utilities.exceptions.EnumValueError`. With no override, - all seven answer both keys the same way -- measured here rather than - assumed, since "all seven behave alike" is exactly the claim GitHub - issue #935 set out to make true. + they all answer both keys the same way -- measured here rather than + assumed, since "they all behave alike" is exactly the claim GitHub issue + #935 set out to make true. Scope, stated as narrowly as the evidence supports: what this settles is the exception a key of some third type raises. Whether any caller passes diff --git a/tests/corekit/test_fields_all_exports_2174_unit.py b/tests/corekit/test_fields_all_exports_2174_unit.py index 0f1ff0c573..3d54e31c0f 100644 --- a/tests/corekit/test_fields_all_exports_2174_unit.py +++ b/tests/corekit/test_fields_all_exports_2174_unit.py @@ -50,7 +50,7 @@ #: ``nested_packet_context`` are helpers for writing a field or its callbacks, #: reached by their module path. ``nested_packet_context`` in particular builds #: the packet context a nested schema's callbacks receive; star-exporting it -#: would drop a generic name into the caller's namespace (:issue:`2174`). +#: would drop a generic name into the caller's namespace (#2174). #: * ``DecodedString`` is the value type :class:`~pcapkit.corekit.fields.strings.StringField` #: returns, not a field. FIELDS_DELIBERATE_NON_EXPORTS = { diff --git a/tests/corekit/test_fields_bytearray_pack_2004_unit.py b/tests/corekit/test_fields_bytearray_pack_2004_unit.py index 4dda430a83..41fa714580 100644 --- a/tests/corekit/test_fields_bytearray_pack_2004_unit.py +++ b/tests/corekit/test_fields_bytearray_pack_2004_unit.py @@ -10,8 +10,9 @@ The PyPy behaviour is reproduced on any interpreter with a :mod:`struct` stand-in that refuses a :obj:`bytearray`, as PyPy's does. The real -:func:`struct.pack` then pins that the output is unchanged and that a value -refused before -- a :obj:`memoryview`, a :obj:`bool`, a :obj:`str` -- still is. +:func:`struct.pack` then pins the output, and that a :obj:`memoryview`, a +:obj:`bool` and a :obj:`str` are refused as :func:`struct.pack` refuses +them. GitHub issue #2015. GraalPy's ``c`` format takes a :obj:`bytearray`, where CPython's refuses it, so the field hands a :obj:`bytearray` for ``c`` over as diff --git a/tests/corekit/test_fields_collections.py b/tests/corekit/test_fields_collections.py index b03957fe1c..98e4369e7a 100644 --- a/tests/corekit/test_fields_collections.py +++ b/tests/corekit/test_fields_collections.py @@ -80,8 +80,8 @@ def test_each_option_is_unpacked_exactly_once(self) -> None: """Selecting an option schema must not parse the option first. Reading the option type by unpacking the whole base schema and throwing - the result away parsed every option twice -- two schema unpacks per - option, the first of which was used for nothing but its type field. The + the result away would parse every option twice -- two schema unpacks + per option, the first of them used for nothing but its type field. The base schema must therefore not appear here at all, and each option schema exactly once. @@ -121,11 +121,10 @@ class OptionFieldForeignBaseSchemaTests(unittest.TestCase): and :mod:`pcapkit.foundation.registry` invites third-party protocols to register their own base schemas, which need not. - Such a base schema must be **unpacked in full**, as it was before the shortcut - existed. Getting this wrong does not raise: reading the wrong octets as the type - code selects the wrong option schema and the packet is silently misparsed, which - is why each case below asserts the parsed result and not merely that the slow - path ran. + Such a base schema must be **unpacked in full**. Getting this wrong does not + raise: reading the wrong octets as the type code selects the wrong option schema + and the packet is silently misparsed, which is why each case below asserts the + parsed result and not merely that the slow path ran. """ @@ -238,7 +237,7 @@ def test_a_type_field_that_is_not_first_falls_back_to_the_full_unpack(self) -> N # field second, the first four octets read ``size=0, kind=1``; a reader that # takes the *leading* two octets as the type code sees 0, which is the # end-of-option-list code, so it stops without parsing the option at all -- - # measured, before the fallback existed: ['End'] and four octets of padding. + # measured without the fallback: ['End'] and four octets of padding. Options = self._type_second() schema = Options.unpack(b'\x00\x00\x00\x01' b'\x00\x00\x00\x00', 8, {}) @@ -278,11 +277,11 @@ def test_a_plain_first_type_field_still_takes_the_shortcut(self) -> None: class OptionFieldPackageDeclarationTests(unittest.TestCase): """Every ``OptionField`` this package declares must still take the shortcut. - The fallback added for foreign base schemas is selected from the base schema's + The fallback for foreign base schemas is selected from the base schema's shape, so a base schema declared here in a shape the shortcut does not fit would silently drop back to parsing every one of its options twice -- correct, - but giving up the whole point of the change with nothing to show it. This is - the guard against that happening unnoticed. + but losing the shortcut with nothing to show it. This is the guard against + that happening unnoticed. """ @@ -325,8 +324,8 @@ class ListFieldSchemaItemTests(unittest.TestCase): ``field = self._item_type(packet)`` builds a per-item copy through :meth:`SchemaField.__call__ `, - which applies both ``callback`` and ``length_callback`` to that copy -- the - schema branch then unpacked from ``self._item_type`` instead of from + which applies both ``callback`` and ``length_callback`` to that copy -- without + the fix the schema branch then unpacks from ``self._item_type`` instead of from ``field``, discarding whatever the copy carries. C.f. #433. No declaration in this package passes either argument, so nothing here @@ -380,12 +379,12 @@ def test_a_callback_is_honoured(self) -> None: """The per-item field's ``callback`` mutation must reach ``unpack``. The callback is evaluated regardless: ``field = self._item_type(packet)`` - runs it for its side effect even on the unpatched code. So what this - proves is not that the callback *fires*, but that the mutation it made - on its copy is what ``unpack`` actually used. It alternates - ``field._schema`` between two otherwise-identical schemas, one per - item; unpacking from ``self._item_type`` instead would use the schema - fixed at construction time for every item. + runs it for its side effect even without the fix. So what this proves is + not that the callback *fires*, but that the mutation it made on its copy + is what ``unpack`` actually used. It alternates ``field._schema`` between + two otherwise-identical schemas, one per item; unpacking from + ``self._item_type`` instead would use the schema fixed at construction + time for every item. """ from pcapkit.corekit.fields.collections import ListField diff --git a/tests/corekit/test_fields_copy_dispatch_unit.py b/tests/corekit/test_fields_copy_dispatch_unit.py index 8d7c11fa70..d0d8f9822d 100644 --- a/tests/corekit/test_fields_copy_dispatch_unit.py +++ b/tests/corekit/test_fields_copy_dispatch_unit.py @@ -51,8 +51,8 @@ class FieldCallUsesCopyDunderDirectlyTests(unittest.TestCase): Rather than :func:`copy.copy(self) `. Each test patches :func:`copy.copy` and :meth:`FieldBase.__copy__` with wrappers that still do - the real work (``side_effect=`` the original), so the field returned is - exactly what it always was; only the call counts are new. + the real work (``side_effect=`` the original), so the field returned is the + real one; the wrappers only count the calls. """ diff --git a/tests/corekit/test_fields_field.py b/tests/corekit/test_fields_field.py index 34ec2444b3..58e8c1d13f 100644 --- a/tests/corekit/test_fields_field.py +++ b/tests/corekit/test_fields_field.py @@ -642,7 +642,7 @@ class FieldBaseShortReadPaddingSideTests(unittest.TestCase): around preserving it, and third-party PR #571 was declined for breaking it. So the cases that the option and list loops depend on -- above all a read against an entirely empty buffer, which pads to all zeros either way -- are - asserted here too, unchanged. + asserted here too. """ @@ -922,8 +922,8 @@ class FieldBaseLengthNegativeResolvedLengthTests(unittest.TestCase): can resolve to that negative remainder. :class:`~pcapkit.corekit.fields. strings._TextField.__call__` then builds a template such as ``'-5s'`` from it with no lower bound, and :func:`struct.calcsize` cannot size - that -- pre-fix this surfaced as a bare, uncatchable :exc:`struct.error` - (GitHub issue #805). + that -- without the fix this surfaces as a bare, uncatchable + :exc:`struct.error` (GitHub issue #805). """ @@ -941,9 +941,9 @@ def setUp(self) -> None: def test_a_negative_resolved_length_raises_protocolerror_not_structerror(self) -> None: """The crash reproduction from #805, isolated to the field property. - Pre-fix, ``struct.error: bad char in struct format`` -- a bare - stdlib exception, not even a :exc:`ValueError`, uncatchable by - ordinary caller code -- escaped here. This is the exact shape + Without the fix, ``struct.error: bad char in struct format`` -- a + bare stdlib exception, not even a :exc:`ValueError`, uncatchable by + ordinary caller code -- escapes here. This is the exact shape HTTP/2's ``GoawayFrame.debug``/``PushPromiseFrame.fragment`` fields hit once their fixed-width siblings have already driven ``pkt['__length__']`` negative. @@ -962,9 +962,9 @@ def test_a_negative_resolved_length_raises_protocolerror_not_structerror(self) - def test_a_zero_or_positive_resolved_length_is_unaffected(self) -> None: """The fix must not change the answer for any non-negative length. - The property is a thin ``try/except`` around the same - :func:`struct.calcsize` call as before; a resolved length that never - raises must return exactly what it always returned. + The property is a thin ``try/except`` around + :func:`struct.calcsize`; a resolved length that does not raise + must return exactly what :func:`struct.calcsize` does. """ for resolved in (0, 1, 5, 1024): with self.subTest(resolved=resolved): @@ -978,10 +978,11 @@ def test_a_malformed_template_raises_protocolerror_naming_the_template(self) -> :func:`struct.calcsize` raises the identical bare :exc:`struct.error` -- ``bad char in struct format`` -- for a malformed template as it does for a negative one (measured on 3.14.7: ``calcsize('-1s')`` and - ``calcsize('Xs')`` both raise it). Pre-fix, :attr:`FieldBase.length - ` caught that blanket - and always reported "resolved to a negative length", which would have - misdiagnosed this case. The template here (``'Xs'``) never matches + ``calcsize('Xs')`` both raise it). Without the fix, + :attr:`FieldBase.length + ` catches that blanket + and always reports "resolved to a negative length", which + misdiagnoses this case. The template here (``'Xs'``) never matches the leading-minus-sign shape a resolved negative count takes, so it must fall to the distinct, template-naming message instead. """ diff --git a/tests/corekit/test_fields_ipaddress.py b/tests/corekit/test_fields_ipaddress.py index 61a3eefc4f..1c55b345e9 100644 --- a/tests/corekit/test_fields_ipaddress.py +++ b/tests/corekit/test_fields_ipaddress.py @@ -446,9 +446,9 @@ def test_parse_ip_address_returns_an_ipaddress_object_unchanged(self) -> None: def test_both_bool_guards_stay_catchable_as_value_error_and_as_base_error(self) -> None: """The two exception classes used for this mistake are interchangeable to callers. - #508's seven sites answer with :exc:`FieldValueError`, because the rejection - happens in a field-level conversion; the two older hand-rolled guards for - the same mistake -- ``MH._make_opt_mn_id`` from #481 and ESP's + #508's sites answer with :exc:`FieldValueError`, because the rejection + happens in a field-level conversion; the hand-rolled guards for the same + mistake -- ``MH._make_opt_mn_id`` from #481 and ESP's ``SecurityAssociation`` from #491 -- answer with :exc:`~pcapkit.utilities.exceptions.ProtocolError`, because they answer for the option rather than for a field. That inconsistency is deliberate and @@ -512,7 +512,7 @@ def test_both_bool_guards_stay_catchable_as_value_error_and_as_base_error(self) self.assertTrue(issubclass(cls, ValueError)) def test_switch_backed_address_makers_reject_a_bool(self) -> None: - """#508's remaining sites, for the two protocols outside :mod:`~pcapkit.protocols.internet.mh`. + """#508's remaining sites, for the protocols outside :mod:`~pcapkit.protocols.internet.mh`. ``HIP``'s locator and ``TCP``'s Multipath ``ADD_ADDR`` address are both backed by a :class:`~pcapkit.corekit.fields.misc.SwitchField`, and both @@ -603,7 +603,7 @@ def test_ipv4_interface_post_process_keeps_a_non_contiguous_netmask(self) -> Non malformed or corrupted IPv4 interface option can carry ``0.255.0.255``, which :func:`ipaddress.ip_interface` rejects. The field keeps such a mask as given, so the value packs back to the same - octets (c.f. :issue:`1294`). + octets (c.f. #1294). """ from pcapkit.corekit.fields.ipaddress import IPv4InterfaceField diff --git a/tests/corekit/test_fields_misc.py b/tests/corekit/test_fields_misc.py index c2af744a52..1e69d21b18 100644 --- a/tests/corekit/test_fields_misc.py +++ b/tests/corekit/test_fields_misc.py @@ -69,12 +69,12 @@ def discover_address_typed_switches() -> 'set[tuple[str, str, str]]': """Find every address-typed :class:`SwitchField` declaration in the schema tree. Walks :attr:`Schema.__fields__` for every :class:`Schema` subclass rather than - grepping annotations, because three of the switches -- + grepping annotations, because some of the switches -- ``mh.BindingIdentifierOption.address`` and both ``SMFIdentificationBasedDPDOption.tid`` -- are wrapped in a :class:`~pcapkit.corekit.fields.misc.ConditionalField` and so are invisible to - a grep for a ``SwitchField`` annotation. #508's own table listed seven for - exactly that reason; there are ten. + a grep for a ``SwitchField`` annotation. #508's own table missed them for + exactly that reason. Returns: ``(module, class, attribute)`` for each distinct declaration. Declarations @@ -248,9 +248,9 @@ def test_the_address_typed_switch_table_is_complete(self) -> None: The table above is hand-written, so on its own it proves nothing about switches nobody listed -- which is the failure mode #491 and #508 share. - Rediscovering them from ``Schema.__fields__`` and comparing closes that: - ten declarations, three of which a ``SwitchField`` annotation grep cannot - see because they are ``ConditionalField``-wrapped. + Rediscovering them from ``Schema.__fields__`` and comparing closes that, + including the declarations a ``SwitchField`` annotation grep cannot see + because they are ``ConditionalField``-wrapped. """ discovered = discover_address_typed_switches() tabled = {(module, name, attr) @@ -263,7 +263,7 @@ def test_the_address_typed_switch_table_is_complete(self) -> None: f'only tabled: {sorted(tabled - discovered)}') self.assertEqual(len(discovered), 10) - # and the three that an annotation grep misses really are wrapped + # and the ones an annotation grep misses really are wrapped from pcapkit.corekit.fields.misc import ConditionalField wrapped = {(module, name, attr) @@ -447,11 +447,11 @@ def test_a_protocol_name_resolves_whatever_its_case(self) -> None: def test_an_unregistered_name_warns_and_still_falls_back_to_raw(self) -> None: """A name the registry does not hold is a caller's mistake, so it warns. - The fallback itself is kept: refusing the assignment would reject a - lenient spelling the field has always accepted, and :obj:`None` -- hence + The fallback itself stays: refusing the assignment would reject a + lenient spelling the field accepts, and :obj:`None` -- hence :class:`~pcapkit.protocols.misc.raw.Raw` -- remains a legitimate state - for a payload whose protocol is genuinely not known. What was wrong was - only that nothing said so. + for a payload whose protocol is genuinely not known. The warning is what + tells the caller. """ from pcapkit.corekit.fields.misc import PayloadField diff --git a/tests/corekit/test_fields_misc_packet_context.py b/tests/corekit/test_fields_misc_packet_context.py index 568f94c3ad..cfca15ab6a 100644 --- a/tests/corekit/test_fields_misc_packet_context.py +++ b/tests/corekit/test_fields_misc_packet_context.py @@ -129,8 +129,8 @@ def test_nested_schema_reads_enclosing_field_by_name_and_does_not_leak_writes(se raw = b'\xaa\x03\x22\xbb\xcc' outer = Outer.unpack(raw, len(raw), None) - # The bug, fixed: 'length' is not Inner's own field, and resolves to - # Outer's by falling through rather than raising KeyError. + # 'length' is not Inner's own field, and resolves to Outer's by + # falling through rather than raising KeyError (#445). self.assertEqual(outer.inner.body, b'\xbb\xcc') self.assertEqual(captured['fallback_length'], 3) @@ -173,7 +173,7 @@ def test_nested_schema_reads_enclosing_field_by_name_and_does_not_leak_writes(se self.assertNotIn('body', parent) def test_pcapng_byteorder_consumer_still_works_with_both_shapes(self) -> None: - """The one existing, hand-rolled ``__packet__`` fallback is unaffected. + """A hand-rolled ``__packet__`` fallback works with both shapes. :func:`~pcapkit.corekit.fields.byteorder.packet_byteorder` is called both with a plain dict built by hand -- as several tests and @@ -188,7 +188,7 @@ def test_pcapng_byteorder_consumer_still_works_with_both_shapes(self) -> None: outer = {'byteorder': 'little'} - # Hand-built, the shape every existing caller uses. + # Hand-built, as those callers build it. self.assertEqual(packet_byteorder({'__packet__': outer}), 'little') # What SchemaField hands a real nested schema. self.assertEqual(packet_byteorder(nested_packet_context(outer)), 'little') @@ -236,8 +236,8 @@ class CGAParametersRegressionTests(unittest.TestCase): A CGA Parameters option could not be parsed at all: sizing ``CGAParameter.extensions`` (``pcapkit/protocols/schema/internet/mh.py``, ``CGAParameter.extensions``) reads ``pkt['length']``, a name only the - enclosing ``CGAParametersOption`` declares. No change to ``mh.py`` itself - was needed -- the fallback in ``nested_packet_context`` resolves it. + enclosing ``CGAParametersOption`` declares. ``mh.py`` needs nothing of + its own: the fallback in ``nested_packet_context`` resolves it. """ @@ -258,11 +258,10 @@ def test_cga_parameters_option_now_parses_end_to_end(self) -> None: ) self.assertEqual(len(raw), 40) - # #446 (ForwardMatchField counted into Schema.__len__, fixed on - # #456/main) was a second, separate defect blocking this exact - # option: with #445 alone, this reached FieldValueError: Field - # parameters has invalid length rather than parsing. With both - # fixes applied, CGA Parameters parses end to end. + # Parsing end to end also needs #446's fix (ForwardMatchField + # counted into Schema.__len__, #456): with #445's alone, this + # raises FieldValueError: Field parameters has invalid length + # rather than parsing. m = MH(raw, len(raw), extension=True) option = m.info.options[Enum_Option.CGA_Parameters] parameter = option.parameters[0] diff --git a/tests/corekit/test_fields_numbers.py b/tests/corekit/test_fields_numbers.py index cfbec8a615..31484da6c0 100644 --- a/tests/corekit/test_fields_numbers.py +++ b/tests/corekit/test_fields_numbers.py @@ -13,11 +13,11 @@ RUNTIME_DEPS = ('tbtrim', 'aenum', 'chardet', 'dictdumper') HAS_RUNTIME = all(importlib.util.find_spec(name) is not None for name in RUNTIME_DEPS) -#: The eight :class:`~pcapkit.corekit.fields.numbers.NumberField` subclasses that -#: fix their sign through ``__signed__``, each paired with the sign it fixes. -#: Derived by introspection in :meth:`FixedSignTests.test_the_census_is_complete` -#: rather than only written down here, so a ninth subclass added tomorrow fails -#: that test instead of quietly escaping every other test in this module. +#: The :class:`~pcapkit.corekit.fields.numbers.NumberField` subclasses that fix +#: their sign through ``__signed__``, each paired with the sign it fixes. Derived +#: by introspection in :meth:`FixedSignTests.test_the_census_is_complete` rather +#: than only written down here, so a subclass added later fails that test instead +#: of quietly escaping every other test in this module. FIXED_SIGN = { 'Int8Field': True, 'Int16Field': True, 'Int32Field': True, 'Int64Field': True, 'UInt8Field': False, 'UInt16Field': False, 'UInt32Field': False, 'UInt64Field': False, @@ -29,7 +29,7 @@ class FixedSignTests(unittest.TestCase): """``signed=`` on a subclass whose ``__signed__`` fixes the sign. GitHub issue #545. The argument was accepted, documented on every one of - these eight classes, and then discarded -- in *both* directions, so + these classes, and then discarded -- in *both* directions, so ``UInt8Field(signed=True)._signed`` was :data:`False` and ``Int8Field(signed=False)._signed`` was :data:`True`. A caller who asked for a signed field got unsigned parsing with no warning, and the values only @@ -140,7 +140,7 @@ def test_the_base_class_still_honours_signed(self) -> None: self.assertEqual(NumberField(length=4, signed=False).template, '>I') def test_the_base_class_defaults_to_unsigned(self) -> None: - """:data:`None` is the new default and has to mean what ``False`` meant.""" + """:data:`None`, the default, has to mean unsigned, as ``False`` does.""" from pcapkit.corekit.fields.numbers import NumberField self.assertIs(NumberField(length=4)._signed, False) diff --git a/tests/corekit/test_fields_numbers_bit_length_1488_unit.py b/tests/corekit/test_fields_numbers_bit_length_1488_unit.py index ef6ac4ae81..956043adb7 100644 --- a/tests/corekit/test_fields_numbers_bit_length_1488_unit.py +++ b/tests/corekit/test_fields_numbers_bit_length_1488_unit.py @@ -120,7 +120,7 @@ class T(Schema): class TestBitLengthSuppliedValue(unittest.TestCase): - """A value a caller supplies is truncated to ``bit_length`` bits, as before.""" + """A value a caller supplies is truncated to ``bit_length`` bits.""" def setUp(self) -> None: reimport_once_per_class(self) diff --git a/tests/corekit/test_fields_numbers_callable_length.py b/tests/corekit/test_fields_numbers_callable_length.py index 3d75358d46..4666df33d3 100644 --- a/tests/corekit/test_fields_numbers_callable_length.py +++ b/tests/corekit/test_fields_numbers_callable_length.py @@ -186,12 +186,12 @@ def test_the_placeholder_itself_still_needs_processing(self) -> None: def test_an_unresolved_field_repairs_its_length_and_honours_the_new_template(self) -> None: """``pre_process``'s ``_length < 0`` safety net, kept consistent. - A field packed without having been resolved falls into the repair at - ``numbers.py``, which recomputes the length from the value and rebuilds - the template. That rebuild can land on a native width, and before the - fix the method went on to return :obj:`bytes` anyway -- the same - mismatch as #591, one branch further down. Widths with no native code - still come back as :obj:`bytes`. + A field packed without having been resolved falls into + ``pre_process``'s repair, which recomputes the length from the value + and rebuilds the template. That rebuild can land on a native width, and + without the fix the method goes on to return :obj:`bytes` anyway -- the + same mismatch as #591, one branch further down. Widths with no native + code still come back as :obj:`bytes`. """ from pcapkit.corekit.fields.numbers import NumberField @@ -255,7 +255,7 @@ def test_an_enum_field_with_a_callable_length_packs(self) -> None: (1 << 4).to_bytes(width, 'big')) def test_a_subclass_fixing_a_template_keeps_working_either_way(self) -> None: - """The control on the other side: these were never broken, and still are not. + """The control on the other side: these do not latch, with or without the fix. ``UInt32Field`` and friends fix ``__template__``, so ``__init__`` skips ``build_template`` entirely and the placeholder never latched anything. diff --git a/tests/corekit/test_fields_numbers_negative_length.py b/tests/corekit/test_fields_numbers_negative_length.py index 69654b151c..0237bceedd 100644 --- a/tests/corekit/test_fields_numbers_negative_length.py +++ b/tests/corekit/test_fields_numbers_negative_length.py @@ -297,15 +297,15 @@ def test_bit_length_supplied_plus_negative_callable_length_matches_the_unsupplie """The second half of #831: one wire condition, one message. No in-tree call site combines a fixed ``bit_length`` with a callable - ``length`` -- ``vlan.py``'s three ``bit_length`` fields all take - positive literal ``length`` -- so this is a constructed reproduction, - not a real wire shape. Without the fix, #829's guard in ``__call__`` - sits inside ``if new_self._bit_length < 0:`` and this combination - never enters that branch, so no ``ProtocolError`` fires there at all; - the field instead degrades to ``template='>-1s'`` and the error is + ``length`` -- ``vlan.py``'s ``bit_length`` fields all take positive + literal ``length`` -- so this is a constructed reproduction, not a + real wire shape. Without the fix, #829's guard in ``__call__`` sits + inside ``if new_self._bit_length < 0:`` and this combination never + enters that branch, so no ``ProtocolError`` fires there at all; the + field instead degrades to ``template='>-1s'`` and the error is deferred to :attr:`~pcapkit.corekit.fields.field.FieldBase.length`, - which reports ``template='>-1s'`` rather than ``length=-1`` -- - a different message for the same underlying defect. + which reports ``template='>-1s'`` rather than ``length=-1`` -- a + different message for the same underlying defect. """ from pcapkit.corekit.fields.numbers import NumberField diff --git a/tests/corekit/test_fields_numbers_port_option_no_mint_unit.py b/tests/corekit/test_fields_numbers_port_option_no_mint_unit.py index cc974c2458..658da7a1f7 100644 --- a/tests/corekit/test_fields_numbers_port_option_no_mint_unit.py +++ b/tests/corekit/test_fields_numbers_port_option_no_mint_unit.py @@ -15,19 +15,19 @@ ephemeral TCP ports with no IANA assignment, and measured at 13-17% of extraction self time. -Each of them falls through to -:meth:`EnumField._unregistered_member ` -instead of minting: an actual instance of the registry the field names -- -``isinstance`` holds, and it renders and dispatches exactly like a declared -member -- built by calling the registry's own storage base's ``__new__`` -directly and skipping the registry's own ``__new__`` (and the -``cls.__registry__.add(...)`` / ``cls.__members_ns__[...]`` line inside it) -entirely, so nothing is ever added to any of its lookup tables. That is the -owner's ruling on this issue's return type: it is to apply to every other -enumeration's legitimate but unbounded values as well, so that no registered -enumeration member is created out of an unrecognised or unregistered value, -unless the user or caller explicitly created it. That is tracked more broadly -as #775, and applied here only to these four call sites. +Each of them falls through to :meth:`EnumField._unregistered_member +` instead of +minting: an actual instance of the registry the field names -- ``isinstance`` +holds, and it renders and dispatches exactly like a declared member -- built by +calling the registry's own storage base's ``__new__`` directly and skipping the +registry's own ``__new__`` (and the ``cls.__registry__.add(...)`` / +``cls.__members_ns__[...]`` line inside it) entirely, so nothing is ever added +to any of its lookup tables. That is the owner's ruling on this issue's return +type: it is to apply to every other enumeration's legitimate but unbounded +values as well, so that no registered enumeration member is created out of an +unrecognised or unregistered value, unless the user or caller explicitly +created it. That is tracked more broadly as #775, and applied here only to +these call sites. This mirrors :mod:`tests.corekit.test_fields_numbers_unassigned_enum`'s shape: the field is tested directly, in isolation from extraction, and the "still @@ -171,10 +171,10 @@ def test_a_value_lookup_on_the_resolved_port_still_raises(self) -> None: """The member is absent from ``_value2member_map_``, so looking the same port back up *by value*, on the same per-transport class the field resolved it against -- rather than reading the attributes off - the object already in hand -- still raises, exactly as it did before - this member existed. Nothing on the parse or reconstruction path - does this to a value it just resolved; this pins that the registry - itself has not changed underneath that assumption. + the object already in hand -- still raises, as it does for any value + the registry does not hold. Nothing on the parse or reconstruction + path does this to a value it just resolved; this pins that the + registry itself has not changed underneath that assumption. A direct call to ``.get()`` is different on purpose and stays that way: the owner's ruling and the docstring above are both explicit @@ -365,14 +365,14 @@ def test_the_resolved_pseudo_member_repacks_to_the_octets_it_came_from(self) -> class UnregisteredMemberStorageBaseTests(unittest.TestCase): """``_unregistered_member``'s ``int`` branch and its ``TypeError`` guard. - Neither is reachable from the four call sites this issue touches: + Neither is reachable from the call sites this issue touches: :class:`~pcapkit.const.reg.apptype.AppType` and - :class:`~pcapkit.const.pcapng.option_type.OptionType` are both - ``StrEnum``, so all four take the :class:`str` branch. The method is - written for registries in general, though, and an untested branch in a - fallback is the kind that turns out to be wrong the first time something - needs it -- so both are driven directly here, against registries standing - in for the ones a future caller would hand it. + :class:`~pcapkit.const.pcapng.option_type.OptionType` are both ``StrEnum``, so + every one of them takes the :class:`str` branch. The method is written for + registries in general, though, and an untested branch in a fallback is the + kind that turns out to be wrong the first time something needs it -- so both + are driven directly here, against registries standing in for the ones a future + caller would hand it. """ @@ -437,12 +437,12 @@ class UnregisteredMemberRoundTripTests(unittest.TestCase): """:mod:`pickle` and :mod:`copy` of a resolved unassigned port. ``Enum.__reduce_ex__`` reduces a member to ``(cls, (value,))``, i.e. to the - one lookup an unregistered member is deliberately absent from. Before this - issue's fix the port fields minted, so the member *was* registered and a - round-trip worked; without the ``__reduce_ex__`` + one lookup an unregistered member is deliberately absent from. A minted + member *is* registered, so its round-trip works; without the + ``__reduce_ex__`` :meth:`~pcapkit.corekit.fields.numbers.EnumField._unregistered_member` - installs, removing the mint would have taken that away -- and taken it away - on read-back, since ``pickle.dumps`` succeeds either way. These pin the + installs, not minting would take that away -- and take it away on + read-back, since ``pickle.dumps`` succeeds either way. These pin the mitigation, and that it does not put the mint back. """ @@ -488,12 +488,12 @@ def test_a_pickle_round_trip_rebuilds_an_equivalent_unregistered_member(self) -> def test_copy_and_deepcopy_survive_without_the_3_11_enum_shim(self) -> None: """CPython's :mod:`enum` grew ``Enum.__copy__``/``__deepcopy__``, both - returning ``self`` without any lookup, in 3.11; on 3.10 -- still a - supported version here -- neither exists and both fall through to - ``__reduce_ex__``. :class:`aenum.Enum` subclasses stdlib - :class:`enum.Enum` and defines only ``__reduce_ex__`` of its own, so - deleting those two off :class:`enum.Enum` is what 3.10 looks like from - here; there is no 3.10 interpreter on this host to run instead. + returning ``self`` without any lookup, in 3.11; below it neither + exists and both fall through to ``__reduce_ex__``. + :class:`aenum.Enum` subclasses stdlib :class:`enum.Enum` and defines + only ``__reduce_ex__`` of its own, so deleting those two off + :class:`enum.Enum` is what those versions look like from here, + whichever interpreter runs the test. """ member = self._resolved() diff --git a/tests/corekit/test_fields_numbers_unassigned_enum.py b/tests/corekit/test_fields_numbers_unassigned_enum.py index 84115315dd..6458d35660 100644 --- a/tests/corekit/test_fields_numbers_unassigned_enum.py +++ b/tests/corekit/test_fields_numbers_unassigned_enum.py @@ -1,7 +1,7 @@ -"""An unassigned enumeration value raised :mod:`aenum`'s own ``ValueError``. +"""An unassigned enumeration value must not raise :mod:`aenum`'s own ``ValueError``. -GitHub issue #701. :meth:`EnumField.post_process -` resolved a wire value +GitHub issue #701. Without the fix, :meth:`EnumField.post_process +` resolves a wire value through the *constructor* of its registry:: return self._namespace(value) @@ -15,21 +15,20 @@ tell it from a bug of its own, nor an :exc:`EOFError`, so :meth:`Extractor.record_frames ` does not catch it. One -unassigned code therefore cost the whole extraction. +unassigned code therefore costs the whole extraction. -It also made the "unknown" reader every one of these formats requires +It also makes the "unknown" reader every one of these formats requires unreachable. PCAP-NG's :class:`~pcapkit.protocols.schema.misc.pcapng.UnknownBlock` exists, its -``__default__`` selects it, and -:meth:`PCAPNG._read_block_unknown ` -reads it -- but the lookup failed several frames before the dispatch that would -have chosen it, so the default only ever fired for the ``Reserved_*`` ranges -:meth:`BlockType._missing_ ` -auto-extends. PCAP-NG repeats a block's total length at both ends precisely so a -reader can skip a block type it does not recognise; that skip is what the fix -restores. +``__default__`` selects it, and :meth:`PCAPNG._read_block_unknown +` reads it -- but the +lookup fails several frames before the dispatch that would choose it, so the +default only ever fires for the ``Reserved_*`` ranges :meth:`BlockType._missing_ +` auto-extends. PCAP-NG +repeats a block's total length at both ends precisely so a reader can skip a +block type it does not recognise; that skip is what the fix restores. -Three things about this suite are deliberate. +These things about this suite are deliberate. **The field layer is tested separately from the extraction.** The defect has two faces -- a foreign exception out of @@ -39,11 +38,10 @@ :class:`UnassignedBlockTypeExtractionTests` pins the consequence the issue was filed about. -**What must go on raising is asserted too.** The change is at the *field* layer, -so every registry's own guard has to be exactly as it was -- a registry that -declares a width and rejects outside it, as -:class:`pcapkit.const.tcp.flags.Flags` does over 16 bits, is making a decision -this layer is in no position to overrule. +**What must go on raising is asserted too.** The fallback is at the *field* +layer, so every registry's own guard has to hold -- a registry that declares a +width and rejects outside it, as :class:`pcapkit.const.tcp.flags.Flags` does +over 16 bits, is making a decision this layer is in no position to overrule. :meth:`UnassignedEnumFieldTests.test_the_registry_own_guard_is_not_weakened` and :meth:`UnassignedEnumFieldTests.test_an_in_library_rejection_is_not_absorbed` are what distinguish the fix from a blanket ``except ValueError: pass``; the @@ -161,7 +159,7 @@ def test_the_resolved_value_repacks_to_the_octets_it_came_from(self) -> None: self.assertEqual(field.pack(resolved, dict()), UNASSIGNED_BLOCK_OCTETS) def test_a_declared_member_still_resolves_to_the_registry_member(self) -> None: - """The 99.99% path is untouched, and identity proves it. + """The declared-member path returns the member itself, and identity proves it. ``assertIs`` rather than ``assertEqual``: a pseudo-member with the same value compares equal to the real one, so equality would pass against a @@ -207,13 +205,13 @@ def test_a_missing_rule_still_takes_precedence_over_the_fallback(self) -> None: self.assertEqual(int(resolved), 0x0bad0bad) def test_the_registry_own_guard_is_not_weakened(self) -> None: - """The change is at the field layer and nowhere else. + """The fallback is at the field layer and nowhere else. A registry rejecting a value is a statement about that registry, and callers -- including the generated ``get()`` -- rely on it. The fallback lives in the field, which knows the value arrived in a field of a fixed width; it does not reach into the registry, so both of these go on - raising exactly as before. + raising. """ from pcapkit.const.pcapng.block_type import BlockType @@ -282,12 +280,12 @@ def _missing_(cls, value: 'int') -> 'AenumBounded': field.unpack(b'\x63', dict()) def test_a_field_with_no_registry_at_all_is_unchanged(self) -> None: - """The branch the fallback reuses still behaves as it did. + """The branch the fallback reuses: a field with no registry. - A namespace-less :class:`~pcapkit.corekit.fields.numbers.EnumField` has - always produced this nameless member; the fix reuses the same mechanism - rather than inventing a second shape, so an unassigned code and a - registry-less field answer alike. + A namespace-less :class:`~pcapkit.corekit.fields.numbers.EnumField` produces + this nameless member; the fix reuses the same mechanism rather than + inventing a second shape, so an unassigned code and a registry-less field + answer alike. """ from pcapkit.corekit.fields.numbers import EnumField @@ -299,7 +297,7 @@ def test_a_field_with_no_registry_at_all_is_unchanged(self) -> None: self.assertEqual(resolved.name, '') def test_bit_length_masking_still_precedes_the_lookup(self) -> None: - """``post_process`` masks before it resolves, and still does. + """``post_process`` masks before it resolves. A bit-field's value is the masked one, so the registry must be asked about the masked value rather than about the octet it was carved out of. @@ -427,14 +425,14 @@ def test_an_unassigned_block_type_no_longer_costs_the_extraction(self) -> None: os.unlink(reported) def test_the_unknown_block_reader_is_reachable_for_an_unassigned_code(self) -> None: - """``UnknownBlock`` was unreachable, and this is the dispatch that proves it. - - Both halves of the dispatch were always in place -- ``__block__`` falls - back to ``'unknown'`` for a code it does not register, and the block-type - schema registry's ``__default__`` selects - :class:`~pcapkit.protocols.schema.misc.pcapng.UnknownBlock`. Neither was - reachable for a genuinely unassigned code, because the enum lookup failed - first. With the code resolving, both answer. + """``UnknownBlock`` is reachable, and this is the dispatch that proves it. + + Both halves of the dispatch are in place -- ``__block__`` falls back to + ``'unknown'`` for a code it does not register, and the block-type schema + registry's ``__default__`` selects + :class:`~pcapkit.protocols.schema.misc.pcapng.UnknownBlock`. Without the + fix neither is reachable for a genuinely unassigned code, because the + enum lookup fails first. With the code resolving, both answer. """ from pcapkit.const.pcapng.block_type import BlockType diff --git a/tests/corekit/test_fields_numbers_width_repair.py b/tests/corekit/test_fields_numbers_width_repair.py index 3a2cdd956f..a9016bfee5 100644 --- a/tests/corekit/test_fields_numbers_width_repair.py +++ b/tests/corekit/test_fields_numbers_width_repair.py @@ -11,7 +11,7 @@ for every value whose bit length is *not* an exact multiple of eight. The intended expression is ``math.ceil(value.bit_length() / 8)``. -Two things about this suite are deliberate. +These things about this suite are deliberate. **The boundaries are tested one method each, not one parametrised sweep.** The defect is a pattern rather than an instance -- it fails at *every* octet boundary @@ -313,7 +313,7 @@ class WidthRepairSurroundingContractTests(unittest.TestCase): """The rest of the field's contract that the repair leans on. Each case here pins a statement or branch of ``numbers.py`` that the repair - depends on and that nothing reached before, which is why they live with the + depends on and that nothing else reaches, which is why they live with the #599 suite rather than apart from it. """ diff --git a/tests/corekit/test_io.py b/tests/corekit/test_io.py index 063fd4d2b7..bc6e6e40b7 100644 --- a/tests/corekit/test_io.py +++ b/tests/corekit/test_io.py @@ -78,11 +78,10 @@ def test_truncate_refuses_on_a_stream_that_reports_itself_unwritable(self) -> No :meth:`io.IOBase.writable` documents the gate for both methods it guards -- "If :data:`False`, :meth:`write` and :meth:`truncate` will raise :exc:`OSError`" -- and - :meth:`~pcapkit.corekit.io.SeekableReader.writable` returns :data:`False` here. Two of - the three writability-gated methods already honoured that: ``write`` and ``writelines`` - raise. ``truncate`` returned its new size instead, so a caller that checked - ``writable()`` first -- which is exactly what the contract invites -- got a surprise - either way round. + :meth:`~pcapkit.corekit.io.SeekableReader.writable` returns :data:`False` here. ``write`` + and ``writelines`` honour that and raise. Without the fix ``truncate`` returns its new + size instead, so a caller that checks ``writable()`` first -- which is exactly what the + contract invites -- gets a surprise either way round. Every form is asserted, since the omitted-size form takes a different path through the method than an explicit size and only the explicit one would have been noticed. @@ -185,8 +184,7 @@ def test_writable_is_the_method_the_io_protocol_consults(self) -> None: reader = self.SeekableReader(io.BytesIO(b'abcdef'), buffer_size=4) # NOTE: the reader genuinely cannot write -- ``write`` raises -- so False is the honest - # answer, and the reporting was never the dishonest half. Only where it was *defined* - # was wrong, and the guard on ``truncate`` was missing. + # answer. What #645 is about is where it is *defined*, and the guard on ``truncate``. self.assertFalse(reader.writable()) self.assertFalse(hasattr(reader, 'writeable')) self._close_reader(reader) @@ -272,8 +270,8 @@ def test_seek_variants_warnings_and_truncate_sizes(self) -> None: self.assertTrue(reader.seekable()) self.assertFalse(reader.writable()) # NOTE: an omitted size means the current position, per :meth:`io.IOBase.truncate`. - # The position is 6 and the buffer starts at 2, so 4 octets of it are kept. This - # asserted 0 until issue #622, which is what an omitted size was resized to. + # The position is 6 and the buffer starts at 2, so 4 octets of it are kept, not the + # 0 an omitted size is resized to without issue #622's fix. self.assertEqual(reader._buffer_set, 2) self.assertEqual(reader._truncate_buffer(None), 4) self.assertEqual(reader._truncate_buffer(6), 6) @@ -538,11 +536,11 @@ def test_truncate_buffer_to_nothing_leaves_the_reader_usable(self) -> None: def test_buffered_read_returns_every_buffered_octet(self) -> None: """A read served from the buffer stops at the content, not one octet short of it. - The existing round trip at :meth:`test_buffered_readline_read_read1_and_peek_paths` - cannot see this: it seeks to 1, where ``_buffer_cur - 1`` happens to equal the - octets actually available, so the two candidate caps agree. Seeking to 0 separates - them -- four octets are available and, without the fix, the cap allows three, - making up the fourth from the stream *past* the octet it skipped. + The round trip at :meth:`test_buffered_readline_read_read1_and_peek_paths` cannot + see this: it seeks to 1, where ``_buffer_cur - 1`` happens to equal the octets + actually available, so the two candidate caps agree. Seeking to 0 separates them + -- four octets are available and, without the fix, the cap allows three, making up + the fourth from the stream *past* the octet it skipped. """ reader = self.SeekableReader(io.BytesIO(b'abcde'), buffer_size=8) @@ -846,14 +844,14 @@ def test_an_unbounded_buffered_readline_still_finishes_the_line(self) -> None: def test_the_window_base_and_content_pointer_track_the_stream_consumption(self) -> None: """``_buffer_set + _buffer_cur`` is how far the stream has been read, always. - This is the invariant #633's first revision broke, turning a loud - :exc:`ValueError` into silent data corruption: :meth:`seek` reads the pair as the - stream's consumption point and reads ahead from it to fill a gap, so a pair that - under-reports makes the next forward seek splice octets in from the wrong absolute - offset without complaining. It is asserted directly here, against a stream that - counts what has actually been taken off it, after *every* operation rather than at - the end -- an output assertion can pass while the bookkeeping behind it is already - wrong, which is exactly how that revision got as far as review. + This is the invariant a draft of #633 broke, turning a loud :exc:`ValueError` into + silent data corruption: :meth:`seek` reads the pair as the stream's consumption + point and reads ahead from it to fill a gap, so a pair that under-reports makes + the next forward seek splice octets in from the wrong absolute offset without + complaining. It is asserted directly here, against a stream that counts what has + actually been taken off it, after *every* operation rather than at the end -- an + output assertion can pass while the bookkeeping behind it is already wrong, which + is exactly how that draft got as far as review. Deriving the buffer's cursor from ``_tell`` at each point of use is what preserves the pair by construction: nothing clamps or adjusts either member to make a read fit, @@ -888,14 +886,14 @@ def test_a_position_the_window_has_dropped_is_refused_not_guessed(self) -> None: :meth:`_truncate_buffer` advances the window's base past the octets it drops, which can leave a position already set sitting before it. :meth:`seek` refuses that position - outright -- the stream cannot be rewound to re-supply the octets, so there is - nothing to read -- but the buffered read paths reached it too, by way of a position - set *before* the truncation rather than after, and answered from whatever offset the - cursor happened to hold. Here that returned ``b'c'``, the octet at 2, for a read at - 1. + outright -- the stream cannot be rewound to re-supply the octets, so there is nothing + to read -- but the buffered read paths reached it too, by way of a position set + *before* the truncation rather than after, and answered from whatever offset the cursor + happened to hold. Without the refusal that is ``b'c'`` here, the octet at 2, for a read + at 1. - Refusing it is the same answer :meth:`seek` already gives, and turns the last silent - wrong answer in this family into a loud one. + Refusing it is the same answer :meth:`seek` already gives, and turns a silent wrong + answer into a loud one. """ reader = self.SeekableReader(io.BytesIO(b'abcdefghijkl'), buffer_size=8) @@ -907,7 +905,7 @@ def test_a_position_the_window_has_dropped_is_refused_not_guessed(self) -> None: self.assertEqual(reader.tell(), 1) # and the position is behind it with self.assertRaises(self.exceptions.SeekError): - reader.read(1) # was b'c', the octet at 2 + reader.read(1) # not b'c', the octet at 2 with self.assertRaises(self.exceptions.SeekError): reader.seek(1) self._close_reader(reader) @@ -916,10 +914,10 @@ def test_a_zero_length_read_is_answered_without_consulting_the_window(self) -> N """A request for no octets must not be refused for having nowhere to read from. The refusal in the previous test is on the window, and the window has nothing to say - about a request that wants nothing: every one of these returned ``b''`` before the - refusal existed, and a zero-length read failing on position grounds is not a - behaviour either issue asked for. Checking the window before looking at the requested - size made all four of them raise from a position :meth:`_truncate_buffer` had stranded. + about a request that wants nothing: every one of these returns ``b''`` without the + refusal, and a zero-length read failing on position grounds is not a behaviour either + issue asked for. Checking the window before looking at the requested size would make + every one of them raise from a position :meth:`_truncate_buffer` had stranded. The same position is asserted both ways round, which is what makes this a statement about the *size* rather than about the state: at size zero all four return, and the diff --git a/tests/corekit/test_io_advance_lying_pipe_2108_unit.py b/tests/corekit/test_io_advance_lying_pipe_2108_unit.py index 3449d343f7..04d763ea21 100644 --- a/tests/corekit/test_io_advance_lying_pipe_2108_unit.py +++ b/tests/corekit/test_io_advance_lying_pipe_2108_unit.py @@ -1,12 +1,11 @@ # -*- coding: utf-8 -*- """:func:`~pcapkit.corekit.io.advance` reads past a record on a pipe that only says it can seek (#2108). -On Windows before CPython 3.16 a pipe's ``seekable()`` is :data:`True` and its -``seek`` silently does nothing (CPython gh-86768). A frame reader built directly -on a caller's stream -- ``Frame(file, ...)`` or ``PCAPNG(file, ...)`` -- hands that -stream to :func:`advance` unwrapped, which then "skipped" a record without moving. -:class:`LyingPipe` reproduces such a pipe on any platform, over a real -:func:`os.pipe`. +On Windows before CPython 3.16 a pipe's ``seekable()`` is :data:`True` and its ``seek`` +silently does nothing (CPython gh-86768). A frame reader built directly on a caller's +stream -- ``Frame(file, ...)`` or ``PCAPNG(file, ...)`` -- hands that stream to +:func:`advance` unwrapped, which without the fix "skips" a record without moving. +:class:`LyingPipe` reproduces such a pipe on any platform, over a real :func:`os.pipe`. """ diff --git a/tests/corekit/test_io_stream_proxies_1516_unit.py b/tests/corekit/test_io_stream_proxies_1516_unit.py index ca8b904f38..76458fc526 100644 --- a/tests/corekit/test_io_stream_proxies_1516_unit.py +++ b/tests/corekit/test_io_stream_proxies_1516_unit.py @@ -5,11 +5,11 @@ ``peek`` one, for :class:`~pcapkit.foundation.extraction.Extractor` (#1506, #1509). :class:`~pcapkit.corekit.io.NamedStream` gives a stream the ``name`` :func:`pcapfile.savefile.load_savefile` reads, for the PyPCAPFile engine. Both -were file-local, as ``_PeekableStream`` in :mod:`pcapkit.foundation.extraction` -and ``_NamedStream`` in :mod:`pcapkit.foundation.engines.pypcapfile`, and moved -beside :class:`~pcapkit.corekit.io.SeekableReader` unchanged. +live beside :class:`~pcapkit.corekit.io.SeekableReader` (formerly the +file-local ``_PeekableStream`` in :mod:`pcapkit.foundation.extraction` and +``_NamedStream`` in :mod:`pcapkit.foundation.engines.pypcapfile`). -Every class is imported from its new module inside each test, after +Every class is imported from :mod:`pcapkit.corekit.io` inside each test, after :func:`~tests._support.reimport_once_per_class`. """ diff --git a/tests/corekit/test_module.py b/tests/corekit/test_module.py index 525fe620a7..802263d660 100644 --- a/tests/corekit/test_module.py +++ b/tests/corekit/test_module.py @@ -133,10 +133,10 @@ def test_klass_raises_protocolerror_for_a_name_that_is_not_there(self) -> None: The fallback above swallows one :exc:`AttributeError` to retry through :func:`importlib.import_module`. A descriptor naming a class that does - not exist has to come back out of that retry naming the same missing - attribute it always did -- but as :exc:`~pcapkit.utilities.\ + not exist has to come back out of that retry naming the missing + attribute -- but as :exc:`~pcapkit.utilities.\ exceptions.ProtocolError` rather than the bare stdlib :exc:`AttributeError`, - so every one of the nine ``register_*`` call sites that build a + so every one of the ``register_*`` call sites that build a descriptor from a bad class name fails as a :mod:`pcapkit` error a caller can actually catch. GitHub issue #832. diff --git a/tests/corekit/test_schema_roundtrip_unit.py b/tests/corekit/test_schema_roundtrip_unit.py index 811bd3740e..42cd68e2e0 100644 --- a/tests/corekit/test_schema_roundtrip_unit.py +++ b/tests/corekit/test_schema_roundtrip_unit.py @@ -4,7 +4,7 @@ Each concrete :class:`~pcapkit.protocols.schema.schema.Schema` subclass under :mod:`pcapkit.protocols.schema` is enumerated, so a schema added tomorrow gets a case tomorrow. For each one a **minimal valid instance** ``s`` is built and -packed to ``b``, and four things must hold: +packed to ``b``, and these must hold: * ``Schema.unpack(b)`` succeeds and accounts for exactly ``b`` (``SELF``); * the parsed schema, packed again, is ``b`` (``REPACK``); @@ -461,7 +461,7 @@ def build(cls: 'type', depth: 'int' = 0, extra: 'Optional[dict[str, Any]]' = Non def check(cls: 'type', schema: 'Any', raw: 'bytes', ctx: 'dict[str, Any]') -> 'Outcome': - """The four assertions of the module docstring, for one built instance.""" + """The assertions of the module docstring, for one built instance.""" with warnings.catch_warnings(): warnings.simplefilter('ignore') try: diff --git a/tests/corekit/test_sentinel_exports_unit.py b/tests/corekit/test_sentinel_exports_unit.py index 6f233658ef..970ed7cff9 100644 --- a/tests/corekit/test_sentinel_exports_unit.py +++ b/tests/corekit/test_sentinel_exports_unit.py @@ -89,12 +89,12 @@ #: the distinct *consumer* modules. #: Built by :func:`_sentinels` from the live import, not at module load. -#: The public three of :data:`SENTINELS`, as ``(consumer module, object name, type -#: name)``. The consumer is the module that uses the sentinel and, per GitHub issue -#: #1744, does not re-export it. ``ABSENT`` is absent from it deliberately: it is -#: private, so it is exported neither way and the export rule does not reach it -- -#: its name carries no leading underscore (GitHub issue #937), but it is private -#: all the same. +#: The public members of :data:`SENTINELS`, as +#: ``(consumer module, object name, type name)``. The consumer is the module that +#: uses the sentinel and, per GitHub issue #1744, does not re-export it. +#: ``ABSENT`` is absent from it deliberately: it is private, so it is exported +#: neither way and the export rule does not reach it -- its name carries no +#: leading underscore (GitHub issue #937), but it is private all the same. PUBLIC_SENTINELS = ( ('pcapkit.corekit.module', 'NULL', 'NullType'), ('pcapkit.corekit.fields.field', 'NO_VALUE', 'NoValueType'), @@ -199,11 +199,11 @@ class SentinelExportTests(_ReimportCase): """``__all__`` names the object and not the type, in the canonical module only.""" def test_module_exports_neither_the_object_nor_the_type(self) -> 'None': - """``['NULL', 'ModuleDescriptor']`` lost ``NULL`` under GitHub issue #1744. + """``__all__`` is ``['ModuleDescriptor']``, without ``NULL`` (GitHub issue #1744). - ``ModuleDescriptor`` is not a sentinel and has to survive the edit, which - is asserted here rather than in a separate test because removing it is the - plausible way to get this line wrong. + ``ModuleDescriptor`` is not a sentinel and has to stay, which is asserted + here rather than in a separate test because removing it is the plausible + way to get this line wrong. """ import pcapkit.corekit.module as module @@ -230,7 +230,7 @@ def test_enum_exports_neither_the_object_nor_the_type(self) -> 'None': self.assertIn('EnumRegistry', enum.__all__) def test_field_exports_neither_the_object_nor_the_type(self) -> 'None': - """``NO_VALUE`` was added here by #911 and removed again by #1744.""" + """``NO_VALUE`` is not exported here (#1744).""" import pcapkit.corekit.fields.field as field self.assertNotIn('NO_VALUE', field.__all__) @@ -241,7 +241,7 @@ def test_star_import_binds_the_objects_and_not_the_types(self) -> 'None': """Through ``import *`` itself, which is the surface the ruling is about. The canonical module binds every public object and no sentinel's own - type; each former re-export site binds neither. + type; each consumer module binds neither. """ canonical = importlib.import_module(CANONICAL_MODULE) @@ -287,7 +287,7 @@ def test_the_private_sentinel_is_exported_neither_way(self) -> 'None': class SentinelPopulationTests(_ReimportCase): - """There are four, they follow the naming rule, and the docs say so.""" + """They follow the naming rule, and the docs say so.""" def test_every_sentinel_follows_the_naming_convention(self) -> 'None': """A sentinel object's type class is named ``Type``. @@ -418,7 +418,7 @@ def test_the_vendored_bare_object_sentinel_went_with_its_backport(self) -> 'None self.assertRegex(source, r'(?m)^from functools import cached_property$') def test_the_sentinel_table_has_a_row_per_sentinel_and_no_more(self) -> 'None': - """Counted from the table itself, so a fifth sentinel cannot be half-added.""" + """Counted from the table itself, so a new sentinel cannot be half-added.""" section = _sentinel_section() table = section[section.index('.. list-table::'):] table = table[:table.index('\n\n', table.index('- Defined in'))] @@ -432,9 +432,9 @@ class SentinelBehaviourTests(_ReimportCase): :file:`docs/source/contributing/conventions/sentinel-convention.rst` documents. - Not part of the export change, and asserted here because the doc edit that goes - with it makes claims about every sentinel -- an undocumented ``__bool__`` or a missing - ``__repr__`` would make the corrected prose wrong in a way no other test sees. + Asserted here because the convention page makes claims about every sentinel -- an + undocumented ``__bool__`` or a missing ``__repr__`` would make its prose wrong in a + way no other test sees. """ @@ -507,14 +507,13 @@ def test_no_value_reprs_as_its_own_name(self) -> 'None': class NoValueIsTheDocumentedFieldDefaultTests(_ReimportCase): """Why the ruling reaches ``NO_VALUE`` at all. - ``NO_VALUE``'s own comment in :mod:`pcapkit.corekit.fields.field` reads *"Default - value for* :attr:`FieldBase.default `*"*, - so it is the value a caller is told to compare a field's default against -- which - is what makes withholding it from ``import *`` the defect rather than a - preference. That contract had no test: the ``default`` setter and deleter were - both uncovered, and the deleter is the only code path that puts the sentinel - *back*. Named ``NoValue`` at the time #911 landed; GitHub issue #937 renamed the - object to ``NO_VALUE`` without touching this contract. + ``NO_VALUE``'s own comment in :mod:`pcapkit.corekit.fields.field` reads *"Default value + for* :attr:`FieldBase.default `*"*, so it + is the value a caller is told to compare a field's default against -- which is what makes + withholding it from ``import *`` the defect rather than a preference. The ``default`` + setter and deleter are covered here, and the deleter is the only code path that puts the + sentinel *back*. GitHub issue #937 renamed the object from ``NoValue`` to ``NO_VALUE`` + without touching this contract. :class:`~pcapkit.corekit.fields.strings.BytesField` is the concrete field under test, following :file:`tests/corekit/test_fields_field.py`: a real user-facing diff --git a/tests/corekit/test_sentinel_singleton_1842_unit.py b/tests/corekit/test_sentinel_singleton_1842_unit.py index 2d66720e63..92e48feb45 100644 --- a/tests/corekit/test_sentinel_singleton_1842_unit.py +++ b/tests/corekit/test_sentinel_singleton_1842_unit.py @@ -230,8 +230,7 @@ def test_the_shared_base_is_public_and_abstract(self) -> 'None': """Every sentinel type derives from ``Sentinel``, which is exported and not instantiable. Its metaclass derives from :class:`abc.ABCMeta`, as the library's other - abstract bases' do, and the private name it had before review is gone, - with no alias left behind. + abstract bases' do, and it keeps no private alias. """ module = _module() @@ -398,7 +397,7 @@ class Virtual: class OldPickleTests(_ReimportCase): - """A pickle written before the change still loads, as the sentinel where it can.""" + """A pickle written without the fix still loads, as the sentinel where it can.""" def test_every_old_pickle_still_loads(self) -> 'None': module = _module() @@ -433,7 +432,7 @@ def test_an_old_default_reduce_below_protocol_2_cannot(self) -> 'None': Below protocol 2 the default reduce rebuilds through :func:`copyreg._reconstructor`, which calls :meth:`object.__new__` directly. No hook on the class runs, so these load as a second instance. - Pickles written after the change reduce to the object's own name instead. + Pickles written with the fix reduce to the object's own name instead. """ module = _module() diff --git a/tests/corekit/test_sentinels_housing_unit.py b/tests/corekit/test_sentinels_housing_unit.py index 5336da6d44..65372ccc77 100644 --- a/tests/corekit/test_sentinels_housing_unit.py +++ b/tests/corekit/test_sentinels_housing_unit.py @@ -117,10 +117,10 @@ class NoImportCycleTests(unittest.TestCase): The issue's own instruction: check for a cycle *deliberately*, because :command:`pylint` already reports hundreds of ``cyclic-import`` findings in this package, so one more would be invisible in the noise rather than caught. - This does not run :command:`pylint` -- that check is done separately and - reported alongside #922 -- it instead pins the one fact that actually - rules a cycle out for this specific set of modules: that - :mod:`pcapkit.corekit.sentinels` imports none of them. + This does not run :command:`pylint` -- that check is done separately (#922) + -- it instead pins the one fact that actually rules a cycle out for this + specific set of modules: that :mod:`pcapkit.corekit.sentinels` imports none + of them. """ @@ -205,9 +205,9 @@ class SentinelsModuleExportRuleTests(unittest.TestCase): Nothing forces :mod:`pcapkit.corekit.sentinels` to honour the ruling that the sentinel objects, and not their types, are what gets exported -- the ruling - was stated about the shim locations, which #916 already fixed -- but shipping - a brand new module that violates the rule its own docstring cites would be a - strange way to land it, so this pins that it does not. + was stated about the shim locations (#916) -- but a module that violates the + rule its own docstring cites would be self-contradictory, so this pins that + it does not. It does export :class:`~pcapkit.corekit.sentinels.Sentinel`, the abstract base every sentinel type shares, since that is no sentinel's own type. diff --git a/tests/corekit/test_stringfield_lossless_unit.py b/tests/corekit/test_stringfield_lossless_unit.py index b07cef2043..bb6d992d94 100644 --- a/tests/corekit/test_stringfield_lossless_unit.py +++ b/tests/corekit/test_stringfield_lossless_unit.py @@ -104,7 +104,7 @@ def test_detected_charset_is_reused_on_pack(self) -> None: def test_undecodable_octet_round_trips_as_replacement_text(self) -> None: value, packed = self.roundtrip(b'caf\xe9', encoding='utf-8') self.assertEqual(packed, b'caf\xe9') - self.assertEqual(value, 'caf�') # the text main decoded, unchanged + self.assertEqual(value, 'caf�') # the text a plain decode gives def test_lossy_errors_scheme_still_round_trips(self) -> None: for errors in ('replace', 'ignore'): @@ -211,8 +211,8 @@ class TestStringFieldUnquote(unittest.TestCase): """``unquote=True`` decodes alike on every Python (#1693). :func:`urllib.parse.unquote` accepts :obj:`bytes` only from Python 3.9, and - every value a field unpacks is :obj:`bytes`, so on 3.8 each of these raised - ``TypeError: Expected str, got bytes``. + every value a field unpacks is :obj:`bytes`, so on 3.8 each of these raises + ``TypeError: Expected str, got bytes`` without the fix. """ def setUp(self) -> None: @@ -245,7 +245,7 @@ def test_unquote_is_the_compat_one(self) -> None: self.assertIs(strings.unquote, compat.unquote) def test_a_value_that_is_not_bytes_still_goes_to_urllib(self) -> None: - """Only :obj:`bytes` take the 3.9 path; the fix leaves anything else as it was. + """Only :obj:`bytes` take the 3.9 path; anything else goes to :mod:`urllib`. A field never unpacks one, but :meth:`post_process` is public: text is unquoted, while a :obj:`bytearray` or :obj:`memoryview` raises exactly what diff --git a/tests/dumpkit/test_common_unit.py b/tests/dumpkit/test_common_unit.py index 63537dbe37..8e2f88a2a1 100644 --- a/tests/dumpkit/test_common_unit.py +++ b/tests/dumpkit/test_common_unit.py @@ -108,7 +108,7 @@ def setUp(self) -> None: def test_dumper_subclass_registration_is_opt_in(self) -> None: """Registration happens if and only if ``fmt`` is given. - This is the #514 opt-in contract, extended to the fifth pair. It guards + This is the #514 opt-in contract, applied to this pair too. It guards against *inferring* an absent ``fmt`` from the subclass' :attr:`~dictdumper.dumper.Dumper.kind` property -- which is an instance property, so inferring it constructs an instance against a @@ -268,13 +268,13 @@ def test_make_dumper_escapes_mapping_keys_for_plist_like_output(self) -> None: ``PLIST._append_dict`` in :mod:`dictdumper.plist` writes a key as ``'{item}'.format(item=item)`` and calls ``_encode_value`` on - the *value* two lines down, never on the key -- so - :meth:`object_hook` is handed every value the writer interpolates and no - key at all, and the escaping proven above cannot reach one. Both - branches that build a mapping therefore escape their own keys: - :class:`~pcapkit.corekit.multidict.MultiDict`, where only an - enum-derived key was escaped before, and a plain :class:`dict`, where - nothing was. + the *value* two lines down, never on the key -- so :meth:`object_hook` + is handed every value the writer interpolates and no key at all, and the + escaping proven above cannot reach one. Both branches that build a + mapping therefore escape their own keys: + :class:`~pcapkit.corekit.multidict.MultiDict`, where without the fix + only an enum-derived key is escaped, and a plain :class:`dict`, where + nothing is. The :class:`bytes` key is :file:`examples/captures/test.pcapng`'s own, and it is the reason that fixture's ``plist`` report did not parse: diff --git a/tests/dumpkit/test_make_dumper_subclass_2144_unit.py b/tests/dumpkit/test_make_dumper_subclass_2144_unit.py index a249880ae4..dcfda64a60 100644 --- a/tests/dumpkit/test_make_dumper_subclass_2144_unit.py +++ b/tests/dumpkit/test_make_dumper_subclass_2144_unit.py @@ -99,7 +99,7 @@ def object_hook(self, o: 'object') -> 'object': self.assertEqual(self.hook(Sub, value), ('sub', value)) def test_unsubclassed_class_unchanged(self) -> None: - """The class ``make_dumper`` returns still falls through as before.""" + """The class ``make_dumper`` returns, unsubclassed, still falls through.""" from pcapkit.dumpkit.common import make_dumper for name, output in self.outputs(): diff --git a/tests/dumpkit/test_nameless_enum_rendering_unit.py b/tests/dumpkit/test_nameless_enum_rendering_unit.py index 3ce79dd7e7..977f2a9410 100644 --- a/tests/dumpkit/test_nameless_enum_rendering_unit.py +++ b/tests/dumpkit/test_nameless_enum_rendering_unit.py @@ -85,7 +85,7 @@ class NamelessEnumRenderingTests(unittest.TestCase): the name half. The replacement is the value's own decimal spelling -- ``'Flags::0 [0]'``, - ``'Flags::8 [8]'`` -- chosen for three reasons these tests pin: + ``'Flags::8 [8]'`` -- chosen for reasons these tests pin: * It is what the enumeration libraries themselves already use for an undeclared residue. ``Flags(2057).name`` is ``'ACK|9'``, naming the @@ -121,8 +121,8 @@ def test_scalar_return_renders_a_nameless_member_as_its_value(self) -> None: Covers the nameless zero *and* every non-zero nameless value the registry admits, because a fix that special-cased zero would pass on - ``Flags(0)`` alone and still emit ``'Flags::8 [8]'``'s predecessor - ``'Flags::None [8]'``. + ``Flags(0)`` alone and still emit ``'Flags::None [8]'`` in place of + ``'Flags::8 [8]'``. Both libraries are swept from the same derivation, so this is still the demonstration that the rendering is not an :mod:`aenum` quirk -- stdlib @@ -154,12 +154,13 @@ def test_scalar_return_renders_a_nameless_member_as_its_value(self) -> None: self.assertNotIn('None', rendered) def test_named_members_are_untouched(self) -> None: - """The control. Only the name half of a *nameless* member changes. + """The control. Only the name half of a *nameless* member gets the fallback. - ``Flags(2049)`` matters most here: its name is ``'ACK|1'``, so it was - never broken, and it is the precedent the fallback follows. If the guard + ``Flags(2049)`` matters most here: its name is ``'ACK|1'``, so it needs + no fallback, and it is the precedent the fallback follows. If the guard were written as "compose the name from the value" rather than "use the - value when there is no name", this is the assertion that would catch it. + value when there is no name", this is the assertion that would catch + it. """ from pcapkit.const.tcp.flags import Flags @@ -174,7 +175,7 @@ def test_named_members_are_untouched(self) -> None: # An explicitly declared zero keeps its declared name, which is the # clearest demonstration that the guard keys on the *name* and not on the - # value: this member's value is 0 and its rendering is unchanged. + # value: this member's value is 0 and it renders under its declared name. from pcapkit.const.reg.apptype import TransportProtocol self.assertEqual(TransportProtocol(0).name, 'undefined') @@ -220,10 +221,10 @@ def test_addon_branch_renders_a_nameless_member(self) -> None: Reached when the member carries public instance attributes, which for a pseudo-member takes a :meth:`~enum.Enum._missing_` override. No registry under :mod:`pcapkit.const` is both a flag enumeration and an annotated - one, so this branch is not reachable from a capture on ``main`` -- - but the interpolation was character-for-character the same as the other - two, so it is guarded with them rather than left as the one place the - literal ``None`` survives. + one, so this branch is not reachable from a capture -- but the + interpolation is character-for-character the same as the other two, so + it is guarded with them rather than left as the one place the literal + ``None`` survives. """ from pcapkit.dumpkit.common import make_dumper @@ -366,8 +367,8 @@ def _const_path() -> 'list[str]': def _flag_registries() -> 'dict[str, FlagRegistry]': """Every flag enumeration declared under :mod:`pcapkit.const`. - Discovered rather than listed, so an eighth registry is swept the day it - lands instead of the day somebody remembers to add it here. Keyed by + Discovered rather than listed, so a new registry is swept the day it lands + instead of the day somebody remembers to add it here. Keyed by ``.`` so a failing subtest names the registry it came from. Both libraries are matched because the two are unrelated types -- diff --git a/tests/dumpkit/test_plist_date_unit.py b/tests/dumpkit/test_plist_date_unit.py index 7883054e1a..c94acd06a9 100644 --- a/tests/dumpkit/test_plist_date_unit.py +++ b/tests/dumpkit/test_plist_date_unit.py @@ -102,7 +102,7 @@ def test_naive_date_and_plain_date_are_written_as_is(self) -> None: }) def test_json_and_tree_keep_the_fractional_date(self) -> None: - """Only the ``plist`` writer is changed; the others keep full precision.""" + """Only the ``plist`` writer drops the fraction; the others keep full precision.""" stamp = datetime.datetime(2017, 11, 19, 15, 49, 5, 471719, tzinfo=UTC) self.assertIn(b'2017-11-19T15:49:05.471719', self.dump('JSON', {'time': stamp})) diff --git a/tests/dumpkit/test_tree_lossy_unit.py b/tests/dumpkit/test_tree_lossy_unit.py index 0aeecb79ac..e975840b08 100644 --- a/tests/dumpkit/test_tree_lossy_unit.py +++ b/tests/dumpkit/test_tree_lossy_unit.py @@ -2,11 +2,12 @@ """The ``tree`` dumper writes a :class:`complex` number instead of raising. GitHub issue #1266: :meth:`dictdumper.tree.Tree._append_number` hands every -number to :func:`math.isnan`, which raises ``TypeError: must be real number, not -complex`` for the :class:`complex` values :class:`~dictdumper.tree.Tree` routes -there. :func:`~pcapkit.dumpkit.common.make_dumper` writes those itself, in -the writer's own ``-> `` style. ``json`` and ``plist`` are untouched: -they already write a :class:`complex` as the string ``(1+2j)``. +number to :func:`math.isnan`, which raises +``TypeError: must be real number, not complex`` for the :class:`complex` values +:class:`~dictdumper.tree.Tree` routes there. +:func:`~pcapkit.dumpkit.common.make_dumper` writes those itself, in the writer's +own ``-> `` style. ``json`` and ``plist`` need nothing: they write a +:class:`complex` as the string ``(1+2j)``. :mod:`pcapkit` is imported inside each test, after :func:`~tests._support.reimport_once_per_class`, not at module load, so that the diff --git a/tests/foundation/engines/test_engine_base.py b/tests/foundation/engines/test_engine_base.py index a5521ae0ca..74eee16df1 100644 --- a/tests/foundation/engines/test_engine_base.py +++ b/tests/foundation/engines/test_engine_base.py @@ -202,7 +202,7 @@ def read_frame(self) -> str: register.assert_called_once_with('parentengine', Parent) # the substitute for ``final``: inheriting a registered class registers - # nothing, where the old fallback registered it as 'derived' + # nothing, where a ``cls.name`` fallback would register it as 'derived' with mock.patch('pcapkit.foundation.extraction.Extractor.register_engine') as register: class Derived(Parent): pass diff --git a/tests/foundation/engines/test_new_engine_parity_runtime.py b/tests/foundation/engines/test_new_engine_parity_runtime.py index a535dd207b..9b4e8b3885 100644 --- a/tests/foundation/engines/test_new_engine_parity_runtime.py +++ b/tests/foundation/engines/test_new_engine_parity_runtime.py @@ -12,11 +12,10 @@ fixture-dependent tier (see :mod:`tests._tiers`). It reads ``tcp.pcap``, ``ipv6.pcap`` and ``test.pcapng``, none of which git tracks -- :file:`examples/generators/make_samples.py` writes them -- so it cannot be -unit-tier, and it was one only because the guard added in GitHub pull request #393 -could not see it: the reads go through ``sample_path(capture)`` with a *variable*, -which only the runtime half of the guard catches, and that half was never reached -on a machine where the engine packages were absent and every test skipped. -Installing an engine made its reads fail at once. +unit-tier. The static half of the guard from GitHub pull request #393 cannot see +that: the reads go through ``sample_path(capture)`` with a *variable*, which only +the runtime half catches, and that half is never reached on a machine where the +engine packages are absent and every test skips. """ from __future__ import annotations diff --git a/tests/foundation/engines/test_pyshark_engine.py b/tests/foundation/engines/test_pyshark_engine.py index c46adc6e2d..25da8c89a5 100644 --- a/tests/foundation/engines/test_pyshark_engine.py +++ b/tests/foundation/engines/test_pyshark_engine.py @@ -3,8 +3,7 @@ The rest of the engine is exercised end to end in :mod:`tests.foundation.engines.test_runtime_engines`, which needs `PyShark`_ and the generated captures. This module covers only the preflight check, which is the part -that has to give the right answer on a machine where `PyShark`_ cannot run at all -- -including this one. +that has to give the right answer on a machine where `PyShark`_ cannot run at all. `PyShark`_ is refused for two independent reasons, and both are measured: @@ -21,9 +20,9 @@ What the running host provides is not assumed either way, and that is worth stating plainly rather than leaving to a silent skip: :program:`tshark` is absent -on most of this file's legs, but #751's ``engine-tests`` job -- which collects this -same module -- installs it via ``apt-get``, so neither "present" nor "absent" can -be relied on. The "missing binary" path is exercised for real by +on most of this file's legs, but #751's ``engine-tests`` job -- which collects +this same module -- installs it via ``apt-get``, so neither "present" nor "absent" +can be relied on. The "missing binary" path is exercised for real by :meth:`PySharkUnsupportedReasonTests.test_this_host_really_has_no_tshark_so_the_check_is_not_vacuous`, which self-skips wherever tshark turns out to be on this host's :envvar:`PATH` instead of assuming the answer; the "binary present" path is exercised mostly by @@ -31,11 +30,11 @@ every leg that collects this module. The one test that needs *both* states on demand -- :meth:`PySharkUnsupportedReasonTests.test_a_config_ini_naming_an_off_path_tshark_is_not_read_as_missing` --- constructs them explicitly rather than depending on whatever the host -happens to provide, which is what makes it pass on ``engine-tests`` (tshark on -``PATH``) and everywhere else (no tshark on ``PATH``) alike. The interpreter is -3.14, so every version below the ceiling is reached by patching -:data:`sys.version_info`, which is the same technique +-- constructs them explicitly rather than depending on whatever the host happens +to provide, which is what makes it pass on ``engine-tests`` (tshark on ``PATH``) +and everywhere else (no tshark on ``PATH``) alike. Every version either side of +the ceiling is reached by patching :data:`sys.version_info`, whichever interpreter +runs the tests, which is the same technique :mod:`tests.foundation.engines.test_pypcapfile_engine` uses. .. _PyShark: https://kiminewt.github.io/pyshark @@ -57,8 +56,8 @@ HAS_PYSHARK = importlib.util.find_spec('pyshark') is not None -#: A version below the ceiling, used to reach the :program:`tshark` branch, which -#: is otherwise unreachable on the 3.14 interpreter these tests run on. +#: A version below the ceiling, used to reach the :program:`tshark` branch +#: whichever interpreter runs the tests. SUPPORTED_VERSION = (3, 11, 0, 'final', 0) @@ -262,18 +261,17 @@ def test_a_config_ini_naming_an_off_path_tshark_is_not_read_as_missing(self) -> :meth:`test_this_host_really_has_no_tshark_so_the_check_is_not_vacuous`; :func:`_tshark_missing` and ``PyShark.unsupported_reason`` must not. - Deliberately host-independent, and that is itself the regression: an - earlier version of this test asserted ``shutil.which('tshark') is - None``, i.e. that the *host* has no tshark anywhere on ``PATH`` at all - -- true on the machine it was written on, false on every - ``engine-tests`` CI leg once #755's own ``apt-get install … - tshark`` step runs, which is exactly the asymmetry that escaped - review. The claim this test actually needs is narrower: that - ``which`` cannot resolve *the stand-in specifically*, because its - directory was never put on ``PATH`` -- true regardless of whether - some unrelated ``tshark`` happens to sit on ``PATH`` elsewhere. Both - worlds are constructed and checked below rather than left to - whatever the running host happens to provide. + Deliberately host-independent, and that is itself the regression: + asserting ``shutil.which('tshark') is None``, i.e. that the *host* has + no tshark anywhere on ``PATH`` at all, is true on a machine without it + and false on every ``engine-tests`` CI leg once #755's own + ``apt-get install … tshark`` step runs, which is exactly the asymmetry + that escaped review. The claim this test actually needs is narrower: + that ``which`` cannot resolve *the stand-in specifically*, because its + directory was never put on ``PATH`` -- true regardless of whether some + unrelated ``tshark`` happens to sit on ``PATH`` elsewhere. Both worlds + are constructed and checked below rather than left to whatever the + running host happens to provide. """ import os diff --git a/tests/foundation/engines/test_scapy_reader_interfaces_runtime.py b/tests/foundation/engines/test_scapy_reader_interfaces_runtime.py index 02dbad5b9b..d7a4207b79 100644 --- a/tests/foundation/engines/test_scapy_reader_interfaces_runtime.py +++ b/tests/foundation/engines/test_scapy_reader_interfaces_runtime.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """The scapy engine takes a packet's link type and clock from its interface. -Three things :class:`scapy.utils.PcapNgReader` reads differently from the -default engine, all fixed in the reader subclass of +What :class:`scapy.utils.PcapNgReader` reads differently from the default +engine, all fixed in the reader subclass of :mod:`pcapkit.foundation.engines.scapy`: * #1503 -- a Simple Packet Block carries no timestamp. Scapy leaves the packet diff --git a/tests/foundation/reassembly/data/test_models.py b/tests/foundation/reassembly/data/test_models.py index 47c2487b26..954fa96240 100644 --- a/tests/foundation/reassembly/data/test_models.py +++ b/tests/foundation/reassembly/data/test_models.py @@ -66,7 +66,7 @@ def test_completion_is_a_string_that_still_reads_as_the_old_bool(self) -> None: from pcapkit.foundation.reassembly.data.data import Completion - # only COMPLETE is truthy, so ``if datagram.completed:`` reads as it did + # only COMPLETE is truthy, so ``if datagram.completed:`` reads as a bool would self.assertTrue(Completion.COMPLETE) self.assertFalse(Completion.PARTIAL) self.assertFalse(Completion.TIMEOUT) diff --git a/tests/foundation/reassembly/test_ip.py b/tests/foundation/reassembly/test_ip.py index 9e71a90d7e..a1bed8b9ce 100644 --- a/tests/foundation/reassembly/test_ip.py +++ b/tests/foundation/reassembly/test_ip.py @@ -118,7 +118,7 @@ class TestIP(IP): @unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed') class IPOverlapConflictTests(unittest.TestCase): - """:issue:`477` -- an overlapping fragment carrying different bytes must + """#477 -- an overlapping fragment carrying different bytes must have the disagreement recorded, not silently overwrite the earlier one. :rfc:`791` resolves *which* bytes win itself: "this procedure will use the @@ -271,9 +271,9 @@ def test_conflict_stays_scoped_to_its_own_bufid(self) -> None: """Coverage gap named in #482's review: ``Buffer.conflict`` is created fresh per BUFID, but "created fresh" is an argument, not a test -- nothing exercised two BUFIDs actually in flight at once. This is the - more valuable of the two gaps: #478's original six boundary cases all - shared a single ACK bucket, and that is exactly the shape of gap that - let a genuine cross-bucket data-loss regression through undetected. + more valuable of the two gaps: #478's boundary cases all shared a + single ACK bucket, and that is exactly the shape of gap that let a + genuine cross-bucket data-loss regression through undetected. So this interleaves two datagrams' fragments -- neither one completes before the other's next fragment arrives -- rather than finishing one diff --git a/tests/foundation/reassembly/test_reassembly_base.py b/tests/foundation/reassembly/test_reassembly_base.py index 296598746d..af10bbdd4d 100644 --- a/tests/foundation/reassembly/test_reassembly_base.py +++ b/tests/foundation/reassembly/test_reassembly_base.py @@ -171,8 +171,8 @@ def reassembly(self, info: DummyPacket) -> None: def submit(self, buf: DummyBuffer, **kwargs: object) -> list[DummyDatagram]: return [] - # #514: registration is opt-in. Before it, the absent keyword fell back to - # ``cls.name`` -- i.e. ``__protocol_name__`` here -- and this registered + # #514: registration is opt-in. A fallback for the absent keyword to + # ``cls.name`` -- i.e. ``__protocol_name__`` here -- would register this # under ``'defaultproto'``. A class attribute is not a registry key. register.assert_not_called() self.assertEqual(Default.name, 'DefaultProto') diff --git a/tests/foundation/reassembly/test_tcp.py b/tests/foundation/reassembly/test_tcp.py index c0c58b6046..c37dfc639a 100644 --- a/tests/foundation/reassembly/test_tcp.py +++ b/tests/foundation/reassembly/test_tcp.py @@ -271,9 +271,9 @@ class TCPReassemblyCoordinateTests(unittest.TestCase): was computed as the sequence number *after* the segment while the descriptor list treats its bounds as inclusive. - A fourth, closely related error surfaced while fixing them: the sequence - number a SYN spends became a NUL octet at the head of the payload, so a - complete datagram did not compare equal to the octets that were sent. + A closely related error is pinned too: the sequence number a SYN spends + became a NUL octet at the head of the payload, so a complete datagram + did not compare equal to the octets that were sent. Every stream here is driven through :class:`~pcapkit.foundation.reassembly.tcp.TCP` with segment descriptors built exactly as the :mod:`pcapkit.toolkit` modules @@ -390,8 +390,8 @@ def test_the_answer_does_not_depend_on_the_initial_sequence_number(self) -> None """The same stream reassembles the same way whatever ISN it uses. This is the property the defect broke: at zero the two coordinate - systems coincide and the fragments came out one octet long each too - long; anywhere else the datagram disappeared entirely. + systems coincide and the fragments came out one octet too long + each; anywhere else the datagram disappeared entirely. """ from pcapkit.foundation.reassembly.data.data import Completion @@ -625,8 +625,8 @@ def test_fin_completes_a_stream_above_2_31_whatever_sys_maxsize(self) -> None: self.assertEqual(datagram.payload, b'A' * 10 + b'B' * 10) # The end-to-end case on a generated capture lives in - # tests/foundation/reassembly/test_tcp_runtime.py, since the unit-test - # workflow runs without the generated fixtures. + # tests/foundation/reassembly/test_tcp_runtime.py, since the unit tier + # runs without the generated fixtures. @unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed') @@ -641,8 +641,7 @@ class TCPReassemblyConflictTests(unittest.TestCase): conflicting portion of whichever segment arrived later is discarded, and the sequence range on which they disagreed is recorded on :attr:`~pcapkit.foundation.reassembly.data.tcp.Datagram.conflict` -- - additive, so ``completed`` is untouched and existing callers are - unaffected. + additive, so ``completed`` means what it does without a conflict. Every test drives the public API only -- build :class:`Packet`, feed :class:`TCP`, read back the :class:`Datagram` from @@ -733,7 +732,7 @@ def test_conflicting_partial_overlap_on_the_tail_side(self) -> None: self.assertEqual(datagram.conflict, ((base + 4, base + 7),)) def test_conflicting_partial_overlap_on_the_head_side_reach_back(self) -> None: - """The mirrored reach-back branch, at the line the issue calls out at :204. + """The mirrored reach-back branch, the one the issue calls out. A segment arriving with a *lower* sequence number than the buffer's current ISN, whose tail overlaps the buffer's head: the overlapping @@ -773,11 +772,11 @@ def test_three_way_conflict_keeps_the_first_and_records_every_disagreement(self) def test_conflict_persists_once_a_later_segment_completes_the_datagram(self) -> None: """A conflict recorded while the stream is still partial survives to completion. - The gap between the two original segments is a real hole -- unlike - the overlap in the other tests here, filling it is an ordinary gap - fill, not a conflict -- and once it closes the datagram reports + The gap between the two original segments is a real hole -- unlike the + overlap in the other tests here, filling it is an ordinary gap fill, + not a conflict -- and once it closes the datagram reports :attr:`~pcapkit.foundation.reassembly.data.data.Completion.COMPLETE`, - per the decision to leave ``completed`` alone: the conflict recorded + since ``completed`` does not consider conflicts: the conflict recorded earlier is still there, on the completed datagram, rather than being dropped or blocking completion. @@ -810,20 +809,19 @@ def test_a_conflict_in_one_ack_bucket_is_not_reported_in_another(self) -> None: The sibling test below covers the inverse -- that a *hole* closed in one bucket does not look received in another -- and asserts an empty - ``conflict`` for both. Nothing asserted the case where one bucket + ``conflict`` for both. This one asserts the case where one bucket genuinely *does* record a conflict while another, in flight at the same time, must not: the gap lists and conflict records are per :attr:`~pcapkit.foundation.reassembly.data.tcp.Fragment`, and this is what proves it rather than arguing it from where the field is declared. Worth stating why this case in particular. The data loss this whole - mechanism exists to fix survived six boundary tests precisely because + mechanism exists to fix survived the boundary tests precisely because every one of them used a *single* ACK bucket, so none could see state - leaking across buckets. The representation has since changed from a - per-octet ``received`` mask to - :attr:`~pcapkit.foundation.reassembly.data.tcp.Fragment.gap`, and that - change arrived with two more single-bucket tests -- so the same blind - spot had reopened against the new mechanism. + leaking across buckets, and the + :attr:`~pcapkit.foundation.reassembly.data.tcp.Fragment.gap` + representation came with more single-bucket tests -- the same blind + spot. Bucket 1000 takes a real conflicting retransmission over ``base..base+3`` and must report exactly that range. Bucket 2000 is interleaved with it, diff --git a/tests/foundation/reassembly/test_tcp_runtime.py b/tests/foundation/reassembly/test_tcp_runtime.py index e0be61e612..080cea7b5d 100644 --- a/tests/foundation/reassembly/test_tcp_runtime.py +++ b/tests/foundation/reassembly/test_tcp_runtime.py @@ -16,9 +16,9 @@ class TCPReassemblyRuntimeTests(unittest.TestCase): This lives in a ``*_runtime.py`` module rather than beside the unit tests because it reads a generated capture: ``examples/captures/`` holds only a handful of committed fixtures, and the rest are rebuilt by - ``examples/generators/make_samples.py``. The unit-test workflow skips this - file for exactly that reason, and the integration workflow generates the - fixtures before running it. + ``examples/generators/make_samples.py``. The ``test`` job of + unit-tests.yml skips this file for exactly that reason, and the + ``integration`` job generates the fixtures before running it. """ diff --git a/tests/foundation/reassembly/test_timeout.py b/tests/foundation/reassembly/test_timeout.py index 693588e581..998cec12d0 100644 --- a/tests/foundation/reassembly/test_timeout.py +++ b/tests/foundation/reassembly/test_timeout.py @@ -90,8 +90,8 @@ def test_timeout_is_told_apart_from_merely_unfinished(self) -> None: ``PARTIAL`` says "these fragments had not arrived yet"; ``TIMEOUT`` says "these fragments are gone, and no further fragment will ever be added". - Both are falsy, so ``if datagram.completed`` reads as it did while the - field was a :obj:`bool`. + Both are falsy, so ``if datagram.completed`` reads as it would on a + :obj:`bool`. """ from pcapkit.foundation.reassembly.data.data import Completion @@ -246,10 +246,10 @@ def _packet(self, *, num: int, fo: int, mf: bool, payload: bytes): def test_an_unterminated_datagram_reports_the_prefix_that_arrived(self) -> None: """No final fragment means no known length -- but the prefix is known. - ``TDL`` is still ``-1`` here, and ``datagram[:-1]`` was 65534 octets of the - preallocated buffer with ``completed=True`` on top. Neither that nor an - empty payload is right: the octets from offset zero to the first hole did - genuinely arrive, so they are what a caller asking for one contiguous + ``TDL`` is still ``-1`` here, and without the fix ``datagram[:-1]`` is 65534 + octets of the preallocated buffer with ``completed=True`` on top. Neither that + nor an empty payload is right: the octets from offset zero to the first hole + did genuinely arrive, so they are what a caller asking for one contiguous payload gets, and the datagram is reported incomplete. """ @@ -292,10 +292,10 @@ def test_the_prefix_stops_at_the_first_hole(self) -> None: self.assertEqual(datagram.payload, (b'A' * 16, b'C' * 16)) def test_a_known_length_still_zero_fills_its_holes(self) -> None: - """With the last fragment in hand the length is known, so nothing changes. + """With the last fragment in hand the length is known, so the holes are zero-filled. - This is the case ``strict=False`` exists for, and its payload is exactly - what it always was -- only ``completed`` stopped claiming the datagram was + This is the case ``strict=False`` exists for: its payload is the + zero-filled datagram, and ``completed`` does not claim the datagram is whole. """ diff --git a/tests/foundation/registry/test_foundation.py b/tests/foundation/registry/test_foundation.py index abbf5d3401..5c26c6f43d 100644 --- a/tests/foundation/registry/test_foundation.py +++ b/tests/foundation/registry/test_foundation.py @@ -151,18 +151,16 @@ def test_callback_and_extractor_registration_wrappers(self) -> None: def test_registration_accepts_pcapkit_own_builtin_classes(self) -> None: """The built-ins pass the internal ``issubclass`` gate, and non-subclasses still fail. - This is the regression for GitHub issue #513. Every sibling test in this - module mocks ``Extractor.register_*`` away, so none of them reaches the - validation at :file:`pcapkit/foundation/extraction.py` -- which is why the - defect survived: all three entry points rejected pcapkit's *own* engines, + This is the regression for GitHub issue #513. A test that mocks + ``Extractor.register_*`` away never reaches the validation in + :file:`pcapkit/foundation/extraction.py` -- which is how the defect + survived: all three entry points rejected pcapkit's *own* engines, reassembly and flow-tracing classes with :exc:`~pcapkit.utilities.exceptions.RegistryError`. The cause was that each check named the auto-registering public class (``Engine``, ``Reassembly``, ``TraceFlow``) while every built-in subclasses - the ``*Base`` variant -- imported under an alias, e.g. - ``from ...engine import EngineBase as Engine`` at - :file:`pcapkit/foundation/engines/pcap.py`, specifically so that it is not + the ``*Base`` variant directly, specifically so that it is not auto-registered. So ``issubclass(PCAP, Engine)`` was :data:`False` while ``issubclass(PCAP, EngineBase)`` was :data:`True`. diff --git a/tests/foundation/registry/test_foundation_keyword_names.py b/tests/foundation/registry/test_foundation_keyword_names.py index db5694db3c..f75f4b156b 100644 --- a/tests/foundation/registry/test_foundation_keyword_names.py +++ b/tests/foundation/registry/test_foundation_keyword_names.py @@ -1,13 +1,11 @@ """The registry helpers' documented keyword names must be the real ones. :func:`~pcapkit.foundation.registry.foundation.register_extractor_engine` -once documented its first argument as ``engine`` while the signature -declared ``name``, so a caller following the docstring got a -:exc:`TypeError` rather than a registration -- noted in passing while -auditing the ``__init_subclass__`` keyword rename in #557. The docstring -has since been corrected to say ``name``, matching the signature, but -nothing pinned the pairing itself, so a future rename could put the two -back out of step just as silently as before. +documented its first argument as ``engine`` while the signature declared +``name``, so a caller following the docstring got a :exc:`TypeError` +rather than a registration -- noted in passing while auditing the +``__init_subclass__`` keyword rename in #557. A rename can put the two +out of step silently. These pin the keyword spelling directly against the real signature. @@ -31,7 +29,7 @@ def setUp(self) -> None: reimport_once_per_class(self) def test_register_extractor_engine_takes_name_not_engine(self) -> None: - """``name=`` registers; ``engine=`` -- as documented -- does not.""" + """``name=`` registers; ``engine=`` does not.""" from pcapkit.corekit.module import ModuleDescriptor from pcapkit.foundation.registry import foundation as registry diff --git a/tests/foundation/registry/test_protocols.py b/tests/foundation/registry/test_protocols.py index a6f8902142..fd4aeba17d 100644 --- a/tests/foundation/registry/test_protocols.py +++ b/tests/foundation/registry/test_protocols.py @@ -120,8 +120,9 @@ def test_register_protocol_warns_when_a_colliding_name_overwrites(self) -> None: warnings.simplefilter('always') registry.register_protocol(replacement) - # The overwrite still happens. This fix reports the collision, - # it does not resolve it -- resolving it is #514's business. + # The overwrite still happens. The warning reports the + # collision, it does not resolve it -- resolving it is #514's + # business. self.assertIs(registry.protocol_registry['HTTP'], replacement) messages = self._registry_warnings(caught, RegistryWarning) @@ -177,17 +178,17 @@ class HTTP(Protocol): def test_register_protocol_stays_quiet_when_nothing_is_displaced(self) -> None: """A fresh key and an identical re-registration are both silent. - Since #718, the code-keyed siblings share this identity guard too -- - see ``test_sibling_registries_now_stay_quiet_on_an_identical_re_registration`` - in this file. Here the key is *derived* from the class rather than - supplied by the caller, and this function is the funnel every wrapper - registrar ends in, so registering one class under two codes -- supported, - and exactly what :func:`~pcapkit.foundation.registry.protocols.register_tcp` - followed by :func:`~pcapkit.foundation.registry.protocols.register_udp` - does -- reaches it twice with the same class and nothing displaced. A - presence-only guard would warn there, and a caller who filters - :exc:`~pcapkit.utilities.warnings.RegistryWarning` to silence that noise - stops seeing the ``HTTP`` collision the warning exists for. + The code-keyed siblings share this identity guard too (#718) -- see + ``test_sibling_registries_now_stay_quiet_on_an_identical_re_registration`` in + this file. Here the key is *derived* from the class rather than supplied by + the caller, and this function is the funnel every wrapper registrar ends in, + so registering one class under two codes -- supported, and exactly what + :func:`~pcapkit.foundation.registry.protocols.register_tcp` followed by + :func:`~pcapkit.foundation.registry.protocols.register_udp` does -- reaches it + twice with the same class and nothing displaced. A presence-only guard would + warn there, and a caller who filters + :exc:`~pcapkit.utilities.warnings.RegistryWarning` to silence that noise stops + seeing the ``HTTP`` collision the warning exists for. """ from pcapkit.foundation.registry import protocols as registry @@ -290,7 +291,7 @@ def test_sibling_registries_name_what_they_displaced(self) -> None: :class:`~pcapkit.protocols.transport.udp.UDP` rather than overriding it, and :meth:`ProtocolBase.register ` itself, which the - other six shadow. + others shadow. """ from pcapkit.const.reg.ethertype import EtherType @@ -390,13 +391,13 @@ def test_register_protocol_disambiguates_classes_sharing_a_repr(self) -> None: warnings.simplefilter('always') registry.register_protocol(second) - # The guard's firing condition is unchanged: the overwrite still - # happens and is still reported exactly once. + # The guard's firing condition: the overwrite happens and is + # reported exactly once. self.assertIs(registry.protocol_registry['UNITPROTOCOL'], second) messages = self._registry_warnings(caught, RegistryWarning) self.assertEqual(len(messages), 1) - # The pre-fix message text must be gone... + # The message is not the bare pair of reprs... self.assertNotIn(f'overwriting {first!r} with {second!r}', messages[0]) # ...replaced by something that tells the two objects apart. id() is @@ -447,8 +448,8 @@ def test_register_protocol_rejects_a_non_class_with_registry_error(self) -> None def test_top_level_link_internet_and_transport_protocol_wrappers(self) -> None: # Members live in the per-transport registries GitHub issue #732 split # AppType into; the base class itself holds none. 3com-amp3 is registered - # on both TCP and UDP, and the TCP copy is used, arbitrarily; since #806 - # its .proto names TCP alone, which the notes below rely on. + # on both TCP and UDP, and the TCP copy is used, arbitrarily; its .proto + # names TCP alone (#806), which the notes below rely on. from pcapkit.const.reg.apptype import TCP as AppType_TCP from pcapkit.const.reg.apptype import UDP as AppType_UDP from pcapkit.const.reg.apptype import TransportProtocol @@ -499,8 +500,8 @@ def test_top_level_link_internet_and_transport_protocol_wrappers(self) -> None: # NOTE: a member with no explicit transport protocol registers under its # own ``proto``, which names exactly the registry it lives in -- so a TCP - # member reaches TCP alone. It fanned out to UDP as well until GitHub - # issue #806 retyped ``proto`` from the whole IANA set to one transport. + # member reaches TCP alone, since ``proto`` names one transport rather + # than the whole IANA set (GitHub issue #806). with mock.patch.object(registry.TCP, 'register') as tcp_register: with mock.patch.object(registry.UDP, 'register') as udp_register: with mock.patch.object(registry, 'register_protocol') as register_protocol: @@ -530,11 +531,11 @@ def test_top_level_link_internet_and_transport_protocol_wrappers(self) -> None: udp_register.assert_called_once() register_protocol.assert_called_once_with(UnitProtocol) - # NOTE: ``class_`` is positional again, at the sibling position -- - # further maintainer ruling on #815 -- so a class name and a - # transport fit in the same call: the class name third, since - # ``module`` here is a ``str``, and the transport from the fourth - # position onward. + # NOTE: ``class_`` is positional, at the sibling position -- + # maintainer ruling on #815 -- so a class name and a transport + # fit in the same call: the class name third, since ``module`` + # here is a ``str``, and the transport from the fourth position + # onward. with mock.patch.object(registry.TCP, 'register') as tcp_register: with mock.patch.object(registry.UDP, 'register') as udp_register: with mock.patch.object(registry, 'register_protocol') as register_protocol: @@ -553,8 +554,8 @@ def test_top_level_link_internet_and_transport_protocol_wrappers(self) -> None: registry.register_apptype(65004, UnitProtocol) # NOTE: a composite is refused: one call registers under one transport - # protocol, and since #808 dropped the ``IntFlag`` base ``tcp | udp`` is - # a bare ``int`` rather than a member. + # protocol, and without an ``IntFlag`` base (#808) ``tcp | udp`` is a + # bare ``int`` rather than a member. with self.assertRaises(RegistryError): registry.register_apptype(65005, UnitProtocol, TransportProtocol.tcp | TransportProtocol.udp) @@ -596,11 +597,11 @@ def test_top_level_link_internet_and_transport_protocol_wrappers(self) -> None: # NOTE: a composite name such as ``'tcp|udp'`` raises for the same # reason the equivalent composite member is refused above: one call - # registers under one transport protocol. This was the case that most - # needed covering while ``TransportProtocol`` was an ``IntFlag``, until - # #808: aenum's ``Flag`` getitem parsed a ``'|'``-joined name into a - # composite value on its own, so a resolver built on it unguarded would - # have accepted this silently instead of refusing it. + # registers under one transport protocol. On an ``IntFlag`` base, + # which #808 removed from ``TransportProtocol``, aenum's ``Flag`` + # getitem parses a ``'|'``-joined name into a composite value on its + # own, so a resolver built on it unguarded would accept this silently + # instead of refusing it. with self.assertRaises(RegistryError): registry.register_apptype(65010, UnitProtocol, 'tcp|udp') @@ -697,8 +698,8 @@ def test_top_level_link_internet_and_transport_protocol_wrappers(self) -> None: self.assertIs(registry.UDP.__proto__[65201], UnitProtocol) # NOTE: a ``str`` transport after a class module reaches ``transport`` - # the same way a member does, then goes through the existing - # ``str``-coercion path unchanged. + # the same way a member does, then goes through the ``str``-coercion + # path. self._guard_registry(registry.TCP.__proto__, 65202) self._guard_registry(registry.UDP.__proto__, 65202) with mock.patch.object(registry, 'register_protocol'): @@ -728,15 +729,14 @@ def test_top_level_link_internet_and_transport_protocol_wrappers(self) -> None: # NOTE: this is not a heuristic on the value -- with a ``str`` module, # a ``str`` third argument is *always* a class name, even one that - # happens to spell a transport's own name. ``'tcp'`` is looked up as - # a class in ``raw_module[0]``, which has none, so it fails the same - # way any other wrong class name would: a ``ProtocolError`` naming - # ``'tcp'`` itself, not a ``RegistryError`` about an unknown - # transport -- proof it was never coerced as one. GitHub issue #832 - # retyped this from a bare stdlib ``AttributeError`` to - # ``ProtocolError`` -- the property that a ``str`` third positional is - # resolved as a class name, never sniffed as a transport, is what has - # to survive; only the exception type changed. + # happens to spell a transport's own name. ``'tcp'`` is looked up as a + # class in ``raw_module[0]``, which has none, so it fails the same way + # any other wrong class name would: a ``ProtocolError`` naming + # ``'tcp'`` itself, not a ``RegistryError`` about an unknown transport + # -- proof it was never coerced as one. The ``ProtocolError`` rather + # than a bare stdlib ``AttributeError`` is GitHub issue #832's; the + # property that a ``str`` third positional is resolved as a class + # name, never sniffed as a transport, is what this pins. with mock.patch.object(registry, 'register_protocol'): with self.assertRaises(ProtocolError) as caught: registry.register_apptype(65204, raw_module[0], 'tcp', TransportProtocol.udp) @@ -797,11 +797,11 @@ def test_register_apptype_omitted_class_name_reports_missing_argument(self) -> N def test_register_apptype_explicit_null_string_is_a_class_name_not_the_sentinel(self) -> None: """GitHub issue #833: ``class_='(null)'`` is data, not the sentinel. - Before the sentinel stopped being a plain :class:`str`, an - equal-but-distinct ``'(null)'`` passed by a caller was indistinguishable - from the default -- both compared equal, and which branch ran depended - on identity/interning rather than intent. Passed explicitly here, it - must be resolved as an ordinary (missing) class name, the same way any + Were the sentinel a plain :class:`str`, an equal-but-distinct + ``'(null)'`` passed by a caller would be indistinguishable from the + default -- both compare equal, and which branch ran would depend on + identity/interning rather than intent. Passed explicitly here, it must + be resolved as an ordinary (missing) class name, the same way any other wrong class name is, and the failure must name the module and the literal ``'(null)'`` that was looked up. @@ -949,8 +949,8 @@ def test_schema_registrars_share_one_signature_contract(self) -> None: ``code`` and ``meth`` are positional, ``schema`` is keyword-only. See #516, where :func:`~pcapkit.foundation.registry.protocols.register_mh_extension` - was missing the ``*`` that its fourteen siblings carry and so accepted - ``schema`` positionally. + was missing the ``*`` that its siblings carry and so accepted ``schema`` + positionally. """ from pcapkit.foundation.registry import protocols as registry diff --git a/tests/foundation/registry/test_registry_name_restore_unit.py b/tests/foundation/registry/test_registry_name_restore_unit.py index 8c15e947f6..1ef5f67858 100644 --- a/tests/foundation/registry/test_registry_name_restore_unit.py +++ b/tests/foundation/registry/test_registry_name_restore_unit.py @@ -9,7 +9,7 @@ custom class named like a built-in left the built-in displaced. :mod:`tests.foundation.registry.test_registry_symmetry_unit` covers the plain -round trip for all nine registrars. These pin the cases it does not reach: a +round trip for every registrar. These pin the cases it does not reach: a displaced built-in name, a class still held by a second code, a name that was there before the registration, which a restore must not take away, and a built-in retaking its name from a custom class shadowing it -- plus a seeded diff --git a/tests/foundation/registry/test_registry_symmetry_unit.py b/tests/foundation/registry/test_registry_symmetry_unit.py index 992e4b2ee4..77094e5dcd 100644 --- a/tests/foundation/registry/test_registry_symmetry_unit.py +++ b/tests/foundation/registry/test_registry_symmetry_unit.py @@ -28,16 +28,17 @@ flow-tracing tables -- and ``names`` covers :data:`pcapkit.protocols.__proto__`, the by-name registry the protocol registrars all funnel into. -The scope is the 36 public registrars in +The scope is the public registrars in :data:`pcapkit.foundation.registry.__all__`. Of those, the callback registrars (``register_reassembly_*_callback`` and ``register_traceflow_tcp_callback``) only append, and offer nothing to undo an append with, so they have no case here; nor does ``register_protocol_code``, which funnels into the same -:meth:`ProtocolBase.register ` -the ``register_tcp`` cases reach. Class-level registrars with no foundation wrapper are out of scope: -``SCTP.register_chunk``, ``register_parameter`` and ``register_cause`` -(covered by :file:`tests/protocols/transport/test_sctp_unit.py`), -``Schema.register``, the PCAP-NG ``Option.register`` and ``ESP.register``. +:meth:`ProtocolBase.register +` the ``register_tcp`` cases +reach. Class-level registrars with no foundation wrapper are out of scope: +``SCTP.register_chunk``, ``register_parameter`` and ``register_cause`` (covered +by :file:`tests/protocols/transport/test_sctp_unit.py`), ``Schema.register``, +the PCAP-NG ``Option.register`` and ``ESP.register``. Every case restores the registries it touched, whatever its outcome. Classes are imported inside each test, after :func:`~tests._support.reimport_once_per_class`. diff --git a/tests/foundation/test_extraction.py b/tests/foundation/test_extraction.py index c66dc890ab..f5dec49c39 100644 --- a/tests/foundation/test_extraction.py +++ b/tests/foundation/test_extraction.py @@ -432,7 +432,7 @@ def test_register_helpers_reject_a_non_class_with_registry_error(self) -> None: itself -- ``register_dumper`` directly (``Dumper`` has a plain ``type`` metaclass), the other three from inside ``abc`` -- so the documented ``RegistryError`` was unreachable. A wrong *class* still reaches the - subclass check and raises ``RegistryError`` as before. + subclass check and raises ``RegistryError``. """ from pcapkit.corekit.module import ModuleDescriptor from pcapkit.foundation.extraction import Extractor @@ -510,7 +510,7 @@ def read_frame(self) -> str: # 3. The registry write still happens even when the guard stays quiet. self.assertIs(Extractor.__engine__['unit-engine-identity'], UnitEngineIdentityA) - # 2. Replacing with a different object still warns, message unchanged. + # 2. Replacing with a different object still warns. with mock.patch('pcapkit.foundation.extraction.warn') as warn: Extractor.register_engine('unit-engine-identity', UnitEngineIdentityB) warn.assert_called_once_with( @@ -556,7 +556,7 @@ def submit(self, buf: object, **kwargs: object) -> list[object]: # 3. The registry write still happens even when the guard stays quiet. self.assertIs(Extractor.__reassembly__['unit-reassembly-identity'], UnitReassemblyIdentityA) - # 2. Replacing with a different object still warns, message unchanged. + # 2. Replacing with a different object still warns. with mock.patch('pcapkit.foundation.extraction.warn') as warn: Extractor.register_reassembly('unit-reassembly-identity', UnitReassemblyIdentityB) warn.assert_called_once_with( @@ -608,7 +608,7 @@ def submit(self) -> tuple[object, ...]: # 3. The registry write still happens even when the guard stays quiet. self.assertIs(Extractor.__traceflow__['unit-traceflow-identity'], UnitTraceFlowIdentityA) - # 2. Replacing with a different object still warns, message unchanged. + # 2. Replacing with a different object still warns. with mock.patch('pcapkit.foundation.extraction.warn') as warn: Extractor.register_traceflow('unit-traceflow-identity', UnitTraceFlowIdentityB) warn.assert_called_once_with( @@ -646,7 +646,7 @@ class UnitDumperIdentityB(NotImplementedIO): # 3. The registry write still happens even when the guard stays quiet. self.assertEqual(Extractor.__output__['unit-dumper-identity'], (NotImplementedIO, '.unit-a')) - # 2. Replacing with a different object still warns, message unchanged. + # 2. Replacing with a different object still warns. with mock.patch('pcapkit.foundation.extraction.warn') as warn: Extractor.register_dumper('unit-dumper-identity', UnitDumperIdentityB, '.unit-b') warn.assert_called_once_with( @@ -933,8 +933,7 @@ def test_record_header_record_frames_iteration_call_and_cleanup(self) -> None: self.assertTrue(call_interrupt._flag_e) # ``_flag_s`` set means ``fin`` was a path and ``Extractor`` opened the - # handle itself, so ``_cleanup`` owns it and has to close it. This - # assertion read ``assertFalse`` until #610, pinning the leak in place. + # handle itself, so ``_cleanup`` owns it and has to close it (#610). named_file = self._bare_extractor() named_file._flag_s = True named_file._ifile = ClosableBytesIO(b'data') @@ -950,8 +949,8 @@ def test_record_header_record_frames_iteration_call_and_cleanup(self) -> None: self.assertFalse(given_stream._ifile.closed_by_test) # Leaving the ``with`` block follows the same ownership rule: it closes - # a handle ``Extractor`` opened, and leaves a caller's stream open. This - # assertion closed the caller's stream until #1999. + # a handle ``Extractor`` opened, and leaves a caller's stream open + # (#1999). manual._flag_s = True with manual as ctx: self.assertIs(ctx, manual) @@ -1216,10 +1215,11 @@ def _warned(warn: mock.Mock, needle: str) -> bool: def test_warns_bullet_for_pcap_trace_output_names_the_engines_its_guard_warns_for(self) -> None: # The bullet lists engines by name and the guard lists them again as a - # tuple, and nothing tied the two: when DPKT and Scapy left the guard in - # #1507 the bullet kept naming them (#1719). Every engine is put through - # the guard -- ``_traced`` takes the requested engine as the one that runs - # -- and the set that warns is compared with the set the bullet names. + # tuple, and nothing else ties the two: the bullet once went on naming + # DPKT and Scapy after #1507 took them out of the guard (#1719). Every + # engine is put through the guard -- ``_traced`` takes the requested + # engine as the one that runs -- and the set that warns is compared + # with the set the bullet names. from pcapkit.foundation.extraction import Extractor warned = set() diff --git a/tests/foundation/test_extraction_constructor_closes_engine_2107_unit.py b/tests/foundation/test_extraction_constructor_closes_engine_2107_unit.py index cd309951c4..ee89eee261 100644 --- a/tests/foundation/test_extraction_constructor_closes_engine_2107_unit.py +++ b/tests/foundation/test_extraction_constructor_closes_engine_2107_unit.py @@ -236,7 +236,7 @@ def test_exit_closes_through_the_shared_path(self) -> None: extractor: Any = extraction.Extractor(fin=str(self.path), nofile=True, auto=False, **self.trace_kwargs()) if close_raises: - # ``__exit__`` lets the close's error out, as it always has. + # ``__exit__`` lets the close's error out. with self.assertRaises(UnsupportedCall), extractor: pass else: diff --git a/tests/foundation/test_extraction_engine_fallback_format_unit.py b/tests/foundation/test_extraction_engine_fallback_format_unit.py index 5c5ea4697f..f23f2deebe 100644 --- a/tests/foundation/test_extraction_engine_fallback_format_unit.py +++ b/tests/foundation/test_extraction_engine_fallback_format_unit.py @@ -120,8 +120,8 @@ def test_trace_format_is_kept(self) -> None: self.assertTrue(flow.fpout.endswith('.pcap'), flow.fpout) def test_available_third_party_engine_still_falls_back_to_json(self) -> None: - # The other half of the contract: an engine that really runs keeps the - # existing replacement, and its warning names that engine. + # The other half of the contract: an engine that really runs still + # falls back to JSON, and its warning names that engine. from pcapkit.utilities.warnings import FormatWarning if importlib.util.find_spec('dpkt') is None: diff --git a/tests/foundation/test_extraction_no_eof.py b/tests/foundation/test_extraction_no_eof.py index d306f9dabc..0645a7ec87 100644 --- a/tests/foundation/test_extraction_no_eof.py +++ b/tests/foundation/test_extraction_no_eof.py @@ -430,9 +430,10 @@ def test_call_form_with_no_eof_raises_once_the_input_is_finished(self) -> None: matches the plain form, which :file:`tests/integration/test_frame_iteration.py` already pins. - This is the case that proved an in-process deadline cannot bound: - without the fix it absorbed the ``SIGALRM`` into the parse path's - ``except Exception`` and then ran unbounded. + This is the case that showed an in-process deadline does not + hold: without the fix it escaped the deadline entirely and ran + unbounded -- see the module docstring for why, and for the cause + it rules out. """ self.run_bounded(f""" diff --git a/tests/foundation/test_extraction_ownership.py b/tests/foundation/test_extraction_ownership.py index e5d2a57e78..c44941571e 100644 --- a/tests/foundation/test_extraction_ownership.py +++ b/tests/foundation/test_extraction_ownership.py @@ -315,10 +315,10 @@ def test_non_seekable_stream_survives_its_seekable_wrapper(self) -> None: # A ``BufferedReader`` over a non-seekable raw stream, which is the shape # ``sys.stdin.buffer`` has -- and stdin is the input that actually reaches - # this path, since ``pcapkit/__main__.py`` hands it over for ``fin='-'``. - # A bare non-seekable :class:`io.BytesIO` is *not* usable here: lacking + # this path, since ``pcapkit/__main__.py`` hands it over for ``fin='-'``. A + # bare non-seekable :class:`io.BytesIO` is *not* usable here: lacking # ``peek`` it takes a different branch of ``SeekableReader`` that mis-reads - # the first record, which is why the existing suite only ever drives one + # the first record, which is why the rest of the suite only ever drives one # with ``Extractor.run`` patched out. stream = io.BufferedReader(_NonSeekableRaw(payload)) extractor = pcapkit.extract(fin=stream, nofile=True, store=False) @@ -420,10 +420,11 @@ class FinaliserBackstopWithoutDel(unittest.TestCase): """#1997: the backstop is a :class:`weakref.finalize`, not ``__del__`` alone. GraalPy never calls ``__del__``, so on that interpreter the #606 backstop - only ever ran if something else did the closing. CPython does call it, which - is why a regression here would go unnoticed on every CPython leg unless the - finaliser is checked on its own: these tests do that, either by inspecting it - or by taking ``__del__`` out of the way, so that they fail on CPython too. + only ever runs if something else does the closing. CPython does call it, + which is why a regression here would go unnoticed on every CPython leg unless + the finaliser is checked on its own: these tests do that, either by + inspecting it or by taking ``__del__`` out of the way, so that they fail on + CPython too. """ @@ -482,7 +483,7 @@ def test_finaliser_alone_releases_an_abandoned_handle(self) -> None: 'without __del__ the handle outlived the extractor') def test_collect_until_waits_out_a_finaliser_that_runs_late(self) -> None: - """A finaliser run after :func:`gc.collect` returns, as GraalPy's can be (:issue:`1963`). + """A finaliser run after :func:`gc.collect` returns, as GraalPy's can be (#1963). The stand-in finaliser runs on the third collection. With no grace the helper reports it as never run, the shape of the GraalPy leg's failure; with a @@ -491,7 +492,7 @@ def test_collect_until_waits_out_a_finaliser_that_runs_late(self) -> None: The collector and the clock are both fakes, with each collection costing a fake second, so the outcome does not depend on how fast the engine's own - :func:`gc.collect` is (:issue:`2016`). + :func:`gc.collect` is (#2016). """ clock = [0.0] diff --git a/tests/foundation/test_extraction_unnamed_stream_unit.py b/tests/foundation/test_extraction_unnamed_stream_unit.py index 9583927b73..04d5f321dd 100644 --- a/tests/foundation/test_extraction_unnamed_stream_unit.py +++ b/tests/foundation/test_extraction_unnamed_stream_unit.py @@ -98,7 +98,7 @@ class TestPathEngines(unittest.TestCase): """Engines that open the input by path refuse an unnamed stream up front. Scapy and PyShark hand ``Extractor._ifnm`` to their library as a file name, - so before the refusal the ```` label was opened as a path -- + so without the refusal the ```` label is opened as a path -- ``FileNotFoundError`` from inside :mod:`scapy`. The refusal is raised by :class:`~pcapkit.foundation.extraction.Extractor` itself, before the engine is constructed, so it does not depend on the engine being able to run. diff --git a/tests/foundation/traceflow/test_tcp.py b/tests/foundation/traceflow/test_tcp.py index 6d07b76d82..5d897d993a 100644 --- a/tests/foundation/traceflow/test_tcp.py +++ b/tests/foundation/traceflow/test_tcp.py @@ -38,9 +38,8 @@ def _packet(self, *, index: int, src: str = '192.0.2.1', dst: str = '198.51.100. def test_tcp_trace_ipv4_fin_submit_cache_callback_and_dump(self) -> None: """One direction, traced with ``bidirectional=False``. - This is the per-direction behaviour flow tracing had before conversations - became one flow, and it is still reachable on request -- so a single FIN - closes the flow, since in that mode a flow *is* one direction. + This is per-direction flow tracing, reachable on request -- so a single + FIN closes the flow, since in that mode a flow *is* one direction. """ from pcapkit.dumpkit.null import NotImplementedIO @@ -82,8 +81,8 @@ def _reply(self, **kwargs): def test_both_halves_of_a_connection_are_one_flow(self) -> None: """The two directions share a buffer, a label and an output file. - Keyed on (source, destination) they were two flows with two labels and two - dump files, leaving a caller to pair them up by reading the labels. + Keyed on (source, destination) they would be two flows with two labels and + two dump files, leaving a caller to pair them up by reading the labels. """ from pcapkit.dumpkit.null import NotImplementedIO @@ -159,11 +158,11 @@ def test_a_four_way_close_keeps_its_final_acknowledgement(self) -> None: def test_a_reused_port_pair_does_not_join_the_closed_connection(self) -> None: """A new connection on the same endpoints is a second flow. - This is the defect the review found. With the flow submitted on the second - FIN, the final ACK arrived after the buffer had been popped and opened a - stray one-packet buffer under the same canonical buffer ID; the next - connection to reuse the endpoints then merged into that stray buffer, and - two unrelated connections came back as one flow. + Were the flow submitted on the second FIN, the final ACK would arrive + after the buffer had been popped and open a stray one-packet buffer under + the same canonical buffer ID; the next connection to reuse the endpoints + would then merge into that stray buffer, and two unrelated connections + would come back as one flow. A SYN is what proves the previous connection can receive nothing further, so it is what finalises the old flow and starts a new one. @@ -228,10 +227,10 @@ def test_the_peers_syn_ack_joins_the_flow_rather_than_splitting_it(self) -> None self.assertEqual(flow.index, (1, 2)) def test_a_reset_ends_the_connection_as_a_close_does(self) -> None: - """RST is the other way a connection ends, and was not modelled at all. + """RST is the other way a connection ends, and has to be modelled too. - Without the flag reaching the tracer a reset connection looked merely - idle, so a later connection reusing the endpoints merged into it -- the + Without the flag reaching the tracer a reset connection looks merely + idle, so a later connection reusing the endpoints merges into it -- the same contamination as the FIN case, by a different route. """ diff --git a/tests/foundation/traceflow/test_traceflow_base.py b/tests/foundation/traceflow/test_traceflow_base.py index f6be87a681..456a9e886d 100644 --- a/tests/foundation/traceflow/test_traceflow_base.py +++ b/tests/foundation/traceflow/test_traceflow_base.py @@ -220,8 +220,8 @@ def trace(self, packet: DummyPacket, *, output: bool = False): def submit(self) -> tuple[DummyIndex, ...]: return () - # #514: registration is opt-in. Before it, the absent keyword fell back to - # ``cls.name`` -- i.e. ``__protocol_name__`` here -- and this registered + # #514: registration is opt-in. A fallback for the absent keyword to + # ``cls.name`` -- i.e. ``__protocol_name__`` here -- would register this # under ``'defaulttrace'``. A class attribute is not a registry key. register.assert_not_called() self.assertEqual(Default.name, 'DefaultTrace') @@ -235,8 +235,8 @@ def test_traceflow_subclass_rejects_unrecognised_keyword(self) -> None: The keyword used here is deliberately *not* ``name``; see the sibling test in ``tests/foundation/reassembly/test_reassembly_base.py`` for why - four class keyword names collide with :meth:`abc.ABCMeta.__new__` on - Python 3.10. The colliding case is pinned separately below. + four class keyword names collide with :meth:`abc.ABCMeta.__new__` below + Python 3.11. The colliding case is pinned separately below. """ from pcapkit.corekit.infoclass import Info, info_final diff --git a/tests/interface/test_core.py b/tests/interface/test_core.py index 89eac13929..81862047ac 100644 --- a/tests/interface/test_core.py +++ b/tests/interface/test_core.py @@ -92,9 +92,9 @@ def __init__(self, packet, layers=0) -> None: class InterfaceCoreTests(unittest.TestCase): def setUp(self) -> None: # ``isolate_modules`` rather than ``purge_modules``: ``_load_module`` below - # binds a non-generic ``ProtocolBase`` stand-in and five other stand-in - # modules under ``pcapkit.*`` names, none of which may outlive the test - # that installed them. See #660. + # binds a non-generic ``ProtocolBase`` stand-in and other stand-in modules + # under ``pcapkit.*`` names, none of which may outlive the test that + # installed them. See #660. isolate_modules(self) def _load_module(self): diff --git a/tests/interface/test_misc.py b/tests/interface/test_misc.py index 48bc9574a3..d8082ba978 100644 --- a/tests/interface/test_misc.py +++ b/tests/interface/test_misc.py @@ -98,11 +98,11 @@ def test_default_engine_finds_every_tcp_stream(self) -> None: self.assertGreaterEqual(len(stream.packets), 1) self.assertIsNotNone(stream.filename) # the merged conversation carries both of its frames, which is the point: - # keyed per direction they were two streams of one frame each + # keyed per direction they would be two streams of one frame each self.assertEqual(sorted(len(stream.packets) for stream in streams), [1, 2]) def test_per_direction_tracing_still_splits_the_conversation(self) -> None: - """``trace_bidirectional=False`` restores the older per-direction flows. + """``trace_bidirectional=False`` gives per-direction flows. The compatibility escape hatch has to keep working, and it is what makes the merge above measurable rather than merely asserted. @@ -131,11 +131,11 @@ def test_dpkt_engine_follows_every_stream(self) -> None: def test_scapy_engine_matches_the_default_engine(self) -> None: # Scapy dissects in.pcap in full -- its two ICMPv6-over-IPv6 frames, its # three IPv4/TCP frames and its one UDP frame -- so it must agree with the - # native engine rather than differ from it. - # This is the regression guard for #406: the engine imported only - # ``scapy.sendrecv``, so Scapy's ``conf.l2types`` registry was empty, - # ``PcapReader`` could not map link type 1, and all six frames arrived as - # ``Raw`` -- turning three streams into zero with nothing raised. + # native engine rather than differ from it. This is the regression guard + # for #406: the engine imported only ``scapy.sendrecv``, so Scapy's + # ``conf.l2types`` registry was empty, ``PcapReader`` could not map link + # type 1, and all six frames arrived as ``Raw`` -- turning every stream + # into none with nothing raised. import pcapkit with warnings.catch_warnings(): @@ -148,7 +148,7 @@ def test_scapy_engine_matches_the_default_engine(self) -> None: # Asserted first, and on the frame type rather than on the stream count, # because it names the *cause*. A regression of #406 fails here as "frame 0 # came back Raw", which points straight at the engine's import, instead of - # surfacing downstream as an unexplained 0 != 3 stream-count mismatch. + # surfacing downstream as an unexplained stream-count mismatch. self.assertNotIsInstance( extractor.frame[0], self._scapy_raw(), 'scapy returned frame 0 of in.pcap as an undissected Raw layer, so its ' diff --git a/tests/project/test_annotation_names.py b/tests/project/test_annotation_names.py index a30518889a..c2ca2bd04d 100644 --- a/tests/project/test_annotation_names.py +++ b/tests/project/test_annotation_names.py @@ -94,14 +94,13 @@ def _as_string(node: 'Optional[ast.expr]') -> 'Iterator[tuple[str, int]]': #: :class:`ast.TypeAlias` where the running interpreter has it, else ``()``. #: -#: ``ast.TypeAlias`` and ``ast.TypeVar`` are :pep:`695` nodes, new in Python 3.12, -#: and the oldest CI leg to run the suite is 3.8 (KTLO, #1633), the oldest supported -#: one 3.11 -- so naming either one directly raises :exc:`AttributeError` on 3.8-3.11, -#: and does so in *every* test in this module rather than one, because -#: :func:`bound_names` walks every node of every file. Resolving -#: them through :func:`getattr` to an empty tuple instead leaves both branches -#: fully live on 3.12+ and merely unreachable below it, since :func:`isinstance` -#: against ``()`` is always :data:`False`. +#: ``ast.TypeAlias`` and ``ast.TypeVar`` are :pep:`695` nodes, new in Python 3.12, and +#: the oldest CI leg to run the suite is 3.8 (KTLO, #1633) -- so naming either one +#: directly raises :exc:`AttributeError` on 3.8-3.11, and does so in *every* test in +#: this module rather than one, because :func:`bound_names` walks every node of every +#: file. Resolving them through :func:`getattr` to an empty tuple instead leaves both +#: branches fully live on 3.12+ and merely unreachable below it, since +#: :func:`isinstance` against ``()`` is always :data:`False`. #: #: A :data:`sys.version_info` comparison would work too, and is not chosen: it #: writes the version down a second time, next to the attribute it guards, and the @@ -189,7 +188,7 @@ def referenced_names(source: str) -> 'Optional[set[str]]': Which is why the descent below is explicit rather than :func:`ast.walk`: the two places a string inside an annotation is a *value* and not a type have to be skipped, or they become false positives. ``Literal["big", "little"]`` names no - type ``big``, and this package writes 20-odd of those; and everything after the + type ``big``, and this package writes many of those; and everything after the first argument of ``Annotated[T, ...]`` is metadata. Both are recognised by the subscripted name, which is how a type checker recognises them too. @@ -373,10 +372,10 @@ def test_a_literal_member_is_not_mistaken_for_a_type(self) -> None: The other half of following nested strings, and the reason the descent has to know what it is descending into: inside ``Literal`` a string is a - *value*, and this package writes twenty-odd of them. Following those - indiscriminately reports every literal member as an unresolvable name -- - a false positive on real code, which is worse than the missed defect it - was meant to fix. ``Annotated``'s trailing metadata is the same case. + *value*, and this package writes many of them. Following those + indiscriminately reports every literal member as an unresolvable name -- a + false positive on real code, which is worse than the missed defect it was + meant to fix. ``Annotated``'s trailing metadata is the same case. """ self.assertEqual( diff --git a/tests/project/test_bump_version.py b/tests/project/test_bump_version.py index dae431b375..5a6c5d7f7b 100644 --- a/tests/project/test_bump_version.py +++ b/tests/project/test_bump_version.py @@ -95,8 +95,8 @@ def _load_script(): ``util/`` is a directory of scripts rather than a package, so there is no import path to it. Loading it by location is safe because the bump runs under - a ``__main__`` guard -- which it did not, before the change these tests cover, - when importing the module rewrote two files as a side effect. + a ``__main__`` guard; without it, importing the module would rewrite two files + as a side effect. """ path = ROOT / 'util' / 'bump_version.py' @@ -178,8 +178,8 @@ def test_a_space_before_the_colon_is_refused_rather_than_missed(self) -> None: bump_version.plan_citation(spaced, '1.5.0b5', '2026-09-22') def test_the_refusal_is_a_runtime_error(self) -> None: - # Anything already catching the ``cannot find version`` RuntimeError this - # script has always raised keeps catching this one. + # Anything catching the ``cannot find version`` RuntimeError this script + # raises catches this one too. self.assertTrue(issubclass(bump_version.CitationFieldError, RuntimeError)) @@ -418,7 +418,7 @@ def test_a_file_without_a_release_date_is_reported(self) -> None: class VersionBumpTests(unittest.TestCase): - """:func:`~bump_version.bump`, whose behaviour the change leaves alone.""" + """:func:`~bump_version.bump`, the version arithmetic itself.""" def test_a_prerelease_bumps_its_counter(self) -> None: self.assertEqual(bump_version.bump('1.5.0b4'), '1.5.0b5') @@ -446,7 +446,7 @@ def test_every_bumped_version_carries_a_non_numeric_suffix(self) -> None: class InitRewriteTests(CitationTreeMixin, unittest.TestCase): - """:file:`pcapkit/__init__.py`, which must still be rewritten exactly as before.""" + """:file:`pcapkit/__init__.py`, which must be rewritten in place, its other lines kept.""" def write_init(self, version: str = '1.5.0b4') -> pathlib.Path: """Write a stand-in :file:`pcapkit/__init__.py` and return its path.""" @@ -487,12 +487,12 @@ def test_nothing_but_the_assignment_moves(self) -> None: self.assertEqual(before[:-1], after[:-1]) def test_the_fallback_reader_strips_the_closing_quote_and_newline(self) -> None: - # The fallback ran ``line.split('=')[1].strip(" '")``, which stops at the - # trailing newline and so returned ``"1.5.0b4'\n"`` -- quote and newline - # included -- which ``packaging`` rejects as an invalid version. The path - # had never worked; nothing noticed because the only caller installs the - # package first, so ``import pcapkit`` always succeeds and the fallback is - # unreachable in CI. Forced here by making that import fail. + # A fallback running ``line.split('=')[1].strip(" '")`` stops at the + # trailing newline and so returns ``"1.5.0b4'\n"`` -- quote and newline + # included -- which ``packaging`` rejects as an invalid version. Nothing + # in CI notices that, because the only caller installs the package first, + # so ``import pcapkit`` always succeeds and the fallback is unreachable. + # Forced here by making that import fail. self.write_init() cwd = os.getcwd() self.addCleanup(os.chdir, cwd) @@ -502,7 +502,7 @@ def test_the_fallback_reader_strips_the_closing_quote_and_newline(self) -> None: version = bump_version.current_version() self.assertEqual(version, '1.5.0b4') - # And the value is usable, which is the part that was broken. + # And the value is usable, which is what that fallback gets wrong. self.assertEqual(bump_version.bump(version), '1.5.0b5') def test_the_fallback_still_complains_when_there_is_no_version(self) -> None: diff --git a/tests/project/test_changelog_md.py b/tests/project/test_changelog_md.py index 701a38057c..64e2b2deeb 100644 --- a/tests/project/test_changelog_md.py +++ b/tests/project/test_changelog_md.py @@ -519,7 +519,7 @@ def test_a_role_entry_renders_without_residual_markup(self) -> None: class ResidualMarkupTests(ChangelogTreeMixin, unittest.TestCase): - """The guard against six regexes quietly copying through what they cannot convert.""" + """The guard against the regexes quietly copying through what they cannot convert.""" def test_an_unconvertible_directive_is_fatal(self) -> None: index = self.make_tree(entry=ENTRY + '\n.. note::\n\n Not in the subset.\n') @@ -549,7 +549,7 @@ def _reject(self, tail: str, expected: str) -> str: return message def test_an_inline_hyperlink_reference_is_fatal(self) -> None: - # The worst of the five: GFM renders ```text `_`` as broken inline + # The worst of these: GFM renders ```text `_`` as broken inline # code followed by a stray underscore, so the link target vanishes # silently rather than merely looking wrong. self._reject('\nSee `the RFC index `_ for more.\n', @@ -659,7 +659,7 @@ def test_markup_quoted_inside_a_code_span_is_not_a_leftover(self) -> None: def test_an_rfc_role_with_an_anchor_is_not_a_leftover(self) -> None: # The gate-level half of #592: rule 2 covers the ``:rfc:`` role, so an entry # citing a section of an RFC must pass rather than be refused as markup the - # rules do not cover. #590's entry was rewritten to work around this. + # rules do not cover. markdown = self.convert(ENTRY.replace(':rfc:`4303`', ':rfc:`4303#section-2.1`')) self.assertEqual(changelog_md.residual(markdown), []) diff --git a/tests/project/test_ci_timing.py b/tests/project/test_ci_timing.py index 03ca3d163a..0b110edbbd 100644 --- a/tests/project/test_ci_timing.py +++ b/tests/project/test_ci_timing.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """Tests for :file:`util/ci_timing.py`, the weekly CI timing review, and its inputs. -GitHub issue #1538 added the review and the per-test timings it reads. Four things -are checked, offline, since this tier has no network and no token: +GitHub issue #1538 added the review and the per-test timings it reads. These are +checked, offline, since this tier has no network and no token: * :class:`TestArithmetic` and :class:`TestWorkflowText` -- the quantiles, the projection to a cap, and the indentation reader the caps come from, on the real @@ -450,7 +450,7 @@ def test_one_session_serves_every_call(self) -> 'None': @unittest.skipUnless(HAS_LXML, NO_LXML) class TestStrictJUnit(unittest.TestCase): - """#1894: JUnit is parsed by lxml with a hardened parser, where ElementTree read it before.""" + """#1894: JUnit is parsed by lxml with a hardened parser, not by ElementTree.""" CASE = b'' PARSED = [('tests.a.TestA', 'test_a', 0.5)] @@ -472,7 +472,7 @@ def test_nesting_past_the_depth_limit_is_refused(self) -> 'None': def test_an_entity_is_neither_expanded_nor_read(self) -> 'None': """An internal one stays an entity node, and one naming a file or a URL is never opened. - ElementTree expanded the internal one, and refused the document over the + ElementTree expands the internal one, and refuses the document over the external ones; neither is what this asks of the parser. """ @@ -1150,7 +1150,7 @@ def test_both_pytest_runs_write_the_file_the_upload_reads(self) -> 'None': self.assertIn('--durations=', section) def test_the_legs_the_review_reads_exist(self) -> 'None': - """``test``'s legs, from .github/python-versions.toml since #1660.""" + """``test``'s legs, from .github/python-versions.toml (#1660).""" found = matrix_legs(matrix('unit-tests.yml', 'test')) for leg in ci_timing.JUNIT_LEGS: with self.subTest(leg=leg): diff --git a/tests/project/test_codeql_config_2130.py b/tests/project/test_codeql_config_2130.py index 3db6529dfc..118cf0931c 100644 --- a/tests/project/test_codeql_config_2130.py +++ b/tests/project/test_codeql_config_2130.py @@ -13,8 +13,8 @@ * :class:`TestTheInitStep` checks ``Initialize CodeQL`` in :file:`codeql-analysis.yml` reads :file:`.github/codeql/codeql-config.yml` and nothing else. -* :class:`TestTheConfig` checks the config ignores :file:`tests` and changes - nothing else: every query still runs, over every other path. +* :class:`TestTheConfig` checks the config ignores :file:`tests` and sets + nothing else: every query runs, over every other path. """ @@ -59,7 +59,7 @@ def test_no_other_step_passes_a_config(self) -> None: class TestTheConfig(unittest.TestCase): - """:file:`tests` left out, and the scan otherwise as it was.""" + """:file:`tests` left out, and no other filter on the scan.""" def test_only_the_tests_are_ignored(self) -> None: self.assertEqual(config()['paths-ignore'], ['tests']) diff --git a/tests/project/test_codeql_report_2030.py b/tests/project/test_codeql_report_2030.py index e7dad4b76c..9193de2cc2 100644 --- a/tests/project/test_codeql_report_2030.py +++ b/tests/project/test_codeql_report_2030.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- """#2030: CodeQL's ``discussion`` job, which posts the open code-scanning alerts to Discussions. -* :class:`TestTheJob` pins :file:`codeql-analysis.yml`: ``analyze`` as it was, - ``discussion`` the one job holding ``security-events: read`` and - ``discussions: write``, run after ``analyze`` on a push or the schedule and - never on a pull request, the mode each event maps to, the thread's title, and - the status the report step hands on. +* :class:`TestTheJob` pins :file:`codeql-analysis.yml`: ``analyze`` with the + permissions :data:`ANALYZE` records, ``discussion`` the one job holding + ``security-events: read`` and ``discussions: write``, run after ``analyze`` on a + push or the schedule and never on a pull request, the mode each event maps to, + the thread's title, and the status the report step hands on. * :class:`TestThePostStep` runs ``Post the report to Discussions`` with a stand-in ``python3``: the report step's status, or ``analyze``'s result where the step wrote none. @@ -96,7 +96,7 @@ def alert(number: 'int', rule: 'str', level: 'str', security: 'str' = '', path: class TestTheJob(unittest.TestCase): - """``discussion`` beside ``analyze``, and ``analyze`` left as it was.""" + """``discussion`` beside ``analyze``, and ``analyze`` with its own permissions.""" def test_only_the_posting_job_holds_its_scopes(self) -> None: spec = load(NAME) diff --git a/tests/project/test_conda_completeness_counts_every_leg_1922.py b/tests/project/test_conda_completeness_counts_every_leg_1922.py index fe0831e02b..2aae9550c0 100644 --- a/tests/project/test_conda_completeness_counts_every_leg_1922.py +++ b/tests/project/test_conda_completeness_counts_every_leg_1922.py @@ -2,8 +2,8 @@ """``version_check``'s Anaconda completeness check counts every platform the ``conda`` legs publish (#1922). * The platforms it counts, :file:`util/release_published.py`'s ``SUBDIRS`` - since #1967, are the ones the ``conda`` job's ``os`` legs map to, read from - that job's matrix and :file:`util/release_conda.py`'s ``PLATFORMS``, which its + (#1967), are the ones the ``conda`` job's ``os`` legs map to, read from that + job's matrix and :file:`util/release_conda.py`'s ``PLATFORMS``, which its steps run. * Run as the workflow runs it, with ``curl`` stubbed, a release reads complete only with each of those platforms for each build, so a missing ``osx-arm64`` diff --git a/tests/project/test_conda_intel_cli_upload_2044.py b/tests/project/test_conda_intel_cli_upload_2044.py index 07c12a0a51..b153d8bd68 100644 --- a/tests/project/test_conda_intel_cli_upload_2044.py +++ b/tests/project/test_conda_intel_cli_upload_2044.py @@ -13,7 +13,7 @@ Each step's ``if:`` is evaluated per matrix leg with :func:`tests.project.test_release_gates.evaluate_condition`. The CLI step's -script, :file:`util/release_conda.py` ``upload`` since #1967, is run with a +script, :file:`util/release_conda.py` ``upload`` (#1967), is run with a stand-in ``conda`` and ``anaconda``, and the arguments it passes are compared with those :func:`action_arguments` builds as the action's "Upload to anaconda.org" step does. diff --git a/tests/project/test_conda_intel_miniforge_2044.py b/tests/project/test_conda_intel_miniforge_2044.py index 03615f0991..731a23a6c7 100644 --- a/tests/project/test_conda_intel_miniforge_2044.py +++ b/tests/project/test_conda_intel_miniforge_2044.py @@ -89,7 +89,7 @@ def installer(received: 'dict[str, str]') -> 'str': class TestTheIntelLegsInstallMiniforge(unittest.TestCase): - """Miniforge on ``macos-26-intel``, and the former installer on every other leg.""" + """Miniforge on ``macos-26-intel``, and Miniconda on every other leg.""" def test_the_step_is_setup_miniconda(self) -> None: for name, job_id in JOBS: @@ -132,7 +132,7 @@ def python_input(entry: 'dict[str, Any]', os: 'str', python: 'str') -> 'str': class TestTheIntelLegsKeepBasesPython(unittest.TestCase): - """#2123: no Python install into base on ``macos-26-intel``; the other legs as before.""" + """#2123: no Python install into base on ``macos-26-intel``; other legs install all but 3.15.""" supported = () # type: tuple[str, ...] diff --git a/tests/project/test_conda_native_platforms_1822.py b/tests/project/test_conda_native_platforms_1822.py index 9bd0d0228f..c5abc6e2a5 100644 --- a/tests/project/test_conda_native_platforms_1822.py +++ b/tests/project/test_conda_native_platforms_1822.py @@ -2,10 +2,10 @@ """Each conda package carries binaries for its own platform (#1822). * Both conda jobs map each runner label of their ``os`` axis, through - :file:`util/release_conda.py`'s ``PLATFORMS`` since #1967, to the conda - platform of that runner's own architecture, as GitHub's runner-images table - gives it (the snapshot in :file:`python_versions_fixtures`), and between them - build exactly :data:`conda_vendor.PLATFORMS`, win-64 included (#2060). + :file:`util/release_conda.py`'s ``PLATFORMS`` (#1967), to the conda platform + of that runner's own architecture, as GitHub's runner-images table gives it + (the snapshot in :file:`python_versions_fixtures`), and between them build + exactly :data:`conda_vendor.PLATFORMS`, win-64 included (#2060). * Neither job runs ``conda convert``. Run as the job runs it, with a stand-in ``conda``, a leg publishes the one package conda built, and fails before any upload when that is not the platform its label maps to. @@ -69,7 +69,7 @@ #: as read on 2026-10-16; the test below checks the snapshot of the image table too. LEGS = {'macos-latest': 'osx-arm64', 'macos-26-intel': 'osx-64', 'ubuntu-latest': 'linux-64', 'windows-latest': 'win-64'} -#: Each job's legs, the same since #2060 gave the release job its Windows one. +#: Each job's legs, alike for both (#2060 gave the release job its Windows one). JOB_LEGS = {('cron-conda.yml', 'conda-dist'): LEGS, ('create-release.yml', 'conda'): LEGS} #: ``platform.machine()``, as the runner-images table names the architecture. ARCH = {'x86_64': 'x64', 'arm64': 'arm64', 'AMD64': 'x64'} diff --git a/tests/project/test_conda_publish_dispatch_2060.py b/tests/project/test_conda_publish_dispatch_2060.py index a47486f399..23a92fd716 100644 --- a/tests/project/test_conda_publish_dispatch_2060.py +++ b/tests/project/test_conda_publish_dispatch_2060.py @@ -26,7 +26,7 @@ :class:`TestTheReportOfAnUnpushedForcedBump`: where Push Changes does not push a forced bump, which no later run repeats, it warns that nothing was published and to dispatch again, and the report says so too; every other - run's notice is as before. + run's notice is the ordinary one. """ @@ -325,7 +325,7 @@ def test_the_bump_steps_follow_it(self) -> None: with self.subTest(step=label): self.assertTrue(step_runs('conda-update', label, context(DISPATCH, 'true', bump='true'))) self.assertFalse(step_runs('conda-update', label, context('schedule', 'true', bump='false'))) - # A build-only run bumps on a change as before, and pushes nothing. + # A build-only run still bumps on a change, and pushes nothing. self.assertTrue(step_runs('conda-update', 'Bump Version', context(DISPATCH, 'false', bump='true'))) self.assertFalse(step_runs('conda-update', 'Push Changes', context(DISPATCH, 'false', bump='true'))) @@ -358,7 +358,7 @@ class TestAnUnpushedForcedBumpSaysSo(CondaStepHarness, unittest.TestCase): No later run repeats a forced bump, so the notice that leaves the bump to the next Conda Update would be wrong: the run ends green having published nothing. Each path warns instead and says to dispatch again; with no forced - bump, each notice is as before, byte for byte. + bump, each notice is the ordinary one, byte for byte. """ diff --git a/tests/project/test_conda_windows_leg_2060.py b/tests/project/test_conda_windows_leg_2060.py index 1790dad837..1be988ec96 100644 --- a/tests/project/test_conda_windows_leg_2060.py +++ b/tests/project/test_conda_windows_leg_2060.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- """#2060: the conda jobs build, smoke-test and publish win-64. -Phase 1 gave ``conda-dist`` in :file:`.github/workflows/cron-conda.yml` a -non-blocking ``windows-latest`` leg that published nothing. Once its Conda -Update runs were green, phase 2 gave ``conda`` in -:file:`.github/workflows/create-release.yml` the leg too, made both blocking, and -uploaded from both with the anaconda-client CLI, as the Intel legs do (#2044). +``conda-dist`` in :file:`.github/workflows/cron-conda.yml` and ``conda`` in +:file:`.github/workflows/create-release.yml` each have a blocking +``windows-latest`` leg, which uploads with the anaconda-client CLI, as the Intel +legs do (#2044). * :class:`TestTheWindowsLegPublishes`: on ``windows-latest`` each job runs one upload, "Upload conda packages to Anaconda (Windows)", evaluated with @@ -73,7 +72,7 @@ ANACONDA_UPLOADS = ['Upload conda packages to Anaconda', INTEL_UPLOAD, WINDOWS_UPLOAD] #: What publishes besides :data:`PUBLISHING_MARKERS`: Conda Update's GitHub Release upload. MORE_MARKERS = ('ncipollo/release-action',) -#: The step that ran phase 1's warnings. +#: A non-blocking leg's warnings step, which the blocking leg must not have. SUMMARY = 'Windows summary' @@ -189,7 +188,7 @@ def test_it_is_the_intel_call_in_a_login_shell(self) -> None: self.assertEqual(names.index(WINDOWS_UPLOAD), names.index(INTEL_UPLOAD) + 1) def test_the_check_sees_an_upload_gated_off_the_leg(self) -> None: - """So :meth:`test_the_windows_leg_runs_its_own_upload_alone` reads the gate, as phase 1 had it.""" + """So :meth:`test_the_windows_leg_runs_its_own_upload_alone` reads the gate, not only the step list.""" for name, job_id in JOBS: job = copy.deepcopy(spec(name, job_id)) job['steps'] = [entry for entry in job['steps'] if entry.get('name') != WINDOWS_UPLOAD] @@ -503,7 +502,7 @@ def test_each_var_reads_the_value_and_refuses_an_unset_one(self) -> None: self.assertEqual(str(caught.exception), 'PCAPKIT_UNSET') def test_build_gets_past_its_variables(self) -> None: - """``build`` reads both variables and runs ``conda build``, where it raised AttributeError.""" + """``build`` reads both variables and runs ``conda build``, rather than raising AttributeError.""" with tempfile.TemporaryDirectory() as temp: output = os.path.join(temp, 'output') out_dir = os.path.join(temp, 'compilation-x') diff --git a/tests/project/test_const_get_overrides_documented_1921_unit.py b/tests/project/test_const_get_overrides_documented_1921_unit.py index cf2c9efe26..032583039b 100644 --- a/tests/project/test_const_get_overrides_documented_1921_unit.py +++ b/tests/project/test_const_get_overrides_documented_1921_unit.py @@ -8,11 +8,11 @@ private member off its page, so a ``:meth:`get``` naming one renders as an unlinked ``get()`` chip. -Whether a reference resolves is a property of the built inventory, which the -pull request checks in the rendered HTML. What this pins is the source-side -precondition: no override carries the tag, in the generated module or in the -vendor template that regenerates it, and no ``:meth:`` reference to one lands -on a member marked private. +Whether a reference resolves is a property of the built inventory, checked in +the rendered HTML. What this pins is the source-side precondition: no +override carries the tag, in the generated module or in the vendor template +that regenerates it, and no ``:meth:`` reference to one lands on a member +marked private. """ diff --git a/tests/project/test_conventions_doc_claims.py b/tests/project/test_conventions_doc_claims.py index cd1f712c38..ae6802c3ed 100644 --- a/tests/project/test_conventions_doc_claims.py +++ b/tests/project/test_conventions_doc_claims.py @@ -3,10 +3,10 @@ The *House Conventions* pages, under :file:`docs/source/contributing/conventions/`, record the settled design rulings, and most of what they record is reasoning -- -which no test can check. Some of it is not: a count of classes, a per-class -classification, a retired name, an exception type. Those are the parts that rot -silently, because Sphinx builds without ``-W`` and without ``nitpicky``, so a page -whose every factual claim has gone stale still renders and CI still passes. +which no test can check. Some of it is not: a per-class classification, a retired +name, an exception type. Those are the parts that rot silently, because Sphinx +builds without ``-W`` and without ``nitpicky``, so a page whose every factual claim +has gone stale still renders and CI still passes. GitHub issue #918 part 1 harvested the settled rulings onto a single page; part 2 split it into :file:`docs/source/contributing/conventions/`, one file per @@ -66,7 +66,7 @@ Deliberately **not** checked here: whether the page's cross-references resolve. That is a property of the built inventory rather than of the source, for the reason :file:`tests/project/test_documentation_claims.py` gives at length, and the honest check -is the rendered HTML. The measured result is recorded in the pull request instead. +is the rendered HTML. """ @@ -99,21 +99,18 @@ INDEX = CONVENTIONS_DIR / 'index.rst' #: Every ``.. _label:`` anchor, in the order the index's toctree lists the files in. -#: ``mint-criterion``, ``sentinel-convention`` and ``registry-protocol`` predate -#: #918; ``extension-header-subclassing`` arrived with it, and ``process`` came -#: next, on the owner's ruling that the settled *process* -#: rulings -- the ``all`` extra, what a changelog entry is, and what the labels -#: mean -- get a page of their own rather than being wedged onto a code-convention -#: page. ``documentation`` came after it, carrying the rulings GitHub issue #719 -#: settled about the prose itself -- heading case, when a Mermaid graph beats a -#: paragraph, and what a sentence on these pages may claim -- which had until then -#: lived only in that thread. ``protocol-layer-placement`` carries the other ruling -#: that thread settled -- which subpackage a dissector belongs in, decided by designed -#: function rather than by encapsulation -- and sits beside the other class-hierarchy -#: page. ``test-coverage`` came next, carrying GitHub issue #1203's rules for what the -#: test suite has to prove, and ``utilities-or-corekit`` came last, carrying GitHub -#: issue #1712's rule for which of those two packages a module belongs in. Every -#: anchor is the bare file stem, which is what :data:`PAGES` below depends on. +#: ``process`` holds, on the owner's ruling, the settled *process* rulings -- the +#: ``all`` extra, what a changelog entry is, and what the labels mean -- on a page of +#: their own rather than wedged onto a code-convention page. ``documentation`` carries +#: the rulings GitHub issue #719 settled about the prose itself -- heading case, when +#: a Mermaid graph beats a paragraph, and what a sentence on these pages may claim. +#: ``protocol-layer-placement`` carries the other ruling that thread settled -- which +#: subpackage a dissector belongs in, decided by designed function rather than by +#: encapsulation -- and sits beside the other class-hierarchy page. ``test-coverage`` +#: carries GitHub issue #1203's rules for what the test suite has to prove, and +#: ``utilities-or-corekit`` GitHub issue #1712's rule for which of those two packages +#: a module belongs in. Every anchor is the bare file stem, which is what +#: :data:`PAGES` below depends on. ANCHORS = ( 'mint-criterion', 'sentinel-convention', @@ -364,8 +361,8 @@ def test_the_index_preamble_does_not_claim_to_be_a_ruling_page_itself(self) -> ' :data:`INDEX`, which carries no ruling of its own; its children do. The second is worse than stale, because it is the standing instruction #918 part 3 exists to keep alive, and on the index it tells a contributor to - write onto the wrong file. #918 part 2's first round moved the preamble - across unedited, and a cross-review caught it. + write onto the wrong file. A preamble moved across from a page unedited + fails this. Checked as a ban on the self-referential singular ``"this page"`` rather than against those exact sentences, so a rewrite that makes the same @@ -415,10 +412,10 @@ def test_the_page_says_phase_two_is_complete_and_the_walk_agrees(self) -> 'None' """*"No enumeration remains outside the hierarchy"* -- against a runtime walk. GitHub issue #930 finished phase 2, so the page says it landed for every - non-registry enumeration. That replaces the figure checks on how many - remained outside and how many of the total had landed (#1811), which said - no more than the walk finding none outside -- and that is what fails here - once an enumeration is added outside :class:`~pcapkit.corekit.enum.EnumLookup`. + non-registry enumeration. No figure is checked for how many remain outside or + how many of the total landed (#1811): it would say no more than the walk + finding none outside -- and that is what fails here once an enumeration is + added outside :class:`~pcapkit.corekit.enum.EnumLookup`. """ for claim in ('the phase landed for every non-registry enumeration', '**No enumeration remains outside the hierarchy**'): @@ -451,11 +448,10 @@ def test_the_page_names_every_enumeration_outside_the_hierarchy(self) -> 'None': def test_the_audit_population_claims_hold_for_the_whole_population(self) -> 'None': """*The Audit, per Class*'s own population, against the same runtime walk. - The section opened with figures for the registries, their files, the - :class:`int`-valued, flag and ``aenum.StrEnum`` splits and the grand total, - which GitHub issue #949 wired to the walk and #1811 removed with the figures - themselves (#1103's ruling: a count drifts). What the section still claims of - the whole population is checked here: every registry is :class:`int`- or + The section gives no figures for the registries, their files, the + :class:`int`-valued, flag and ``aenum.StrEnum`` splits or the grand total + (#1811; #1103's ruling: a count drifts). What it claims of the whole + population is checked here: every registry is :class:`int`- or ``str``-valued, and every one of them is under :mod:`pcapkit.const`. """ @@ -579,7 +575,7 @@ def test_the_table_was_parsed(self) -> 'None': 'assertions below assume may no longer hold') def test_the_table_covers_the_whole_family_and_nothing_else(self) -> 'None': - """A ninth extension header has to reach the page, not only the code.""" + """A new extension header has to reach the page, not only the code.""" listed = {name for row in self.rows[1:] for name in self._named(row[0])} self.assertEqual(listed, set(self.family), 'the page and IPv6_Ext.__subclasses__() disagree about which ' @@ -649,7 +645,7 @@ def test_the_retired_base_name_is_absent_from_the_package(self) -> 'None': '#924 is that it was an intermediate state and never released') def test_the_shared_base_still_carries_the_name_the_ruling_left(self) -> 'None': - """The other half: the rename landed, rather than the name simply going.""" + """The other half: the name was renamed, not simply removed.""" from pcapkit.protocols.internet.ipv6_ext import IPv6_Ext self.assertEqual(IPv6_Ext.__name__, 'IPv6_Ext') @@ -773,15 +769,14 @@ class GetOverrideContractTests(unittest.TestCase): on ``quiet=True``, #935 on an advertised signature that is refused, #940 on deleting an override that only reimplements the base. The remaining one, the ``@classmethod`` requirement for delegation, is **not** a ruling and is not - presented as one: it is - forced by the language, since zero-argument :func:`super` inside a - ``@staticmethod`` has nothing to bind, and #913 set the shape that #908 then - followed. Each states something the tree can be asked about. The page's - half is what rots: the same claims are already pinned in ``tests/corekit`` - and ``tests/protocols`` against the *code*, so a later change that moves the code + presented as one: it is forced by the language, since zero-argument :func:`super` + inside a ``@staticmethod`` has nothing to bind, and #913 set the shape that #908 + then followed. Each states something the tree can be asked about. The page's half + is what rots: the same claims are already pinned in ``tests/corekit`` and + ``tests/protocols`` against the *code*, so a later change that moves the code fails there, while a page still describing the old shape fails nothing. This - class is the other direction: it is what catches a stale audit row such as - *"Two of them define a ``get`` of their own"*, false since #940. + class is the other direction: it is what catches a stale audit row such as *"Two + of them define a ``get`` of their own"*, which #940 made false. """ @@ -852,11 +847,10 @@ def test_a_delegating_override_is_a_classmethod(self) -> 'None': 'OptionType.get now delegates, so the page is wrong to list it ' 'as the surviving staticmethod') - # Execute the outcomes rather than assert the page names them. The - # first draft of this test only grepped for the error string, and the - # string it grepped for was the wrong one -- the page claimed + # Execute the outcomes rather than assert the page names them. A grep + # for the error string cannot tell a wrong one -- the page once claimed # `RuntimeError: super(): no arguments` for a shape that actually raises - # `TypeError`, and a prose-only assertion could not tell. + # `TypeError`, and a prose-only assertion passed it. class _Base: @classmethod def get(cls, key, default=None): # noqa: D102 @@ -874,12 +868,12 @@ def get(): # type: ignore[override] # noqa: D102 with self.assertRaises(TypeError) as caught: _WithParam.get('BASELINE-CONTROL') - # Substring chosen to survive CPython's own rewording: 3.10-3.12 say - # "obj must be an instance or subtype of type" while 3.13+ say "obj - # (instance of str) is not an instance or subtype of type (Cls)". Only - # `instance or subtype of type` is common to both, and pinning either - # full sentence would fail the required Compat legs that word it the other way -- - # invisible locally, since this venv is 3.14. + # Substring chosen to survive CPython's own rewording: 3.10-3.12 say "obj must be + # an instance or subtype of type" while 3.13+ say "obj (instance of str) is not + # an instance or subtype of type (Cls)". Only `instance or subtype of type` is + # common to both, and pinning either full sentence would fail the required Compat + # legs that word it the other way -- invisible on a local run under one + # interpreter. self.assertIn('instance or subtype of type', str(caught.exception), 'zero-argument super() in a staticmethod binds the first ' 'positional parameter -- the lookup key -- as its instance') @@ -931,8 +925,8 @@ def test_the_base_raises_a_name_miss_quietly(self) -> 'None': Measured rather than read off the source, because the cost the ruling turns on is the side effect: a loud :class:`BaseError` logs at - :data:`logging.CRITICAL` and, until #983, also set - :data:`sys.tracebacklimit` to ``0`` for the whole process (#362), and a + :data:`logging.CRITICAL` -- without #983's fix it also sets + :data:`sys.tracebacklimit` to ``0`` for the whole process (#362) -- and a quiet one does neither. """ @@ -952,12 +946,11 @@ def _restore() -> 'None': self.addCleanup(_restore) - # Run the behavioural check unconditionally. An earlier version guarded it - # with `if not had:`, which meant a test that had already set - # sys.tracebacklimit turned this into a prose-only check that still - # reported pass -- silent degradation under test-order pollution rather - # than a failure. Clearing it first is safe because _restore puts whatever - # was there back. + # Run the behavioural check unconditionally. Guarded with `if not had:`, a + # test that had already set sys.tracebacklimit would turn this into a + # prose-only check that still reports pass -- silent degradation under + # test-order pollution rather than a failure. Clearing it first is safe + # because _restore puts whatever was there back. if had: del sys.tracebacklimit with self.assertRaises(EnumKeyError): @@ -968,19 +961,19 @@ def _restore() -> 'None': 'ruled overrides follow the base here, not the reverse') flat = self._flat() - # The *claim*, not his wording. This asserted `'not be loud'` until GitHub issue - # #949, which is a fragment of the sentence he typed on the issue rather than - # anything the ruling turns on; the substance is that the first answer on #933 is - # not the ruling and the second one is. Occurs once on the page, checked. + # The *claim*, not his wording (GitHub issue #949): a fragment of the sentence he + # typed on the issue is not anything the ruling turns on; the substance is that + # the first answer on #933 is not the ruling and the second one is. Occurs once + # on the page, checked. self.assertIn('first declined, then reversed', flat, "the page no longer records that #933's ruling is the owner's " 'reversal rather than his first answer, which is the whole reason ' 'both answers are on the issue') # Anchored to the headline sentence, not the bare token. `quiet=True` occurs - # more than once on the page, so `assertIn('``quiet=True``')` was satisfied by a - # later mention -- inverting the ruling itself to `quiet=False` failed nothing. - # The same shape as the `labels: bug` assertion and the bare figure, which - # passed the same way, so the rule is explicit -- never assert a token + # more than once on the page, so `assertIn('``quiet=True``')` is satisfied by a + # later mention -- inverting the ruling itself to `quiet=False` would fail + # nothing. The same shape as the `labels: bug` assertion and the bare figure, + # which passed the same way, so the rule is explicit -- never assert a token # that appears more than once on the page it is meant to pin. self.assertIn('Raise the way the base raises, which means** ``quiet=True``', flat, 'the page no longer states the ruling as its headline, so a ' @@ -1044,8 +1037,8 @@ def test_a_non_string_key_raises_a_value_miss_on_every_helper(self) -> 'None': # Derived, not listed: the population is every EnumLookup subclass those two # modules *define* -- re-exports from pcapkit.const.ngap.* are not helpers of - # theirs. An earlier version hard-coded a tally that disagreed with both its - # own name and the page, so the set is measured here and asserted by name. + # theirs. A hard-coded tally can disagree with both its own name and the + # page, so the set is measured here and asserted by name. import inspect from pcapkit.corekit.enum import EnumLookup @@ -1096,9 +1089,9 @@ def _optional_dependencies() -> 'dict[str, list[str]]': (``grep -n tomli pyproject.toml`` finds nothing), so there is nothing to fall back to and adding one for a docs-claims test is not worth it. - **Verified against** :mod:`tomllib` **rather than asserted.** A second version - matched arrays with a non-greedy bracket pattern, which stopped at the first - literal ``]`` -- which mis-parsed several extras, silently: + **Verified against** :mod:`tomllib` **rather than asserted.** Matching arrays + with a non-greedy bracket pattern, which stops at the first literal ``]``, was + measured to mis-parse several extras, silently: * ``vendor`` came back **empty**, because the ``]`` inside ``"requests[socks]"`` closed the match early. @@ -1127,9 +1120,9 @@ def _optional_dependencies() -> 'dict[str, list[str]]': 'pyproject.toml has no [project.optional-dependencies] section, so the ' "page's extras claims have nothing to be checked against") - # Comments first: a comment may contain ``]`` or a quoted string, and both fooled - # the previous version. A ``#`` inside a requirement string is not a comment, so - # quoted spans are skipped rather than blindly cut at the first ``#``. + # Comments first: a comment may contain ``]`` or a quoted string, and both fool a + # bracket match. A ``#`` inside a requirement string is not a comment, so quoted + # spans are skipped rather than blindly cut at the first ``#``. stripped = [] for line in block.group(1).splitlines(): out, quote = [], None @@ -1175,10 +1168,9 @@ class ProcessConventionTests(unittest.TestCase): deliberately pins neither against the owner's own phrasing. Quoting him is what the page is forbidden to do here -- his instruction on GitHub issue #918 was to paraphrase -- and asserting a quoted sentence is separately a trap this module has - already been bitten by: tests elsewhere in this file pinned two off-hand replies of - his as literal strings, which made a sentence typed into a GitHub thread a CI build - dependency. GitHub issue #949 removed the last of those. Attribution lives in the - issue number the page cites; the tests check substance. + already been bitten by: pinning an off-hand reply of his as a literal string makes + a sentence typed into a GitHub thread a CI build dependency (GitHub issue #949). + Attribution lives in the issue number the page cites; the tests check substance. """ @@ -1283,12 +1275,12 @@ def test_the_page_keeps_the_engine_and_installability_exclusions_apart(self) -> self.assertIn(extra, extras, f'the {extra} extra is gone, so the page names an ' 'extra a user cannot install') - # Compare *requirements*, not the extra's name against them. The - # previous form asked whether 'DPKT' was an element of - # ['emoji', 'cryptography>=3.4', 'pycrate'] -- element equality against - # a requirement string, so it could never be true and the regression its - # own message names was unreachable. Measured: widening `all` back to - # the pre-#910 engine set left this green while the sibling assertEqual + # Compare *requirements*, not the extra's name against them. Asking + # whether 'DPKT' is an element of ['emoji', 'cryptography>=3.4', + # 'pycrate'] is element equality against a requirement string, which can + # never be true, so the regression its own message names would be + # unreachable. Measured: with that form, widening `all` to take the + # engine extras back left this green while the sibling assertEqual # caught it. self.assertFalse( set(extras[extra]) & set(extras['all']), @@ -1306,11 +1298,10 @@ def test_the_page_denies_one_changelog_entry_per_commit(self) -> 'None': """The ruling's substance, in the page's words rather than the owner's. The claim is narrow on purpose: entries are grouped by topic and are **not** - one per commit. The grouping scheme itself was open when this test was first - written and the page then carried a disclaimer saying so; it was ruled shortly - afterwards -- a section per top-level module, with the kind headings nested - inside -- so the page records the scheme and this checks for it. What is still - unruled is narrower: which module an entry spanning several belongs under. + one per commit. The grouping scheme is ruled too -- a section per top-level + module, with the kind headings nested inside -- so the page records the scheme + and this checks for it. What is still unruled is narrower: which module an + entry spanning several belongs under. """ self.assertIn('not one line per commit', self.flat, @@ -1467,13 +1458,12 @@ def test_the_page_says_the_labels_are_set_by_hand(self) -> 'None': if line.startswith('labels:'): templated[template.name] = line.split(':', 1)[1].strip() - # The dependabot attribution, which had no pin at all -- which is how the page - # came to name ``github_actions`` as dependabot-applied while - # .github/dependabot.yml configured only ``pip``. Since #1968 it configures - # ``github-actions`` too, so the label is dependabot's on a workflow bump and - # hand-applied everywhere else. Getting either half wrong is this section's own - # point stated backwards, and the places the page mentioned it once disagreed - # with each other about ``python``. + # The dependabot attribution: unpinned, the page once named ``github_actions`` + # as dependabot-applied while .github/dependabot.yml configured only ``pip``. + # It configures ``github-actions`` too (#1968), so the label is dependabot's on + # a workflow bump and hand-applied everywhere else. Getting either half wrong + # is this section's own point stated backwards, and the places the page + # mentioned it once disagreed with each other about ``python``. dependabot = (ROOT / '.github' / 'dependabot.yml').read_text(encoding='utf-8') ecosystems = re.findall(r'package-ecosystem:\s*"([^"]+)"', dependabot) @@ -1488,14 +1478,13 @@ def test_the_page_says_the_labels_are_set_by_hand(self) -> 'None': # issue #949 asked to be looked at. Every sentence pairing the tool with the # label is checked instead, so the phrasing does not matter. # - # Proximity is not the test, and a first attempt at #949 that used it was wrong: - # the section then stated more than once, deliberately, that ``github_actions`` - # was *not* dependabot's, and each of those sentences named the tool and the - # label together. - # Distinguishing an attribution from a denial by looking for a negation nearby - # also failed -- the attributing bullet's own sentence ended "so it never opens a - # workflow bump here", so the negation was present in the sentence that made the - # claim as well as in the two that denied it. + # Proximity is not the test, and was measured wrong on #949: the section then + # stated more than once, deliberately, that ``github_actions`` was *not* + # dependabot's, and each of those sentences named the tool and the label + # together. Distinguishing an attribution from a denial by looking for a + # negation nearby also failed -- the attributing bullet's own sentence ended + # "so it never opens a workflow bump here", so the negation was present in the + # sentence that made the claim as well as in the two that denied it. # # So the page's attribution is *parsed* instead: the one clause that says what # dependabot puts on its pull requests is located, the labels named inside it are @@ -1600,7 +1589,7 @@ def test_every_issue_link_number_matches_its_own_url(self) -> 'None': f'can see and no build can warn about: {mismatched}') def test_the_page_describes_the_changelog_grouping_as_it_is(self) -> 'None': - """The current shape of ``1.5.0.rst``, counted rather than eyeballed. + """The shape of ``1.5.0.rst``, counted rather than eyeballed. What is checked is the file's shape: one ``-`` underlined section per top-level module plus one for what belongs to none, each carrying ``~`` @@ -1634,8 +1623,8 @@ def test_the_page_describes_the_changelog_grouping_as_it_is(self) -> 'None': [], [k for k in kinds if k not in ('Added', 'Changed', 'Fixed')], 'a kind subsection is named something other than Added/Changed/Fixed') - # The inline form is gone. This is the half that makes the claim falsifiable in - # the direction that matters: a partial revert would restore it. + # No inline form. This is the half that makes the claim falsifiable in the + # direction that matters: an entry written the inline way would fail it. self.assertEqual( [], re.findall(r'(?m)^\* \*\*(?:Added|Changed|Fixed)\*\*', changelog), 'an entry carries an inline kind label again, so the file is back to the ' diff --git a/tests/project/test_cron_reports_2030.py b/tests/project/test_cron_reports_2030.py index b6fa651e3d..0e6e5ac904 100644 --- a/tests/project/test_cron_reports_2030.py +++ b/tests/project/test_cron_reports_2030.py @@ -98,7 +98,7 @@ def launcher(directory: 'pathlib.Path', name: 'str', script: 'Optional[str]' = N class TestTheWorkflowsPost(unittest.TestCase): - """The ``discussion`` job of each workflow, and every other job left as it was.""" + """The ``discussion`` job of each workflow, and every other job's permissions per :data:`PERMISSIONS`.""" def test_only_the_posting_job_may_write_discussions(self) -> None: for name, permissions in PERMISSIONS.items(): diff --git a/tests/project/test_discussion_report_2030.py b/tests/project/test_discussion_report_2030.py index 1c32fdec29..65890280eb 100644 --- a/tests/project/test_discussion_report_2030.py +++ b/tests/project/test_discussion_report_2030.py @@ -11,10 +11,10 @@ one ``::warning`` and exit 0, so reporting never fails a run. * :class:`TestTheWorkflowsPost` pins the ``discussion`` job of :file:`ci-timing.yml` and :file:`cron-vendor.yml`: the only job holding - ``discussions: write``, every other job's permissions as they were, first-party - actions only, the mode each event maps to, the script called on the - artifact the reporting job uploads, and cron-vendor's job running on a - failed gate but not on a deliberate skip. + ``discussions: write``, every other job's permissions as :data:`PERMISSIONS` + records them, first-party actions only, the mode each event maps to, the + script called on the artifact the reporting job uploads, and cron-vendor's + job running on a failed gate but not on a deliberate skip. * :class:`TestTheVendorPostStep` runs cron-vendor's post step with a stand-in ``python3``: the status it passes, and the report it writes for a failed gate. * :class:`TestTheVendorReport` runs ``vendor-update``'s ``Write the run report`` @@ -442,8 +442,8 @@ class TestTheWorkflowCommands(_FakeGitHub): """#2047: the whole of what the script prints and appends to the summary, byte for byte. The tests above read the warning with ``assertIn``; these pin each run's whole - standard output and summary file, with text each escape changes, so moving the - writes onto util/_actions.py can be shown to change nothing. + standard output and summary file, with text each escape changes, so any change + to what is written fails here. """ @@ -553,7 +553,7 @@ def post_step(name: 'str') -> 'dict[str, Any]': class TestTheWorkflowsPost(unittest.TestCase): - """The ``discussion`` job of each workflow, and every other job left as it was.""" + """The ``discussion`` job of each workflow, and every other job's permissions per :data:`PERMISSIONS`.""" def test_only_the_posting_job_may_write_discussions(self) -> None: for name, permissions in PERMISSIONS.items(): diff --git a/tests/project/test_docs_engines_page_unit.py b/tests/project/test_docs_engines_page_unit.py index f21fb7df37..f7a478c760 100644 --- a/tests/project/test_docs_engines_page_unit.py +++ b/tests/project/test_docs_engines_page_unit.py @@ -23,7 +23,7 @@ INDEX = DOCS / 'index.rst' PAGE = DOCS / 'engines.rst' -#: The headings that moved, each written once on the page. +#: The headings :file:`engines.rst` holds, each written once on the page. MOVED = ('Engine Support by Python Version', 'Engine Prerequisites', 'Test Environment', 'Test Results', 'Test Results (Relative)') @@ -51,7 +51,7 @@ def section(text: 'str', title: 'str') -> 'str': class PlacementTests(unittest.TestCase): - """The page exists, is reachable, and holds what moved.""" + """The page exists, is reachable, and holds those headings.""" def setUp(self) -> 'None': self.index = INDEX.read_text(encoding='utf-8') @@ -74,7 +74,7 @@ def test_page_carries_every_moved_section_once(self) -> 'None': self.assertEqual(found.count(title), 1) def test_index_keeps_a_summary_that_links_the_page(self) -> 'None': - """Its ``#engine-comparison`` anchor survives, and none of the moved sections do.""" + """Its ``#engine-comparison`` anchor is there, and none of the page's sections are.""" found = headings(self.index) self.assertIn('Engine Comparison', found) self.assertIn(':doc:`engines`', section(self.index, 'Engine Comparison')) diff --git a/tests/project/test_docs_epilog_links_1936_unit.py b/tests/project/test_docs_epilog_links_1936_unit.py index 76ede7160c..117feaca11 100644 --- a/tests/project/test_docs_epilog_links_1936_unit.py +++ b/tests/project/test_docs_epilog_links_1936_unit.py @@ -248,7 +248,7 @@ def test_no_file_defines_an_epilog_name_again(self) -> 'None': self.assertEqual(offenders, []) def test_a_doc_comment_redefinition_is_seen(self) -> 'None': - """A ``#:`` comment renders, so a definition in one counts (:issue:`2177`).""" + """A ``#:`` comment renders, so a definition in one counts (#2177).""" with tempfile.TemporaryDirectory() as tmp: path = pathlib.Path(tmp) / 'mod.py' path.write_text('#: A `DPKT`_ packet.\n#:\n#: .. _DPKT: https://dpkt.readthedocs.io\n' diff --git a/tests/project/test_docs_internal_refs_1940_unit.py b/tests/project/test_docs_internal_refs_1940_unit.py index 6f814c6e0b..6dfccaa34c 100644 --- a/tests/project/test_docs_internal_refs_1940_unit.py +++ b/tests/project/test_docs_internal_refs_1940_unit.py @@ -18,10 +18,9 @@ ``:special-members: __new__`` document an enumeration's own ``__new__``. Whether a role resolves is a property of the built inventory, not of the source, -so the honest check is a ``sphinx-build`` run, recorded in the pull request. What -this pins is the textual precondition a plain (non-nitpicky) build would not -notice losing: the entry is declared, the role is written, and the dotted path it -names is a real object. +so the honest check is a ``sphinx-build`` run. What this pins is the textual +precondition a plain (non-nitpicky) build would not notice losing: the entry is +declared, the role is written, and the dotted path it names is a real object. """ @@ -102,7 +101,7 @@ ':class:`~pcapkit.const.ftp.command.CommandType`'), ('docs/source/contributing/conventions/sentinel-convention.rst', '``NoDefaultType``', ':class:`~pcapkit.corekit.sentinels.NoDefaultType`'), - # Moved here from documentation.rst with the backport it named (#2051). + # On this page with the backport it names (#2051). ('docs/source/contributing/conventions/sentinel-convention.rst', '``cached_property``', ':class:`~pcapkit.utilities.compat.cached_property`'), ('docs/source/contributing/releasing.rst', '``pcapkit.__version__``', diff --git a/tests/project/test_documentation_claims.py b/tests/project/test_documentation_claims.py index e8ed932f29..6883809a58 100644 --- a/tests/project/test_documentation_claims.py +++ b/tests/project/test_documentation_claims.py @@ -20,18 +20,15 @@ #546's item 2 -- ``register_extractor_engine`` documenting a parameter named ``engine`` where the signature reads ``name`` -- is **not** tested here, because -:file:`tests/test_docstring_contract.py` already checks that property across the -whole of :mod:`pcapkit` and carried the defect as a :data:`KNOWN_DEFECTS` entry -reading "owned by the registry docstring change". Fixing it is therefore a -*deletion* from that tuple rather than a new test, and a second checker here would -only be a narrower copy of a better one. +:file:`tests/test_docstring_contract.py` checks that property across the whole of +:mod:`pcapkit`, and a second checker here would only be a narrower copy of a +better one. Deliberately **not** tested here either: whether a Sphinx cross-reference resolves. That is a property of the built inventory, not of the source, and the honest check for it is a nitpicky ``sphinx-build`` -- some 20 minutes, and it needs the docs toolchain installed. A unit test that re-implemented Sphinx's resolution rules would pass while the real build failed, which is worse than not testing it. -The measured counts are recorded in the pull request instead. """ diff --git a/tests/project/test_engine_stubs_1878_unit.py b/tests/project/test_engine_stubs_1878_unit.py index 316936d18e..8c428349cd 100644 --- a/tests/project/test_engine_stubs_1878_unit.py +++ b/tests/project/test_engine_stubs_1878_unit.py @@ -3,10 +3,9 @@ An engine keeps that package in ``_expkg``. Each engine annotates it with a :class:`~typing.Protocol` of its own, defined in its module-level -``if TYPE_CHECKING:`` block and declaring what the engine reads through it. An -``'ModuleType'`` annotation, as #1870 gave it, is read by mypy as -:data:`~typing.Any` for every attribute, so nothing called through it would be -checked. +``if TYPE_CHECKING:`` block and declaring what the engine reads through it. A +``'ModuleType'`` annotation is read by mypy as :data:`~typing.Any` for every +attribute, so nothing called through it would be checked. ``scapy`` ships its own types. The other engine packages ship none, so :file:`typings/` holds stubs for the part of each that :mod:`pcapkit` uses, diff --git a/tests/project/test_experimental_concurrency_1903.py b/tests/project/test_experimental_concurrency_1903.py index 9cf8c45e9f..b4c82f755e 100644 --- a/tests/project/test_experimental_concurrency_1903.py +++ b/tests/project/test_experimental_concurrency_1903.py @@ -84,7 +84,7 @@ def test_no_concurrency_block(self) -> None: class TestUnitTestsKeepsARunPerCommit(unittest.TestCase): - """unit-tests.yml is unchanged: only a pull request's run is superseded.""" + """unit-tests.yml keeps a run per commit: only a pull request's run is superseded.""" def setUp(self) -> None: self.unit = Concurrency('unit-tests.yml') diff --git a/tests/project/test_ftp_featcode_doc_page_934_unit.py b/tests/project/test_ftp_featcode_doc_page_934_unit.py index 711a37384e..84978bca4f 100644 --- a/tests/project/test_ftp_featcode_doc_page_934_unit.py +++ b/tests/project/test_ftp_featcode_doc_page_934_unit.py @@ -17,10 +17,10 @@ :file:`tests/project/test_sentinels_doc_page_934_unit.py` explains for the sibling half of this same issue, whether a reference resolves is a property of the built inventory rather than of the source, and the honest check is the rendered HTML from -an actual ``sphinx-build`` run, recorded in the pull request rather than -reimplemented here. What this pins is the purely textual precondition a later edit -could silently break even though a plain (non-nitpicky) build would keep passing -either way: the page has to keep declaring an ``autoclass`` for the class. +an actual ``sphinx-build`` run, not reimplemented here. What this pins is the +purely textual precondition a later edit could silently break even though a plain +(non-nitpicky) build would keep passing either way: the page has to keep declaring +an ``autoclass`` for the class. """ diff --git a/tests/project/test_gnu_timeout_2020.py b/tests/project/test_gnu_timeout_2020.py index fd95984565..5a3c10d28b 100644 --- a/tests/project/test_gnu_timeout_2020.py +++ b/tests/project/test_gnu_timeout_2020.py @@ -11,10 +11,10 @@ A ``timeout`` with no option, ``timeout 60 apt-get update`` as the apt steps run it, behaves alike under both in the same image: exit 124, and its child's own -children end with it. So the rule here is that a ``timeout`` given any option -runs through that resolution, in every workflow. Since #1967 the steps' shell -moves into ``util/`` scripts, so the rule covers what those run too: no string in -one is an optioned ``timeout`` command, and no command one builds as a sequence, +children end with it. So the rule here is that a ``timeout`` given any option runs +through that resolution, in every workflow. The steps' shell lives in ``util/`` +scripts (#1967), so the rule covers what those run too: no string in one is an +optioned ``timeout`` command, and no command one builds as a sequence, ``['sudo', 'timeout', '60', 'apt-get', 'update']`` as :file:`util/apt_packages.py` does, gives ``timeout`` an option. diff --git a/tests/project/test_graalpy_shards_1963.py b/tests/project/test_graalpy_shards_1963.py index 5f3408528c..3475e52969 100644 --- a/tests/project/test_graalpy_shards_1963.py +++ b/tests/project/test_graalpy_shards_1963.py @@ -3,12 +3,12 @@ #1753 gives every GraalPy process a 2 GiB heap and an exit once it is spent, and a worker's live heap grows with every test it runs. Over the curated ``ALL`` tier -that took job 118912680486's workers to 2.5-3.0 GB RSS and nine of them down, -each failing the test it ran, though each such test passes alone. So -``Run unit tests`` hands GraalPy's pytest command to :file:`util/pytest_shards.py`, -which runs it once per directory ``SHARDS`` names and once over the rest, each -run with workers of its own, all under the step's one ``timeout``, scope and -watcher. 3.14t's leg runs pytest once, as before. +that took job 118912680486's workers to 2.5-3.0 GB RSS and nine of them down, each +failing the test it ran, though each such test passes alone. So ``Run unit tests`` +hands GraalPy's pytest command to :file:`util/pytest_shards.py`, which runs it once +per directory ``SHARDS`` names and once over the rest, each run with workers of its +own, all under the step's one ``timeout``, scope and watcher. 3.14t's leg runs +pytest once. :class:`TestTheStep` reads the step, :class:`TestTheStepRuns` runs it with ``sudo`` and ``python`` stubbed on ``PATH``, and :class:`TestTheScript` runs the @@ -66,7 +66,7 @@ def test_graalpy_alone_runs_in_shards(self) -> None: for version in versions: with self.subTest(version=version): self.assertEqual(by_impl(env['SHARDS'], version), SHARDS if version.startswith('graalpy') else '') - # The heap and the workers are #1753's and #1740's, unchanged. + # The heap and the workers are #1753's and #1740's. self.assertEqual(by_impl(env['GRAAL_PYTHON_VM_ARGS'], 'graalpy-25.4'), HEAP) self.assertEqual(by_impl(env['WORKERS'], 'graalpy-25.4'), '3') diff --git a/tests/project/test_isort_clean.py b/tests/project/test_isort_clean.py index 8ab1ff89f8..9c3e3c5572 100644 --- a/tests/project/test_isort_clean.py +++ b/tests/project/test_isort_clean.py @@ -69,13 +69,12 @@ exists to stop and which #779 avoids the same way for the sibling ``mypy`` check. -The install line is the other half. ``isort`` is a ``test`` extra -requirement, so the pytest jobs whose selection collects this module -- -``test`` and ``gate``, since #849 took ``engine-tests`` off it -- run it for -real rather than reporting a skip. That is the opposite resolution from -``mypy``'s, whose :data:`~tests._dependency_gates.DEPENDENCY_GATE_EXCLUSIONS` -entry declines the install line, and the difference is not taste: -:file:`lint.yml` already runs +The install line is the other half. ``isort`` is a ``test`` extra requirement, +so the pytest jobs whose selection collects this module -- ``test`` and +``gate``, not ``engine-tests`` (#849) -- run it for real rather than reporting +a skip. That is the opposite resolution from ``mypy``'s, whose +:data:`~tests._dependency_gates.DEPENDENCY_GATE_EXCLUSIONS` entry declines the +install line, and the difference is not taste: :file:`lint.yml` already runs ``make mypy`` over the whole package, so a pytest copy would be a *second* copy, while that same workflow's header records that ``isort`` is deliberately not one of the linters it installs -- it appears only in @@ -83,12 +82,12 @@ on a schedule rather than as a check. So declining the install line here would leave ``make isort``'s verdict checked nowhere at all, which is what #766 objected to. It is cheap enough to be uncontroversial, too: the lines below -take about 1.5s together, the "costs nothing per leg" category the test -job's own install comment puts ``DPKT`` in rather than the ``Scapy`` one. +take about 1.5s together, the "costs nothing per leg" category the test job's +own install comment puts ``DPKT`` in rather than the ``Scapy`` one. -``isort`` is in ``Pipfile``'s ``[dev-packages]`` too, as of #810 -- that being -what ``make isort``'s own ``pipenv run`` prefix resolves against, so the target -does not depend on a contributor happening to have isort on ``PATH``. The two +``isort`` is in ``Pipfile``'s ``[dev-packages]`` too (#810) -- that being what +``make isort``'s own ``pipenv run`` prefix resolves against, so the target does +not depend on a contributor happening to have isort on ``PATH``. The two declarations are additive rather than duplicate: the ``pyproject.toml`` extra serves consumers installing the package, ``Pipfile`` serves this repository's own dev environment, and ``make isort`` reads only the latter. @@ -201,19 +200,18 @@ def _pcapkit_tree_targets() -> 'list[str]': def _find_mindepth_2_targets(sub: str) -> 'list[str]': """``$(find pcapkit/ -mindepth 2 -name '*.py')`` (#765) -- - ``Path.glob('*/**/*.py')`` matches the same set ``find -mindepth 2`` does: - at least one subdirectory below ````, then any depth. Pathlib's own - glob has no shell and no bash version to depend on in the first place, so - this mirror was never exposed to the bug the Makefile itself just had. - The leading ``*/`` (equivalently, the ``-mindepth 2``) is deliberate, not - slack -- a bare ``**/*.py`` would also reach :file:`pcapkit//__init__.py` - itself, whose hand-grouped, commented import order (``# base crawler``, - only under ``vendor``; ``# IANA registration``, ``# Miscellanous`` and - ``# per protocol`` under both) isort's ``-l100 -ppcapkit`` would flatten - into one alphabetical block, scattering the comments across it -- - confirmed with ``isort --diff``, not assumed. That file stays reached by - neither this call nor the whole-tree line (which skips every - ``__init__.py``). + ``Path.glob('*/**/*.py')`` matches the same set ``find -mindepth 2`` does: at + least one subdirectory below ````, then any depth. Pathlib's own glob has + no shell and no bash version to depend on in the first place, so this mirror + is not exposed to the bash-version bug the module docstring describes. The + leading ``*/`` (equivalently, the ``-mindepth 2``) is deliberate, not slack -- + a bare ``**/*.py`` would also reach :file:`pcapkit//__init__.py` itself, + whose hand-grouped, commented import order (``# base crawler``, only under + ``vendor``; ``# IANA registration``, ``# Miscellanous`` and ``# per protocol`` + under both) isort's ``-l100 -ppcapkit`` would flatten into one alphabetical + block, scattering the comments across it -- confirmed with ``isort --diff``, + not assumed. That file stays reached by neither this call nor the whole-tree + line (which skips every ``__init__.py``). """ return sorted(str(path) for path in (PACKAGE / sub).glob('*/**/*.py')) @@ -221,9 +219,9 @@ def _find_mindepth_2_targets(sub: str) -> 'list[str]': def _const_targets() -> 'list[str]': """``pcapkit/const/`` half of the pair described in - :func:`_find_mindepth_2_targets` -- its own line since #765's second fix - (see the module docstring), matching :file:`cron-vendor.yml`'s own - separate calls for ``const`` and ``vendor``. + :func:`_find_mindepth_2_targets` -- on its own line (#765; see the + module docstring), matching :file:`cron-vendor.yml`'s own separate + calls for ``const`` and ``vendor``. """ return _find_mindepth_2_targets('const') @@ -248,13 +246,11 @@ def _isort_recipe_line_numbers() -> 'list[int]': """The physical, 1-indexed line numbers of the Makefile's ``isort:`` target's own recipe lines, computed fresh from :file:`Makefile` every run rather than hardcoded -- a hardcoded number is exactly the kind of - silent rot this module exists to catch, and an earlier revision of this - file was guilty of it in its own name: functions called - ``_line_125_targets`` and so on stopped matching reality the moment a - :file:`Makefile` comment two lines above the target grew by a few - lines, and nothing here noticed until a reviewer measured it by hand. - None of the functions below are named after a line number any more for - that reason; :data:`MAKEFILE_LINES` carries the number as data instead, + silent rot this module exists to catch: a function called + ``_line_125_targets`` stops matching reality the moment a + :file:`Makefile` comment above the target grows by a few lines, with + nothing here to notice. So none of the functions below is named after a + line number; :data:`MAKEFILE_LINES` carries the number as data instead, paired with the callable it belongs to positionally rather than by a key that could drift out of sync with what it claims to label. @@ -262,12 +258,12 @@ def _isort_recipe_line_numbers() -> 'list[int]': ``isort:`` or another such line, a blank line, or a comment line at column 0 -- make itself treats a blank or a column-0 ``#`` line inside a recipe as transparent rather than as ending the rule (measured with - ``make -n isort`` as the oracle: it still reports the same commands - with either dropped in between two recipe lines, which an earlier - revision of this function got wrong by breaking on the first line that - was not tab-indented, comment or not). The first following line that is - genuine content and not tab-indented ends the target -- a new target - declaration, most likely, since nothing above passes that test. + ``make -n isort`` as the oracle: it still reports the same commands with + either dropped in between two recipe lines, so breaking on the first + line that is not tab-indented, comment or not, would be wrong). The + first following line that is genuine content and not tab-indented ends + the target -- a new target declaration, most likely, since nothing above + passes that test. Matching ``isort:`` requires the *whole* line, not a prefix: an ``isort-check:`` target, or a ``.PHONY: isort`` line naming ``isort`` as diff --git a/tests/project/test_ktlo_jobs_gate_nothing_1738.py b/tests/project/test_ktlo_jobs_gate_nothing_1738.py index b120995523..30cc789232 100644 --- a/tests/project/test_ktlo_jobs_gate_nothing_1738.py +++ b/tests/project/test_ktlo_jobs_gate_nothing_1738.py @@ -41,7 +41,7 @@ NAME = 'unit-tests.yml' #: Each gating job and the job holding its KTLO legs. TWINS = {'test': 'test-ktlo', 'integration': 'integration-ktlo', 'pypcap-parity': 'pypcap-parity-ktlo'} -#: Each gating job's check-name prefix, as main named its legs before the split. +#: Each gating job's check-name prefix, which its KTLO twin's legs share. PREFIX = {'test': 'Python', 'integration': 'Integration Python', 'pypcap-parity': 'PyPCAP/PyPCAPFile parity Python'} #: The KTLO versions each twin runs; ``pypcap-parity`` runs on ``ubuntu-latest`` and below 3.12 only, and #: on CPython alone, where ``test`` and ``integration`` run PyPy's too (#1958). @@ -224,7 +224,7 @@ def test_an_aliased_job_has_its_anchors_steps(self) -> None: def test_the_critical_path_leaves_the_ktlo_jobs_out(self) -> None: required = next(spec for spec in self.specs if spec['name'] == self.module.REQUIRED_JOB) self.assertFalse(set(TWINS.values()) & set(required['needs'])) - # The KTLO leg finishes last, so on main, where it was `test`'s, it led. + # The KTLO leg finishes last, so counted as `test`'s it would lead. run = {'event': 'pull_request', 'conclusion': 'success', 'jobs': [ {'name': name, 'runner_name': 'GitHub Actions 1', 'conclusion': 'success', 'started_at': '2026-10-14T00:00:00Z', 'completed_at': end} diff --git a/tests/project/test_ktlo_legs_stay_green_1672.py b/tests/project/test_ktlo_legs_stay_green_1672.py index b01ecc6ac1..8845d1da88 100644 --- a/tests/project/test_ktlo_legs_stay_green_1672.py +++ b/tests/project/test_ktlo_legs_stay_green_1672.py @@ -24,13 +24,13 @@ (#1967), :file:`create-release.yml`'s ``pypi``'s too, whose former inline copy of its shell is run against the script on the same steps. -#1660 added an experimental tier, free-threaded CPython and GraalPy, as legs of -``test`` only; #1732 moved them to a job of their own, ``experimental``, which -nothing gating needs. They are non-blocking by the same mechanics, every step -that can fail guarded, and report through ``Experimental summary``, that job's -last step: :class:`TestAnExperimentalLegIsNonBlockingTheSameWay`. Their -setup-python, install and test steps have caps of their own, so a slow or hung leg -fails a step rather than being cancelled with its job: +The experimental tier, free-threaded CPython and GraalPy (#1660), is a job of its +own, ``experimental``, which nothing gating needs (#1732). They are non-blocking +by the same mechanics, every step that can fail guarded, and report through +``Experimental summary``, that job's last step: +:class:`TestAnExperimentalLegIsNonBlockingTheSameWay`. Their setup-python, install +and test steps have caps of their own, so a slow or hung leg fails a step rather +than being cancelled with its job: :class:`TestASlowExperimentalLegTimesOutAStepNotTheJob`. """ @@ -169,7 +169,7 @@ #: tests. #1732: the tests' is GraalPy's unit tier less the round-trip sweeps, 88-113 #: minutes (estimate) from the 26% its leg reached in 25 (job 115915976024). EXPERIMENTAL_CAPS = {'python': 5, 'install': 10, 'watcher-python': 1, 'watcher': 1, 'tests': 120} -#: ``experimental``'s own ``timeout-minutes`` (#1732), and ``test``'s, main's 45 again. +#: ``experimental``'s own ``timeout-minutes`` (#1732), and ``test``'s. EXPERIMENTAL_JOB_CAP = 140 JOB_CAP = 45 #: The job's minutes those caps leave for the rest of an experimental leg: the steps @@ -559,7 +559,7 @@ def test_the_summary_is_quiet_when_nothing_failed(self) -> None: self.assertIn(f'{LEG_KTLO}: no step failed.', result.stdout) def test_the_former_release_copy_reports_as_the_script_does(self) -> None: - """``pypi``'s ``KTLO summary`` held its own shell until #1967; the script reports as it did.""" + """``pypi``'s ``KTLO summary`` shell from before #1967 and the script report alike.""" if shutil.which('jq') is None: # pragma: no cover self.skipTest('jq, on every Ubuntu runner image, is needed to run the former KTLO summary') self.assertEqual(summary_step(job(*UNCOPIED_SUMMARY))['run'], SUMMARY_RUN) @@ -639,7 +639,7 @@ def test_the_engine_ktlo_cell_warns(self) -> None: class TestASupportedLegStillFails(unittest.TestCase): - """(c) On a supported leg every step fails the job as before, and no summary runs.""" + """(c) On a supported leg every step fails the job, and no summary runs.""" def test_every_failure_still_fails_a_supported_leg(self) -> None: """A step's failure is the job's, unless it is masked on every leg by its own design. @@ -822,10 +822,7 @@ def test_an_experimental_legs_steps_end_inside_its_job(self) -> None: self.assertTrue(continue_on_error(step, {'matrix': leg}), label(step)) def test_test_keeps_mains_caps(self) -> None: - """#1732: ``test``'s job cap is 45 again, and none of its steps has one, so the 45 binds. - - main's step caps were 360, the documented maximum, on every leg but the - experimental ones, which leave with them. + """#1732: ``test``'s job cap is 45, and none of its steps has one, so the 45 binds. """ spec = job('unit-tests.yml', 'test') diff --git a/tests/project/test_lint_pages_reports_2030.py b/tests/project/test_lint_pages_reports_2030.py index 2f66f35817..a3191366d5 100644 --- a/tests/project/test_lint_pages_reports_2030.py +++ b/tests/project/test_lint_pages_reports_2030.py @@ -80,7 +80,7 @@ class TestTheWorkflowsPost(unittest.TestCase): - """The ``discussion`` job of each workflow, and every other job left as it was.""" + """The ``discussion`` job of each workflow, and every other job's permissions per :data:`PERMISSIONS`.""" def test_only_the_posting_job_may_write_discussions(self) -> None: for name, permissions in PERMISSIONS.items(): diff --git a/tests/project/test_module_isolation.py b/tests/project/test_module_isolation.py index feb2c7d525..07c9138adb 100644 --- a/tests/project/test_module_isolation.py +++ b/tests/project/test_module_isolation.py @@ -17,18 +17,17 @@ invisible. Each case is a *pair*: the order that failed, and one file that is known to -pollute paired with one that is known to import the package. Three distinct leaks -were found, and they are covered separately because they fail differently and are -fixed by different halves of the change: +pollute paired with one that is known to import the package. Each leak is covered +separately because they fail differently and have different fixes: * :meth:`OrderIndependenceTests.test_the_reported_three_file_order_passes` is the reproduction from the issue verbatim -- ``tests/corekit/test_protochain.py`` leaking a fake ``ProtocolBase``. * :meth:`OrderIndependenceTests.test_the_integration_tier_polluter_order_passes` - is a second leak, in ``tests/integration/test_module_loading.py``, found while - fixing the first and not mentioned in the issue. It fails differently -- a stub - ``pcapkit`` carrying only ``__path__``, so ``__all__`` entries resolve to - nothing rather than raising. + is a second leak, in ``tests/integration/test_module_loading.py``, not + mentioned in the issue. It fails differently -- a stub ``pcapkit`` + carrying only ``__path__``, so ``__all__`` entries resolve to nothing + rather than raising. * :meth:`OrderIndependenceTests.test_the_cli_polluter_order_passes` is the third leak, in ``tests/cli/test_main.py``. That file calls :func:`tests._support.isolate_modules` like its neighbours, so this case is a diff --git a/tests/project/test_pcapng_download_retry_1658_unit.py b/tests/project/test_pcapng_download_retry_1658_unit.py index 989f49c8a7..90a39e7537 100644 --- a/tests/project/test_pcapng_download_retry_1658_unit.py +++ b/tests/project/test_pcapng_download_retry_1658_unit.py @@ -6,13 +6,13 @@ HTTP 429 or 5xx, or a timeout, with backoff, honouring ``Retry-After``, within a small budget; any other failure falls back at once. -#1894 moved the download from :mod:`urllib.request` to :mod:`requests`. -:class:`DownloadRetryTests` runs against a stubbed ``requests.get`` and a fake -clock bound over the generator's ``time``, so nothing touches the network or -sleeps. :class:`LocalServerTests` sends real requests to a server on the -loopback interface, because what :mod:`requests` raises for a timeout while the -body is read is its own behaviour, not something a stub can show. This module -is unit-tier and reads no capture. +The download goes through :mod:`requests` (#1894). :class:`DownloadRetryTests` +runs against a stubbed ``requests.get`` and a fake clock bound over the +generator's ``time``, so nothing touches the network or sleeps. +:class:`LocalServerTests` sends real requests to a server on the loopback +interface, because what :mod:`requests` raises for a timeout while the body is +read is its own behaviour, not something a stub can show. This module is +unit-tier and reads no capture. """ from __future__ import annotations diff --git a/tests/project/test_project_status.py b/tests/project/test_project_status.py index fea9c9ccb3..28cf673f98 100644 --- a/tests/project/test_project_status.py +++ b/tests/project/test_project_status.py @@ -837,7 +837,7 @@ def test_a_truncated_reconcile(self) -> None: '1 of 1 item(s) changed\n'))) def test_a_percent_in_the_repository_is_escaped(self) -> None: - """Before util/_actions.py the query went out as written; ``%`` is not in a repository name GitHub allows.""" + """Escaped all the same, though ``%`` is not in a repository name GitHub allows.""" fake = FakeGraphQL([], issue_count=1) code, printed = self.run_main(['--reconcile', '--repo', 'o/50%'], fake) self.assertEqual(code, 0) diff --git a/tests/project/test_public_api.py b/tests/project/test_public_api.py index 564c87214c..d1069827ab 100644 --- a/tests/project/test_public_api.py +++ b/tests/project/test_public_api.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- """The public API surface holds together. -Four invariants about :attr:`__all__`, none of which anything else in the suite -checked before: +Invariants about :attr:`__all__`: #. every public subpackage survives ``from import *``; #. every name in every public module's :attr:`__all__` actually resolves; @@ -38,14 +37,14 @@ **Nothing is excluded, :mod:`pcapkit.vendor` included.** The vendor crawlers ``import requests`` and ``import bs4`` at module scope, which reads as a reason to skip the subtree through :data:`EXCLUDED_ROOTS` if those came only from the -``vendor`` extra: this module is unit-tier -- see :mod:`tests._tiers` -- so it -has to pass on ``pip install -e '.[test]'``. Both are in the ``test`` extra too, -since #507 (:file:`pyproject.toml`), so such a skip buys nothing. Sweeping the -vendor modules and packages (#1772) found -packages holding public attributes their :attr:`__all__` omitted: -:mod:`pcapkit.vendor.ipx` leaked ``path``, the temporary of its ``sys.path`` -save-and-restore, since renamed ``_path``; the attributes :mod:`pcapkit.vendor` -itself left unlisted are recorded in :data:`DELIBERATE_NON_EXPORTS`. +``vendor`` extra: this module is unit-tier -- see :mod:`tests._tiers` -- so it has +to pass on ``pip install -e '.[test]'``. Both are in the ``test`` extra too (#507, +:file:`pyproject.toml`), so such a skip buys nothing. Sweeping the vendor modules +and packages (#1772) found packages holding public attributes their +:attr:`__all__` omitted: :mod:`pcapkit.vendor.ipx` leaked ``path``, the temporary +of its ``sys.path`` save-and-restore, private as ``_path``; the attributes +:mod:`pcapkit.vendor` itself left unlisted are recorded in +:data:`DELIBERATE_NON_EXPORTS`. No import is wrapped in a skip, either. That is deliberate and also measured: with ``dpkt``, ``scapy``, ``emoji``, ``cryptography`` and ``pycrate`` all absent, every @@ -76,15 +75,15 @@ load-bearing rather than an escape hatch: without it the assertion is unsatisfiable, and with it every exclusion is something somebody wrote down and a reviewer can argue with. Of the names the package sweep flagged, about half were -genuine omissions and are exported as of the commit that added this assertion -- -among them the ``register_*_callback`` and ``register_extractor_*`` functions that -:mod:`pcapkit.foundation` withheld while listing the other registry functions, -``ReassemblyManager`` and ``TraceFlowManager``, which are defined *below* the -:attr:`__all__` literal that forgot them and imported by name from several modules, -and the decorators :mod:`pcapkit.utilities` then advertised in its own module -docstring (since moved to :mod:`pcapkit.corekit.decorators` and dropped from that -package by #1735). The rest are listed with their reasons, as are the names -:mod:`pcapkit.vendor` added once it joined the sweep (#1772), and +genuine omissions and are exported -- among them the ``register_*_callback`` and +``register_extractor_*`` functions that :mod:`pcapkit.foundation` withheld while +listing the other registry functions, ``ReassemblyManager`` and +``TraceFlowManager``, which are defined *below* the :attr:`__all__` literal that +forgot them and imported by name from several modules, and the decorators +:mod:`pcapkit.utilities` then advertised in its own module docstring (in +:mod:`pcapkit.corekit.decorators`, #1735). The rest are listed with their +reasons, as are the names :mod:`pcapkit.vendor` added once it joined the sweep +(#1772), and :meth:`~PublicAPISurfaceTests.test_the_non_export_allowlist_is_tight` stops the list rotting into a silencer: every entry has to name a package that exists, an attribute that package really holds, and a name its :attr:`__all__` really omits, @@ -99,9 +98,9 @@ happened, and this is the assertion that makes the next one fail immediately instead of in three years. -Further assertions cover the rest of the family, because a name can fail to -be exported in more ways than by being misspelled. Each of them failed -against the tree this module was written on, and each is fixed in the same commit: +Further assertions cover the rest of the family, because a name can fail to be +exported in more ways than by being misspelled. Each of them failed against the +tree this module was written on: * :meth:`~PublicAPISurfaceTests.test_no_misspelled_all` -- ``pcapkit/const/pcapng/__init__.py`` spelled it ``___all__``, with *three* @@ -114,7 +113,7 @@ * :meth:`~PublicAPISurfaceTests.test_every_public_package_declares_all` -- the same defect seen from the other side, and the one that catches "forgot it entirely" rather than "typed it wrong". Every public package declares - :attr:`__all__` once ``pcapng`` is fixed. + :attr:`__all__`. * :meth:`~PublicAPISurfaceTests.test_no_duplicate_all_entries` -- ``'MH_HandoverACKStatus'`` appeared *twice* in both :mod:`pcapkit.const` and :mod:`pcapkit.const.mh`, and the duplicate sat in the slot that should have held @@ -131,9 +130,8 @@ class exists at ``pcapkit/const/mh/handover_initiate_status.py`` and is used by ``pcapkit/vendor/__init__.py`` and ``pcapkit/vendor/mh/__init__.py``, and there it was worse: ``pcapkit/vendor/__main__.py`` builds its crawler target lists out of those :attr:`__all__` lists, so the ``handover_initiate_status`` crawler had never -run. #531 fixed it while the subtree was still excluded, and -:mod:`tests.vendor.test_crawler_reachability_unit` guards it; since #1772 these -assertions cover that copy too. +run. #531 fixed it, :mod:`tests.vendor.test_crawler_reachability_unit` guards it, +and these assertions cover that copy too (#1772). """ from __future__ import annotations @@ -148,11 +146,11 @@ class exists at ``pcapkit/const/mh/handover_initiate_status.py`` and is used by #: build paths. __tier__ = 'ALL' -#: First path component under ``pcapkit.`` whose subtree the sweep skips. Empty: -#: it held ``'vendor'`` until #1772, though ``requests`` and ``beautifulsoup4`` -#: were in the ``test`` extra before the exclusion was written, and +#: First path component under ``pcapkit.`` whose subtree the sweep skips. Empty, +#: :mod:`pcapkit.vendor` included (#1772), since ``requests`` and +#: ``beautifulsoup4`` are in the ``test`` extra, and #: :meth:`~PublicAPISurfaceTests.test_public_modules_were_actually_found` pins -#: :mod:`pcapkit.vendor` in the walk so that restoring it fails. +#: :mod:`pcapkit.vendor` in the walk so that excluding it fails. EXCLUDED_ROOTS = frozenset() # type: frozenset[str] #: The packages whose whole job is to re-export their children, and which @@ -165,11 +163,11 @@ class exists at ``pcapkit/const/mh/handover_initiate_status.py`` and is used by 'pcapkit.foundation.registry', ) -#: Public attributes a public package holds and deliberately does not export, -#: keyed by the package. Adding a name here is an API decision to be argued for in -#: review -- it is not the way to quiet a failure of +#: Public attributes a public package holds and deliberately does not export, keyed by the +#: package. Adding a name here is an API decision to be argued for in review -- it is not +#: the way to quiet a failure of #: :meth:`~PublicAPISurfaceTests.test_every_public_package_exports_every_public_attribute`. -#: Three kinds of thing are in here, and nothing else should be. +#: These kinds of thing are in here, and nothing else should be. #: #: **The abstract bases** -- ``Field``, ``Engine``, ``Reassembly``, #: ``IP_Reassembly``, ``TraceFlow``, ``Protocol``, ``Application``, ``Internet``, @@ -291,8 +289,8 @@ def test_public_modules_were_actually_found(self) -> None: # a silent pass over an empty list, so pin the shape of what it returns. # The floor is deliberately far below the population found when this was # written: it catches "found nothing", not "the library gained a module". - # The vendor packages are pinned because the subtree was excluded until - # #1772, and each held __all__ gaps the exclusion hid. + # The vendor packages are pinned because excluding the subtree hid + # __all__ gaps in each (#1772). modules = public_modules() packages = [name for name, is_package in modules if is_package] diff --git a/tests/project/test_pypy310_cryptography_cap_2040.py b/tests/project/test_pypy310_cryptography_cap_2040.py index e103dd3b9c..5401a1ba1f 100644 --- a/tests/project/test_pypy310_cryptography_cap_2040.py +++ b/tests/project/test_pypy310_cryptography_cap_2040.py @@ -32,7 +32,7 @@ #: The newest release with a manylinux ``pp310`` wheel, and the first without one. LAST_PP310 = Version('45.0.7') FIRST_WITHOUT = Version('46.0.0') -#: The release ``pypy3.10`` resolved to before the cap, whose sdist fails there. +#: The release ``pypy3.10`` resolves to without the cap, whose sdist fails there. NEWEST = Version('50.0.2') diff --git a/tests/project/test_pypy_worker_memory_2005.py b/tests/project/test_pypy_worker_memory_2005.py index 6463107edf..54401248a5 100644 --- a/tests/project/test_pypy_worker_memory_2005.py +++ b/tests/project/test_pypy_worker_memory_2005.py @@ -5,8 +5,8 @@ whole tier at ``-n 4`` its workers outgrew the runner. ``Run unit tests`` runs pytest once per directory its ``SHARDS`` names on a PyPy leg, then once more over the rest, ignoring each, so every run starts its workers afresh. Every -other leg runs pytest once, as before. :class:`TestTheRunsCoverTheTier` runs the -step's own script with a stub ``python``, as +other leg runs pytest once. :class:`TestTheRunsCoverTheTier` runs the step's +own script with a stub ``python``, as :mod:`tests.project.test_sweeps_on_latest_only_1732` does. """ diff --git a/tests/project/test_pyshark_encap_map.py b/tests/project/test_pyshark_encap_map.py index 4fa9adb22d..3b147eab7f 100644 --- a/tests/project/test_pyshark_encap_map.py +++ b/tests/project/test_pyshark_encap_map.py @@ -15,17 +15,17 @@ Most of this is unit-tier: it exercises the parsing and formatting helpers against fabricated PDML/help-text fragments and a fabricated measurement list, -never shelling out to :program:`editcap`/:program:`tshark`. Two classes do -shell out, both gated on :data:`HAS_WIRESHARK` -- a real ``HAS_*`` flag, -deliberately, rather than a same-effect :func:`shutil.which` check under an -unprefixed name. The latter was this file's first cut, on the theory that a -name ``tests/_dependency_gates.py``'s AST scan does not recognise cannot need -an entry there -- true, but the wrong lesson: it also means the scan cannot -see the gate at all, so a real skip on every CI leg (Wireshark is not -installed on most of them) went unreported as anything. ``HAS_WIRESHARK`` is -declared in :data:`~tests._dependency_gates.NON_DISTRIBUTION_FLAGS`, next to -the existing ``HAS_PROC_FD`` precedent for "a flag that asks about something -pip cannot install at all" -- see that module for the declaration, and +never shelling out to :program:`editcap`/:program:`tshark`. Two classes do shell +out, both gated on :data:`HAS_WIRESHARK` -- a real ``HAS_*`` flag, deliberately, +rather than a same-effect :func:`shutil.which` check under an unprefixed name. +The latter rests on the theory that a name ``tests/_dependency_gates.py``'s AST +scan does not recognise cannot need an entry there -- true, but the wrong +lesson: it also means the scan cannot see the gate at all, so a real skip on +every CI leg (Wireshark is not installed on most of them) would go unreported as +anything. ``HAS_WIRESHARK`` is declared in +:data:`~tests._dependency_gates.NON_DISTRIBUTION_FLAGS`, next to the existing +``HAS_PROC_FD`` precedent for "a flag that asks about something pip cannot +install at all" -- see that module for the declaration, and :func:`tests.test_tier_guard.DependencyGateCoverageTests .test_every_gated_flag_is_classified` for the check that would fail if it were missing. @@ -323,7 +323,7 @@ def make_tmp_dir(case: unittest.TestCase) -> pathlib.Path: @unittest.skipUnless(HAS_LXML, NO_LXML) class StrictPdmlTests(unittest.TestCase): - """#1894: the PDML is parsed by lxml with a hardened parser, where ElementTree read it before. + """#1894: the PDML is parsed by lxml with a hardened parser, not by ElementTree. Through :func:`~pyshark_encap_map.measure_encap`, against stand-ins for :program:`editcap` and :program:`tshark`: the first writes a pcap header diff --git a/tests/project/test_python_matrix_tiers_1633.py b/tests/project/test_python_matrix_tiers_1633.py index c94ae1e999..9e2e5a5363 100644 --- a/tests/project/test_python_matrix_tiers_1633.py +++ b/tests/project/test_python_matrix_tiers_1633.py @@ -5,11 +5,11 @@ 2026-10-09, so the owner's ruling on #1633 split the matrices: * **Supported** -- 3.11 to 3.15, in every matrix (unit tests, compatibility, - conda, PyPI), blocking as before. + conda, PyPI), blocking. * **KTLO** -- end-of-life versions that still build and test (3.10 wherever it ran, plus 3.8 and 3.9 in the test matrices), never blocking: not CI, not a release, not a required context. -* ``requires-python`` stayed ``>=3.6, <4``, until #2051 raised it to ``>=3.8``. +* ``requires-python`` is ``>=3.8`` (#2051). A KTLO leg can block in more ways than one, so each is pinned on its own: a job that ``required-checks`` or ``release_status`` aggregates through one @@ -19,22 +19,21 @@ and PyPI's completeness evidence must not count a KTLO wheel, or one failed KTLO leg would leave every later release run retrying it. The release-side checks execute the workflow's own shell, stubbing only ``curl``. The last class reads -the prose that states the tiers back against the matrices. #1672 later made -every KTLO leg conclude ``success``, Compat's included, and report a failed step -as a warning; :mod:`tests.project.test_ktlo_legs_stay_green_1672` pins that. - -3.7 joined the tier afterwards, at the owner's request, and left it with #2051, -whose ``>=3.8`` pip enforces at install, so no leg runs below that floor. A KTLO -leg that cannot install or compile still fails fast and says why, at warning -level, and :class:`TestAKTLOLegFailsFast` pins that -- the install and verify -steps are executed with ``python`` stubbed, and each annotation is parsed as the -runner parses it. - -Since #1660 the tiers are declared once, in :file:`.github/python-versions.toml`, +the prose that states the tiers back against the matrices. Every KTLO leg +concludes ``success``, Compat's included, and reports a failed step as a warning +(#1672); :mod:`tests.project.test_ktlo_legs_stay_green_1672` pins that. + +No leg runs below 3.8, the ``requires-python`` floor pip enforces at install +(#2051). A KTLO leg that cannot install or compile still fails fast and says +why, at warning level, and :class:`TestAKTLOLegFailsFast` pins that -- the +install and verify steps are executed with ``python`` stubbed, and each +annotation is parsed as the runner parses it. + +The tiers are declared once, in :file:`.github/python-versions.toml` (#1660), and every matrix is read from it, so the expectations below are read from it too: what is pinned here is that each tier keeps its mechanics wherever the file -puts a version. :mod:`tests.project.test_python_versions_1660` pins that the file -reproduces the matrices ``main`` spelt out before. +puts a version. :mod:`tests.project.test_python_versions_1660` pins how each +matrix is read from the file. """ @@ -95,7 +94,7 @@ def tier(impl: 'str', name: 'str') -> 'tuple[str, ...]': LEG_TEMPLATE = "Python ${{ matrix.python-version }}${{ matrix.ktlo && ' (KTLO, non-blocking)' || '' }}" EXPERIMENTAL_LEG_TEMPLATE = 'Python ${{ matrix.python-version }} (experimental, non-blocking)' #: The job holding experimental legs, the only one (#1660), in a workflow of its own -#: since #1732, so that nothing in unit-tests.yml waits on it. +#: (#1732), so that nothing in unit-tests.yml waits on it. EXPERIMENTAL_JOB = ('experimental.yml', 'experimental') #: What ``test`` and ``integration`` run, and their KTLO twins: CPython's tier #: then PyPy's, as each implementation runs the ``ALL`` test tier (#1958). @@ -118,7 +117,7 @@ def tier(impl: 'str', name: 'str') -> 'tuple[str, ...]': AGGREGATED = (('unit-tests.yml', 'test'), ('unit-tests.yml', 'integration'), ('unit-tests.yml', 'pypcap-parity'), ('create-release.yml', 'pypi')) #: Where each KTLO leg must be, as (workflow, job, KTLO versions). unit-tests.yml's -#: are jobs of their own since #1738, which nothing gating waits on. +#: are jobs of their own (#1738), which nothing gating waits on. KTLO_LEGS = ( ('unit-tests.yml', 'test-ktlo', TESTED_KTLO), ('unit-tests.yml', 'integration-ktlo', TESTED_KTLO), @@ -307,7 +306,7 @@ def test_each_ktlo_leg_is_there(self) -> None: self.assertEqual(found, sorted(versions)) def test_37_to_39_run_where_setup_python_still_builds_them(self) -> None: - """No Ubuntu 26.04 build of any, and ``ubuntu-latest`` moves to 26.04.""" + """No Ubuntu 26.04 build of any, which ``ubuntu-latest`` serves.""" for name, job_id, _ in KTLO_LEGS: spec = job(name, job_id) for leg in legs(name, job_id): @@ -719,7 +718,7 @@ def test_the_compatibility_leg_names_its_first_compile_error(self) -> None: self.assertIn('SyntaxError: invalid syntax', result.stdout) def test_a_supported_leg_fails_the_same_steps_at_error_level(self) -> None: - """#1672 changed the KTLO level only: on 3.12 each of these still fails as an ``::error``.""" + """#1672 is about the KTLO level only: on 3.12 each of these fails as an ``::error``.""" cases = [(name, job_id, install, PIP_STUB, {'PIP_OUTPUT': PIP_37, 'PIP_STATUS': '1'}, '::error title=pcapkit did not install on Python 3.12::No matching distribution found ' 'for pytest>=8; extra == "test". The rest of this leg is skipped.') @@ -798,8 +797,9 @@ class TestTheProseFollowsTheTiers(unittest.TestCase): """What the docs and comments say about the matrices, read against the matrices. Nothing executes these, so a tier change leaves them stale without failing - anything else: round one of #1633 left the workflows page miscounting the - ``test`` legs, and comments naming 3.10 as CI's floor or 3.15 as allowed to fail. + anything else: a first pass at #1633 left the workflows page miscounting the + ``test`` legs, and comments naming 3.10 as CI's floor or 3.15 as allowed to + fail. """ diff --git a/tests/project/test_python_versions_1660.py b/tests/project/test_python_versions_1660.py index b13510d8b8..40acdbf032 100644 --- a/tests/project/test_python_versions_1660.py +++ b/tests/project/test_python_versions_1660.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- """#1660: the Python version tiers are declared once, in :file:`.github/python-versions.toml`. -Before #1660 every matrix spelt its versions out, so a tier change -- 3.11 -reaching end-of-life, a new pre-release -- meant editing many files by hand. +With every matrix spelling its versions out, a tier change -- 3.11 reaching +end-of-life, a new pre-release -- means editing many files by hand. :file:`util/python_versions.py` reads the file instead, and each workflow takes its matrices, and each single-interpreter job its ``latest``, from that script's step outputs. These things are pinned here: @@ -11,7 +11,7 @@ file, ``latest`` is the newest supported CPython (the owner's ruling on #1661), each exemption's reason is the comment above it, and the script runs on the ``python3`` of every runner that runs it. -* **Nothing moved.** The tiers as this change introduced them expand to exactly +* **Nothing moved.** The tiers as #1660 introduced them expand to exactly the legs, in the same order, that ``main`` spelt out at :data:`MAIN`, apart from the one deliberate move: the coverage leg and the ``gate`` from 3.14 to ``latest``, 3.15. Job-name templates are compared too, since rulesets match @@ -231,8 +231,8 @@ def setups(spec: 'dict[str, Any]') -> 'list[dict[str, Any]]': {'python-version': '3.9', 'ktlo': True, 'os': 'ubuntu-24.04'}, {'python-version': '3.10', 'ktlo': True}] CONDA_AT_MAIN = {'os': ['macos-latest', 'ubuntu-latest'], 'python-version': SUPPORTED_AT_MAIN} -#: The conda jobs' ``os`` axis since #1822, which added the Intel macOS runner, -#: and #2060, which added the Windows one. +#: The conda jobs' ``os`` axis, with the Intel macOS runner (#1822) and the +#: Windows one (#2060). CONDA_JOBS = (('create-release.yml', 'conda'), ('cron-conda.yml', 'conda-dist')) CONDA_OS = ['macos-latest', 'macos-26-intel', 'ubuntu-latest', 'windows-latest'] MAIN_MATRICES = { @@ -268,12 +268,12 @@ def setups(spec: 'dict[str, Any]') -> 'list[dict[str, Any]]': 'Conda deployment (update) on ${{ matrix.os }} with Python ${{ matrix.python-version }}', } #: Each unit-tests.yml job MAIN ran KTLO legs in, and the job those legs run in -#: since #1738, which nothing gating waits on. +#: (#1738), which nothing gating waits on. KTLO_TWINS = {('unit-tests.yml', 'test'): 'test-ktlo', ('unit-tests.yml', 'integration'): 'integration-ktlo', ('unit-tests.yml', 'pypcap-parity'): 'pypcap-parity-ktlo'} #: Ruleset 23497679's required contexts at MAIN. REQUIRED_AT_MAIN = {'Required checks passed'} | {f'Compat Python {v}' for v in SUPPORTED_AT_MAIN} -#: The tiers MAIN's matrices encode, as this change first wrote them into the file. +#: The tiers MAIN's matrices encode, as #1660 first wrote them into the file. TIERS_AT_MAIN = { 'latest': '3.15', 'versions': { @@ -478,7 +478,7 @@ def test_the_command_line_writes_each_output_as_the_runner_reads_it(self) -> Non self.assertEqual(self.parse_github_output(output.read_text(encoding='utf-8')), live_outputs()) def test_the_workflow_commands_byte_for_byte(self) -> None: - """#2047: the whole of each run's output, so moving it onto util/_actions.py can be shown to change nothing. + """#2047: the whole of each run's output, so any change to what is written fails here. Each multi-line output's delimiter is fresh, so it is read as ``EOF`` once each is shown to open and close one value, and to differ from the others. @@ -508,10 +508,10 @@ def test_the_workflow_commands_byte_for_byte(self) -> None: self.assertEqual(output.read_bytes().decode('utf-8'), written) def test_the_file_named_in_an_error_is_escaped(self) -> None: - """#2047: ``--file``'s ``%`` and line breaks are escaped, as the error text's always were. + """#2047: ``--file``'s ``%`` and line breaks are escaped, as the error text's are. - Before util/_actions.py the path went out as written, so a ``%`` in it read - as the start of an escape, and a line break split the command in two. + Written out unescaped, a ``%`` in the path would read as the start of an + escape, and a line break would split the command in two. """ missing = self.tmp / '50%\nmissing.toml' @@ -666,10 +666,10 @@ def test_engine_tests_installs_and_runs_mains_cells(self) -> None: def test_no_job_name_changes_but_the_gates(self) -> None: """Rulesets match check names literally, so each template and leg name stays. - ``test``'s template is :data:`MAIN`'s again since its experimental legs - left it for a job of their own (#1732). Since #1738 each job of - :data:`KTLO_TWINS` names its legs with a template of its own, the KTLO - job's ending `` (KTLO)``, so what stays is each leg's rendered name. + ``test``'s template is :data:`MAIN`'s, its experimental legs being a + job of their own (#1732). Each job of :data:`KTLO_TWINS` names its legs + (#1738) with a template of its own, the KTLO job's ending `` (KTLO)``, + so what stays is each leg's rendered name. """ for (name, job_id), template in TEMPLATES_AT_MAIN.items(): diff --git a/tests/project/test_python_versions_empty_job_1747.py b/tests/project/test_python_versions_empty_job_1747.py index 08e6a72644..bb509ad6a0 100644 --- a/tests/project/test_python_versions_empty_job_1747.py +++ b/tests/project/test_python_versions_empty_job_1747.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """#1747: the version updater names a job its update would leave with no leg. -Since #1746, ``pypcap-parity`` holds the supported versions below 3.12 only, so -3.11 reaching end of life leaves its matrix empty while ``required-checks`` -still needs it. The owner's ruling on #1747: the updater retires nothing; its +``pypcap-parity`` holds the supported versions below 3.12 only (#1746), so 3.11 +reaching end of life leaves its matrix empty while ``required-checks`` still +needs it. The owner's ruling on #1747: the updater retires nothing; its proposal flags the job as a manual step, for him to retire or remove it, rather than quietly writing a zero-leg matrix. The tier file is still written as computed, and the fingerprint is still the tiers alone. diff --git a/tests/project/test_python_versions_update_1660.py b/tests/project/test_python_versions_update_1660.py index 87cb18cdf9..e07f967ebb 100644 --- a/tests/project/test_python_versions_update_1660.py +++ b/tests/project/test_python_versions_update_1660.py @@ -304,7 +304,7 @@ def test_an_error_status_and_a_dropped_connection_are_retried(self) -> None: self.assertEqual(self.sleeps, [5, 10]) def test_the_last_failure_is_an_oserror_naming_the_source(self) -> None: - """``main`` reports an :exc:`OSError` as the run's error, as it did before.""" + """``main`` reports an :exc:`OSError` as the run's error.""" self.outcomes = [http_response(404)] * 3 with self.assertRaises(OSError) as caught: self.module.fetch('release-cycle') @@ -360,7 +360,7 @@ def test_a_line_of_prose_with_a_pipe_is_not_the_table(self) -> None: self.assertEqual(module.parse_images(text), [module.Image('ubuntu', '24.04', 'x64', ('ubuntu-24.04',))]) def test_what_it_refuses(self) -> None: - """The same errors as before markdown-it-py read the table.""" + """What the table reader refuses, and the error each gets.""" module = updater() cases = ( ('no table', 'Images: `ubuntu-24.04`.\n', 'has no table with a "YAML Label" column'), @@ -409,7 +409,7 @@ def test_the_snapshot_reproduces_the_file_but_for_pypy_312(self) -> None: _, plan, texts = plan_for(self, snapshot()) self.assertEqual(changes(plan), {'pypy3.12': (None, {'tier': 'supported'})}) self.assertEqual(plan['latest'], ['3.15', '3.15']) - # Only the tier file: releasing.rst counted the wheels until #1811, so a new PyPy rewrote it. + # Only the tier file: releasing.rst counts no wheels (#1811), so a new PyPy leaves it alone. self.assertEqual(sorted(texts), ['.github/python-versions.toml']) self.assertEqual(plan['required'], {'remove': [], 'add': []}) # #1958: a supported PyPy gets a `test` and an `integration` leg too. @@ -767,8 +767,8 @@ def test_a_run_that_changes_nothing_writes_nothing(self) -> None: root = self.scratch() plan, texts = updater().compute_plan(root, as_of_the_file(snapshot())) self.assertEqual((plan['changes'], texts, plan['notes']), ([], {}, [])) - # The leg counts this read back went with #1811; the fact behind them is - # that the hand-kept entries are experimental.yml's legs and no Unit Tests job's. + # No leg count is read back (#1811); the fact that matters is that the hand-kept + # entries are experimental.yml's legs and no Unit Tests job's. module = updater() values = module.pv.outputs(module.validate((root / module.TIER_FILE).read_text(encoding='utf-8'))) found = {job: {leg.get('python-version') for leg in legs} @@ -1262,11 +1262,11 @@ def test_publish_follows_the_branchs_fingerprint(self) -> None: class TestDetectAgainstAHandEditedBranch(ScratchOrigin): - """The cross-review's case, end to end: ``update``, ``body`` and ``publish`` against a scratch ``origin``. + """A hand-edited branch, end to end: ``update``, ``body`` and ``publish`` against a scratch ``origin``. The pull request body tells the owner to finish a failed audit by hand on the - branch. A ``detect`` reading only the tip then took his commit for no - proposal, asked for the approval every week, and ``publish`` refused it. + branch. A ``detect`` reading only the tip would take his commit for no + proposal, ask for the approval every week, and ``publish`` would refuse it. """ @@ -1385,9 +1385,9 @@ class TestTheWorkflowCommands(unittest.TestCase): """#2047: every annotation, step output and summary line the script writes, byte for byte. The other tests read the commands with ``assertIn``; these pin each whole - line, in order, with text each escape changes, so moving the writes onto - util/_actions.py can be shown to change nothing. The plan, ``git``, ``gh`` - and the branch lookups are stubbed: only the writing is under test. + line, in order, with text each escape changes, so any change to what is + written fails here. The plan, ``git``, ``gh`` and the branch lookups are + stubbed: only the writing is under test. """ diff --git a/tests/project/test_release_gates.py b/tests/project/test_release_gates.py index 60a6a5c157..c728cd2bdd 100644 --- a/tests/project/test_release_gates.py +++ b/tests/project/test_release_gates.py @@ -2,10 +2,10 @@ """Tests that nothing in the workflows that publish does so ungated or unpublished. Two files: :file:`.github/workflows/create-release.yml`, which everything up to -:class:`TestYAMLAgreesWithTheScanner` is about, and -- since #1632 -- -:file:`.github/workflows/cron-conda.yml`, whose conda build bumps stopped being -tagged and uploaded altogether. They share the scanner, the expression -evaluator and the cascade model below; nothing else. +:class:`TestYAMLAgreesWithTheScanner` is about, and +:file:`.github/workflows/cron-conda.yml`, whose conda build bumps #1632 found +neither tagged nor uploaded. They share the scanner, the expression evaluator +and the cascade model below; nothing else. #641: the ``pypi`` job's ``environment: release`` was commented out while the ``id-token: write`` from the same upstream snippet had been re-added live below it, @@ -48,9 +48,9 @@ tier runs on a fresh clone with no network and no token. It is written down in the workflow's own comment and in #641 instead. * **Whether GitHub requests one approval per environment or one per matrix leg.** - ``pypi`` and ``conda`` are both matrix jobs. That is a property of - the Actions runner, observable only from a real run -- and moot for both, - since #887 removed their required reviewers. The question would resurface + ``pypi`` and ``conda`` are both matrix jobs. That is a property of the + Actions runner, observable only from a real run -- and moot for both, + which have no required reviewers (#887). The question would resurface only if a matrix job were gated by its own environment again. :mod:`yaml` comes only with the ``test`` extra, so the scanner in this module is @@ -167,14 +167,14 @@ #1632 is what happens when a job does not escape it, so for :file:`cron-conda.yml` the cascade is evaluated rather than only asserted -about. Since #731 that workflow runs on ``workflow_run`` and skips its own +about. That workflow runs on ``workflow_run`` (#731) and skips its own ``unit-tests`` gate on that event; ``conda-update`` escapes the skipped ancestor with ``!cancelled()`` and commits a build bump, while ``conda-tag`` and ``conda-dist`` called no status-check function at all -- so Actions' implicit ``success()`` skipped both, and the five bumps #1632 lists were -committed and then neither tagged nor uploaded. Measured on run -38325136710: ``Update requirements.txt`` succeeded with its Bump, Commit and -Push steps all green (pushing ``f4f94bcad``), and both downstream jobs reported +committed and then neither tagged nor uploaded. Measured on run 38325136710: +``Update requirements.txt`` succeeded with its Bump, Commit and Push steps +all green (pushing ``f4f94bcad``), and both downstream jobs reported ``skipped``. That defect sits one layer *below* an ``if:`` -- both conditions evaluated @@ -184,8 +184,8 @@ :func:`job_runs`, which composes the declared condition with :func:`cascade_skipped_by`'s model of the implicit check; :class:`TestTheCascadeModel` pins that model on fixtures, including the -pre-fix condition text, so the model itself cannot drift into agreeing with -whatever the file happens to say. +condition text #1632 found, with no status-check call, so the model itself +cannot drift into agreeing with whatever the file happens to say. Getting past the cascade exposed a second defect: two runs can compute the same bump from one checkout, and the second reports the bump the first already @@ -207,11 +207,11 @@ :file:`util/verdict_covers_checkout.py`'s rule, and Conda Tag tags the ``CONDA_COMMIT`` it pushed; :class:`TestCondaTagTagsWhatWasPushed` runs Conda Tag's own steps after it. -Since #1967 the ``unit-tests`` gate and the ``verdict`` job are the jobs of -:file:`cron-head.yml`, which ``cron-conda.yml`` calls as its ``head`` job. The -rows still say whether each of those two runs, and :data:`HEAD_JOBS` are -decided against that file; the jobs of ``cron-conda.yml`` read ``head``'s result -and its ``verified`` output. +The ``unit-tests`` gate and the ``verdict`` job are the jobs of +:file:`cron-head.yml` (#1967), which ``cron-conda.yml`` calls as its ``head`` +job. The rows still say whether each of those two runs, and :data:`HEAD_JOBS` +are decided against that file; the jobs of ``cron-conda.yml`` read ``head``'s +result and its ``verified`` output. """ @@ -243,15 +243,14 @@ #: The workflow #641 is about, and the one every test above #: :class:`TestCondaBumpIsTaggedAndUploaded` reads. #: -#: Not the only one that publishes, which this comment claimed until #1632: -#: :file:`cron-conda.yml` uploads to anaconda.org and attaches the same -#: packages to a GitHub Release, from its ``conda-dist`` job -#: (``anaconda/actions/upload-package`` and ``ncipollo/release-action``, -#: :file:`cron-conda.yml` at the end of the file). It is read through -#: :data:`CONDA_WORKFLOW` below, for whether a bump reaches those uploads at -#: all; the ``environment:`` rule in this module is asserted over *this* file -#: only. ``conda-dist`` names ``anaconda`` since #1968, but for its -#: ``ANACONDA_TOKEN`` rather than as an approval gate, which +#: Not the only one that publishes (#1632): :file:`cron-conda.yml` uploads to +#: anaconda.org and attaches the same packages to a GitHub Release, from its +#: ``conda-dist`` job (``anaconda/actions/upload-package`` and +#: ``ncipollo/release-action``, :file:`cron-conda.yml` at the end of the +#: file). It is read through :data:`CONDA_WORKFLOW` below, for whether a bump +#: reaches those uploads at all; the ``environment:`` rule in this module is +#: asserted over *this* file only. ``conda-dist`` names ``anaconda`` (#1968), +#: but for its ``ANACONDA_TOKEN`` rather than as an approval gate, which #: :mod:`tests.project.test_secret_environments_1968` checks. #: #: :file:`cron-vendor.yml` does build and commit without uploading, and @@ -275,11 +274,12 @@ 'pypa/gh-action-pypi-publish': 'uploads a distribution to PyPI or TestPyPI', 'anaconda/actions/upload-package': 'uploads a package to anaconda.org', # #2044: the ``macos-26-intel`` leg's ``anaconda … upload``, which - # util/release_conda.py makes since #1967; a bare ``anaconda`` is not matched. + # util/release_conda.py makes (#1967); a bare ``anaconda`` is not + # matched. 'util/release_conda.py upload': 'uploads a package to anaconda.org with the anaconda-client CLI', 'softprops/action-gh-release': 'creates a public GitHub Release, and the tag it names', - # ``pypi`` and ``conda`` attach their files with ``gh release upload`` since - # #1634, which util/release_upload.py runs since #1967. + # ``pypi`` and ``conda`` attach their files with ``gh release upload`` + # (#1634), which util/release_upload.py runs (#1967). 'util/release_upload.py': 'attaches a file to a public GitHub Release', 'git push': 'pushes a commit or a tag to the repository', } @@ -389,8 +389,8 @@ def declared_needs(block: 'str') -> 'list[str]': """The job names in this job's ``needs: [ ... ]`` list, empty if it has none. ``version_check`` has no ``needs:`` at all and correctly returns ``[]`` - rather than raising, since it is the root of the graph this module walks -- - since #1052, when it moved ahead of ``unit-tests``. + rather than raising, since it is the root of the graph this module walks, + ahead of ``unit-tests`` (#1052). """ for line in block.splitlines(): @@ -405,7 +405,7 @@ def declared_if(block: 'str') -> 'str': Handles both the single-line ``if: ${{ ... }}`` form used elsewhere in this file and the folded block-scalar form (``if: >-``, continued below at six - spaces) #888 introduces for ``tag``, ``pypi`` and ``conda``, whose + spaces) #888 introduced for ``tag``, ``pypi`` and ``conda``, whose conditions grew past one line. Folding here is "join each continuation line with a single space", which is @@ -1198,9 +1198,9 @@ def gate_rows(job: 'str') -> 'list[tuple[str, dict[str, object], bool]]': ``version_check`` actually leaves. Weaker as a guard, and kept because it is the shape the retry case takes. * **``unit-tests`` over all four results**, #1052's axis. Only - ``success`` runs: since that issue the release test gate skips whenever - nothing is publishable, so a ``skipped`` gate is a normal result, and a - publisher that admitted one would publish untested. + ``success`` runs: the release test gate skips whenever nothing is + publishable, so a ``skipped`` gate is a normal result, and a publisher + that admitted one would publish untested. * **Each publishing predecessor over all four results.** ``success`` and a legitimate ``skipped`` both run -- that is #888's escape hatch, and the ``skipped`` row is what stops it being quietly removed again -- while @@ -1324,8 +1324,8 @@ def text_gate_violations(condition: 'str', job: 'str') -> 'list[str]': ``version_check`` having strictly succeeded. The mirror exists so that :class:`TestGateMutationsChangeTheTruthTable` can report *which* layer rejects each mutation; #962 settled that the text assertions themselves - stay exactly as #961 left them, so they are copied here rather than - refactored into a helper the real tests then call. + stay as #961 wrote them, so they are copied here rather than refactored + into a helper the real tests then call. ``test_the_real_conditions_violate_neither_layer`` is what keeps the mirror from drifting away from the original. @@ -1693,8 +1693,9 @@ def conda_context(unit_tests: 'str' = 'skipped', Except in one respect: ``verdict`` defaults to a verdict that covers the checkout. 38325136710's did not -- it was for 9083163f5 with 2e941bbb7 - checked out -- and since #1643 that run's own shape is the row that skips - ``conda-update``. ``verified=NULL`` is a ``verdict`` that wrote no output. + checked out -- and that run's own shape is the row that skips + ``conda-update`` (#1643). ``verified=NULL`` is a ``verdict`` that wrote no + output. ``unit_tests`` and ``verdict`` are :data:`HEAD_JOBS`' results (#1967). They sit under ``needs.head.jobs``, which no expression reads, and make @@ -1902,9 +1903,9 @@ def conda_context(unit_tests: 'str' = 'skipped', outputs_present=False), {'verdict': False, 'conda-update': False, 'conda-tag': False, 'conda-dist': False}), # #2060: a manual run. Without `publish` it builds what `conda-update` - # left and tags nothing, as before; with it from `main`, the build is - # bumped although nothing changed, then tagged and uploaded as the - # schedule's is; with it from another ref, `mode` fails and nothing runs. + # left and tags nothing; with it from `main`, the build is bumped + # although nothing changed, then tagged and uploaded as the schedule's + # is; with it from another ref, `mode` fails and nothing runs. ('a manual run without publish, nothing changed', conda_context(event_name='workflow_dispatch', unit_tests='success', verdict='skipped', verified=NULL, files_changed='false', pushed=NULL, conda_tag='skipped'), @@ -1949,7 +1950,7 @@ def test_every_publishing_job_declares_an_environment(self) -> None: """No job may reach PyPI, Anaconda, a Release or a git push unapproved. An ``environment:`` is what lets Actions hold a job for a human, but - since #887 only ``github-release``'s actually does -- ``conda-tag``, + only ``github-release``'s actually does (#887) -- ``conda-tag``, ``pypi`` and ``anaconda`` kept their environments with no reviewer on them, and are held instead by depending on ``github`` through ``needs:``. This test only checks that the line is present, not that @@ -1992,11 +1993,12 @@ def test_each_gated_job_has_its_own_environment(self) -> None: prevent rather than rename. Different credentials reach the two indexes -- OIDC trusted publishing and ``ANACONDA_TOKEN`` -- and ``tag``'s write to ``main`` uses a third, so sharing one environment - would still merge those blast radii even though, since #887, only - ``github-release`` carries a reviewer of its own; the other gated jobs - are held by depending on it instead. That each *does* depend on it is - :class:`TestGatedJobsDependOnGithub`'s check, not this one -- this - test only stops the four gates from being collapsed back into one. + would still merge those blast radii even though only + ``github-release`` carries a reviewer of its own (#887); the other + gated jobs are held by depending on it instead. That each *does* + depend on it is :class:`TestGatedJobsDependOnGithub`'s check, not + this one -- this test only stops the four gates from being collapsed + back into one. """ environments = {} # type: dict[str, list[str]] @@ -2231,7 +2233,7 @@ def test_dropping_any_gate_clause_is_caught(self) -> None: self.assertTrue(gate_coverage_violations(mutant, self.jobs)) -#: ``version_check``'s PyPI and Anaconda evidence checks, since #1967. +#: ``version_check``'s PyPI and Anaconda evidence checks (#1967). EVIDENCE_SCRIPT = 'util/release_published.py' @@ -2343,7 +2345,7 @@ def test_version_check_must_strictly_succeed(self) -> None: pair applied to "each of their own direct dependencies", which is false for ``version_check`` specifically -- a direct dependency of all three, required to have *strictly* succeeded, with no - ``|| 'skipped'``. Nothing in this module pinned that before. + ``|| 'skipped'``. An ``assertNotIn`` over known-bad spellings cannot pin this, because a weakening can keep the ``== 'success'`` literal while meaning the @@ -2363,18 +2365,17 @@ def test_version_check_must_strictly_succeed(self) -> None: whole -- which fails *closed*, since Actions rejects the real expression and nothing publishes. - **Not closed by this test:** a top-level ``||`` outside the - conjunction chain. Appending ``|| github.event_name == - 'workflow_dispatch'`` to the end of the condition, or wrapping it in - ``true || ...``, bypasses the gate while leaving the compacted clause - byte-identical. Conjunction is monotone, so *adding* ``&&`` conjuncts - can only strengthen the gate and needs no guard; disjunction is the - only remaining weakening, and catching it needs the expression - evaluated rather than matched. + **Not closed by this test:** a top-level ``||`` outside the conjunction + chain. Appending ``|| github.event_name == 'workflow_dispatch'`` to the + end of the condition, or wrapping it in ``true || ...``, bypasses the + gate while leaving the compacted clause byte-identical. Conjunction is + monotone, so *adding* ``&&`` conjuncts can only strengthen the gate and + needs no guard; disjunction is the only remaining weakening, and + catching it needs the expression evaluated rather than matched. :class:`TestGateExpressionsEvaluateCorrectly` is where #962 does that, and ``TestGateMutationsChangeTheTruthTable.MUTATIONS`` is where the ``true ||`` prefix is recorded as caught by the evaluator and missed - here. This test stays as #961 left it: it fails with a message naming + here. This test stays as #961 wrote it: it fails with a message naming the clause rather than a scenario row, and it catches the one thing the evaluator cannot -- operands reversed, which keeps the truth table intact. @@ -2546,13 +2547,13 @@ class TestGateExpressionsEvaluateCorrectly(WorkflowMixin, unittest.TestCase): ] #: ``(row name, context, whether version_check must run)``. Its ``if:`` - #: is the workflow's outermost gate -- held by ``unit-tests`` until #1052 - #: moved ``version_check`` ahead of it: a ``workflow_run`` trigger only - #: proceeds when the Vendor Update run that triggered it concluded - #: ``success``, and every other trigger proceeds -- #1634 -- unless it is - #: the ``v*`` push the release App's token makes, whose run would rerun - #: the gate and ask for a second approval. A person's push, and a - #: ``workflow_run`` whatever its actor, still run. + #: is the workflow's outermost gate -- ``version_check`` runs ahead of + #: ``unit-tests`` (#1052): a ``workflow_run`` trigger only proceeds when + #: the Vendor Update run that triggered it concluded ``success``, and + #: every other trigger proceeds -- #1634 -- unless it is the ``v*`` push + #: the release App's token makes, whose run would rerun the gate and ask + #: for a second approval. A person's push, and a ``workflow_run`` + #: whatever its actor, still run. VERSION_CHECK_ROWS = [ ('a v* tag push', release_context(event_name='push'), True), ("a v* tag push by the release App's bot", @@ -2667,12 +2668,12 @@ def test_the_table_covers_every_declared_dependency(self) -> None: """The scenario table has to name the same predecessors the file does. :data:`PUBLISHING_PREDECESSORS` is hand-written, so a ``needs:`` added - to one of these jobs would otherwise be absent from every row -- - held at a running default and never varied, which is a gate the table + to one of these jobs would otherwise be absent from every row -- held + at a running default and never varied, which is a gate the table silently stops checking. :data:`STRICT_PREDECESSORS` -- - ``version_check`` and, since #1052, ``unit-tests`` -- are added back - here because they are deliberately *not* in that mapping: they are held - to the stricter ``== 'success'`` alone, with no ``|| 'skipped'``. + ``version_check`` and ``unit-tests`` (#1052) -- are added back here + because they are deliberately *not* in that mapping: they are held to + the stricter ``== 'success'`` alone, with no ``|| 'skipped'``. """ for job in ('tag', 'pypi', 'conda'): @@ -2792,10 +2793,9 @@ class TestGateMutationsChangeTheTruthTable(WorkflowMixin, unittest.TestCase): """Every mutation, applied to a scratch copy of each real condition, with the layer that rejects it recorded. - This is the part the three earlier attempts at #961 got wrong: a test that - passes against the current file says nothing about what it would catch. - So each mutation below is actually applied and actually run, against both - layers -- :func:`gate_violations` (the evaluator) and + A test that passes against the current file says nothing about what it + would catch. So each mutation below is actually applied and actually run, + against both layers -- :func:`gate_violations` (the evaluator) and :func:`text_gate_violations` (the assertions :class:`TestEvidenceBasedGating` already makes) -- and the set of layers that rejected it is asserted exactly rather than "at least one did". @@ -2824,15 +2824,14 @@ class TestGateMutationsChangeTheTruthTable(WorkflowMixin, unittest.TestCase): #: regenerated condition would differ in ways nobody would have typed. #: #: The mutations #962 names come first, then this module's own, covering - #: the clauses the issue only points at -- the - #: publishing predecessors' equality pair, the ``startsWith`` - #: short-circuit, and reading somebody else's evidence. The ``!= 'true'`` - #: row is #967's, and is the only row here that the table as #962 left it - #: did not catch at all: it is rejected by exactly one scenario -- the - #: ``null`` evidence row :func:`gate_rows` grew for it -- and by no text - #: assertion. The rows after it are #1052's, for the release test gate - #: conjunct that issue added, and the last rows are #1748's, for its - #: released-elsewhere conjunct. + #: the clauses the issue only points at -- the publishing predecessors' + #: equality pair, the ``startsWith`` short-circuit, and reading somebody + #: else's evidence. The ``!= 'true'`` row is #967's, and is the only row + #: here that #962's table did not catch at all: it is rejected by exactly + #: one scenario -- the ``null`` evidence row :func:`gate_rows` grew for + #: it -- and by no text assertion. The rows after it are #1052's, for the + #: release test gate conjunct that issue added, and the last rows are + #: #1748's, for its released-elsewhere conjunct. MUTATIONS = [ ('the && before the version_check clause becomes ||', "!cancelled() && needs.version_check.result", @@ -3221,7 +3220,7 @@ def test_the_test_gate_is_not_mistaken_for_a_publisher(self) -> None: self.assertEqual(publishing_reasons(self.jobs[name]), []) -#: ``release_status``'s step, and the script it runs since #1967. +#: ``release_status``'s step, and the script it runs (#1967). RELEASE_STATUS_STEP = 'Report why this run did or did not release anything' RELEASE_STATUS_SCRIPT = 'util/release_status.py' @@ -3296,14 +3295,14 @@ class TestReleaseStatusScriptExecutesCorrectly(WorkflowMixin, unittest.TestCase) over a table of the shapes a real run can take: the step's ``python3 util/release_status.py`` (#1967), on this interpreter. - A cross-review of the #888 change found that an earlier revision of this - script demanded the same outcome (all four release jobs ``skipped``, or - all four ``success``) before treating a run as complete, which reads a - self-heal -- ``github``/``tag`` skipping because their artefacts already - exist, ``pypi``/``conda`` running and succeeding because theirs did not -- - as the #888 shape itself, erroring on the run that just fixed it. Every - other test in this module is a string assertion over the YAML, so none - of them executed the script and none of them could have caught that. + A script demanding the same outcome (all four release jobs ``skipped``, or + all four ``success``) before treating a run as complete reads a self-heal + -- ``github``/``tag`` skipping because their artefacts already exist, + ``pypi``/``conda`` running and succeeding because theirs did not -- as the + #888 shape itself, erroring on the run that just fixed it, as a + cross-review of #888 found. Every other test in this module is a string + assertion over the YAML, so none of them executes the script or could + catch that. """ @@ -3345,8 +3344,8 @@ def _run(self, overrides: 'dict[str, str]') -> 'subprocess.CompletedProcess[str] ) #: ``(scenario name, env overrides, expected title substring, expected exit code)``. - #: The self-heal row is the one an earlier revision of the script got - #: wrong: mixed results, all reconciled against their own evidence. + #: The self-heal row is the one a same-outcome check gets wrong: mixed results, all + #: reconciled against their own evidence. VECTORS = [ ( 'vendor_update_had_nothing_to_do', @@ -3646,7 +3645,7 @@ class TestPythonSnippetsSurviveOlderInterpreters(WorkflowMixin, unittest.TestCas itself put there -- this class is what actually runs each snippet under an older interpreter to check that claim, rather than trusting it. - Since #1967 the PyPI and Anaconda evidence checks in ``version_check`` feed + The PyPI and Anaconda evidence checks in ``version_check`` (#1967) feed ``python3 -`` from :file:`util/release_published.py` instead, and ``conda``'s per-leg check from :file:`util/release_conda.py`, so this runs those snippets, as those scripts build them. @@ -3821,7 +3820,7 @@ def test_the_same_job_to_if_mapping(self) -> None: """:func:`declared_if` against the same real parse, for the same reason. This is the one that actually exercises the folded block-scalar form - #888 introduces -- ``test_the_same_job_to_environment_mapping`` and + #888 introduced -- ``test_the_same_job_to_environment_mapping`` and ``test_the_same_job_to_needs_mapping`` above only ever see single-line values, so neither would have caught a folding mistake the way this one does. @@ -3869,12 +3868,11 @@ class TestCondaBumpIsTaggedAndUploaded(CondaWorkflowMixin, unittest.TestCase): The chain is ``head`` (the gate, :file:`cron-head.yml`) -> ``conda-update`` (bumps ``conda/build`` and pushes the commit) -> ``conda-tag`` (pushes - ``conda-+``) -> ``conda-dist`` (builds that tag and uploads - it to anaconda.org). Since #731 the gate skips on the ``workflow_run`` - event, and the two publishing jobs called no status-check function, so - Actions' implicit ``success()`` skipped them behind the skipped gate even - though ``conda-update`` had escaped it and succeeded -- see this module's - docstring for the measured run. + ``conda-+``) -> ``conda-dist`` (builds that tag and uploads it to + anaconda.org). The gate skips on the ``workflow_run`` event (#731), and in #1632 + the two publishing jobs called no status-check function, so Actions' implicit + ``success()`` skipped them behind the skipped gate even though ``conda-update`` + had escaped it and succeeded -- see this module's docstring for the measured run. Every assertion here goes through :func:`job_runs` rather than :func:`evaluate_condition`, because the conditions were *true* on the run @@ -4051,8 +4049,8 @@ def test_a_bump_another_run_already_pushed_is_not_tagged_again(self) -> None: Run 38325153882's ``files_changed`` was ``'true'`` and its push landed nothing, so the row is decided by which of the two ``CONDA_CHANGED`` is - mapped to. Mapped to ``files_changed``, as it was, ``conda-tag`` runs - and fails on a second ``git tag conda-1.5.0b9+3``. + mapped to. Mapped to ``files_changed``, ``conda-tag`` runs and fails on + a second ``git tag conda-1.5.0b9+3``. """ context = conda_context(pushed='false', conda_tag='skipped', conda_dist='skipped') @@ -4182,7 +4180,7 @@ def test_conda_tag_checks_out_and_tags_the_commit_push_changes_pushed(self) -> N 'Create Tag does not tag `$CONDA_COMMIT` by name') def test_cron_conda_really_does_publish(self) -> None: - """The claim #1632 found wrong in this module's own comment. + """The claim #1632 found wrong in a comment of this module's. :data:`WORKFLOW`'s comment read that ``cron-conda.yml`` "build[s] and commit[s] but upload[s] nothing", which is how this chain came to have @@ -4883,10 +4881,10 @@ class TestTheCascadeModel(unittest.TestCase): The cascade model is the one thing in this module that is not checkable against the file it describes -- it *is* the description -- so it is pinned - on a fixture carrying the #1632 shape, and on the pre-fix condition text - itself. Same reasoning as :class:`TestTheExpressionEvaluator`: a mistake - here would otherwise show up as a quietly wrong row in - :data:`CONDA_ROWS`. + on a fixture carrying the #1632 shape, and on the condition text #1632 + found, with no status-check call. Same reasoning as + :class:`TestTheExpressionEvaluator`: a mistake here would otherwise show up + as a quietly wrong row in :data:`CONDA_ROWS`. """ diff --git a/tests/project/test_release_notice_states_no_totals_1849.py b/tests/project/test_release_notice_states_no_totals_1849.py index f716a4e133..49f9072756 100644 --- a/tests/project/test_release_notice_states_no_totals_1849.py +++ b/tests/project/test_release_notice_states_no_totals_1849.py @@ -3,11 +3,11 @@ ``release_status`` in :file:`.github/workflows/create-release.yml` posts it when every target reconciled by skipping. It named fixed PyPI and Anaconda totals, -while ``version_check`` derives both at release time from the tag's own tier -file, so the notice went wrong once a supported version was added or retired, -and for an older tag. The step is read with :func:`tests._workflows.steps` and its -script, :file:`util/release_status.py` since #1967, run in that state, so the -notice checked is the one a run prints. +while ``version_check`` derives both at release time from the tag's own tier file, +so the notice went wrong once a supported version was added or retired, and for an +older tag. The step is read with :func:`tests._workflows.steps` and its script, +:file:`util/release_status.py` (#1967), run in that state, so the notice checked +is the one a run prints. """ diff --git a/tests/project/test_release_tags_the_run_sha_1634.py b/tests/project/test_release_tags_the_run_sha_1634.py index 1ea75c6280..4c663ca68e 100644 --- a/tests/project/test_release_tags_the_run_sha_1634.py +++ b/tests/project/test_release_tags_the_run_sha_1634.py @@ -1316,7 +1316,7 @@ def test_the_released_reader_runs_with_its_own_helper(self) -> None: self.assertNotIn('Error', result.stdout + result.stderr) def test_a_reader_from_before_the_helper_runs_alone(self) -> None: - """A commit without util/_actions.py, whose reader imports nothing from it, is read as before.""" + """A commit without util/_actions.py, whose reader imports nothing from it, is still read.""" (self.seed / 'util' / '_actions.py').unlink() (self.seed / 'util' / 'python_versions.py').write_text( "import sys\nprint({'pypi-wheels': 'cp37 cp38', 'conda-builds': 'py37 py38'}[sys.argv[-1]])\n", @@ -1328,7 +1328,7 @@ def test_a_reader_from_before_the_helper_runs_alone(self) -> None: self.assertEqual(written, {'pypi-wheels': 'cp37 cp38', 'conda-builds': 'py37 py38'}) def test_a_finished_release_reads_complete_after_the_tiers_grow(self) -> None: - """The two checks, fed the step's answer and, as before it, this run's tiers.""" + """The two checks, fed the step's answer and this run's tiers.""" self.push(self.added, f'refs/tags/v{self.VERSION}') self.checkout(self.grown) _, written = self.released(exists=True) @@ -1690,29 +1690,30 @@ def runs(self, outputs: 'dict[str, str]', runner: 'str' = 'ubuntu-latest') -> 'b class TestCondaUpdateNeverMakesTheRelease(CondaDistMixin, unittest.TestCase): """:file:`cron-conda.yml`'s GitHub upload only ever updates a published release. - ``conda-dist`` attaches its packages with ``ncipollo/release-action@v1.21.0`` - and ``allowUpdates: true``, kept so #1632's ``test_cron_conda_really_does_publish`` - still finds it. That action's ``createOrUpdateRelease`` (``src/Action.ts``) - reads the release by tag, which GitHub's REST docs give as "a published - release"; on a 404 it updates a draft of that tag or else calls - ``createRelease`` (``checkForMissingReleaseError`` and - ``updateDraftOrCreateRelease``), both with ``target_commitish: commit`` - (``updateRelease``, and ``GithubReleases.create`` and ``GithubReleases.update`` in - ``src/Releases.ts``). With no release, a bump - made while Create Release waits on its approval -- 1.5.0b9 waited three - days, and cron-conda bumped three times meanwhile -- would put - ``v`` on ``main``'s tip, and Create Release's tag check would then - fail. A draft stays a draft (``omitDraftDuringUpdate``, through - ``CoreInputs.updatedDraft`` in ``src/Inputs.ts``) but is retargeted at ``commit``; Create Release's + ``conda-dist`` attaches its packages with + ``ncipollo/release-action@v1.21.0`` and ``allowUpdates: true``, kept so + #1632's ``test_cron_conda_really_does_publish`` still finds it. That + action's ``createOrUpdateRelease`` (``src/Action.ts``) reads the release by + tag, which GitHub's REST docs give as "a published release"; on a 404 it + updates a draft of that tag or else calls ``createRelease`` + (``checkForMissingReleaseError`` and ``updateDraftOrCreateRelease``), both + with ``target_commitish: commit`` (``updateRelease``, and + ``GithubReleases.create`` and ``GithubReleases.update`` in + ``src/Releases.ts``). With no release, a bump made while Create Release + waits on its approval -- 1.5.0b9 waited three days, and cron-conda bumped + three times meanwhile -- would put ``v`` on ``main``'s tip, and + Create Release's tag check would then fail. A draft stays a draft + (``omitDraftDuringUpdate``, through ``CoreInputs.updatedDraft`` in + ``src/Inputs.ts``) but is retargeted at ``commit``; Create Release's ``workflow_run`` run finds the draft and resets its target to the run's sha (``action-gh-release`` 3.0.2's ``release`` and ``findTagFromReleases`` in - ``src/github.ts``), so - skipping a draft is a precaution against retargeting a release Create - Release owns. On an existing release it calls Update a release with - ``target_commitish: commit`` (``Action.updateRelease`` in ``src/Action.ts``, - ``GithubReleases.update`` in ``src/Releases.ts``), and GitHub's REST docs check "the new value of - target_commitish if you are changing it, otherwise the existing target" - against the workflow-file rule, which since #1634 is the run's commit. + ``src/github.ts``), so skipping a draft is a precaution against retargeting + a release Create Release owns. On an existing release it calls Update a + release with ``target_commitish: commit`` (``Action.updateRelease`` in + ``src/Action.ts``, ``GithubReleases.update`` in ``src/Releases.ts``), and + GitHub's REST docs check "the new value of target_commitish if you are + changing it, otherwise the existing target" against the workflow-file rule, + which is the run's commit (#1634). """ def test_the_action_is_still_the_upload(self) -> None: diff --git a/tests/project/test_resource_monitor_1740.py b/tests/project/test_resource_monitor_1740.py index bc2a0fb283..d05554e2c1 100644 --- a/tests/project/test_resource_monitor_1740.py +++ b/tests/project/test_resource_monitor_1740.py @@ -237,14 +237,15 @@ def test_the_stop_step_passes_on_a_leg_the_monitor_never_started(self) -> None: self.assertEqual(stop.stdout, 'The resource monitor did not start on this leg.\n') -#: Round 2's containment, as the issue's research sized it (estimates, until a run shows them); -#: #1753 adds the exit on a spent heap, which tests/project/test_graalpy_worker_heap_1753.py pins. +#: The containment, as the issue's research sized it (estimates); #1753 +#: adds the exit on a spent heap, which +#: tests/project/test_graalpy_worker_heap_1753.py pins. HEAP, GRAAL_WORKERS, SCOPE_MEMORY = '--vm.Xmx2g --vm.XX:+ExitOnOutOfMemoryError', '3', '12G' #: A ``LATEST``-tier module, as ``List the LATEST tier`` would hand the tests step (#1958): #: a stand-in naming no real module, so this pin runs no test module by path. SWEEP = 'tests/protocols/test_a_latest_sweep_unit.py' FSIZE = 2 * 1024 ** 3 -#: Round 3: the probe is the scope itself, every flag included, under a throwaway unit. +#: The probe is the scope itself, every flag included, under a throwaway unit. PROBE = 'if probe=$(scoped "pcapkit-probe-$$.scope" true 2>&1); then' SCOPE_FLAGS = ('sudo -n --preserve-env systemd-run --scope --quiet --unit="$unit"', f'-p MemoryMax={SCOPE_MEMORY}', '-p MemorySwapMax=0', '-p CPUWeight=20', @@ -269,7 +270,7 @@ def by_impl(expression: 'str', version: 'str') -> 'str': class TestTheTestsAreContained(unittest.TestCase): - """Round 2: the tests are held to what leaves the runner alive, so a breach fails the step.""" + """The tests are held to what leaves the runner alive, so a breach fails the step.""" def legs(self) -> 'list[str]': found = [leg['python-version'] for leg in legs(*EXPERIMENTAL)] @@ -434,7 +435,7 @@ def test_without_a_scope_the_tests_run_uncapped_with_a_warning(self) -> None: 'without the 12G scope. Experimental legs never block (#1660), so this job still succeeds.']) def test_the_uncapped_tests_run_the_capped_argv(self) -> None: - """Round 3: the fallback drops the scope and nothing else, so the two paths run one pytest.""" + """The fallback drops the scope and nothing else, so the two paths run one pytest.""" argvs = {} for path, sudo in (('capped', None), ('uncapped', 'echo "sudo: a password is required" >&2\nexit 1\n')): if sudo is None: @@ -496,7 +497,7 @@ def test_the_summary_reports_each_cause_distinctly(self) -> None: self.assertIn('scope 12G', oom) def test_a_hung_teardown_killed_after_the_timeout_is_a_timeout(self) -> None: - """Round 3: ``--kill-after`` ends a teardown SIGINT did not with SIGKILL, exit 137, as an OOM kill does.""" + """``--kill-after`` ends a teardown SIGINT did not with SIGKILL, exit 137, as an OOM kill does.""" base = 'scope=12G\nmemory_errors=0\n' timeout = ['tests timed out at 110 minutes'] self.assertEqual(self.titles(base + 'exit=137\noom_kills=0\nelapsed=6781\n'), timeout) @@ -527,7 +528,7 @@ def test_the_summary_is_quiet_without_a_status(self) -> None: self.assertIn('the tests step wrote no tests-status', result.stdout) def test_experimental_summary_is_mains_and_runs_last(self) -> None: - """Round 3: the causes are a step of their own, so ``Experimental summary`` stays the KTLO summary's.""" + """The causes are a step of their own, so ``Experimental summary`` stays the KTLO summary's.""" self.assertEqual(position(SUMMARY) + 1, position('Experimental summary')) self.assertEqual(step(SUMMARY).get('if'), '${{ !cancelled() }}') self.assertIs(step(SUMMARY).get('continue-on-error'), True) diff --git a/tests/project/test_rfc_anchor_fragments.py b/tests/project/test_rfc_anchor_fragments.py index 51ba8bf354..64b185f4f1 100644 --- a/tests/project/test_rfc_anchor_fragments.py +++ b/tests/project/test_rfc_anchor_fragments.py @@ -5,16 +5,15 @@ ``secion``, missing the ``t`` -- in both :file:`pcapkit/vendor/ftp/command.py` (inside the ``LINE`` f-string template, the source of truth) and its generated copy :file:`pcapkit/const/ftp/command.py`. Under Sphinx the role is -``sphinx.roles.RFC`` (:file:`sphinx/roles.py`, ``build_uri``, around -:file:`~362-367`): it appends whatever follows ``#`` onto the RFC's base URL -with no validation at all, and returns a plain ``nodes.reference`` carrying -that URL as ``refuri`` -- never a ``pending_xref``. Docutils' own -``rfc_reference_role`` behaves identically for the same reason. So this -rendered a live link to a **non-existent** anchor, and no build warned: not -because nitpicky mode is scoped to ``py:`` targets -- this repository's -``docs/source/conf.py`` does not enable nitpicky at all -- but because there -is no cross-reference node here for nitpicky to ever have inspected in the -first place, on any Sphinx configuration. +``sphinx.roles.RFC``, whose ``build_uri`` appends whatever follows ``#`` onto +the RFC's base URL with no validation at all, and returns a plain +``nodes.reference`` carrying that URL as ``refuri`` -- never a +``pending_xref``. Docutils' own ``rfc_reference_role`` behaves identically for +the same reason. So this rendered a live link to a **non-existent** anchor, and +no build warned: not because nitpicky mode is scoped to ``py:`` targets -- this +repository's ``docs/source/conf.py`` does not enable nitpicky at all -- but +because there is no cross-reference node here for nitpicky to ever have +inspected in the first place, on any Sphinx configuration. Pinning the exact string that was wrong only catches *this* typo coming back. :class:`RFCAnchorFragmentTests` instead walks every ``:rfc:`` role with a ``#`` @@ -44,13 +43,13 @@ keeps only the number, so each emits ``#section-{sec}`` explicitly. :data:`ACCEPTED_FRAGMENT`'s set is not a survey of shapes this tree happens to -use -- it is Sphinx's own. ``sphinx.roles._format_rfc_target`` titles -exactly three anchor prefixes, ``{'appendix', 'page', 'section'}``, and +use -- it is Sphinx's own. ``sphinx.roles._format_rfc_target`` titles exactly +three anchor prefixes, ``{'appendix', 'page', 'section'}``, and :file:`tests/project/test_changelog_md.py` already exercises ``#page-N`` as a -correctly-rendered citation (``793#page-5``); confirmed real against RFC -793's own HTML (``id="page-5"`` present). A shapes-in-use survey would have -missed it, since nothing under :mod:`pcapkit` cites a page anchor yet -- -``page-\\d+`` is accepted here anyway, matching Sphinx and that sibling test. +correctly-rendered citation (``793#page-5``); confirmed real against RFC 793's +own HTML (``id="page-5"`` present). A shapes-in-use survey would have missed +it, since nothing under :mod:`pcapkit` cites a page anchor -- ``page-\\d+`` is +accepted here anyway, matching Sphinx and that sibling test. That sibling test also exercises ``#introduction`` and a bare ``#section`` (no number) as citations Sphinx does not error on. Deliberately **not** @@ -149,20 +148,19 @@ #: one. That asymmetry is why the list holds prefix/suffix pairs rather than a single #: delimiter. #: -#: **This list replaced a general masker, and the reason is the point.** Deciding -#: "is this text inside any inline literal" over the whole tree was tried five times -#: and was wrong five times -- a lookbehind heuristic, then a ``re.DOTALL`` span that -#: crossed paragraph breaks and hid a live ``:rfc:`4303#section-2.1``` role, then a -#: greedy close that swallowed the next literal's opener, then a role's own backtick -#: fusing with an adjacent literal. Each fix addressed the mechanism the previous -#: postmortem had found and missed the next. The general problem is RST's -#: inline-markup grammar, which a regex was never going to model. +#: **A list rather than a general masker, and the reason is the point.** Deciding "is +#: this text inside any inline literal" over the whole tree was measured wrong by +#: every approach tried -- a lookbehind heuristic, a ``re.DOTALL`` span that crosses +#: paragraph breaks and hides a live ``:rfc:`4303#section-2.1``` role, a greedy close +#: that swallows the next literal's opener, a role's own backtick fusing with an +#: adjacent literal. The general problem is RST's inline-markup grammar, which a +#: regex was never going to model. #: -#: The bounded question is a different size. Rather than classifying every literal in -#: the tree -- **23,869** spans across **926** files, every one of which had to be got -#: right -- it asks only where a *denylisted fragment* appears, which is a -#: handful of occurrences, and classifies each against this list. Adding a form means one -#: reviewed entry, the same scrutiny :data:`KNOWN_DEAD_ANCHORS` entries already get. +#: The bounded question is a different size. Rather than classifying every literal in the +#: tree -- every one of which would have to be got right -- it asks only where a +#: *denylisted fragment* appears, which is a handful of occurrences, and classifies each +#: against this list. Adding a form means one reviewed entry, the same scrutiny +#: :data:`KNOWN_DEAD_ANCHORS` entries already get. QUOTED_FORMS: 'tuple[tuple[str, str], ...]' = ( ('``', '``'), ) @@ -215,9 +213,8 @@ def _is_documentation(text: 'str', start: 'int', length: 'int') -> 'bool': Local and exact: it looks only at the characters immediately either side of this one citation, so it cannot be thrown off by markup elsewhere in the file. That is - the whole advantage over the masker it replaced, which decided the same question by - pairing every literal delimiter in the file and got it wrong whenever the pairing - slipped. + the whole advantage over a masker, which decides the same question by pairing every + literal delimiter in the file and gets it wrong whenever the pairing slips. Args: text: The file's contents. @@ -237,25 +234,24 @@ def _is_documentation(text: 'str', start: 'int', length: 'int') -> 'bool': # The opener has to *begin* a token, and only whitespace or the start of the # file may precede it. # - # An earlier version also allowed ``([{"'`` there, on the reasoning that an - # opening bracket or quote may sit before a literal. That was **the sixth - # mechanism**, and it was mine: those characters are equally valid as the last - # character of a literal's *content*, in which case the ``` `` ``` is a - # **closer** and the role after it is live. So ``` ``'base'``:rfc:`959#…`` ``` - # read as documentation -- and ``` ``'base'`` ``` is a real literal in - # :file:`pcapkit/const/ftp/command.py`. Measured across the scanned roots, - # **764** literals in **188** files end in one of those five characters, so the - # exemption was one adjacency away from hiding a live dead link. + # ``([{"'`` are not allowed there, though an opening bracket or quote may sit + # before a literal -- that is **the sixth mechanism**: those characters are + # equally valid as the last character of a literal's *content*, in which case + # the ``` `` ``` is a **closer** and the role after it is live. So ``` + # ``'base'``:rfc:`959#…`` ``` would read as documentation -- and + # ``` ``'base'`` ``` is a real literal in :file:`pcapkit/const/ftp/command.py`. + # Measured across the scanned roots, hundreds of literals end in one of those + # characters, so allowing them leaves the exemption one adjacency away from + # hiding a live dead link. # # Whitespace and start-of-file are safe by RST's own grammar: an inline # literal's *end*-string cannot be preceded by whitespace, so ``` `` ``` # following whitespace is always an opener. # - # The comment this replaced claimed the condition was "measured, not reasoned - # about". It had been measured only against ``` ``foo`` ``` -- a literal ending - # in a letter -- and never against the characters in its own allowed set, which - # is exactly where it failed. A measurement that skips the interesting inputs - # is reasoning wearing a measurement's clothes. + # A condition measured only against ``` ``foo`` ``` -- a literal ending in a + # letter -- and never against the characters in its own allowed set fails + # exactly there. A measurement that skips the interesting inputs is reasoning + # wearing a measurement's clothes. if opener == 0 or text[opener - 1].isspace(): return True return False @@ -268,15 +264,14 @@ def _dead_anchor_citations() -> 'list[DeadAnchorFinding]': network -- so it runs the same way in CI, which has none. Searches for each denylisted fragment as a **literal string** rather than matching - every ``:rfc:`` role and comparing. Two consequences worth stating, because the - previous design had neither: + every ``:rfc:`` role and comparing. Two consequences worth stating: * The work is proportional to the number of *denylisted* fragments present, not to the amount of markup in the tree. There is no classifier to get wrong on a file that has nothing to do with these anchors. - * A citation this table has never heard of is not examined at all. That is not a - regression -- the denylist never made a claim about one -- but it means this - function answers a narrower question than its predecessor pretended to. + * A citation this table has never heard of is not examined at all. The denylist + makes no claim about one, so this function answers a narrower question than a + scan of every role would appear to. Scans :data:`DEAD_ANCHOR_SCAN` rather than :mod:`pcapkit` alone: scanning one directory is what let dead-anchor sites sit unnoticed outside it, most of them @@ -317,7 +312,7 @@ def test_no_malformed_fragments(self) -> 'None': Catches the *next* ``secion``, not just #942's: any freshly introduced typo of ``section-``/``appendix-``/``page-``, or any bare-number - fragment like the ten this same sweep found and fixed, shows up as a + fragment like those #942's sweep found and fixed, shows up as a finding and fails this test -- there is no allowlist left for one to hide behind. Shape only, not anchor existence -- see #944. @@ -332,14 +327,14 @@ def test_no_malformed_fragments(self) -> 'None': def test_the_documentation_exemption_holds_on_every_known_mechanism(self) -> 'None': """:func:`_is_documentation` on each input known to defeat a naive masker. - This is the regression suite for several rounds of review, and it exists because - each of those rounds fixed the mechanism the previous postmortem had named and - then missed the next one. Pinning the *table* rather than the latest fix is the - difference: a new mechanism has to be added here to be considered handled, and - a redesign has to keep every row passing. + This is the regression suite for every mechanism review has found, and it exists + because fixing the mechanism a postmortem names has repeatedly missed the next + one. Pinning the *table* rather than the latest fix is the difference: a new + mechanism has to be added here to be considered handled, and a redesign has to + keep every row passing. - The rows are the real inputs, not paraphrases of them. Four are false negatives - the masker produced -- a live role it hid -- which is the dangerous direction, + The rows are the real inputs, not paraphrases of them. Most are false negatives + a masker produces -- a live role it hides -- which is the dangerous direction, since a hidden role is a dead link nobody sees. One is the documentation idiom that must stay exempt, or the defect becomes impossible to write about in the very file that guards against it. @@ -363,7 +358,7 @@ def test_the_documentation_exemption_holds_on_every_known_mechanism(self) -> 'No 'cite %s``next`` after', True), ('a live role flush against a spaced literal', 'cite %s` ``next`` after', True), - # Mechanism 6: the five characters an earlier boundary check allowed before + # Mechanism 6: the characters ``([{"'`` a boundary check might allow before # the opener are equally valid as a literal's last *content* character, in # which case the delimiter is a closer and the role is live. One row each, # because they were allowed as a set and had to be refuted as a set. @@ -439,7 +434,7 @@ def test_no_known_dead_anchor_citations(self) -> 'None': ) #: Matches the ``FEATCode`` class header regardless of its base classes, - #: so a future re-parenting (like #930/#932/#937 did to five siblings) + #: so a future re-parenting (like #930/#932/#937 did to its siblings) #: does not fail this pin for a reason unrelated to the RFC citation -- #: only the docstring wording and the citation itself stay exact. _FEATCODE_CITATION = re.compile( @@ -470,8 +465,8 @@ def test_command_feat_attribute_docstring_cites_section_2_2(self) -> 'None': Pinned by exact site for the same reason as the ``FEATCode`` pin above -- RFC 5797 §2.2 (*Registry Format*) is where the ``FEAT Code`` column - ``feat`` holds is actually defined, so only the spelling was ever - wrong here; the section number is correct and unchanged. + ``feat`` holds is actually defined, so only the spelling was + wrong here; the section number is correct. """ expected = ( diff --git a/tests/project/test_rfc_constants_one_home_1739_unit.py b/tests/project/test_rfc_constants_one_home_1739_unit.py index aba0f05ad1..8bd80bc72b 100644 --- a/tests/project/test_rfc_constants_one_home_1739_unit.py +++ b/tests/project/test_rfc_constants_one_home_1739_unit.py @@ -670,9 +670,10 @@ def test_attribute_readers(self) -> 'None': self.assertTrue(loads, f'{rel} never reads {module}.{name}') def test_pypcapfile_binds_none(self) -> 'None': - # The four names defined there until #1739: reading them through - # ``pcapkit.toolkit.pypcapfile`` would be a re-export, which ruling 1 on - # #1726 rules out, so the module reaches them as module attributes. + # The names #1739 moved out of there: reading them through + # ``pcapkit.toolkit.pypcapfile`` would be a re-export, which + # ruling 1 on #1726 rules out, so the module reaches them as + # module attributes. pypcapfile = importlib.import_module('pcapkit.toolkit.pypcapfile') for name in ('TCP_MIN_HEADER_LEN', 'IPV4_HEADER_LEN', 'IPV6_HEADER_LEN', 'AH_HEADER_LEN'): with self.subTest(name=name): diff --git a/tests/project/test_script_dependencies_1894.py b/tests/project/test_script_dependencies_1894.py index 5e9352ed7c..26bf50bc4e 100644 --- a/tests/project/test_script_dependencies_1894.py +++ b/tests/project/test_script_dependencies_1894.py @@ -8,8 +8,8 @@ :file:`Pipfile`'s ``[dev-packages]``, which ``make``'s ``pipenv run`` targets resolve against, carries them too, with the ``test`` extra's markers. Those keep each off the interpreters PyPI has no wheel of it for: ``psutil`` and ``lxml`` -are CPython only, ``lxml`` since #1958, as PyPI has none of it for GraalPy or -PyPy 8.0. The ``dev`` extra is not asked: it holds what the toolchain linting +are CPython only (``lxml`` by #1958), as PyPI has none of it for GraalPy or PyPy +8.0. The ``dev`` extra is not asked: it holds what the toolchain linting :mod:`pcapkit` must resolve, not the scripts' dependencies. """ diff --git a/tests/project/test_sentinels_doc_page_934_unit.py b/tests/project/test_sentinels_doc_page_934_unit.py index cad8be98ba..0732b2f331 100644 --- a/tests/project/test_sentinels_doc_page_934_unit.py +++ b/tests/project/test_sentinels_doc_page_934_unit.py @@ -12,13 +12,13 @@ This does **not** re-test Sphinx's cross-reference resolution itself -- as :file:`tests/project/test_documentation_claims.py` explains at length, whether a reference resolves is a property of the built inventory rather than of the source, and -the honest check is the rendered HTML from an actual ``sphinx-build`` run, recorded in -the pull request rather than reimplemented here. What this pins instead is the two -purely textual preconditions a later edit could silently break even though a plain -(non-nitpicky) build would keep passing either way: the page has to keep existing and -keep declaring the module it documents, and the ``corekit`` index has to keep listing it -in its toctree so it is not an orphan page -- Sphinx does not fail a plain build over an -orphan page either, so nothing else would catch that regression. +the honest check is the rendered HTML from an actual ``sphinx-build`` run, not +reimplemented here. What this pins instead is the two purely textual preconditions a +later edit could silently break even though a plain (non-nitpicky) build would keep +passing either way: the page has to keep existing and keep declaring the module it +documents, and the ``corekit`` index has to keep listing it in its toctree so it is not +an orphan page -- Sphinx does not fail a plain build over an orphan page either, so +nothing else would catch that regression. """ diff --git a/tests/project/test_stdlib_feature_probes_1709_unit.py b/tests/project/test_stdlib_feature_probes_1709_unit.py index c7b2db9df9..d0f32a8684 100644 --- a/tests/project/test_stdlib_feature_probes_1709_unit.py +++ b/tests/project/test_stdlib_feature_probes_1709_unit.py @@ -23,8 +23,8 @@ probe of a module the file imports by name or of a builtin class, and a literal probe of any object for an attribute in :data:`VERSIONED`. A computed attribute name, as in ``getattr(hashlib, digest)``, is a lookup rather than a question -about the interpreter, and is not matched. Run against the tree before issue -#1709, it reports exactly those and the :data:`EXEMPT` entries. +about the interpreter, and is not matched. Run against the tree without the +fixes of issue #1709, it reports exactly those and the :data:`EXEMPT` entries. This module is unit-tier: it reads source text and never imports the package. diff --git a/tests/project/test_superseded_runs_1938.py b/tests/project/test_superseded_runs_1938.py index 0e0ca9f73b..e0a74eb0a7 100644 --- a/tests/project/test_superseded_runs_1938.py +++ b/tests/project/test_superseded_runs_1938.py @@ -639,8 +639,8 @@ class TestTheWorkflowCommands(unittest.TestCase): """#2047: the whole of what ``check`` and ``watch`` print, append to the summary and output, byte for byte. The tests above read the commands one by one; these pin each run's whole - standard output and files, with text each escape changes, so moving the - writes onto util/_actions.py can be shown to change nothing. + standard output and files, with text each escape changes, so any change + to what is written fails here. """ diff --git a/tests/project/test_sweeps_on_latest_only_1732.py b/tests/project/test_sweeps_on_latest_only_1732.py index c8fbea21ab..dac2779e5b 100644 --- a/tests/project/test_sweeps_on_latest_only_1732.py +++ b/tests/project/test_sweeps_on_latest_only_1732.py @@ -433,7 +433,7 @@ def triggers(spec: 'dict[str, Any]') -> 'dict[str, Any]': class TestTheExperimentalLegsAreAWorkflowOfTheirOwn(unittest.TestCase): - """#1732, option 2: nothing waits on the experimental legs, so they leave unit-tests.yml.""" + """#1732, option 2: nothing waits on the experimental legs, so they are not in unit-tests.yml.""" def setUp(self) -> None: self.unit = workflow('unit-tests.yml') @@ -457,7 +457,7 @@ def test_the_workflow_holds_every_experimental_leg_and_unit_tests_none(self) -> def test_the_same_events_and_no_concurrency_group(self) -> None: """unit-tests.yml's push and pull_request, no ``workflow_call``, read-only, sharing no group. - #1938 drops its group, so a superseded run is skipped or stopped, not + No group (#1938), so a superseded run is skipped or stopped, not cancelled: :mod:`tests.project.test_superseded_runs_1938`. """ @@ -520,7 +520,7 @@ def test_the_caps(self) -> None: def test_a_failure_never_turns_the_run_red(self) -> None: """Job-level ``continue-on-error`` there and on the KTLO jobs alone: a ``test`` leg's failure must still fail its gate. - unit-tests.yml's KTLO jobs carry it too since #1738, which nothing gating + unit-tests.yml's KTLO jobs carry it too (#1738), which nothing gating ``needs:`` (``tests/project/test_ktlo_jobs_gate_nothing_1738.py``). """ diff --git a/tests/project/test_tshark_apparmor_2019.py b/tests/project/test_tshark_apparmor_2019.py index f472ee6193..d3f663ef35 100644 --- a/tests/project/test_tshark_apparmor_2019.py +++ b/tests/project/test_tshark_apparmor_2019.py @@ -69,7 +69,7 @@ def test_the_step_directly_follows_each_install(self) -> None: def test_every_job_installing_tshark_is_covered(self) -> None: """A new tshark install elsewhere in the workflows needs the step too. - Since #1967 an install runs :file:`util/apt_packages.py install`, which installs + An install runs :file:`util/apt_packages.py install` (#1967), which installs the job's ``APT_PACKAGES``, so a job counts when that names tshark. """ diff --git a/tests/project/test_unit_tests_push_reports_2030.py b/tests/project/test_unit_tests_push_reports_2030.py index 203702649b..e47a0b4cc1 100644 --- a/tests/project/test_unit_tests_push_reports_2030.py +++ b/tests/project/test_unit_tests_push_reports_2030.py @@ -556,7 +556,7 @@ def test_a_failed_push_run_lists_every_warning(self) -> None: self.assertIn('- `Python 3.13`: `DeprecationWarning: something old`', report) def test_a_scheduled_run_counts_every_warning(self) -> None: - """A run that posts every time keeps the rule as it was: any annotation on a passing job warns.""" + """A run that posts every time keeps the plain rule: any annotation on a passing job warns.""" outputs, report, _ = self.report('success', check_runs(('Compat Python 3.13', 'SUCCESS', BLOCKING[2])), workflow='Python Compatibility', event='schedule') self.assertEqual(outputs, {'post': 'true', 'title': 'python-compatibility: main scheduled runs', diff --git a/tests/project/test_unit_tests_queue.py b/tests/project/test_unit_tests_queue.py index 294883783f..c9909f2845 100644 --- a/tests/project/test_unit_tests_queue.py +++ b/tests/project/test_unit_tests_queue.py @@ -16,7 +16,7 @@ ``continue-on-error`` rather than a job-level one (#1633), and the job has no ``DPKT`` cell (#1538). * The classifier, :file:`util/classify_changes.py` (#1967), the engine cell loops - and the apt retry, :file:`util/apt_packages.py`'s since #1967, are executed + and the apt retry, :file:`util/apt_packages.py`'s (#1967), are executed too: the classifier against a scratch merge commit, the others with their commands stubbed on ``PATH``. * Every test outside :file:`tests/project` that reads ``docs/`` or Markdown is @@ -275,7 +275,7 @@ def test_unittest_ordering_queues_behind_test_and_survives_its_failure(self) -> " && github.event_name != 'pull_request' }}\n", head) def test_the_ktlo_cell_runs_on_every_event(self) -> None: - """#1633: 3.10 still runs wherever it did; #1538's main-only cut was for prerelease 3.15.""" + """#1633: the KTLO cell runs on every event; #1538's main-only cut was for a prerelease.""" ktlo = step(job('engine-tests'), 'Run the KTLO cells (non-blocking)') self.assertNotRegex(ktlo, r'(?m)^ if:') diff --git a/tests/project/test_unittest_ordering_shards.py b/tests/project/test_unittest_ordering_shards.py index 7b9d3820b4..63edc708bf 100644 --- a/tests/project/test_unittest_ordering_shards.py +++ b/tests/project/test_unittest_ordering_shards.py @@ -13,7 +13,7 @@ ``protocols`` leg, so a new ``tests/protocols//`` is either inside a shard or fails here; * every other matrix cell is a bare direct child of ``tests/`` with no - ``--exclude``, i.e. runs exactly what it ran before the split; + ``--exclude``, i.e. runs that whole directory; * every ``tests/`` subdirectory holding a test module is a leg, except the :data:`NOT_LEGS` names, each left out for the reason the workflow comment gives (#2115: ``corekit`` was missing, and nothing said so). diff --git a/tests/project/test_vendor_comment_only_no_bump_1674.py b/tests/project/test_vendor_comment_only_no_bump_1674.py index 1ab3bcf1d3..7cc3267d2b 100644 --- a/tests/project/test_vendor_comment_only_no_bump_1674.py +++ b/tests/project/test_vendor_comment_only_no_bump_1674.py @@ -7,7 +7,7 @@ without the bump, so that documentation still reaches ``main`` and only a code change cuts a beta. -Three layers: +The layers: * :class:`TestTheScriptOnARegeneratedTree` runs :file:`util/vendor_code_changed.py` in throwaway repositories, on fixtures diff --git a/tests/project/test_whole_suite_on_latest_1958.py b/tests/project/test_whole_suite_on_latest_1958.py index 332b887c6b..fe89416219 100644 --- a/tests/project/test_whole_suite_on_latest_1958.py +++ b/tests/project/test_whole_suite_on_latest_1958.py @@ -32,7 +32,7 @@ LATEST_STEP = "Install the whole suite's dependencies on latest" LATEST_IF = ("${{ steps.install.outcome == 'success' && " 'matrix.python-version == needs.python-versions.outputs.latest }}') -#: The install line every leg of each matrix job runs, unchanged by #1958. +#: The install line every leg of each matrix job runs. BASE = { 'test': frozenset({'test', 'DPKT', 'crypto', 'NGAP'}), 'integration': frozenset({'test', 'Scapy', 'DPKT', 'cli'}), diff --git a/tests/project/test_windows_test_leg_2060.py b/tests/project/test_windows_test_leg_2060.py index 25c9357e67..d399b33ad1 100644 --- a/tests/project/test_windows_test_leg_2060.py +++ b/tests/project/test_windows_test_leg_2060.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- """#2060: a non-blocking Windows unit-test leg, ahead of the win-64 conda package's release. -The plan for #2060 asks that a Windows unit-test leg run before phase 2 publishes, -so the first win-64 package has had a test run. :file:`.github/workflows/experimental.yml` -gained ``windows``, a job of its own beside ``experimental``, whose Linux -containment (cgroup scope, ``prlimit``, resource monitor, superseded watcher) -cannot run on Windows. +The plan for #2060 asks that a Windows unit-test leg run before a win-64 package +publishes. :file:`.github/workflows/experimental.yml` has ``windows``, a job of its own +beside ``experimental``, whose Linux containment (cgroup scope, ``prlimit``, resource +monitor, superseded watcher) cannot run on Windows. * :class:`TestTheLeg`: one leg on ``windows-latest``, on ``latest``, the newest CPython the conda builds target, named ``Python on Windows (experimental)``, diff --git a/tests/project/test_workflow_apt_cache.py b/tests/project/test_workflow_apt_cache.py index 40c72d8565..88db30ea2b 100644 --- a/tests/project/test_workflow_apt_cache.py +++ b/tests/project/test_workflow_apt_cache.py @@ -2,14 +2,14 @@ """Tests for the apt ``.deb`` cache and fallback mirror in :file:`unit-tests.yml` (#1432). #1432: ``azure.archive.ubuntu.com`` served ~75 kB/s, so ``timeout 180`` killed -both tshark install attempts, three times in one day. The fix wraps the -unchanged install step (pinned by -:class:`tests.project.test_unit_tests_queue.TestAptRetry`) with a plan step that -computes a cache key, ``actions/cache`` restore/seed/collect/save steps, and a -retry that drops azure from the runner's ``mirror+file:`` list. +both tshark install attempts, three times in one day. The fix wraps the install +step (pinned by :class:`tests.project.test_unit_tests_queue.TestAptRetry`) with +a plan step that computes a cache key, ``actions/cache`` +restore/seed/collect/save steps, and a retry that drops azure from the runner's +``mirror+file:`` list. Each property below is exercised by running the step's real ``run:`` script, which -runs :file:`util/apt_packages.py` since #1967, under ``bash -e`` with +runs :file:`util/apt_packages.py` (#1967), under ``bash -e`` with ``sudo``/``timeout``/``apt-get`` stubbed, the way ``TestAptRetry`` does, or by reading the step text where only the Actions runner could execute it: @@ -337,7 +337,7 @@ class TestOneSaverPerKey(unittest.TestCase): def test_only_one_parity_cell_saves(self) -> None: """APT_CACHE_SAVE selects exactly one of the parity matrices' Python versions. - Since #1738 the KTLO legs are ``pypcap-parity-ktlo``'s, under the same + The KTLO legs are ``pypcap-parity-ktlo``'s (#1738), under the same ``APT_SET`` and so the same key, and that job never saves. """ diff --git a/tests/project/test_workflow_apt_timeouts.py b/tests/project/test_workflow_apt_timeouts.py index eba15ad815..caf8ee8754 100644 --- a/tests/project/test_workflow_apt_timeouts.py +++ b/tests/project/test_workflow_apt_timeouts.py @@ -24,9 +24,9 @@ The second half of the rule matters as much as the first. A step timeout at or above the job's budget is decorative -- the job's own timer fires first and the -run is cancelled exactly as before -- so a bound that does not actually bind is -treated as a failure here rather than passing on the strength of the key being -present. +run is cancelled exactly as without it -- so a bound that does not actually +bind is treated as a failure here rather than passing on the strength of the +key being present. Two things this deliberately does **not** test: @@ -47,8 +47,9 @@ assertion that must run everywhere does not need the dependency, and the one that needs it guards the substitute. -Since #1967 the steps run apt through :file:`util/apt_packages.py`, so a step running -its ``plan`` or ``install`` counts as invoking apt, as one naming ``apt-get`` does. +The steps run apt through :file:`util/apt_packages.py` (#1967), so a step running +its ``plan`` or ``install`` counts as invoking apt, as one naming ``apt-get`` +does. Comments are stripped before the ``apt-get`` marker is looked for. Without that, prose *about* apt -- including the long note #974 added above the very step this @@ -89,9 +90,9 @@ #: it cannot invent a second one. Adding a different installer to a workflow #: means adding its marker here. APT_MARKER = 'apt-get' -#: The commands that invoke apt for a step since #1967: :file:`util/apt_packages.py`'s +#: The commands that invoke apt for a step (#1967): :file:`util/apt_packages.py`'s #: ``plan`` and ``install``, which run ``apt-get``. Its ``seed`` and ``collect`` run -#: none, and so need no bound of their own, as before. +#: none, and so need no bound of their own. APT_SCRIPT = re.compile(r'\butil/apt_packages\.py (?:plan|install)\b') #: The steps known to invoke apt, as ``(workflow name, step name)``. Quoted diff --git a/tests/project/test_workflow_run_verdict.py b/tests/project/test_workflow_run_verdict.py index 6a300dbcdd..93dc4a12f2 100644 --- a/tests/project/test_workflow_run_verdict.py +++ b/tests/project/test_workflow_run_verdict.py @@ -16,9 +16,9 @@ and paths are git's answers rather than a model's; * :class:`TestEachWorkflowGatesOnTheVerdict` evaluates each workflow's job conditions and concurrency group over the scenarios, with the evaluator - :mod:`tests.project.test_release_gates` pins. Since #1967 the test gate and - the ``verdict`` job are one copy, in :data:`HEAD`, which each workflow calls - as its ``head`` job. + :mod:`tests.project.test_release_gates` pins. The test gate and the + ``verdict`` job are one copy, in :data:`HEAD` (#1967), which each workflow + calls as its ``head`` job. """ @@ -342,7 +342,7 @@ def _commands(self, verdict: 'str', head: 'str', *argv: 'str', return result.returncode, result.stdout.decode('utf-8'), path.read_bytes() if path.exists() else None def test_the_workflow_commands_byte_for_byte(self) -> None: - """#2047: each answer's whole output, so moving it onto util/_actions.py can be shown to change nothing.""" + """#2047: each answer's whole output, so any change to what is written fails here.""" verdict, head = self.sha['9083163f5'], self.sha['2e941bbb7'] refused = (f"::notice title=Skipped as the verdict is for another commit::{self.sha['bbcabf1dd']} after the " f'verified {verdict} was authored by {HUMAN[1]} and committed by {GITHUB[1]}, not by the bot. ' @@ -365,9 +365,9 @@ def test_the_workflow_commands_byte_for_byte(self) -> None: def test_the_workflow_commands_escape_what_they_interpolate(self) -> None: """#2047: ``%`` and LF in a path, and ``%`` in a sha or a ref, are escaped: one command, one line. - Before util/_actions.py these went out as written: a bot commit to a path - holding a line break split its notice in two, the second line read as a - log line, or as a workflow command of its own where it began ``::``. + Written out unescaped, a bot commit to a path holding a line break would + split its notice in two, the second line read as a log line, or as a + workflow command of its own where it began ``::``. """ base = self.sha['f4f94bcad'] diff --git a/tests/test_base_class_contract.py b/tests/test_base_class_contract.py index 934fe03c37..7f1729ccbf 100644 --- a/tests/test_base_class_contract.py +++ b/tests/test_base_class_contract.py @@ -32,23 +32,20 @@ built-ins for three years). These tests replace the convention with something that fails. -The sweep and the migration are both complete: every site is renamed, the -last batch of them (all ``ProtocolBase``, across ``pcapkit/protocols/`` and some -``pcapkit/foundation/`` modules) having landed in #514 part (c) once #726 and -#742 merged. :data:`PENDING_ALIAS_PATHS` is therefore empty -- its intended end -state -- and :func:`is_pending` is trivially ``False`` for every path; the dict -stays as a mechanism rather than being deleted, in case a future ``*Base`` -family needs the same transitional carve-out. - -:class:`RegistrationGateTests` is a regression pin, not a new behaviour. Every -assertion in it already held before part (c), because parts (a) and (b) -(pull requests #547 and #570) made registration opt-in on a keyword. It is written down -because the ruling promotes it from an accident of where the hook happens to live -into the specification, and an unasserted specification is one refactor away from -being untrue. The property worth noticing is the last one: a library-style -subclass cannot opt in *even if it tries*, because the ``*Base`` hook does not -accept the keyword at all. That is the ``final``-substitute the split was built -to provide, and it is stronger than a convention. +Every site is renamed (#514 part (c), #726, #742). :data:`PENDING_ALIAS_PATHS` +is therefore empty -- its intended end state -- and :func:`is_pending` is +trivially ``False`` for every path; the dict stays as a mechanism rather than +being deleted, in case a future ``*Base`` family needs the same transitional +carve-out. + +:class:`RegistrationGateTests` is a regression pin: registration is opt-in on a keyword +(#514 parts (a) and (b), pull requests #547 and #570). It is written down because the +ruling promotes it from an accident of where the hook happens to live into the +specification, and an unasserted specification is one refactor away from being untrue. +The property worth noticing is the last one: a library-style subclass cannot opt in +*even if it tries*, because the ``*Base`` hook does not accept the keyword at all. That +is the ``final``-substitute the split was built to provide, and it is stronger than a +convention. .. note:: @@ -140,13 +137,12 @@ #: owns the whole ``pcapkit/protocols/`` subtree and enumerating its sites #: would turn this into a changelog. #: -#: Each entry is a promise, not an exemption: when the named pull request lands, -#: the alias comes out and the entry goes with it. **An empty dict is the end -#: state**, and :meth:`BaseClassAliasTests.test_no_library_module_imports_a_base_under_its_public_name` -#: fails on an entry that has stopped matching anything, so a stale promise -#: cannot sit here unnoticed once its pull request merges. #514 part (c) landed -#: the last sites, so this is empty and every alias check below runs -#: unconditionally. +#: Each entry is a promise, not an exemption: when the named pull request lands, the alias +#: comes out and the entry goes with it. **An empty dict is the end state**, and +#: :meth:`BaseClassAliasTests.test_no_library_module_imports_a_base_under_its_public_name` +#: fails on an entry that has stopped matching anything, so a stale promise cannot sit +#: here unnoticed once its pull request merges. It is empty, so every alias check below +#: runs unconditionally. PENDING_ALIAS_PATHS = {} # type: dict[str, int] diff --git a/tests/test_docstring_contract.py b/tests/test_docstring_contract.py index b46a731184..a5c1b0c50f 100644 --- a/tests/test_docstring_contract.py +++ b/tests/test_docstring_contract.py @@ -42,9 +42,8 @@ :meth:`DocstringCitationTests.test_quoted_rfc_sentences_cite_their_own_section` A docstring that quotes a sentence from :rfc:`8200` verbatim and attributes it to a section that does not contain it. Issues #517 and #530 were both - exactly this, in sibling files, and neither was catchable by anything here - before -- see :data:`RFC8200_QUOTED_SENTENCES` for what this can and cannot - prove. + exactly this, in sibling files -- see :data:`RFC8200_QUOTED_SENTENCES` for + what this can and cannot prove. Why a documented name may legitimately be absent from the signature, which is the trap this module has to avoid: this project documents keyword arguments @@ -126,10 +125,9 @@ PACKAGE = ROOT / 'pcapkit' #: Napoleon headers that open a parameter block. ``Args`` is the house -#: spelling, but ``Arguments`` is used 200-odd times and ``Parameters`` and -#: ``Keyword Args`` a handful more, and a checker that matched only ``Args`` -#: would silently skip every one of them -- which is how a census of this -#: class undercounts by a tenth. +#: spelling, but ``Arguments``, ``Parameters`` and ``Keyword Args`` are used +#: too, and a checker that matched only ``Args`` would silently skip every +#: one of them -- which is how a census of this class undercounts. PARAM_SECTIONS = frozenset({ 'Args', 'Arguments', 'Parameters', 'Keyword Args', 'Keyword Arguments', }) @@ -170,9 +168,9 @@ #: #: Values are *sets* of acceptable sections rather than one section, because a #: sentence can honestly belong to more than one: the TLV deferral below is -#: worded identically in Section 4.3 and Section 4.6 (rfc8200.txt lines 722 and -#: 1268), so a docstring citing either is right and a checker demanding one -#: would fail a correct citation in the sibling header. +#: worded identically in Section 4.3 and Section 4.6, so a docstring citing +#: either is right and a checker demanding one would fail a correct citation +#: in the sibling header. RFC8200_QUOTED_SENTENCES = { 'the length of the option data field of this option, in octets': frozenset({'4.2'}), @@ -236,11 +234,11 @@ def _documented_names(doc: 'str') -> 'tuple[list[tuple[str, bool]], list[str]]': A checker keyed on the absolute column matches one and silently skips the other. - It is *not* for the reason an earlier draft of this docstring gave, which - said ``__doc__`` is dedented at compile time so the source and runtime - columns differ. That is true of ``__doc__`` and irrelevant here, because - :func:`functions` reads ``ast.get_docstring(node, clean=False)``, which - preserves the raw source indentation. Measured on 3.14.7 for a method whose + It is *not* because ``__doc__`` is dedented at compile time so the source + and runtime columns differ. That is true of ``__doc__`` and irrelevant + here, because :func:`functions` reads + ``ast.get_docstring(node, clean=False)``, which preserves the raw source + indentation. Measured on 3.14.7 for a method whose ``Args:`` sits at eight spaces:: get_docstring(clean=False) -> indents [0, 8, 12, 12] @@ -249,8 +247,7 @@ def _documented_names(doc: 'str') -> 'tuple[list[tuple[str, bool]], list[str]]': So this module never sees the dedented form at all. The relative measurement is right, but for the nesting reason above rather than that - one -- recorded because a wrong rationale in a checker for wrong - rationales is worth correcting explicitly. + one. """ lines = doc.splitlines() @@ -341,11 +338,11 @@ def _raised_names(node: 'ast.AST') -> 'set[str]': * Only :class:`ast.Name` and :class:`ast.Attribute` raise targets are resolved, so a computed ``raise REGISTRY[code]`` would register neither as a matching raise nor, in :func:`_calls_anything_callable`, as a call. - Inert: across ``pcapkit/`` the 1,060 raise targets break down as - 1,058 ``Name`` and 2 ``Attribute``, and **no** ``Subscript``. + Inert: across ``pcapkit/`` every raise target is a ``Name`` or an + ``Attribute``, and **none** a ``Subscript``. * :func:`ast.walk` descends into nested ``def``\\ s, so a ``raise`` inside a - local helper is attributed to the function enclosing it. 19 documented - functions contain a nested ``def`` and 6 of those raise inside it. + local helper is attributed to the function enclosing it, and some + documented functions raise inside a nested ``def``. That second one reads like a bug and is the right answer for a nested ``def`` that is a local helper the enclosing body actually calls, so the diff --git a/tests/test_final_enforcement.py b/tests/test_final_enforcement.py index 91e6d4fe13..42bd8646c5 100644 --- a/tests/test_final_enforcement.py +++ b/tests/test_final_enforcement.py @@ -9,7 +9,7 @@ it. Issue #778 is the ruling that it should be read, and this module is what pins the reading. -**Four answers, one per shape, and the differences are the ruling.** In the +**One answer per shape, and the differences are the ruling.** In the maintainer's terms: ``@info_final`` implies ``@final``, and ``@final`` must not be used without ``@info_final``. @@ -29,10 +29,11 @@ :func:`info_final` with ``__final__`` already in the class's own ``__dict__``, so only ``__finalised__`` -- which records *that function* having run -- can tell "already finalised" from "merely marked". Keying the re-entry check on -``__final__`` instead made row two warn and skip the generation, and that is a -defect, not a style choice: the class came back with no generated ``__init__`` -and construction failed with ``TypeError: 'int' object is not iterable`` on the -:class:`Info` side, and *silently returned bookkeeping attributes* on the +``__final__`` instead would make row two warn and skip the generation, and that +is a defect, not a style choice: the class comes back with no generated +``__init__`` and construction fails with +``TypeError: 'int' object is not iterable`` on the :class:`Info` side, and +*silently returns bookkeeping attributes* on the :class:`~pcapkit.protocols.schema.schema.Schema` side. What the rule costs is nothing up front and everything later, which is why it needs @@ -42,7 +43,7 @@ and none is subclassed. So the raise cannot fire on any declaration in the tree as it stands, and the only way to know it fires at all is to make it. -Three properties are easy to get subtly wrong, and each has a test here for the +These properties are easy to get subtly wrong, and each has a test here for the *wrong* answer as much as the right one: :meth:`OwnDictRuleTests` @@ -251,10 +252,10 @@ def test_a_refused_enum_schema_declaration_leaves_the_registry_alone(self) -> No :meth:`EnumSchema.__init_subclass__` writes ``cls`` into ``__enum__`` for every ``code`` the declaration names, and the guard it inherits is - what refuses the declaration. Were the base hook still called at the end - of that method rather than at its start, the raise would discard the - class object while leaving the registry pointing at it -- so a - declaration that failed would still have overwritten a built-in schema. + what refuses the declaration. Were the base hook called at the end of + that method rather than at its start, the raise would discard the class + object while leaving the registry pointing at it -- so a declaration + that failed would still have overwritten a built-in schema. """ from pcapkit.protocols.schema.schema import EnumSchema, schema_final @@ -278,19 +279,20 @@ class Derived(Sealed, code=Code.two): # pylint: disable=unused-variable self.assertNotIn(Code.two, before) def test_a_refused_pcapng_option_declaration_leaves_the_registry_alone(self) -> None: - """The same guarantee, on the one in-tree hook that did not have it. + """The same guarantee, on the in-tree hook that overrides it. :meth:`~pcapkit.protocols.schema.misc.pcapng.Option.__init_subclass__` overrides :class:`EnumSchema`'s hook and calls :meth:`~pcapkit.protocols.schema.misc.pcapng.Option.register` -- which - writes ``cls`` into :attr:`~pcapkit.protocols.schema.misc.pcapng.Option.\ - __enum__` -- before calling ``super().__init_subclass__()``, which is - where the guard that refuses to subclass a finalised schema actually - lives. So a refused declaration still displaced a built-in option schema - first, exactly the failure the test above pins for the generic - :class:`EnumSchema` hook, and this pins the same guarantee for the one - subclass override that had grown out of step with it. A dedicated - namespace keeps this from touching the built-in ``opt`` registry at all. + writes ``cls`` into + :attr:`~pcapkit.protocols.schema.misc.pcapng.Option.__enum__` -- before + calling ``super().__init_subclass__()``, which is where the guard that + refuses to subclass a finalised schema actually lives. So without the + fix a refused declaration displaces a built-in option schema first, + exactly the failure the test above pins for the generic + :class:`EnumSchema` hook, and this pins the same guarantee for the + subclass override. A dedicated namespace keeps this from touching the + built-in ``opt`` registry at all. """ from pcapkit.const.pcapng.option_type import \ diff --git a/tests/test_tier_guard.py b/tests/test_tier_guard.py index c783388152..63de1d8024 100644 --- a/tests/test_tier_guard.py +++ b/tests/test_tier_guard.py @@ -802,12 +802,11 @@ def test_skip_idiom_modules_matches_a_grep_based_scan(self) -> None: Two files are excluded on purpose, not overlooked: :file:`tests/_tiers.py` itself and :file:`tests/test_tier_guard.py`, this module. Both mention - ``sample_path(`` and ``except FileNotFoundError`` freely -- in - docstrings, in error messages, and in source strings ``write_module()`` - writes out for other tests to audit -- without containing a single real - call to either. A first version of this check included them and failed - immediately for exactly that reason, which is the point: a check that - cannot fail is not a check. + ``sample_path(`` and ``except FileNotFoundError`` freely -- in docstrings, + in error messages, and in source strings ``write_module()`` writes out for + other tests to audit -- without containing a single real call to either. + Including them fails at once for exactly that reason (measured), which is + the point: a check that cannot fail is not a check. Restricted to ``test_*.py`` for the same reason :mod:`pytest` itself is (see ``python_files`` in :file:`pyproject.toml`): a module outside @@ -966,8 +965,8 @@ def test_every_tracked_name_exists_and_matches_git(self) -> None: :func:`~tests._tiers.committed_captures` -- a second call to that same function would only prove it agrees with itself, not that it agrees with git. A hardcoded, stale, or otherwise wrong literal set would fail - the comparison below; it could only have passed the old body, which - asserted nothing but non-emptiness and the absence of a ``/``. + the comparison below; it would pass a body asserting nothing but + non-emptiness and the absence of a ``/``. """ tracked = _tiers.committed_captures() @@ -1502,7 +1501,7 @@ def test_the_extras_read_back_what_pyproject_declares(self) -> None: """Spot checks on the extras the rest of this suite reasons about.""" declared = _dependency_gates.declared_requirements() - # Since #2040 a second line caps PyPy below 3.11. + # A second line caps PyPy below 3.11 (#2040). self.assertEqual( [requirement.text for requirement in declared['crypto']], ["cryptography>=3.4; platform_python_implementation != 'PyPy' or python_version >= '3.11'", @@ -2137,10 +2136,10 @@ def test_the_crawler_dependencies_are_satisfied_by_the_test_extra(self) -> None: #745's own text lists ``HAS_CRAWLER_DEPS`` alongside ``HAS_VENDOR_DEPS`` as ruled onto a non-blocking leg. It is not: it asks only for - ``requests`` and ``bs4``, both of which the ``test`` extra has carried - since #507, so every job installs it. Only ``HAS_VENDOR_DEPS`` -- which - additionally wants :mod:`html5lib`, and so - ``beautifulsoup4[html5lib]`` -- is genuinely dark. + ``requests`` and ``bs4``, both of which the ``test`` extra carries + (#507), so every job installs it. Only ``HAS_VENDOR_DEPS`` -- which + additionally wants :mod:`html5lib`, and so ``beautifulsoup4[html5lib]`` + -- is genuinely dark. """ gaps = {gap.flag for gap in _dependency_gates.dependency_gate_gaps()} @@ -2153,11 +2152,11 @@ def test_the_crawler_dependencies_are_satisfied_by_the_test_extra(self) -> None: def test_the_vendor_extra_closes_every_job_reaching_the_vendor_gates(self) -> None: """#1958: the ``latest`` legs install ``vendor``, so html5lib is no gap anywhere. - #738 ruled these gates onto a non-blocking leg, and ``engine-tests`` was - that leg until #849 narrowed it to explicit engine paths, after which - they ran nowhere. The owner's ruling E on #1958 runs the whole suite on - latest CPython, so ``test``'s ``latest`` leg and ``gate`` install - ``vendor``, and the exclusion that recorded the gap is gone. + #738 ruled these gates onto a non-blocking leg, and ``engine-tests``, + narrowed to explicit engine paths (#849), runs them nowhere. The owner's + ruling E on #1958 runs the whole suite on latest CPython, so ``test``'s + ``latest`` leg and ``gate`` install ``vendor``, and the exclusion that + recorded the gap is gone. """ reaching = {job.name for job in _dependency_gates.pytest_jobs() diff --git a/tests/toolkit/test_pyshark_unit.py b/tests/toolkit/test_pyshark_unit.py index d9e4a31311..524d42f248 100644 --- a/tests/toolkit/test_pyshark_unit.py +++ b/tests/toolkit/test_pyshark_unit.py @@ -79,19 +79,17 @@ def __init__(self, *, ipv6: bool = False, tcp: bool = True, frame_fields['encap_type'] = encap_type self.frame_info = FakeLayer('frame', **frame_fields) self.layers = [ - # Real PyShark names a layer from the PDML ```` - # attribute, which is Wireshark's protocol *filter* name -- - # ``eth``, not ``ethernet`` -- so the fake has to match that or it - # is not exercising what the real engine hands ``tcp_traceflow``. - # That name is not a LinkType *member* name either, so - # ``pcapkit.toolkit.pyshark.FILTER_NAME_TO_LINKTYPE`` translates - # the ones measured to be unambiguous -- see that table's own - # comment for the evidence and for what was deliberately left - # out. Since #843 that table is the *fallback*, reached only when - # the frame layer carries no ``encap_type``, and there is no - # ``LinkType.get()`` attempt after it. ``link_layer`` lets a test pick any - # root-layer name, including one outside that table, without - # duplicating this whole fake. + # Real PyShark names a layer from the PDML ```` attribute, + # which is Wireshark's protocol *filter* name -- ``eth``, not ``ethernet`` + # -- so the fake has to match that or it is not exercising what the real + # engine hands ``tcp_traceflow``. That name is not a LinkType *member* + # name either, so ``pcapkit.toolkit.pyshark.FILTER_NAME_TO_LINKTYPE`` + # translates the ones measured to be unambiguous -- see that table's own + # comment for the evidence and for what was deliberately left out. That + # table is the *fallback* (#843), reached only when the frame layer + # carries no ``encap_type``, and there is no ``LinkType.get()`` attempt + # after it. ``link_layer`` lets a test pick any root-layer name, including + # one outside that table, without duplicating this whole fake. FakeLayer(link_layer, src='aa:aa:aa:aa:aa:aa'), FakeLayer('ip', src='192.0.2.1', dst='198.51.100.1'), # ``flags_reset`` is PyShark's spelling of Wireshark's @@ -220,8 +218,8 @@ def test_tcp_traceflow_resolves_token_ring_via_filter_name_mapping(self) -> None def test_tcp_traceflow_raises_for_an_ip_rooted_packet(self) -> None: """``'ip'`` is absent from ``FILTER_NAME_TO_LINKTYPE``, so the - fallback path raises. Since #843 the name is reported as PyShark - gave it rather than upper-cased, there being no upper-case + fallback path raises. The name is reported as PyShark gave it + rather than upper-cased (#843), there being no upper-case ``Enum_LinkType.get()`` attempt left to upper-case it for. The double feeds ``'ip'`` as ``layers[0]`` to exercise that path. A real @@ -273,19 +271,19 @@ def test_tcp_traceflow_disambiguates_the_shared_filter_names(self) -> None: wrong* DLT with no error. ``frame.encap_type`` separates them, and ``ENCAP_TYPE_TO_LINKTYPE`` turns it into the right member. - Each ``(encap_type, filter name, expected)`` triple below was - measured on this host with ``editcap``/``tshark`` 4.6.9 -- the - encapsulation rewritten with ``editcap -F pcap -T ``, the DLT - read out of the resulting pcap file header at offset 20, and - ``frame.encap_type`` read back from ``tshark -T pdml``. The - ``wrong`` column is what the pre-#843 filter-name path returned. + Each ``(encap_type, filter name, expected)`` triple below was measured on + this host with ``editcap``/``tshark`` 4.6.9 -- the encapsulation + rewritten with ``editcap -F pcap -T ``, the DLT read out of the + resulting pcap file header at offset 20, and ``frame.encap_type`` read + back from ``tshark -T pdml``. The ``wrong`` column is what an + upper-casing filter-name path returns (#843). """ from pcapkit.const.reg.linktype import LinkType from pcapkit.toolkit import pyshark as toolkit cases = [ - # encap, editcap -T, filter name, expected, pre-#843 answer + # encap, editcap -T, filter name, expected, upper-cased answer ('174', 'loop', 'null', LinkType.LOOP, LinkType.NULL), ('15', 'null', 'null', LinkType.NULL, LinkType.NULL), ('210', 'linux-sll2', 'sll', LinkType.LINUX_SLL2, None), # raised @@ -370,12 +368,12 @@ def test_tcp_traceflow_raises_for_a_non_numeric_encap_type(self) -> None: self.assertEqual(ctx.exception.args[0], 'frame.encap_type=Ethernet') def test_filter_name_fallback_no_longer_upper_cases_onto_a_member(self) -> None: - """The pre-#843 fallback tried ``Enum_LinkType.get(name.upper())``, - which is what silently answered ``PPP`` (9) for a - ``ppp-with-direction`` capture (DLT 204) and ``LAPD`` (203) for a - ``linux-lapd`` one (DLT 177). Those names are ambiguous, so with no - ``frame.encap_type`` to key on there is nothing to resolve them to - and they must raise instead of answering the commoner DLT. + """A fallback trying ``Enum_LinkType.get(name.upper())`` (#843) + silently answers ``PPP`` (9) for a ``ppp-with-direction`` capture + (DLT 204) and ``LAPD`` (203) for a ``linux-lapd`` one (DLT 177). + Those names are ambiguous, so with no ``frame.encap_type`` to key + on there is nothing to resolve them to and they must raise instead + of answering the commoner DLT. """ from pcapkit.toolkit import pyshark as toolkit diff --git a/tests/utilities/test_config_unit.py b/tests/utilities/test_config_unit.py index 9d6b5bc86c..6e8fc42425 100644 --- a/tests/utilities/test_config_unit.py +++ b/tests/utilities/test_config_unit.py @@ -57,8 +57,8 @@ '_ZERO_PAD_BUDGET_RATIO'], } -#: What :mod:`pcapkit.utilities.config` held for the :mod:`pcapkit.vendor` -#: crawlers before they kept their own variables again (:issue:`1726`). +#: The :mod:`pcapkit.vendor` crawlers' own variables, which +#: :mod:`pcapkit.utilities.config` does not hold (#1726). VENDOR_NAMES = ('VENDOR_RETRY', 'CI_MODE', 'http_proxy', 'https_proxy') #: The public readers of :mod:`pcapkit.utilities.config`, which are not variables. diff --git a/tests/utilities/test_exceptions_excepthook.py b/tests/utilities/test_exceptions_excepthook.py index e3f153373c..76fa311213 100644 --- a/tests/utilities/test_exceptions_excepthook.py +++ b/tests/utilities/test_exceptions_excepthook.py @@ -93,7 +93,7 @@ def run_in_subprocess(code: 'str', devmode: 'bool' = False) -> 'subprocess.Compl class UnrelatedTracebackSurvivesALoudErrorTests(unittest.TestCase): - """The regression itself, live and reproducible against the pre-fix tree.""" + """The regression itself, live and reproducible without the fix.""" def test_unrelated_exception_keeps_its_frames_after_a_loud_error(self) -> None: code = ''' @@ -493,13 +493,13 @@ def tearDown(self) -> None: def test_a_fresh_instance_installs_over_a_stale_instance_hook(self) -> None: # ``capture()`` alone is enough to keep this quiet -- it swaps the # logger's handlers out and restores them in a ``finally``. Setting - # ``logger.disabled`` directly instead, as an earlier revision of this - # test did, would not be: ``logging.getLogger(name)`` hands back the - # *same* cached singleton regardless of how many times this module is - # reloaded, so disabling it here without restoring it would silence - # every other test in the process sharing that name afterwards -- - # exactly the class of leak this file exists to catch, just aimed at a - # different piece of global state. + # ``logger.disabled`` directly instead would not be: + # ``logging.getLogger(name)`` hands back the *same* cached singleton + # regardless of how many times this module is reloaded, so disabling + # it here without restoring it would silence every other test in the + # process sharing that name afterwards -- exactly the class of leak + # this file exists to catch, just aimed at a different piece of global + # state. first = bootstrap(devmode=False)['exceptions'] with capture(first.logger): first.BaseError('boom') diff --git a/tests/utilities/test_logging.py b/tests/utilities/test_logging.py index 9159475f4a..7883c1ee6b 100644 --- a/tests/utilities/test_logging.py +++ b/tests/utilities/test_logging.py @@ -470,7 +470,7 @@ class ExtractorLoggingTests(unittest.TestCase): whatever unrelated point the collector reaches it -- inside a sibling module's test. :mod:`tests.utilities.test_stacklevel` records warnings to inspect their attribution, and a window asserted to hold exactly one record is failed by a - stray handle from here (:issue:`606`). + stray handle from here (#606). Beyond that, the constructions are plain: with ``auto=True`` the extraction runs in ``__init__``, so the bodies are empty and everything asserted is read diff --git a/tests/utilities/test_stacklevel.py b/tests/utilities/test_stacklevel.py index ac477f168e..f53547f2f1 100644 --- a/tests/utilities/test_stacklevel.py +++ b/tests/utilities/test_stacklevel.py @@ -178,7 +178,7 @@ def emissions(records: 'list[pywarnings.WarningMessage]', :exc:`ResourceWarning` among them. A file handle some earlier test left open is reported whenever the collector happens to reach it, so it surfaces inside an arbitrary window, and asserting that a window held exactly one record let - another test's litter fail *this* one (:issue:`606`). + another test's litter fail *this* one (#606). That is not a theoretical ordering concern. The same failure was watched migrating between two pull requests with neither branch touched and neither @@ -192,8 +192,8 @@ def emissions(records: 'list[pywarnings.WarningMessage]', The callers count over :class:`~pcapkit.utilities.warnings.BaseWarning`, the root of the package's own categories, rather than over the one category they - expect. Narrowing the count that far would have dropped something the old - assertion did buy: a *second* complaint from :mod:`pcapkit` under some other + expect. Narrowing the count that far would drop something an exactly-one + count does buy: a *second* complaint from :mod:`pcapkit` under some other category is a real defect, and counting only the expected category would let it pass unnoticed. Counting pcapkit's warnings as a group keeps that, and gives up only the part that was never this module's business -- warnings the @@ -238,9 +238,9 @@ def test_level_counts_the_pcapkit_frames_not_the_outer_ones(self) -> None: def test_level_does_not_grow_with_the_outer_stack(self) -> None: """The defect, stated directly: the outer depth must not matter. - The old ``index - 1`` returned the number of outer frames less one, so - these six shapes -- identical on the :mod:`pcapkit` side -- gave six - different answers. + An ``index - 1`` returns the number of outer frames less one, so these + shapes -- identical on the :mod:`pcapkit` side -- would give a + different answer each. """ levels = {outer: self.level_for(outer=outer, inner=4) @@ -405,8 +405,8 @@ def test_a_foreign_warning_in_the_window_does_not_fail_the_probe(self) -> None: The probes record with ``simplefilter('always')``, which un-ignores the categories Python hides by default, so a file handle an earlier test left for the collector arrives in the window as a :exc:`ResourceWarning`. Two - assumptions break on that (:issue:`606`): a count requiring the window to - hold exactly one record, and picking by *position*, which returns the last + assumptions break on that (#606): a count requiring the window to hold + exactly one record, and picking by *position*, which returns the last record rather than the probe's own. Warnings are raised on both sides of the probe here deliberately -- one