Skip to content

chore(deps): bump the actions group across 1 directory with 6 updates #683

chore(deps): bump the actions group across 1 directory with 6 updates

chore(deps): bump the actions group across 1 directory with 6 updates #683

Workflow file for this run

# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: Python Package
on:
# `push` restricted to `main` so feature branches only run via `pull_request`.
# Otherwise every push to a branch with an open PR would trigger the workflow
# twice (once for `push`, once for `pull_request`) — doubling CI cost and
# latency for no benefit.
push:
branches: [main]
pull_request:
# Allow re-running the workflow without an empty push (handy after the
# workflow gets `disabled_inactivity` after 60 days idle).
workflow_dispatch:
schedule:
- cron: '0 0 */7 * *'
# Cancel an in-flight run when a newer commit lands on the same ref. Keeps the
# queue lean and stops stale runs from blocking the merge button.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Install lint deps
run: |
python -m pip install --upgrade pip
pip install flake8
- name: Lint
run: |
flake8 PyMemoryEditor tests
type-check:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Install dev deps
# Include the `speed` extra (NumPy) so mypy type-checks the NumPy fast
# path against real stubs. Without it, `import numpy` resolves to `Any`
# and the `# type: ignore` in scan_numpy.py reads as unused.
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,speed]"
- name: Run mypy
run: |
mypy PyMemoryEditor
build:
needs: lint
runs-on: ${{ matrix.os }}
# Hard ceiling, same purpose as build-macos'. A cell takes ~4 minutes
# (measured 2026-10-08: 2m30s to 4m25s); without this the default is 6h.
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13']
os:
- ubuntu-latest
- windows-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
- name: Install Qt system libraries (Linux)
# PySide6 links against libEGL/libGL/libxkbcommon/libfontconfig and the
# XCB stack at import time, even when running under the `offscreen`
# platform plugin. The Ubuntu runner ships without them, so pytest-qt's
# `import QtGui` crashes with `libEGL.so.1: cannot open shared object`.
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libegl1 \
libgl1 \
libxkbcommon0 \
libfontconfig1 \
libdbus-1-3 \
libxcb-cursor0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-shape0 \
libxcb-sync1 \
libxcb-xfixes0 \
libxcb-xinerama0 \
libxcb-xkb1 \
libxkbcommon-x11-0
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Test with pytest
env:
QT_QPA_PLATFORM: offscreen
# `--ignore=tests/mcp_server`: the MCP server has its own job
# (`build-mcp`) so this matrix keeps measuring the library and the app,
# at the wall clock it always had. Its tests walk a live process's whole
# address space, which on macOS is ~390 GiB (see build-macos below) --
# running them in every cell of a 4x2 matrix plus macOS plus speed paid
# that cost eight times over, and it pushed build-macos past its
# 20-minute budget.
#
# --maxfail=10 rather than -x: one broken test should not hide the next
# nine, but a wholesale breakage (a backend that fails to import) still
# stops early instead of printing 600 tracebacks.
#
# --cov-fail-under is the real, in-repo coverage gate (deterministic and
# independent of any external service), and `--cov-config` is what keeps
# it meaningful now that the MCP suite lives in `build-mcp`.
#
# Without it this job measures PyMemoryEditor/mcp -- 918 statements it
# never runs -- as uncovered, which is a measurement of absence, not of
# quality. It also varies by platform in a way the gate cannot absorb:
# measured that way, Linux came out at 46% and macOS at 59%, so a floor
# picked from macOS alone failed all eight matrix cells. Scoped to the
# library, the numbers are 87% on macOS and 66% on Linux. The floor is
# 62: four points under the lowest cell (Linux), so it trips on a real
# regression rather than on platform spread. (It was 60 from before the
# MCP server existed, when nothing had been measured.)
#
# (coverage.py only counts the host backend + shared code -- the two
# foreign backends aren't imported, which is most of that platform
# spread.) Ratchet up once the numbers settle on Codecov.
run: |
pytest tests --ignore=tests/mcp_server -v -s --maxfail=10 --cov=PyMemoryEditor --cov-config=.coveragerc-lib --cov-report=term --cov-report=xml --cov-fail-under=62
- name: Upload coverage to Codecov
# Upload from every matrix cell so Codecov merges each OS's backend
# coverage into one combined view (each platform exercises a different
# win32/linux/macos backend). Informational only — see codecov.yml — so a
# flaky upload never blocks the merge; the hard gate is --cov-fail-under
# above. Runs even when tests fail so partial coverage stays visible.
if: always()
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
files: ./coverage.xml
flags: ${{ runner.os }}-py${{ matrix.python-version }}
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
# macOS gets a single cell instead of a row in the matrix above. Its only
# purpose is to measure the `macos` backend so the merged Codecov report
# covers all three platforms; the Python-version sweep stays on Linux and
# Windows, where it costs a fraction of the wall clock.
#
# Unlike the matrix, this job installs `.[dev,speed]` (NumPy). That is not a
# preference — it is what makes a macOS cell viable at all. A macOS process
# exposes ~390 GiB of sparse address space across ~145 regions, two orders of
# magnitude more than a Linux one, and the scan tests walk all of it. Measured
# on an M-series Mac, this exact command takes ~82s with NumPy and 681s
# without: an 8x gap that a 3-core runner only widens, which is what made
# macOS look like it hung here before (it was removed in 041bfc0).
# The pure-Python scan path loses nothing — all eight cells of the matrix
# above still exercise it.
build-macos:
needs: lint
runs-on: macos-latest
# Hard ceiling so a pathological scan can never sit at the 6h default.
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
# No Qt system-library step here: the PySide6 macOS wheel bundles its own
# Qt frameworks, unlike the Linux one.
- name: Install dependencies (with NumPy speed extra)
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,speed]"
- name: Test with pytest
env:
QT_QPA_PLATFORM: offscreen
# Same conservative coverage gate as the main matrix (see that step's note).
run: |
pytest tests --ignore=tests/mcp_server -v -s --maxfail=10 --cov=PyMemoryEditor --cov-config=.coveragerc-lib --cov-report=term --cov-report=xml --cov-fail-under=62
- name: Upload coverage to Codecov
# Flag mirrors the matrix format (`runner.os` is "macOS" here) so Codecov
# groups this cell alongside the Linux and Windows uploads.
if: always()
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
files: ./coverage.xml
flags: macOS-py3.12
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
# Isolated job for the optional NumPy-accelerated scan fast path (the
# `[speed]` extra). The matrix above deliberately runs WITHOUT NumPy so the
# pure-Python scan path stays covered; this single Linux job installs
# `.[speed]` on top of the dev deps to exercise the vectorized path.
build-speed:
needs: lint
runs-on: ubuntu-latest
# ~3 minutes measured; see the `build` note.
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Install Qt system libraries (Linux)
# PySide6 links against libEGL/libGL/libxkbcommon/libfontconfig and the
# XCB stack at import time, even when running under the `offscreen`
# platform plugin. The Ubuntu runner ships without them, so pytest-qt's
# `import QtGui` crashes with `libEGL.so.1: cannot open shared object`.
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
libegl1 \
libgl1 \
libxkbcommon0 \
libfontconfig1 \
libdbus-1-3 \
libxcb-cursor0 \
libxcb-icccm4 \
libxcb-image0 \
libxcb-keysyms1 \
libxcb-randr0 \
libxcb-render-util0 \
libxcb-shape0 \
libxcb-sync1 \
libxcb-xfixes0 \
libxcb-xinerama0 \
libxcb-xkb1 \
libxkbcommon-x11-0
- name: Install dependencies (with NumPy speed extra)
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,speed]"
- name: Test with pytest (NumPy fast path)
env:
QT_QPA_PLATFORM: offscreen
# Same conservative coverage gate as the main matrix (see that step's note).
run: |
pytest tests --ignore=tests/mcp_server -v -s --maxfail=10 --cov=PyMemoryEditor --cov-config=.coveragerc-lib --cov-report=term --cov-report=xml --cov-fail-under=62
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
files: ./coverage.xml
flags: speed-Linux-py3.12
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
# The MCP server gets its own job rather than a row in the matrix above.
#
# Its tests are the expensive ones: `test_live.py` and half of
# `test_fake_fidelity.py` attach to the running test process and scan its
# entire address space, which is exactly the workload the build-macos note
# below measures at ~390 GiB. Running them in all ten cells of the other
# jobs multiplied that by ten and timed macOS out; running them once per OS
# keeps the platform coverage that actually matters and costs a fraction.
#
# One Python version, three operating systems -- the opposite shape from the
# matrix above, and deliberately so. Nothing in this package is
# version-sensitive (it is plain 3.10+ typing over the library's own API),
# but plenty of it is platform-sensitive: the Windows-only read-only handle,
# macOS `task_for_pid`, and the per-platform process enumeration and denylist.
# The one Windows-only bug this suite has caught so far was a test reading a
# UTF-8 file with the locale's encoding, which only cp1252 could fail on.
#
# NumPy is installed for the same reason build-macos installs it: without the
# vectorized scan path, walking a macOS address space takes ~8x longer.
build-mcp:
needs: lint
runs-on: ${{ matrix.os }}
# Hard ceiling, same purpose as build-macos'. The suite itself takes
# ~15s of pytest on all three runners; this is generous on purpose.
#
# It was briefly 45 while this job took 20+ minutes everywhere. The cause
# was not scan cost: one test parametrized a 200 000-character value, so
# its pytest id was 400 KB, and the Actions log uploader spent ~150s per
# such line. Five lines, twenty minutes. Fixed at the test.
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
# `scan_heavy` -- the tests that scan a *live* process's whole address
# space -- runs on Linux only. The cost is wildly platform-dependent: a
# macOS process exposes ~390 GiB across ~145 regions and Windows is not
# much better, so on a runner each of those scans is minutes. Measured
# on the previous attempt: this job took 22m21s on macOS and hit its
# timeout at 30m on Windows, while the same suite is 18s locally.
#
# Nothing is lost by pinning them to Linux. What the other two OSes are
# here for is the platform-specific code -- the Windows-only read-only
# handle, macOS task_for_pid, per-platform enumeration and the
# truncated-name denylist -- none of which are scans. The library's own
# live-scan coverage on those platforms comes from `build` and
# `build-macos`.
#
# Set through PYTEST_ADDOPTS rather than the run: line because the
# Windows runner's default shell is pwsh, and `-m "not scan_heavy"`
# would need different quoting there than on bash.
include:
- os: ubuntu-latest
addopts: ''
- os: windows-latest
addopts: '-m "not scan_heavy"'
- os: macos-latest
addopts: '-m "not scan_heavy"'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Install dependencies (MCP SDK + NumPy speed extra)
run: |
python -m pip install --upgrade pip
pip install -e ".[dev,speed]"
- name: Test the MCP server
# Coverage is scoped to the package under test, via both --cov and
# .coveragerc-mcp. Measured against the whole package instead, this gate
# came out at 71% on macOS but 60.4% on Linux -- four tenths of a point
# above a floor of 60, on a number mostly about code this job does not
# target. Scoped, the MCP package is 91% covered by its own suite on
# macOS and 90.4% on Linux, so 85 leaves five points of headroom.
env:
PYTEST_ADDOPTS: ${{ matrix.addopts }}
# `-q --durations=25` rather than the `-v -s` the library jobs use. This
# job's timing is the open question -- 20 minutes on a runner against
# 12.5s in a container, and cutting the process-table walks 14x changed
# nothing -- and `-v -s` emitted 977 log lines in the first 8 seconds,
# so GitHub truncated the log long before the part that would say which
# tests were slow. Quiet output keeps the log small enough that the
# duration table at the end survives.
#
# -p no:pytest-qt: the dev extra brings pytest-qt, whose plugin imports
# QtGui at startup. The name is the entry point's, hyphen included --
# `-p no:pytestqt` silently does nothing, which is how this failed twice.
# It dies with `ImportError: libEGL.so.1` on a bare Linux runner; the
# library jobs apt-install a pile of X libraries for it. This job runs no
# Qt tests, so it disables the plugin instead of paying for them.
run: |
pytest tests/mcp_server -p no:pytest-qt -q --durations=25 --maxfail=10 --cov=PyMemoryEditor/mcp --cov-config=.coveragerc-mcp --cov-report=term --cov-report=xml --cov-fail-under=85
- name: Upload coverage to Codecov
# Its own flag, so the merged report shows MCP coverage alongside the
# library's instead of one overwriting the other.
if: always()
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
with:
files: ./coverage.xml
flags: mcp-${{ runner.os }}-py3.12
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
# Build the Sphinx docs with warnings as errors, so a broken cross-reference,
# a missing toctree entry or an autodoc import failure fails the PR instead
# of surfacing later on Read the Docs. Read the Docs keeps
# `fail_on_warning: false` (.readthedocs.yaml) so a warning that only its
# environment produces never stops the published docs from updating; this
# job is the gate.
docs:
needs: lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
- name: Install docs dependencies
run: |
python -m pip install --upgrade pip
pip install -r docs/requirements.txt
pip install -e .
- name: Build HTML docs (warnings are errors)
run: |
python -m sphinx -W --keep-going -b html docs docs/_build/html
# Dependency audit. The core library has no dependencies; this checks the
# extras (Qt app, MCP SDK, NumPy) and their transitive closure against the
# PyPI advisory database. Advisory, not a gate: `continue-on-error` keeps a
# new upstream CVE from blocking an unrelated PR, and the weekly `schedule`
# run surfaces it on main regardless. Dependabot security PRs remain the
# fix path; this job is the visibility the disabled version-update PRs
# don't provide (see .github/dependabot.yml).
audit:
needs: lint
runs-on: ubuntu-latest
timeout-minutes: 10
continue-on-error: true
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
- name: Install the package with every runtime extra
run: |
python -m pip install --upgrade pip
pip install ".[app,mcp,speed]" pip-audit
- name: pip-audit
run: |
pip-audit --progress-spinner off