Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ jobs:
- uses: actions/checkout@v7

# One interpreter, not a matrix. Unit Tests and Python Compatibility sweep
# 3.11-3.15, plus the non-blocking KTLO legs 3.8-3.10 (#1633), because
# 3.11-3.15, plus the non-blocking KTLO legs 3.7-3.10 (#1633), because
# they check that the package *runs* everywhere; these tools read the
# source, and running them several times over would produce several
# copies of the same findings for several times the runner cost.
Expand Down
35 changes: 29 additions & 6 deletions .github/workflows/python-compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ jobs:
#
# #1633: two tiers. The supported tier, 3.11-3.15, keeps the plain name,
# which is what ruleset 23497679 requires one context per version of. The
# KTLO tier -- end-of-life versions that still run, 3.8-3.10 -- is suffixed
# KTLO tier -- end-of-life versions that still run, 3.7-3.10 -- is suffixed
# ` (KTLO)`, so no required context can ever match one, and carries
# `continue-on-error`, so a failed leg shows red without failing the run.
# 3.6 and 3.7 are left out: neither can install and import pcapkit (see the
# note on `requires-python` in pyproject.toml).
# 3.7 is red by design: it cannot import pcapkit (see "Verify package"). 3.6
# is left out: setup-python builds it only for retired Ubuntu images.
name: Compat Python ${{ matrix.python-version }}${{ matrix.ktlo && ' (KTLO)' || '' }}
runs-on: ${{ matrix.os || 'ubuntu-latest' }}
continue-on-error: ${{ matrix.ktlo == true }}
Expand All @@ -49,8 +49,12 @@ jobs:
- "3.14"
- "3.15"
# setup-python publishes 3.8 and 3.9 builds up to Ubuntu 24.04 only, and
# `ubuntu-latest` moves to 26.04 from 2026-10-19, hence the pin.
# `ubuntu-latest` moves to 26.04 from 2026-10-19, hence the pin. 3.7's
# newest is for the deprecated 22.04; see the `test` job in unit-tests.yml.
include:
- python-version: "3.7"
ktlo: true
os: ubuntu-22.04
- python-version: "3.8"
ktlo: true
os: ubuntu-24.04
Expand All @@ -71,7 +75,26 @@ jobs:
python -m pip install -U pip setuptools wheel
python -m pip install -e .

# One annotation saying why, so a red leg -- 3.7's, today, where the
# editable install succeeds and 41 modules do not compile -- is legible
# from the run summary without opening the log. Each title escapes `,` as
# `%2C`: the runner splits a command's properties on `,`, so the KTLO
# leg's title would otherwise stop at "(KTLO" (actions/runner
# src/Runner.Common/ActionCommand.cs:100, at 5f0b3ddcc).
- name: Verify package
shell: bash
env:
LEG: Python ${{ matrix.python-version }}${{ matrix.ktlo && ' (KTLO, non-blocking)' || '' }}
run: |
python -m compileall -q pcapkit
python -c 'import pcapkit; print(pcapkit.__version__)'
log="$RUNNER_TEMP/compileall.log"
if ! python -m compileall -q pcapkit > "$log" 2>&1; then
cat "$log"
count=$(awk '/^\*\*\* Error compiling /{n++} END{print n+0}' "$log")
first=$(awk '/^ File "/{sub(/^ /, ""); print; exit}' "$log")
echo "::error title=pcapkit does not compile on ${LEG//,/%2C}::$count modules fail to compile, the first at ${first:-an unreported place}; see this step's log."
exit 1
fi
if ! python -c 'import pcapkit; print(pcapkit.__version__)'; then
echo "::error title=pcapkit does not import on ${LEG//,/%2C}::See the traceback in this step's log."
exit 1
fi
76 changes: 65 additions & 11 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ jobs:
# as a red step and an error annotation. 3.8 and 3.9 need
# ubuntu-24.04: setup-python has no Ubuntu 26.04 build of either, and
# `ubuntu-latest` moves to 26.04 from 2026-10-19.
#
# 3.7 needs ubuntu-22.04, the newest image setup-python builds it for
# (3.7.17). It cannot install today -- the `test` extra's `pytest>=8`
# needs 3.8, and the sources use syntax 3.7 cannot parse -- so the leg
# stops at its install step, with one annotation naming pip's reason
# (see that step). ubuntu-22.04 is deprecated: brownouts from
# 2027-03-23, retirement on 2027-04-17 (actions/runner-images#14254).
# A brownout fails the leg before any step runs, which no step-level
# `continue-on-error` can contain, so this leg must go before then.
- python-version: "3.7"
ktlo: true
os: ubuntu-22.04
- python-version: "3.8"
ktlo: true
os: ubuntu-24.04
Expand All @@ -93,6 +105,7 @@ jobs:
- uses: actions/checkout@v7

