#!/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