195 lines
7.6 KiB
YAML
195 lines
7.6 KiB
YAML
name: E2E Bridge Smoke (deterministic, no LLM)
|
|
|
|
# Boots a headless Unity Editor, starts the Python MCP server's wire path, and
|
|
# drives a fixed sequence of real tool calls with exact assertions
|
|
# (Server/tests/e2e/bridge_smoke.py). Unlike claude-nl-suite.yml this needs
|
|
# NO Anthropic API key -- it is deterministic and cheap, so it can gate PRs and
|
|
# releases. It still needs Unity license secrets to boot the Editor.
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
pull_request:
|
|
paths:
|
|
- "MCPForUnity/Editor/**"
|
|
- "MCPForUnity/Runtime/**"
|
|
- "Server/src/**"
|
|
- "Server/tests/e2e/**"
|
|
- "tools/local_harness.py"
|
|
- ".github/workflows/e2e-bridge.yml"
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
UNITY_IMAGE: unityci/editor:ubuntu-2021.3.45f2-linux-il2cpp-3
|
|
|
|
jobs:
|
|
e2e-bridge:
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 40
|
|
steps:
|
|
- name: Detect Unity license secrets
|
|
id: detect
|
|
env:
|
|
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
|
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
|
run: |
|
|
set -e
|
|
if [ -n "$UNITY_LICENSE" ] || { [ -n "$UNITY_EMAIL" ] && [ -n "$UNITY_PASSWORD" ] && [ -n "$UNITY_SERIAL" ]; }; then
|
|
echo "unity_ok=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "unity_ok=false" >> "$GITHUB_OUTPUT"
|
|
echo "::warning::Unity license secrets absent; E2E bridge smoke will be skipped (not failed)."
|
|
fi
|
|
|
|
- uses: actions/checkout@v4
|
|
if: steps.detect.outputs.unity_ok == 'true'
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- uses: astral-sh/setup-uv@v4
|
|
if: steps.detect.outputs.unity_ok == 'true'
|
|
with:
|
|
python-version: "3.11"
|
|
|
|
- name: Install MCP server
|
|
if: steps.detect.outputs.unity_ok == 'true'
|
|
run: |
|
|
set -eux
|
|
uv venv
|
|
echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> "$GITHUB_ENV"
|
|
echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH"
|
|
uv pip install -e Server
|
|
|
|
# --- License staging (mirrors claude-nl-suite.yml) ---
|
|
- name: Decide license sources
|
|
if: steps.detect.outputs.unity_ok == 'true'
|
|
id: lic
|
|
shell: bash
|
|
env:
|
|
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
|
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
|
run: |
|
|
set -eu
|
|
use_ulf=false; use_ebl=false
|
|
[[ -n "${UNITY_LICENSE:-}" ]] && use_ulf=true
|
|
[[ -n "${UNITY_EMAIL:-}" && -n "${UNITY_PASSWORD:-}" && -n "${UNITY_SERIAL:-}" ]] && use_ebl=true
|
|
echo "use_ulf=$use_ulf" >> "$GITHUB_OUTPUT"
|
|
echo "use_ebl=$use_ebl" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Stage Unity .ulf license (from secret)
|
|
if: steps.detect.outputs.unity_ok == 'true' && steps.lic.outputs.use_ulf == 'true'
|
|
id: ulf
|
|
env:
|
|
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
|
|
shell: bash
|
|
run: |
|
|
set -eu
|
|
mkdir -p "$RUNNER_TEMP/unity-license-ulf" "$RUNNER_TEMP/unity-local/Unity"
|
|
f="$RUNNER_TEMP/unity-license-ulf/Unity_lic.ulf"
|
|
if printf "%s" "$UNITY_LICENSE" | base64 -d - >/dev/null 2>&1; then
|
|
printf "%s" "$UNITY_LICENSE" | base64 -d - > "$f"
|
|
else
|
|
printf "%s" "$UNITY_LICENSE" > "$f"
|
|
fi
|
|
chmod 600 "$f" || true
|
|
if grep -qi '<Signature>' "$f"; then
|
|
cp -f "$f" "$RUNNER_TEMP/unity-local/Unity/Unity_lic.ulf"
|
|
echo "ok=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "ok=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Activate Unity (EBL via container)
|
|
if: steps.detect.outputs.unity_ok == 'true' && steps.lic.outputs.use_ebl == 'true'
|
|
shell: bash
|
|
env:
|
|
UNITY_IMAGE: ${{ env.UNITY_IMAGE }}
|
|
UNITY_EMAIL: ${{ secrets.UNITY_EMAIL }}
|
|
UNITY_PASSWORD: ${{ secrets.UNITY_PASSWORD }}
|
|
UNITY_SERIAL: ${{ secrets.UNITY_SERIAL }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p "$RUNNER_TEMP/unity-config" "$RUNNER_TEMP/unity-local"
|
|
docker run --rm --network host \
|
|
-e HOME=/root -e UNITY_EMAIL -e UNITY_PASSWORD -e UNITY_SERIAL \
|
|
-v "$RUNNER_TEMP/unity-config:/root/.config/unity3d" \
|
|
-v "$RUNNER_TEMP/unity-local:/root/.local/share/unity3d" \
|
|
"$UNITY_IMAGE" bash -lc '
|
|
set -euxo pipefail
|
|
/opt/unity/Editor/Unity -batchmode -nographics -logFile - \
|
|
-username "$UNITY_EMAIL" -password "$UNITY_PASSWORD" -serial "$UNITY_SERIAL" -quit || true
|
|
'
|
|
|
|
- name: Warm up project (import Library once)
|
|
if: steps.detect.outputs.unity_ok == 'true'
|
|
shell: bash
|
|
env:
|
|
UNITY_IMAGE: ${{ env.UNITY_IMAGE }}
|
|
ULF_OK: ${{ steps.ulf.outputs.ok }}
|
|
run: |
|
|
set -euxo pipefail
|
|
manual_args=()
|
|
if [[ "${ULF_OK:-false}" == "true" ]]; then
|
|
manual_args=(-manualLicenseFile "/root/.local/share/unity3d/Unity/Unity_lic.ulf")
|
|
fi
|
|
docker run --rm --network host \
|
|
-e HOME=/root \
|
|
-v "${{ github.workspace }}:${{ github.workspace }}" -w "${{ github.workspace }}" \
|
|
-v "$RUNNER_TEMP/unity-config:/root/.config/unity3d" \
|
|
-v "$RUNNER_TEMP/unity-local:/root/.local/share/unity3d" \
|
|
-v "$RUNNER_TEMP/unity-cache:/root/.cache/unity3d" \
|
|
"$UNITY_IMAGE" /opt/unity/Editor/Unity -batchmode -nographics -logFile - \
|
|
-projectPath "${{ github.workspace }}/TestProjects/UnityMCPTests" \
|
|
"${manual_args[@]}" -quit
|
|
|
|
- name: Clean old MCP status
|
|
if: steps.detect.outputs.unity_ok == 'true'
|
|
run: |
|
|
set -eux
|
|
mkdir -p "$GITHUB_WORKSPACE/.unity-mcp"
|
|
rm -f "$GITHUB_WORKSPACE/.unity-mcp"/unity-mcp-status-*.json || true
|
|
|
|
- name: Run headless bridge harness (boot + wait + smoke/editmode/playmode)
|
|
if: steps.detect.outputs.unity_ok == 'true'
|
|
shell: bash
|
|
env:
|
|
UNITY_IMAGE: ${{ env.UNITY_IMAGE }}
|
|
ULF_OK: ${{ steps.ulf.outputs.ok }}
|
|
run: |
|
|
set -euxo pipefail
|
|
# In --ci mode the harness drives the DockerLauncher: it runs the same
|
|
# docker container (repo .unity-mcp status dir, docker liveness/teardown,
|
|
# log redaction), waits on the status file, derives the instance, then
|
|
# runs the smoke + EditMode + PlayMode legs over the bridge.
|
|
license_args=()
|
|
if [[ "${ULF_OK:-false}" == "true" ]]; then
|
|
license_args=(--editor-arg -manualLicenseFile \
|
|
--editor-arg "/root/.local/share/unity3d/Unity/Unity_lic.ulf")
|
|
fi
|
|
python3 tools/local_harness.py --ci \
|
|
--legs smoke,editmode,playmode \
|
|
--project-path TestProjects/UnityMCPTests \
|
|
--reports reports \
|
|
"${license_args[@]}"
|
|
|
|
- name: Unity logs on failure
|
|
if: failure() && steps.detect.outputs.unity_ok == 'true'
|
|
run: docker logs unity-mcp --tail 200 | sed -E 's/((email|serial|license|password|token)[^[:space:]]*)/[REDACTED]/Ig' || true
|
|
|
|
- name: Upload E2E report
|
|
if: always() && steps.detect.outputs.unity_ok == 'true'
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: e2e-bridge-report
|
|
path: reports/junit-*.xml
|
|
if-no-files-found: ignore
|