- uses: actions/setup-python@v7
id: python
continue-on-error: ${{ matrix.ktlo == true }}
with:
python-version: ${{ matrix.python-version }}
Expand Down Expand Up @@ -127,11 +140,29 @@ jobs:
# HAS_PYPCAPFILE-gated methods between them), deliberately apart from
# this job rather than added to it -- see either job's own comment for
# why.
#
# #1633: a KTLO leg that cannot install -- 3.7 today -- fails fast and
# once: one annotation names pip's last ERROR line (its title's `,` as
# `%2C`, as the runner splits properties on `,`), and every later step
# needs `steps.install.outcome == 'success'`, so it is skipped rather
# than failing against a missing package. The install needs its own
# interpreter: if setup-python failed, the image's `python` would be
# tested under this leg's name. On a supported leg either fails the job.
- name: Install package and test dependencies
id: install
if: ${{ steps.python.outcome == 'success' }}
continue-on-error: ${{ matrix.ktlo == true }}
shell: bash
env:
LEG: Python ${{ matrix.python-version }}${{ matrix.ktlo && ' (KTLO, non-blocking)' || '' }}
run: |
python -m pip install -U pip setuptools wheel
python -m pip install -e '.[test,DPKT,crypto,NGAP]'
if ! python -m pip install -e '.[test,DPKT,crypto,NGAP]' 2>&1 | tee "$RUNNER_TEMP/pip-install.log"; then
reason=$(sed -n 's/^ERROR: //p' "$RUNNER_TEMP/pip-install.log" | tail -n 1)
if [ -z "$reason" ]; then reason="pip failed without an ERROR line; see this step's log"; fi
echo "::error title=pcapkit did not install on ${LEG//,/%2C}::${reason//%/%25}. The rest of this leg is skipped."
exit 1
fi

# 7.10 is the first release with `[run] patch = ["subprocess"]`, which
# .github/coverage.toml relies on to measure the xdist workers.
Expand All @@ -143,13 +174,15 @@ jobs:
# the log says what that was -- print it so the number `-n auto` picked
# is a measured fact instead of an assumption about runner size.
- name: Report available parallelism
if: ${{ steps.install.outcome == 'success' }}
continue-on-error: ${{ matrix.ktlo == true }}
run: |
nproc
python -c "import os; print('cpu_count', os.cpu_count())"

- name: Run unit tests
id: tests
if: ${{ steps.install.outcome == 'success' }}
continue-on-error: ${{ matrix.ktlo == true }}
# `--dist load` is pinned explicitly rather than left to default: a
# future change to xdist's own default, or someone "optimising" to
Expand Down Expand Up @@ -294,7 +327,7 @@ jobs:
# `continue-on-error` because the file is only diagnostic: an artifact
# service outage must not fail a leg that `Required checks passed` needs.
- name: Upload test timings
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
continue-on-error: true
uses: actions/upload-artifact@v7
with:
Expand All @@ -319,6 +352,9 @@ jobs:
- "3.14"
- "3.15"
include:
- python-version: "3.7"
ktlo: true
os: ubuntu-22.04
- python-version: "3.8"
ktlo: true
os: ubuntu-24.04
Expand All @@ -332,6 +368,7 @@ jobs:
- uses: actions/checkout@v7

- uses: actions/setup-python@v7
id: python
continue-on-error: ${{ matrix.ktlo == true }}
with:
python-version: ${{ matrix.python-version }}
Expand Down Expand Up @@ -359,15 +396,28 @@ jobs:
# make installing it here a regression, now fixed by #743/#746. #751's
# `pypcap-parity` job below covers those 6 methods instead, on the same
# fixture-tier selection as this job but in a venv of its own.
#
# A KTLO leg that cannot install stops here, as in the `test` job above.
- name: Install package, test and generator dependencies
id: install
if: ${{ steps.python.outcome == 'success' }}
continue-on-error: ${{ matrix.ktlo == true }}
shell: bash
env:
LEG: Python ${{ matrix.python-version }}${{ matrix.ktlo && ' (KTLO, non-blocking)' || '' }}
run: |
python -m pip install -U pip setuptools wheel
python -m pip install -e '.[test,Scapy,DPKT,cli]'
if ! python -m pip install -e '.[test,Scapy,DPKT,cli]' 2>&1 | tee "$RUNNER_TEMP/pip-install.log"; then
reason=$(sed -n 's/^ERROR: //p' "$RUNNER_TEMP/pip-install.log" | tail -n 1)
if [ -z "$reason" ]; then reason="pip failed without an ERROR line; see this step's log"; fi
echo "::error title=pcapkit did not install on ${LEG//,/%2C}::${reason//%/%25}. The rest of this leg is skipped."
exit 1
fi

