Skip to main content

.github,tests: use macos-15 in GitHub Actions

ID
6cf02ef
date
2025-10-03 20:33:20+00:00
author
Alex Chan <alex@alexwlchan.net>
parent
b9daafd
message
.github,tests: use macos-15 in GitHub Actions

This patch updates to the latest version of macOS, and precompiles the
`get_live_text` binary to fix an error "IOServiceMatchingfailed for:
AppleM2ScalerParavirtDriver" which is only occurring in CI.

Fixes #2
changed files
6 files, 130 additions, 115 deletions

Changed files

.github/workflows/test.yml (630) → .github/workflows/test.yml (502)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 5ab3260..ad016be 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,9 +11,7 @@ on:
 
 jobs:
   test:
-    # I'm trying pinning this to an older version to see if it fixes
-    # https://github.com/alexwlchan/get_live_text/issues/2
-    runs-on: macos-13
+    runs-on: macos-15
 
     steps:
     - uses: actions/checkout@v5
@@ -21,7 +19,7 @@ jobs:
     - name: Set up Python
       uses: actions/setup-python@v6
       with:
-        python-version: "3.12"
+        python-version: "3.13"
         cache: 'pip'
         cache-dependency-path: 'dev_requirements.txt'
 

.github/workflows/upload_binaries.yml (1283) → .github/workflows/upload_binaries.yml (1098)

diff --git a/.github/workflows/upload_binaries.yml b/.github/workflows/upload_binaries.yml
index 57a6175..42a47f2 100644
--- a/.github/workflows/upload_binaries.yml
+++ b/.github/workflows/upload_binaries.yml
@@ -21,13 +21,11 @@ jobs:
   upload-assets:
     strategy:
       matrix:
-
-        # See https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories
         include:
           - target: aarch64-apple-darwin
             os: macos-latest
           - target: x86_64-apple-darwin
-            os: macos-13
+            os: macos-latest
 
     runs-on: ${{ matrix.os }}
 

dev_requirements.txt (557) → dev_requirements.txt (437)

diff --git a/dev_requirements.txt b/dev_requirements.txt
index f166961..5ab423a 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -1,16 +1,14 @@
 # This file was autogenerated by uv via the following command:
 #    uv pip compile dev_requirements.in --output-file dev_requirements.txt
-exceptiongroup==1.3.0
-    # via pytest
 execnet==2.1.1
     # via pytest-xdist
-iniconfig==2.0.0
+iniconfig==2.1.0
     # via pytest
-packaging==24.0
+packaging==25.0
     # via pytest
-pluggy==1.5.0
+pluggy==1.6.0
     # via pytest
-pygments==2.19.1
+pygments==2.19.2
     # via pytest
 pytest==8.4.2
     # via
@@ -18,7 +16,3 @@ pytest==8.4.2
     #   pytest-xdist
 pytest-xdist==3.8.0
     # via -r dev_requirements.in
-tomli==2.2.1
-    # via pytest
-typing-extensions==4.13.2
-    # via exceptiongroup

tests/conftest.py (0) → tests/conftest.py (473)

diff --git a/tests/conftest.py b/tests/conftest.py
new file mode 100644
index 0000000..6a5c720
--- /dev/null
+++ b/tests/conftest.py
@@ -0,0 +1,20 @@
+import os
+import subprocess
+
+import pytest
+
+
+@pytest.fixture(scope="session")
+def bin_path(tmp_path_factory: pytest.TempPathFactory) -> str:
+    """
+    Compiles a new `get_live_text` binary, and returns a path to the binary.
+    """
+    p = tmp_path_factory.mktemp("bin") / "get_live_text"
+    p.parent.mkdir(exist_ok=True)
+
+    subprocess.check_call(
+        ["swiftc", os.path.abspath("get_live_text.swift")], cwd=p.parent
+    )
+    assert p.exists()
+
+    return str(p)

tests/test_get_live_text.py (1979) → tests/test_get_live_text.py (3206)

diff --git a/tests/test_get_live_text.py b/tests/test_get_live_text.py
index 62719ed..d2d1124 100755
--- a/tests/test_get_live_text.py
+++ b/tests/test_get_live_text.py
@@ -1,79 +1,119 @@
 #!/usr/bin/env python3
 
 import os
-import pathlib
-import plistlib
+from pathlib import Path
 import re
+import subprocess
 
 import pytest
 
-from utils import get_live_text
 
