Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,6 @@ body:
attributes:
label: Environment
description: OS and version, Xcode version.
placeholder: "macOS 26.1, Xcode 26.0"
placeholder: "macOS 27.0, Xcode 27.0"
validations:
required: true
2 changes: 1 addition & 1 deletion .github/actions/select-xcode/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: >
inputs:
xcode-major:
description: Xcode major version to select
default: "26"
default: "27"

runs:
using: composite
Expand Down
16 changes: 11 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ concurrency:
jobs:
build-test:
name: Build & Test
runs-on: macos-15
runs-on: xcode-27
permissions:
contents: read
id-token: write # Codecov upload via GitHub OIDC (no static token)
Expand All @@ -34,13 +34,19 @@ jobs:
- name: Test
run: swift test --enable-code-coverage

# SwiftPM builds one .xctest bundle per test target (not a single
# <Package>PackageTests.xctest), so pass every bundle's binary to llvm-cov.
- name: Export coverage (lcov)
run: |
BIN_PATH=$(swift build --show-bin-path)
objects=()
for bundle in "$BIN_PATH"/*.xctest; do
objects+=(-object "$bundle/Contents/MacOS/$(basename "$bundle" .xctest)")
done
xcrun llvm-cov export -format=lcov \
-instr-profile "$BIN_PATH/codecov/default.profdata" \
-ignore-filename-regex="\.build|Tests" \
"$BIN_PATH/TortoiseGraphicsPackageTests.xctest/Contents/MacOS/TortoiseGraphicsPackageTests" \
"${objects[@]}" \
> coverage.lcov

- name: Upload coverage to Codecov
Expand All @@ -54,7 +60,7 @@ jobs:
# parallel job, so the test job doesn't build everything twice.
release-build:
name: Release Build
runs-on: macos-15
runs-on: xcode-27
steps:
- uses: actions/checkout@v7

Expand All @@ -76,7 +82,7 @@ jobs:
linux:
name: Linux (Core + SVG)
runs-on: ubuntu-latest
container: swift:6.2
container: swift:6.4
steps:
- uses: actions/checkout@v7

Expand All @@ -92,7 +98,7 @@ jobs:

format-lint:
name: swift-format lint
runs-on: macos-15
runs-on: xcode-27
steps:
- uses: actions/checkout@v7

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ concurrency:
jobs:
build:
name: Build DocC
runs-on: macos-15
runs-on: xcode-27
steps:
- uses: actions/checkout@v7

Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## Unreleased

### Changed
- Requires Swift 6.4+ / Xcode 27+ (was Swift 6.2+ / Xcode 26+) — the package
manifest now declares `swift-tools-version: 6.4`. No source changes were
needed: the package builds without warnings and passes all tests under
Swift 6.4. Platform requirements are unchanged (iOS 26+ · macOS 26+ ·
visionOS 26+ · Linux)

## 2.1.0

### Added
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ SVG tests avoid raw string literals (`#"..."#`) when the expected string contain
**Drawing-scenario golden tests.** `Tests/TortoiseTestSupport/` (a non-product target) defines `DrawingScenario.all` — feature-grouped tortoise programs that together cover every `TortoiseCommand` case. Both renderers are verified against the same scenarios via pointfreeco/swift-snapshot-testing:

- `TortoiseSVGTests/DrawingScenarioSVGTests` compares full SVG strings against goldens in `Tests/TortoiseSVGTests/__Snapshots__/`.
- `TortoiseUITests/DrawingScenarioCanvasTests` (macOS-only, `#if os(macOS)`) renders `TortoiseCanvas` via `ImageRenderer` (`scale = 2`, instant mode forced by prepending `speed = 0`) and compares PNGs in `Tests/TortoiseUITests/__Snapshots__/` with `precision: 0.995, perceptualPrecision: 0.98` to absorb OS-level antialiasing drift.
- `TortoiseUITests/DrawingScenarioCanvasTests` (macOS-only, `#if os(macOS)`) renders `TortoiseCanvas` via `ImageRenderer` (`scale = 2`, instant mode forced by prepending `speed = 0`) and compares PNGs in `Tests/TortoiseUITests/__Snapshots__/` with `.canvasGolden` (byte-wise `precision: 0.995`, defined in `CanvasGoldenSnapshotting.swift`) to absorb OS-level antialiasing drift. **Do not add `perceptualPrecision`**: swift-snapshot-testing's perceptual path passes a bare `CGRect` to `CIAreaAverage`, which Core Image on macOS 27 rejects with an uncaught exception that kills the whole test process. It only triggers when bytes differ, so it passes on the recording macOS and crashes on the CI runner's. Tracked in #49 (still reproduces on Xcode 27.0 GA; re-check once the runner image ships a GA macOS 27).

After intentionally changing scenario programs or renderer output, re-record **both** golden sets in the same commit and visually inspect them:

Expand Down
14 changes: 9 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ requests, documentation fixes, and pull requests are all welcome.

## Requirements

- **Swift** 6.2+ / **Xcode** 26+
- **Swift** 6.4+ / **Xcode** 27+
- **macOS** to run the full test suite — the golden image tests in
`TortoiseUITests` render a SwiftUI view and are macOS-only.

Expand Down Expand Up @@ -73,10 +73,14 @@ Rules:
visible output — add or update a scenario so both renderers stay covered.

The PNG goldens depend on OS-level rendering. The current set was recorded on
**macOS 26** (Xcode 26); CI compares them on `macos-15` runners, with a small
tolerance (`precision: 0.995`, `perceptualPrecision: 0.98`) absorbing
antialiasing drift between OS versions. If a PR bumps the CI runner image
(`runs-on: macos-XX`) or you re-record on a different macOS version, confirm
**macOS 26** (Xcode 26); CI compares them on the `xcode-27` runner image
(macOS 27), with a small
byte-wise tolerance (`precision: 0.995`, defined once as `.canvasGolden` in
`Tests/TortoiseUITests/CanvasGoldenSnapshotting.swift`) absorbing
antialiasing drift between OS versions. Don't add `perceptualPrecision`: that
comparison crashes the test process on macOS 27
([#49](https://github.com/temoki/TortoiseGraphics2/issues/49)). If a PR bumps the CI runner image
(`runs-on:`) or you re-record on a different macOS version, confirm
the PNG golden tests still pass in CI — and if they don't, re-record both
golden sets and visually inspect them as described above.

Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version: 6.2
// swift-tools-version: 6.4
import PackageDescription

var products: [Product] = [
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# TortoiseGraphics2

[![Swift](https://img.shields.io/badge/Swift-6.2-orange.svg)](https://swift.org)
[![Swift](https://img.shields.io/badge/Swift-6.4-orange.svg)](https://swift.org)
[![Swift Package Manager](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-brightgreen.svg)](https://swift.org/package-manager)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![codecov](https://codecov.io/gh/temoki/TortoiseGraphics2/graph/badge.svg)](https://codecov.io/gh/temoki/TortoiseGraphics2)
Expand Down Expand Up @@ -68,8 +68,8 @@ and testing all share a single source of truth.

## Requirements

- **Swift** 6.2+
- **Xcode** 26+ (Apple platforms)
- **Swift** 6.4+
- **Xcode** 27+ (Apple platforms)
- **Platforms** iOS 26+ · macOS 26+ · visionOS 26+ · Linux (`TortoiseCore` / `TortoiseSVG` only — `TortoiseUI` requires SwiftUI)

## Installation
Expand Down
23 changes: 23 additions & 0 deletions Tests/TortoiseUITests/CanvasGoldenSnapshotting.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#if os(macOS)
import AppKit
import SnapshotTesting

extension Snapshotting where Value == NSImage, Format == NSImage {
/// The comparison every canvas PNG golden uses.
///
/// Byte-wise with a small tolerance, deliberately *without*
/// `perceptualPrecision`. swift-snapshot-testing's perceptual path passes a
/// bare `CGRect` as `CIAreaAverage`'s `inputExtent`, which Core Image on
/// macOS 27 rejects with an uncaught `-[NSConcreteValue CGRectValue]`
/// exception — crashing the whole test process rather than failing one
/// test. An exact match never reaches that path, which is why the goldens
/// still pass on the macOS they were recorded on.
///
/// Still reproduces on Xcode 27.0 GA (27A266a); the CI runner's macOS 27 is
/// itself still a beta build, so whether `perceptualPrecision` can ever come
/// back is tracked in #49.
static var canvasGolden: Snapshotting {
.image(precision: 0.995)
}
}
#endif
2 changes: 1 addition & 1 deletion Tests/TortoiseUITests/DrawingScenarioCanvasTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
}
assertSnapshot(
of: image,
as: .image(precision: 0.995, perceptualPrecision: 0.98),
as: .canvasGolden,
named: scenario.name,
testName: "scenario"
)
Expand Down
2 changes: 1 addition & 1 deletion Tests/TortoiseUITests/TortoiseSpriteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ extension CGSize {
return
}
assertSnapshot(
of: image, as: .image(precision: 0.995, perceptualPrecision: 0.98),
of: image, as: .canvasGolden,
fileID: fileID, file: filePath, testName: function, line: line, column: column)
}
}
Expand Down
Loading