diff options
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/test.lua | 8 | ||||
| -rwxr-xr-x | scripts/test_linux/block-clone-test.sh (renamed from scripts/test_scripts/block-clone-test.sh) | 2 | ||||
| -rwxr-xr-x | scripts/test_linux/service-test.sh | 380 | ||||
| -rwxr-xr-x | scripts/test_mac/block-clone-test.sh (renamed from scripts/test_scripts/block-clone-test-mac.sh) | 2 | ||||
| -rwxr-xr-x | scripts/test_mac/service-test.sh | 368 | ||||
| -rw-r--r-- | scripts/test_windows/block-clone-test.ps1 (renamed from scripts/test_scripts/block-clone-test-windows.ps1) | 2 | ||||
| -rw-r--r-- | scripts/test_windows/service-test.ps1 | 353 |
7 files changed, 1110 insertions, 5 deletions
diff --git a/scripts/test.lua b/scripts/test.lua index 3c18225fb..52495342a 100644 --- a/scripts/test.lua +++ b/scripts/test.lua @@ -231,8 +231,12 @@ function main() if use_noskip then cmd = string.format("%s --no-skip", cmd) end - if use_verbose and name == "integration" then - cmd = string.format("%s --verbose", cmd) + if name == "integration" then + cmd = string.format("%s --kill-stale-processes", cmd) + + if use_verbose then + cmd = string.format("%s --verbose", cmd) + end end for _, arg in ipairs(extra_args) do cmd = string.format("%s %s", cmd, arg) diff --git a/scripts/test_scripts/block-clone-test.sh b/scripts/test_linux/block-clone-test.sh index 7c6bf5605..0a74283f2 100755 --- a/scripts/test_scripts/block-clone-test.sh +++ b/scripts/test_linux/block-clone-test.sh @@ -4,7 +4,7 @@ # Requires: root/sudo, btrfs-progs (mkfs.btrfs), xfsprogs (mkfs.xfs) # # Usage: -# sudo ./scripts/test_scripts/block-clone-test.sh [path-to-zencore-test] +# sudo ./scripts/test_linux/block-clone-test.sh [path-to-zencore-test] # # If no path is given, defaults to build/linux/x86_64/debug/zencore-test # relative to the repository root. diff --git a/scripts/test_linux/service-test.sh b/scripts/test_linux/service-test.sh new file mode 100755 index 000000000..f91339e1b --- /dev/null +++ b/scripts/test_linux/service-test.sh @@ -0,0 +1,380 @@ +#!/usr/bin/env bash +# Test zen service command lifecycle on Linux (systemd). +# +# Requires: root/sudo, systemd +# +# Usage: +# sudo ./scripts/test_linux/service-test.sh [path-to-zen] +# +# If no path is given, defaults to build/linux/x86_64/debug/zen +# relative to the repository root. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +ZEN_BINARY="${1:-$REPO_ROOT/build/linux/x86_64/debug/zen}" +ZENSERVER_BINARY="$(dirname "$ZEN_BINARY")/zenserver" +SERVICE_NAME="ZenServerTest-$$" +UNIT_NAME="com.epicgames.unreal.${SERVICE_NAME}" +UNIT_FILE="/etc/systemd/system/${UNIT_NAME}.service" + +PASSED=0 +FAILED=0 +TESTS_RUN=0 + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +cleanup() { + local exit_code=$? + set +e + + echo "" + echo "--- Cleanup ---" + + # Stop service if running + if systemctl is-active --quiet "$UNIT_NAME" 2>/dev/null; then + echo "Stopping test service..." + systemctl stop "$UNIT_NAME" 2>/dev/null + fi + + # Disable and remove unit file + if [ -f "$UNIT_FILE" ]; then + echo "Removing test unit file..." + systemctl disable "$UNIT_NAME" 2>/dev/null || true + rm -f "$UNIT_FILE" + systemctl daemon-reload 2>/dev/null || true + fi + + echo "" + echo "==============================" + printf " Tests run: %d\n" "$TESTS_RUN" + printf " ${GREEN}Passed: %d${NC}\n" "$PASSED" + if [ "$FAILED" -gt 0 ]; then + printf " ${RED}Failed: %d${NC}\n" "$FAILED" + else + printf " Failed: %d\n" "$FAILED" + fi + echo "==============================" + + if [ "$FAILED" -gt 0 ]; then + exit 1 + fi + exit "$exit_code" +} + +trap cleanup EXIT + +pass() { + TESTS_RUN=$((TESTS_RUN + 1)) + PASSED=$((PASSED + 1)) + printf " ${GREEN}PASS${NC}: %s\n" "$1" +} + +fail() { + TESTS_RUN=$((TESTS_RUN + 1)) + FAILED=$((FAILED + 1)) + printf " ${RED}FAIL${NC}: %s\n" "$1" + if [ -n "${2:-}" ]; then + printf " %s\n" "$2" + fi +} + +# ── Preflight checks ────────────────────────────────────────────── + +if [ "$(id -u)" -ne 0 ]; then + echo "Error: this test must be run as root (sudo)." + exit 1 +fi + +if ! command -v systemctl &>/dev/null; then + echo "Error: systemctl not found — this test requires systemd." + exit 1 +fi + +if [ ! -x "$ZEN_BINARY" ]; then + echo "Error: zen binary not found at '$ZEN_BINARY'" + echo "Build with: xmake config -m debug && xmake build zen" + exit 1 +fi + +if [ ! -x "$ZENSERVER_BINARY" ]; then + echo "Error: zenserver binary not found at '$ZENSERVER_BINARY'" + echo "Build with: xmake config -m debug && xmake build zenserver" + exit 1 +fi + +echo "zen binary: $ZEN_BINARY" +echo "zenserver binary: $ZENSERVER_BINARY" +echo "service name: $SERVICE_NAME" +echo "unit name: $UNIT_NAME" +echo "" + +# Determine which user to run the service as (the user who invoked sudo) +SERVICE_USER="${SUDO_USER:-$(whoami)}" + +# ── Test: status before install (should fail) ───────────────────── + +echo "--- Test: status before install ---" + +OUTPUT=$("$ZEN_BINARY" service status "$SERVICE_NAME" 2>&1 || true) +if echo "$OUTPUT" | grep -qi "not installed"; then + pass "status reports 'not installed' for non-existent service" +else + fail "status should report 'not installed'" "got: $OUTPUT" +fi + +# ── Test: install ───────────────────────────────────────────────── + +echo "--- Test: install ---" + +OUTPUT=$("$ZEN_BINARY" service install "$ZENSERVER_BINARY" "$SERVICE_NAME" --user "$SERVICE_USER" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "install exits with code 0" +else + fail "install exits with code 0" "got exit code: $EXIT_CODE, output: $OUTPUT" +fi + +if [ -f "$UNIT_FILE" ]; then + pass "unit file created at $UNIT_FILE" +else + fail "unit file created at $UNIT_FILE" +fi + +# Verify unit file contents +if [ -f "$UNIT_FILE" ]; then + if grep -q "ExecStart=.*zenserver" "$UNIT_FILE"; then + pass "unit file contains ExecStart with zenserver" + else + fail "unit file contains ExecStart with zenserver" "$(cat "$UNIT_FILE")" + fi + + if grep -q "User=$SERVICE_USER" "$UNIT_FILE"; then + pass "unit file contains correct User=$SERVICE_USER" + else + fail "unit file contains correct User=$SERVICE_USER" "$(grep User "$UNIT_FILE")" + fi + + if grep -q "Type=notify" "$UNIT_FILE"; then + pass "unit file uses Type=notify" + else + fail "unit file uses Type=notify" + fi + + if grep -q "WantedBy=multi-user.target" "$UNIT_FILE"; then + pass "unit file has WantedBy=multi-user.target" + else + fail "unit file has WantedBy=multi-user.target" + fi + + # Verify the service is enabled + if systemctl is-enabled --quiet "$UNIT_NAME" 2>/dev/null; then + pass "service is enabled after install" + else + fail "service is enabled after install" + fi +fi + +# ── Test: install again (already installed, no --full) ──────────── + +echo "--- Test: install again (idempotent) ---" + +OUTPUT=$("$ZEN_BINARY" service install "$ZENSERVER_BINARY" "$SERVICE_NAME" --user "$SERVICE_USER" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "re-install exits with code 0" +else + fail "re-install exits with code 0" "got exit code: $EXIT_CODE" +fi + +if echo "$OUTPUT" | grep -qi "already installed"; then + pass "re-install reports service already installed" +else + fail "re-install reports service already installed" "got: $OUTPUT" +fi + +# ── Test: status after install (not yet started) ────────────────── + +echo "--- Test: status after install (stopped) ---" + +OUTPUT=$("$ZEN_BINARY" service status "$SERVICE_NAME" 2>&1 || true) +# The status command throws if not running, so we expect an error message +if echo "$OUTPUT" | grep -qi "not running"; then + pass "status reports 'not running' for stopped service" +else + fail "status reports 'not running' for stopped service" "got: $OUTPUT" +fi + +# ── Test: start ─────────────────────────────────────────────────── + +echo "--- Test: start ---" + +OUTPUT=$("$ZEN_BINARY" service start "$SERVICE_NAME" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "start exits with code 0" +else + # The TODO comments in the code indicate start may not work perfectly + fail "start exits with code 0" "got exit code: $EXIT_CODE, output: $OUTPUT" +fi + +# Give the service a moment to start +sleep 1 + +if systemctl is-active --quiet "$UNIT_NAME" 2>/dev/null; then + pass "service is active after start" + + # ── Test: status while running ──────────────────────────────── + + echo "--- Test: status (running) ---" + + OUTPUT=$("$ZEN_BINARY" service status "$SERVICE_NAME" 2>&1 || true) + if echo "$OUTPUT" | grep -qi "Running"; then + pass "status reports 'Running'" + else + fail "status reports 'Running'" "got: $OUTPUT" + fi + + if echo "$OUTPUT" | grep -qi "zenserver"; then + pass "status shows executable path" + else + fail "status shows executable path" "got: $OUTPUT" + fi + + # ── Test: start again (already running) ─────────────────────── + + echo "--- Test: start again (already running) ---" + + OUTPUT=$("$ZEN_BINARY" service start "$SERVICE_NAME" 2>&1) + if echo "$OUTPUT" | grep -qi "already running"; then + pass "start reports service already running" + else + fail "start reports service already running" "got: $OUTPUT" + fi + + # ── Test: stop ──────────────────────────────────────────────── + + echo "--- Test: stop ---" + + OUTPUT=$("$ZEN_BINARY" service stop "$SERVICE_NAME" 2>&1) + EXIT_CODE=$? + + if [ "$EXIT_CODE" -eq 0 ]; then + pass "stop exits with code 0" + else + fail "stop exits with code 0" "got exit code: $EXIT_CODE, output: $OUTPUT" + fi + + sleep 1 + + if ! systemctl is-active --quiet "$UNIT_NAME" 2>/dev/null; then + pass "service is inactive after stop" + else + fail "service is inactive after stop" + fi +else + fail "service is active after start" "(skipping start-dependent tests)" +fi + +# ── Test: stop when already stopped ─────────────────────────────── + +echo "--- Test: stop when already stopped ---" + +# Make sure it's stopped first +systemctl stop "$UNIT_NAME" 2>/dev/null || true +sleep 1 + +OUTPUT=$("$ZEN_BINARY" service stop "$SERVICE_NAME" 2>&1 || true) +if echo "$OUTPUT" | grep -qi "not running"; then + pass "stop reports 'not running' when already stopped" +else + fail "stop reports 'not running' when already stopped" "got: $OUTPUT" +fi + +# ── Test: uninstall while running (should fail) ─────────────────── + +echo "--- Test: uninstall while running (should fail) ---" + +# Start the service so we can test uninstall-while-running +"$ZEN_BINARY" service start "$SERVICE_NAME" 2>&1 || true +sleep 1 + +if systemctl is-active --quiet "$UNIT_NAME" 2>/dev/null; then + OUTPUT=$("$ZEN_BINARY" service uninstall "$SERVICE_NAME" 2>&1 || true) + EXIT_CODE=$? + + if [ "$EXIT_CODE" -ne 0 ] || echo "$OUTPUT" | grep -qi "running.*stop"; then + pass "uninstall refuses while service is running" + else + fail "uninstall refuses while service is running" "got: exit=$EXIT_CODE, output: $OUTPUT" + fi + + # Stop it for the real uninstall test + "$ZEN_BINARY" service stop "$SERVICE_NAME" 2>&1 || true + systemctl stop "$UNIT_NAME" 2>/dev/null || true + sleep 1 +else + echo " (skipped: could not start service for this test)" +fi + +# ── Test: uninstall ─────────────────────────────────────────────── + +echo "--- Test: uninstall ---" + +# Ensure stopped +systemctl stop "$UNIT_NAME" 2>/dev/null || true +sleep 1 + +OUTPUT=$("$ZEN_BINARY" service uninstall "$SERVICE_NAME" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "uninstall exits with code 0" +else + fail "uninstall exits with code 0" "got exit code: $EXIT_CODE, output: $OUTPUT" +fi + +if [ ! -f "$UNIT_FILE" ]; then + pass "unit file removed after uninstall" +else + fail "unit file removed after uninstall" +fi + +# ── Test: status after uninstall ────────────────────────────────── + +echo "--- Test: status after uninstall ---" + +OUTPUT=$("$ZEN_BINARY" service status "$SERVICE_NAME" 2>&1 || true) +if echo "$OUTPUT" | grep -qi "not installed"; then + pass "status reports 'not installed' after uninstall" +else + fail "status reports 'not installed' after uninstall" "got: $OUTPUT" +fi + +# ── Test: uninstall when not installed (idempotent) ─────────────── + +echo "--- Test: uninstall when not installed ---" + +OUTPUT=$("$ZEN_BINARY" service uninstall "$SERVICE_NAME" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "uninstall of non-existent service exits with code 0" +else + fail "uninstall of non-existent service exits with code 0" "got exit code: $EXIT_CODE" +fi + +if echo "$OUTPUT" | grep -qi "not installed"; then + pass "uninstall reports service not installed" +else + fail "uninstall reports service not installed" "got: $OUTPUT" +fi diff --git a/scripts/test_scripts/block-clone-test-mac.sh b/scripts/test_mac/block-clone-test.sh index a3d3ca4d3..1a9575dcf 100755 --- a/scripts/test_scripts/block-clone-test-mac.sh +++ b/scripts/test_mac/block-clone-test.sh @@ -5,7 +5,7 @@ # clonefile(), so no special setup is needed — just run the tests. # # Usage: -# ./scripts/test_scripts/block-clone-test-mac.sh [path-to-zencore-test] +# ./scripts/test_mac/block-clone-test.sh [path-to-zencore-test] # # If no path is given, defaults to build/macosx/<arch>/debug/zencore-test # relative to the repository root. diff --git a/scripts/test_mac/service-test.sh b/scripts/test_mac/service-test.sh new file mode 100755 index 000000000..da4a966e7 --- /dev/null +++ b/scripts/test_mac/service-test.sh @@ -0,0 +1,368 @@ +#!/usr/bin/env bash +# Test zen service command lifecycle on macOS (launchd). +# +# Does NOT require sudo — macOS service commands run as the current user +# via ~/Library/LaunchAgents. +# +# Usage: +# ./scripts/test_mac/service-test.sh [path-to-zen] +# +# If no path is given, defaults to build/macosx/<arch>/debug/zen +# relative to the repository root. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +ARCH="$(uname -m)" +ZEN_BINARY="${1:-$REPO_ROOT/build/macosx/$ARCH/debug/zen}" +ZENSERVER_BINARY="$(dirname "$ZEN_BINARY")/zenserver" +SERVICE_NAME="ZenServerTest-$$" +DAEMON_NAME="com.epicgames.unreal.${SERVICE_NAME}" +PLIST_FILE="$HOME/Library/LaunchAgents/${DAEMON_NAME}.plist" + +PASSED=0 +FAILED=0 +TESTS_RUN=0 + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +cleanup() { + local exit_code=$? + set +e + + echo "" + echo "--- Cleanup ---" + + # Bootout (stop) the agent if loaded + if launchctl list "$DAEMON_NAME" &>/dev/null; then + echo "Stopping test service..." + launchctl bootout "gui/$(id -u)" "$PLIST_FILE" 2>/dev/null || true + fi + + # Remove plist file + if [ -f "$PLIST_FILE" ]; then + echo "Removing test plist file..." + rm -f "$PLIST_FILE" + fi + + echo "" + echo "==============================" + printf " Tests run: %d\n" "$TESTS_RUN" + printf " ${GREEN}Passed: %d${NC}\n" "$PASSED" + if [ "$FAILED" -gt 0 ]; then + printf " ${RED}Failed: %d${NC}\n" "$FAILED" + else + printf " Failed: %d\n" "$FAILED" + fi + echo "==============================" + + if [ "$FAILED" -gt 0 ]; then + exit 1 + fi + exit "$exit_code" +} + +trap cleanup EXIT + +pass() { + TESTS_RUN=$((TESTS_RUN + 1)) + PASSED=$((PASSED + 1)) + printf " ${GREEN}PASS${NC}: %s\n" "$1" +} + +fail() { + TESTS_RUN=$((TESTS_RUN + 1)) + FAILED=$((FAILED + 1)) + printf " ${RED}FAIL${NC}: %s\n" "$1" + if [ -n "${2:-}" ]; then + printf " %s\n" "$2" + fi +} + +# ── Preflight checks ────────────────────────────────────────────── + +if [ "$(uname)" != "Darwin" ]; then + echo "Error: this test is for macOS only." + exit 1 +fi + +if ! command -v launchctl &>/dev/null; then + echo "Error: launchctl not found." + exit 1 +fi + +if [ ! -x "$ZEN_BINARY" ]; then + echo "Error: zen binary not found at '$ZEN_BINARY'" + echo "Build with: xmake config -m debug && xmake build zen" + exit 1 +fi + +if [ ! -x "$ZENSERVER_BINARY" ]; then + echo "Error: zenserver binary not found at '$ZENSERVER_BINARY'" + echo "Build with: xmake config -m debug && xmake build zenserver" + exit 1 +fi + +echo "zen binary: $ZEN_BINARY" +echo "zenserver binary: $ZENSERVER_BINARY" +echo "service name: $SERVICE_NAME" +echo "daemon name: $DAEMON_NAME" +echo "plist path: $PLIST_FILE" +echo "" + +# ── Test: status before install (should fail) ───────────────────── + +echo "--- Test: status before install ---" + +OUTPUT=$("$ZEN_BINARY" service status "$SERVICE_NAME" 2>&1 || true) +if echo "$OUTPUT" | grep -qi "not installed"; then + pass "status reports 'not installed' for non-existent service" +else + fail "status should report 'not installed'" "got: $OUTPUT" +fi + +# ── Test: install ───────────────────────────────────────────────── + +echo "--- Test: install ---" + +OUTPUT=$("$ZEN_BINARY" service install "$ZENSERVER_BINARY" "$SERVICE_NAME" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "install exits with code 0" +else + fail "install exits with code 0" "got exit code: $EXIT_CODE, output: $OUTPUT" +fi + +if [ -f "$PLIST_FILE" ]; then + pass "plist file created at $PLIST_FILE" +else + fail "plist file created at $PLIST_FILE" +fi + +# Verify plist contents +if [ -f "$PLIST_FILE" ]; then + if grep -q "<string>$ZENSERVER_BINARY</string>" "$PLIST_FILE"; then + pass "plist contains zenserver executable path" + else + fail "plist contains zenserver executable path" "$(cat "$PLIST_FILE")" + fi + + if grep -q "<key>Label</key>" "$PLIST_FILE"; then + pass "plist has Label key" + else + fail "plist has Label key" + fi + + if grep -q "$DAEMON_NAME" "$PLIST_FILE"; then + pass "plist contains correct daemon name" + else + fail "plist contains correct daemon name" + fi + + if grep -q "<key>ProgramArguments</key>" "$PLIST_FILE"; then + pass "plist has ProgramArguments" + else + fail "plist has ProgramArguments" + fi +fi + +# ── Test: install again (already installed, no --full) ──────────── + +echo "--- Test: install again (idempotent) ---" + +OUTPUT=$("$ZEN_BINARY" service install "$ZENSERVER_BINARY" "$SERVICE_NAME" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "re-install exits with code 0" +else + fail "re-install exits with code 0" "got exit code: $EXIT_CODE" +fi + +if echo "$OUTPUT" | grep -qi "already installed"; then + pass "re-install reports service already installed" +else + fail "re-install reports service already installed" "got: $OUTPUT" +fi + +# ── Test: status after install (not yet started) ────────────────── + +echo "--- Test: status after install (stopped) ---" + +OUTPUT=$("$ZEN_BINARY" service status "$SERVICE_NAME" 2>&1 || true) +if echo "$OUTPUT" | grep -qi "not running"; then + pass "status reports 'not running' for stopped service" +else + fail "status reports 'not running' for stopped service" "got: $OUTPUT" +fi + +# ── Test: start ─────────────────────────────────────────────────── + +echo "--- Test: start ---" + +OUTPUT=$("$ZEN_BINARY" service start "$SERVICE_NAME" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "start exits with code 0" +else + fail "start exits with code 0" "got exit code: $EXIT_CODE, output: $OUTPUT" +fi + +# Give the service a moment to start +sleep 2 + +if launchctl list "$DAEMON_NAME" &>/dev/null; then + pass "service is loaded after start" + + # ── Test: status while running ──────────────────────────────── + + echo "--- Test: status (running) ---" + + OUTPUT=$("$ZEN_BINARY" service status "$SERVICE_NAME" 2>&1 || true) + if echo "$OUTPUT" | grep -qi "Running"; then + pass "status reports 'Running'" + else + fail "status reports 'Running'" "got: $OUTPUT" + fi + + if echo "$OUTPUT" | grep -qi "zenserver"; then + pass "status shows executable path" + else + fail "status shows executable path" "got: $OUTPUT" + fi + + # ── Test: start again (already running) ─────────────────────── + + echo "--- Test: start again (already running) ---" + + OUTPUT=$("$ZEN_BINARY" service start "$SERVICE_NAME" 2>&1) + if echo "$OUTPUT" | grep -qi "already running"; then + pass "start reports service already running" + else + fail "start reports service already running" "got: $OUTPUT" + fi + + # ── Test: stop ──────────────────────────────────────────────── + + echo "--- Test: stop ---" + + OUTPUT=$("$ZEN_BINARY" service stop "$SERVICE_NAME" 2>&1) + EXIT_CODE=$? + + if [ "$EXIT_CODE" -eq 0 ]; then + pass "stop exits with code 0" + else + fail "stop exits with code 0" "got exit code: $EXIT_CODE, output: $OUTPUT" + fi + + sleep 1 + + if ! launchctl list "$DAEMON_NAME" &>/dev/null; then + pass "service is unloaded after stop" + else + fail "service is unloaded after stop" + fi +else + fail "service is loaded after start" "(skipping start-dependent tests)" +fi + +# ── Test: stop when already stopped ─────────────────────────────── + +echo "--- Test: stop when already stopped ---" + +# Make sure it's stopped first +launchctl bootout "gui/$(id -u)" "$PLIST_FILE" 2>/dev/null || true +sleep 1 + +OUTPUT=$("$ZEN_BINARY" service stop "$SERVICE_NAME" 2>&1 || true) +if echo "$OUTPUT" | grep -qi "not running"; then + pass "stop reports 'not running' when already stopped" +else + fail "stop reports 'not running' when already stopped" "got: $OUTPUT" +fi + +# ── Test: uninstall while running (should fail) ─────────────────── + +echo "--- Test: uninstall while running (should fail) ---" + +# Start the service so we can test uninstall-while-running +"$ZEN_BINARY" service start "$SERVICE_NAME" 2>&1 || true +sleep 2 + +if launchctl list "$DAEMON_NAME" &>/dev/null; then + OUTPUT=$("$ZEN_BINARY" service uninstall "$SERVICE_NAME" 2>&1 || true) + EXIT_CODE=$? + + if [ "$EXIT_CODE" -ne 0 ] || echo "$OUTPUT" | grep -qi "running.*stop"; then + pass "uninstall refuses while service is running" + else + fail "uninstall refuses while service is running" "got: exit=$EXIT_CODE, output: $OUTPUT" + fi + + # Stop it for the real uninstall test + "$ZEN_BINARY" service stop "$SERVICE_NAME" 2>&1 || true + launchctl bootout "gui/$(id -u)" "$PLIST_FILE" 2>/dev/null || true + sleep 1 +else + echo " (skipped: could not start service for this test)" +fi + +# ── Test: uninstall ─────────────────────────────────────────────── + +echo "--- Test: uninstall ---" + +# Ensure stopped +launchctl bootout "gui/$(id -u)" "$PLIST_FILE" 2>/dev/null || true +sleep 1 + +OUTPUT=$("$ZEN_BINARY" service uninstall "$SERVICE_NAME" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "uninstall exits with code 0" +else + fail "uninstall exits with code 0" "got exit code: $EXIT_CODE, output: $OUTPUT" +fi + +if [ ! -f "$PLIST_FILE" ]; then + pass "plist file removed after uninstall" +else + fail "plist file removed after uninstall" +fi + +# ── Test: status after uninstall ────────────────────────────────── + +echo "--- Test: status after uninstall ---" + +OUTPUT=$("$ZEN_BINARY" service status "$SERVICE_NAME" 2>&1 || true) +if echo "$OUTPUT" | grep -qi "not installed"; then + pass "status reports 'not installed' after uninstall" +else + fail "status reports 'not installed' after uninstall" "got: $OUTPUT" +fi + +# ── Test: uninstall when not installed (idempotent) ─────────────── + +echo "--- Test: uninstall when not installed ---" + +OUTPUT=$("$ZEN_BINARY" service uninstall "$SERVICE_NAME" 2>&1) +EXIT_CODE=$? + +if [ "$EXIT_CODE" -eq 0 ]; then + pass "uninstall of non-existent service exits with code 0" +else + fail "uninstall of non-existent service exits with code 0" "got exit code: $EXIT_CODE" +fi + +if echo "$OUTPUT" | grep -qi "not installed"; then + pass "uninstall reports service not installed" +else + fail "uninstall reports service not installed" "got: $OUTPUT" +fi diff --git a/scripts/test_scripts/block-clone-test-windows.ps1 b/scripts/test_windows/block-clone-test.ps1 index df24831a4..aa6ec3a39 100644 --- a/scripts/test_scripts/block-clone-test-windows.ps1 +++ b/scripts/test_windows/block-clone-test.ps1 @@ -7,7 +7,7 @@ # # Usage: # # From an elevated PowerShell prompt: -# .\scripts\test_scripts\block-clone-test-windows.ps1 [-TestBinary <path>] +# .\scripts\test_windows\block-clone-test.ps1 [-TestBinary <path>] # # If -TestBinary is not given, defaults to build\windows\x64\debug\zencore-test.exe # relative to the repository root. diff --git a/scripts/test_windows/service-test.ps1 b/scripts/test_windows/service-test.ps1 new file mode 100644 index 000000000..4c484c63f --- /dev/null +++ b/scripts/test_windows/service-test.ps1 @@ -0,0 +1,353 @@ +# Test zen service command lifecycle on Windows (SCM). +# +# Requires: Administrator privileges +# +# Usage: +# # From an elevated PowerShell prompt: +# .\scripts\test_windows\service-test.ps1 [-ZenBinary <path>] +# +# If -ZenBinary is not given, defaults to build\windows\x64\debug\zen.exe +# relative to the repository root. + +param( + [string]$ZenBinary +) + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$RepoRoot = (Resolve-Path "$ScriptDir\..\..").Path + +if (-not $ZenBinary) { + $ZenBinary = Join-Path $RepoRoot "build\windows\x64\debug\zen.exe" +} +$ZenServerBinary = Join-Path (Split-Path -Parent $ZenBinary) "zenserver.exe" +$ServiceName = "ZenServerTest-$PID" + +$Script:Passed = 0 +$Script:Failed = 0 +$Script:TestsRun = 0 + +function Pass($Message) { + $Script:TestsRun++ + $Script:Passed++ + Write-Host " PASS: $Message" -ForegroundColor Green +} + +function Fail($Message, $Detail) { + $Script:TestsRun++ + $Script:Failed++ + Write-Host " FAIL: $Message" -ForegroundColor Red + if ($Detail) { + Write-Host " $Detail" + } +} + +function Cleanup { + Write-Host "" + Write-Host "--- Cleanup ---" + + # Stop the service if running + $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue + if ($svc -and $svc.Status -eq "Running") { + Write-Host "Stopping test service..." + Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 + } + + # Delete the service if it exists + if (Get-Service -Name $ServiceName -ErrorAction SilentlyContinue) { + Write-Host "Removing test service..." + sc.exe delete $ServiceName 2>$null | Out-Null + } + + Write-Host "" + Write-Host "==============================" + Write-Host " Tests run: $Script:TestsRun" + Write-Host " Passed: $Script:Passed" -ForegroundColor Green + if ($Script:Failed -gt 0) { + Write-Host " Failed: $Script:Failed" -ForegroundColor Red + } else { + Write-Host " Failed: $Script:Failed" + } + Write-Host "==============================" +} + +# ── Preflight checks ────────────────────────────────────────────── + +$IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole( + [Security.Principal.WindowsBuiltInRole]::Administrator) + +if (-not $IsAdmin) { + Write-Host "Error: this test must be run from an elevated (Administrator) prompt." -ForegroundColor Red + exit 1 +} + +if (-not (Test-Path $ZenBinary)) { + Write-Host "Error: zen binary not found at '$ZenBinary'" -ForegroundColor Red + Write-Host "Build with: xmake config -m debug && xmake build zen" + exit 1 +} + +if (-not (Test-Path $ZenServerBinary)) { + Write-Host "Error: zenserver binary not found at '$ZenServerBinary'" -ForegroundColor Red + Write-Host "Build with: xmake config -m debug && xmake build zenserver" + exit 1 +} + +Write-Host "zen binary: $ZenBinary" +Write-Host "zenserver binary: $ZenServerBinary" +Write-Host "service name: $ServiceName" +Write-Host "" + +try { + +# ── Test: status before install (should fail) ───────────────────── + +Write-Host "--- Test: status before install ---" + +$Output = & $ZenBinary service status $ServiceName 2>&1 | Out-String +if ($Output -match "not installed") { + Pass "status reports 'not installed' for non-existent service" +} else { + Fail "status should report 'not installed'" "got: $Output" +} + +# ── Test: install ───────────────────────────────────────────────── + +Write-Host "--- Test: install ---" + +$Output = & $ZenBinary service install $ZenServerBinary $ServiceName --allow-elevation 2>&1 | Out-String +$ExitCode = $LASTEXITCODE + +if ($ExitCode -eq 0) { + Pass "install exits with code 0" +} else { + Fail "install exits with code 0" "got exit code: $ExitCode, output: $Output" +} + +$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue +if ($svc) { + Pass "service registered in SCM" +} else { + Fail "service registered in SCM" +} + +# Verify service configuration +if ($svc) { + $svcWmi = Get-CimInstance -ClassName Win32_Service -Filter "Name='$ServiceName'" -ErrorAction SilentlyContinue + if ($svcWmi -and $svcWmi.PathName -match "zenserver") { + Pass "service binary path contains zenserver" + } else { + Fail "service binary path contains zenserver" "got: $($svcWmi.PathName)" + } + + if ($svcWmi -and $svcWmi.StartMode -eq "Auto") { + Pass "service is set to auto-start" + } else { + Fail "service is set to auto-start" "got: $($svcWmi.StartMode)" + } +} + +# ── Test: install again (already installed, no --full) ──────────── + +Write-Host "--- Test: install again (idempotent) ---" + +$Output = & $ZenBinary service install $ZenServerBinary $ServiceName --allow-elevation 2>&1 | Out-String +$ExitCode = $LASTEXITCODE + +if ($ExitCode -eq 0) { + Pass "re-install exits with code 0" +} else { + Fail "re-install exits with code 0" "got exit code: $ExitCode" +} + +if ($Output -match "already installed") { + Pass "re-install reports service already installed" +} else { + Fail "re-install reports service already installed" "got: $Output" +} + +# ── Test: status after install (not yet started) ────────────────── + +Write-Host "--- Test: status after install (stopped) ---" + +$Output = & $ZenBinary service status $ServiceName 2>&1 | Out-String +if ($Output -match "not running") { + Pass "status reports 'not running' for stopped service" +} else { + Fail "status reports 'not running' for stopped service" "got: $Output" +} + +# ── Test: start ─────────────────────────────────────────────────── + +Write-Host "--- Test: start ---" + +$Output = & $ZenBinary service start $ServiceName --allow-elevation 2>&1 | Out-String +$ExitCode = $LASTEXITCODE + +if ($ExitCode -eq 0) { + Pass "start exits with code 0" +} else { + Fail "start exits with code 0" "got exit code: $ExitCode, output: $Output" +} + +Start-Sleep -Seconds 2 + +$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue +if ($svc -and $svc.Status -eq "Running") { + Pass "service is running after start" + + # ── Test: status while running ──────────────────────────────── + + Write-Host "--- Test: status (running) ---" + + $Output = & $ZenBinary service status $ServiceName 2>&1 | Out-String + if ($Output -match "Running") { + Pass "status reports 'Running'" + } else { + Fail "status reports 'Running'" "got: $Output" + } + + if ($Output -match "zenserver") { + Pass "status shows executable path" + } else { + Fail "status shows executable path" "got: $Output" + } + + # ── Test: start again (already running) ─────────────────────── + + Write-Host "--- Test: start again (already running) ---" + + $Output = & $ZenBinary service start $ServiceName --allow-elevation 2>&1 | Out-String + if ($Output -match "already running") { + Pass "start reports service already running" + } else { + Fail "start reports service already running" "got: $Output" + } + + # ── Test: stop ──────────────────────────────────────────────── + + Write-Host "--- Test: stop ---" + + $Output = & $ZenBinary service stop $ServiceName --allow-elevation 2>&1 | Out-String + $ExitCode = $LASTEXITCODE + + if ($ExitCode -eq 0) { + Pass "stop exits with code 0" + } else { + Fail "stop exits with code 0" "got exit code: $ExitCode, output: $Output" + } + + Start-Sleep -Seconds 2 + + $svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue + if ($svc -and $svc.Status -ne "Running") { + Pass "service is not running after stop" + } else { + Fail "service is not running after stop" + } +} else { + Fail "service is running after start" "(skipping start-dependent tests)" +} + +# ── Test: stop when already stopped ─────────────────────────────── + +Write-Host "--- Test: stop when already stopped ---" + +Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue +Start-Sleep -Seconds 1 + +$Output = & $ZenBinary service stop $ServiceName --allow-elevation 2>&1 | Out-String +if ($Output -match "not running") { + Pass "stop reports 'not running' when already stopped" +} else { + Fail "stop reports 'not running' when already stopped" "got: $Output" +} + +# ── Test: uninstall while running (should fail) ─────────────────── + +Write-Host "--- Test: uninstall while running (should fail) ---" + +& $ZenBinary service start $ServiceName --allow-elevation 2>&1 | Out-Null +Start-Sleep -Seconds 2 + +$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue +if ($svc -and $svc.Status -eq "Running") { + $Output = & $ZenBinary service uninstall $ServiceName --allow-elevation 2>&1 | Out-String + $ExitCode = $LASTEXITCODE + + if ($ExitCode -ne 0 -or $Output -match "running.*stop") { + Pass "uninstall refuses while service is running" + } else { + Fail "uninstall refuses while service is running" "got: exit=$ExitCode, output: $Output" + } + + # Stop it for the real uninstall test + & $ZenBinary service stop $ServiceName --allow-elevation 2>&1 | Out-Null + Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue + Start-Sleep -Seconds 2 +} else { + Write-Host " (skipped: could not start service for this test)" +} + +# ── Test: uninstall ─────────────────────────────────────────────── + +Write-Host "--- Test: uninstall ---" + +Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue +Start-Sleep -Seconds 1 + +$Output = & $ZenBinary service uninstall $ServiceName --allow-elevation 2>&1 | Out-String +$ExitCode = $LASTEXITCODE + +if ($ExitCode -eq 0) { + Pass "uninstall exits with code 0" +} else { + Fail "uninstall exits with code 0" "got exit code: $ExitCode, output: $Output" +} + +$svc = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue +if (-not $svc) { + Pass "service removed from SCM after uninstall" +} else { + Fail "service removed from SCM after uninstall" +} + +# ── Test: status after uninstall ────────────────────────────────── + +Write-Host "--- Test: status after uninstall ---" + +$Output = & $ZenBinary service status $ServiceName 2>&1 | Out-String +if ($Output -match "not installed") { + Pass "status reports 'not installed' after uninstall" +} else { + Fail "status reports 'not installed' after uninstall" "got: $Output" +} + +# ── Test: uninstall when not installed (idempotent) ─────────────── + +Write-Host "--- Test: uninstall when not installed ---" + +$Output = & $ZenBinary service uninstall $ServiceName --allow-elevation 2>&1 | Out-String +$ExitCode = $LASTEXITCODE + +if ($ExitCode -eq 0) { + Pass "uninstall of non-existent service exits with code 0" +} else { + Fail "uninstall of non-existent service exits with code 0" "got exit code: $ExitCode" +} + +if ($Output -match "not installed") { + Pass "uninstall reports service not installed" +} else { + Fail "uninstall reports service not installed" "got: $Output" +} + +} finally { + Cleanup +} + +if ($Script:Failed -gt 0) { + exit 1 +} |