diff --git a/.github/workflows/build-all.yaml b/.github/workflows/build-all.yaml index 66e860658..7ca0c4d5c 100644 --- a/.github/workflows/build-all.yaml +++ b/.github/workflows/build-all.yaml @@ -1,6 +1,4 @@ ---- -name: build-all - +name: Build Oolite package for various platforms on: workflow_dispatch: push: @@ -35,7 +33,7 @@ jobs: - name: Install packages run: | - sudo oolite/ShellScripts/Linux/install_deps_root.sh + sudo oolite/ShellScripts/Linux/install_deps_root.sh --skip-wayland - name: Build Oolite run: | diff --git a/.github/workflows/test_builds.yaml b/.github/workflows/test_builds.yaml index 34d7bedc3..a0a3de15e 100644 --- a/.github/workflows/test_builds.yaml +++ b/.github/workflows/test_builds.yaml @@ -1,4 +1,4 @@ -name: Build Oolite package on various platforms +name: Test Oolite package on various platforms on: push: pull_request: @@ -23,7 +23,7 @@ jobs: sudo ShellScripts/Linux/install_deps_root.sh - name: Build Oolite run: | - ShellScripts/common/build_oolite.sh pkg-appimage + ShellScripts/common/build_oolite.sh test fedora: runs-on: ubuntu-latest @@ -38,10 +38,15 @@ jobs: run: | dnf -y update dnf -y install git - - name: Checkout Oolite + - name: Pre-setup ownership run: | + # This must run before the checkout action if the action tries to do git commands + mkdir -p "$GITHUB_WORKSPACE" git config --global --add safe.directory "$GITHUB_WORKSPACE" - git clone --recursive https://github.com/${{ github.repository }} . + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + submodules: true - name: Checkout dependencies run: | ShellScripts/Linux/checkout_deps.sh @@ -50,7 +55,7 @@ jobs: ShellScripts/Linux/install_deps_root.sh - name: Build Oolite run: | - ShellScripts/common/build_oolite.sh pkg-appimage + ShellScripts/common/build_oolite.sh test arch: runs-on: ubuntu-latest @@ -65,10 +70,15 @@ jobs: run: | pacman -Syu --noconfirm pacman -S --noconfirm --needed git - - name: Checkout Oolite + - name: Pre-setup ownership run: | + # This must run before the checkout action if the action tries to do git commands + mkdir -p "$GITHUB_WORKSPACE" git config --global --add safe.directory "$GITHUB_WORKSPACE" - git clone --recursive https://github.com/${{ github.repository }} . + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + submodules: true - name: Checkout dependencies run: | ShellScripts/Linux/checkout_deps.sh @@ -77,4 +87,39 @@ jobs: ShellScripts/Linux/install_deps_root.sh - name: Build Oolite run: | - ShellScripts/common/build_oolite.sh pkg-appimage + ShellScripts/common/build_oolite.sh test + + windows: + runs-on: windows-latest + steps: + - name: Set up MSYS2 + uses: msys2/setup-msys2@v2 + with: + update: true + + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + submodules: true + + - name: Download latest release assets + uses: robinraju/release-downloader@v1 + with: + repository: OoliteProject/oolite_windeps_build + latest: true + fileName: "*" + out-file-path: build/packages + + - name: Install packages + shell: msys2 {0} + env: + msystem: UCRT64 + run: | + ShellScripts/Windows/install_deps.sh clang + + - name: Build Oolite + shell: msys2 {0} + env: + msystem: UCRT64 + run: | + ShellScripts/common/build_oolite.sh test diff --git a/DebugOXP/Debug.oxp/Scripts/oolite-debug-console.js b/DebugOXP/Debug.oxp/Scripts/oolite-debug-console.js index 6860594ba..04653b6c8 100644 --- a/DebugOXP/Debug.oxp/Scripts/oolite-debug-console.js +++ b/DebugOXP/Debug.oxp/Scripts/oolite-debug-console.js @@ -858,6 +858,16 @@ this.evaluate = function evaluate(command, PARAM) } } +this.quit = function() { + log("debugConsole.automation", "Triggering global quitGame bridge..."); + + // This calls the native function in OOJSGlobal.m, which in turn calls [Universe quitGame] + if (typeof quitGame === "function") { + quitGame(); + } else { + log("debugConsole.automation", "Error: quitGame() global function not found. Did you recompile?"); + } +}; // Identify the location of the eval() command above for the debug location formatter. this.markConsoleEntryPoint = special.markConsoleEntryPoint; diff --git a/Makefile b/Makefile index 4a00c2cd4..e5c02f74f 100755 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ help: .PHONY: release release: $(MAKE) -f GNUmakefile debug=no strip=yes lto=yes - mkdir -p build/AddOns && rm -rf build/AddOns/Basic-debug.oxp && cp -rf DebugOXP/Debug.oxp build/AddOns/Basic-debug.oxp + mkdir -p oolite.app/AddOns && rm -rf oolite.app/AddOns/Basic-debug.oxp && cp -rf DebugOXP/Debug.oxp oolite.app/AddOns/Basic-debug.oxp .PHONY: release-deployment release-deployment: @@ -42,18 +42,21 @@ release-deployment: .PHONY: release-snapshot release-snapshot: $(MAKE) -f GNUmakefile SNAPSHOT_BUILD=yes debug=no - mkdir -p build/AddOns && rm -rf build/AddOns/Basic-debug.oxp && cp -rf DebugOXP/Debug.oxp build/AddOns/Basic-debug.oxp + mkdir -p oolite.app/AddOns && rm -rf oolite.app/AddOns/Basic-debug.oxp && cp -rf DebugOXP/Debug.oxp oolite.app/AddOns/Basic-debug.oxp .PHONY: debug debug: $(MAKE) -f GNUmakefile debug=yes strip=no - mkdir -p build/AddOns && rm -rf build/AddOns/Basic-debug.oxp && cp -rf DebugOXP/Debug.oxp build/AddOns/Basic-debug.oxp + mkdir -p oolite.app/AddOns && rm -rf oolite.app/AddOns/Basic-debug.oxp && cp -rf DebugOXP/Debug.oxp oolite.app/AddOns/Basic-debug.oxp + +.PHONY: test +test: release-snapshot + tests/run_test.sh .PHONY: clean clean: $(MAKE) -f GNUmakefile clean $(RM) -rf oolite.app - $(RM) -rf build/AddOns .PHONY: all all: release release-deployment release-snapshot debug diff --git a/README.md b/README.md index 498d43db3..1ff5b713e 100644 --- a/README.md +++ b/README.md @@ -101,13 +101,13 @@ sudo ShellScripts/Linux/install_deps_root.sh ### Building Oolite -Next run this in your bash or MSYS2 prompt to build Oolite: +Next run this in your Bash or MSYS2 prompt to build Oolite: ```bash ShellScripts/common/build_oolite.sh release ``` -The completed build (executable and games files) can be found in the Oolite.app directory. +The completed build (executable and games files) can be found in the oolite.app directory. Subsequently, you can clean and build as follows: @@ -116,6 +116,12 @@ make -f Makefile clean make -f Makefile release -j$(nproc) ``` +You can run a test from your Bash or MSYS2 prompt as follows: + +```bash +make -f Makefile test +``` + On Linux, you will need to run this beforehand: `source /usr/local/share/GNUstep/Makefiles/GNUstep.sh` On Windows, this is set up be default in the shell: `source $MINGW_PREFIX/share/GNUstep/Makefiles/GNUstep.sh` diff --git a/ShellScripts/Linux/install_deps_root.sh b/ShellScripts/Linux/install_deps_root.sh index 5c2d16a03..4214267cf 100755 --- a/ShellScripts/Linux/install_deps_root.sh +++ b/ShellScripts/Linux/install_deps_root.sh @@ -4,6 +4,16 @@ run_script() { + local skip_wayland=false + + # Parse arguments + while [[ "$#" -gt 0 ]]; do + case $1 in + -s|--skip-wayland) skip_wayland=true; shift ;; + *) shift ;; + esac + done + # If current user ID is NOT 0 (root) if [[ $EUID -ne 0 ]]; then echo "This script requires root to install dependencies. Rerun and escalate privileges (eg. sudo ...)" @@ -30,6 +40,17 @@ run_script() { if ! install_package gnutls-dev; then return 1 fi + # Check Python + if ! python3 --version >/dev/null 2>&1; then + if ! install_package python; then + return 1 + fi + fi + if [[ $skip_wayland == false ]]; then + if ! install_package xwfb-run; then + return 1 + fi + fi if ! install_package icu-dev; then return 1 fi diff --git a/ShellScripts/Linux/install_freedesktop_fn.sh b/ShellScripts/Linux/install_freedesktop_fn.sh index 26ec37f00..54f42146d 100755 --- a/ShellScripts/Linux/install_freedesktop_fn.sh +++ b/ShellScripts/Linux/install_freedesktop_fn.sh @@ -5,7 +5,7 @@ install_freedesktop() { # $1: app folder (destination) # $2: appdata or metainfo - local err_msg="❌ Error: Failed to install " + local err_msg="❌ Error: Failed to" SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd) pushd "$SCRIPT_DIR" @@ -19,13 +19,19 @@ install_freedesktop() { APPSHR="$1/share" # Install binaries and scripts - install -D "$PROGDIR/oolite" "$APPBIN/oolite" || { echo "$err_msg oolite binary" >&2; return 1; } - install -D "$PROGDIR/run_oolite.sh" "$APPBIN/run_oolite.sh" || { echo "$err_msg run_oolite.sh" >&2; return 1; } - install -D "$PROGDIR/splash-launcher" "$APPBIN/splash-launcher" || { echo "$err_msg splash-launcher" >&2; return 1; } + install -D "$PROGDIR/oolite" "$APPBIN/oolite" || { echo "$err_msg install oolite binary" >&2; return 1; } + install -D "$PROGDIR/run_oolite.sh" "$APPBIN/run_oolite.sh" || { echo "$err_msg install run_oolite.sh" >&2; return 1; } + install -D "$PROGDIR/splash-launcher" "$APPBIN/splash-launcher" || { echo "$err_msg install splash-launcher" >&2; return 1; } # Resources copy mkdir -p "$APPBIN/Resources" - cp -rf "$PROGDIR/Resources/." "$APPBIN/Resources/" || { echo "$err_msg Copying Resources folder" >&2; return 1; } + cp -rf "$PROGDIR/Resources/." "$APPBIN/Resources/" || { echo "$err_msg copy Resources folder" >&2; return 1; } + + # AddOns copy if folder exists in oolite.app + if [ -d "$PROGDIR/AddOns" ]; then + mkdir -p "$APPBIN/AddOns" + cp -rf "$PROGDIR/AddOns/." "$APPBIN/AddOns/" || { echo "$err_msg copy AddOns folder" >&2; return 1; } + fi install -D "GNUstep.conf.template" "$APPBIN/Resources/GNUstep.conf.template" || { echo "$err_msg GNUstep template" >&2; return 1; } diff --git a/ShellScripts/Linux/install_package_fn.sh b/ShellScripts/Linux/install_package_fn.sh index e57b2fb71..765a8dc74 100755 --- a/ShellScripts/Linux/install_package_fn.sh +++ b/ShellScripts/Linux/install_package_fn.sh @@ -37,6 +37,15 @@ install_package() { arch) PKG_NAME="gnutls" ;; esac ;; + "python") + case "$CURRENT_DISTRO" in + debian) PKG_NAME="python3-pip" ;; + redhat) PKG_NAME="python3-pip" ;; + arch) PKG_NAME="python-pip" ;; + esac ;; + + "xwfb-run") PKG_NAME="xwayland-run weston" ;; + "icu-dev") case "$CURRENT_DISTRO" in debian) PKG_NAME="libicu-dev" ;; diff --git a/ShellScripts/Windows/install_deps.sh b/ShellScripts/Windows/install_deps.sh index c4139adf9..8c5d09285 100755 --- a/ShellScripts/Windows/install_deps.sh +++ b/ShellScripts/Windows/install_deps.sh @@ -39,6 +39,8 @@ run_script() { pacman -S pactoys --noconfirm pacboy -S binutils --noconfirm pacboy -S uutils-coreutils --noconfirm + pacboy -S python-pip --noconfirm + pacboy -S mesa --noconfirm source ../common/checkout_submodules_fn.sh checkout_submodules diff --git a/ShellScripts/common/post_build.sh b/ShellScripts/common/post_build.sh index eca068dff..ce6c05a64 100755 --- a/ShellScripts/common/post_build.sh +++ b/ShellScripts/common/post_build.sh @@ -44,7 +44,7 @@ run_script() { for resdir in "${RESOURCE_DIRS[@]}"; do cp -rfu "$resdir" "$PROGDIR/Resources" done - + cp -fu Resources/README.TXT "$PROGDIR/Resources" cp -fu Resources/InfoPlist.strings "$PROGDIR/Resources" cp -fu src/Cocoa/Info-Oolite.plist "$PROGDIR/Resources/Info-gnustep.plist" diff --git a/installers/appimage/create_appimage.sh b/installers/appimage/create_appimage.sh index 3bae45325..5c1f9a594 100755 --- a/installers/appimage/create_appimage.sh +++ b/installers/appimage/create_appimage.sh @@ -22,11 +22,6 @@ run_script() { return 1 fi - if (( $# == 1 )); then - echo "Including Basic-debug.oxp" - cp -rf AddOns "$APPBIN" - fi - LINUXDEPLOY_BIN="./linuxdeploy" if [ ! -x "$LINUXDEPLOY_BIN" ]; then echo "📥 linuxdeploy not found or not executable. Downloading..." diff --git a/installers/win32/OOlite.nsi b/installers/win32/OOlite.nsi index 8bd1e6394..1da430bf0 100644 --- a/installers/win32/OOlite.nsi +++ b/installers/win32/OOlite.nsi @@ -133,7 +133,7 @@ SectionEnd Section "Basic-debug.OXP" ooDebugOXP ; Do not use any of the Debug OXP files when we are building Deployment SetOutPath $INSTDIR -File /r "..\..\build\AddOns" +File /r "${DST}\AddOns" SectionEnd ; Below are the descriptions of the two component sections @@ -251,7 +251,7 @@ File "Privacy.pdf" ${If} ${ADDCHANGELOG} == "1" File "..\..\Doc\CHANGELOG.TXT" ${EndIf} -File /r /x .git /x .svn /x *~ "${DST}" +File /r /x AddOns /x .git /x .svn /x *~ "${DST}" ; Generate version info FileOpen $9 release.txt w ;Opens a Empty File and fills it diff --git a/src/Core/ResourceManager.m b/src/Core/ResourceManager.m index 0c80ac6b1..3d70528f2 100644 --- a/src/Core/ResourceManager.m +++ b/src/Core/ResourceManager.m @@ -190,9 +190,7 @@ + (NSArray *)userRootPaths stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"AddOns"], #endif -#if !OOLITE_WINDOWS @"AddOns", -#endif [[OOOXZManager sharedManager] extractAddOnsPath], nil]; sUserRootPaths = [[[[OOOXZManager sharedManager] additionalAddOnsPaths] arrayByAddingObjectsFromArray:defaultAddOnsPaths] retain]; diff --git a/src/Core/Scripting/OOJSGlobal.m b/src/Core/Scripting/OOJSGlobal.m index 6f9ceece8..875cd19c5 100644 --- a/src/Core/Scripting/OOJSGlobal.m +++ b/src/Core/Scripting/OOJSGlobal.m @@ -74,6 +74,7 @@ - (void)sendMonitorLogMessage:(NSString *)message static JSBool GlobalSetScreenBackgroundForKey(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalAutoAIForRole(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalPauseGame(JSContext *context, uintN argc, jsval *vp); +static JSBool GlobalQuitGame(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalGetGuiColorSettingForKey(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalSetGuiColorSettingForKey(JSContext *context, uintN argc, jsval *vp); static JSBool GlobalSetExtraGuiScreenKeys(JSContext *context, uintN argc, jsval *vp); @@ -151,6 +152,7 @@ - (void)sendMonitorLogMessage:(NSString *)message #ifndef NDEBUG { "takeSnapShot", GlobalTakeSnapShot, 1 }, + { "quitGame", GlobalQuitGame, 0 }, #endif { "pauseGame", GlobalPauseGame, 0 }, { 0 } @@ -853,3 +855,17 @@ static JSBool GlobalPauseGame(JSContext *context, uintN argc, jsval *vp) OOJS_NATIVE_EXIT } + +// quitGame() : Boolean +static JSBool GlobalQuitGame(JSContext *context, uintN argc, jsval *vp) +{ + OOJS_NATIVE_ENTER(context) + + OOLog(@"script.debug.quit", @"Quit requested via JavaScript global.quitGame()"); + + [UNIVERSE quitGame]; + + OOJS_RETURN_BOOL(YES); + + OOJS_NATIVE_EXIT +} \ No newline at end of file diff --git a/src/Core/Universe.h b/src/Core/Universe.h index cb3eca1d0..11d70ad3a 100644 --- a/src/Core/Universe.h +++ b/src/Core/Universe.h @@ -414,6 +414,7 @@ enum #endif - (void) pauseGame; +- (void) quitGame; - (void) carryPlayerOn:(StationEntity*)carrier inWormhole:(WormholeEntity*)wormhole; - (void) setUpUniverseFromStation; diff --git a/src/Core/Universe.m b/src/Core/Universe.m index 8c3afa3e1..3ac6b10ec 100644 --- a/src/Core/Universe.m +++ b/src/Core/Universe.m @@ -1050,6 +1050,11 @@ - (void) pauseGame [[self gameController] setGamePaused:YES]; } +- (void) quitGame +{ + OOLog(@"universe.quit", @"Quit command received by Universe."); + [[self gameController] exitAppWithContext:@"Universe Request"]; +} - (void) carryPlayerOn:(StationEntity*)carrier inWormhole:(WormholeEntity*)wormhole { diff --git a/tests/launch_snapshot.py b/tests/launch_snapshot.py new file mode 100644 index 000000000..3a23a84ec --- /dev/null +++ b/tests/launch_snapshot.py @@ -0,0 +1,236 @@ +import argparse +import socket +import subprocess +import time +import os +import select +import sys +import struct +import plistlib +import tempfile +import shutil + +# --- PROTOCOL CONSTANTS --- +# See https://github.com/OoliteProject/oolite-debug-console/blob/master/ooliteConsoleServer/_protocol.py +# Using the exact string definitions required by the Oolite Debug Protocol +REQUEST_CONNECTION = "Request Connection" +APPROVE_CONNECTION = "Approve Connection" +PERFORM_COMMAND = "Perform Command" +PACKET_TYPE_KEY = "packet type" +MESSAGE_KEY = "message" +CONSOLE_IDENTITY_KEY = "console identity" +OOLITE_VERSION_KEY = "Oolite version" + +# --- CONFIGURATION --- +IS_WINDOWS = sys.platform == "win32" or (os.name == "nt") +PORT = 8563 +HOST = "127.0.0.1" +MIN_FILE_SIZE_KB = 100 # Threshold for a valid render + + +def send_plist_packet(sock, packet): + """ + Implements the framing protocol from + https://github.com/OoliteProject/oolite-debug-console/blob/master/ooliteConsoleServer/PropertyListPacketProtocol.py: + 1. Encodes the dictionary as an XML Property List. + 2. Calculates a 32-bit big-endian integer for the length. + 3. Prepends this 4-byte header to the data. + """ + try: + # Matches writePlistToString logic for Python 3 + data = plistlib.dumps(packet, fmt=plistlib.FMT_XML) + length = len(data) + + # Header is 4 bytes, network-endian (Big-Endian) + header = struct.pack(">I", length) + + sock.sendall(header + data) + return True + except Exception as e: + print(f"[!] Failed to encode/send packet: {e}") + return False + + +def receive_plist_packet(sock): + """ + Implements the receiving state machine from: + https://github.com/OoliteProject/oolite-debug-console/blob/master/ooliteConsoleServer/OoliteDebugConsoleProtocol.py + 1. Reads the 4-byte header to determine expected length. + 2. Reads the specific number of bytes for the XML payload. + """ + try: + header = sock.recv(4) + if len(header) < 4: + return None + + length = struct.unpack(">I", header)[0] + data = b"" + while len(data) < length: + chunk = sock.recv(length - len(data)) + if not chunk: + break + data += chunk + + return plistlib.loads(data) + except Exception as e: + print(f"[!] Error receiving packet: {e}") + return None + + +def run_test(bin_name, snapshots_dir): + # Setup TCP Server + server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_sock.bind((HOST, PORT)) + server_sock.listen(1) + server_sock.setblocking(False) + + print(f"[*] Console server listening on {PORT}") + + # Environment configuration for headless snapshotting + env = os.environ.copy() + env["LIBGL_ALWAYS_SOFTWARE"] = "1" + env["GALLIUM_DRIVER"] = "llvmpipe" + env["SDL_AUDIODRIVER"] = "dummy" + env["ALSOFT_DRIVERS"] = "null" + env["OO_SNAPSHOTSDIR"] = snapshots_dir + + # Launch Oolite + if IS_WINDOWS: + cmd = [f"./{bin_name}", "--no-splash"] + else: + cmd = ["xwfb-run", "--", f"./{bin_name}", "--no-splash"] + + print(f"[*] Executing: {' '.join(cmd)}") + proc = subprocess.Popen( + cmd, + env=env, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + + conn = None + try: + # 1. Wait for Oolite to initiate connection + timeout = time.time() + 20 + while time.time() < timeout: + readable, _, _ = select.select([server_sock], [], [], 1) + if readable: + conn, addr = server_sock.accept() + conn.setblocking(True) + print(f"[+] Oolite connected from {addr}") + break + + if not conn: + print("[!] Failure: Oolite failed to connect.") + return False + + # 2. THE HANDSHAKE from: + # https://github.com/OoliteProject/oolite-debug-console/blob/master/ooliteConsoleServer/OoliteDebugConsoleProtocol.py + # Wait for 'Request Connection' from Oolite + pkt = receive_plist_packet(conn) + + if pkt and pkt.get(PACKET_TYPE_KEY) == REQUEST_CONNECTION: + print( + f"[+] Handshake started. Oolite version: {pkt.get(OOLITE_VERSION_KEY)}" + ) + + # Respond with 'Approve Connection' and identity + approval = { + PACKET_TYPE_KEY: APPROVE_CONNECTION, + CONSOLE_IDENTITY_KEY: "OoliteAutomationTester", + } + send_plist_packet(conn, approval) + print("[*] Connection Approved.") + else: + print("[!] Handshake failed: Expected 'Request Connection'.") + return False + + # Allow time for engine state transition + time.sleep(5) + + # 3. THE COMMAND (Using Perform Command packet type) + # Combines snapshot and quit into one execution string + print("[*] Requesting snapshot and quit...") + cmd_packet = { + PACKET_TYPE_KEY: PERFORM_COMMAND, + MESSAGE_KEY: "takeSnapShot(); quit();", + } + send_plist_packet(conn, cmd_packet) + + # 4. WAIT FOR PROCESS EXIT + print("[*] Waiting for process to exit...") + try: + proc.wait(timeout=15) + + # 5. VERIFY OUTPUT + snaps = [f for f in os.listdir(snapshots_dir) if f.endswith(".png")] + if snaps: + snap_path = os.path.join(snapshots_dir, snaps[0]) + file_size_kb = os.path.getsize(snap_path) // 1024 + print(f"[*] Captured {snap_path} ({file_size_kb} KB)") + + if file_size_kb < MIN_FILE_SIZE_KB: + print( + f"[!] Failure: Snapshot is too small ({file_size_kb} KB). Likely a black frame." + ) + return False + + print("[+] Success: Snapshot passed quality check.") + return True + else: + print("[!] Error: Oolite exited but no snapshot was found.") + return False + + except subprocess.TimeoutExpired: + print("[!] Error: Oolite ignored the shutdown command.") + return False + + finally: + if conn: + conn.close() + server_sock.close() + if proc.poll() is None: + proc.kill() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Oolite Automation Tester") + parser.add_argument( + "--path", default="./", help="Path to the Oolite directory (default: ./)" + ) + args = parser.parse_args() + + # Determine binary name and original path + bin_name = "oolite.exe" if IS_WINDOWS else "oolite" + original_cwd = os.getcwd() + target_dir = os.path.abspath(args.path) + + # If the user pointed to a file, get the containing directory + if os.path.isfile(target_dir): + bin_name = os.path.basename(target_dir) + target_dir = os.path.dirname(target_dir) + + # Use a temporary directory for snapshots relative to the current CWD + # before we jump into the Oolite folder. + temp_snap_dir = tempfile.mkdtemp(prefix="oolite_snapshot_") + + success = False + try: + print(f"[*] Moving to {target_dir}") + os.chdir(target_dir) + + # Run the test from within the Oolite directory + success = run_test(bin_name, temp_snap_dir) + + except Exception as e: + print(f"[!] Critical Error: {e}") + finally: + # ALWAYS return to the original path + os.chdir(original_cwd) + print(f"[*] Returned to {original_cwd}") + + # Cleanup snapshots + shutil.rmtree(temp_snap_dir) + + sys.exit(0 if success else 1) diff --git a/tests/run_test.sh b/tests/run_test.sh new file mode 100755 index 000000000..be4473add --- /dev/null +++ b/tests/run_test.sh @@ -0,0 +1,43 @@ +#!/bin/bash +# Processes Oolite data files after compilation + +run_script() { + if python3 --version >/dev/null 2>&1; then + local PYTHON_CMD="python3" + elif python --version >/dev/null 2>&1; then + local PYTHON_CMD="python" + else + echo "❌ Python executable not found!" >&2 + return 1 + fi + + SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd) + pushd "$SCRIPT_DIR" + + if [[ -n "$MSYSTEM" ]]; then + MESA_DLL="${MSYSTEM_PREFIX}/bin/opengl32.dll" + TARGET_DIR="../oolite.app" + + if [[ -f "$MESA_DLL" ]]; then + echo "📦 Found $MSYSTEM Mesa driver at $MESA_DLL" + cp "$MESA_DLL" "$TARGET_DIR/" + fi + fi + + if ! $PYTHON_CMD launch_snapshot.py --path=../oolite.app; then + echo "❌ Oolite test failed!" >&2 + return 1 + fi + + echo "✅ Oolite test completed successfully" + popd +} + +run_script "$@" +status=$? + +# Exit only if not sourced +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + exit $status +fi +