Skip to content
Closed
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
174 changes: 174 additions & 0 deletions .github/workflows/unity-cli-experiment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
name: Unity CLI experiment

# An experiment, not a required check: can the official Unity CLI replace game-ci/unity-test-runner in the Unity
# EditMode job of ci.yml, and does it make the job simpler or faster? It runs the same throwaway project and tests on
# the bare runner, without a container. The CLI installs the Editor, the Licensing Client inside that Editor takes the
# Personal seat (`unity license activate --personal` rejects CI credentials), and `unity test` runs the tests.
# This file does not match the Unity plan regex of ci.yml, so a PR that changes only this file skips the GameCI matrix
# and the two never compete for the Personal seats of one account.

on:
pull_request:
paths:
- .github/workflows/unity-cli-experiment.yml

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
unity-tests:
name: Unity CLI EditMode (${{ matrix.unity }})
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
# The versions of ci.yml. All of them start at once, as there: on the bare runner each job is a new machine
# and asks for a seat of its own, while the GameCI container reuses one machine id ("SameMachine").
unity: [6000.0.53f1, 6000.1.17f1, 6000.2.15f1, 6000.3.26f1, 6000.4.12f1, 6000.5.11f1, 6000.6.5f1]
env:
UNITY: ${{ matrix.unity }}
# The CLI release this job was measured with. install.sh verifies the binary against that release's manifest.
UNITY_CLI_VERSION: 1.0.0-beta.13
UNITY_NON_INTERACTIVE: 1
UNITY_NO_CONSENT_PROMPT: 1
UNITY_NO_CRASH_REPORT: 1
UNITY_NO_UPDATE_CHECK: 1
UNITY_NO_BANNER: 1
steps:
# What the test project reads: the package and the script that builds the project.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
sparse-checkout: |
Aspid.FastTools/Packages/tech.aspid.fasttools
scripts

- name: Install the Unity CLI
run: |
curl -fsSL https://unity.com/install.sh | bash
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
# A restored cache holds the Editor folder only: the CLI's install path is set to this root on every run.
# `unity install-path --set` rejects a path with `..`, so it is not built from github.workspace.
echo "EDITORS=$HOME/unity-editors" >> "$GITHUB_ENV"
"$HOME/.local/bin/unity" --version

# The installed Editor, not the downloaded archives: restoring the folder skips both the download and the unpack.
- id: editor-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ env.EDITORS }}/${{ matrix.unity }}
key: unity-editor-${{ runner.os }}-${{ matrix.unity }}

- name: Install the Unity Editor
run: |
mkdir -p "$EDITORS"
unity install-path --set "$EDITORS"
if [ ! -x "$EDITORS/$UNITY/Editor/Unity" ]; then
unity install "$UNITY" --yes --accept-eula
fi
unity editors -i --format json
editor="$EDITORS/$UNITY/Editor/Unity"
if [ ! -x "$editor" ]; then
echo "::error::No Editor at $editor after the install; see the list above."
exit 1
fi
echo "UNITY_EDITOR=$editor" >> "$GITHUB_ENV"
du -h -d 4 "$EDITORS/$UNITY" | sort -h | tail -n 8
du -h -d 1 "$EDITORS/$UNITY/Editor/Data/Resources/PackageManager" | sort -h
df -h "$EDITORS"

# Saved right after the install, so a failed test run still leaves the Editor cached for the next one.
# One entry is 4.5 GB and the repository holds 10 GB, so only the minimum version is cached here.
- if: steps.editor-cache.outputs.cache-hit != 'true' && matrix.unity == '6000.0.53f1'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ env.EDITORS }}/${{ matrix.unity }}
key: ${{ steps.editor-cache.outputs.cache-primary-key }}

- name: Create the test project
run: scripts/make-unity-test-project.sh ci-unity-project "$UNITY"

# The client exits 0 also when Unity assigned no seat; only a "Seat ID:" line in its output means a seat. Unity
# sometimes assigns none when several jobs activate one account at once, so that case gets two more attempts.
# GitHub masks both secrets in the log.
- name: Activate the Personal license
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
run: |
client="$(dirname "$UNITY_EDITOR")/Data/Resources/Licensing/Client/Unity.Licensing.Client"
log="$RUNNER_TEMP/activate.log"
for attempt in 1 2 3; do
"$client" --activate-all --include-personal --username "$UNITY_EMAIL" --password "$UNITY_PASSWORD" 2>&1 | tee "$log" || true
if grep -q 'Seat ID:' "$log"; then exit 0; fi
if grep -qiE 'invalid (username|password|credentials)|two.?factor|verification code' "$log"; then break; fi
if [ "$attempt" -lt 3 ]; then
echo "::warning::No Personal seat on attempt $attempt; trying again."
sleep $((30 * attempt))
fi
done
echo "::error::The Licensing Client assigned no Personal seat; see its output above."
exit 1

# Exit 8: the tests ran and some failed. Exit 6: no verdict (license, compile error, crash, timeout); that one
# gets a second attempt, as in ci.yml. The Editor log goes to a file next to the results.
# The tests that open an EditorWindow or paint IMGUI need a graphics device: with -nographics 68 of them fail
# with "No graphic device is available to initialize the view". The Editor runs on a virtual X display instead,
# as in the GameCI image, whose unity-editor wrapper calls xvfb-run.
- name: Run the EditMode tests
run: |
results="$GITHUB_WORKSPACE/unity-test-results"
for attempt in 1 2; do
code=0
xvfb-run -a -s '-screen 0 1024x768x24' \
unity test ci-unity-project --editor-path "$UNITY_EDITOR" --mode EditMode \
--report-format nunit,junit --output "$results/editmode-results.xml" --timeout 1200 --format github \
-- -aspidFastToolsStrictSetup -logFile "$results/editor-$attempt.log" || code=$?
echo "unity test exited with $code."
if [ "$code" -ne 0 ]; then
echo "::group::Editor log, last 200 lines"
tail -n 200 "$results/editor-$attempt.log" || true
echo "::endgroup::"
fi
if [ "$code" -ne 6 ] || [ "$attempt" -eq 2 ]; then exit "$code"; fi
echo "::warning::The test run gave no verdict (exit 6); trying once more."
sleep 30
done

# `unity license status` exits 0 while a license is active and 4 when none is, so it tells whether the seat is
# back. Neither CLI route returns a Personal seat: `unity license return` answers "free Personal licenses can't
# be returned", and `Unity.Licensing.Client --return-ulf` fails with "Ulf license file not found (1404)", since
# the seat is an entitlement and not a .ulf. The Editor's -returnlicense does it, as in GameCI.
- name: Return the Personal license
if: always() && env.UNITY_EDITOR != ''
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
run: |
unity license status || exit 0
mkdir -p "$RUNNER_TEMP/return-license"
"$UNITY_EDITOR" -batchmode -nographics -quit -returnlicense -username "$UNITY_EMAIL" -password "$UNITY_PASSWORD" \
-projectPath "$RUNNER_TEMP/return-license" -logFile - || true
if unity license status; then
echo "::warning::The Personal seat may still be held; release it at https://id.unity.com."
fi

# The Editor log can name the account; the uploaded copy must not.
- name: Remove the account from the Editor logs
if: always()
env:
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
run: |
[ -n "$UNITY_EMAIL" ] || exit 0
shopt -s nullglob
for f in unity-test-results/*.log; do perl -pi -e 's/\Q$ENV{UNITY_EMAIL}\E/***/g' "$f"; done

- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
if: always()
with:
name: unity-cli-test-results-${{ matrix.unity }}
path: unity-test-results
Loading