* feat(images): move deleted image files to the macOS Trash (#499) Trash mode on macOS now calls -[NSFileManager trashItemAtURL:resultingItemURL:error:] through ctypes (no dependency), in utils/trash_macos.py. Before any move it refuses what it cannot show to be recoverable, already in the review dialog: network volumes, read-only volumes, iCloud Drive and File Provider folders (cloud-sync), a trash folder inside an output root (by file identity), and files macOS would refuse for permission (no-permission). A refused move keeps the file and logs the NSError; a move macOS reports done but whose place cannot be shown goes under errors as trash-unconfirmed, keeps its records and stops the job. The native proof runs in portability.yml's new native-trash job on macos-latest and macos-15-intel (PM_NATIVE_TRASH=macos). Windows still reports no-trash (#500). * test(trash): resolve the temp folder and wait for a busy image to be released (#499) The permission tests keyed their lstat fakes by the unresolved temp path, but the backend resolves paths (/var -> /private/var on macOS, 8.3 short names on Windows), so on those runners the fakes never matched (portability run 40954618922; reproduced on Linux with a symlinked TMPDIR). The native test's detach first proves this process holds nothing under the mount, then retries the plain detach until macOS releases a volume it has just written a trash folder to (macos-15-intel), and still fails after a forced detach. * test(trash): count a volume that unmounted after a busy detach as detached (#499) On macos-15-intel a detach that reported "Resource busy" finished afterwards, so the retry found no such volume (portability run 40957282805). The loop now checks os.path.ismount before each attempt.
107 lines
4.0 KiB
YAML
107 lines
4.0 KiB
YAML
name: Windows and macOS
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
# Non-blocking portability runs (#420): never on pull requests or in the merge queue, and no context
|
|
# here is a required check. A red run is fixed at its cause before the workspace cutover (#334).
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
schedule:
|
|
- cron: "17 3 * * *"
|
|
workflow_dispatch:
|
|
|
|
# Every run gets its own group (run_id), so no main commit's run is ever cancelled or dropped as pending.
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.run_id }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
PYTHONFAULTHANDLER: "1"
|
|
|
|
# Time limit rule as in test.yml: ceil(1.5 x the slowest of three recent green runs on main) + 3
|
|
# minutes, never under 5, timed as whole-job wall time. One matrix job, so the slowest leg of each
|
|
# run counts (tests/vendor/ci-workflow.test.mjs recomputes it, #493).
|
|
# Measured 2026-10-29 on main runs 40935337099 (dd247e3), 40938407252 (dd247e3), 40941240624 (dd247e3):
|
|
# slowest leg: 7m24s (py3.12 windows) / 7m31s (py3.13 windows) / 7m32s (py3.14 windows) -> 15 min
|
|
# (windows 4m56s-7m32s, macos 4m39s-6m14s)
|
|
# native-trash (#499) has no green main runs yet: its probe, run 40949370943 (2026-10-29), took
|
|
# about 1 minute per macOS leg, so ceil(1.5 x 2) + 3 gives it 6 minutes (step 4). Recalibrate
|
|
# after three green runs.
|
|
jobs:
|
|
test:
|
|
name: py${{ matrix.python-version }} on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 15
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [windows-latest, macos-latest]
|
|
python-version: ["3.12", "3.13", "3.14"]
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Set up Python ${{ matrix.python-version }}
|
|
uses: actions/setup-python@v7
|
|
with:
|
|
python-version: ${{ matrix.python-version }}
|
|
cache: pip
|
|
cache-dependency-path: requirements-dev.txt
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install -r requirements-dev.txt
|
|
|
|
# The runner's TEMP is on C:, the OS disk, where a flush took about 6 ms
|
|
# and a test database's create-to-delete about 111 ms; on the D: work
|
|
# disk the same took 0.5 ms and 23 ms (macOS: 19 ms). Tests write their
|
|
# databases under TEMP, so it moves to a folder on D: (measured in #487,
|
|
# run 40915866444). The runner's TEMP was an 8.3 short name
|
|
# (C:\Users\RUNNER~1\...), so the new one is spelled by its short name too
|
|
# and paths still have to be resolved. D: makes no short names by default
|
|
# (run 40922502209), so they are switched on for it first.
|
|
- name: Keep test files on the work disk (Windows)
|
|
if: runner.os == 'Windows'
|
|
shell: pwsh
|
|
run: |
|
|
fsutil 8dot3name set D: 0
|
|
$folder = New-Item -ItemType Directory -Force -Path (Join-Path $env:RUNNER_TEMP "PromptManagerTests")
|
|
$short = (New-Object -ComObject Scripting.FileSystemObject).GetFolder($folder.FullName).ShortPath
|
|
Write-Output "test TEMP: $short (long name $($folder.FullName))"
|
|
if ($short -eq $folder.FullName) { Write-Output "::warning::D: gave the test TEMP no 8.3 short name" }
|
|
"TEMP=$short" >> $env:GITHUB_ENV
|
|
"TMP=$short" >> $env:GITHUB_ENV
|
|
|
|
- name: Run tests
|
|
env:
|
|
PM_TEST_SLOWEST: "40"
|
|
run: python -m tests.run_suite -v
|
|
|
|
# The real Trash on both macOS CPUs (#499 design §10.2). It runs apart from the suite, so a red
|
|
# suite never skips the native proof.
|
|
native-trash:
|
|
name: native trash on ${{ matrix.os }}
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 6
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
os: [macos-latest, macos-15-intel]
|
|
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@v7
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Native trash proof
|
|
timeout-minutes: 4
|
|
env:
|
|
PM_NATIVE_TRASH: macos
|
|
run: python -m unittest tests.test_trash_macos_native tests.test_trash_macos -v
|