diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index b2e0217c7a..a354e184b2 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -452,10 +452,25 @@ jobs: # pypcap ships no wheel: it compiles pcap.c against libpcap, so both the # headers and the shared library have to be present before pip is asked # to build it. - - name: Install libpcap headers and a C toolchain + # + # tshark joins them per #845's finding 1: this job installs pyshark and + # its fixture-tier selection (see this job's own comment above) is the + # only place tests/integration/test_engine_runtime.py's real + # ``engine='pyshark'`` extraction is reachable at all -- + # `engine-tests`'s own selection ignores tests/integration and + # *_runtime.py wholesale. Without tshark, PyShark.unsupported_reason() + # declines the engine, the extractor silently falls back to the default + # parser, and the test's assertions used to pass on that fallback's + # output -- a vacuous pass, invisible in skip counts. The debconf + # pre-seed and DEBIAN_FRONTEND mirror `engine-tests`'s "Install system + # libpcap and tshark" step exactly, for the same reason: tshark's + # postinst otherwise blocks on the "allow non-superusers to capture + # packets" prompt. + - name: Install libpcap headers, a C toolchain, and tshark run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends build-essential libpcap-dev + echo "wireshark-common wireshark-common/install-setuid boolean false" | sudo debconf-set-selections + sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends build-essential libpcap-dev tshark - name: Install package, test and generator dependencies run: | diff --git a/tests/integration/test_engine_parity.py b/tests/integration/test_engine_parity.py index 2f12dd8b51..bb7c728220 100644 --- a/tests/integration/test_engine_parity.py +++ b/tests/integration/test_engine_parity.py @@ -5,9 +5,11 @@ capture through each engine and wrote four reports nobody compared, into assertions that the engines agree. -``pyshark`` is left out on purpose. It needs :program:`tshark`, which is not -installed here, and on Python 3.14 it fails inside its own -``get_event_loop()`` before it reads a byte; +``pyshark`` is left out on purpose -- not for want of :program:`tshark`, which +the ``pypcap-parity`` job (the one whose selection collects this file) now +installs, but because on Python 3.14 it fails inside its own +``get_event_loop()`` before it reads a byte, so it cannot take part in a parity +comparison that runs on every version; :file:`tests/integration/test_engine_runtime.py` already pins that. The ``pipeline`` and ``server`` engines are commented out in the original script and are not covered here either. diff --git a/tests/integration/test_engine_runtime.py b/tests/integration/test_engine_runtime.py index 698667b34e..a2efbad487 100644 --- a/tests/integration/test_engine_runtime.py +++ b/tests/integration/test_engine_runtime.py @@ -4,6 +4,7 @@ import importlib.util import sys import unittest +from unittest import mock from tests._support import close_extractor, purge_modules, sample_path @@ -99,8 +100,25 @@ def test_pyshark_engine_is_refused_before_asyncio_can_break(self) -> None: # And the fall back is a working extraction, not an empty one. self.assertEqual(extractor.length, 6) else: - extractor = extract(fin=sample_path('in.pcap'), fout='/tmp/out', format='tree', store=True, nofile=True, engine='pyshark') + # Prove pyshark actually ran, rather than the built-in parser wearing + # its label -- see test_new_engine_parity_runtime.py's own `extract` + # helper, which asserts the same thing for PyPCAP/PyPCAPFile. Without + # this, a CI leg missing the ``tshark`` binary makes + # ``PyShark.unsupported_reason()`` decline the engine, the extractor + # silently falls back to the default parser, and the length assertion + # below passes on the fallback's output -- a vacuous pass (#845). + from pcapkit.foundation.engines.pyshark import PyShark + + with mock.patch('pcapkit.foundation.extraction.warn') as warn: + extractor = extract(fin=sample_path('in.pcap'), fout='/tmp/out', format='tree', store=True, nofile=True, engine='pyshark') self.addCleanup(close_extractor, extractor) + + engines = [call.args[0] for call in warn.call_args_list + if len(call.args) > 1 and call.args[1] is EngineWarning] + self.assertEqual(engines, [], "'pyshark' was replaced by the fallback engine") + self.assertEqual(extractor._exnam, 'pyshark') + self.assertIsInstance(extractor.engine, PyShark) + self.assertGreater(extractor.length, 0)