From c69265ad3970fcf55110f50942f113b8cd694865 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Wed, 14 Oct 2026 14:14:20 -0400 Subject: [PATCH] docs(toolkit): drop agreement claims from adapter docstrings (#1708) Third-party engines are best effort (#1687), so the docstrings and comments stop saying an adapter does what another engine does: - toolkit/dpkt: six clauses ("as the default engine hands over", "as every engine's adapters do", "in every other engine", ...) - toolkit/pyshark: the epoch, tunnel, fixed-header and SPB notes - engines/dpkt: the SPB, Loopback, packet-block and raw-fallback notes now say what the engine does - engines/pyshark: packet records are counted by the engine itself - engines/pypcapfile: a zero-length record no longer cites the default engine - extraction: record_header no longer compares to the built-in engines - reassembly/ip: a Total Length of 0 no longer claims every adapter tests/project/test_no_engine_agreement_prose_1708_unit.py scans pcapkit/ and docs/source/ (changelog excluded) for those shapes, with whitespace, comment markers and reST markup normalised. It fails on d06701064 with 19 hits and passes here; two hits in files #1726 owns are listed as pending. Docstring- and comment-only: the AST with docstrings stripped is identical for all seven modules. Refs #1708 --- pcapkit/foundation/engines/dpkt.py | 36 ++- pcapkit/foundation/engines/pypcapfile.py | 2 +- pcapkit/foundation/engines/pyshark.py | 8 +- pcapkit/foundation/extraction.py | 5 +- pcapkit/foundation/reassembly/ip.py | 23 +- pcapkit/toolkit/dpkt.py | 33 +-- pcapkit/toolkit/pyshark.py | 23 +- ...est_no_engine_agreement_prose_1708_unit.py | 256 ++++++++++++++++++ 8 files changed, 313 insertions(+), 73 deletions(-) create mode 100644 tests/project/test_no_engine_agreement_prose_1708_unit.py diff --git a/pcapkit/foundation/engines/dpkt.py b/pcapkit/foundation/engines/dpkt.py index 557b933a12..a13fa1008c 100644 --- a/pcapkit/foundation/engines/dpkt.py +++ b/pcapkit/foundation/engines/dpkt.py @@ -189,9 +189,8 @@ def _read_simple(self, buf: 'bytes') -> 'tuple[float, bytes]': """Decode a Simple Packet Block, against the section's first interface. `DPKT`_ has no type for this block, so it is parsed by - :class:`~pcapkit.protocols.misc.pcapng.PCAPNG`, as for the default - engine: the packet data is ``min(original_len, snaplen)`` octets, with - the snaplen of interface 0. + :class:`~pcapkit.protocols.misc.pcapng.PCAPNG`: the packet data is + ``min(original_len, snaplen)`` octets, with the snaplen of interface 0. Args: buf: Whole block, as read from the file. @@ -199,8 +198,7 @@ def _read_simple(self, buf: 'bytes') -> 'tuple[float, bytes]': Returns: Timestamp and packet data, as :meth:`_read_packet` returns them. A Simple Packet Block carries no timestamp, so the packet is dated at - the UNIX epoch, as the default engine dates it; the original length - is the block's own. + the UNIX epoch; the original length is the block's own. Raises: FormatError: If the section describes no interface. A Simple Packet @@ -293,8 +291,8 @@ def __iter__(self) -> 'Iterator[tuple[float, bytes]]': if nanosecond: yield DecimalTimestamp(value, resolution, hdr.len), buf else: - # at the default engine's precision, not the caller's, which - # may be too low to hold it (:issue:`1668`) + # at a precision of 64 digits, not the caller's, which may + # be too low to hold it (:issue:`1668`) with localcontext(prec=64): exact = decimal.Decimal(hdr.tv_sec) + decimal.Decimal(hdr.tv_usec) / resolution yield Timestamp(value, exact, resolution, hdr.len), buf @@ -305,17 +303,16 @@ def __iter__(self) -> 'Iterator[tuple[float, bytes]]': @functools.lru_cache(maxsize=None) def _loopback_protocol(base: 'Type[DPKTPacket]', ip: 'Type[DPKTPacket]', ip6: 'Type[DPKTPacket]', linktype: 'int') -> 'Type[DPKTPacket]': - """A :class:`dpkt.loopback.Loopback` that reads as the default engine's. + """A :class:`dpkt.loopback.Loopback` that reads the address family by link type. It reads the address family in the byte order :func:`~pcapkit.protocols.link.loopback.family_byteorder` gives for ``linktype`` -- network byte order for ``LINKTYPE_LOOP``, inferred for - ``LINKTYPE_NULL`` -- and dispatches on the same families as - :class:`~pcapkit.protocols.link.loopback.Loopback`. Unlike `DPKT`_'s own, it - names an IPv4 or IPv6 payload ``ip`` or ``ip6``, as - :class:`dpkt.ethernet.Ethernet` does and as :mod:`pcapkit.toolkit.dpkt` - looks for it, and keeps a payload of any other family as raw octets rather - than parsing it as Ethernet (:issue:`1574`). + ``LINKTYPE_NULL`` -- and dispatches on families 2 (IPv4) and 24, 28 and 30 + (IPv6). Unlike `DPKT`_'s own, it names an IPv4 or IPv6 payload ``ip`` or + ``ip6``, as :class:`dpkt.ethernet.Ethernet` does and as + :mod:`pcapkit.toolkit.dpkt` looks for it, and keeps a payload of any other + family as raw octets rather than parsing it as Ethernet (:issue:`1574`). Args: base: :class:`dpkt.loopback.Loopback` itself. @@ -361,9 +358,9 @@ class DPKT(EngineBase['DPKTPacket']): :class:`dpkt.pcapng.Reader`, so each packet takes the link type and the timestamp resolution and offset of the interface it was captured on. - It reads every packet block the default engine reads -- the Enhanced, the - Simple and the (obsolete) Packet Block -- and skips every other block - without a warning. `DPKT`_'s own reader skips the Simple Packet Block too. + It reads the Enhanced, the Simple and the (obsolete) Packet Block, and + skips every other block without a warning. `DPKT`_'s own reader skips the + Simple Packet Block too. .. _DPKT: https://dpkt.readthedocs.io @@ -500,9 +497,8 @@ def read_frame(self) -> 'DPKTPacket': # ``frag_off`` from whatever header follows an IPv6 Fragment header, # so a Fragment header followed by ESP or Destination Options raises # :exc:`AttributeError` (:issue:`1351`). The frame is kept as a raw - # packet, so one undecodable frame does not end the extraction and - # the frame numbering still matches the other engines, which fall - # back to a raw payload in the same way. + # packet, so one undecodable frame neither ends the extraction nor + # shifts the numbering of the frames after it. warn(f'Frame {ext._frnum + 1}: dpkt cannot parse the frame ' f'({type(exc).__name__}: {exc}); kept as raw data', DPKTWarning, stacklevel=stacklevel()) diff --git a/pcapkit/foundation/engines/pypcapfile.py b/pcapkit/foundation/engines/pypcapfile.py index 6f59fa5223..bcbc588413 100644 --- a/pcapkit/foundation/engines/pypcapfile.py +++ b/pcapkit/foundation/engines/pypcapfile.py @@ -400,7 +400,7 @@ def _decode(self, packet: 'PCAPFilePacket', frnum: 'int') -> 'PCAPFilePacket': """ # A zero-length record holds nothing to decode, which is not a decoding - # failure: the ``default`` engine reads one without a warning too. + # failure. if self._declf is None or not packet.packet: return packet diff --git a/pcapkit/foundation/engines/pyshark.py b/pcapkit/foundation/engines/pyshark.py index 48dfe392bf..262e99cb2c 100644 --- a/pcapkit/foundation/engines/pyshark.py +++ b/pcapkit/foundation/engines/pyshark.py @@ -58,8 +58,8 @@ def _is_packet(packet: 'PySharkPacket') -> 'bool': :program:`tshark` numbers every record it reads, not only packets: a PCAP-NG systemd Journal Export Block, Custom Block or Sysdig Event Block is a frame - to it, measured with :program:`tshark` 4.6.9. The default engine counts - Enhanced, Simple and obsolete Packet Blocks only. + to it, measured with :program:`tshark` 4.6.9. This engine counts packet + records only. The marker is ``frame.encap_type`` on the record's :func:`_frame_layer`. Wireshark adds it for ``REC_TYPE_PACKET`` records only: @@ -273,8 +273,8 @@ def read_frame(self) -> 'PySharkPacket': """Read frames with PyShark engine. A tshark record that is not a packet, such as a PCAP-NG systemd Journal - Export Block, is skipped. Frames are numbered by count, as the default - engine numbers them, not by tshark's frame number. + Export Block, is skipped. Frames are numbered by count, not by tshark's + frame number. Returns: Parsed frame instance. diff --git a/pcapkit/foundation/extraction.py b/pcapkit/foundation/extraction.py index be433d2488..51e138e9a9 100644 --- a/pcapkit/foundation/extraction.py +++ b/pcapkit/foundation/extraction.py @@ -964,9 +964,8 @@ def record_header(self) -> 'EngineBase[_P]': :class:`~pcapkit.foundation.engines.dpkt.DPKT`, :class:`~pcapkit.foundation.engines.scapy.Scapy` and :class:`~pcapkit.foundation.engines.pyshark.PyShark` -- which call it at - setup, so their output opens with the same header record as the - built-in engines' and :attr:`format` is set even for a capture with no - frames. + setup, so their output opens with the header record dumped above and + :attr:`format` is set even for a capture with no frames. Raises: FormatError: If the input is neither a PCAP nor a PCAP-NG file. diff --git a/pcapkit/foundation/reassembly/ip.py b/pcapkit/foundation/reassembly/ip.py index 675b42806f..112c05a676 100644 --- a/pcapkit/foundation/reassembly/ip.py +++ b/pcapkit/foundation/reassembly/ip.py @@ -182,18 +182,17 @@ def reassembly(self, info: 'Packet[_AT]') -> 'None': # # A Total Length of 0 declares nothing. It is what TCP segmentation # offload leaves in a capture taken on the sending host, and the - # datagram then runs to the end of the captured frame -- which is the - # payload every adapter hands over (:issue:`1547`). Wireshark ("presumed - # TSO") substitutes that length for the field before it reassembles - # anything, and so does this, whatever **MF** and the offset say: an - # unfragmented one is then whole rather than ``-IHL`` octets long - # (:issue:`1555`). Linux's BIG TCP zeroes the field precisely because - # the datagram, header included, outgrows the 65535 octets it can - # declare -- and such a datagram never completes in a buffer of that - # length (:meth:`_is_complete`). So the buffer grows past it, to hold - # the datagram rather than clip it, ``RCVBT`` in step with it; it grows - # by an octet even when the data alone would fit, since its growing is - # what lifts the bound. + # datagram then runs to the end of the captured frame (:issue:`1547`). + # Wireshark ("presumed TSO") substitutes that length for the field + # before it reassembles anything, and so does this, whatever **MF** and + # the offset say: an unfragmented one is then whole rather than ``-IHL`` + # octets long (:issue:`1555`). Linux's BIG TCP zeroes the field + # precisely because the datagram, header included, outgrows the 65535 + # octets it can declare -- and such a datagram never completes in a + # buffer of that length (:meth:`_is_complete`). So the buffer grows past + # it, to hold the datagram rather than clip it, ``RCVBT`` in step with + # it; it grows by an octet even when the data alone would fit, since its + # growing is what lifts the bound. # # With nothing declared, a snaplen cut cannot be seen in the length. A # non-final fragment's cut still shows, as a partial last block, which diff --git a/pcapkit/toolkit/dpkt.py b/pcapkit/toolkit/dpkt.py index 877fe15bbb..e1e6aafd9e 100644 --- a/pcapkit/toolkit/dpkt.py +++ b/pcapkit/toolkit/dpkt.py @@ -83,9 +83,9 @@ class RecordTimestamp: :class:`DecimalTimestamp` instead, which *is* the value `DPKT`_ yields -- so a stored frame's timestamp is exactly what it was before -- and also carries those three. Reassembly and flow tracing are handed a plain - :obj:`float` of it, as by every other engine (:issue:`1501`): of its exact - value, since `DPKT`_'s own for a microsecond PCAP rounds twice - (:issue:`1668`; see :func:`_capture_time`). + :obj:`float` of it (:issue:`1501`): of its exact value, since `DPKT`_'s + own for a microsecond PCAP rounds twice (:issue:`1668`; see + :func:`_capture_time`). A copy, a deep copy and a pickle at any protocol all rebuild a timestamp from what its :meth:`~object.__reduce__` returns, so all keep the three. @@ -225,9 +225,9 @@ def _capture_time(timestamp: 'float | Decimal') -> 'float': timestamp: Capture timestamp of the packet. Returns: - The :obj:`float` nearest the exact timestamp, as the default engine hands - over. `DPKT`_ reads a microsecond PCAP's as ``tv_sec + tv_usec / 1e6``, - which rounds twice, so can be an ulp off at a small ``tv_sec``: + The :obj:`float` nearest the exact timestamp. `DPKT`_ reads a + microsecond PCAP's as ``tv_sec + tv_usec / 1e6``, which rounds twice, + so can be an ulp off at a small ``tv_sec``: ``1.0036909999999999`` for ``1.003691`` (:issue:`1668`). A :class:`RecordTimestamp` is therefore read by its exact value. Any other timestamp -- one passed by hand, or yielded by a ``dpkt`` whose reader @@ -296,9 +296,9 @@ def packet2frame(packet: 'Packet', timestamp: 'float | Decimal', *, Note: The fraction in ``frame_info.ts_usec`` is in the capture's own resolution, - as the default engine's is, and both it and ``orig_len`` come from the - reader: :class:`~pcapkit.foundation.engines.dpkt.DPKT` reads PCAP and - PCAP-NG alike into a :class:`RecordTimestamp`. A timestamp that is not one + and both it and ``orig_len`` come from the reader: + :class:`~pcapkit.foundation.engines.dpkt.DPKT` reads PCAP and PCAP-NG + alike into a :class:`RecordTimestamp`. A timestamp that is not one -- a packet built by hand, or read by a ``dpkt`` whose reader the engine could not extend -- is read as `DPKT`_ yields it: a :class:`~decimal.Decimal` counts nanoseconds and a :obj:`float` @@ -565,7 +565,7 @@ def ipv4_reassembly(packet: 'Packet', timestamp: 'float', *, does. A caller holding only a frame can read it back with :func:`packet2timestamp`, which is where :class:`~pcapkit.foundation.engines.dpkt.DPKT` leaves it. Handed on - as a :obj:`float`, as every engine's adapters do, though `DPKT`_ reads + as a :obj:`float`, though `DPKT`_ reads a nanosecond PCAP's as a :class:`~decimal.Decimal` (:issue:`1501`), and as the one nearest the exact value it carries, if any, where `DPKT`_'s own may be an ulp off (:issue:`1668`; see @@ -706,9 +706,7 @@ def ipv6_reassembly(packet: 'Packet', timestamp: 'float', *, # (:rfc:`8200#section-4.5`), not the IPv6 header's Flow Label. The # label is optional and routinely zero, so keying on it collapses # every datagram between one address pair into a single buffer and - # interleaves their fragments; it would also disagree with - # ``bufid[2]`` in every other engine, which feeds - # :attr:`pcapkit.foundation.reassembly.data.ip.DatagramID.id`. + # interleaves their fragments. ipv6_frag.id, # identification Enum_TransType.get(ipv6_frag.nxt), # next header field in IPv6 Fragment Header ), @@ -742,11 +740,10 @@ def _tcp_flags(tcp: 'TCP') -> 'tuple[bool, bool, bool]': Note: Each flag is masked out of :attr:`dpkt.tcp.TCP.flags` by its ``TH_*`` - value, so it is read from the flags octet alone, as the default engine - reads it. `DPKT`_ 1.9.8 makes that field nine bits wide, its top bit - the NS bit (:rfc:`3540`): read by its position among the field's binary - digits, every flag was one bit low with NS set, a SYN reading as a FIN - (:issue:`1618`). + value, so it is read from the flags octet alone. `DPKT`_ 1.9.8 makes + that field nine bits wide, its top bit the NS bit (:rfc:`3540`): read by + its position among the field's binary digits, every flag was one bit low + with NS set, a SYN reading as a FIN (:issue:`1618`). .. _DPKT: https://dpkt.readthedocs.io diff --git a/pcapkit/toolkit/pyshark.py b/pcapkit/toolkit/pyshark.py index e28c5037c5..a425e95c6c 100644 --- a/pcapkit/toolkit/pyshark.py +++ b/pcapkit/toolkit/pyshark.py @@ -353,13 +353,7 @@ def _parse_epoch(value: 'Any') -> 'float': value: The field as PyShark reports it, decimal text. Returns: - The timestamp, as the same :obj:`float` the default engine reports. - - Note: - The default engine computes the epoch as a :class:`~decimal.Decimal` - and hands the tracer ``float()`` of it (:mod:`pcapkit.toolkit.pcap`). - This takes the same route, so both are the double nearest the same - decimal value: identical, not merely close. + The timestamp, as the :obj:`float` nearest the decimal value. Raises: ProtocolError: If ``value`` is not decimal text. @@ -468,8 +462,7 @@ def _carrier(packet: 'Packet') -> 'tuple[Any, Any]': Note: The nearest is the innermost IP layer of an IP-in-IP tunnel (:rfc:`2003`, :rfc:`2473`, :rfc:`4213`), whose addresses are the TCP - endpoints', as for the default engine - (:func:`pcapkit.toolkit.pcap.tcp_segment`). ``packet.ip`` is the first + endpoints'. ``packet.ip`` is the first ``ip`` layer instead, which is the tunnel's (:issue:`1581`). """ @@ -493,10 +486,10 @@ def _has_fixed_header(tcp: 'Any') -> 'bool': Whether the layer carries ``tcp.urgent_pointer``. Note: - The default engine traces a segment only if its 20-octet fixed header - (:rfc:`9293#section-3.1`) was captured and its Data Offset is at least - 5 (:func:`pcapkit.toolkit.pcap.tcp_segment`). tshark omits fields in - both other cases, measured with tshark 4.6.9 (:issue:`1611`): + A segment is traced only if its 20-octet fixed header + (:rfc:`9293#section-3.1`) was captured and its Data Offset is at + least 5. tshark omits fields in both other cases, measured with + tshark 4.6.9 (:issue:`1611`): * a Data Offset below 5 stops the dissection at ``tcp.hdr_len``, so ``tcp.ack`` and the ``tcp.flags`` fields are absent; @@ -614,8 +607,8 @@ def tcp_traceflow(packet: 'Packet', *, count: 'int' = -1) -> 'TF_TCP_Packet | No # A Simple Packet Block carries no timestamp, so tshark reports no # ``frame.time_epoch`` for it; the PDML ``geninfo`` timestamp PyShark - # keeps as ``sniff_timestamp`` is then ``'0.000000000'``, which is what - # the default engine reports (measured on ``test.pcapng`` frame 4). + # keeps as ``sniff_timestamp`` is then ``'0.000000000'``, the UNIX epoch + # (measured on ``test.pcapng`` frame 4). epoch = getattr(frame, 'time_epoch', None) if epoch is None: epoch = packet.sniff_timestamp diff --git a/tests/project/test_no_engine_agreement_prose_1708_unit.py b/tests/project/test_no_engine_agreement_prose_1708_unit.py new file mode 100644 index 0000000000..766df855ba --- /dev/null +++ b/tests/project/test_no_engine_agreement_prose_1708_unit.py @@ -0,0 +1,256 @@ +# -*- coding: utf-8 -*- +"""No docstring, comment or page claims a third-party engine agrees with another. + +The owner's ruling on GitHub issue #1687, applied by issue #1708: third-party +engines are best effort, their adapters need not match the default engine one +to one, and protocol rules belong in the default engine only. So prose that +says an adapter does something "as the default engine does", "as every other +engine's adapters do", or "stops at the same depth" states a contract nobody +keeps. Issue #1708 removed the tests and the code behind those claims, then the +claims themselves. + +This scans every module of :mod:`pcapkit` and every page and module under +:file:`docs/source` but the changelog for the shapes in :data:`PATTERNS`. The +text is matched whitespace-normalised, comment markers included, since a claim +wrapped across two lines is a claim all the same, and with its reST markup +stripped, since a reader sees "the default engine" in "the ``default`` +engine" too. Each pattern is anchored on the engine or adapter being compared +to, so the ``pypcap-parity`` job, the pypcap against pcap-ct measurements, +"falls back to the default engine" and the reassembly conflict tables are not +matched. + +This module is unit-tier: it reads source text and never imports the package. + +""" +from __future__ import annotations + +import bisect +import re +import unittest +from typing import TYPE_CHECKING + +from tests._tiers import ROOT + +if TYPE_CHECKING: + from pathlib import Path + from typing import Iterator + +#: ``(name, pattern)`` of each agreement-shaped claim, matched case-insensitively. +PATTERNS = { + # "as the default engine dates it", "as for the default engine", + # "reads as the default engine's", "at the default engine's precision" + 'as the default engine': ( + r"\bas (?:for |by |with )?the default engine(?:['’]s)?(?: \w+)?" + r"|\bat the default engine['’]s \w+"), + # "which is what the default engine reports", "the same float the + # default engine reports", "every packet block the default engine reads" + 'what the default engine does': ( + r'\b(?:what|(?:the same|every) \S+(?: \S+)?) the default engine ' + r'(?:reports|reads|returns|yields|hands|gives|does|dates|numbers|counts|traces)\b'), + # "as by every other engine", "as every engine's adapters do" + 'every other engine': r"\bevery other engine\b|\bevery engine['’]s adapters?\b", + # "which is the payload every adapter hands over" + 'every adapter does': ( + r'\bevery (?:other )?adapter ' + r'(?:hands|reports|reads|returns|does|yields|gives)\b'), + # "the frame numbering still matches the other engines" + 'matches the other engines': ( + r'(?\s*\n[ \t]*#:?(?![#!])[ \t]*|\s+)' + r'|(?P(?::[\w.+-]+)+:(?=`)|`+~?|(?<=`)_{1,2}(?!\w))') + + +def normalise(text: str) -> 'tuple[str, list[int]]': + """Collapse whitespace, comment markers and reST markup, keeping each character's line. + + Args: + text: File text. + + Returns: + The text read through :data:`_NORMALISE`, with no two spaces in a row, + and the line number in ``text`` of each character of it. + + """ + chars, lines = [], [] + starts = [0] + [match.end() for match in re.finditer('\n', text)] + + def keep(start: int, stop: int) -> None: + for offset in range(start, stop): + chars.append(text[offset]) + lines.append(bisect.bisect_right(starts, offset)) + + position = 0 + for match in _NORMALISE.finditer(text): + keep(position, match.start()) + if match.group('space') is not None and not (chars and chars[-1] == ' '): + chars.append(' ') + lines.append(bisect.bisect_right(starts, match.start())) + position = match.end() + keep(position, len(text)) + return ''.join(chars), lines + + +def claims(text: str) -> 'Iterator[tuple[int, str, str]]': + """Yield ``(lineno, name, phrase)`` for each agreement claim in ``text``. + + Args: + text: File text. + + Yields: + The line the claim starts on, the :data:`PATTERNS` name it matched, + and the matched phrase, whitespace-normalised. + + """ + flat, lines = normalise(text) + for name, regex in REGEXES.items(): + for match in regex.finditer(flat): + yield lines[match.start()], name, match.group(0) + + +def scanned() -> 'Iterator[Path]': + """Yield every module of the package, and every page and module of the docs but the changelog.""" + yield from sorted((ROOT / 'pcapkit').rglob('*.py')) + for path in sorted({*ROOT.glob('docs/sourc?/**/*.rst'), *ROOT.glob('docs/sourc?/**/*.py')}): + if not path.relative_to(ROOT).parts[2].startswith('changelog'): + yield path + + +class EngineAgreementProseTests(unittest.TestCase): + def test_no_prose_claims_an_engine_agrees_with_another(self) -> None: + found = [] + for path in scanned(): + relative = path.relative_to(ROOT).as_posix() + for lineno, name, phrase in claims(path.read_text(encoding='utf-8')): + if (relative, phrase) not in EXEMPT.union(PENDING): + found.append(f'{relative}:{lineno}: [{name}] {phrase!r}') + self.assertEqual( + found, [], + 'third-party engines are best effort (#1687): state what the adapter ' + 'does, not that it does what another engine does (#1708)') + + def test_every_exemption_still_matches(self) -> None: + """An exemption whose phrase has gone is a stale entry, not a pass.""" + for relative, phrase in sorted(EXEMPT): + with self.subTest(path=relative): + text = (ROOT / relative).read_text(encoding='utf-8') + self.assertIn(phrase, [found for (_, _, found) in claims(text)]) + + def test_the_issue_1708_shapes_are_matched(self) -> None: + """Each claim issue #1708 removed is seen, wrapped or not, so a pass is not vacuous.""" + source = ( + ' """Reassembly and flow tracing are handed a plain float of it, as by\n' + ' every other engine (#1501). The float nearest the exact timestamp, as\n' + ' the default engine hands over. Handed on as a float, as every\n' + " engine's adapters do. The fraction is in the capture's own resolution,\n" + " as the default engine's is.\n" + ' """\n' + ' # interleaves their fragments; it would also disagree with\n' + ' # ``bufid[2]`` in every other engine, which feeds DatagramID.id.\n' + " #: The toolkit adapters that walk a\n" + " #: chain themselves stop at the same depth.\n" + " # keeps as ``sniff_timestamp`` is then '0.000000000', which is what\n" + ' # the default engine reports (measured on frame 4).\n' + ' # the frame numbering still matches the other\n' + ' # engines, which fall back to a raw payload.\n' + ' # the datagram runs to the end of the frame -- which is the\n' + ' # payload every adapter hands over (#1547).\n' + ' """The timestamp, as the same float the default engine reports."""\n' + ' """It reads every packet block the default engine reads."""\n' + " # at the default engine's precision, not the caller's\n" + ' # A zero-length record holds nothing to decode, which is not a decoding\n' + ' # failure: the ``default`` engine reads one without a warning too.\n' + ' """Its header record, just as the other engines do."""\n' + ' """Read as a :obj:`float`, as the other adapters do."""\n' + ' """Dated at the UNIX epoch, as for the built-in engines."""\n' + ' """It skips the block, like the default engine."""\n' + ' setup, so their output opens with the same header record as the\n' + " built-in engines' and :attr:`format` is set.\n" + ' """Each flag is read from the flags octet, as the :doc:`builtin` engines do."""\n' + ) + self.assertEqual( + [(lineno, name) for (lineno, name, _) in sorted(claims(source))], + [(2, 'as the default engine'), (2, 'every other engine'), + (3, 'every other engine'), (5, 'as the default engine'), + (8, 'every other engine'), (10, 'stops at the same depth'), + (11, 'what the default engine does'), (13, 'matches the other engines'), + (16, 'every adapter does'), (17, 'what the default engine does'), + (18, 'what the default engine does'), (19, 'as the default engine'), + (21, 'the default engine too'), (22, 'as the other engines'), + (23, 'as the other engines'), (24, 'as the other engines'), + (25, 'like the default engine'), (26, 'as the other engines'), + (28, 'as the other engines')]) + + def test_legitimate_uses_are_not_matched(self) -> None: + """The contract itself, the parity job, fallbacks and conflict prose pass.""" + source = ( + 'The adapters below are best effort (#1687): they need not match the\n' + ':doc:`builtin` adapters one to one, since protocol rules belong in the\n' + 'default engine only. Like every third-party engine, both need not match\n' + 'the default engine. The ``pypcap-parity`` job, ``PyPCAP/PyPCAPFile parity\n' + 'Python 3.11``. By parity with ``TransportProtocol``. The extraction falls\n' + 'back to the default engine with a warning. Find where this fragment\n' + 'disagrees with what the buffer already holds. pcap-ct reads in.pcap\n' + 'identically on both. Every engine reads it to decide whether to feed\n' + 'the reassembler; every engine registration lands in one table.\n' + 'Unlike the default engine, it keeps the payload raw. The fragment is\n' + 'kept as the other fragments are, and the ``default`` engine is too slow.\n' + ) + self.assertEqual(list(claims(source)), []) + + +if __name__ == '__main__': + unittest.main()