diff options
Diffstat (limited to 'scripts/test_linux')
| -rwxr-xr-x | scripts/test_linux/block-clone-test.sh | 143 | ||||
| -rwxr-xr-x | scripts/test_linux/crashpad-test.sh | 184 | ||||
| -rwxr-xr-x | scripts/test_linux/service-test.sh | 380 |
3 files changed, 707 insertions, 0 deletions
diff --git a/scripts/test_linux/block-clone-test.sh b/scripts/test_linux/block-clone-test.sh new file mode 100755 index 000000000..0a74283f2 --- /dev/null +++ b/scripts/test_linux/block-clone-test.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# Test block-clone functionality on temporary Btrfs and XFS loopback filesystems. +# +# Requires: root/sudo, btrfs-progs (mkfs.btrfs), xfsprogs (mkfs.xfs) +# +# Usage: +# 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. +# +# Options: +# --btrfs-only Only test Btrfs +# --xfs-only Only test XFS + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +TEST_BINARY="" +RUN_BTRFS=true +RUN_XFS=true + +for arg in "$@"; do + case "$arg" in + --btrfs-only) RUN_XFS=false ;; + --xfs-only) RUN_BTRFS=false ;; + *) TEST_BINARY="$arg" ;; + esac +done + +TEST_BINARY="${TEST_BINARY:-$REPO_ROOT/build/linux/x86_64/debug/zencore-test}" +IMAGE_SIZE="512M" +TEST_CASES="TryCloneFile,CopyFile.Clone,SupportsBlockRefCounting,CloneQueryInterface" + +# Track all temp files for cleanup +CLEANUP_MOUNTS=() +CLEANUP_DIRS=() +CLEANUP_FILES=() + +cleanup() { + local exit_code=$? + set +e + + for mnt in "${CLEANUP_MOUNTS[@]}"; do + if mountpoint -q "$mnt" 2>/dev/null; then + umount "$mnt" + fi + done + for dir in "${CLEANUP_DIRS[@]}"; do + [ -d "$dir" ] && rmdir "$dir" + done + for f in "${CLEANUP_FILES[@]}"; do + [ -f "$f" ] && rm -f "$f" + done + + if [ $exit_code -ne 0 ]; then + echo "FAILED (exit code $exit_code)" + fi + exit $exit_code +} +trap cleanup EXIT + +# --- Preflight checks --- + +if [ "$(id -u)" -ne 0 ]; then + echo "error: this script must be run as root (for mount/umount)" >&2 + exit 1 +fi + +if [ ! -x "$TEST_BINARY" ]; then + echo "error: test binary not found or not executable: $TEST_BINARY" >&2 + echo "hint: build with 'xmake config -m debug && xmake build zencore-test'" >&2 + exit 1 +fi + +if $RUN_BTRFS && ! command -v mkfs.btrfs &>/dev/null; then + echo "warning: mkfs.btrfs not found — install btrfs-progs to test Btrfs, skipping" >&2 + RUN_BTRFS=false +fi + +if $RUN_XFS && ! command -v mkfs.xfs &>/dev/null; then + echo "warning: mkfs.xfs not found — install xfsprogs to test XFS, skipping" >&2 + RUN_XFS=false +fi + +if ! $RUN_BTRFS && ! $RUN_XFS; then + echo "error: no filesystems to test" >&2 + exit 1 +fi + +# --- Helper to create, mount, and run tests on a loopback filesystem --- + +run_tests_on_fs() { + local fs_type="$1" + local mkfs_cmd="$2" + + echo "" + echo "========================================" + echo " Testing block-clone on $fs_type" + echo "========================================" + + local image_path mount_path + image_path="$(mktemp "/tmp/${fs_type}-clone-test-XXXXXX.img")" + mount_path="$(mktemp -d "/tmp/${fs_type}-clone-mount-XXXXXX")" + CLEANUP_FILES+=("$image_path") + CLEANUP_DIRS+=("$mount_path") + CLEANUP_MOUNTS+=("$mount_path") + + echo "Creating ${IMAGE_SIZE} ${fs_type} image at ${image_path} ..." + truncate -s "$IMAGE_SIZE" "$image_path" + $mkfs_cmd "$image_path" + + echo "Mounting at ${mount_path} ..." + mount -o loop "$image_path" "$mount_path" + chmod 777 "$mount_path" + + echo "Copying test binary ..." + cp "$TEST_BINARY" "$mount_path/zencore-test" + chmod +x "$mount_path/zencore-test" + + echo "Running tests ..." + echo "---" + "$mount_path/zencore-test" \ + --test-suite="core.filesystem" \ + --test-case="$TEST_CASES" + echo "---" + echo "$fs_type: all block-clone tests passed." +} + +# --- Run --- + +if $RUN_BTRFS; then + run_tests_on_fs "btrfs" "mkfs.btrfs -q" +fi + +if $RUN_XFS; then + run_tests_on_fs "xfs" "mkfs.xfs -q -m reflink=1" +fi + +echo "" +echo "All block-clone tests passed." diff --git a/scripts/test_linux/crashpad-test.sh b/scripts/test_linux/crashpad-test.sh new file mode 100755 index 000000000..bd08cefc5 --- /dev/null +++ b/scripts/test_linux/crashpad-test.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +# Verify that crashpad_handler is active when zenserver (release build) starts. +# +# This test: +# 1. Launches zenserver from the release build. +# 2. Waits for the HTTP health endpoint to become ready. +# 3. Checks that a crashpad_handler child process is running. +# 4. Checks that the startup log contains "sentry initialized" (not a failure). +# +# Usage: +# ./scripts/test_linux/crashpad-test.sh [path-to-zenserver] +# +# If no path is given, defaults to build/linux/x86_64/release/zenserver +# relative to the repository root. +# +# The test exits 0 on success, 1 on failure. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +ZENSERVER_BINARY="${1:-$REPO_ROOT/build/linux/x86_64/release/zenserver}" +CRASHPAD_HANDLER="$(dirname "$ZENSERVER_BINARY")/crashpad_handler" +PORT=18558 # Use a non-default port so we don't collide with a running zenserver +DATA_DIR="$(mktemp -d)" +STDOUT_FILE="$DATA_DIR/stdout.log" +ZENSERVER_PID="" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +PASSED=0 +FAILED=0 + +pass() { + echo -e " ${GREEN}PASS${NC} $1" + (( PASSED++ )) || true +} + +fail() { + echo -e " ${RED}FAIL${NC} $1" + (( FAILED++ )) || true +} + +cleanup() { + set +e + if [ -n "$ZENSERVER_PID" ] && kill -0 "$ZENSERVER_PID" 2>/dev/null; then + kill "$ZENSERVER_PID" 2>/dev/null + wait "$ZENSERVER_PID" 2>/dev/null + fi + rm -rf "$DATA_DIR" +} +trap cleanup EXIT + +# ── Preflight ──────────────────────────────────────────────────────────────── + +echo "" +echo "==============================" +echo " Crashpad active check" +echo "==============================" +echo "" + +if [ ! -f "$ZENSERVER_BINARY" ]; then + echo -e "${RED}ERROR: zenserver binary not found: $ZENSERVER_BINARY${NC}" + echo " Build with: xmake config -m release && xmake build zenserver" + exit 1 +fi + +if [ ! -f "$CRASHPAD_HANDLER" ]; then + echo -e "${RED}ERROR: crashpad_handler not found alongside zenserver: $CRASHPAD_HANDLER${NC}" + echo " It should be copied there automatically by the build." + exit 1 +fi + +echo "zenserver: $ZENSERVER_BINARY" +echo "crashpad_handler: $CRASHPAD_HANDLER" +echo "port: $PORT" +echo "data dir: $DATA_DIR" +echo "" + +# ── Start zenserver ────────────────────────────────────────────────────────── + +# zenserver runs in the foreground until SIGINT/SIGTERM. Launch in background +# so we can poll its health endpoint and inspect child processes. +"$ZENSERVER_BINARY" \ + --port="$PORT" \ + --data-dir="$DATA_DIR" \ + > "$STDOUT_FILE" 2>&1 & +ZENSERVER_PID=$! + +echo "Started zenserver (pid $ZENSERVER_PID), waiting for health endpoint..." + +# ── Wait for health endpoint ───────────────────────────────────────────────── + +READY=false +for i in $(seq 1 40); do + if curl -sf "http://localhost:$PORT/health" > /dev/null 2>&1; then + READY=true + break + fi + sleep 0.5 +done + +if [ "$READY" = false ]; then + echo -e "${RED}ERROR: zenserver did not become ready within 20 seconds${NC}" + if [ -f "$STDOUT_FILE" ]; then + echo "" + echo "--- stdout ---" + cat "$STDOUT_FILE" + fi + exit 1 +fi + +echo "Server is ready." + +# Give the server a moment to finish startup logging (sentry init log +# message is emitted after the health endpoint comes up). +sleep 1 +echo "" + +# ── Test 1: crashpad_handler process is running for our data dir ───────────── +# +# sentry-native starts crashpad_handler with --database pointing at +# $DATA_DIR/.sentry-native. The process re-parents itself out of zenserver's +# process tree, so we look for it by its command-line arguments rather than +# by parent PID. + +HANDLER_PID="$(pgrep -f "crashpad_handler.*${DATA_DIR}" 2>/dev/null | head -1 || true)" +if [ -n "$HANDLER_PID" ]; then + pass "crashpad_handler is running (pid $HANDLER_PID) with database in our data dir" +else + fail "crashpad_handler process not found — sentry_init may have failed or sentry is disabled" +fi + +# ── Test 2: No sentry_init failure in startup log ──────────────────────────── +# +# The "sentry initialized" success message is logged at INFO level under the +# sentry-sdk log category, which is filtered to Warn by default — so it won't +# appear in normal stdout. We check for the absence of the failure message +# instead (which IS at Warn level and would appear if sentry_init failed). + +if grep -q "sentry_init returned failure" "$STDOUT_FILE" 2>/dev/null; then + ERRMSG="$(grep "sentry_init returned failure" "$STDOUT_FILE" | head -1)" + fail "sentry_init reported failure: $ERRMSG" +else + pass "No sentry_init failure message in startup log" +fi + +# ── Test 3: ldd sanity — crashpad_handler must not need libc++.so.1 ────────── + +MISSING_LIBCXX="$(ldd "$CRASHPAD_HANDLER" 2>/dev/null | grep "libc++\.so\.1" | grep "not found" || true)" +if [ -n "$MISSING_LIBCXX" ]; then + fail "crashpad_handler has an unsatisfied libc++.so.1 dependency (static linking patch not applied)" +elif ldd "$CRASHPAD_HANDLER" 2>/dev/null | grep -q "libc++\.so\.1"; then + fail "crashpad_handler links libc++.so.1 dynamically — it should be statically linked" +else + pass "crashpad_handler has no dynamic libc++.so.1 dependency" +fi + +# ── Summary ────────────────────────────────────────────────────────────────── + +echo "" +echo "==============================" +printf " Passed: " +echo -e "${GREEN}${PASSED}${NC}" +printf " Failed: " +if [ "$FAILED" -gt 0 ]; then + echo -e "${RED}${FAILED}${NC}" +else + echo -e "${GREEN}${FAILED}${NC}" +fi +echo "==============================" +echo "" + +if [ "$FAILED" -gt 0 ]; then + if [ -f "$STDOUT_FILE" ]; then + echo "--- stdout ---" + cat "$STDOUT_FILE" + fi + exit 1 +fi 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 |