-
-def test_gets_empty_result_if_no_text() -> None:
-    result = get_live_text(["tests/fixtures/checkerboard.png"])
-
-    assert result == {
-        "returncode": 0,
-        "stdout": "\n",
-        "stderr": None,
-    }
-
-
-def test_gets_text_from_image() -> None:
-    result = get_live_text(["tests/fixtures/with_text.png"])
-
-    assert result == {
-        "returncode": 0,
-        "stdout": "This is an image with more than one block of text\n",
-        "stderr": None,
-    }
-
-
-def test_gives_useful_error_if_no_such_file() -> None:
-    result = get_live_text(["tests/fixtures/doesnotexist.gif"])
-
-    assert result == {
-        "returncode": 1,
-        "stdout": None,
-        "stderr": "Cannot find file at path: tests/fixtures/doesnotexist.gif\n",
-    }
-
-
-def test_gives_useful_error_if_cannot_recognize_image(tmp_path: pathlib.Path) -> None:
-    with open(tmp_path / "broken.tif", "wb") as outfile:
-        outfile.write(b"helloworld")
-
-    result = get_live_text([str(tmp_path / "broken.tif")])
-
-    assert result["returncode"] == 1
-    assert result["stdout"] is None
-    assert result["stderr"].startswith("Unable to recognise text:")
+def test_gets_empty_result_if_no_text(bin_path: Path) -> None:
+    """
+    If you pass an image without any text, you get empty output.
+    """
+    proc = subprocess.Popen(
+        [bin_path, "tests/fixtures/checkerboard.png"],
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+    )
+    stdout, stderr = proc.communicate()
+
+    assert proc.returncode == 0
+    assert stdout == b"\n"
+    
+    if os.environ.get("GITHUB_ACTIONS") != "true":
+        assert stderr == b""
+
+
+def test_gets_text_from_image(bin_path: Path) -> None:
+    """
+    If you pass an image that contains text, it gets printed to stdout.
+    """
+    proc = subprocess.Popen(
+        [bin_path, "tests/fixtures/with_text.png"],
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+    )
+    stdout, stderr = proc.communicate()
+
+    assert proc.returncode == 0
+    assert stdout == b"This is an image with more than one block of text\n"
+    
+    if os.environ.get("GITHUB_ACTIONS") != "true":
+        assert stderr == b""
+
+
+def test_gives_useful_error_if_no_such_file(bin_path: Path) -> None:
+    """
+    If you pass a path that doesn't exist, you get a useful error.
+    """
+    proc = subprocess.Popen(
+        [bin_path, "tests/fixtures/doesnotexist.gif"],
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+    )
+    stdout, stderr = proc.communicate()
+
+    assert proc.returncode == 1
+    assert stdout == b""
+    assert stderr == b"Cannot find file at path: tests/fixtures/doesnotexist.gif\n"
+
+
+def test_gives_useful_error_if_cannot_recognize_image(bin_path: Path) -> None:
+    """
+    If you pass a path that doesn't look like an image, you get a useful error.
+    """
+    proc = subprocess.Popen(
+        [bin_path, "README.md"],
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+    )
+    stdout, stderr = proc.communicate()
+
+    assert proc.returncode == 1
+    assert stdout == b""
+    assert stderr.startswith(b"Unable to recognise text:")
 
 
 @pytest.mark.parametrize(
     "argv",
     [
         pytest.param([], id="no_arguments"),
-        pytest.param(["example.png", "example.gif", "--debug"], id="too_many_arguments"),
+        pytest.param(
+            ["example.png", "example.gif", "--debug"], id="too_many_arguments"
+        ),
     ],
 )
-def test_it_fails_if_you_supply_the_wrong_arguments(argv: list[str]) -> None:
-    result = get_live_text(argv)
-
-    assert result == {
-        "returncode": 1,
-        "stdout": None,
-        "stderr": "Usage: get_live_text.swift <PATH>\n",
-    }
-
-
-
-def test_prints_the_version() -> None:
-    result = get_live_text(["--version"])
-
-    assert result["returncode"] == 0
-    assert result["stderr"] is None
-    assert re.match(
-        r"^get_live_text.swift [0-9]+\.[0-9]+\.[0-9]+\n$", result["stdout"]
-    ), result["stdout"]
+def test_it_fails_if_you_supply_the_wrong_arguments(
+    bin_path: Path, argv: list[str]
+) -> None:
+    """
+    If you pass the wrong arguments, you get an error explaining how to use it.
+    """
+    proc = subprocess.Popen(
+        [bin_path] + argv,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+    )
+    stdout, stderr = proc.communicate()
+
+    assert proc.returncode == 1
+    assert stdout == b""
+    assert stderr.startswith(b"Usage:")
+
+
+def test_prints_the_version(bin_path: Path) -> None:
+    """
+    If you run it with the --version flag, it prints the version number.
+    """
+    proc = subprocess.Popen(
+        [bin_path, "--version"],
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+    )
+    stdout, stderr = proc.communicate()
+
+    assert proc.returncode == 0
+    assert re.match(r"get_live_text [0-9]+\.[0-9]+\.[0-9]+\n$", stdout.decode("utf8"))
+    assert stderr == b""

tests/utils.py (794) → tests/utils.py (0)

diff --git a/tests/utils.py b/tests/utils.py
deleted file mode 100644
index 3b10776..0000000
--- a/tests/utils.py
+++ /dev/null
@@ -1,35 +0,0 @@
-import pathlib
-import subprocess
-import typing
-
-
-class CommandOutput(typing.TypedDict):
-    returncode: int
-    stdout: str | None
-    stderr: str | None
-
-
-def get_live_text(argv: list[str | pathlib.Path]) -> CommandOutput:
-    """
-    Run the ``get_live_text.swift`` script and return the result.
-    """
-    cmd = ["swift", "get_live_text.swift"] + [str(av) for av in argv]
-
-    proc = subprocess.Popen(
-        cmd,
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE,
-    )
-    stdout, stderr = proc.communicate()
-
-    if stdout is not None:
-        stdout = stdout.decode("utf8")
-
-    if stderr is not None:
-        stderr = stderr.decode("utf8")
-
-    return CommandOutput(
-        returncode=proc.returncode,
-        stdout=stdout or None,
-        stderr=stderr or None,
-    )