From ee83d515f9d4c84edb37c90b15f5bd906607d7e6 Mon Sep 17 00:00:00 2001 From: Rafael Haenel Date: Thu, 12 Nov 2026 16:14:37 -0500 Subject: [PATCH 1/4] ci: test on macOS and Windows with Python 3.14 Run the test suite on macos-latest (arm64, which installs the Metal backend) and windows-latest for the newest Python only, without fail-fast, and print whether Metal can run on the macOS runner. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f816df5..03032d23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,10 +13,23 @@ concurrency: jobs: build: - runs-on: ubuntu-latest + name: test (${{ matrix.os }}, ${{ matrix.python-version }}) + runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: + os: [ubuntu-latest] python-version: ["3.11", "3.12", "3.13", "3.14"] + # NOTE: macOS (arm64, which installs the Metal backend) and Windows run only the + # newest Python; version-specific failures already show up on Linux. + include: + - os: macos-latest + python-version: "3.14" + - os: windows-latest + python-version: "3.14" + defaults: + run: + shell: bash steps: - uses: actions/checkout@v7 @@ -33,6 +46,12 @@ jobs: run: uv sync --python ${{ matrix.python-version }} --dev - name: Show test interpreter run: uv run --python ${{ matrix.python-version }} python --version + - name: Report Metal availability + if: runner.os == 'macOS' + run: >- + uv run --python ${{ matrix.python-version }} python -c + "from tsim.statevector.metal import runtime; + print('Metal:', runtime.unavailable() or 'available')" - name: Run tests # For example, using `pytest` run: uv run --python ${{ matrix.python-version }} just coverage From 22b0912c4d573b65c3488da4c0334cf2f4c10b35 Mon Sep 17 00:00:00 2001 From: Rafael Haenel Date: Thu, 12 Nov 2026 16:16:22 -0500 Subject: [PATCH 2/4] build: skip tesseract-decoder on Windows, which has no wheels for it Co-Authored-By: Claude Opus 5.5 --- pyproject.toml | 3 ++- uv.lock | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5ef984d7..13522f3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,8 @@ dev = [ "ruff>=0.7.3", "rust-just>=1.36.0", "sinter~=1.16.dev", - "tesseract-decoder>=0.1.1.dev20251113222502", + # NOTE: tesseract-decoder has no Windows wheels. + "tesseract-decoder>=0.1.1.dev20251113222502; sys_platform != 'win32'", "tqdm>=4.67.1", "vulture>=2.16", "radon>=6.0.1", diff --git a/uv.lock b/uv.lock index ff76fd32..2b9ca142 100644 --- a/uv.lock +++ b/uv.lock @@ -167,7 +167,7 @@ dev = [ { name = "ruff" }, { name = "rust-just" }, { name = "sinter" }, - { name = "tesseract-decoder" }, + { name = "tesseract-decoder", marker = "sys_platform != 'win32'" }, { name = "tqdm" }, { name = "tsim-dev" }, { name = "vulture" }, @@ -221,7 +221,7 @@ dev = [ { name = "ruff", specifier = ">=0.7.3" }, { name = "rust-just", specifier = ">=1.36.0" }, { name = "sinter", specifier = "~=1.16.dev0" }, - { name = "tesseract-decoder", specifier = ">=0.1.1.dev20251113222502" }, + { name = "tesseract-decoder", marker = "sys_platform != 'win32'", specifier = ">=0.1.1.dev20251113222502" }, { name = "tqdm", specifier = ">=4.67.1" }, { name = "tsim-dev", editable = "dev" }, { name = "vulture", specifier = ">=2.16" }, From 60df472c63252a4d3a382c2d0c8f10528c27a76f Mon Sep 17 00:00:00 2001 From: Rafael Haenel Date: Thu, 12 Nov 2026 16:20:07 -0500 Subject: [PATCH 3/4] test: skip the tsim-dev CLI tests on Windows, where fcntl is missing Co-Authored-By: Claude Opus 5.5 --- test/unit/test_dev_cli.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/unit/test_dev_cli.py b/test/unit/test_dev_cli.py index 1dc483f7..ab584450 100644 --- a/test/unit/test_dev_cli.py +++ b/test/unit/test_dev_cli.py @@ -8,6 +8,11 @@ import pytest import yaml + +# NOTE: the development CLI is POSIX-only (it locks files with fcntl and caps memory). +if sys.platform == "win32": + pytest.skip("tsim-dev runs only on POSIX systems", allow_module_level=True) + from tsim_dev import bench, checks, cli, common, corpus, jobs, width ROOT = Path(__file__).resolve().parents[2] From 8a8878485831052aad79f4bb3d465a604a9291e7 Mon Sep 17 00:00:00 2001 From: Rafael Haenel Date: Thu, 12 Nov 2026 16:30:04 -0500 Subject: [PATCH 4/4] test: read the skill's Markdown as UTF-8, which Windows does not default to Co-Authored-By: Claude Opus 5.5 --- test/unit/test_skills.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/unit/test_skills.py b/test/unit/test_skills.py index 8f56c6dc..7f9df6c3 100644 --- a/test/unit/test_skills.py +++ b/test/unit/test_skills.py @@ -39,7 +39,7 @@ def _ids(path: Path) -> str: def _frontmatter(path: Path) -> tuple[dict, str]: - text = path.read_text() + text = path.read_text(encoding="utf-8") assert text.startswith("---\n"), "SKILL.md must start with YAML frontmatter" head, body = text[4:].split("\n---\n", 1) front = yaml.safe_load(head) @@ -85,7 +85,7 @@ def test_frontmatter_follows_the_spec(skill_dir: Path): @pytest.mark.parametrize("path", MARKDOWN_FILES, ids=_ids) def test_relative_links_resolve(path: Path): - for target in LINK.findall(path.read_text()): + for target in LINK.findall(path.read_text(encoding="utf-8")): if re.match(r"^[a-z]+:", target) or target.startswith("#"): continue assert (path.parent / target.split("#")[0]).exists(), f"broken link {target}" @@ -94,7 +94,7 @@ def test_relative_links_resolve(path: Path): @pytest.mark.parametrize("path", MARKDOWN_FILES, ids=_ids) def test_code_blocks_run(path: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch): monkeypatch.chdir(tmp_path) # blocks write figures and logs - blocks = CODE_BLOCK.findall(path.read_text()) + blocks = CODE_BLOCK.findall(path.read_text(encoding="utf-8")) # NOTE: blocks must check their results, not only run, so that a change in behavior fails. assert not blocks or any( "assert " in code for code in blocks