Skip to content

Commit 2f48167

Browse files
ci(audience): run linux PlayMode under xvfb (SDK-317 / SDK-318)
- Reworks playmode-linux to run inside a docker container under xvfb, via .github/scripts/audience/playmode-linux.sh. game-ci/unity-test-runner@v4 hardcodes -nographics, so PlayMode tests came back inconclusive and silently passed. - Watchdog SIGTERMs Unity 30s after "Test run completed" so cells exit on suite finish; handles Unity 6's known shutdown hang. - -force-glcore at runtime skips the Unity 6 Vulkan init and matches the Unity 2021.3 default path. - Suppresses in-app log pane on Unity 6 Linux to skip llvmpipe rasterising UI Toolkit triangles per frame. - Stamps CI build info into Player.log on player startup; gated to CI runs only. - Mirrors SDK output and OnError fires to Debug.Log so failures land in Player.log. - DiskStore.ReadBatch treats missing queue dir as empty (matches existing guards). - Live-fire test SetUp ignores cleanup-time OnError fires so background flush cancellations do not fail unrelated tests. - Trims 30 unused packages from the sample-app manifest. - Extracts the macOS and Windows playmode runners and the Windows VS Build Tools setup to .github/scripts/, mirroring the Linux pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4017213 commit 2f48167

19 files changed

