Skip to content

Commit 9121863

Browse files
authored
fix(examples): stop legacy_smoke test_perf raising KeyError for engines it skips (#2305) (#2375)
test_perf.py walked _engine_support.ENGINES but looked each benchmark up in a table covering only default, pyshark, scapy and dpkt, so the first other engine to pass preflight (pypcap, pcap_ct, pypcapfile) raised KeyError and ended the run. One bench() takes the engine name, so every engine in ENGINES is benchmarked into ../captures/engines/<engine>.txt or skipped with its reason. The README's engine list for test_perf follows. tests/project/test_legacy_smoke_perf_2305.py runs the demo with pyperf and the engine checks stubbed.
1 parent 0c9335e commit 9121863

3 files changed

Lines changed: 112 additions & 33 deletions

File tree

‎examples/legacy_smoke/README.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,11 @@ to work out whether an engine can run on this host.
5555

5656
Most of these run with nothing but `pcapkit`. These do not:
5757

58-
- **test_engine, test_time, test_perf** exercise the `dpkt`, `scapy` and
59-
`pyshark` engines, and test_engine and test_time the `pypcap`, `pcap_ct` and
60-
`pypcapfile` engines too. Those are optional packages, and `pyshark` additionally
61-
drives Wireshark's `tshark` binary. An engine that is missing is reported as
62-
skipped, with the reason, and the rest still run -- see `_engine_support.py`.
58+
- **test_engine, test_time, test_perf** exercise the `dpkt`, `scapy`, `pyshark`,
59+
`pypcap`, `pcap_ct` and `pypcapfile` engines. Those are optional packages, and
60+
`pyshark` additionally drives Wireshark's `tshark` binary. An engine that is
61+
missing is reported as skipped, with the reason, and the rest still run -- see
62+
`_engine_support.py`.
6363
Worth knowing:
6464

6565
- `pyshark` 0.6 does not work on Python 3.14 at all. It asks

‎examples/legacy_smoke/test_perf.py‎

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -34,35 +34,11 @@
3434
from pcapkit import extract # noqa: E402 # pylint: disable=wrong-import-position
3535

3636

37-
def default() -> 'None':
38-
extract(fin='../captures/in.pcap', fout='../captures/engines/default.txt',
39-
format='tree', engine='default')
37+
def bench(engine: 'str') -> 'None':
38+
extract(fin='../captures/in.pcap', fout=f'../captures/engines/{engine}.txt',
39+
format='tree', engine=engine)
4040

4141

42-
def scapy() -> 'None':
43-
extract(fin='../captures/in.pcap', fout='../captures/engines/scapy.txt',
44-
format='tree', engine='scapy')
45-
46-
47-
def dpkt() -> 'None':
48-
extract(fin='../captures/in.pcap', fout='../captures/engines/dpkt.txt',
49-
format='tree', engine='dpkt')
50-
51-
52-
def pyshark() -> 'None':
53-
extract(fin='../captures/in.pcap', fout='../captures/engines/pyshark.txt',
54-
format='tree', engine='pyshark')
55-
56-
57-
#: The benchmark for each engine name this demo covers, a subset of
58-
#: :data:`_engine_support.ENGINES`.
59-
BENCHMARKS = {
60-
'default': default,
61-
'pyshark': pyshark,
62-
'scapy': scapy,
63-
'dpkt': dpkt,
64-
}
65-
6642
runner = pyperf.Runner()
6743

6844
benched = 0
@@ -72,7 +48,7 @@ def pyshark() -> 'None':
7248
report(engine, f'skipped -- {reason}')
7349
continue
7450

75-
runner.bench_func(engine, BENCHMARKS[engine])
51+
runner.bench_func(engine, bench, engine)
7652
benched += 1
7753

7854
if not benched:
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# -*- coding: utf-8 -*-
2+
"""#2305: :file:`examples/legacy_smoke/test_perf.py` benchmarks every engine it does not skip.
3+
4+
The demo walks :data:`_engine_support.ENGINES` and hands each engine that passes
5+
``preflight`` to ``pyperf``. Looking the benchmark up in a hand-written table that
6+
covers only some of those engines raises a :exc:`KeyError` for the first other
7+
engine to pass ``preflight``, ending the run, so one ``bench`` takes the engine
8+
name instead. Run here with ``pyperf`` and the engine checks stubbed, and with
9+
:func:`pcapkit.extract` recorded rather than run:
10+
11+
* :class:`TestEveryEngineIsBenchmarked`: with every engine available, each one in
12+
``ENGINES`` is benchmarked, under its own name, extracting into
13+
:file:`../captures/engines/<engine>.txt`;
14+
* :class:`TestASkippedEngineIsReported`: an engine ``preflight`` refuses is reported
15+
with the reason and not benchmarked.
16+
17+
"""
18+
19+
from __future__ import annotations
20+
21+
import importlib.util
22+
import pathlib
23+
import runpy
24+
import sys
25+
import types
26+
import unittest
27+
from typing import TYPE_CHECKING
28+
from unittest import mock
29+
30+
if TYPE_CHECKING:
31+
from typing import Any, Callable, Optional
32+
33+
#: Test tier (#1958): runs an example script with its harness stubbed.
34+
__tier__ = 'LATEST'
35+
36+
ROOT = pathlib.Path(__file__).resolve().parents[2]
37+
DEMOS = ROOT / 'examples' / 'legacy_smoke'
38+
SCRIPT = DEMOS / 'test_perf.py'
39+
40+
41+
def real_engines() -> 'tuple[str, ...]':
42+
"""``ENGINES`` as :file:`examples/legacy_smoke/_engine_support.py` declares it."""
43+
spec = importlib.util.spec_from_file_location('_pcapkit_2305_engine_support', DEMOS / '_engine_support.py')
44+
assert spec is not None and spec.loader is not None
45+
module = importlib.util.module_from_spec(spec)
46+
spec.loader.exec_module(module)
47+
return tuple(module.ENGINES)
48+
49+
50+
ENGINES = real_engines()
51+
52+
53+
class _Demo(unittest.TestCase):
54+
55+
def setUp(self) -> None:
56+
self.benched = [] # type: list[tuple[str, Callable[..., Any], tuple[Any, ...]]]
57+
self.reports = [] # type: list[tuple[str, str]]
58+
59+
def run_demo(self, preflight: 'Callable[[str, str], Optional[str]]') -> 'mock.Mock':
60+
"""Run the demo; return the stand-in :func:`pcapkit.extract`."""
61+
62+
class Runner:
63+
def bench_func(runner, name: 'str', func: 'Callable[..., Any]', *args: 'Any') -> 'None': # pylint: disable=no-self-argument
64+
self.benched.append((name, func, args))
65+
66+
pyperf = types.ModuleType('pyperf')
67+
pyperf.Runner = Runner # type: ignore[attr-defined]
68+
support = types.ModuleType('_engine_support')
69+
support.ENGINES = ENGINES # type: ignore[attr-defined]
70+
support.preflight = preflight # type: ignore[attr-defined]
71+
support.report = lambda engine, detail: self.reports.append((engine, detail)) # type: ignore[attr-defined]
72+
extract = mock.Mock(name='extract')
73+
with mock.patch.dict(sys.modules, {'pyperf': pyperf, '_engine_support': support}), \
74+
mock.patch('pcapkit.extract', extract):
75+
runpy.run_path(str(SCRIPT), run_name='__main__')
76+
return extract
77+
78+
79+
class TestEveryEngineIsBenchmarked(_Demo):
80+
81+
def test_each_engine_is_benchmarked_into_its_own_report(self) -> None:
82+
extract = self.run_demo(lambda engine, fin: None)
83+
self.assertEqual([name for name, _, _ in self.benched], list(ENGINES))
84+
self.assertEqual(self.reports, [])
85+
for name, func, args in self.benched:
86+
with self.subTest(engine=name):
87+
extract.reset_mock()
88+
func(*args)
89+
extract.assert_called_once_with(fin='../captures/in.pcap', fout=f'../captures/engines/{name}.txt',
90+
format='tree', engine=name)
91+
92+
93+
class TestASkippedEngineIsReported(_Demo):
94+
95+
def test_a_refused_engine_is_reported_and_not_benchmarked(self) -> None:
96+
refused = ENGINES[-1]
97+
self.run_demo(lambda engine, fin: 'not here' if engine == refused else None)
98+
self.assertEqual([name for name, _, _ in self.benched], list(ENGINES[:-1]))
99+
self.assertEqual(self.reports, [(refused, 'skipped -- not here')])
100+
101+
102+
if __name__ == '__main__':
103+
unittest.main()

0 commit comments

Comments
 (0)