Skip to content

Merge pull request #207 from lfreleng-actions/dependabot/github_actio… #553

Merge pull request #207 from lfreleng-actions/dependabot/github_actio…

Merge pull request #207 from lfreleng-actions/dependabot/github_actio… #553

Workflow file for this run

---
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: 2025 The Linux Foundation
# Action test/validation workflow
name: "Action Tests"
# yamllint disable-line rule:truthy
on:
workflow_dispatch:
push:
branches: ["main"]
pull_request:
branches: ["main"]
paths:
- '**'
- '!.github/**'
- '!.*'
- '!tox.ini'
permissions: {}
# Cancel any in-progress runs of this workflow on the same ref when a new
# commit is pushed. Pull request updates trigger a fresh run, so this keeps
# the queue tidy and ensures only the latest revision's results are visible.
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true
env:
python_version: "3.12"
jobs:
### Test the GitHub Action in this Repository ###
#
# Runs the action across both x64 and arm64 ubuntu-24.04 runners as a
# regression guard against a previously observed failure mode where
# actions/setup-python on these runners did not reliably bootstrap pip
# for Python 3.12, causing 'No module named pip' errors. The migration
# from pip to uv removes the dependency on actions/setup-python's pip
# provisioning, but we keep both architectures permanently in CI to
# detect any future regression in the underlying setup chain.
#
# Original failure references:
# yamllint disable rule:line-length
# https://github.com/ModeSevenIndustrialSolutions/python-nss-ng/actions/runs/24763417356/job/72452664716 # arm64
# https://github.com/ModeSevenIndustrialSolutions/python-nss-ng/actions/runs/24763417356/job/72452544137 # x64
# yamllint enable rule:line-length
standard-tests:
name: "test-python-project (${{ matrix.runs-on }})"
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
runs-on:
- ubuntu-24.04
- ubuntu-24.04-arm
permissions:
contents: read
timeout-minutes: 12
steps:
# Harden the runner used by this workflow
# yamllint disable-line rule:line-length
- uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: 'audit'
- name: "Checkout repository"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Perform setup prior to running test(s)
- name: "Checkout sample project repository"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: "lfreleng-actions/test-python-project"
path: "test-python-project"
persist-credentials: false
# Build sample Python project
- name: "Build Python project"
id: build
# yamllint disable-line rule:line-length
uses: lfreleng-actions/python-build-action@97cf36d2a5914c94761408545d154c983024de1d # v2.0.0
with:
path_prefix: "test-python-project"
tox_build: false
# Disambiguate artefact names across matrix legs (ubuntu-24.04
# and ubuntu-24.04-arm) to avoid actions/upload-artifact 409
# Conflict errors when both legs upload concurrently.
artefact_name: "lfreleng-test-python-project-${{ matrix.runs-on }}"
# Perform Python project standard tests
- name: "Run action: ${{ github.repository }} [PYTEST]"
uses: ./
with:
python_version: ${{ env.python_version }}
path_prefix: "test-python-project"
tests_path: "tests"
github_token: ${{ secrets.GITHUB_TOKEN }}
# Perform Python project failing tests
- name: "Run action: ${{ github.repository }} [Test Failure]"
uses: ./
with:
python_version: ${{ env.python_version }}
path_prefix: "test-python-project"
report_artefact: false
# Test permit failure using action input
tests_path: "tests_fail"
permit_fail: true
github_token: ${{ secrets.GITHUB_TOKEN }}
# Perform Python project failing tests
- name: "Run action: ${{ github.repository }} [Test Failure]"
id: tests-fail
uses: ./
continue-on-error: true
with:
python_version: ${{ env.python_version }}
path_prefix: "test-python-project"
report_artefact: false
# Test permit failure using action input
tests_path: "tests_fail"
github_token: ${{ secrets.GITHUB_TOKEN }}
- name: "Validate previous step failure"
if: steps.tests-fail.outcome == 'success'
shell: bash
run: |
# Check previous step failure
echo "Error: previous step should have failed ❌"
exit 1
### Coverage configuration regression matrix ###
#
# Runs the action against a curated set of small fixtures under
# .fixtures/, each shaped to exercise one cell of the
# (coverage source list × addopts --cov) matrix. Every fixture must
# produce non-zero coverage; a regression that breaks the action's
# decision about when to inject '--cov' (issue #138) will silently
# report 0% for at least one cell, which this job then surfaces as
# a hard failure.
#
# The full rationale and fixture-by-fixture explanation lives in
# .fixtures/README.md.
coverage-fixtures:
name: "coverage-fixtures (${{ matrix.fixture }})"
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
fixture:
# The exact regression case from issue #138: src/ layout +
# addopts '--cov=mypkg' + path-based source list. Under the
# broken v1.1.0 behaviour this reported 0% coverage despite
# all tests passing.
- src-layout-path-source
# The recommended shape: package-name source list. Works
# for both editable and non-editable installs because
# coverage.py resolves the location via import.
- src-layout-pkg-source
# Zero-configuration case. The action derives the package
# import name from [project].name and injects '--cov=<pkg>'
# so collection works for non-editable installs (a bare
# '--cov' would not, since coverage.py's default rule
# excludes site-packages). Falls back to a bare '--cov'
# only when [project].name cannot be extracted.
- flat-layout-no-config
# Middle-ground case: '--cov=mypkg' in addopts only, no
# source list. The detection regex must spot the addopts
# entry and skip the '--cov' injection.
- addopts-cov-only
permissions:
contents: read
timeout-minutes: 8
steps:
# Harden the runner used by this workflow
# yamllint disable-line rule:line-length
- uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: 'audit'
- name: "Checkout repository"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Run the action under test against the current fixture.
# 'editable' is left at its default (false) so the package
# executes from site-packages/ rather than from src/ - this
# is the install layout that originally exposed issue #138.
- name: "Run action against fixture: ${{ matrix.fixture }}"
uses: ./
with:
python_version: ${{ env.python_version }}
path_prefix: ".fixtures/${{ matrix.fixture }}"
tests_path: "tests"
# yamllint disable-line rule:line-length
artefact_name: "coverage-fixtures-${{ matrix.fixture }}-coverage"
report_artefact: "true"
github_token: ${{ secrets.GITHUB_TOKEN }}
# Assert the produced coverage.xml reports non-zero coverage.
# Tests passing on its own is not sufficient evidence that the
# action wired pytest-cov correctly: the issue #138 regression
# produced 'all tests passed, 0% coverage' which would slip
# past a tests-only assertion. Reading the XML directly avoids
# any dependency on the action's own log output format.
- name: "Assert non-zero coverage from fixture"
shell: bash
env:
PYTHON_VERSION: ${{ env.python_version }}
FIXTURE: ${{ matrix.fixture }}
run: |
# Assert non-zero coverage from fixture
set -euo pipefail
# The action stores the XML at /tmp/coverage-py<XY>/coverage.xml
# where <XY> is the python_version with dots stripped. Build
# the same path here rather than depending on action outputs
# so the assertion is decoupled from action implementation
# details.
py_xy="$(echo "${PYTHON_VERSION}" | tr -d '.')"
xml="/tmp/coverage-py${py_xy}/coverage.xml"
if [ ! -f "$xml" ]; then
echo "Error: coverage XML not found at $xml ❌"
ls -la /tmp/coverage-* 2>/dev/null || true
exit 1
fi
# Parse and validate the line-rate inside Python so the
# comparison is a real numeric check (float > 0). A shell
# 'case' on the raw string would only catch a fixed set of
# zero-spellings ('0', '0.0', '0.00', ...) and would let
# an unexpected formatting like '0.0000' or scientific
# notation slip through, defeating the purpose of the
# assertion. The fixtures are constructed so that 100% is
# achievable, but we only require strictly > 0 here so the
# assertion is not coupled to fixture details that may
# legitimately change.
python3 - "$xml" "$FIXTURE" <<'PY'
import sys
import xml.etree.ElementTree as ET
xml_path, fixture = sys.argv[1], sys.argv[2]
root = ET.parse(xml_path).getroot()
rate_str = root.get('line-rate')
if rate_str is None:
print(f'FAIL: line-rate attribute missing in {xml_path}',
file=sys.stderr)
sys.exit(1)
try:
rate = float(rate_str)
except ValueError:
print(
f'FAIL: line-rate {rate_str!r} not parseable as float',
file=sys.stderr)
sys.exit(1)
print(f'Fixture: {fixture}')
print(f'Coverage XML: {xml_path}')
print(f'line-rate: {rate_str} (parsed: {rate})')
if rate <= 0:
print(
f'FAIL: coverage line-rate is {rate} for {fixture} ❌',
file=sys.stderr)
print(
'This indicates the action did not configure '
'pytest-cov correctly for this project shape.',
file=sys.stderr)
print(
'See .fixtures/README.md and issue #138.',
file=sys.stderr)
sys.exit(1)
print(f'OK: {fixture} reported non-zero coverage ✅')
PY
### detect_coverage.py unit test suite ###
#
# The detection logic that decides whether to inject '--cov', what
# fallback package name to derive, and (since issue #138 follow-up)
# whether the consumer's omit list excludes the install location
# lives in 'scripts/detect_coverage.py'. The 'coverage-fixtures'
# job above exercises each shape end-to-end by running the entire
# action, but that path is expensive (per-fixture venv, real pytest,
# XML parse) and slow to iterate on. The unit-test job below runs
# the same script against the same fixtures plus a handful of
# synthetic edge cases in well under a second, catching detection
# regressions long before they could ship through the matrix job.
detect-coverage-unit-tests:
name: "detect_coverage.py unit tests"
runs-on: ubuntu-24.04
permissions:
contents: read
timeout-minutes: 5
steps:
# Harden the runner used by this workflow
# yamllint disable-line rule:line-length
- uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: 'audit'
- name: "Checkout repository"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Use the same uv-managed interpreter the action itself uses
# in production. Pinning to env.python_version keeps the unit
# tests on the same Python the rest of this workflow runs
# against; the script must work under 3.10-3.14 (and the
# tomllib/tomli fallback for the latter) but a single version
# is enough to catch logic regressions here - the matrix job
# is what guarantees runtime coverage across the version grid.
#
# 'activate-environment: true' tells setup-uv to create a
# venv at .venv/ under the workspace and prepend its bin/
# to PATH (also exporting VIRTUAL_ENV). We need that here
# because Ubuntu 24.04's system Python is PEP-668
# externally-managed, so 'uv pip install --system' refuses
# to install pytest. With the venv active, an unqualified
# 'uv pip install' targets the venv's interpreter and 'python
# -m pytest' resolves to the same one.
- name: "Set up uv"
# yamllint disable-line rule:line-length
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
python-version: ${{ env.python_version }}
activate-environment: true
enable-cache: true
- name: "Install pytest"
shell: bash
run: |
# Install pytest into the uv-activated venv so 'python
# -m pytest' below resolves to the same interpreter.
uv pip install pytest
- name: "Run detect_coverage.py unit tests"
shell: bash
run: |
# The tests live under tests/ at the repo root; the test
# module prepends scripts/ to sys.path so it can import
# detect_coverage directly. Run via 'python -m pytest' to
# keep import resolution consistent with how the action
# itself invokes the script.
python -m pytest tests/test_detect_coverage.py -v
### Coverage 'omit excludes install' warning fixture ###
#
# The 'omit-excludes-install' fixture is structurally different
# from the four cells in the coverage-fixtures matrix: it ships a
# config that the action cannot rescue (the broken omit list masks
# all measurement no matter what flags are passed), so the
# invariant being checked is the WARNING, not the coverage value.
#
# The action emits the warning to console AND to
# $GITHUB_STEP_SUMMARY, but $GITHUB_STEP_SUMMARY is a per-step
# file (each step gets a fresh path; the runner concatenates them
# at job-end into the published summary), so a follow-up
# assertion step cannot read what an earlier step wrote there.
# We therefore verify the warning channel via two surfaces that
# DO survive across steps:
#
# 1. The KEY=VALUE pairs the action wrote to $GITHUB_ENV in
# the 'Detect coverage configuration' step
# ('coverage_omit_excludes_install',
# 'coverage_problematic_omit_patterns'). If those are set
# correctly it proves the bash template reached the warning
# branch without crashing on a quoting accident.
# 2. The generated coverage.xml's 'line-rate' attribute, which
# must be exactly 0.0 for this fixture's pyproject.toml (it
# reproduces the bug). A future change that silently 'fixed'
# omit handling would also be caught here.
#
# The exact rendered markdown that goes into the step summary is
# covered by the detect_coverage.py unit tests above (which check
# the KEY=VALUE contract verbatim) and by manual inspection of
# the per-job summary published in the GitHub UI.
#
# We intentionally leave coverage's '--cov-fail-under' unset in the
# fixture's pyproject.toml so that 0% coverage does NOT fail the
# action's pytest step - otherwise we'd be testing the failure
# mode rather than the warning channel.
coverage-omit-warning:
name: "coverage-omit-warning fixture"
runs-on: ubuntu-24.04
permissions:
contents: read
timeout-minutes: 8
steps:
# Harden the runner used by this workflow
# yamllint disable-line rule:line-length
- uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: 'audit'
- name: "Checkout repository"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Run the action against the omit fixture. editable left at
# its default 'false' - that is the install layout that
# triggers the regression in real consumers, and the warning
# only fires for non-editable installs.
- name: "Run action against fixture: omit-excludes-install"
uses: ./
with:
python_version: ${{ env.python_version }}
path_prefix: ".fixtures/omit-excludes-install"
tests_path: "tests"
# yamllint disable-line rule:line-length
artefact_name: "coverage-omit-warning-coverage"
report_artefact: "true"
github_token: ${{ secrets.GITHUB_TOKEN }}
# Assert the detection step set the omit signals correctly.
# These env vars survive across steps because the action
# writes them via $GITHUB_ENV. Reaching the assertion below
# with the booleans flipped (or the patterns string empty)
# would mean either detect_coverage.py regressed or the
# action's bash failed to forward them - either is a hard
# fail.
- name: "Assert omit detection signals propagated to env"
shell: bash
run: |
# Verify env vars from the action's detection step
set -euo pipefail
echo "coverage_omit_excludes_install=" \
"${coverage_omit_excludes_install:-<unset>}"
echo "coverage_problematic_omit_patterns=" \
"${coverage_problematic_omit_patterns:-<unset>}"
if [ "${coverage_omit_excludes_install:-false}" != "true" ]; then
echo "Error: coverage_omit_excludes_install was not 'true' ❌" >&2
echo "The action's detect_coverage.py step did not flag" >&2
echo "the fixture's '*/.venv/*' omit entry." >&2
exit 1
fi
# The fixture sets exactly one offending pattern; assert
# both that the variable is non-empty and that the
# specific pattern is present. ';' is the documented
# separator (see scripts/detect_coverage.py main()).
if [ -z "${coverage_problematic_omit_patterns:-}" ]; then
echo "Error: coverage_problematic_omit_patterns is empty ❌" >&2
exit 1
fi
case ";${coverage_problematic_omit_patterns};" in
*";*/.venv/*;"*)
;;
*)
echo "Error: '*/.venv/*' missing from patterns ❌" >&2
echo "Got: ${coverage_problematic_omit_patterns}" >&2
exit 1
;;
esac
echo "OK: omit signals propagated correctly ✅"
# Reproducing the bug is the whole point of this fixture, so
# assert that the action did NOT manage to collect coverage.
# If a future change to the action somehow caused omit
# patterns to be ignored (or to be applied differently),
# this assertion would surface that before the warning
# channel stopped being relevant.
#
# When pytest-cov collects no data (which is exactly what
# the broken omit list causes), it skips XML emission
# entirely - the file simply does not exist. coverage.py
# 7.x prints 'No data was collected.' and pytest-cov
# follows up with 'Failed to generate report: No data to
# report.', then writes nothing. So 'no XML at all' is the
# primary symptom of the bug. We also accept the secondary
# case where some pytest-cov / coverage.py future combination
# might write an XML with line-rate=0 instead of skipping it,
# so the assertion stays robust if that behaviour changes.
- name: "Assert fixture reproduces the 0% coverage failure"
shell: bash
env:
PYTHON_VERSION: ${{ env.python_version }}
run: |
# Confirm coverage was not collected - the bug is reproduced
set -euo pipefail
py_xy="$(echo "${PYTHON_VERSION}" | tr -d '.')"
xml="/tmp/coverage-py${py_xy}/coverage.xml"
if [ ! -f "$xml" ]; then
echo "OK: bug reproduced - no coverage XML written ✅"
echo "(pytest-cov collected no data; the omit glob" \
"masked everything, so nothing to report)."
exit 0
fi
# XML exists - belt-and-braces check that line-rate is
# zero. Any value > 0 means coverage actually got
# measured, which would mean the omit list is no longer
# excluding the install location and the fixture has
# stopped exercising the regression.
python3 - "$xml" <<'PY'
import sys
import xml.etree.ElementTree as ET
xml_path = sys.argv[1]
root = ET.parse(xml_path).getroot()
rate = float(root.get('line-rate') or '0')
print(f'line-rate: {rate}')
if rate != 0.0:
print(
f'FAIL: expected zero coverage to reproduce the '
f'omit-excludes-install bug, got {rate} ❌',
file=sys.stderr)
print(
'The omit pattern no longer excludes the install '
'location; either coverage.py changed pattern '
'matching, or the fixture needs updating.',
file=sys.stderr)
sys.exit(1)
print('OK: bug reproduced (XML exists with line-rate=0) ✅')
PY
### Real-world regression matrix: python-nss-ng ###
#
# Runs the action against the full python-nss-ng repository across
# both x64 and arm64 runners and the full supported Python matrix
# (3.10 - 3.14). python-nss-ng is one of the more complex Python
# projects we exercise: it ships C extensions linking against
# system NSS/NSPR libraries, builds via meson-python, and historically
# surfaced two distinct classes of bug in this action that simpler
# fixtures missed:
#
# 1. pip -> uv interpreter mismatch on real packages with
# [test,dev] extras + meson-python build
# (uv 0.11.x mis-detected the interpreter's patch version,
# installing pytest into a site-packages path the running
# python could not find).
# 2. setup-python vs setup-uv patch-version disagreement on
# Ubuntu noble + Python 3.12, producing a venv whose binary
# advertised one patch level while loading stdlib from another;
# this crashed CPython inside LogRecord.__init__.
#
# python-nss-ng is pinned to a known-good commit so that any
# regression detected here is attributable to changes in this
# action rather than upstream churn. Bump the pin deliberately
# when validating against a newer python-nss-ng baseline.
#
# This job runs in parallel to the lightweight 'standard-tests' job
# above and is intentionally heavier (full Python matrix, native
# build, apt-installed NSS dependencies). It is not a replacement.
regression-tests:
# yamllint disable-line rule:line-length
name: "python-nss-ng (${{ matrix.runs-on }} / Py${{ matrix.python-version }})"
runs-on: ${{ matrix.runs-on }}
strategy:
fail-fast: false
matrix:
runs-on:
- ubuntu-24.04
- ubuntu-24.04-arm
python-version:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- "3.14"
permissions:
contents: read
timeout-minutes: 25
env:
# Pinned python-nss-ng commit. Update this when validating
# against a newer baseline of the downstream project.
NSS_NG_REPO: "ModeSevenIndustrialSolutions/python-nss-ng"
NSS_NG_REF: "01543d576ed845b22edc0369182526e64e078331"
steps:
# Harden the runner used by this workflow
# yamllint disable-line rule:line-length
- uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: 'audit'
- name: "Checkout repository (action under test)"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
path: "action"
persist-credentials: false
- name: "Checkout python-nss-ng at pinned commit"
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
repository: "${{ env.NSS_NG_REPO }}"
ref: "${{ env.NSS_NG_REF }}"
path: "python-nss-ng"
persist-credentials: false
- name: "Install NSS/NSPR system dependencies"
working-directory: "python-nss-ng"
shell: bash
run: |
# Install apt-level NSS/NSPR build deps via the project's
# own Makefile target. Python-level deps are deliberately
# left to the action under test.
make deps-nss deps-test-system env-github-actions
# Refresh ldconfig cache so libnss/libnspr are discoverable
sudo ldconfig
# Export multi-arch library path for downstream steps.
LIB_PATH="/usr/lib"
LIB_PATH="${LIB_PATH}:/usr/lib/x86_64-linux-gnu"
LIB_PATH="${LIB_PATH}:/usr/lib/aarch64-linux-gnu"
echo "LD_LIBRARY_PATH=${LIB_PATH}" >> "$GITHUB_ENV"
- name: "Verify NSS libraries are discoverable"
shell: bash
run: |
# Verify NSS libraries are discoverable
echo "=== Library Path Diagnostics ==="
echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}"
echo ""
echo "=== ldconfig cache (NSS / NSPR) ==="
ldconfig -p | grep -E '(nss|nspr)' || \
echo "NSS/NSPR not in ldconfig cache ⚠️"
echo ""
echo "=== pkg-config ==="
pkg-config --exists nss && \
echo "NSS: $(pkg-config --modversion nss) ✅" || \
echo "NSS not found by pkg-config ⚠️"
pkg-config --exists nspr && \
echo "NSPR: $(pkg-config --modversion nspr) ✅" || \
echo "NSPR not found by pkg-config ⚠️"
# Build python-nss-ng's meson tree before running pytest.
#
# python-nss-ng's test suite contains bare imports such as
# 'from deprecations import ...' / 'from nss_context import ...'
# / 'from secure_logging import ...' at module scope (e.g. in
# test/test_deprecations.py, test/test_secure_logging.py,
# test/test_zzz_nss_context.py). Those modules live at
# src/{deprecations,nss_context,secure_logging}.py and meson
# installs them under the 'nss' subpackage at install time, so
# they are NOT importable as top-level names from a plain
# 'uv pip install' of the project.
#
# The project's own conftest.py works around this by inserting
# the meson 'build/lib.<platform>-<X.Y>' directory into sys.path
# via util.get_build_dir() - but that directory only exists if
# an in-tree meson build has been performed. Without it, pytest
# collection fails with ModuleNotFoundError on those three test
# modules.
#
# python-nss-ng's own CI runs lfreleng-actions/python-build-action
# before invoking python-test-action, which produces the build
# tree as a side effect. Mirror that here so the regression job
# exercises the same end-to-end shape consumers actually use.
- name: "Build python-nss-ng (meson) Py${{ matrix.python-version }}"
# yamllint disable-line rule:line-length
uses: lfreleng-actions/python-build-action@97cf36d2a5914c94761408545d154c983024de1d # v2.0.0
with:
path_prefix: "python-nss-ng"
python_version: "${{ matrix.python-version }}"
# Build via the project's Makefile so meson is invoked the
# same way python-nss-ng's own CI invokes it. NSS apt deps
# are installed in the previous step, but re-running the
# idempotent target here keeps env-github-actions in sync.
make: "true"
make_args: "deps-nss env-github-actions"
# The action under test handles its own coverage/test
# artefact uploads; the build artefact is not needed here.
# yamllint disable-line rule:line-length
artefact_name: "regression-nss-ng-build-${{ matrix.runs-on }}-py${{ matrix.python-version }}"
- name: "Run action: python-nss-ng Py${{ matrix.python-version }}"
# Use the local checkout of the action under test rather than
# 'uses: ./' at workflow root, since the workflow root holds
# python-nss-ng's checkout in this job.
#
# PYTHONPATH note: python-nss-ng/test/{test_deprecations,
# test_secure_logging,test_zzz_nss_context}.py contain bare
# top-level imports ('from deprecations import ...' etc.)
# whose targets live at python-nss-ng/src/*.py. The project's
# own conftest.py works around this by walking up from CWD
# looking for 'meson.build' + 'build/' and inserting the
# discovered build dir into sys.path. That walk only succeeds
# when CWD == project root; in this regression job CWD is
# $GITHUB_WORKSPACE and meson.build lives one level below in
# python-nss-ng/, so the walk never finds it.
#
# Setting PYTHONPATH to python-nss-ng/src directly makes the
# bare imports resolve from source. We deliberately point at
# 'src' rather than at the meson build dir because 'src' is
# the canonical location and is always present after checkout.
#
uses: ./action
env:
PYTHONPATH: "${{ github.workspace }}/python-nss-ng/src"
with:
python_version: "${{ matrix.python-version }}"
path_prefix: "python-nss-ng"
tests_path: "test"
# yamllint disable-line rule:line-length
artefact_name: "regression-nss-ng-${{ matrix.runs-on }}-py${{ matrix.python-version }}-coverage"
report_artefact: "true"
github_token: "${{ secrets.GITHUB_TOKEN }}"
- name: "Post-test diagnostics (on failure)"
if: failure()
shell: bash
run: |
# Post-test diagnostics
# Surface enough state to triage interpreter / install /
# link-time issues without rerunning the job manually.
echo "=== POST-TEST DIAGNOSTICS ==="
echo "Active VIRTUAL_ENV: ${VIRTUAL_ENV:-<unset>}"
echo "python on PATH: $(command -v python || true)"
if command -v python >/dev/null 2>&1; then
python - <<'PY' || true
import sys
print(f"sys.executable = {sys.executable}")
print(f"sys.version = {sys.version}")
print(f"sys.prefix = {sys.prefix}")
print(f"sys.base_prefix = {sys.base_prefix}")
PY
fi
# The action provisions/activates the venv at workspace root
# (.venv), not under path_prefix. Resolve the actual venv via
# $VIRTUAL_ENV so this diagnostic reports useful state
# regardless of where the action created it.
pyvenv_cfg="${VIRTUAL_ENV:+$VIRTUAL_ENV/pyvenv.cfg}"
if [ -z "$pyvenv_cfg" ]; then
pyvenv_cfg=".venv/pyvenv.cfg"
fi
if [ -f "$pyvenv_cfg" ]; then
echo "---- $pyvenv_cfg ----"
cat "$pyvenv_cfg"
echo "----------------------------------------"
else
echo "No pyvenv.cfg found at $pyvenv_cfg"
fi
echo ""
echo "=== Installed nss/python-nss-ng artefacts ==="
# The action does not install pip into the venv (uv replaces
# it), so 'python -m pip' is not always available. Detect
# availability first so a missing-pip case produces a
# meaningful message rather than masking as a missing package.
if command -v python >/dev/null 2>&1; then
if python -m pip --version >/dev/null 2>&1; then
python -m pip show python-nss-ng || \
echo "python-nss-ng not visible to pip"
else
echo "pip is not available; falling back to importlib"
python - <<'PY' || true
import importlib.metadata as md
try:
dist = md.distribution("python-nss-ng")
print(f"python-nss-ng {dist.version} at {dist.locate_file('')}")
except md.PackageNotFoundError:
print("python-nss-ng not visible via importlib.metadata")
PY
fi
fi
echo ""
echo "=== ldd of any built nss .so files ==="
find python-nss-ng -name "*.so" 2>/dev/null | while read -r so; do
echo "--- $so ---"
ldd "$so" 2>&1 | grep -E "(nss|nspr|not found)" || true
done