Compare commits

...
Author SHA1 Message Date
gronod cba9476af7 Update .github/workflows/macos.yml
macOS CI / build-and-test (push) Skipped
macOS CI / build-and-test (pull_request) Failing after 3m40s
macOS CI / package (pull_request) Skipped
2026-09-14 16:41:41 +01:00
gronod 288b08e3d9 ci: add GitHub Actions twin of the Gitea macOS workflow
macOS CI / build-and-test (push) Skipped
macOS CI / build-and-test (pull_request) Failing after 42m38s
macOS CI / package (pull_request) Skipped
Copy .gitea/workflows/macos.yml to .github with the github.com deltas:
macos-14 (macos-12 is gone), upload-artifact@v4, no private-CA bundle,
and gh release upload instead of the Gitea asset script.
2026-09-14 15:33:13 +00:00
gronod 18f1d1ff33 Merge pull request 'test(m4): wait for Start Read enabled before chartread (CI 35251/35293)' (#173) from fix/m4-handheld-start-read into develop
macOS CI / build-and-test (push) Successful in 42m34s
macOS CI / package (push) Waiting to run
test(m4): wait for Start Read enabled before chartread

Runs 35251 and 35293 clicked a disabled Start Read.
2026-09-14 16:16:34 +01:00
gronod 9dfa79ebd0 test(m4): wait for Start Read to be enabled before clicking
macOS CI / build-and-test (push) Skipped
macOS CI / build-and-test (pull_request) Canceled after 0s
macOS CI / package (pull_request) Canceled after 0s
Runs 35251 and 35293 left Stage 3 idle because XCTest clicked a
disabled Start Read. driveOnePass already waits for isEnabled;
the handheld average test now uses it.
2026-09-14 15:16:25 +00:00
3 changed files with 237 additions and 18 deletions
+229
View File
@@ -0,0 +1,229 @@
# GitHub Actions twin of .gitea/workflows/macos.yml.
# Deltas from the Gitea file (everything else is the same jobs/steps):
# - runs-on macos-14: github.com retired macos-12 (the Gitea runner label).
# The Gitea workflow already notes macos-14 works for this pipeline.
# - actions/upload-artifact@v4: v3 is shut down on github.com. Gitea act_runner
# still uses v3.
# - No NODE_EXTRA_CA_CERTS / System keychain bundle: that is only for the
# private Gitea CA when the runner talks to git.i3omb.com.
# - Tag DMGs go to a GitHub Release via `gh` instead of
# scripts/attach-release-asset.sh (Gitea /api/v1).
name: macOS CI
on:
push:
branches:
- develop
tags:
- 'v*'
pull_request:
branches:
- develop
permissions:
contents: read
jobs:
build-and-test:
runs-on: macos-26-intel
env:
DERIVED: build/DerivedData-test
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Assert Xcode 14+ toolchain
run: |
line="$(xcodebuild -version | head -1)"
major="$(printf '%s' "$line" | sed -n 's/^Xcode \([0-9][0-9]*\)\..*/\1/p')"
if [ -z "$major" ] || [ "$major" -lt 14 ]; then
echo "Unexpected Xcode version: $line" >&2; exit 1
fi
echo "$line"
# Tag pushes whose name contains "prerelease" skip the test build and both
# test legs: they exist to package a build already validated elsewhere.
# The job still succeeds quickly so `package`'s `needs:` stays satisfied.
# Homebrew's xcodegen formula requires Xcode 15.3, which cannot be
# installed on macOS 12 (#109). The script installs a pinned
# prebuilt release instead. dmgbuild is not installed here — the
# test job does not package (#95).
- name: Ensure host tools
if: "!(startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 'prerelease'))"
run: scripts/ensure-host-tools.sh
- name: Generate Xcode project
if: "!(startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 'prerelease'))"
run: xcodegen generate --spec project.yml
# Tests only ever run on the runner's own architecture; build
# just that slice. Packaging (scripts/package-release.sh) still
# produces the universal Release binary.
- name: Build for testing (host arch)
if: "!(startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 'prerelease'))"
run: |
xcodebuild build-for-testing \
-scheme ICCery \
-destination 'platform=macOS' \
-derivedDataPath "$DERIVED" \
-configuration Debug \
ARCHS="$(uname -m)" \
ONLY_ACTIVE_ARCH=NO \
CODE_SIGNING_ALLOWED=YES \
CODE_SIGN_IDENTITY='-'
# Xcode embeds the shared ICCeryCore package framework into the app
# and the test bundle without signing it. Ad-hoc hosts still require
# every loaded dylib to carry a cdhash — dyld killed the test host at
# launch (run 31992) — so sign every embedded copy once the build is
# done (embed steps run after any build script phase) (#119).
- name: Sign package product frameworks
if: "!(startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 'prerelease'))"
run: |
find "$DERIVED/Build/Products/Debug" -depth -name '*_PackageProduct.framework' -print0 \
| while IFS= read -r -d '' fw; do
echo "signing $fw"
codesign --force --sign - --timestamp=none "$fw"
done
- name: Test unit (ICCeryCoreTests)
if: "!(startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 'prerelease'))"
run: |
XCTESTRUN="$(find "$DERIVED" -name 'ICCery*.xctestrun' | head -n 1)"
if [ -z "$XCTESTRUN" ] || [ ! -f "$XCTESTRUN" ]; then
echo "error: no xctestrun produced by build-for-testing" >&2
exit 1
fi
echo "xctestrun: $XCTESTRUN"
xcodebuild test-without-building \
-xctestrun "$XCTESTRUN" \
-only-testing:ICCeryCoreTests \
-destination 'platform=macOS' \
-derivedDataPath "$DERIVED"
# UI tests need macOS Automation / Accessibility permission on the
# runner. GitHub-hosted macos-14 images enable this; a self-hosted
# Mac can still time out enabling that mode (run 29700) or launch
# the app into `.runningBackground` (run 29804). Kill any leftover
# unit-test host first; retry once; if the runner still cannot
# attach, do not fail the required gate so tag packaging can
# proceed. Real XCTest assertion failures still fail the job.
- name: Test UI (ICCeryUITests)
if: "!(startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 'prerelease'))"
run: |
set -o pipefail
XCTESTRUN="$(find "$DERIVED" -name 'ICCery*.xctestrun' | head -n 1)"
LOG="$DERIVED/ui-test.log"
pkill -x ICCery 2>/dev/null || true
sleep 1
run_ui() {
local label="$1"
shift
echo "::group::UI tests $label"
set +e
xcodebuild test-without-building \
-xctestrun "$XCTESTRUN" \
-destination 'platform=macOS' \
-derivedDataPath "$DERIVED" \
"$@" | tee "$LOG"
rc=${PIPESTATUS[0]}
set -e
echo "::endgroup::"
return "$rc"
}
is_runner_attach_failure() {
grep -Eq "Timed out while enabling automation mode|Failed to activate application|current state: Running Background" "$LOG"
}
attempt=1
while [ "$attempt" -le 2 ]; do
# Probe one case first. A background-activate failure costs
# ~65s here instead of ~25 minutes for the whole suite (29804).
if ! run_ui "probe attempt $attempt" \
-only-testing:ICCeryUITests/AboutHelpUITests/testAboutDialogShowsVersionAndBuildDate; then
if is_runner_attach_failure; then
echo "warning: UI runner could not attach/activate the app (attempt $attempt)"
pkill -x ICCery 2>/dev/null || true
attempt=$((attempt + 1))
sleep 8
continue
fi
echo "error: UI probe failed with a real test error" >&2
exit 1
fi
if run_ui "full suite attempt $attempt" -only-testing:ICCeryUITests \
-skip-testing:ICCeryUITests/AboutHelpUITests/testAboutDialogShowsVersionAndBuildDate; then
exit 0
fi
if is_runner_attach_failure; then
echo "warning: UI runner lost activation mid-suite (attempt $attempt)"
pkill -x ICCery 2>/dev/null || true
attempt=$((attempt + 1))
sleep 8
continue
fi
echo "error: UI tests failed with a real test error" >&2
exit 1
done
echo "warning: skipping UI tests after repeated runner attach/activate failures"
exit 0
# XCTest stores the a11y hierarchy snapshot and screenshots in the
# xcresult on failure — upload it so UI failures can be triaged
# without access to the runner (#126).
- name: Upload UI test xcresult
if: "failure() && !(startsWith(github.ref, 'refs/tags/') && contains(github.ref_name, 'prerelease'))"
uses: actions/upload-artifact@v4
with:
name: ui-test-xcresult
path: build/DerivedData-test/Logs/Test
package:
needs: build-and-test
runs-on: macos-14
if: github.ref == 'refs/heads/develop' || startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
# scripts/package-release.sh runs `xcodegen generate` and dmgbuild;
# see build-and-test for why brew is not used on macOS 12 (#109).
# INSTALL_DMGBUILD isolates dmgbuild in build/.venv-dmgbuild so
# the test job never pip-installs it (#95).
- name: Ensure host tools
run: INSTALL_DMGBUILD=1 scripts/ensure-host-tools.sh
- name: Package release
run: scripts/package-release.sh
env:
CODESIGN_IDENTITY: ${{ secrets.CODESIGN_IDENTITY }}
DEVELOPMENT_TEAM: ${{ secrets.DEVELOPMENT_TEAM }}
NOTARIZE_APPLE_ID: ${{ secrets.NOTARIZE_APPLE_ID }}
NOTARIZE_PASSWORD: ${{ secrets.NOTARIZE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Upload DMG artifact
uses: actions/upload-artifact@v4
with:
name: iccery-dmg
path: ICCery-*.dmg
- name: Attach DMG to GitHub release
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ github.token }}
run: |
set -eu
TAG="$GITHUB_REF_NAME"
case "$TAG" in
*prerelease*) PRE_FLAG=--prerelease ;;
*) PRE_FLAG= ;;
esac
if ! gh release view "$TAG" >/dev/null 2>&1; then
gh release create "$TAG" --title "$TAG" --target "$GITHUB_SHA" $PRE_FLAG
fi
gh release upload "$TAG" ICCery-*.dmg --clobber
+5 -1
View File
@@ -206,9 +206,13 @@ xcodebuild test -scheme ICCery -destination 'platform=macOS' \
-only-testing:ICCeryUITests/Milestone5UITests
```
CI (`.gitea/workflows/macos.yml`) runs `build-and-test` then `package` on
CI (`.gitea/workflows/macos.yml` on Gitea, `.github/workflows/macos.yml` on
GitHub) runs `build-and-test` then `package` on
`develop` and on `v*` tags. Tags whose name contains `prerelease` skip the
test job and still package. `pull_request` is wired for **`develop` only**.
The GitHub file is the same pipeline on `macos-14` (github.com retired
`macos-12`), `actions/upload-artifact@v4`, and `gh release upload` for tag
DMGs.
UI tests need an unlocked console (`IOConsoleLocked=false`). Mock Argyll /
CUPS fixtures live under the test bundles; they must not be treated as proof
+3 -17
View File
@@ -98,23 +98,9 @@ final class Milestone4UITests: XCTestCase {
app.buttons["btnDetectInstruments"].click()
_ = waitFor("chartreadInstrumentSelect", timeout: 20)
// Keep Auto (port 1) and start the session.
XCTAssertTrue(app.buttons["btnStartRead"].waitForExistence(timeout: 5))
app.buttons["btnStartRead"].click()
// Calibrate.
let calibrate = element("btnCalibrate")
if !calibrate.waitForExistence(timeout: 25) {
let error = element("chartreadLastError").label
let value = element("chartreadLastError").value as? String ?? "<nil>"
XCTFail("No calibrate button. lastError.label='\(error)' value='\(value)'")
}
app.buttons["btnCalibrate"].click()
// Trigger each strip until all are read Done & Save appears.
driveStripsUntilDone()
XCTAssertTrue(element("btnDoneRead").exists)
app.buttons["btnDoneRead"].firstMatch.click()
// Wait until Start is enabled before clicking. Existence-only
// clicks are no-ops on the disabled control (runs 35251, 35293).
driveOnePass(startButton: "btnStartRead")
// Averaging panel appears with one pass snapshot.
_ = waitFor("chartreadAveragingPanel", timeout: 20)