forked from stride3d/stride
-
Notifications
You must be signed in to change notification settings - Fork 1
325 lines (307 loc) · 14.5 KB
/
Copy pathtest-ios-game.yml
File metadata and controls
325 lines (307 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
name: Test iOS Game (Vulkan)
run-name: >-
${{ github.event.inputs.commit-sha != '' && format('Test iOS Game (Vulkan) @ {0}', github.event.inputs.commit-sha) || '' }}
on:
workflow_dispatch:
inputs:
build-type:
description: Build Configuration
default: Debug
type: choice
options:
- Debug
- Release
test-filter:
description: 'vstest --filter expression forwarded to the on-device runner (blank = all tests)'
type: string
default: ''
project:
description: 'Single test .csproj to build/run (blank = Combined .app aggregating all suites)'
type: string
default: ''
commit-sha:
description: Commit SHA to test (default = HEAD of selected branch)
type: string
default: ''
upload-binlog:
description: Capture and upload build binlog (off by default; adds some I/O overhead)
default: false
type: boolean
use-combined:
description: 'Combine all test suites into one .app (faster build/install/run); false = per-suite .apps'
default: true
type: boolean
repeat:
description: 'Rerun the filtered test set up to N times in-process, stop on first failure (flake hunting). 1 = single run (default).'
type: number
default: 1
workflow_call:
inputs:
build-type:
default: Debug
type: string
test-filter:
default: ''
type: string
project:
default: ''
type: string
commit-sha:
default: ''
type: string
# Internal (callers only, e.g. test-gold-gen): tolerate test failures so generating new gold
# doesn't fail the run. Build failures still fail. Not exposed in the dispatch UI.
tolerate-test-failures:
default: false
type: boolean
upload-binlog:
default: false
type: boolean
use-combined:
default: true
type: boolean
repeat:
type: number
default: 1
schedule:
# Daily at 02:43 UTC — offset off top-of-hour to avoid GitHub Actions cron load spikes;
# spaced from the other nightlies (03:37–05:17) so they don't compete for the scheduler.
- cron: '43 2 * * *'
concurrency:
group: test-ios-game-${{ github.event.pull_request.number || github.ref }}-${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}
cancel-in-progress: true
jobs:
iOS-Tests-Game:
# Scheduled (nightly) runs only fire on repos that opt in via STRIDE_ENABLE_SCHEDULED_CI;
# workflow_call (main.yml path filter / ci-ios label) and dispatch always run.
if: >
github.event_name != 'schedule'
|| vars.STRIDE_ENABLE_SCHEDULED_CI == 'true'
name: iOS (Vulkan, ${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }})
# macOS host: iOS Simulator only runs on macOS. macos-26 ships Xcode 26.x (we select 26.5
# below to match the .NET iOS 26.5 SDK pack's strict version check). macos-15's newest Xcode
# is 26.3, which the 26.5 pack rejects.
runs-on: macos-26
timeout-minutes: 45
env:
DOTNET_NUGET_SIGNATURE_VERIFICATION: "false"
# Workaround for paravirt-GPU SimMetalHost crash on macos-26 runners: force MoltenVK to
# never use Metal argument buffers, so the descriptor-set bind path doesn't trigger
# argumentEncoderSetValues on the paravirt driver. SIMCTL_CHILD_ prefix is stripped by
# simctl when launching the sim app. Mode 0 = NEVER (mode 1 still uses them when
# descriptor indexing is required, which didn't avoid the crash).
SIMCTL_CHILD_MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS: "0"
steps:
# Bootstrap .github so the local composite action below is resolvable pre-checkout.
- uses: actions/checkout@v7
with:
sparse-checkout: .github
- uses: ./.github/actions/stride-checkout
with:
ref: ${{ (github.event.inputs.commit-sha != '' && github.event.inputs.commit-sha) || (inputs.commit-sha != '' && inputs.commit-sha) || github.ref }}
exclude: samples
- name: Install iOS + macOS Workloads
# macOS workload is needed because the engine libs cross-target net10.0-macos (for the
# AVFoundation/Metal bring-up work). slnf-driven builds dispatch all TFMs of every project
# they pull in, so the iOS slnf still needs the macOS workload available.
uses: ./.github/actions/stride-workload
with:
workloads: ios macos
- name: Select newest Xcode
# The .NET iOS SDK pack enforces a minimum Xcode version that advances with the workload
# set (the latest set now requires 26.6). Select the newest installed Xcode so the check
# passes, instead of pinning a version the pack later outgrows.
run: |
ls -d /Applications/Xcode_*.app
latest=$(ls -d /Applications/Xcode_*.app | sort -V | tail -1)
echo "Selecting $latest"
sudo xcode-select -s "$latest"
xcodebuild -version
- name: Ensure Xcode toolchain utilities resolve
# Xcode 26.6 on the runner image has a broken `xcodebuild -sdk … -find <tool>`, so the .NET
# macOS/iOS SDK can't locate the utilities it needs to build/link/package the .app
# (install_name_tool, mdimport, actool, …). Symlink each into the active toolchain bin from a
# real copy (Command Line Tools, Xcode's own bin, PATH, or the llvm-named variant); the SDK's
# fallback then finds it there.
run: |
set -x
dev="$(xcode-select -p)"
tcbin="$dev/Toolchains/XcodeDefault.xctoolchain/usr/bin"
for tool in install_name_tool mdimport dsymutil strip lipo bitcode_strip actool ibtool derq; do
xcrun -find "$tool" >/dev/null 2>&1 && continue
for cand in \
"/Library/Developer/CommandLineTools/usr/bin/$tool" \
"$dev/usr/bin/$tool" \
"$(command -v "$tool" 2>/dev/null || true)" \
"$tcbin/llvm-$tool"; do
[ -x "$cand" ] && { sudo ln -sf "$cand" "$tcbin/$tool"; echo "linked $tool -> $cand"; break; }
done
xcrun -find "$tool" >/dev/null 2>&1 || echo "::warning::could not resolve $tool"
done
- name: Start iOS Simulator (background)
# Kick the simulator boot off early so its ~50s first-boot data migration
# (PreferencesMigrator etc.) happens in parallel with the build. We block on
# readiness in a later step right before tests run.
run: |
set -e
UDID=$(xcrun simctl list devices --json | python3 -c '
import json, sys
d = json.load(sys.stdin)["devices"]
for runtime in sorted((k for k in d if k.startswith("com.apple.CoreSimulator.SimRuntime.iOS-")), reverse=True):
for x in d[runtime]:
if x.get("isAvailable") and "iPhone" in x["name"]:
print(x["udid"]); sys.exit(0)
sys.exit(1)
')
if [ -z "$UDID" ]; then
echo "::error::No available iPhone simulator runtime found"
xcrun simctl list devices
exit 1
fi
echo "Booting $UDID"
xcrun simctl boot $UDID
echo "IOS_SIM_UDID=$UDID" >> $GITHUB_ENV
- name: Resolve build target / suite
run: |
# `project` set (gold-gen: one suite only) → that .csproj's .app (~4 min build).
# Unset → use-combined=true (default): Combined.csproj — all suites in one .app,
# one install, one process run (faster build/testing).
# use-combined=false → slnf build + per-suite .apps + per-suite install/run.
PROJ="${{ github.event.inputs.project || inputs.project }}"
USE_COMBINED="${{ github.event.inputs.use-combined || inputs.use-combined || 'true' }}"
if [ -n "$PROJ" ]; then
echo "IOS_BUILD_TARGET=$PROJ" >> "$GITHUB_ENV"
echo "IOS_TEST_SUITES=$(basename "$PROJ" .csproj)" >> "$GITHUB_ENV"
elif [ "$USE_COMBINED" = "true" ]; then
echo "IOS_BUILD_TARGET=sources/tests/Stride.Tests.Combined/Stride.Tests.Combined.csproj" >> "$GITHUB_ENV"
echo "IOS_TEST_SUITES=Stride.Tests.Combined" >> "$GITHUB_ENV"
else
# slnf path: one build evaluates the graph once; per-suite .app bundles land under
# bin/Tests/<Suite>/iOS-Vulkan/<Config>/<Suite>.app. Run step iterates IOS_TEST_SUITES.
echo "IOS_BUILD_TARGET=build/Stride.Tests.Game.iOS.slnf" >> "$GITHUB_ENV"
{
echo "IOS_TEST_SUITES<<EOF"
echo "Stride.Engine.NoAssets.Tests"
echo "Stride.Input.Tests"
echo "Stride.Audio.Tests"
echo "Stride.Particles.Tests"
echo "Stride.UI.Tests"
echo "Stride.Navigation.Tests"
echo "Stride.Physics.Tests"
echo "Stride.Engine.Tests"
echo "Stride.Graphics.Tests"
echo "Stride.Graphics.Tests.10_0"
echo "Stride.Graphics.Tests.11_0"
echo "EOF"
} >> "$GITHUB_ENV"
fi
- name: Build iOS test .app bundle
# No explicit RuntimeIdentifier: passing one as a global property leaks into the host
# net10.0 build (used to load assemblies into the AssetCompiler) and crashes its
# ResolveNativeReferences. The iOS SDK infers iossimulator-arm64 on its own for
# net10.0-ios on an arm64 host.
run: |
dotnet build "$IOS_BUILD_TARGET" \
-nr:false -v:m -p:WarningLevel=0 \
-p:StrideSkipAutoPack=true \
${{ inputs.upload-binlog && '-bl:build.binlog' || '' }} \
-p:Configuration=${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }} \
-p:StridePlatforms=iOS
- name: Upload build binlog
# failure() so we still get the binlog when Build dies (most useful case)
if: failure() || inputs.upload-binlog
uses: actions/upload-artifact@v7
with:
name: build-binlog-ios-game
path: build.binlog
if-no-files-found: ignore
- name: Wait for iOS Simulator boot
# Block on the boot we kicked off earlier. If the build took longer than the simulator's
# first-boot migration, this is instant; otherwise we wait the remaining seconds.
run: xcrun simctl bootstatus $IOS_SIM_UDID -b
- name: Run tests on iOS Simulator
shell: pwsh
continue-on-error: ${{ inputs.tolerate-test-failures == true }}
run: |
$ErrorActionPreference = 'Continue'
New-Item -ItemType Directory -Force -Path TestResults | Out-Null
$config = "${{ github.event.inputs.build-type || inputs.build-type || 'Debug' }}"
$filter = "${{ github.event.inputs.test-filter || inputs.test-filter }}"
$repeat = "${{ github.event.inputs.repeat || inputs.repeat || '1' }}"
# IOS_TEST_SUITES is single-line (Combined / project=set) OR multi-line (slnf path).
# Loop over them — one suite means one launch (matches the Combined case identically).
$suites = ($env:IOS_TEST_SUITES -split "`n") | Where-Object { $_ -ne '' }
$worstExit = 0
foreach ($suite in $suites) {
$appPath = Get-ChildItem -Path "bin/Tests/$suite/iOS-Vulkan/$config" -Recurse -Filter "$suite.app" -ErrorAction SilentlyContinue | Select-Object -First 1
if (-not $appPath) {
Write-Host "::warning::$suite.app not built — skipping"
continue
}
# Bundle id lives in the .app's Info.plist.
$plist = Join-Path $appPath.FullName 'Info.plist'
$package = (& /usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' $plist).Trim()
Write-Host "::group::Running $suite (bundle=$package)"
$driverArgs = @(
'-Package', $package,
'-Suite', $suite,
'-App', $appPath.FullName,
'-Simulator', $env:IOS_SIM_UDID,
'-ResultsDir', "$PWD/tests/local",
'-TimeoutSeconds', '1800',
'-KeepSimulator',
'-StreamLog'
)
if ($filter) { $driverArgs += @('-Filter', $filter) }
if ([int]$repeat -gt 1) { $driverArgs += @('-Repeat', $repeat) }
& pwsh tests/ios/run-ios-tests.ps1 @driverArgs
if ($LASTEXITCODE -ne 0 -and $worstExit -eq 0) { $worstExit = $LASTEXITCODE }
Write-Host "::endgroup::"
}
# Flatten per-suite TRX into TestResults/ for the test-reporting action (no recurse).
Get-ChildItem -Path 'tests/local' -Recurse -Filter '*.trx' -ErrorAction SilentlyContinue |
ForEach-Object { Copy-Item $_.FullName 'TestResults/' -Force }
if ($worstExit -ne 0) {
Write-Host "::error::At least one suite exited with non-zero code (worst=$worstExit)"
exit $worstExit
}
- name: Publish Test Report
if: always()
uses: phoenix-actions/test-reporting@v16
with:
name: 'Test Report: iOS Game (Vulkan)'
path: TestResults/*.trx
reporter: dotnet-trx
output-to: step-summary
list-tests: 'failed'
fail-on-error: ${{ inputs.tolerate-test-failures != true }}
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results-ios-vulkan
path: TestResults/
if-no-files-found: ignore
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: test-artifacts-ios-vulkan
path: tests/local/
if-no-files-found: ignore
- name: Collect crash reports
# macOS writes .ips crash reports to ~/Library/Logs/DiagnosticReports for any process
# killed unexpectedly. Captures both the test app's own crash AND SimMetalHost / other
# sim infrastructure crashes that take the test process down by side-effect.
if: always()
run: |
mkdir -p crash-reports
find ~/Library/Logs/DiagnosticReports -type f \( -name '*.ips' -o -name '*.crash' \) -mmin -60 -exec cp {} crash-reports/ \; 2>/dev/null || true
ls -la crash-reports/
- name: Upload crash reports
if: always()
uses: actions/upload-artifact@v7
with:
name: crash-reports-ios-vulkan
path: crash-reports/
if-no-files-found: ignore