-
Notifications
You must be signed in to change notification settings - Fork 1.2k
194 lines (177 loc) · 7.55 KB
/
Copy pathe2e-bridge.yml
File metadata and controls
194 lines (177 loc) · 7.55 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
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