@@ -151,39 +151,271 @@ jobs:
151151
152152 unity-testbed-headless :
153153 runs-on : ubuntu-latest
154- needs : docker-ci
154+ timeout-minutes : 35
155+ env :
156+ UNITY_EDITOR_IMAGE : unityci/editor:ubuntu-2019.4.41f2-linux-il2cpp-3
155157 steps :
156158 - name : Checkout
157159 uses : actions/checkout@v4
158160
161+ - name : Check Unity credentials
162+ id : unity-creds
163+ env :
164+ UNITY_EMAIL : ${{ secrets.UNITY_EMAIL }}
165+ UNITY_PASSWORD : ${{ secrets.UNITY_PASSWORD }}
166+ UNITY_TOTP_KEY : ${{ secrets.UNITY_TOTP_KEY }}
167+ run : |
168+ set -euo pipefail
169+
170+ missing=0
171+ [ -n "${UNITY_EMAIL:-}" ] || missing=1
172+ [ -n "${UNITY_PASSWORD:-}" ] || missing=1
173+ [ -n "${UNITY_TOTP_KEY:-}" ] || missing=1
174+
175+ if [ "$missing" -eq 1 ]; then
176+ echo "should_run=false" >> "$GITHUB_OUTPUT"
177+ echo "Missing one or more required UNITY_* secrets (UNITY_EMAIL, UNITY_PASSWORD, UNITY_TOTP_KEY). UnityTestBed will be skipped."
178+ else
179+ echo "should_run=true" >> "$GITHUB_OUTPUT"
180+ echo "All required UNITY_* secrets are present. UnityTestBed will run."
181+ fi
182+
183+ - name : Setup Node
184+ if : steps.unity-creds.outputs.should_run == 'true'
185+ uses : actions/setup-node@v4
186+ with :
187+ node-version : " 20"
188+
189+ - name : Cache npm
190+ if : steps.unity-creds.outputs.should_run == 'true'
191+ uses : actions/cache@v4
192+ with :
193+ path : ~/.npm
194+ key : unity-license-activate-npm-${{ runner.os }}-2be4152c6ade907c090817c988588d5d9f3bc974
195+ restore-keys : |
196+ unity-license-activate-npm-${{ runner.os }}-
197+
198+ - name : Install unity-license-activate
199+ if : steps.unity-creds.outputs.should_run == 'true'
200+ run : |
201+ npm install --global github:game-ci/unity-license-activate#2be4152c6ade907c090817c988588d5d9f3bc974
202+ npm install --global unity-verify-code
203+
204+ - name : Restore Unity editor image cache
205+ if : steps.unity-creds.outputs.should_run == 'true'
206+ id : unity-image-cache-restore
207+ uses : actions/cache/restore@v4
208+ with :
209+ path : /tmp/unity-editor-image.tar
210+ key : unity-editor-image-${{ runner.os }}-ubuntu-2019.4.41f2-linux-il2cpp-3
211+
212+ - name : Load cached Unity editor image
213+ if : steps.unity-creds.outputs.should_run == 'true' && steps.unity-image-cache-restore.outputs.cache-hit == 'true'
214+ run : docker load -i /tmp/unity-editor-image.tar
215+
216+ - name : Pull Unity editor image
217+ if : steps.unity-creds.outputs.should_run == 'true' && steps.unity-image-cache-restore.outputs.cache-hit != 'true'
218+ id : unity-image-pull
219+ run : docker pull "$UNITY_EDITOR_IMAGE"
220+
221+ - name : Start Unity test container
222+ if : steps.unity-creds.outputs.should_run == 'true'
223+ id : unity-container
224+ run : |
225+ set -euo pipefail
226+
227+ workdir="$PWD/_unity-license"
228+ mkdir -p "$workdir"
229+ container_name="unity-testbed-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
230+
231+ docker rm -f "$container_name" >/dev/null 2>&1 || true
232+ docker run -d \
233+ --name "$container_name" \
234+ --workdir /github/workspace \
235+ --volume "$PWD:/github/workspace" \
236+ --volume "$workdir:/license" \
237+ "$UNITY_EDITOR_IMAGE" \
238+ /bin/bash -lc "sleep infinity" >/dev/null
239+
240+ echo "container_name=$container_name" >> "$GITHUB_OUTPUT"
241+
242+ - name : Generate fresh ALF
243+ if : steps.unity-creds.outputs.should_run == 'true'
244+ id : unity-alf-fresh
245+ run : |
246+ set -euo pipefail
247+
248+ workdir="$PWD/_unity-license"
249+ container_name="${{ steps.unity-container.outputs.container_name }}"
250+
251+ rm -f "$workdir"/*.alf || true
252+
253+ set +e
254+ timeout 60 docker exec "$container_name" /bin/bash -lc \
255+ 'cd /license && unity-editor -logFile /license/unity-alf.log -batchmode -nographics -quit -createManualActivationFile'
256+ alf_exit=$?
257+ set -e
258+
259+ alf_path="$(find "$workdir" -maxdepth 1 -type f -name '*.alf' | head -n 1)"
260+ if [ -z "${alf_path:-}" ]; then
261+ echo "::error::No .alf generated for Unity license activation."
262+ echo "ALF generation exit code: $alf_exit"
263+ tail -n 200 "$workdir/unity-alf.log" || true
264+ exit 1
265+ fi
266+
267+ echo "alf_path=$alf_path" >> "$GITHUB_OUTPUT"
268+
269+ - name : Exchange fresh ALF to ULF
270+ if : steps.unity-creds.outputs.should_run == 'true'
271+ id : unity-license-fresh
272+ env :
273+ UNITY_EMAIL : ${{ secrets.UNITY_EMAIL }}
274+ UNITY_PASSWORD : ${{ secrets.UNITY_PASSWORD }}
275+ UNITY_TOTP_KEY : ${{ secrets.UNITY_TOTP_KEY }}
276+ run : |
277+ set -euo pipefail
278+
279+ workdir="$PWD/_unity-license"
280+ fresh_alf="${{ steps.unity-alf-fresh.outputs.alf_path }}"
281+
282+ if [ -z "${fresh_alf:-}" ] || [ ! -s "$fresh_alf" ]; then
283+ echo "::error::Fresh ALF missing before exchange."
284+ exit 1
285+ fi
286+
287+ cd "$workdir"
288+ rm -f "$workdir"/*.ulf || true
289+
290+ fresh_exit=1
291+ for attempt in 1 2 3; do
292+ set +e
293+ timeout 60 unity-license-activate \
294+ "$UNITY_EMAIL" \
295+ "$UNITY_PASSWORD" \
296+ "$fresh_alf" \
297+ --authenticator-key "$UNITY_TOTP_KEY" \
298+ >"$workdir/unity-license-activate-fresh.stdout" \
299+ 2>"$workdir/unity-license-activate-fresh.stderr"
300+ fresh_exit=$?
301+ set -e
302+ if [ "$fresh_exit" -eq 0 ]; then
303+ break
304+ fi
305+ if grep -q "2FA (Email)" "$workdir/unity-license-activate-fresh.stdout"; then
306+ echo "::error::Unity requested Email 2FA, so UNITY_TOTP_KEY cannot be used. Configure this Unity account for Authenticator App 2FA or provide mailbox automation for unity-verify-code."
307+ break
308+ fi
309+ if grep -Eq "ERR_NETWORK_CHANGED|ETIMEDOUT|Navigation timeout" "$workdir/unity-license-activate-fresh.stderr" "$workdir/unity-license-activate-fresh.stdout"; then
310+ echo "Transient network/navigation failure on fresh ALF exchange attempt $attempt/3; retrying."
311+ sleep 3
312+ else
313+ break
314+ fi
315+ done
316+
317+ if [ "$fresh_exit" -ne 0 ]; then
318+ echo "::error::Fresh ALF exchange failed with exit code $fresh_exit."
319+ tail -n 200 "$workdir/unity-license-activate-fresh.stdout" || true
320+ tail -n 200 "$workdir/unity-license-activate-fresh.stderr" || true
321+ exit 1
322+ fi
323+
324+ ulf_path="$(find "$workdir" -maxdepth 1 -type f -name '*.ulf' | head -n 1)"
325+ if [ -z "${ulf_path:-}" ]; then
326+ echo "::error::No .ulf produced by fresh ALF exchange."
327+ exit 1
328+ fi
329+
159330 - name : Setup .NET
331+ if : steps.unity-creds.outputs.should_run == 'true'
160332 uses : actions/setup-dotnet@v4
161333 with :
162334 dotnet-version : " 8.0.x"
163335
164336 - name : Build Unity test dependencies (net45)
337+ if : steps.unity-creds.outputs.should_run == 'true'
165338 run : |
166339 cd src
167340 dotnet build MoonSharp.Interpreter.Tests/MoonSharp.Interpreter.Tests.csproj -c Release -f net45 -v minimal
168341
169342 - name : Sync UnityTestBed plugins
343+ if : steps.unity-creds.outputs.should_run == 'true'
170344 run : |
171345 cd src
172346 ./Unity/ResynchAssets.sh
173347
174- - name : Run UnityTestBed scene in batchmode (GameCI)
175- uses : game-ci/unity-builder@v4
176- env :
177- UNITY_LICENSE : ${{ secrets.UNITY_LICENSE }}
178- UNITY_EMAIL : ${{ secrets.UNITY_EMAIL }}
179- UNITY_PASSWORD : ${{ secrets.UNITY_PASSWORD }}
348+ - name : Activate Unity license inside container
349+ if : steps.unity-creds.outputs.should_run == 'true'
350+ id : unity-activate-container
351+ run : |
352+ set -euo pipefail
353+
354+ workdir="$PWD/_unity-license"
355+ container_name="${{ steps.unity-container.outputs.container_name }}"
356+ ulf_path="$(find "$workdir" -maxdepth 1 -type f -name '*.ulf' | head -n 1)"
357+
358+ if [ -z "${ulf_path:-}" ] || [ ! -s "$ulf_path" ]; then
359+ echo "::error::No .ulf available to install in Unity container."
360+ exit 1
361+ fi
362+
363+ ulf_file="$(basename "$ulf_path")"
364+
365+ set +e
366+ timeout 60 docker exec "$container_name" /bin/bash -lc \
367+ "unity-editor -logFile /license/unity-activate.log -batchmode -nographics -quit -manualLicenseFile /license/$ulf_file"
368+ activate_exit=$?
369+ set -e
370+
371+ if [ "$activate_exit" -ne 0 ]; then
372+ if grep -q "License file loaded." "$workdir/unity-activate.log"; then
373+ echo "::warning::Unity returned exit $activate_exit during manual license install, but log confirms license file loaded. Continuing."
374+ else
375+ echo "::error::Unity license install failed in container (exit $activate_exit)."
376+ tail -n 200 "$workdir/unity-activate.log" || true
377+ exit 1
378+ fi
379+ fi
380+
381+ - name : Run UnityTestBed scene in batchmode (Docker)
382+ if : steps.unity-creds.outputs.should_run == 'true'
383+ timeout-minutes : 5
384+ run : |
385+ set -euo pipefail
386+
387+ container_name="${{ steps.unity-container.outputs.container_name }}"
388+
389+ docker exec "$container_name" /bin/bash -lc \
390+ 'unity-editor -batchmode -nographics -quit -projectPath /github/workspace/src/Unity/UnityTestBed -executeMethod MoonSharp.UnityTestBed.BatchRunner.Run -logFile -'
391+
392+ - name : Export Unity editor image for cache
393+ if : always() && steps.unity-creds.outputs.should_run == 'true' && steps.unity-image-pull.conclusion == 'success'
394+ id : unity-image-cache-export
395+ run : |
396+ set -euo pipefail
397+ if docker image inspect "$UNITY_EDITOR_IMAGE" >/dev/null 2>&1; then
398+ docker save "$UNITY_EDITOR_IMAGE" -o /tmp/unity-editor-image.tar
399+ echo "has_tar=true" >> "$GITHUB_OUTPUT"
400+ else
401+ echo "has_tar=false" >> "$GITHUB_OUTPUT"
402+ fi
403+
404+ - name : Save Unity editor image cache
405+ if : always() && steps.unity-creds.outputs.should_run == 'true' && steps.unity-image-pull.conclusion == 'success' && steps.unity-image-cache-export.outputs.has_tar == 'true'
406+ uses : actions/cache/save@v4
180407 with :
181- projectPath : src/Unity/UnityTestBed
182- unityVersion : 2019.4.41f2
183- targetPlatform : StandaloneLinux64
184- buildMethod : MoonSharp.UnityTestBed.BatchRunner.Run
185- allowDirtyBuild : true
186- customParameters : -batchmode -nographics -logFile -
408+ path : /tmp/unity-editor-image.tar
409+ key : unity-editor-image-${{ runner.os }}-ubuntu-2019.4.41f2-linux-il2cpp-3
410+
411+ - name : Stop Unity test container
412+ if : always() && steps.unity-creds.outputs.should_run == 'true' && steps.unity-container.outputs.container_name != ''
413+ run : |
414+ docker rm -f "${{ steps.unity-container.outputs.container_name }}" >/dev/null 2>&1 || true
415+
416+ - name : UnityTestBed skipped
417+ if : steps.unity-creds.outputs.should_run != 'true'
418+ run : ' echo "UnityTestBed skipped: UNITY_EMAIL, UNITY_PASSWORD, or UNITY_TOTP_KEY is missing."'
187419
188420 devtools-build :
189421 runs-on : ubuntu-latest
0 commit comments