From 9b5b0b31141a9a403c7bfe7fd1c05a2c569c7a77 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:13:00 +0200 Subject: [PATCH 01/25] Add Android emulator test script with Hello World C program - Creates Android AVD with system images stored in scripts/avd_home/ - Auto-detects installed system image ABI (arm64-v8a / x86_64) from SDK - Uses ANDROID_AVD_HOME consistently for avdmanager + emulator commands - Compiles hello_world/hello.c with latest NDK clang for detected ABI - Pushes binary to emulator, runs it, verifies 'hello world' in logcat - Polls logcat instead of fixed sleep for reliable verification - Manages emulator lifecycle on fixed port 5554 to avoid killing unrelated instances Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitignore | 4 + scripts/android_emulator_test.sh | 274 +++++++++++++++++++++++++++++++ scripts/hello_world/hello.c | 8 + 3 files changed, 286 insertions(+) create mode 100755 scripts/android_emulator_test.sh create mode 100644 scripts/hello_world/hello.c diff --git a/.gitignore b/.gitignore index 0260b044..a8f57676 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ /Build Tests/UnitTests/dist/ + +# Android emulator artifacts +scripts/avd_home/ +scripts/hello_world/hello diff --git a/scripts/android_emulator_test.sh b/scripts/android_emulator_test.sh new file mode 100755 index 00000000..67cb4006 --- /dev/null +++ b/scripts/android_emulator_test.sh @@ -0,0 +1,274 @@ +#!/usr/bin/env bash +# android_emulator_test.sh +# Creates an Android AVD, compiles a Hello World C program with the NDK, +# pushes it to the emulator, runs it, and verifies "hello world" in logcat. +# +# AVD data (ini + avd dir) is stored under scripts/avd_home/ in the repo. + +set -euo pipefail + +# ─── Config ─────────────────────────────────────────────────────────────────── +ANDROID_SDK="${ANDROID_HOME:-$HOME/Library/Android/sdk}" +ANDROID_API=36 +AVD_NAME="JSRemuTestAVD" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +# AVD_HOME is placed in the current folder (alongside this script). +# avdmanager writes both the .ini and the .avd/ dir here. +AVD_HOME="$SCRIPT_DIR/avd_home" +LOGCAT_TAG="HelloWorld" +LOGCAT_TIMEOUT=30 # seconds to wait for logcat entry + +# ─── Tool paths ─────────────────────────────────────────────────────────────── +EMULATOR="$ANDROID_SDK/emulator/emulator" +ADB="$ANDROID_SDK/platform-tools/adb" + +# Prefer PATH installs of sdkmanager/avdmanager (Homebrew, etc.) +if command -v sdkmanager &>/dev/null; then + SDKMANAGER=$(command -v sdkmanager) +else + SDKMANAGER="$ANDROID_SDK/cmdline-tools/latest/bin/sdkmanager" +fi +if command -v avdmanager &>/dev/null; then + AVDMANAGER=$(command -v avdmanager) +else + AVDMANAGER="$ANDROID_SDK/cmdline-tools/latest/bin/avdmanager" +fi + +# ─── Detect host arch and NDK host tag ─────────────────────────────────────── +HOST_ARCH="$(uname -m)" +OS="$(uname -s)" +case "$OS-$HOST_ARCH" in + Darwin-arm64|Darwin-aarch64) NDK_HOST_TAG="darwin-x86_64" ;; # Rosetta runs x86_64 NDK + Darwin-x86_64) NDK_HOST_TAG="darwin-x86_64" ;; + Linux-x86_64) NDK_HOST_TAG="linux-x86_64" ;; + Linux-aarch64) NDK_HOST_TAG="linux-x86_64" ;; # rare; adjust if needed + *) + echo "ERROR: Unsupported OS/arch: $OS/$HOST_ARCH" >&2; exit 1 ;; +esac +echo "Host : $OS $HOST_ARCH (NDK host tag: $NDK_HOST_TAG)" + +# Verify Rosetta is usable on Apple Silicon (NDK ships darwin-x86_64 binaries) +if [ "$OS" = "Darwin" ] && [ "$HOST_ARCH" = "arm64" ]; then + if ! arch -x86_64 true 2>/dev/null; then + echo "ERROR: Rosetta 2 required on Apple Silicon to run the darwin-x86_64 NDK toolchain." >&2 + echo " Install it with: softwareupdate --install-rosetta --agree-to-license" >&2 + exit 1 + fi +fi + +# ─── Find latest installed NDK ──────────────────────────────────────────────── +NDK_ROOT=$(ls -d "$ANDROID_SDK/ndk/"*/ 2>/dev/null | sort -V | tail -1) +NDK_ROOT="${NDK_ROOT%/}" +if [ -z "$NDK_ROOT" ]; then + echo "ERROR: No NDK found under $ANDROID_SDK/ndk." >&2; exit 1 +fi +echo "Using NDK : $NDK_ROOT" + +NDK_TOOLCHAIN="$NDK_ROOT/toolchains/llvm/prebuilt/$NDK_HOST_TAG/bin" + +# ─── Detect installed system image ABI ─────────────────────────────────────── +# Pick the highest installed API <= ANDROID_API with an available image, then +# pick the ABI that matches the host (arm64-v8a on Apple Silicon/arm64 Linux, +# x86_64 on Intel). Fall back to whatever is installed. +prefer_abi() { + case "$HOST_ARCH" in + arm64|aarch64) echo "arm64-v8a" ;; + *) echo "x86_64" ;; + esac +} + +IMAGE_BASE="$ANDROID_SDK/system-images" +ANDROID_ABI="" +ACTUAL_API="$ANDROID_API" +PREFERRED_ABI="$(prefer_abi)" + +# Search from ANDROID_API down to find a matching installed image +for try_api in $ANDROID_API 35 34 33; do + for try_abi in "$PREFERRED_ABI" arm64-v8a x86_64; do + for try_tag in google_apis_playstore google_apis default; do + candidate="$IMAGE_BASE/android-${try_api}/${try_tag}/${try_abi}" + if [ -d "$candidate" ]; then + ACTUAL_API="$try_api" + ANDROID_ABI="$try_abi" + IMAGE_TAG="$try_tag" + break 3 + fi + done + done +done + +if [ -z "$ANDROID_ABI" ]; then + echo "No system image found locally. Installing android-${ANDROID_API} / google_apis_playstore / ${PREFERRED_ABI} ..." + ANDROID_ABI="$PREFERRED_ABI" + ACTUAL_API="$ANDROID_API" + IMAGE_TAG="google_apis_playstore" + echo "y" | "$SDKMANAGER" "system-images;android-${ACTUAL_API};${IMAGE_TAG};${ANDROID_ABI}" +fi + +SYSTEM_IMAGE_PKG="system-images;android-${ACTUAL_API};${IMAGE_TAG};${ANDROID_ABI}" +echo "System img : $SYSTEM_IMAGE_PKG" +echo "Android ABI: $ANDROID_ABI" + +# ─── Select NDK clang for the target ABI ───────────────────────────────────── +case "$ANDROID_ABI" in + arm64-v8a) CLANG_PREFIX="aarch64-linux-android" ;; + x86_64) CLANG_PREFIX="x86_64-linux-android" ;; + armeabi-v7a) CLANG_PREFIX="armv7a-linux-androideabi" ;; + x86) CLANG_PREFIX="i686-linux-android" ;; + *) + echo "ERROR: Unsupported ABI: $ANDROID_ABI" >&2; exit 1 ;; +esac + +CLANG="$NDK_TOOLCHAIN/${CLANG_PREFIX}${ACTUAL_API}-clang" +if [ ! -x "$CLANG" ]; then + # Fall back to highest API version available in toolchain + CLANG=$(ls "$NDK_TOOLCHAIN/${CLANG_PREFIX}"*-clang 2>/dev/null | grep -v '++' | sort -V | tail -1 || true) +fi +if [ ! -x "$CLANG" ]; then + echo "ERROR: NDK clang not found at $NDK_TOOLCHAIN for prefix $CLANG_PREFIX" >&2; exit 1 +fi +echo "Clang : $CLANG" + +# ─── Create AVD (idempotent, all files under AVD_HOME) ─────────────────────── +# Export ANDROID_AVD_HOME so avdmanager writes its .ini here, not ~/.android/avd/ +export ANDROID_AVD_HOME="$AVD_HOME" +mkdir -p "$AVD_HOME" + +if ANDROID_AVD_HOME="$AVD_HOME" "$AVDMANAGER" list avd 2>/dev/null | grep -q "Name: $AVD_NAME"; then + echo "AVD '$AVD_NAME' already exists in $AVD_HOME — reusing." +else + echo "Creating AVD '$AVD_NAME' in $AVD_HOME ..." + # --path sets the .avd/ dir location; ANDROID_AVD_HOME sets where the .ini goes + echo "no" | ANDROID_AVD_HOME="$AVD_HOME" "$AVDMANAGER" create avd \ + --name "$AVD_NAME" \ + --package "$SYSTEM_IMAGE_PKG" \ + --path "$AVD_HOME/$AVD_NAME.avd" \ + --force +fi + +# ─── Write Hello World C source ─────────────────────────────────────────────── +SRC_DIR="$SCRIPT_DIR/hello_world" +mkdir -p "$SRC_DIR" + +cat > "$SRC_DIR/hello.c" <<'CSRC' +#include +#include + +#define TAG "HelloWorld" + +int main(void) { + printf("hello world\n"); + __android_log_print(ANDROID_LOG_INFO, TAG, "hello world"); + return 0; +} +CSRC + +# ─── Compile for Android ────────────────────────────────────────────────────── +echo "Compiling hello.c for Android ($ANDROID_ABI, API $ACTUAL_API) ..." +"$CLANG" -o "$SRC_DIR/hello" "$SRC_DIR/hello.c" -llog +echo "Binary: $SRC_DIR/hello ($(file "$SRC_DIR/hello" | cut -d: -f2-))" + +# ─── Kill any stale emulator on the default port (5554) ────────────────────── +EMU_SERIAL="emulator-5554" +if "$ADB" devices | grep -q "^$EMU_SERIAL"; then + echo "Stopping stale emulator $EMU_SERIAL ..." + "$ADB" -s "$EMU_SERIAL" emu kill 2>/dev/null || true + sleep 3 +fi + +# ─── Start emulator ────────────────────────────────────────────────────────── +echo "Starting emulator ..." +ANDROID_AVD_HOME="$AVD_HOME" "$EMULATOR" \ + -avd "$AVD_NAME" \ + -no-window \ + -no-audio \ + -no-boot-anim \ + -gpu swiftshader_indirect \ + -port 5554 \ + & +EMULATOR_PID=$! +echo "Emulator PID: $EMULATOR_PID" + +cleanup() { + echo "Shutting down emulator (PID $EMULATOR_PID) ..." + kill "$EMULATOR_PID" 2>/dev/null || true +} +trap cleanup EXIT + +# ─── Wait for device online ─────────────────────────────────────────────────── +echo "Waiting for device to come online ..." +WAIT_TIMEOUT=60 +ELAPSED=0 +while ! "$ADB" -s "$EMU_SERIAL" get-state 2>/dev/null | grep -q "device"; do + if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then + echo "ERROR: Emulator process died unexpectedly." >&2; exit 1 + fi + if [ "$ELAPSED" -ge "$WAIT_TIMEOUT" ]; then + echo "ERROR: Emulator did not come online within ${WAIT_TIMEOUT}s." >&2; exit 1 + fi + sleep 2; ELAPSED=$((ELAPSED + 2)) +done + +echo "Waiting for boot to complete (may take a few minutes) ..." +BOOT_TIMEOUT=300 +ELAPSED=0 +while true; do + BOOT_PROP=$("$ADB" -s "$EMU_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') + if [ "$BOOT_PROP" = "1" ]; then + echo "Emulator booted successfully." + break + fi + if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then + echo "ERROR: Emulator process died while waiting for boot." >&2; exit 1 + fi + if [ "$ELAPSED" -ge "$BOOT_TIMEOUT" ]; then + echo "ERROR: Emulator did not boot within ${BOOT_TIMEOUT}s." >&2; exit 1 + fi + sleep 5; ELAPSED=$((ELAPSED + 5)) + echo " ... waiting (${ELAPSED}s)" +done + +# ─── Push and run binary ───────────────────────────────────────────────────── +echo "Pushing binary to emulator ..." +"$ADB" -s "$EMU_SERIAL" push "$SRC_DIR/hello" /data/local/tmp/hello +"$ADB" -s "$EMU_SERIAL" shell chmod +x /data/local/tmp/hello + +echo "Clearing logcat buffer ..." +"$ADB" -s "$EMU_SERIAL" logcat -c + +echo "Running hello binary ..." +STDOUT=$("$ADB" -s "$EMU_SERIAL" shell /data/local/tmp/hello 2>&1) +echo "Program stdout: $STDOUT" + +# ─── Poll logcat until entry appears or timeout ─────────────────────────────── +echo "Polling logcat for '$LOGCAT_TAG' (up to ${LOGCAT_TIMEOUT}s) ..." +LOGCAT="" +ELAPSED=0 +while [ "$ELAPSED" -lt "$LOGCAT_TIMEOUT" ]; do + LOGCAT=$("$ADB" -s "$EMU_SERIAL" logcat -d -s "$LOGCAT_TAG:I" 2>&1) + if echo "$LOGCAT" | grep -qi "hello world"; then + break + fi + sleep 2; ELAPSED=$((ELAPSED + 2)) +done +echo "--- logcat ($LOGCAT_TAG) ---" +echo "$LOGCAT" +echo "----------------------------" + +# ─── Verify ────────────────────────────────────────────────────────────────── +STDOUT_OK=false; LOGCAT_OK=false +echo "$STDOUT" | grep -qi "hello world" && STDOUT_OK=true +echo "$LOGCAT" | grep -qi "hello world" && LOGCAT_OK=true + +if $STDOUT_OK && $LOGCAT_OK; then + echo "" + echo "✅ SUCCESS: 'hello world' found in both stdout AND logcat." +elif $STDOUT_OK; then + echo "" + echo "✅ SUCCESS: 'hello world' found in stdout." + echo "⚠️ WARNING: Not found in logcat within ${LOGCAT_TIMEOUT}s." +else + echo "" + echo "❌ FAILURE: 'hello world' NOT found in program output." >&2 + exit 1 +fi diff --git a/scripts/hello_world/hello.c b/scripts/hello_world/hello.c new file mode 100644 index 00000000..8e056feb --- /dev/null +++ b/scripts/hello_world/hello.c @@ -0,0 +1,8 @@ +#include +#include +#define TAG "HelloWorld" +int main(void) { + printf("hello world\n"); + __android_log_print(ANDROID_LOG_INFO, TAG, "hello world"); + return 0; +} From 64fccbf18f9f5010863920bcca6f74043ad233d8 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:25:12 +0200 Subject: [PATCH 02/25] Fix avdmanager SDK detection for Homebrew installation Homebrew's avdmanager wrapper derives the Android SDK root from the 'com.android.sdkmanager.toolsdir' JVM property, which defaults to its own Homebrew cellar directory and therefore cannot find installed system images. Fix: export AVDMANAGER_OPTS and SDKMANAGER_OPTS with toolsdir set to $ANDROID_SDK/platform-tools so avdmanager computes the correct SDK root (one level up from platform-tools = the ANDROID_HOME directory). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/android_emulator_test.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/android_emulator_test.sh b/scripts/android_emulator_test.sh index 67cb4006..7c086efc 100755 --- a/scripts/android_emulator_test.sh +++ b/scripts/android_emulator_test.sh @@ -34,6 +34,13 @@ else AVDMANAGER="$ANDROID_SDK/cmdline-tools/latest/bin/avdmanager" fi +# Homebrew's avdmanager/sdkmanager compute the SDK root from 'toolsdir'. +# By pointing toolsdir at $ANDROID_SDK/platform-tools (always present), the +# parent directory becomes the correct SDK root. Without this the Homebrew +# wrapper uses its own cellar path and cannot find installed system images. +export AVDMANAGER_OPTS="-Dcom.android.sdkmanager.toolsdir=$ANDROID_SDK/platform-tools" +export SDKMANAGER_OPTS="-Dcom.android.sdkmanager.toolsdir=$ANDROID_SDK/platform-tools" + # ─── Detect host arch and NDK host tag ─────────────────────────────────────── HOST_ARCH="$(uname -m)" OS="$(uname -s)" From 3d7d3a65b8f051383c6c26fb0d656db8b6dba458 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:32:08 +0200 Subject: [PATCH 03/25] Fix emulator boot: resilient adb waits + correct SDK path Three bugs fixed: 1. config.ini image path: Homebrew avdmanager writes image.sysdir.1 relative to dirname(ANDROID_SDK), producing 'sdk/system-images/...' Post-patch strips the spurious 'sdk/' prefix so the emulator resolves the path correctly against ANDROID_SDK_ROOT=ANDROID_SDK. 2. set -eo pipefail + adb during boot: adb shell/get-state return non-zero transiently while the emulator is still initializing, silently killing the script. All adb calls in wait loops now use '|| true' guards. 3. Device online check: replaced 'adb wait-for-device | grep' pipeline (fragile under pipefail) with an explicit get-state polling loop that also checks emulator process health. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/android_emulator_test.sh | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/scripts/android_emulator_test.sh b/scripts/android_emulator_test.sh index 7c086efc..1360e7fa 100755 --- a/scripts/android_emulator_test.sh +++ b/scripts/android_emulator_test.sh @@ -153,6 +153,17 @@ else --force fi +# The Homebrew avdmanager writes image.sysdir.1 relative to dirname($ANDROID_SDK) +# (e.g. "sdk/system-images/...") because it computes the SDK root as one level +# above $ANDROID_SDK. Patch config.ini so the path is correct relative to +# $ANDROID_SDK itself (strip the leading "sdk/" prefix). +CONFIG_INI="$AVD_HOME/$AVD_NAME.avd/config.ini" +if grep -q "^image\.sysdir\.1=sdk/" "$CONFIG_INI" 2>/dev/null; then + echo "Patching config.ini: removing spurious 'sdk/' prefix from image.sysdir.1 ..." + sed -i.bak 's|^image\.sysdir\.1=sdk/|image.sysdir.1=|' "$CONFIG_INI" + echo " image.sysdir.1=$(grep '^image\.sysdir\.1=' "$CONFIG_INI")" +fi + # ─── Write Hello World C source ─────────────────────────────────────────────── SRC_DIR="$SCRIPT_DIR/hello_world" mkdir -p "$SRC_DIR" @@ -185,7 +196,7 @@ fi # ─── Start emulator ────────────────────────────────────────────────────────── echo "Starting emulator ..." -ANDROID_AVD_HOME="$AVD_HOME" "$EMULATOR" \ +ANDROID_AVD_HOME="$AVD_HOME" ANDROID_SDK_ROOT="$ANDROID_SDK" "$EMULATOR" \ -avd "$AVD_NAME" \ -no-window \ -no-audio \ @@ -204,9 +215,12 @@ trap cleanup EXIT # ─── Wait for device online ─────────────────────────────────────────────────── echo "Waiting for device to come online ..." -WAIT_TIMEOUT=60 +WAIT_TIMEOUT=120 ELAPSED=0 -while ! "$ADB" -s "$EMU_SERIAL" get-state 2>/dev/null | grep -q "device"; do +while true; do + # adb get-state may fail during early boot — don't let set -e exit the script + DEV_STATE=$("$ADB" -s "$EMU_SERIAL" get-state 2>/dev/null || true) + [ "$DEV_STATE" = "device" ] && break if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then echo "ERROR: Emulator process died unexpectedly." >&2; exit 1 fi @@ -215,12 +229,15 @@ while ! "$ADB" -s "$EMU_SERIAL" get-state 2>/dev/null | grep -q "device"; do fi sleep 2; ELAPSED=$((ELAPSED + 2)) done +echo "Device online." echo "Waiting for boot to complete (may take a few minutes) ..." BOOT_TIMEOUT=300 ELAPSED=0 while true; do - BOOT_PROP=$("$ADB" -s "$EMU_SERIAL" shell getprop sys.boot_completed 2>/dev/null | tr -d '\r') + # adb shell may fail transiently during boot — guard with || true + BOOT_PROP=$("$ADB" -s "$EMU_SERIAL" shell getprop sys.boot_completed 2>/dev/null || true) + BOOT_PROP=$(printf '%s' "$BOOT_PROP" | tr -d '\r\n') if [ "$BOOT_PROP" = "1" ]; then echo "Emulator booted successfully." break @@ -244,7 +261,7 @@ echo "Clearing logcat buffer ..." "$ADB" -s "$EMU_SERIAL" logcat -c echo "Running hello binary ..." -STDOUT=$("$ADB" -s "$EMU_SERIAL" shell /data/local/tmp/hello 2>&1) +STDOUT=$("$ADB" -s "$EMU_SERIAL" shell /data/local/tmp/hello 2>&1 || true) echo "Program stdout: $STDOUT" # ─── Poll logcat until entry appears or timeout ─────────────────────────────── @@ -252,7 +269,7 @@ echo "Polling logcat for '$LOGCAT_TAG' (up to ${LOGCAT_TIMEOUT}s) ..." LOGCAT="" ELAPSED=0 while [ "$ELAPSED" -lt "$LOGCAT_TIMEOUT" ]; do - LOGCAT=$("$ADB" -s "$EMU_SERIAL" logcat -d -s "$LOGCAT_TAG:I" 2>&1) + LOGCAT=$("$ADB" -s "$EMU_SERIAL" logcat -d -s "$LOGCAT_TAG:I" 2>&1 || true) if echo "$LOGCAT" | grep -qi "hello world"; then break fi From 943adf0846afa018a40a2e2d9971bf75dc9c58a7 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:01:26 +0200 Subject: [PATCH 04/25] Add UnitTests Android emulator pipeline and fix fdsan crash in StdoutLogger - Add scripts/android_unit_tests.sh: end-to-end pipeline that builds the UnitTests Android project with gradlew, starts the AVD emulator, deploys and runs the instrumented tests, and reports pass/fail. Auto-detects the highest installed NDK to override the pinned ndkVersion in build.gradle (NDK 23 lacks C++20 support needed by the codebase). - Add patches/AndroidExtensions/StdoutLogger.cpp: fix an fdsan violation that caused SIGABRT on Android API 29+. StdoutLogger::Start() calls fdopen(fd[0]) which transfers fd ownership to the FILE*. StdoutLogger::Stop() was then calling close(fd[0]) directly, violating fdsan's ownership rules. Fix: only close the write ends (fd[1]) in Stop(); the reader threads close the read ends via fclose(). - Wire the patch into CMakeLists.txt via FetchContent_Declare PATCH_COMMAND so clean builds automatically apply the fix after fetching AndroidExtensions. Test results (Android 36, arm64-v8a): 136 JavaScript tests passing (3s) [ PASSED ] 3 tests from 3 suites (JavaScript.All, Console.Log, AppRuntime.DestroyDoesNotDeadlock) 0 failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 3 + patches/AndroidExtensions/StdoutLogger.cpp | 120 +++++++++++++ scripts/android_unit_tests.sh | 198 +++++++++++++++++++++ 3 files changed, 321 insertions(+) create mode 100644 patches/AndroidExtensions/StdoutLogger.cpp create mode 100755 scripts/android_unit_tests.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 782efa22..34553dc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,9 @@ include(FetchContent) FetchContent_Declare(AndroidExtensions GIT_REPOSITORY https://github.com/BabylonJS/AndroidExtensions.git GIT_TAG 2e85a8d43b89246c460112c9e5546ad54b6e87b4 + PATCH_COMMAND ${CMAKE_COMMAND} -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/patches/AndroidExtensions/StdoutLogger.cpp" + "Source/StdoutLogger.cpp" EXCLUDE_FROM_ALL) FetchContent_Declare(arcana.cpp GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git diff --git a/patches/AndroidExtensions/StdoutLogger.cpp b/patches/AndroidExtensions/StdoutLogger.cpp new file mode 100644 index 00000000..cb3762f7 --- /dev/null +++ b/patches/AndroidExtensions/StdoutLogger.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + std::mutex g_stateMutex; + bool g_started = false; + int fd_stdout[2] = {-1, -1}; + int fd_stderr[2] = {-1, -1}; + + void thread_func(int fd, int prio) + { + FILE* stream = fdopen(fd, "r"); + + while (true) + { + char* line = nullptr; + size_t n = 0; + ssize_t nread = getline(&line, &n, stream); + if (nread == -1) + { + break; + } + + line[nread - 1] = '\0'; + __android_log_write(prio, "StdoutLogger", line); + free(line); + } + + fclose(stream); + } + + void* thread_func_stdout(void*) + { + thread_func(fd_stdout[0], ANDROID_LOG_INFO); + return 0; + } + + void* thread_func_stderr(void*) + { + thread_func(fd_stderr[0], ANDROID_LOG_ERROR); + return 0; + } + + void redirect(int fd[2], int fd_redirect, void*(*thread_func)(void*)) + { + // create the pipe and redirect + pipe(fd); + dup2(fd[1], fd_redirect); + + // spawn the thread + pthread_t thr; + if (pthread_create(&thr, 0, thread_func, 0) == -1) + { + return; + } + + pthread_detach(thr); + } +} + +namespace android::StdoutLogger +{ + void Start() + { + std::lock_guard lock(g_stateMutex); + + if (g_started) + { + return; + } + + redirect(fd_stdout, fileno(stdout), thread_func_stdout); + redirect(fd_stderr, fileno(stderr), thread_func_stderr); + + g_started = true; + } + + void Stop() + { + std::lock_guard lock(g_stateMutex); + + if (!g_started) + { + return; + } + + g_started = false; + + // Close the write ends of the pipes. This signals EOF to the reader + // threads, which will then call fclose() to close the read ends. + // Do NOT close the read ends here: fdopen() transferred ownership of + // fd[0] to the FILE* inside the reader thread, and calling close() on + // an fd owned by a FILE* is a fdsan violation on Android API 29+. + if (fd_stdout[1] != -1) + { + close(fd_stdout[1]); + fd_stdout[1] = -1; + } + fd_stdout[0] = -1; // owned by reader thread's FILE*; closed via fclose() + + if (fd_stderr[1] != -1) + { + close(fd_stderr[1]); + fd_stderr[1] = -1; + } + fd_stderr[0] = -1; // owned by reader thread's FILE*; closed via fclose() + } + + bool IsStarted() + { + std::lock_guard lock(g_stateMutex); + return g_started; + } +} diff --git a/scripts/android_unit_tests.sh b/scripts/android_unit_tests.sh new file mode 100755 index 00000000..370eb939 --- /dev/null +++ b/scripts/android_unit_tests.sh @@ -0,0 +1,198 @@ +#!/usr/bin/env bash +# android_unit_tests.sh +# Builds the UnitTests Android project with gradlew, runs them on an emulator, +# and reports pass/fail. Reuses the AVD created by android_emulator_test.sh. + +set -euo pipefail + +# ─── Config ─────────────────────────────────────────────────────────────────── +ANDROID_SDK="${ANDROID_HOME:-$HOME/Library/Android/sdk}" +ANDROID_API=36 +AVD_NAME="JSRemuTestAVD" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +TESTS_DIR="$REPO_ROOT/Tests" +ANDROID_PROJECT="$TESTS_DIR/UnitTests/Android" +GRADLEW="$ANDROID_PROJECT/gradlew" +AVD_HOME="$SCRIPT_DIR/avd_home" +EMU_SERIAL="emulator-5554" + +# ─── Tool paths ─────────────────────────────────────────────────────────────── +EMULATOR="$ANDROID_SDK/emulator/emulator" +ADB="$ANDROID_SDK/platform-tools/adb" + +if command -v avdmanager &>/dev/null; then + AVDMANAGER=$(command -v avdmanager) +else + AVDMANAGER="$ANDROID_SDK/cmdline-tools/latest/bin/avdmanager" +fi + +# Homebrew avdmanager needs toolsdir to find the SDK root correctly +export AVDMANAGER_OPTS="-Dcom.android.sdkmanager.toolsdir=$ANDROID_SDK/platform-tools" +export ANDROID_AVD_HOME="$AVD_HOME" + +# ─── Detect ABI and NDK version ────────────────────────────────────────────── +HOST_ARCH="$(uname -m)" +case "$HOST_ARCH" in + arm64|aarch64) ANDROID_ABI="arm64-v8a" ;; + x86_64) ANDROID_ABI="x86_64" ;; + *) echo "ERROR: Unsupported arch: $HOST_ARCH" >&2; exit 1 ;; +esac +echo "Host : $(uname -s) $HOST_ARCH → Android ABI: $ANDROID_ABI" + +# Auto-detect the highest installed NDK version to override the one in build.gradle +NDK_DIR="$ANDROID_SDK/ndk" +NDK_VERSION="" +if [ -d "$NDK_DIR" ]; then + NDK_VERSION=$(ls "$NDK_DIR" | sort -V | tail -1) +fi +if [ -z "$NDK_VERSION" ]; then + echo "ERROR: No NDK found in $NDK_DIR" >&2; exit 1 +fi +echo "NDK : $NDK_VERSION" + +# ─── Write local.properties so Gradle can find the SDK ─────────────────────── +echo "sdk.dir=$ANDROID_SDK" > "$ANDROID_PROJECT/local.properties" +echo "SDK dir : $ANDROID_SDK" + +# ─── npm install: build the JavaScript test bundle ─────────────────────────── +echo "" +echo "═══ Step 1: Building JS test bundle (npm install in $TESTS_DIR) ═══" +(cd "$TESTS_DIR" && npm install) +echo "JS bundle : $TESTS_DIR/UnitTests/dist/ ✓" + +# ─── Ensure AVD exists ─────────────────────────────────────────────────────── +echo "" +echo "═══ Step 2: Ensuring AVD exists ═══" +mkdir -p "$AVD_HOME" + +SYSTEM_IMAGE_PKG="system-images;android-${ANDROID_API};google_apis_playstore;${ANDROID_ABI}" + +if ANDROID_AVD_HOME="$AVD_HOME" "$AVDMANAGER" list avd 2>/dev/null | grep -q "Name: $AVD_NAME"; then + echo "AVD '$AVD_NAME' already exists — reusing." +else + echo "Creating AVD '$AVD_NAME' ..." + echo "no" | ANDROID_AVD_HOME="$AVD_HOME" "$AVDMANAGER" create avd \ + --name "$AVD_NAME" \ + --package "$SYSTEM_IMAGE_PKG" \ + --path "$AVD_HOME/$AVD_NAME.avd" \ + --force +fi + +# Patch the image path (Homebrew avdmanager writes a spurious "sdk/" prefix) +CONFIG_INI="$AVD_HOME/$AVD_NAME.avd/config.ini" +if grep -q "^image\.sysdir\.1=sdk/" "$CONFIG_INI" 2>/dev/null; then + sed -i.bak 's|^image\.sysdir\.1=sdk/|image.sysdir.1=|' "$CONFIG_INI" + echo "Patched config.ini: $(grep '^image\.sysdir\.1=' "$CONFIG_INI")" +fi + +# ─── Kill any stale emulator on port 5554 ──────────────────────────────────── +if "$ADB" devices | grep -q "^$EMU_SERIAL"; then + echo "Stopping stale emulator $EMU_SERIAL ..." + "$ADB" -s "$EMU_SERIAL" emu kill 2>/dev/null || true + sleep 3 +fi + +# ─── Start emulator ────────────────────────────────────────────────────────── +echo "" +echo "═══ Step 3: Starting emulator ═══" +ANDROID_AVD_HOME="$AVD_HOME" ANDROID_SDK_ROOT="$ANDROID_SDK" "$EMULATOR" \ + -avd "$AVD_NAME" \ + -no-window \ + -no-audio \ + -no-boot-anim \ + -gpu swiftshader_indirect \ + -port 5554 \ + & +EMULATOR_PID=$! +echo "Emulator PID: $EMULATOR_PID" + +cleanup() { + echo "" + echo "Shutting down emulator (PID $EMULATOR_PID) ..." + kill "$EMULATOR_PID" 2>/dev/null || true +} +trap cleanup EXIT + +# ─── Wait for device online ─────────────────────────────────────────────────── +echo "Waiting for device to come online ..." +WAIT_TIMEOUT=120 +ELAPSED=0 +while true; do + DEV_STATE=$("$ADB" -s "$EMU_SERIAL" get-state 2>/dev/null || true) + [ "$DEV_STATE" = "device" ] && break + if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then + echo "ERROR: Emulator process died unexpectedly." >&2; exit 1 + fi + if [ "$ELAPSED" -ge "$WAIT_TIMEOUT" ]; then + echo "ERROR: Emulator did not come online within ${WAIT_TIMEOUT}s." >&2; exit 1 + fi + sleep 2; ELAPSED=$((ELAPSED + 2)) +done + +echo "Waiting for full boot ..." +BOOT_TIMEOUT=300 +ELAPSED=0 +while true; do + BOOT_PROP=$("$ADB" -s "$EMU_SERIAL" shell getprop sys.boot_completed 2>/dev/null || true) + BOOT_PROP=$(printf '%s' "$BOOT_PROP" | tr -d '\r\n') + [ "$BOOT_PROP" = "1" ] && { echo "Emulator booted."; break; } + if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then + echo "ERROR: Emulator died during boot." >&2; exit 1 + fi + if [ "$ELAPSED" -ge "$BOOT_TIMEOUT" ]; then + echo "ERROR: Boot timed out after ${BOOT_TIMEOUT}s." >&2; exit 1 + fi + sleep 5; ELAPSED=$((ELAPSED + 5)) + echo " ... waiting (${ELAPSED}s)" +done + +# ─── Run Gradle instrumented tests ─────────────────────────────────────────── +echo "" +echo "═══ Step 4: Building and running Android unit tests ═══" +echo "Project : $ANDROID_PROJECT" +echo "Device : $EMU_SERIAL" +echo "" + +GRADLE_EXIT=0 +(cd "$ANDROID_PROJECT" && \ + ANDROID_SDK_ROOT="$ANDROID_SDK" \ + "$GRADLEW" connectedDebugAndroidTest \ + -PndkVersion="$NDK_VERSION" \ + 2>&1) || GRADLE_EXIT=$? + +# ─── Parse test results ─────────────────────────────────────────────────────── +echo "" +echo "═══ Step 5: Test results ═══" + +RESULTS_DIR="$ANDROID_PROJECT/app/build/outputs/androidTest-results/connected" +RESULT_SUMMARY="$ANDROID_PROJECT/app/build/reports/androidTests/connected/index.html" + +TOTAL=0; FAILURES=0; ERRORS=0; SKIPPED=0 +if [ -d "$RESULTS_DIR" ]; then + for xml in "$RESULTS_DIR"/*.xml "$RESULTS_DIR"/**/*.xml 2>/dev/null; do + [ -f "$xml" ] || continue + t=$(grep -o 'tests="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) + f=$(grep -o 'failures="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) + e=$(grep -o 'errors="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) + s=$(grep -o 'skipped="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) + TOTAL=$((TOTAL + t)) + FAILURES=$((FAILURES + f)) + ERRORS=$((ERRORS + e)) + SKIPPED=$((SKIPPED + s)) + echo " Result file: $(basename "$xml") — tests=$t failures=$f errors=$e" + # Print any failure details + grep -A5 '/dev/null | head -30 || true + done +else + echo " No result XML files found in $RESULTS_DIR" +fi + +echo "" +if [ "$GRADLE_EXIT" -eq 0 ] && [ "$FAILURES" -eq 0 ] && [ "$ERRORS" -eq 0 ]; then + echo "✅ SUCCESS: All $TOTAL tests passed (skipped: $SKIPPED)." +else + echo "❌ FAILURE: $FAILURES failure(s), $ERRORS error(s) out of $TOTAL tests." >&2 + [ "$GRADLE_EXIT" -ne 0 ] && echo " Gradle exit code: $GRADLE_EXIT" >&2 + exit 1 +fi From a3df3be71353f5ed33db9c8c0d57a44b9a368566 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:08:55 +0200 Subject: [PATCH 05/25] Remove Hello World test pipeline; keep only UnitTests pipeline scripts/android_emulator_test.sh and scripts/hello_world/hello.c are removed. The UnitTests Android pipeline (scripts/android_unit_tests.sh) is the sole test entry point. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitignore | 1 - scripts/android_emulator_test.sh | 298 ------------------------------- scripts/hello_world/hello.c | 8 - 3 files changed, 307 deletions(-) delete mode 100755 scripts/android_emulator_test.sh delete mode 100644 scripts/hello_world/hello.c diff --git a/.gitignore b/.gitignore index a8f57676..5560fb81 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ Tests/UnitTests/dist/ # Android emulator artifacts scripts/avd_home/ -scripts/hello_world/hello diff --git a/scripts/android_emulator_test.sh b/scripts/android_emulator_test.sh deleted file mode 100755 index 1360e7fa..00000000 --- a/scripts/android_emulator_test.sh +++ /dev/null @@ -1,298 +0,0 @@ -#!/usr/bin/env bash -# android_emulator_test.sh -# Creates an Android AVD, compiles a Hello World C program with the NDK, -# pushes it to the emulator, runs it, and verifies "hello world" in logcat. -# -# AVD data (ini + avd dir) is stored under scripts/avd_home/ in the repo. - -set -euo pipefail - -# ─── Config ─────────────────────────────────────────────────────────────────── -ANDROID_SDK="${ANDROID_HOME:-$HOME/Library/Android/sdk}" -ANDROID_API=36 -AVD_NAME="JSRemuTestAVD" -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -# AVD_HOME is placed in the current folder (alongside this script). -# avdmanager writes both the .ini and the .avd/ dir here. -AVD_HOME="$SCRIPT_DIR/avd_home" -LOGCAT_TAG="HelloWorld" -LOGCAT_TIMEOUT=30 # seconds to wait for logcat entry - -# ─── Tool paths ─────────────────────────────────────────────────────────────── -EMULATOR="$ANDROID_SDK/emulator/emulator" -ADB="$ANDROID_SDK/platform-tools/adb" - -# Prefer PATH installs of sdkmanager/avdmanager (Homebrew, etc.) -if command -v sdkmanager &>/dev/null; then - SDKMANAGER=$(command -v sdkmanager) -else - SDKMANAGER="$ANDROID_SDK/cmdline-tools/latest/bin/sdkmanager" -fi -if command -v avdmanager &>/dev/null; then - AVDMANAGER=$(command -v avdmanager) -else - AVDMANAGER="$ANDROID_SDK/cmdline-tools/latest/bin/avdmanager" -fi - -# Homebrew's avdmanager/sdkmanager compute the SDK root from 'toolsdir'. -# By pointing toolsdir at $ANDROID_SDK/platform-tools (always present), the -# parent directory becomes the correct SDK root. Without this the Homebrew -# wrapper uses its own cellar path and cannot find installed system images. -export AVDMANAGER_OPTS="-Dcom.android.sdkmanager.toolsdir=$ANDROID_SDK/platform-tools" -export SDKMANAGER_OPTS="-Dcom.android.sdkmanager.toolsdir=$ANDROID_SDK/platform-tools" - -# ─── Detect host arch and NDK host tag ─────────────────────────────────────── -HOST_ARCH="$(uname -m)" -OS="$(uname -s)" -case "$OS-$HOST_ARCH" in - Darwin-arm64|Darwin-aarch64) NDK_HOST_TAG="darwin-x86_64" ;; # Rosetta runs x86_64 NDK - Darwin-x86_64) NDK_HOST_TAG="darwin-x86_64" ;; - Linux-x86_64) NDK_HOST_TAG="linux-x86_64" ;; - Linux-aarch64) NDK_HOST_TAG="linux-x86_64" ;; # rare; adjust if needed - *) - echo "ERROR: Unsupported OS/arch: $OS/$HOST_ARCH" >&2; exit 1 ;; -esac -echo "Host : $OS $HOST_ARCH (NDK host tag: $NDK_HOST_TAG)" - -# Verify Rosetta is usable on Apple Silicon (NDK ships darwin-x86_64 binaries) -if [ "$OS" = "Darwin" ] && [ "$HOST_ARCH" = "arm64" ]; then - if ! arch -x86_64 true 2>/dev/null; then - echo "ERROR: Rosetta 2 required on Apple Silicon to run the darwin-x86_64 NDK toolchain." >&2 - echo " Install it with: softwareupdate --install-rosetta --agree-to-license" >&2 - exit 1 - fi -fi - -# ─── Find latest installed NDK ──────────────────────────────────────────────── -NDK_ROOT=$(ls -d "$ANDROID_SDK/ndk/"*/ 2>/dev/null | sort -V | tail -1) -NDK_ROOT="${NDK_ROOT%/}" -if [ -z "$NDK_ROOT" ]; then - echo "ERROR: No NDK found under $ANDROID_SDK/ndk." >&2; exit 1 -fi -echo "Using NDK : $NDK_ROOT" - -NDK_TOOLCHAIN="$NDK_ROOT/toolchains/llvm/prebuilt/$NDK_HOST_TAG/bin" - -# ─── Detect installed system image ABI ─────────────────────────────────────── -# Pick the highest installed API <= ANDROID_API with an available image, then -# pick the ABI that matches the host (arm64-v8a on Apple Silicon/arm64 Linux, -# x86_64 on Intel). Fall back to whatever is installed. -prefer_abi() { - case "$HOST_ARCH" in - arm64|aarch64) echo "arm64-v8a" ;; - *) echo "x86_64" ;; - esac -} - -IMAGE_BASE="$ANDROID_SDK/system-images" -ANDROID_ABI="" -ACTUAL_API="$ANDROID_API" -PREFERRED_ABI="$(prefer_abi)" - -# Search from ANDROID_API down to find a matching installed image -for try_api in $ANDROID_API 35 34 33; do - for try_abi in "$PREFERRED_ABI" arm64-v8a x86_64; do - for try_tag in google_apis_playstore google_apis default; do - candidate="$IMAGE_BASE/android-${try_api}/${try_tag}/${try_abi}" - if [ -d "$candidate" ]; then - ACTUAL_API="$try_api" - ANDROID_ABI="$try_abi" - IMAGE_TAG="$try_tag" - break 3 - fi - done - done -done - -if [ -z "$ANDROID_ABI" ]; then - echo "No system image found locally. Installing android-${ANDROID_API} / google_apis_playstore / ${PREFERRED_ABI} ..." - ANDROID_ABI="$PREFERRED_ABI" - ACTUAL_API="$ANDROID_API" - IMAGE_TAG="google_apis_playstore" - echo "y" | "$SDKMANAGER" "system-images;android-${ACTUAL_API};${IMAGE_TAG};${ANDROID_ABI}" -fi - -SYSTEM_IMAGE_PKG="system-images;android-${ACTUAL_API};${IMAGE_TAG};${ANDROID_ABI}" -echo "System img : $SYSTEM_IMAGE_PKG" -echo "Android ABI: $ANDROID_ABI" - -# ─── Select NDK clang for the target ABI ───────────────────────────────────── -case "$ANDROID_ABI" in - arm64-v8a) CLANG_PREFIX="aarch64-linux-android" ;; - x86_64) CLANG_PREFIX="x86_64-linux-android" ;; - armeabi-v7a) CLANG_PREFIX="armv7a-linux-androideabi" ;; - x86) CLANG_PREFIX="i686-linux-android" ;; - *) - echo "ERROR: Unsupported ABI: $ANDROID_ABI" >&2; exit 1 ;; -esac - -CLANG="$NDK_TOOLCHAIN/${CLANG_PREFIX}${ACTUAL_API}-clang" -if [ ! -x "$CLANG" ]; then - # Fall back to highest API version available in toolchain - CLANG=$(ls "$NDK_TOOLCHAIN/${CLANG_PREFIX}"*-clang 2>/dev/null | grep -v '++' | sort -V | tail -1 || true) -fi -if [ ! -x "$CLANG" ]; then - echo "ERROR: NDK clang not found at $NDK_TOOLCHAIN for prefix $CLANG_PREFIX" >&2; exit 1 -fi -echo "Clang : $CLANG" - -# ─── Create AVD (idempotent, all files under AVD_HOME) ─────────────────────── -# Export ANDROID_AVD_HOME so avdmanager writes its .ini here, not ~/.android/avd/ -export ANDROID_AVD_HOME="$AVD_HOME" -mkdir -p "$AVD_HOME" - -if ANDROID_AVD_HOME="$AVD_HOME" "$AVDMANAGER" list avd 2>/dev/null | grep -q "Name: $AVD_NAME"; then - echo "AVD '$AVD_NAME' already exists in $AVD_HOME — reusing." -else - echo "Creating AVD '$AVD_NAME' in $AVD_HOME ..." - # --path sets the .avd/ dir location; ANDROID_AVD_HOME sets where the .ini goes - echo "no" | ANDROID_AVD_HOME="$AVD_HOME" "$AVDMANAGER" create avd \ - --name "$AVD_NAME" \ - --package "$SYSTEM_IMAGE_PKG" \ - --path "$AVD_HOME/$AVD_NAME.avd" \ - --force -fi - -# The Homebrew avdmanager writes image.sysdir.1 relative to dirname($ANDROID_SDK) -# (e.g. "sdk/system-images/...") because it computes the SDK root as one level -# above $ANDROID_SDK. Patch config.ini so the path is correct relative to -# $ANDROID_SDK itself (strip the leading "sdk/" prefix). -CONFIG_INI="$AVD_HOME/$AVD_NAME.avd/config.ini" -if grep -q "^image\.sysdir\.1=sdk/" "$CONFIG_INI" 2>/dev/null; then - echo "Patching config.ini: removing spurious 'sdk/' prefix from image.sysdir.1 ..." - sed -i.bak 's|^image\.sysdir\.1=sdk/|image.sysdir.1=|' "$CONFIG_INI" - echo " image.sysdir.1=$(grep '^image\.sysdir\.1=' "$CONFIG_INI")" -fi - -# ─── Write Hello World C source ─────────────────────────────────────────────── -SRC_DIR="$SCRIPT_DIR/hello_world" -mkdir -p "$SRC_DIR" - -cat > "$SRC_DIR/hello.c" <<'CSRC' -#include -#include - -#define TAG "HelloWorld" - -int main(void) { - printf("hello world\n"); - __android_log_print(ANDROID_LOG_INFO, TAG, "hello world"); - return 0; -} -CSRC - -# ─── Compile for Android ────────────────────────────────────────────────────── -echo "Compiling hello.c for Android ($ANDROID_ABI, API $ACTUAL_API) ..." -"$CLANG" -o "$SRC_DIR/hello" "$SRC_DIR/hello.c" -llog -echo "Binary: $SRC_DIR/hello ($(file "$SRC_DIR/hello" | cut -d: -f2-))" - -# ─── Kill any stale emulator on the default port (5554) ────────────────────── -EMU_SERIAL="emulator-5554" -if "$ADB" devices | grep -q "^$EMU_SERIAL"; then - echo "Stopping stale emulator $EMU_SERIAL ..." - "$ADB" -s "$EMU_SERIAL" emu kill 2>/dev/null || true - sleep 3 -fi - -# ─── Start emulator ────────────────────────────────────────────────────────── -echo "Starting emulator ..." -ANDROID_AVD_HOME="$AVD_HOME" ANDROID_SDK_ROOT="$ANDROID_SDK" "$EMULATOR" \ - -avd "$AVD_NAME" \ - -no-window \ - -no-audio \ - -no-boot-anim \ - -gpu swiftshader_indirect \ - -port 5554 \ - & -EMULATOR_PID=$! -echo "Emulator PID: $EMULATOR_PID" - -cleanup() { - echo "Shutting down emulator (PID $EMULATOR_PID) ..." - kill "$EMULATOR_PID" 2>/dev/null || true -} -trap cleanup EXIT - -# ─── Wait for device online ─────────────────────────────────────────────────── -echo "Waiting for device to come online ..." -WAIT_TIMEOUT=120 -ELAPSED=0 -while true; do - # adb get-state may fail during early boot — don't let set -e exit the script - DEV_STATE=$("$ADB" -s "$EMU_SERIAL" get-state 2>/dev/null || true) - [ "$DEV_STATE" = "device" ] && break - if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then - echo "ERROR: Emulator process died unexpectedly." >&2; exit 1 - fi - if [ "$ELAPSED" -ge "$WAIT_TIMEOUT" ]; then - echo "ERROR: Emulator did not come online within ${WAIT_TIMEOUT}s." >&2; exit 1 - fi - sleep 2; ELAPSED=$((ELAPSED + 2)) -done -echo "Device online." - -echo "Waiting for boot to complete (may take a few minutes) ..." -BOOT_TIMEOUT=300 -ELAPSED=0 -while true; do - # adb shell may fail transiently during boot — guard with || true - BOOT_PROP=$("$ADB" -s "$EMU_SERIAL" shell getprop sys.boot_completed 2>/dev/null || true) - BOOT_PROP=$(printf '%s' "$BOOT_PROP" | tr -d '\r\n') - if [ "$BOOT_PROP" = "1" ]; then - echo "Emulator booted successfully." - break - fi - if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then - echo "ERROR: Emulator process died while waiting for boot." >&2; exit 1 - fi - if [ "$ELAPSED" -ge "$BOOT_TIMEOUT" ]; then - echo "ERROR: Emulator did not boot within ${BOOT_TIMEOUT}s." >&2; exit 1 - fi - sleep 5; ELAPSED=$((ELAPSED + 5)) - echo " ... waiting (${ELAPSED}s)" -done - -# ─── Push and run binary ───────────────────────────────────────────────────── -echo "Pushing binary to emulator ..." -"$ADB" -s "$EMU_SERIAL" push "$SRC_DIR/hello" /data/local/tmp/hello -"$ADB" -s "$EMU_SERIAL" shell chmod +x /data/local/tmp/hello - -echo "Clearing logcat buffer ..." -"$ADB" -s "$EMU_SERIAL" logcat -c - -echo "Running hello binary ..." -STDOUT=$("$ADB" -s "$EMU_SERIAL" shell /data/local/tmp/hello 2>&1 || true) -echo "Program stdout: $STDOUT" - -# ─── Poll logcat until entry appears or timeout ─────────────────────────────── -echo "Polling logcat for '$LOGCAT_TAG' (up to ${LOGCAT_TIMEOUT}s) ..." -LOGCAT="" -ELAPSED=0 -while [ "$ELAPSED" -lt "$LOGCAT_TIMEOUT" ]; do - LOGCAT=$("$ADB" -s "$EMU_SERIAL" logcat -d -s "$LOGCAT_TAG:I" 2>&1 || true) - if echo "$LOGCAT" | grep -qi "hello world"; then - break - fi - sleep 2; ELAPSED=$((ELAPSED + 2)) -done -echo "--- logcat ($LOGCAT_TAG) ---" -echo "$LOGCAT" -echo "----------------------------" - -# ─── Verify ────────────────────────────────────────────────────────────────── -STDOUT_OK=false; LOGCAT_OK=false -echo "$STDOUT" | grep -qi "hello world" && STDOUT_OK=true -echo "$LOGCAT" | grep -qi "hello world" && LOGCAT_OK=true - -if $STDOUT_OK && $LOGCAT_OK; then - echo "" - echo "✅ SUCCESS: 'hello world' found in both stdout AND logcat." -elif $STDOUT_OK; then - echo "" - echo "✅ SUCCESS: 'hello world' found in stdout." - echo "⚠️ WARNING: Not found in logcat within ${LOGCAT_TIMEOUT}s." -else - echo "" - echo "❌ FAILURE: 'hello world' NOT found in program output." >&2 - exit 1 -fi diff --git a/scripts/hello_world/hello.c b/scripts/hello_world/hello.c deleted file mode 100644 index 8e056feb..00000000 --- a/scripts/hello_world/hello.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include -#define TAG "HelloWorld" -int main(void) { - printf("hello world\n"); - __android_log_print(ANDROID_LOG_INFO, TAG, "hello world"); - return 0; -} From 1d9ef82fedc9eea785fa8a4ecd4d696c86cd459e Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:11:13 +0200 Subject: [PATCH 06/25] Fix syntax error in test result parsing loop Replace 'for xml in ... 2>/dev/null' (invalid shell syntax) with 'find | while read' which handles redirection correctly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/android_unit_tests.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/android_unit_tests.sh b/scripts/android_unit_tests.sh index 370eb939..6b09d924 100755 --- a/scripts/android_unit_tests.sh +++ b/scripts/android_unit_tests.sh @@ -170,8 +170,7 @@ RESULT_SUMMARY="$ANDROID_PROJECT/app/build/reports/androidTests/connected/index. TOTAL=0; FAILURES=0; ERRORS=0; SKIPPED=0 if [ -d "$RESULTS_DIR" ]; then - for xml in "$RESULTS_DIR"/*.xml "$RESULTS_DIR"/**/*.xml 2>/dev/null; do - [ -f "$xml" ] || continue + while IFS= read -r xml; do t=$(grep -o 'tests="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) f=$(grep -o 'failures="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) e=$(grep -o 'errors="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) @@ -181,9 +180,8 @@ if [ -d "$RESULTS_DIR" ]; then ERRORS=$((ERRORS + e)) SKIPPED=$((SKIPPED + s)) echo " Result file: $(basename "$xml") — tests=$t failures=$f errors=$e" - # Print any failure details grep -A5 '/dev/null | head -30 || true - done + done < <(find "$RESULTS_DIR" -name "*.xml" 2>/dev/null) else echo " No result XML files found in $RESULTS_DIR" fi From 4426159d2f2c0f2bb5a374a8e2fff91a3f757774 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:12:39 +0200 Subject: [PATCH 07/25] Capture logcat to scripts/logcat.log during test run - Start adb logcat capture (after boot, before tests) into scripts/logcat.log - Stop capture and print the path in the cleanup trap - On failure, print filtered logcat (StdoutLogger/TestRunner/crash lines) inline so failures are immediately visible without extra commands - gitignore scripts/logcat.log Usage after a run: grep StdoutLogger scripts/logcat.log # test output cat scripts/logcat.log # everything Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitignore | 1 + scripts/android_unit_tests.sh | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/.gitignore b/.gitignore index 5560fb81..ae35c933 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ Tests/UnitTests/dist/ # Android emulator artifacts scripts/avd_home/ +scripts/logcat.log diff --git a/scripts/android_unit_tests.sh b/scripts/android_unit_tests.sh index 6b09d924..1f9d8ca4 100755 --- a/scripts/android_unit_tests.sh +++ b/scripts/android_unit_tests.sh @@ -107,8 +107,13 @@ ANDROID_AVD_HOME="$AVD_HOME" ANDROID_SDK_ROOT="$ANDROID_SDK" "$EMULATOR" \ EMULATOR_PID=$! echo "Emulator PID: $EMULATOR_PID" +LOGCAT_FILE="$SCRIPT_DIR/logcat.log" + cleanup() { echo "" + # Stop logcat capture + kill "$LOGCAT_PID" 2>/dev/null || true + echo "Logcat saved : $LOGCAT_FILE" echo "Shutting down emulator (PID $EMULATOR_PID) ..." kill "$EMULATOR_PID" 2>/dev/null || true } @@ -147,6 +152,12 @@ while true; do echo " ... waiting (${ELAPSED}s)" done +# Start logcat capture (clear first so we only capture test output) +"$ADB" -s "$EMU_SERIAL" logcat -c +"$ADB" -s "$EMU_SERIAL" logcat > "$LOGCAT_FILE" 2>&1 & +LOGCAT_PID=$! +echo "Logcat PID : $LOGCAT_PID → $LOGCAT_FILE" + # ─── Run Gradle instrumented tests ─────────────────────────────────────────── echo "" echo "═══ Step 4: Building and running Android unit tests ═══" @@ -192,5 +203,11 @@ if [ "$GRADLE_EXIT" -eq 0 ] && [ "$FAILURES" -eq 0 ] && [ "$ERRORS" -eq 0 ]; the else echo "❌ FAILURE: $FAILURES failure(s), $ERRORS error(s) out of $TOTAL tests." >&2 [ "$GRADLE_EXIT" -ne 0 ] && echo " Gradle exit code: $GRADLE_EXIT" >&2 + echo "" + echo "── Logcat (StdoutLogger / test output) ──────────────────────────────" + grep "StdoutLogger\|TestRunner\|FAILED\|PASSED\|AndroidRuntime\|FATAL" \ + "$LOGCAT_FILE" 2>/dev/null | tail -60 || true + echo "── Full logcat: $LOGCAT_FILE ──────────────────────────────────────" exit 1 fi +echo "── Full logcat: $LOGCAT_FILE" From 15159085e815b57ba1587076698dc14276892318 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:13:25 +0200 Subject: [PATCH 08/25] Fix LOGCAT_PID unbound variable in cleanup trap Initialize LOGCAT_PID to empty string before the trap is registered so set -u does not abort if the script exits before logcat capture starts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/android_unit_tests.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/android_unit_tests.sh b/scripts/android_unit_tests.sh index 1f9d8ca4..e359991d 100755 --- a/scripts/android_unit_tests.sh +++ b/scripts/android_unit_tests.sh @@ -108,12 +108,12 @@ EMULATOR_PID=$! echo "Emulator PID: $EMULATOR_PID" LOGCAT_FILE="$SCRIPT_DIR/logcat.log" +LOGCAT_PID="" cleanup() { echo "" - # Stop logcat capture - kill "$LOGCAT_PID" 2>/dev/null || true - echo "Logcat saved : $LOGCAT_FILE" + [ -n "$LOGCAT_PID" ] && kill "$LOGCAT_PID" 2>/dev/null || true + [ -f "$LOGCAT_FILE" ] && echo "Logcat saved : $LOGCAT_FILE" echo "Shutting down emulator (PID $EMULATOR_PID) ..." kill "$EMULATOR_PID" 2>/dev/null || true } From 417d3523bcdf6e6aaa6ad6d38d07ccf6d2f78032 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 17:15:20 +0200 Subject: [PATCH 09/25] Disable emulator snapshots with -no-snapshot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- scripts/android_unit_tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/android_unit_tests.sh b/scripts/android_unit_tests.sh index e359991d..361793e2 100755 --- a/scripts/android_unit_tests.sh +++ b/scripts/android_unit_tests.sh @@ -101,6 +101,7 @@ ANDROID_AVD_HOME="$AVD_HOME" ANDROID_SDK_ROOT="$ANDROID_SDK" "$EMULATOR" \ -no-window \ -no-audio \ -no-boot-anim \ + -no-snapshot \ -gpu swiftshader_indirect \ -port 5554 \ & From ff6bb10732a48a545bba56f08ffa4d8791fd5e39 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 18:04:05 +0200 Subject: [PATCH 10/25] Point AndroidExtensions to fork branch with fdsan fix Replace the local PATCH_COMMAND workaround with a direct reference to the fix commit on the fork (PR: BabylonJS/AndroidExtensions#17). Remove patches/AndroidExtensions/StdoutLogger.cpp. Once the PR is merged upstream, revert GIT_REPOSITORY back to BabylonJS/AndroidExtensions and update GIT_TAG to the merge commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 7 +- patches/AndroidExtensions/StdoutLogger.cpp | 120 --------------------- 2 files changed, 2 insertions(+), 125 deletions(-) delete mode 100644 patches/AndroidExtensions/StdoutLogger.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 34553dc3..3b2176cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,11 +10,8 @@ include(FetchContent) # Declarations # -------------------------------------------------- FetchContent_Declare(AndroidExtensions - GIT_REPOSITORY https://github.com/BabylonJS/AndroidExtensions.git - GIT_TAG 2e85a8d43b89246c460112c9e5546ad54b6e87b4 - PATCH_COMMAND ${CMAKE_COMMAND} -E copy - "${CMAKE_CURRENT_SOURCE_DIR}/patches/AndroidExtensions/StdoutLogger.cpp" - "Source/StdoutLogger.cpp" + GIT_REPOSITORY https://github.com/CedricGuillemet/AndroidExtensions.git + GIT_TAG d140b34f09614894937465cdc3384c14b796358b EXCLUDE_FROM_ALL) FetchContent_Declare(arcana.cpp GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git diff --git a/patches/AndroidExtensions/StdoutLogger.cpp b/patches/AndroidExtensions/StdoutLogger.cpp deleted file mode 100644 index cb3762f7..00000000 --- a/patches/AndroidExtensions/StdoutLogger.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -namespace -{ - std::mutex g_stateMutex; - bool g_started = false; - int fd_stdout[2] = {-1, -1}; - int fd_stderr[2] = {-1, -1}; - - void thread_func(int fd, int prio) - { - FILE* stream = fdopen(fd, "r"); - - while (true) - { - char* line = nullptr; - size_t n = 0; - ssize_t nread = getline(&line, &n, stream); - if (nread == -1) - { - break; - } - - line[nread - 1] = '\0'; - __android_log_write(prio, "StdoutLogger", line); - free(line); - } - - fclose(stream); - } - - void* thread_func_stdout(void*) - { - thread_func(fd_stdout[0], ANDROID_LOG_INFO); - return 0; - } - - void* thread_func_stderr(void*) - { - thread_func(fd_stderr[0], ANDROID_LOG_ERROR); - return 0; - } - - void redirect(int fd[2], int fd_redirect, void*(*thread_func)(void*)) - { - // create the pipe and redirect - pipe(fd); - dup2(fd[1], fd_redirect); - - // spawn the thread - pthread_t thr; - if (pthread_create(&thr, 0, thread_func, 0) == -1) - { - return; - } - - pthread_detach(thr); - } -} - -namespace android::StdoutLogger -{ - void Start() - { - std::lock_guard lock(g_stateMutex); - - if (g_started) - { - return; - } - - redirect(fd_stdout, fileno(stdout), thread_func_stdout); - redirect(fd_stderr, fileno(stderr), thread_func_stderr); - - g_started = true; - } - - void Stop() - { - std::lock_guard lock(g_stateMutex); - - if (!g_started) - { - return; - } - - g_started = false; - - // Close the write ends of the pipes. This signals EOF to the reader - // threads, which will then call fclose() to close the read ends. - // Do NOT close the read ends here: fdopen() transferred ownership of - // fd[0] to the FILE* inside the reader thread, and calling close() on - // an fd owned by a FILE* is a fdsan violation on Android API 29+. - if (fd_stdout[1] != -1) - { - close(fd_stdout[1]); - fd_stdout[1] = -1; - } - fd_stdout[0] = -1; // owned by reader thread's FILE*; closed via fclose() - - if (fd_stderr[1] != -1) - { - close(fd_stderr[1]); - fd_stderr[1] = -1; - } - fd_stderr[0] = -1; // owned by reader thread's FILE*; closed via fclose() - } - - bool IsStarted() - { - std::lock_guard lock(g_stateMutex); - return g_started; - } -} From 26d06d504679353b3bc6d9f09fb63f9b29689ab6 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 18:52:37 +0200 Subject: [PATCH 11/25] Replace shell script with CI workflow; enable Android V8 tests build-android.yml: - Add 'Install NDK' step (sdkmanager ndk;28.2.13676358) so the pinned NDK version is guaranteed to be present on the runner - Add 'Build JS test bundle' step (npm install in Tests/) which triggers the postinstall webpack build producing UnitTests/dist/tests.js - Add -no-snapshot -no-window -no-boot-anim -no-audio to emulator-options ci.yml: - Enable Android_V8 job (removes the TODO comment block) - Keep Android_JSC commented until verified Remove scripts/android_unit_tests.sh (superseded by the workflow). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-android.yml | 8 ++ .github/workflows/ci.yml | 8 +- .gitignore | 1 - scripts/android_unit_tests.sh | 214 ---------------------------- 4 files changed, 12 insertions(+), 219 deletions(-) delete mode 100755 scripts/android_unit_tests.sh diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 9dd3a354..671d50fd 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -22,6 +22,13 @@ jobs: distribution: temurin java-version: '17' + - name: Install NDK + run: echo "y" | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "ndk;${{ env.NDK_VERSION }}" + + - name: Build JS test bundle + working-directory: Tests + run: npm install + - name: Enable KVM run: | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules @@ -34,6 +41,7 @@ jobs: api-level: 33 target: google_apis arch: x86_64 + emulator-options: -no-snapshot -no-window -no-boot-anim -no-audio script: | cd Tests/UnitTests/Android chmod +x gradlew diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7355d66d..e42340f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,10 +64,10 @@ jobs: # with: # js-engine: JavaScriptCore - # Android_V8: - # uses: ./.github/workflows/build-android.yml - # with: - # js-engine: V8 + Android_V8: + uses: ./.github/workflows/build-android.yml + with: + js-engine: V8 # ── macOS ───────────────────────────────────────────────────── macOS_Xcode164: diff --git a/.gitignore b/.gitignore index ae35c933..5560fb81 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ Tests/UnitTests/dist/ # Android emulator artifacts scripts/avd_home/ -scripts/logcat.log diff --git a/scripts/android_unit_tests.sh b/scripts/android_unit_tests.sh deleted file mode 100755 index 361793e2..00000000 --- a/scripts/android_unit_tests.sh +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env bash -# android_unit_tests.sh -# Builds the UnitTests Android project with gradlew, runs them on an emulator, -# and reports pass/fail. Reuses the AVD created by android_emulator_test.sh. - -set -euo pipefail - -# ─── Config ─────────────────────────────────────────────────────────────────── -ANDROID_SDK="${ANDROID_HOME:-$HOME/Library/Android/sdk}" -ANDROID_API=36 -AVD_NAME="JSRemuTestAVD" -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -TESTS_DIR="$REPO_ROOT/Tests" -ANDROID_PROJECT="$TESTS_DIR/UnitTests/Android" -GRADLEW="$ANDROID_PROJECT/gradlew" -AVD_HOME="$SCRIPT_DIR/avd_home" -EMU_SERIAL="emulator-5554" - -# ─── Tool paths ─────────────────────────────────────────────────────────────── -EMULATOR="$ANDROID_SDK/emulator/emulator" -ADB="$ANDROID_SDK/platform-tools/adb" - -if command -v avdmanager &>/dev/null; then - AVDMANAGER=$(command -v avdmanager) -else - AVDMANAGER="$ANDROID_SDK/cmdline-tools/latest/bin/avdmanager" -fi - -# Homebrew avdmanager needs toolsdir to find the SDK root correctly -export AVDMANAGER_OPTS="-Dcom.android.sdkmanager.toolsdir=$ANDROID_SDK/platform-tools" -export ANDROID_AVD_HOME="$AVD_HOME" - -# ─── Detect ABI and NDK version ────────────────────────────────────────────── -HOST_ARCH="$(uname -m)" -case "$HOST_ARCH" in - arm64|aarch64) ANDROID_ABI="arm64-v8a" ;; - x86_64) ANDROID_ABI="x86_64" ;; - *) echo "ERROR: Unsupported arch: $HOST_ARCH" >&2; exit 1 ;; -esac -echo "Host : $(uname -s) $HOST_ARCH → Android ABI: $ANDROID_ABI" - -# Auto-detect the highest installed NDK version to override the one in build.gradle -NDK_DIR="$ANDROID_SDK/ndk" -NDK_VERSION="" -if [ -d "$NDK_DIR" ]; then - NDK_VERSION=$(ls "$NDK_DIR" | sort -V | tail -1) -fi -if [ -z "$NDK_VERSION" ]; then - echo "ERROR: No NDK found in $NDK_DIR" >&2; exit 1 -fi -echo "NDK : $NDK_VERSION" - -# ─── Write local.properties so Gradle can find the SDK ─────────────────────── -echo "sdk.dir=$ANDROID_SDK" > "$ANDROID_PROJECT/local.properties" -echo "SDK dir : $ANDROID_SDK" - -# ─── npm install: build the JavaScript test bundle ─────────────────────────── -echo "" -echo "═══ Step 1: Building JS test bundle (npm install in $TESTS_DIR) ═══" -(cd "$TESTS_DIR" && npm install) -echo "JS bundle : $TESTS_DIR/UnitTests/dist/ ✓" - -# ─── Ensure AVD exists ─────────────────────────────────────────────────────── -echo "" -echo "═══ Step 2: Ensuring AVD exists ═══" -mkdir -p "$AVD_HOME" - -SYSTEM_IMAGE_PKG="system-images;android-${ANDROID_API};google_apis_playstore;${ANDROID_ABI}" - -if ANDROID_AVD_HOME="$AVD_HOME" "$AVDMANAGER" list avd 2>/dev/null | grep -q "Name: $AVD_NAME"; then - echo "AVD '$AVD_NAME' already exists — reusing." -else - echo "Creating AVD '$AVD_NAME' ..." - echo "no" | ANDROID_AVD_HOME="$AVD_HOME" "$AVDMANAGER" create avd \ - --name "$AVD_NAME" \ - --package "$SYSTEM_IMAGE_PKG" \ - --path "$AVD_HOME/$AVD_NAME.avd" \ - --force -fi - -# Patch the image path (Homebrew avdmanager writes a spurious "sdk/" prefix) -CONFIG_INI="$AVD_HOME/$AVD_NAME.avd/config.ini" -if grep -q "^image\.sysdir\.1=sdk/" "$CONFIG_INI" 2>/dev/null; then - sed -i.bak 's|^image\.sysdir\.1=sdk/|image.sysdir.1=|' "$CONFIG_INI" - echo "Patched config.ini: $(grep '^image\.sysdir\.1=' "$CONFIG_INI")" -fi - -# ─── Kill any stale emulator on port 5554 ──────────────────────────────────── -if "$ADB" devices | grep -q "^$EMU_SERIAL"; then - echo "Stopping stale emulator $EMU_SERIAL ..." - "$ADB" -s "$EMU_SERIAL" emu kill 2>/dev/null || true - sleep 3 -fi - -# ─── Start emulator ────────────────────────────────────────────────────────── -echo "" -echo "═══ Step 3: Starting emulator ═══" -ANDROID_AVD_HOME="$AVD_HOME" ANDROID_SDK_ROOT="$ANDROID_SDK" "$EMULATOR" \ - -avd "$AVD_NAME" \ - -no-window \ - -no-audio \ - -no-boot-anim \ - -no-snapshot \ - -gpu swiftshader_indirect \ - -port 5554 \ - & -EMULATOR_PID=$! -echo "Emulator PID: $EMULATOR_PID" - -LOGCAT_FILE="$SCRIPT_DIR/logcat.log" -LOGCAT_PID="" - -cleanup() { - echo "" - [ -n "$LOGCAT_PID" ] && kill "$LOGCAT_PID" 2>/dev/null || true - [ -f "$LOGCAT_FILE" ] && echo "Logcat saved : $LOGCAT_FILE" - echo "Shutting down emulator (PID $EMULATOR_PID) ..." - kill "$EMULATOR_PID" 2>/dev/null || true -} -trap cleanup EXIT - -# ─── Wait for device online ─────────────────────────────────────────────────── -echo "Waiting for device to come online ..." -WAIT_TIMEOUT=120 -ELAPSED=0 -while true; do - DEV_STATE=$("$ADB" -s "$EMU_SERIAL" get-state 2>/dev/null || true) - [ "$DEV_STATE" = "device" ] && break - if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then - echo "ERROR: Emulator process died unexpectedly." >&2; exit 1 - fi - if [ "$ELAPSED" -ge "$WAIT_TIMEOUT" ]; then - echo "ERROR: Emulator did not come online within ${WAIT_TIMEOUT}s." >&2; exit 1 - fi - sleep 2; ELAPSED=$((ELAPSED + 2)) -done - -echo "Waiting for full boot ..." -BOOT_TIMEOUT=300 -ELAPSED=0 -while true; do - BOOT_PROP=$("$ADB" -s "$EMU_SERIAL" shell getprop sys.boot_completed 2>/dev/null || true) - BOOT_PROP=$(printf '%s' "$BOOT_PROP" | tr -d '\r\n') - [ "$BOOT_PROP" = "1" ] && { echo "Emulator booted."; break; } - if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then - echo "ERROR: Emulator died during boot." >&2; exit 1 - fi - if [ "$ELAPSED" -ge "$BOOT_TIMEOUT" ]; then - echo "ERROR: Boot timed out after ${BOOT_TIMEOUT}s." >&2; exit 1 - fi - sleep 5; ELAPSED=$((ELAPSED + 5)) - echo " ... waiting (${ELAPSED}s)" -done - -# Start logcat capture (clear first so we only capture test output) -"$ADB" -s "$EMU_SERIAL" logcat -c -"$ADB" -s "$EMU_SERIAL" logcat > "$LOGCAT_FILE" 2>&1 & -LOGCAT_PID=$! -echo "Logcat PID : $LOGCAT_PID → $LOGCAT_FILE" - -# ─── Run Gradle instrumented tests ─────────────────────────────────────────── -echo "" -echo "═══ Step 4: Building and running Android unit tests ═══" -echo "Project : $ANDROID_PROJECT" -echo "Device : $EMU_SERIAL" -echo "" - -GRADLE_EXIT=0 -(cd "$ANDROID_PROJECT" && \ - ANDROID_SDK_ROOT="$ANDROID_SDK" \ - "$GRADLEW" connectedDebugAndroidTest \ - -PndkVersion="$NDK_VERSION" \ - 2>&1) || GRADLE_EXIT=$? - -# ─── Parse test results ─────────────────────────────────────────────────────── -echo "" -echo "═══ Step 5: Test results ═══" - -RESULTS_DIR="$ANDROID_PROJECT/app/build/outputs/androidTest-results/connected" -RESULT_SUMMARY="$ANDROID_PROJECT/app/build/reports/androidTests/connected/index.html" - -TOTAL=0; FAILURES=0; ERRORS=0; SKIPPED=0 -if [ -d "$RESULTS_DIR" ]; then - while IFS= read -r xml; do - t=$(grep -o 'tests="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) - f=$(grep -o 'failures="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) - e=$(grep -o 'errors="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) - s=$(grep -o 'skipped="[0-9]*"' "$xml" | head -1 | grep -o '[0-9]*' || echo 0) - TOTAL=$((TOTAL + t)) - FAILURES=$((FAILURES + f)) - ERRORS=$((ERRORS + e)) - SKIPPED=$((SKIPPED + s)) - echo " Result file: $(basename "$xml") — tests=$t failures=$f errors=$e" - grep -A5 '/dev/null | head -30 || true - done < <(find "$RESULTS_DIR" -name "*.xml" 2>/dev/null) -else - echo " No result XML files found in $RESULTS_DIR" -fi - -echo "" -if [ "$GRADLE_EXIT" -eq 0 ] && [ "$FAILURES" -eq 0 ] && [ "$ERRORS" -eq 0 ]; then - echo "✅ SUCCESS: All $TOTAL tests passed (skipped: $SKIPPED)." -else - echo "❌ FAILURE: $FAILURES failure(s), $ERRORS error(s) out of $TOTAL tests." >&2 - [ "$GRADLE_EXIT" -ne 0 ] && echo " Gradle exit code: $GRADLE_EXIT" >&2 - echo "" - echo "── Logcat (StdoutLogger / test output) ──────────────────────────────" - grep "StdoutLogger\|TestRunner\|FAILED\|PASSED\|AndroidRuntime\|FATAL" \ - "$LOGCAT_FILE" 2>/dev/null | tail -60 || true - echo "── Full logcat: $LOGCAT_FILE ──────────────────────────────────────" - exit 1 -fi -echo "── Full logcat: $LOGCAT_FILE" From d00b0aab72b4638a217eb3d7cf0009336f8fca80 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 18:54:23 +0200 Subject: [PATCH 12/25] Enable Android_JSC job; restore logcat.log gitignore entry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 9 ++++----- .gitignore | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e42340f5..752cda09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,11 +58,10 @@ jobs: js-engine: V8 # ── Android ─────────────────────────────────────────────────── - # TODO: Re-enable when Android emulator works reliably on GitHub Actions runners. - # Android_JSC: - # uses: ./.github/workflows/build-android.yml - # with: - # js-engine: JavaScriptCore + Android_JSC: + uses: ./.github/workflows/build-android.yml + with: + js-engine: JavaScriptCore Android_V8: uses: ./.github/workflows/build-android.yml diff --git a/.gitignore b/.gitignore index 5560fb81..ae35c933 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ Tests/UnitTests/dist/ # Android emulator artifacts scripts/avd_home/ +scripts/logcat.log From d8044583092538462a414c8e7b2471795c744699 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 18:55:10 +0200 Subject: [PATCH 13/25] Revert .gitignore to upstream state Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitignore | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitignore b/.gitignore index ae35c933..0260b044 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ /Build Tests/UnitTests/dist/ - -# Android emulator artifacts -scripts/avd_home/ -scripts/logcat.log From 69adf6a24f4564f05fb771019064552dbfc9d9a3 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:07:24 +0200 Subject: [PATCH 14/25] Fix gradlew path: avoid cd in android-emulator-runner script Each line of 'script:' is executed as a separate sh -c invocation, so cd does not persist. Use full path + -p flag instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-android.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 671d50fd..dc63e145 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -43,9 +43,8 @@ jobs: arch: x86_64 emulator-options: -no-snapshot -no-window -no-boot-anim -no-audio script: | - cd Tests/UnitTests/Android - chmod +x gradlew - ./gradlew connectedAndroidTest \ + chmod +x Tests/UnitTests/Android/gradlew + Tests/UnitTests/Android/gradlew -p Tests/UnitTests/Android connectedAndroidTest \ -PabiFilters=x86_64 \ -PjsEngine=${{ inputs['js-engine'] }} \ -PndkVersion=${{ env.NDK_VERSION }} From 0cdbe200b125980dae02c083d36ff9feee42ff28 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:31:38 +0200 Subject: [PATCH 15/25] Fix gradlew args: use single-line script to avoid backslash continuation bug android-emulator-runner runs each script line in a separate sh -c call, so backslash line continuations are passed as literal args to Gradle, causing 'Task backslash not found' and -PndkVersion never reaching Gradle. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-android.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index dc63e145..be0d48f2 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -42,12 +42,7 @@ jobs: target: google_apis arch: x86_64 emulator-options: -no-snapshot -no-window -no-boot-anim -no-audio - script: | - chmod +x Tests/UnitTests/Android/gradlew - Tests/UnitTests/Android/gradlew -p Tests/UnitTests/Android connectedAndroidTest \ - -PabiFilters=x86_64 \ - -PjsEngine=${{ inputs['js-engine'] }} \ - -PndkVersion=${{ env.NDK_VERSION }} + script: chmod +x Tests/UnitTests/Android/gradlew && Tests/UnitTests/Android/gradlew -p Tests/UnitTests/Android connectedAndroidTest -PabiFilters=x86_64 -PjsEngine=${{ inputs['js-engine'] }} -PndkVersion=${{ env.NDK_VERSION }} - name: Dump Test Results if: always() From 853028f408bbbbc3d82bb52cf516b58c0806aef8 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 19:37:26 +0200 Subject: [PATCH 16/25] Fix android/log.h include casing for case-sensitive Linux CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Tests/UnitTests/Android/app/src/main/cpp/JNI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/UnitTests/Android/app/src/main/cpp/JNI.cpp b/Tests/UnitTests/Android/app/src/main/cpp/JNI.cpp index fe243eb5..4415ce87 100644 --- a/Tests/UnitTests/Android/app/src/main/cpp/JNI.cpp +++ b/Tests/UnitTests/Android/app/src/main/cpp/JNI.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include #include From 61f7b015a66404be06e584adc64822837ed0fe49 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Mon, 20 Apr 2026 20:23:03 +0200 Subject: [PATCH 17/25] Add logcat capture as CI artifact for Android test diagnosis Write a proper shell script before the emulator starts: - Clear logcat before the test run - Capture logcat to a file in background while tests run - Preserve the gradlew exit code - Upload logcat.txt as a downloadable artifact per engine Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-android.yml | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index be0d48f2..35078a56 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -35,6 +35,21 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm + - name: Prepare test script + run: | + cat > /tmp/run_android_tests.sh << EOF + #!/bin/bash + adb logcat -c || true + adb logcat > \$GITHUB_WORKSPACE/logcat.txt & + LOGCAT_PID=\$! + chmod +x Tests/UnitTests/Android/gradlew + Tests/UnitTests/Android/gradlew -p Tests/UnitTests/Android connectedAndroidTest -PabiFilters=x86_64 -PjsEngine=${{ inputs['js-engine'] }} -PndkVersion=${{ env.NDK_VERSION }} + STATUS=\$? + kill \$LOGCAT_PID 2>/dev/null || true + exit \$STATUS + EOF + chmod +x /tmp/run_android_tests.sh + - name: Run Connected Android Test uses: reactivecircus/android-emulator-runner@v2 with: @@ -42,7 +57,7 @@ jobs: target: google_apis arch: x86_64 emulator-options: -no-snapshot -no-window -no-boot-anim -no-audio - script: chmod +x Tests/UnitTests/Android/gradlew && Tests/UnitTests/Android/gradlew -p Tests/UnitTests/Android connectedAndroidTest -PabiFilters=x86_64 -PjsEngine=${{ inputs['js-engine'] }} -PndkVersion=${{ env.NDK_VERSION }} + script: /tmp/run_android_tests.sh - name: Dump Test Results if: always() @@ -60,3 +75,11 @@ jobs: name: AndroidTestResults_${{ inputs['js-engine'] }} path: Tests/UnitTests/Android/app/build/outputs/androidTest-results/connected if-no-files-found: ignore + + - name: Upload Logcat + if: always() + uses: actions/upload-artifact@v4 + with: + name: Logcat_${{ inputs['js-engine'] }} + path: logcat.txt + if-no-files-found: ignore From 2898f045142d84c047743171aac898e4ead91029 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:35:42 +0200 Subject: [PATCH 18/25] Revert "Add logcat capture as CI artifact for Android test diagnosis" This reverts commit 61f7b015a66404be06e584adc64822837ed0fe49. --- .github/workflows/build-android.yml | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 35078a56..be0d48f2 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -35,21 +35,6 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: Prepare test script - run: | - cat > /tmp/run_android_tests.sh << EOF - #!/bin/bash - adb logcat -c || true - adb logcat > \$GITHUB_WORKSPACE/logcat.txt & - LOGCAT_PID=\$! - chmod +x Tests/UnitTests/Android/gradlew - Tests/UnitTests/Android/gradlew -p Tests/UnitTests/Android connectedAndroidTest -PabiFilters=x86_64 -PjsEngine=${{ inputs['js-engine'] }} -PndkVersion=${{ env.NDK_VERSION }} - STATUS=\$? - kill \$LOGCAT_PID 2>/dev/null || true - exit \$STATUS - EOF - chmod +x /tmp/run_android_tests.sh - - name: Run Connected Android Test uses: reactivecircus/android-emulator-runner@v2 with: @@ -57,7 +42,7 @@ jobs: target: google_apis arch: x86_64 emulator-options: -no-snapshot -no-window -no-boot-anim -no-audio - script: /tmp/run_android_tests.sh + script: chmod +x Tests/UnitTests/Android/gradlew && Tests/UnitTests/Android/gradlew -p Tests/UnitTests/Android connectedAndroidTest -PabiFilters=x86_64 -PjsEngine=${{ inputs['js-engine'] }} -PndkVersion=${{ env.NDK_VERSION }} - name: Dump Test Results if: always() @@ -75,11 +60,3 @@ jobs: name: AndroidTestResults_${{ inputs['js-engine'] }} path: Tests/UnitTests/Android/app/build/outputs/androidTest-results/connected if-no-files-found: ignore - - - name: Upload Logcat - if: always() - uses: actions/upload-artifact@v4 - with: - name: Logcat_${{ inputs['js-engine'] }} - path: logcat.txt - if-no-files-found: ignore From b6f9576d6ac3040a3980a6f3b7056728f574c1ff Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:35:52 +0200 Subject: [PATCH 19/25] Point AndroidExtensions to BabylonJS upstream merged commit PR #17 (fix-fdsan-stdout-logger) has been merged into BabylonJS/AndroidExtensions. Switch from fork to upstream at the merge commit 5fe60c8. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b2176cf..f0335704 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ include(FetchContent) # Declarations # -------------------------------------------------- FetchContent_Declare(AndroidExtensions - GIT_REPOSITORY https://github.com/CedricGuillemet/AndroidExtensions.git - GIT_TAG d140b34f09614894937465cdc3384c14b796358b + GIT_REPOSITORY https://github.com/BabylonJS/AndroidExtensions.git + GIT_TAG 5fe60c8634dd9a5df76888e73e69202875e894f0 EXCLUDE_FROM_ALL) FetchContent_Declare(arcana.cpp GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git From 8fd66d80f3027ed26d63b829e008ab9e0ee49fe8 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:44:13 +0200 Subject: [PATCH 20/25] Bump actions/checkout to v5 (Node.js 24) v4 runs on Node.js 20 which is deprecated on GitHub Actions runners. v5 uses node24. All other actions in this workflow already use node24. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index be0d48f2..34f24df8 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-java@v4 with: From 799f75cd662cc91f96d25889e0d0fd92666bcb28 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Tue, 21 Apr 2026 09:59:03 +0200 Subject: [PATCH 21/25] Increase WebSocket multiple-connection test timeout to 10s The Android emulator routes traffic through QEMU NAT which adds significant latency vs native. The single-connection test already took 1741ms of the 2000ms budget; the multi-connection test (two serial round trips) reliably exceeded it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Tests/UnitTests/Scripts/tests.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/UnitTests/Scripts/tests.ts b/Tests/UnitTests/Scripts/tests.ts index 5728d4fa..764c838b 100644 --- a/Tests/UnitTests/Scripts/tests.ts +++ b/Tests/UnitTests/Scripts/tests.ts @@ -449,6 +449,7 @@ if (hostPlatform !== "Unix") { }); it("should connect correctly with multiple websocket connections", function (done) { + this.timeout(10000); const testMessage1 = "testMessage1"; const testMessage2 = "testMessage2"; let error: unknown; From e54df03d82a69ca680c66c07808c6d15d5505ba3 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:06:25 +0200 Subject: [PATCH 22/25] Bump actions/checkout to v5 across all workflows (Node.js 24) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-ios.yml | 2 +- .github/workflows/build-linux.yml | 2 +- .github/workflows/build-macos.yml | 2 +- .github/workflows/build-uwp.yml | 2 +- .github/workflows/build-win32.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-ios.yml b/.github/workflows/build-ios.yml index 76df92d1..bb197722 100644 --- a/.github/workflows/build-ios.yml +++ b/.github/workflows/build-ios.yml @@ -19,7 +19,7 @@ jobs: runs-on: ${{ inputs.runs-on }} timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Select Xcode ${{ inputs.xcode-version }} run: sudo xcode-select --switch /Applications/Xcode_${{ inputs.xcode-version }}.app/Contents/Developer diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index f8433225..49cd44a4 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -28,7 +28,7 @@ jobs: CC: ${{ inputs.cc }} CXX: ${{ inputs.cxx }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install packages run: | diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index fe5879db..24a6af91 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -24,7 +24,7 @@ jobs: runs-on: ${{ inputs.runs-on }} timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Select Xcode ${{ inputs.xcode-version }} run: sudo xcode-select --switch /Applications/Xcode_${{ inputs.xcode-version }}.app/Contents/Developer diff --git a/.github/workflows/build-uwp.yml b/.github/workflows/build-uwp.yml index cc534947..c07157a2 100644 --- a/.github/workflows/build-uwp.yml +++ b/.github/workflows/build-uwp.yml @@ -15,7 +15,7 @@ jobs: runs-on: windows-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Configure CMake run: > diff --git a/.github/workflows/build-win32.yml b/.github/workflows/build-win32.yml index d3bc0363..4fa03e85 100644 --- a/.github/workflows/build-win32.yml +++ b/.github/workflows/build-win32.yml @@ -15,7 +15,7 @@ jobs: runs-on: windows-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Configure CMake run: cmake -B Build/Win32 -A ${{ inputs.platform }} -D NAPI_JAVASCRIPT_ENGINE=${{ inputs.js-engine }} From 64a89165932a729e7d5ab526b63560bd5d2c7cae Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:15:37 +0200 Subject: [PATCH 23/25] Opt into Node.js 24 for actions in build-android.yml setup-java@v4 and upload-artifact@v4 tagged releases still run on node20. Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true to suppress the deprecation warning until v5 releases are available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-android.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 34f24df8..66acbd5c 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -9,6 +9,7 @@ on: env: NDK_VERSION: '28.2.13676358' + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: build: From e8d2fb70a30f1a34883e3bb4528858b1777273e7 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:22:49 +0200 Subject: [PATCH 24/25] Revert "Opt into Node.js 24 for actions in build-android.yml" This reverts commit 64a89165932a729e7d5ab526b63560bd5d2c7cae. --- .github/workflows/build-android.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 66acbd5c..34f24df8 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -9,7 +9,6 @@ on: env: NDK_VERSION: '28.2.13676358' - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: build: From 82fd3c97139cf76189742cf20fb352599e3d7340 Mon Sep 17 00:00:00 2001 From: Cedric Guillemet <1312968+CedricGuillemet@users.noreply.github.com> Date: Tue, 21 Apr 2026 10:42:32 +0200 Subject: [PATCH 25/25] Use dot notation for inputs.js-engine (consistent with other workflows) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build-android.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index 34f24df8..97b1690d 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -42,7 +42,7 @@ jobs: target: google_apis arch: x86_64 emulator-options: -no-snapshot -no-window -no-boot-anim -no-audio - script: chmod +x Tests/UnitTests/Android/gradlew && Tests/UnitTests/Android/gradlew -p Tests/UnitTests/Android connectedAndroidTest -PabiFilters=x86_64 -PjsEngine=${{ inputs['js-engine'] }} -PndkVersion=${{ env.NDK_VERSION }} + script: chmod +x Tests/UnitTests/Android/gradlew && Tests/UnitTests/Android/gradlew -p Tests/UnitTests/Android connectedAndroidTest -PabiFilters=x86_64 -PjsEngine=${{ inputs.js-engine }} -PndkVersion=${{ env.NDK_VERSION }} - name: Dump Test Results if: always() @@ -57,6 +57,6 @@ jobs: if: always() uses: actions/upload-artifact@v4 with: - name: AndroidTestResults_${{ inputs['js-engine'] }} + name: AndroidTestResults_${{ inputs.js-engine }} path: Tests/UnitTests/Android/app/build/outputs/androidTest-results/connected if-no-files-found: ignore