Skip to content

Commit 9dc1e5d

Browse files
committed
Add more godot tests in CI, fix a GC crash on native (after implementing async functions)
1 parent b56f1d0 commit 9dc1e5d

13 files changed

Lines changed: 694 additions & 12 deletions

.github/workflows/build-loreline.yml

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,90 @@ jobs:
14481448
compression-level: 0
14491449

14501450

1451+
loreline-godot-test-linux:
1452+
1453+
needs: [loreline-godot]
1454+
runs-on: ubuntu-22.04
1455+
steps:
1456+
1457+
- uses: actions/checkout@v6
1458+
1459+
- name: Download assembled Godot package
1460+
uses: actions/download-artifact@v7
1461+
with:
1462+
name: loreline-godot.zip
1463+
path: .
1464+
1465+
- name: Extract package
1466+
run: |
1467+
mkdir -p godot-pkg
1468+
unzip -qo loreline-godot.zip -d godot-pkg
1469+
1470+
- name: Install Godot 4.6.1 stable
1471+
run: |
1472+
curl -fsSL -o /tmp/godot.zip https://github.com/godotengine/godot/releases/download/4.6.1-stable/Godot_v4.6.1-stable_linux.x86_64.zip
1473+
unzip -q /tmp/godot.zip -d /tmp/godot-bin
1474+
sudo mv /tmp/godot-bin/Godot_v4.6.1-stable_linux.x86_64 /usr/local/bin/godot
1475+
sudo chmod +x /usr/local/bin/godot
1476+
godot --version
1477+
1478+
- name: Import sample project
1479+
run: godot --headless --path godot-pkg/sample --import || true
1480+
1481+
- name: Run save/restore test scene
1482+
run: bash godot-pkg/sample/run-headless-test.sh scenes/test_saverestore.tscn "TEST PASSED"
1483+
1484+
- name: Run async test scene
1485+
run: bash godot-pkg/sample/run-headless-test.sh scenes/test_async.tscn "FINISHED" "DIALOGUE: Your score is 42." "[async] fetchScore resolving"
1486+
1487+
- name: Run GC regression scene (must survive 35s of post-FINISHED idle, ≥2 forced collects)
1488+
run: RUN_TIMEOUT=60 bash godot-pkg/sample/run-headless-test.sh scenes/test_gc_crash_repro.tscn "GC_REGRESSION_OK"
1489+
1490+
1491+
loreline-godot-test-web:
1492+
1493+
needs: [loreline-godot]
1494+
runs-on: ubuntu-22.04
1495+
steps:
1496+
1497+
- uses: actions/checkout@v6
1498+
1499+
- name: Download assembled Godot package
1500+
uses: actions/download-artifact@v7
1501+
with:
1502+
name: loreline-godot.zip
1503+
path: .
1504+
1505+
- name: Extract package
1506+
run: |
1507+
mkdir -p godot-pkg
1508+
unzip -qo loreline-godot.zip -d godot-pkg
1509+
1510+
- name: Install Godot 4.6.1 stable
1511+
run: |
1512+
curl -fsSL -o /tmp/godot.zip https://github.com/godotengine/godot/releases/download/4.6.1-stable/Godot_v4.6.1-stable_linux.x86_64.zip
1513+
unzip -q /tmp/godot.zip -d /tmp/godot-bin
1514+
sudo mv /tmp/godot-bin/Godot_v4.6.1-stable_linux.x86_64 /usr/local/bin/godot
1515+
sudo chmod +x /usr/local/bin/godot
1516+
godot --version
1517+
1518+
- name: Install Godot 4.6.1 export templates
1519+
run: |
1520+
curl -fsSL -o /tmp/templates.tpz https://github.com/godotengine/godot/releases/download/4.6.1-stable/Godot_v4.6.1-stable_export_templates.tpz
1521+
mkdir -p /tmp/templates-extract
1522+
unzip -q /tmp/templates.tpz -d /tmp/templates-extract
1523+
mkdir -p "$HOME/.local/share/godot/export_templates/4.6.1.stable"
1524+
mv /tmp/templates-extract/templates/* "$HOME/.local/share/godot/export_templates/4.6.1.stable/"
1525+
1526+
- name: Setup Node
1527+
uses: actions/setup-node@v5
1528+
with:
1529+
node-version: 22
1530+
1531+
- name: Run web smoke test (export + serve + Playwright)
1532+
run: bash godot-pkg/sample/run-web-test.sh
1533+
1534+
14511535
loreline-sdl3:
14521536

14531537
needs: [loreline-mac, loreline-windows, loreline-linux, loreline-ios, loreline-android]
@@ -1682,7 +1766,7 @@ jobs:
16821766
16831767
create-release:
16841768
if: startsWith(github.ref, 'refs/tags/')
1685-
needs: [loreline-windows, loreline-mac, loreline-linux, loreline-ios, loreline-android, loreline-wasm, loreline-js, loreline-csharp, loreline-python, loreline-lua, loreline-unity, loreline-web, loreline-jvm, loreline-cpp, loreline-sdl3, loreline-sdl3-verify, loreline-godot]
1769+
needs: [loreline-windows, loreline-mac, loreline-linux, loreline-ios, loreline-android, loreline-wasm, loreline-js, loreline-csharp, loreline-python, loreline-lua, loreline-unity, loreline-web, loreline-jvm, loreline-cpp, loreline-sdl3, loreline-sdl3-verify, loreline-godot, loreline-godot-test-linux, loreline-godot-test-web]
16861770
runs-on: ubuntu-22.04
16871771
steps:
16881772
- uses: actions/checkout@v6

sample/loreline-godot/.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,9 @@
1717
!/story/TestAsync.lor
1818
!/story/TestSaveRestore.lor
1919

20+
# Web test harness artifacts (created by run-web-test.sh)
21+
/node_modules/
22+
/package.json
23+
/package-lock.json
24+
/.tmp-web/
25+

sample/loreline-godot/export_presets.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ custom_features=""
761761
export_filter="all_resources"
762762
include_filter=""
763763
exclude_filter=""
764-
export_path="../../loreline-godot-web/index.html"
764+
export_path="../../../loreline-godot-web/index.html"
765765
patches=PackedStringArray()
766766
patch_delta_encoding=false
767767
patch_delta_compression_level_zstd=19
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env bash
2+
set -u
3+
4+
# Run a Godot scene from the sample project in headless mode and assert that
5+
# the captured stdout contains all required substring markers.
6+
#
7+
# Usage:
8+
# bash run-headless-test.sh <scene-path-from-project-root> <marker> [<marker> ...]
9+
#
10+
# Example:
11+
# bash run-headless-test.sh scenes/test_saverestore.tscn "TEST PASSED"
12+
#
13+
# Godot binary resolution: $GODOT_BIN, then `godot` on PATH, then on macOS
14+
# /Applications/Godot.app/Contents/MacOS/Godot.
15+
#
16+
# Timeout: uses `timeout` (GNU coreutils), `gtimeout` (brew coreutils on macOS),
17+
# or falls back to a tiny perl alarm. Default 30 seconds, override with
18+
# $RUN_TIMEOUT.
19+
20+
if [ "$#" -lt 2 ]; then
21+
echo "usage: $0 <scene-path> <marker> [<marker> ...]" >&2
22+
exit 2
23+
fi
24+
25+
scene="$1"
26+
shift
27+
markers=("$@")
28+
29+
project_dir="$(cd "$(dirname "$0")" && pwd)"
30+
31+
# Resolve Godot binary
32+
if [ -n "${GODOT_BIN:-}" ]; then
33+
godot="$GODOT_BIN"
34+
elif command -v godot >/dev/null 2>&1; then
35+
godot="$(command -v godot)"
36+
elif [ "$(uname)" = "Darwin" ] && [ -x "/Applications/Godot.app/Contents/MacOS/Godot" ]; then
37+
godot="/Applications/Godot.app/Contents/MacOS/Godot"
38+
else
39+
echo "error: cannot find Godot binary. Set GODOT_BIN or put 'godot' on PATH." >&2
40+
exit 2
41+
fi
42+
43+
# Resolve timeout
44+
timeout_secs="${RUN_TIMEOUT:-30}"
45+
if command -v timeout >/dev/null 2>&1; then
46+
timeout_cmd=(timeout "$timeout_secs")
47+
elif command -v gtimeout >/dev/null 2>&1; then
48+
timeout_cmd=(gtimeout "$timeout_secs")
49+
else
50+
timeout_cmd=(perl -e 'alarm shift @ARGV; exec @ARGV or die "$!"' "$timeout_secs")
51+
fi
52+
53+
log_dir="${TMPDIR:-/tmp}"
54+
log_file="$log_dir/loreline-godot-test-$$-$(basename "$scene" .tscn).log"
55+
56+
echo "==> godot: $godot"
57+
echo "==> scene: res://$scene"
58+
echo "==> log: $log_file"
59+
echo "==> timeout: ${timeout_secs}s"
60+
echo
61+
62+
set +e
63+
"${timeout_cmd[@]}" \
64+
"$godot" --headless --path "$project_dir" "res://$scene" 2>&1 \
65+
| tee "$log_file"
66+
exit_code=${PIPESTATUS[0]}
67+
set -e
68+
69+
echo
70+
echo "==> godot exit code: $exit_code"
71+
72+
# 124 = timeout fired (both GNU timeout and the perl fallback's SIGALRM
73+
# typically yield a non-zero, non-124 code, so we treat any non-zero as
74+
# potential timeout when no markers are found).
75+
if [ "$exit_code" -eq 124 ]; then
76+
echo "FAIL: godot timed out after ${timeout_secs}s" >&2
77+
exit 1
78+
fi
79+
80+
# Hard failure marker — only the test scripts ever print this.
81+
if grep -q "TEST FAILED:" "$log_file"; then
82+
echo "FAIL: scene reported TEST FAILED" >&2
83+
exit 1
84+
fi
85+
86+
missing=()
87+
for marker in "${markers[@]}"; do
88+
if ! grep -F -q -- "$marker" "$log_file"; then
89+
missing+=("$marker")
90+
fi
91+
done
92+
93+
if [ "${#missing[@]}" -ne 0 ]; then
94+
echo "FAIL: missing required markers:" >&2
95+
for m in "${missing[@]}"; do
96+
echo " - $m" >&2
97+
done
98+
exit 1
99+
fi
100+
101+
echo "OK: all ${#markers[@]} markers found"
102+
exit 0
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env bash
2+
set -u
3+
4+
# End-to-end web smoke test for the Loreline Godot integration.
5+
#
6+
# Patches the sample project's main_scene to the test_web_runner scene,
7+
# exports a Web build, serves it locally, runs headless Chromium via
8+
# Playwright and asserts the ALL_WEB_TESTS_PASSED marker, then restores
9+
# the original main_scene. Used by CI and by local developers (Mac+Linux).
10+
#
11+
# Requirements:
12+
# - Godot 4.6.x installed (resolved like run-headless-test.sh: $GODOT_BIN,
13+
# `godot` on PATH, or /Applications/Godot.app/Contents/MacOS/Godot on Mac).
14+
# - Matching Godot Web export templates installed (Mac: ~/Library/Application
15+
# Support/Godot/export_templates/<ver>/; Linux: ~/.local/share/godot/
16+
# export_templates/<ver>/).
17+
# - python3 (for the static HTTP server).
18+
# - node + npm (for Playwright). The script will `npm install` Playwright and
19+
# `npx playwright install chromium` on first run if missing.
20+
21+
project_dir="$(cd "$(dirname "$0")" && pwd)"
22+
23+
# Resolve Godot binary
24+
if [ -n "${GODOT_BIN:-}" ]; then
25+
godot="$GODOT_BIN"
26+
elif command -v godot >/dev/null 2>&1; then
27+
godot="$(command -v godot)"
28+
elif [ "$(uname)" = "Darwin" ] && [ -x "/Applications/Godot.app/Contents/MacOS/Godot" ]; then
29+
godot="/Applications/Godot.app/Contents/MacOS/Godot"
30+
else
31+
echo "error: cannot find Godot binary. Set GODOT_BIN or put 'godot' on PATH." >&2
32+
exit 2
33+
fi
34+
35+
if ! command -v python3 >/dev/null 2>&1; then
36+
echo "error: python3 not found (needed for the static HTTP server)." >&2
37+
exit 2
38+
fi
39+
40+
if ! command -v node >/dev/null 2>&1; then
41+
echo "error: node not found (needed for the Playwright harness)." >&2
42+
exit 2
43+
fi
44+
45+
port="${WEB_TEST_PORT:-8765}"
46+
timeout_ms="${WEB_TEST_TIMEOUT:-90000}"
47+
48+
out_dir="$project_dir/.tmp-web"
49+
project_file="$project_dir/project.godot"
50+
backup_file="$project_dir/.tmp-project.godot.bak.$$"
51+
52+
cleanup() {
53+
if [ -f "$backup_file" ]; then
54+
mv "$backup_file" "$project_file"
55+
fi
56+
if [ -n "${server_pid:-}" ] && kill -0 "$server_pid" 2>/dev/null; then
57+
kill "$server_pid" 2>/dev/null || true
58+
wait "$server_pid" 2>/dev/null || true
59+
fi
60+
}
61+
trap cleanup EXIT INT TERM
62+
63+
echo "==> godot: $godot"
64+
echo "==> project: $project_dir"
65+
echo "==> output: $out_dir"
66+
echo "==> port: $port"
67+
echo
68+
69+
# 1. Patch project.godot to use the web runner scene as main_scene.
70+
cp "$project_file" "$backup_file"
71+
awk '
72+
/^run\/main_scene=/ { print "run/main_scene=\"res://scenes/test_web_runner.tscn\""; next }
73+
{ print }
74+
' "$project_file" > "$project_file.tmp"
75+
mv "$project_file.tmp" "$project_file"
76+
77+
# 2. Ensure Playwright is installed in the project dir.
78+
cd "$project_dir"
79+
if [ ! -d node_modules/playwright ]; then
80+
echo "==> installing playwright (first run)"
81+
if [ ! -f package.json ]; then
82+
npm init -y >/dev/null
83+
fi
84+
npm install --no-save --silent playwright
85+
npx --yes playwright install chromium
86+
fi
87+
88+
# Tell Godot to skip node_modules during indexing/export (would otherwise be
89+
# bundled into the .pck because the Web preset uses export_filter=all_resources).
90+
# .gdignore is the Godot-native marker for "do not scan this directory".
91+
touch node_modules/.gdignore
92+
93+
# 3. Import the project (needed before --export-release).
94+
echo "==> importing project"
95+
"$godot" --headless --path "$project_dir" --import || true
96+
97+
# 4. Export the Web build.
98+
mkdir -p "$out_dir"
99+
echo "==> exporting Web build to $out_dir"
100+
"$godot" --headless --path "$project_dir" --export-release Web "$out_dir/index.html"
101+
102+
if [ ! -f "$out_dir/index.html" ]; then
103+
echo "FAIL: export did not produce $out_dir/index.html" >&2
104+
exit 1
105+
fi
106+
107+
# 5. Serve and run Playwright.
108+
echo "==> serving $out_dir on http://localhost:$port"
109+
(cd "$out_dir" && python3 -m http.server "$port" >/dev/null 2>&1) &
110+
server_pid=$!
111+
112+
# Wait briefly for the server to come up.
113+
sleep 1
114+
115+
set +e
116+
node "$project_dir/web-test-runner.js" \
117+
--url "http://localhost:$port/index.html" \
118+
--markers "ALL_WEB_TESTS_PASSED" \
119+
--timeout "$timeout_ms"
120+
test_status=$?
121+
set -e
122+
123+
exit "$test_status"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[gd_scene load_steps=2 format=3]
2+
3+
[ext_resource type="Script" path="res://scripts/test_gc_crash_repro.gd" id="1_script"]
4+
5+
[node name="TestGcCrashRepro" type="Node"]
6+
script = ExtResource("1_script")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[gd_scene load_steps=2 format=3]
2+
3+
[ext_resource type="Script" path="res://scripts/test_web_runner.gd" id="1_script"]
4+
5+
[node name="TestWebRunner" type="Node"]
6+
script = ExtResource("1_script")

sample/loreline-godot/scripts/test_async.gd

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@ func _on_choice(_interp, _options: Array, _select: Callable) -> void:
3434

3535
func _on_finished(_interp) -> void:
3636
print("FINISHED")
37+
# Auto-quit headless/exported runs. Editor runs keep the window open.
38+
if not OS.has_feature("editor") or DisplayServer.get_name() == "headless":
39+
get_tree().call_deferred("quit")

0 commit comments

Comments
 (0)