Skip to content

fix(deps): update all major dependencies (major) #3563

fix(deps): update all major dependencies (major)

fix(deps): update all major dependencies (major) #3563

Workflow file for this run

# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
# Every pull request, not only those targeting main. A stacked pull request
# is based on the branch below it, so filtering by base branch skipped CI
# entirely for the stacks this repository's workflow produces.
pull_request:
jobs:
lint-and-check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Set up uv
uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
enable-cache: true
- name: Install dependencies
run: |
uv sync
uv sync --group dev
- name: Run pre-commit hooks
uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
- name: Optimize uv cache for CI
run: uv cache prune --ci
dependency-review:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Review dependency changes
uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
fail-on-severity: high
fail-on-scopes: runtime, development, unknown
retry-on-snapshot-warnings: true
retry-on-snapshot-warnings-timeout: 120
# The action reports snapshot warnings after its retry window but does not
# fail on them. That can make a missing head snapshot look like an empty
# dependency diff, so query the same comparison and reject any warning.
- name: Require complete dependency snapshots
env:
GH_TOKEN: ${{ github.token }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
response="$RUNNER_TEMP/dependency-comparison"
gh api --include \
"repos/${GITHUB_REPOSITORY}/dependency-graph/compare/${BASE_SHA}...${HEAD_SHA}?per_page=100" \
> "$response"
python3 - "$response" <<'PY'
import sys
from pathlib import Path
header = "x-github-dependency-graph-snapshot-warnings:"
for line in Path(sys.argv[1]).read_text(encoding="utf-8").splitlines():
if not line:
break
if line.lower().startswith(header) and line[len(header) :].strip():
raise SystemExit("Dependency comparison returned snapshot warnings")
PY
windows-daemon:
name: Windows daemon (Python ${{ matrix.python-version }})
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
# 3.12.4 exactly, because it is the declared floor and the one version
# where private state creation on Windows became possible at all. The
# others follow their newest patch, so nothing else here would notice a
# daemon path reaching for something added after it.
python-version: ["3.12.4", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
enable-cache: true
python-version: ${{ matrix.python-version }}
- run: uv sync --group dev
- name: Run daemon protocol and containment tests
run: >-
uv run pytest
tests/test_daemon_election.py::TestAtomicStartupCommit
tests/test_daemon_election.py::TestPublishingLast
tests/test_daemon_election.py::TestRealOwner::test_an_owner_is_started_and_answers
tests/test_daemon_election.py::TestRealOwner::test_a_crashed_owner_is_replaced
tests/test_daemon_election.py::TestRealOwner::test_many_clients_starting_at_once_elect_exactly_one_owner
tests/test_daemon_election.py::TestRealOwner::test_launch_barrier_timeout_exits_without_electing
tests/test_daemon_election.py::TestRealOwner::test_launch_barrier_failure_collects_started_frontends
tests/test_bootstrap.py::TestInstallerSupervisorLaunch::test_real_cpython_wait_unregister_contract
tests/test_daemon_descriptor.py::TestPreparedCommit
tests/test_daemon_descriptor.py::TestStateLocation
tests/test_daemon_lock.py
tests/test_installer_supervisor.py
tests/test_installer_worker.py
tests/test_process_tree.py
tests/test_private_state.py
- run: uv cache prune --ci
# Branch protection on `main` requires `lint-and-check` and `test` only, and
# that list is a repository setting no workflow file can edit. So this job is
# where a contract that has to block a merge gets attached: it fails unless
# every job it names succeeded, which makes those jobs required without
# renaming anything. `if: always()` so a failure upstream still reaches the
# check below rather than skipping it, because a skipped required check is a
# green one.
test:
needs: [dependency-review, windows-daemon, platform-behaviour]
if: always()
runs-on: ubuntu-latest
steps:
# The review job is intentionally skipped for pushes to main. On pull
# requests it joins this existing required check instead of adding a new
# branch-protection context.
- name: Require dependency review on pull requests
if: github.event_name == 'pull_request'
run: test '${{ needs.dependency-review.result }}' = success
- name: Require the Windows daemon tests
run: test '${{ needs.windows-daemon.result }}' = success
# Real browser containment on Windows and macOS runs nowhere else: the
# POSIX environment marker and the Windows Job a Chromium joins are
# facts only those platforms can confirm, and the lease and owner-only
# storage are a different implementation on each. Reading the result of
# the whole matrix, so one red leg fails this.
- name: Require the platform behaviour tests
run: test '${{ needs.platform-behaviour.result }}' = success
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
enable-cache: true
- run: uv sync --group dev
# The browser is keyed on the resolved patchright version, because that
# is what decides which Chromium revision `install` fetches. Read from
# the synced environment rather than from `pyproject.toml`, which names
# a floor and not the version in use.
- name: Resolve the patchright version
id: patchright
run: |
version=$(uv run python -c 'from importlib.metadata import version; print(version("patchright"))')
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Restore the browser
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: ms-playwright-${{ runner.os }}-${{ steps.patchright.outputs.version }}
# `--no-shell`, the same install the server performs: the headless shell
# is a different product with no plugins and no `window.chrome`, and
# nothing here launches it. `--with-deps` runs on a cache hit too, since
# the cache holds the browser and not the apt packages it links against;
# it also brings Xvfb, which is in playwright's `tools` dependency group.
- name: Install the browser
run: uv run patchright install --with-deps chromium --no-shell
# A headed browser needs a display. The default mode does not on Linux,
# where the hidden target is unsupported and Chromium's own headless mode
# runs instead, but the identity gate launches headed as well and that is
# the mode where the headless token has to be absent.
- name: Start a display
run: |
Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp >/tmp/xvfb.log 2>&1 &
for _ in $(seq 1 100); do
if [ -S /tmp/.X11-unix/X99 ]; then exit 0; fi
sleep 0.1
done
echo "Xvfb never created /tmp/.X11-unix/X99:" >&2
cat /tmp/xvfb.log >&2
exit 1
# `--dist loadgroup` keeps every test that launches Chromium on one
# worker. The identity cases share two cached launches; the DOM and auth
# contracts launch per case, and the DOM timers use wall-clock deadlines
# that concurrent browser startups made flaky. Tests without the group
# mark are distributed exactly as before.
- name: Run tests
env:
DISPLAY: ":99"
run: uv run pytest --cov --cov-report=term-missing -n auto --dist loadgroup -v -s
- run: uv cache prune --ci
# Everything here coordinates or protects state through the operating system
# rather than through Python, so each platform runs a different
# implementation: the lease locks with flock or with LockFileEx, and
# owner-only storage is a permission bit or an access control list. The
# Ubuntu job above executes neither Windows path. Serial by choice: the lease
# tests spawn their own competing processes, and xdist would only add
# scheduling noise on top of that.
platform-behaviour:
name: Platform behaviour (${{ matrix.os }})
runs-on: ${{ matrix.os }}
# Long enough for a cold browser download and far short of the six hours
# a job without one is allowed to sit. These tests spawn real processes
# and wait on them, so a contract that stops holding turns into a wedged
# job rather than a red one, and a wedged job reads as work in progress
# for as long as anyone is willing to wait.
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
include:
- os: macos-latest
browsers: ~/Library/Caches/ms-playwright
- os: windows-latest
browsers: ~\AppData\Local\ms-playwright
# Here for the browser rather than for the operating system.
# Patchright ships Chrome for Testing everywhere except Linux arm64,
# which gets Playwright's own Chromium build, and the release
# publishes an arm64 image. Without this leg an identity regression
# in that build reaches the container with both other jobs green.
- os: ubuntu-24.04-arm
browsers: ~/.cache/ms-playwright
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
with:
enable-cache: true
- run: uv sync --group dev
# ``faulthandler_timeout`` rather than a plugin: it is pytest's own, and
# what it buys is the stack of every thread at the moment a test stops
# making progress. These tests wait on processes they started, so the
# difference between a slow one and a stuck one is not visible from the
# outside and the report is the only way to tell them apart.
- name: Run platform behaviour tests
run: >-
uv run pytest -v -o faulthandler_timeout=120
tests/test_profile_lease.py
tests/test_profile_lease_integration.py
tests/test_private_state.py
tests/test_daemon_lock.py
tests/test_process_tree.py
tests/test_greenlet_runtime.py
# The identity gate runs on every leg, because every leg's browser is a
# different one from the Ubuntu job's. macOS is the only platform with a
# hidden target, which is the whole reason the default mode can run
# without announcing itself as headless. Linux arm64 is the only platform
# whose bundle is Playwright's own Chromium rather than Chrome for
# Testing. Windows has its own binary, its own window manager and its own
# DPI handling, and the gate measures a headed window against the screen
# it stands on -- which is exactly where those differ. An earlier version
# of this file left Windows out on the grounds that it falls back to
# headless mode like Linux does; that is true of one case in that file
# and says nothing about the rest of them.
#
# Every leg blocks a merge, through `test` rather than by name. Branch
# protection requires `lint-and-check` and `test`, and `test` fails
# unless this whole matrix succeeded, so the required list stays as it is
# while a red leg here stops the merge. Before that it did not: this job
# held the only real browser containment run on Windows and macOS and
# nothing depended on it, so those contracts were advisory.
#
# `shell: bash` because one of the legs is Windows, where the default is
# PowerShell and none of the syntax below means anything.
- name: Resolve the patchright version
id: patchright
shell: bash
run: |
version=$(uv run python -c 'from importlib.metadata import version; print(version("patchright"))')
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Restore the browser
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ matrix.browsers }}
key: ms-playwright-${{ matrix.os }}-${{ steps.patchright.outputs.version }}
# `--with-deps` on Linux only. It is the platform where a missing
# system library stops Chromium starting at all, and the runner image
# makes no promise about them. macOS needs none, and on Windows the
# switch installs a Media Pack through a script that wants privileges
# this job has no reason to take.
- name: Install the browser
run: uv run patchright install ${{ runner.os == 'Linux' && '--with-deps' || '' }} chromium --no-shell
# Same reason as the Ubuntu job: the headed half of the gate needs
# somewhere to put a window, and the default half does not.
- name: Start a display
if: runner.os == 'Linux'
run: |
Xvfb :99 -screen 0 1920x1080x24 -nolisten tcp >/tmp/xvfb.log 2>&1 &
for _ in $(seq 1 100); do
if [ -S /tmp/.X11-unix/X99 ]; then exit 0; fi
sleep 0.1
done
echo "Xvfb never created /tmp/.X11-unix/X99:" >&2
cat /tmp/xvfb.log >&2
exit 1
# Separate from the process tests above, which run before a browser
# exists and would skip this. It belongs on every leg because the
# attribution differs per platform: POSIX scans for an environment
# marker the browser carries, while Windows has no such marker and
# depends on Chromium joining the Job its Node driver was assigned to
# -- a fact about Windows that only Windows can confirm.
- name: Run the browser containment test
run: uv run pytest tests/test_browser_containment.py
- name: Run the identity gate
env:
# Only where one was started. macOS ignores it, but naming a display
# that does not exist is the kind of thing someone later debugs.
DISPLAY: ${{ runner.os == 'Linux' && ':99' || '' }}
run: uv run pytest tests/test_browser_identity.py
- run: uv cache prune --ci