# `shell: bash` rather than the default, because it runs with -o pipefail:
# without it the `tee` below would swallow the generator's exit status.
- name: Regenerate sample captures
if: ${{ steps.install.outcome == 'success' }}
continue-on-error: ${{ matrix.ktlo == true }}
shell: bash
run: |
Expand All @@ -393,6 +443,7 @@ jobs:

# See the `test` job above for why this step exists.
- name: Report available parallelism
if: ${{ steps.install.outcome == 'success' }}
continue-on-error: ${{ matrix.ktlo == true }}
run: |
nproc
Expand All @@ -416,6 +467,7 @@ jobs:
# say so. `--dist load` is pinned explicitly; see the `test` job above
# for why.
- name: Run full test suite
if: ${{ steps.install.outcome == 'success' }}
continue-on-error: ${{ matrix.ktlo == true }}
shell: bash
run: |
Expand All @@ -433,7 +485,7 @@ jobs:

# See the `test` job's step of the same name.
- name: Upload test timings
if: ${{ !cancelled() }}
if: ${{ !cancelled() && steps.install.outcome == 'success' }}
continue-on-error: true
uses: actions/upload-artifact@v7
with:
Expand Down Expand Up @@ -496,9 +548,9 @@ jobs:
# with step-level `continue-on-error: true` -- the treatment #845 gave the
# then-prerelease 3.15 cell -- so a 3.10 failure shows as a failed step and an
# error annotation while the job, and `required-checks`, which reads its
# result, stay green. 3.8 and 3.9 are not cells here: setup-python has no
# Ubuntu 26.04 build of either, and this job installs every interpreter on
# `ubuntu-latest`; PCAP_CT's extra is also `>= '3.10'`.
# result, stay green. 3.7-3.9 are not cells here: setup-python has no
# Ubuntu 26.04 build of any of them, and this job installs every interpreter
# on `ubuntu-latest`; PCAP_CT's extra is also `>= '3.10'`.
#
# Scoped deliberately out (#845): per-engine integration coverage (needs
# regenerated captures, so it stays in aggregate in `integration` and
Expand Down Expand Up @@ -805,7 +857,7 @@ jobs:
'read timed out|connection (reset|refused|aborted)|temporary failure in name resolution|max retries exceeded|could not fetch url|newconnectionerror|readtimeouterror|httpsconnectionpool|retrying \(retry\(|http error (5[0-9][0-9]|429)|status code 429|too many 429 error responses|no matching distribution found' \
&& ! echo "$output" | grep -qiE \
'failed building wheel|error: command .* failed with exit code|subprocess-exited-with-error'; then
echo "::error title=$ENGINE's install failure on Python $PY looks transient, not a compile failure::pip exited $status, but the output matches a network/index failure pattern rather than the C-API compile failure this cell records as expected. Re-run the job rather than trusting this as a genuine 'not-installable' result."
echo "::error title=$ENGINE's install failure on Python $PY looks transient%2C not a compile failure::pip exited $status, but the output matches a network/index failure pattern rather than the C-API compile failure this cell records as expected. Re-run the job rather than trusting this as a genuine 'not-installable' result."
exit 1
fi
echo "::notice title=$ENGINE is not installable on Python $PY (expected)::pip exited $status, matching this cell's recorded expectation ('not-installable'), and the output does not match a transient-failure pattern. No tests run for this cell -- this is a deliberate skip, not a pass."
Expand Down Expand Up @@ -878,7 +930,7 @@ jobs:
if ! bash "$RUNNER_TEMP/engine-cell.sh" 3.10; then
echo "| 3.10 | $ENGINE | | FAILED (KTLO, non-blocking) |" >> "$GITHUB_STEP_SUMMARY"
echo "::endgroup::"
echo "::error title=$ENGINE failed on Python 3.10 (KTLO, non-blocking)::3.10 is end-of-life and KTLO per #1633; this does not fail the job."
echo "::error title=$ENGINE failed on Python 3.10 (KTLO%2C non-blocking)::3.10 is end-of-life and KTLO per #1633; this does not fail the job."
exit 1
fi
echo "::endgroup::"
Expand Down Expand Up @@ -1613,15 +1665,17 @@ jobs:
# here: the cell script exits 0 for a cell that declined exactly as
# expected, so they contribute to that job's `success` like any other cell.
#
# Nor is the KTLO tier (#1633): engine-tests' 3.10 cell, and the 3.8-3.10
# Nor is the KTLO tier (#1633): engine-tests' 3.10 cell, and the 3.7-3.10
# legs of `test` and `integration` and the 3.10 leg of `pypcap-parity`.
# Each is non-blocking through step-level `continue-on-error`, whose effect
# on the job's result *is* documented (a step's `conclusion` is its result
# after `continue-on-error` is applied), so the job, and this gate, read
# `success` while the failed step stays visible. Not a job-level
# `continue-on-error`: GitHub does not document whether `needs.<job>.result`
# sees such a job's failure as `failure` or `success`, so a KTLO-only
# regression could block every merge.
# regression could block every merge. The one failure no step can contain is
# a leg that never gets a runner: the 3.7 legs' deprecated ubuntu-22.04 image
# has brownouts from 2027-03-23 -- see the `test` job's matrix.
#
# The docs-only path. On a pull request whose diff touches nothing but
# `docs/**` and `*.md` files outside the code directories (the `changes`
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Where that depth is not needed, the same interface also drives six third-party
extraction engines.

