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
67 changes: 29 additions & 38 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,28 +174,20 @@ jobs:
# why.
#
# #1633: a KTLO leg that cannot install -- such as 3.7 -- 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;
# on a KTLO leg the annotation is a warning (#1672).
# once: util/pip_install.py writes one annotation naming pip's last ERROR
# line, 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; on a KTLO leg the annotation is a warning (#1672).
- 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
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 "::${ANNOTATION_LEVEL} title=pcapkit did not install on ${LEG//,/%2C}::${reason//%/%25}. The rest of this leg is skipped."
exit 1
fi
run: python util/pip_install.py -e '.[test,DPKT,crypto,NGAP]'

# #1958, the owner's ruling E: latest CPython runs the whole suite, so its
# leg installs everything the suite gates on that pip can install there --
Expand Down Expand Up @@ -491,14 +483,7 @@ jobs:
shell: bash
env:
LEG: Python ${{ matrix.python-version }}${{ matrix.ktlo && ' (KTLO, non-blocking)' || '' }}
run: |
python -m pip install -U pip setuptools wheel
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 "::${ANNOTATION_LEVEL} title=pcapkit did not install on ${LEG//,/%2C}::${reason//%/%25}. The rest of this leg is skipped."
exit 1
fi
run: python util/pip_install.py -e '.[test,Scapy,DPKT,cli]'

# #1958: the `test` job's step of the same name. On `latest` this job runs
# the whole fixture tier, so it installs the whole suite's set too.
Expand Down Expand Up @@ -1296,13 +1281,14 @@ jobs:
# As in the `test` job (#1672): the install needs this leg's interpreter,
# and the steps after it need the install, so a KTLO leg whose setup or
# install failed stops there instead of testing the image's `python`.
# util/pip_install.py annotates a failed install, as in `test`.
- name: Install package, test and generator dependencies
id: install
if: ${{ steps.python.outcome == 'success' }}
continue-on-error: ${{ matrix.ktlo == true }}
run: |
python -m pip install -U pip setuptools wheel
python -m pip install -e '.[test,Scapy,DPKT,cli,PyShark,PyPCAP,PyPCAPFile]'
env:
LEG: Python ${{ matrix.python-version }}${{ matrix.ktlo && ' (KTLO, non-blocking)' || '' }}
run: python util/pip_install.py -e '.[test,Scapy,DPKT,cli,PyShark,PyPCAP,PyPCAPFile]'

# See the `integration` job above.
- name: Regenerate sample captures
Expand Down Expand Up @@ -1569,11 +1555,12 @@ jobs:
# DPKT/crypto/NGAP join `test` for the same reason the `test` job above
# installs them: this leg's root-level modules and its own directory's
# modules both reach gates that would otherwise skip silently rather
# than run.
# than run. This job has no KTLO leg, so a failed install is an error.
- name: Install package and test dependencies
run: |
python -m pip install -U pip setuptools wheel
python -m pip install -e '.[test,DPKT,crypto,NGAP]'
env:
ANNOTATION_LEVEL: error
LEG: Python ${{ steps.python-versions.outputs.latest }}
run: python util/pip_install.py -e '.[test,DPKT,crypto,NGAP]'

- name: Report available parallelism
run: |
Expand Down Expand Up @@ -1711,11 +1698,13 @@ jobs:
cache: pip

# The whole suite's set, the line `test` adds on `latest` (#1958), so a
# gated pin runs here exactly as the rest of its tier does there.
# gated pin runs here exactly as the rest of its tier does there. No KTLO
# leg, so a failed install is an error.
- name: Install package and test dependencies
run: |
python -m pip install -U pip setuptools wheel
python -m pip install -e '.[test,Scapy,DPKT,crypto,cli,NGAP,vendor,docs,PyShark]'
env:
ANNOTATION_LEVEL: error
LEG: Python ${{ steps.python-versions.outputs.latest }}
run: python util/pip_install.py -e '.[test,Scapy,DPKT,crypto,cli,NGAP,vendor,docs,PyShark]'

# See the `test` job above for why this step exists.
- name: Report available parallelism
Expand Down Expand Up @@ -1798,11 +1787,13 @@ jobs:
# #1958: `vendor`, `docs` and `PyShark` join them, so this is the set the
# `latest` legs install (see `test`'s "Install the whole suite's
# dependencies on latest" step), and the html5lib, Sphinx and PyShark
# gates run here as they do there.
# gates run here as they do there. No KTLO leg, so a failed install is an
# error.
- name: Install package, test and generator dependencies
run: |
python -m pip install -U pip setuptools wheel
python -m pip install -e '.[test,Scapy,DPKT,crypto,cli,NGAP,vendor,docs,PyShark]'
env:
ANNOTATION_LEVEL: error
LEG: Python ${{ needs.python-versions.outputs.latest }}
run: python util/pip_install.py -e '.[test,Scapy,DPKT,crypto,cli,NGAP,vendor,docs,PyShark]'

