Skip to content

Commit 214467e

Browse files
dsarnoclaude
andcommitted
Add Tier 2 CI: Godot handler tests via headless editor + Docker
Uses barichello/godot-ci:4.6.2 with Godot pre-installed. Starts MCP server, launches Godot headless with plugin, calls run_tests via MCP HTTP endpoint, asserts 0 failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c25b51b commit 214467e

1 file changed

Lines changed: 98 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
branches: [main]
88

99
jobs:
10-
test:
10+
python-tests:
1111
name: Python ${{ matrix.python-version }} / ${{ matrix.os }}
1212
runs-on: ${{ matrix.os }}
1313
strategy:
@@ -32,3 +32,100 @@ jobs:
3232

3333
- name: Lint
3434
run: ruff check src/ tests/
35+
36+
godot-tests:
37+
name: Godot handler tests
38+
runs-on: ubuntu-latest
39+
container:
40+
image: barichello/godot-ci:4.6.2
41+
42+
steps:
43+
- uses: actions/checkout@v4
44+
45+
- name: Set up Python
46+
run: |
47+
apt-get update -qq && apt-get install -y -qq python3 python3-pip python3-venv > /dev/null
48+
python3 -m venv /tmp/venv
49+
/tmp/venv/bin/pip install -e ".[dev]" > /dev/null
50+
51+
- name: Start MCP server
52+
run: |
53+
/tmp/venv/bin/python -m godot_ai --transport streamable-http --port 8000 &
54+
# Wait for server to be ready
55+
for i in $(seq 1 30); do
56+
if curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8000/mcp -X POST \
57+
-H "Content-Type: application/json" \
58+
-H "Accept: application/json, text/event-stream" \
59+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"ci","version":"1.0"}}}' | grep -q 200; then
60+
echo "Server ready"
61+
break
62+
fi
63+
sleep 1
64+
done
65+
66+
- name: Run Godot with plugin (headless)
67+
run: |
68+
# Import the project first (generates .godot/ cache)
69+
timeout 30 godot --headless --path test_project --import || true
70+
# Start Godot editor headless — plugin will connect to server
71+
timeout 60 godot --headless --path test_project --editor &
72+
GODOT_PID=$!
73+
# Wait for plugin to connect (watch server logs for session)
74+
for i in $(seq 1 30); do
75+
if curl -s http://127.0.0.1:8000/mcp -X POST \
76+
-H "Content-Type: application/json" \
77+
-H "Accept: application/json, text/event-stream" \
78+
-d '{"jsonrpc":"2.0","id":99,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"ci","version":"1.0"}}}' 2>/dev/null | grep -q "Godot AI"; then
79+
break
80+
fi
81+
sleep 2
82+
done
83+
# Give plugin a moment to fully initialize
84+
sleep 3
85+
86+
- name: Run handler tests via MCP
87+
run: |
88+
# Initialize a session
89+
SESSION_ID=$(curl -si http://127.0.0.1:8000/mcp -X POST \
90+
-H "Content-Type: application/json" \
91+
-H "Accept: application/json, text/event-stream" \
92+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"ci","version":"1.0"}}}' 2>&1 | grep -i 'mcp-session-id' | tr -d '\r' | awk '{print $2}')
93+
94+
curl -s http://127.0.0.1:8000/mcp -X POST \
95+
-H "Content-Type: application/json" \
96+
-H "Accept: application/json, text/event-stream" \
97+
-H "Mcp-Session-Id: $SESSION_ID" \
98+
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}' > /dev/null
99+
100+
# Call run_tests
101+
RESULT=$(curl -s http://127.0.0.1:8000/mcp -X POST \
102+
-H "Content-Type: application/json" \
103+
-H "Accept: application/json, text/event-stream" \
104+
-H "Mcp-Session-Id: $SESSION_ID" \
105+
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"run_tests","arguments":{}}}')
106+
107+
echo "$RESULT" | python3 -c "
108+
import json, sys
109+
raw = sys.stdin.read()
110+
# Parse SSE format
111+
for line in raw.split('\n'):
112+
if line.startswith('data: '):
113+
data = json.loads(line[6:])
114+
content = data.get('result', {}).get('structuredContent', {})
115+
if not content:
116+
text = data.get('result', {}).get('content', [{}])[0].get('text', '{}')
117+
content = json.loads(text)
118+
passed = content.get('passed', 0)
119+
failed = content.get('failed', 0)
120+
total = content.get('total', 0)
121+
print(f'Godot tests: {passed}/{total} passed, {failed} failed')
122+
if content.get('failures'):
123+
for f in content['failures']:
124+
print(f' FAIL: {f[\"suite\"]}.{f[\"test\"]}: {f[\"message\"]}')
125+
if failed > 0:
126+
sys.exit(1)
127+
if total == 0:
128+
print('ERROR: No tests ran')
129+
sys.exit(1)
130+
break
131+
"

0 commit comments

Comments
 (0)