The whole project declares **Python 3.6** or later. CI supports 3.11 to 3.15, and
still tests the end-of-life 3.8 to 3.10 without letting a failure block anything.
still tests the end-of-life 3.7 to 3.10 without letting a failure block anything;
3.7 cannot import the package yet.

## Installation

Expand Down
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ lines are not patched -- upgrading is the fix.

Supported interpreters are the test matrix's blocking tier: CPython 3.11
through 3.15. `pyproject.toml` declares `requires-python = ">=3.6, <4"` through the
`bpc-*` source conversion, but 3.8 to 3.10 are best-effort (tested, never blocking)
and below 3.8 is intent rather than something exercised -- see the note above
`bpc-*` source conversion, but 3.7 to 3.10 are best-effort (tested, never blocking)
and below 3.8 is intent rather than something that works -- see the note above
`requires-python` in `pyproject.toml`. A report that only reproduces outside the tested range is still
welcome; it may be fixed by raising the floor rather than by patching.

Expand Down
4 changes: 2 additions & 2 deletions docs/source/contributing/pep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -588,8 +588,8 @@ Test Cases

**Largely done.** There is a systematic test suite under ``tests/``, and it runs
in CI against Python 3.11 through 3.15, per
``.github/workflows/unit-tests.yml``, and against the end-of-life 3.8 through 3.10
on legs whose failures block nothing.
``.github/workflows/unit-tests.yml``, and against the end-of-life 3.7 through 3.10
on legs whose failures block nothing; 3.7's stop at the install.
The module count is measurable rather than worth writing down:

.. code-block:: shell
Expand Down
4 changes: 2 additions & 2 deletions docs/source/contributing/releasing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Editing ``__version__`` by hand without also moving :file:`CITATION.cff`'s
(``:494-514``) asserts the two agree, and ``create-release.yml``'s
``unit-tests`` job (``:401-415``) calls ``unit-tests.yml`` with
``gate-only: true``, which runs the **full** suite rather than the tiered
subset an ordinary push runs (its ``gate`` job, ``unit-tests.yml:1481-1550``).
subset an ordinary push runs (its ``gate`` job, ``unit-tests.yml:1533-1602``).
That gate runs whenever a release will -- a moved ``__version__`` is exactly
what makes ``version_check``'s evidence read "not yet published" -- and every
publishing job requires it to have succeeded (``:434,619,784,997``), so a
Expand All @@ -56,7 +56,7 @@ Either run the script, or move both fields by hand in the same commit.
This is the **only** edit a person makes to get a release started -- the tag,
the Release, and every upload are the workflow's job from here. Two other
checks run unconditionally on the same path, though neither is a step in *this*
process: the ``changelog`` job (``unit-tests.yml:1341-1362``) fails outright if
process: the ``changelog`` job (``unit-tests.yml:1393-1414``) fails outright if
``CHANGELOG.md`` has drifted from its source entry under
:file:`docs/source/changelog/`, and the release-body step warns, without
failing, if that entry's heading still reads "unreleased"
Expand Down
Loading
Loading