# See the `integration` job above. This job has no KTLO leg, so a
# failure is always an error.
Expand Down
8 changes: 7 additions & 1 deletion docs/source/contributing/workflows.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,10 @@ does; each has tests under :file:`tests/project/` that run it.
- ``Regenerate sample captures`` in ``unit-tests.yml``'s ``integration`` and
``pypcap-parity`` (and so their KTLO twins), at the job's ``ANNOTATION_LEVEL``,
and ``gate``, at ``error``.
* - :file:`util/pip_install.py`
- The install step of ``unit-tests.yml``'s ``test``, ``integration`` and
``pypcap-parity`` (and so their KTLO twins), at the job's ``ANNOTATION_LEVEL``,
and of ``unittest-ordering``, ``project-tests`` and ``gate``, at ``error``.

The summaries run on the image's ``/usr/bin/python3`` rather than the leg's
interpreter, which may have failed to set up or be too broken to run anything, so
Expand All @@ -1216,7 +1220,9 @@ too, standard library only, so on GraalPy's leg the leg's interpreter runs pytes
alone; on PyPy's legs the leg's interpreter runs the script as well, under the
file-size cap every test command of ``unit-tests.yml`` takes.
:file:`util/regenerate_samples.py` is standard library only but runs on the leg's
interpreter, since the generator it runs imports ``pcapkit``. Both ``changes`` jobs check out on every event, since the script is in the
interpreter, since the generator it runs imports ``pcapkit``, and so does
:file:`util/pip_install.py`, since it installs into that interpreter; it runs on
every version the matrices hold, 3.7 included. Both ``changes`` jobs check out on every event, since the script is in the
tree. ``create-release.yml``'s ``pypi`` job keeps its own copy of
the KTLO summary's shell until its shell moves to scripts too; a project pin runs
that copy and the script on the same steps and requires the same output.
Expand Down
14 changes: 9 additions & 5 deletions tests/_dependency_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@
from typing import Iterator, Optional

__all__ = [
'WORKFLOW', 'PYPROJECT', 'LATEST_LEG_CONDITION', 'CORE', 'MODULE_PROVIDERS', 'NON_DISTRIBUTION_FLAGS',
'MUTUALLY_EXCLUSIVE_IMPORTS', 'DEPENDENCY_GATE_EXCLUSIONS',
'WORKFLOW', 'PYPROJECT', 'LATEST_LEG_CONDITION', 'EDITABLE_INSTALL', 'CORE', 'MODULE_PROVIDERS',
'NON_DISTRIBUTION_FLAGS', 'MUTUALLY_EXCLUSIVE_IMPORTS', 'DEPENDENCY_GATE_EXCLUSIONS',
'Requirement', 'Gate', 'Job', 'Gap', 'Exclusion', 'AmbiguousProvider',
'requirement_key', 'provided_by', 'declared_requirements', 'extras_providing',
'module_providers', 'contested_imports',
Expand Down Expand Up @@ -136,6 +136,10 @@
#: The ``if:`` of a step that runs on the ``latest`` leg alone (#1958), which is
#: how :func:`pytest_jobs` tells that leg's install line from every leg's.
LATEST_LEG_CONDITION = 'matrix.python-version == needs.python-versions.outputs.latest'
#: An editable install of the checkout, its one group the extras: pip's own
#: ``pip install -e '.[...]'``, or ``util/pip_install.py -e '.[...]'``, which runs
#: it for most install steps (#1967). Every reader of an install line uses it.
EDITABLE_INSTALL = re.compile(r"(?:pip install|util/pip_install\.py) -e '\.\[([^]]*)\]'")

#: Stands in for ``[project] dependencies`` where an extra name is expected:
#: what ``pip install -e .`` installs with no extra asked for at all. Spelled
Expand Down Expand Up @@ -1245,7 +1249,7 @@ def _engine_matrix_variants(name: 'str', section: 'str') -> 'Optional[tuple[Job,
)
declared_engines = frozenset(re.findall(r'-[ \t]*(\w+)', declared_match.group(1)))

base_installs = re.findall(r"pip install -e '\.\[([^]]*)\]'", code)
base_installs = EDITABLE_INSTALL.findall(code)
if len(base_installs) != 1:
raise AssertionError(
f'the {name!r} job varies by matrix.engine but has {len(base_installs)} '
Expand Down Expand Up @@ -1339,14 +1343,14 @@ def pytest_jobs(workflow: 'Optional[pathlib.Path]' = None) -> 'tuple[Job, ...]':
# install line, read apart from the one every leg runs.
latest_steps = [step for step in steps if LATEST_LEG_CONDITION in step.partition('run:')[0]]
latest_installs = [extras for step in latest_steps
for extras in re.findall(r"pip install -e '\.\[([^]]*)\]'", step)]
for extras in EDITABLE_INSTALL.findall(step)]
if len(latest_installs) > 1:
raise AssertionError(
f'the {name!r} job has {len(latest_installs)} latest-only '
f"\"pip install -e '.[...]'\" lines, and this guard cannot tell which "
f'extras that leg would have'
)
installs = re.findall(r"pip install -e '\.\[([^]]*)\]'", section)
installs = EDITABLE_INSTALL.findall(section)
installs = [extras for extras in installs if extras not in latest_installs] \
if latest_installs else installs
if len(installs) != 1:
Expand Down
Loading
Loading