From 9cdd8255ce86f2a62d77f93ed24331c04a6bcb81 Mon Sep 17 00:00:00 2001 From: Casper Nielsen Date: Fri, 25 Sep 2026 12:47:48 +0200 Subject: [PATCH 1/2] ci: stop a hung unit-test run from holding the runner for 6 hours Give the unit-test step a 20-minute limit, and have pytest's faulthandler dump every thread's stack when one test runs for more than 5 minutes, so the log shows where it hung. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Casper Nielsen --- .github/workflows/build-tag.yaml | 6 +++++- .github/workflows/build.yaml | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-tag.yaml b/.github/workflows/build-tag.yaml index 66b052f3e..0f72f80c3 100644 --- a/.github/workflows/build-tag.yaml +++ b/.github/workflows/build-tag.yaml @@ -70,8 +70,12 @@ jobs: - name: Check Typing run: uv run mypy - name: Run unit-tests + # The suite takes a few minutes. Without a limit, a hung test holds the runner for + # GitHub's 6-hour default; faulthandler dumps every thread's stack after 5 minutes + # in one test, so the log shows where it hung. + timeout-minutes: 20 run: | - uv run coverage run -m pytest tests -m "not e2e" --ignore=tests/integration --ignore=tests/examples --import-mode=importlib + uv run coverage run -m pytest tests -m "not e2e" --ignore=tests/integration --ignore=tests/examples --import-mode=importlib -o faulthandler_timeout=300 uv run coverage xml - name: Upload test coverage uses: codecov/codecov-action@v7 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 699419718..86f09b927 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -98,8 +98,12 @@ jobs: - name: Install dependencies run: uv sync --frozen --all-packages --group dev - name: Run unit-tests + # The suite takes a few minutes. Without a limit, a hung test holds the runner for + # GitHub's 6-hour default; faulthandler dumps every thread's stack after 5 minutes + # in one test, so the log shows where it hung. + timeout-minutes: 20 run: | - uv run coverage run -m pytest tests -m "not e2e" --ignore=tests/integration --ignore=tests/examples --import-mode=importlib + uv run coverage run -m pytest tests -m "not e2e" --ignore=tests/integration --ignore=tests/examples --import-mode=importlib -o faulthandler_timeout=300 uv run coverage xml - name: Upload test coverage uses: codecov/codecov-action@v7 From 84289429db502950e4bc0d322deb55bab8b494a4 Mon Sep 17 00:00:00 2001 From: Casper Nielsen Date: Fri, 25 Sep 2026 14:23:47 +0200 Subject: [PATCH 2/2] ci: stop Windows unit-test jobs hiding pytest failures The unit-test step runs two commands. On Windows it ran under PowerShell, which only reports the last command's exit code, so a pytest failure was hidden behind a successful `coverage xml`. Run the step under bash, which stops on the first failure. That surfaced a test that has been failing on Windows unnoticed: the fake distribution in test_legacy_extension_check built its file list with pathlib.Path, which renders backslashes on Windows. importlib.metadata returns PackagePath, a PurePosixPath, so use PurePosixPath in the fake. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Casper Nielsen --- .github/workflows/build.yaml | 3 +++ tests/test_legacy_extension_check.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 86f09b927..2945eb1e6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -102,6 +102,9 @@ jobs: # GitHub's 6-hour default; faulthandler dumps every thread's stack after 5 minutes # in one test, so the log shows where it hung. timeout-minutes: 20 + # Windows defaults to PowerShell, which only reports the last command's exit code, so a + # pytest failure was hidden behind a successful `coverage xml`. bash -e stops on it. + shell: bash run: | uv run coverage run -m pytest tests -m "not e2e" --ignore=tests/integration --ignore=tests/examples --import-mode=importlib -o faulthandler_timeout=300 uv run coverage xml diff --git a/tests/test_legacy_extension_check.py b/tests/test_legacy_extension_check.py index 68a8b348f..ee25750f7 100644 --- a/tests/test_legacy_extension_check.py +++ b/tests/test_legacy_extension_check.py @@ -19,7 +19,7 @@ import tempfile import unittest import warnings -from pathlib import Path +from pathlib import Path, PurePosixPath from typing import Iterable from unittest import mock @@ -31,10 +31,10 @@ class _FakeDist: def __init__(self, version: str, files: Iterable[str] | None) -> None: self.version = version - self._files = None if files is None else [Path(p) for p in files] + self._files = None if files is None else [PurePosixPath(p) for p in files] @property - def files(self) -> list[Path] | None: + def files(self) -> list[PurePosixPath] | None: return self._files