Lines changed: 778 additions & 507 deletions
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# PSScriptAnalyzer config for super-linter.
2+
# These rules don't apply to short CI helper scripts:
3+
# PSAvoidUsingWriteHost - Write-Host is the right call here. Write-Output
4+
# would be captured as the function's return value (e.g. Resolve-Unity
5+
# returns the editor path).
6+
# PSUseSingularNouns - the scripts aren't a PowerShell module; they have
7+
# functions like Invoke-Tests that genuinely act on a collection.
8+
9+
@{
10+
ExcludeRules = @(
11+
'PSAvoidUsingWriteHost',
12+
'PSUseSingularNouns'
13+
)
14+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Fails the cell when test-results.xml is missing, empty, or inconclusive.
2+
# NUnit marks tests "inconclusive" (not "failed") when the player never starts,
3+
# and dorny/test-reporter does not fail on inconclusive.
4+
# Workflow caller: .github/workflows/test-audience-sample-app.yml (playmode job).
5+
6+
$xml = "artifacts/test-results.xml"
7+
if (-not (Test-Path $xml) -or (Get-Item $xml).Length -eq 0) {
8+
Write-Host "::error::No test-results.xml at $xml."
9+
exit 1
10+
}
11+
12+
$content = Get-Content $xml -Raw
13+
14+
if ($content -match 'inconclusive="[1-9]') {
15+
Write-Host "::error::Tests came back inconclusive. Check xvfb and player launch."
16+
exit 1
17+
}
18+
19+
if ($content -notmatch 'passed="[1-9]' -and $content -notmatch 'failed="[1-9]') {
20+
Write-Host "::error::Zero tests passed and zero failed. The suite did not execute."
21+
exit 1
22+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
# Fails the cell when test-results.xml is missing, empty, or inconclusive.
3+
# NUnit marks tests "inconclusive" (not "failed") when the player never starts,
4+
# and dorny/test-reporter does not fail on inconclusive.
5+
# Workflow caller: .github/workflows/test-audience-sample-app.yml (playmode-linux job).
6+
7+
xml="artifacts/test-results.xml"
8+
test -s "$xml" || { echo "::error::No test-results.xml at $xml."; exit 1; }
9+
10+
if grep -qE 'inconclusive="[1-9]' "$xml"; then
11+
echo "::error::Tests came back inconclusive. Check xvfb and player launch."
12+
exit 1
13+
fi
14+
15+
if ! grep -qE 'passed="[1-9]' "$xml" && ! grep -qE 'failed="[1-9]' "$xml"; then
16+
echo "::error::Zero tests passed and zero failed. The suite did not execute."
17+
exit 1
18+
fi
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Cleans the Windows workspace before actions/checkout@v4 clones over it.
2+
# Workflow caller: .github/workflows/test-audience-sample-app.yml (playmode job).
3+
4+
# actions/checkout@v4 removes the prior workspace before cloning. If a
5+
# previous run's Unity build, IL2CPP linker, bee_backend, or shader compiler
6+
# is still holding handles, checkout dies with EBUSY on examples/audience.
7+
# Kill known offenders, then force-remove the workspace contents.
8+
Get-Process | Where-Object {
9+
$_.Name -like 'Unity*' -or
10+
$_.Name -like 'il2cpp*' -or
11+
$_.Name -like 'UnityShaderCompiler*' -or
12+
$_.Name -like 'UnityCrashHandler*' -or
13+
$_.Name -like 'bee_backend*' -or
14+
$_.Name -like 'mono*'
15+
} | ForEach-Object {
16+
Write-Host "Killing stale process: $($_.Name) (pid $($_.Id))"
17+
Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue
18+
}
19+
Start-Sleep -Seconds 3
20+
21+
$ws = "$env:GITHUB_WORKSPACE"
22+
if (-not (Test-Path $ws)) { return }
23+
for ($i = 1; $i -le 6; $i++) {
24+
try {
25+
Get-ChildItem -Path $ws -Force -ErrorAction Stop |
26+
Remove-Item -Recurse -Force -ErrorAction Stop
27+
Write-Host "Cleaned $ws on attempt ${i}"
28+
return
29+
} catch {
30+
Write-Host "Attempt ${i}: $($_.Exception.Message)"
31+
Start-Sleep -Seconds 3
32+
}
33+
}
34+
Write-Host "::warning::Workspace not fully cleaned; checkout may fail"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Ensures Visual Studio Build Tools (VC.Tools + Win10 SDK) are present on the runner.
2+
# Workflow caller: .github/workflows/test-audience-sample-app.yml (Windows IL2CPP cells).
3+
4+
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
5+
6+
# Match Unity's detection logic: vswhere requires VC.Tools (any version), registry
7+
# probe for any Win10 SDK at v10.0/InstallationFolder. Pinning a specific SDK
8+
# version in -requires is too strict; VCTools ships with whatever Win10 SDK is
9+
# current, and Unity accepts any.
10+
function Test-Toolchain {
11+
$vc = if (Test-Path $vswhere) {
12+
& $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2>$null
13+
} else { '' }
14+
$sdk = (Get-ItemProperty 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows\v10.0' -ErrorAction SilentlyContinue).InstallationFolder
15+
return @{ VcTools = $vc; Win10Sdk = $sdk }
16+
}
17+
18+
$state = Test-Toolchain
19+
if ($state.VcTools -and $state.Win10Sdk) {
20+
Write-Host "VC.Tools at: $($state.VcTools)"
21+
Write-Host "Win10 SDK at: $($state.Win10Sdk)"
22+
exit 0
23+
}
24+
Write-Host "Toolchain incomplete. VC.Tools='$($state.VcTools)' Win10Sdk='$($state.Win10Sdk)'"
25+
26+
Write-Host "::group::Install VS 2022 Build Tools (VCTools + Win10 SDK)"
27+
$installer = "$env:RUNNER_TEMP\vs_BuildTools.exe"
28+
Invoke-WebRequest -Uri 'https://aka.ms/vs/17/release/vs_BuildTools.exe' -OutFile $installer
29+
30+
$installArgs = @(
31+
'--quiet','--wait','--norestart','--nocache',
32+
'--add','Microsoft.VisualStudio.Workload.VCTools',
33+
'--add','Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
34+
'--add','Microsoft.VisualStudio.Component.Windows10SDK.20348',
35+
'--includeRecommended'
36+
)
37+
$p = Start-Process -FilePath $installer -ArgumentList $installArgs -Wait -PassThru -NoNewWindow
38+
# 3010 = success, reboot pending (tools are usable without reboot).
39+
if ($p.ExitCode -ne 0 -and $p.ExitCode -ne 3010) {
40+
Write-Host "::error::VS Build Tools installer exited $($p.ExitCode)"
41+
exit $p.ExitCode
42+
}
43+
Write-Host "::endgroup::"
44+
45+
$state = Test-Toolchain
46+
if (-not ($state.VcTools -and $state.Win10Sdk)) {
47+
Write-Host "::group::diagnostic"
48+
Write-Host "VC.Tools path (vswhere): '$($state.VcTools)'"
49+
Write-Host "Win10 SDK (registry v10.0/InstallationFolder): '$($state.Win10Sdk)'"
50+
Write-Host "--- all VS installations ---"
51+
if (Test-Path $vswhere) { & $vswhere -all -products * -format json }
52+
Write-Host "--- HKLM Win10 SDK roots ---"
53+
Get-ChildItem 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Microsoft SDKs\Windows' -ErrorAction SilentlyContinue | Format-List
54+
Write-Host "::endgroup::"
55+
Write-Host "::error::Install reported success but VC.Tools or Win10 SDK still not detected. Runner service account likely lacks admin to install system-wide. Install VS Build Tools manually on IMX_SDKBUILD: vs_BuildTools.exe --quiet --wait --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
56+
exit 1
57+
}
58+
Write-Host "Verified VC.Tools at: $($state.VcTools)"
59+
Write-Host "Verified Win10 SDK at: $($state.Win10Sdk)"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"unity_versions": [
3+
{ "version": "2021.3.45f2", "changeset": "88f88f591b2e" },
4+
{ "version": "6000.4.0f1", "changeset": "8cf496087c8f" },
5+
{ "version": "2022.3.62f2", "changeset": "7670c08855a9" }
6+
],
7+
"scripting_backends": ["IL2CPP", "Mono2x"],
8+
"desktop_targets": [
9+
{ "target": "StandaloneWindows64", "runner": ["self-hosted", "Windows", "X64"], "script": ".github/scripts/audience/playmode-windows.ps1" },
10+
{ "target": "StandaloneOSX", "runner": ["self-hosted", "macOS", "ARM64"], "script": ".github/scripts/audience/playmode-macos.sh" },
11+
{ "target": "StandaloneLinux64", "runner": "ubuntu-latest-8-cores", "script": ".github/scripts/audience/playmode-linux.sh" }
12+
],
13+
"mobile_targets": [
14+
{ "target": "Android", "method": "AndroidBuilder.Build" },
15+
{ "target": "iOS", "method": "IosBuilder.Build" }
16+
],
17+
"pr_exclude": [
18+
{ "unity": { "version": "2022.3.62f2", "changeset": "7670c08855a9" } }
19+
]
20+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
#!/bin/bash
2+
# Audience SDK PlayMode test runner for Linux: in-container body.
3+
# Runs inside the unityci/editor:ubuntu-X-linux-il2cpp-3 container.
4+
# Caller: .github/scripts/audience/playmode-linux.sh (host-side docker wrapper).
5+
6+
set -uo pipefail
7+
8+
LOG=/github/workspace/artifacts/unity.log
9+
ACTIVATION_LOG=/github/workspace/artifacts/activation.log
10+
RESULTS=/github/workspace/artifacts/test-results.xml
11+
PROJECT=/github/workspace/examples/audience
12+
13+
test_rc=1
14+
15+
activate_license() {
16+
unity-editor -batchmode -nographics -quit \
17+
-username "$UNITY_EMAIL" \
18+
-password "$UNITY_PASSWORD" \
19+
-serial "$UNITY_SERIAL" \
20+
-logFile - 2>&1 | tee "$ACTIVATION_LOG" || true
21+
22+
if grep -qE "License activation has failed|\[Licensing::Client\] Error: Code [0-9]+" "$ACTIVATION_LOG"; then
23+
echo "::error::Unity license activation failed."
24+
exit 1
25+
fi
26+
if ! grep -qE "Successfully activated the entitlement license" "$ACTIVATION_LOG"; then
27+
echo "::error::Unity license activation: no success marker in log."
28+
exit 1
29+
fi
30+
}
31+
32+
run_tests_with_watchdog() {
33+
# xvfb-run gives Unity a virtual X display. UI Toolkit needs GLX + render;
34+
# llvmpipe in the image provides software OpenGL so no GPU is needed.
35+
# -force-glcore skips the Unity 6 Vulkan init and matches the Unity 2021.3 default path.
36+
xvfb-run -a --server-args="-ac +extension GLX +render -noreset" -- \
37+
unity-editor \
38+
-batchmode \
39+
-force-glcore \
40+
-screen-fullscreen 0 \
41+
-screen-width 320 \
42+
-screen-height 240 \
43+
-projectPath "$PROJECT" \
44+
-runTests \
45+
-testPlatform StandaloneLinux64 \
46+
-testResults "$RESULTS" \
47+
-logFile "$LOG" &
48+
local unity_pid=$!
49+
50+
# Mirror Unity log to job stdout while the editor is alive.
51+
tail --pid=$unity_pid -F "$LOG" 2>/dev/null &
52+
53+
# Watchdog (vs fixed timeout) because per-version run length varies wildly:
54+
# Unity 2021.3 cells finish in ~2 min, Unity 6 in ~22 min, and Unity 6 has a
55+
# known post-test shutdown hang. SIGTERM 30 s after "Test run completed" so
56+
# each cell exits as soon as its suite finishes. 40 min hard cap as fallback.
57+
local deadline=$((SECONDS + 2400))
58+
local flush_deadline=0
59+
local kill_reason=""
60+
while kill -0 "$unity_pid" 2>/dev/null; do
61+
if [ "$SECONDS" -ge "$deadline" ]; then
62+
kill_reason="hard-cap-40m"
63+
break
64+
fi
65+
if [ "$flush_deadline" -eq 0 ] && grep -q "Test run completed" "$LOG" 2>/dev/null; then
66+
flush_deadline=$((SECONDS + 30))
67+
echo "[watchdog] saw \"Test run completed\" at ${SECONDS}s; SIGTERM after 30s flush window"
68+
fi
69+
if [ "$flush_deadline" -gt 0 ] && [ "$SECONDS" -ge "$flush_deadline" ]; then
70+
kill_reason="flush-window-elapsed"
71+
break
72+
fi
73+
sleep 5
74+
done
75+
76+
if [ -n "$kill_reason" ]; then
77+
echo "[watchdog] sending SIGTERM to Unity (reason: $kill_reason)"
78+
kill -TERM "$unity_pid" 2>/dev/null || true
79+
# 15 s grace, then SIGKILL.
80+
for _ in 1 2 3; do
81+
kill -0 "$unity_pid" 2>/dev/null || break
82+
sleep 5
83+
done
84+
if kill -0 "$unity_pid" 2>/dev/null; then
85+
echo "[watchdog] SIGTERM not honored, sending SIGKILL"
86+
kill -KILL "$unity_pid" 2>/dev/null || true
87+
fi
88+
fi
89+
90+
wait "$unity_pid" 2>/dev/null
91+
test_rc=$?
92+
if [ "$kill_reason" = "hard-cap-40m" ]; then
93+
echo "::warning::Unity hit the 40 min hard cap without logging \"Test run completed\". Inspect Player.log."
94+
fi
95+
}
96+
97+
capture_player_log() {
98+
# Player runs in a separate process from the editor; copy its Player.log so
99+
# HTTP traces and OnError fires are captured. Glob across companies / products.
100+
find /root/.config/unity3d -name "Player.log" 2>/dev/null | while IFS= read -r f; do
101+
co=$(basename "$(dirname "$(dirname "$f")")")
102+
pr=$(basename "$(dirname "$f")")
103+
cp "$f" "/github/workspace/artifacts/Player-${co}-${pr}.log" 2>/dev/null || true
104+
done
105+
}
106+
107+
return_license() {
108+
# Always return the seat to keep the activation pool from exhausting on reruns.
109+
unity-editor -batchmode -nographics -quit -returnlicense -logFile - 2>&1 || true
110+
}
111+
112+
activate_license
113+
run_tests_with_watchdog
114+
capture_player_log
115+
return_license
116+
117+
# Unity exits 2 on test failure or inconclusive; propagate so the step fails.
118+
exit "$test_rc"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
# Audience SDK PlayMode test runner for Linux: host-side docker wrapper.
3+
# Runs unityci/editor with the env and volume mounts the inner
4+
# playmode-linux-container.sh expects. Lives outside the container so the
5+
# workflow can launch all 3 desktop platforms from one matrix-shared.json entry.
6+
#
7+
# Manual docker run because game-ci/unity-test-runner@v4 hardcodes
8+
# -nographics. Without a virtual display every PlayMode test comes back
9+
# inconclusive, and the action's USE_EXIT_CODE=false suppresses Unity
10+
# exit 2, so cells went silently green.
11+
#
12+
# Workflow caller: .github/workflows/test-audience-sample-app.yml (playmode job).
13+
# Inputs (env): UNITY_VERSION, UNITY_EMAIL, UNITY_PASSWORD, UNITY_SERIAL,
14+
# AUDIENCE_TEST_PUBLISHABLE_KEY, AUDIENCE_SCRIPTING_BACKEND.
15+
16+
set -uo pipefail
17+
mkdir -p artifacts
18+
19+
docker run --rm \
20+
--workdir /github/workspace \
21+
--env UNITY_EMAIL --env UNITY_PASSWORD --env UNITY_SERIAL \
22+
--env AUDIENCE_TEST_PUBLISHABLE_KEY --env AUDIENCE_SCRIPTING_BACKEND \
23+
--env AUDIENCE_TEST_RUN_ID --env AUDIENCE_TEST_CELL_ID \
24+
--volume "$PWD":/github/workspace:z \
25+
--cpus=8 --memory=30487m \
26+
"unityci/editor:ubuntu-${UNITY_VERSION}-linux-il2cpp-3" \
27+
bash /github/workspace/.github/scripts/audience/playmode-linux-container.sh

0 commit comments

Comments
 (0)