|
| 1 | +#!/bin/bash |
| 2 | +# Integration test: setup_autostart() / main_service_remove() against a REAL OpenRC. |
| 3 | +# |
| 4 | +# Covers the non-systemd path added for Alpine (#130). tests/test_telegram_service_openrc.sh |
| 5 | +# verifies that the right rc-update/rc-service commands *would* be invoked, using stubs. |
| 6 | +# This runs the real thing: the init script is written to the real /etc/init.d, rc-update |
| 7 | +# really registers it in a runlevel, and rc-service really executes it. |
| 8 | +# |
| 9 | +# Intended to run in a throwaway Alpine container: |
| 10 | +# docker run --rm -v "$PWD:/src" -w /src alpine:3.20 sh -c ' |
| 11 | +# apk add --no-cache bash openrc && bash tests/integration/autostart_openrc.sh' |
| 12 | +# |
| 13 | +# Prints SKIP and exits 0 when OpenRC is not present, so it is harmless to run anywhere. |
| 14 | + |
| 15 | +set -o pipefail |
| 16 | + |
| 17 | +if [ "${BASH_VERSINFO[0]:-0}" -lt 4 ]; then |
| 18 | + echo "SKIP: bash 4+ required (got ${BASH_VERSION:-unknown})" >&2 |
| 19 | + exit 0 |
| 20 | +fi |
| 21 | + |
| 22 | +if [ ! -x /sbin/openrc-run ] || ! command -v rc-service >/dev/null 2>&1; then |
| 23 | + echo "SKIP: OpenRC not present on this host" >&2 |
| 24 | + exit 0 |
| 25 | +fi |
| 26 | + |
| 27 | +if [ "$(id -u)" -ne 0 ]; then |
| 28 | + echo "ERROR: must run as root — this writes to /etc/init.d." >&2 |
| 29 | + echo " sudo bash $0" >&2 |
| 30 | + exit 1 |
| 31 | +fi |
| 32 | + |
| 33 | +# OpenRC's verify_boot() refuses to run any service unless it believes the system was |
| 34 | +# booted by OpenRC. In a container nothing booted it, so the marker has to be created. |
| 35 | +SOFTLEVEL_CREATED=0 |
| 36 | +if [ ! -e /run/openrc/softlevel ]; then |
| 37 | + mkdir -p /run/openrc |
| 38 | + : >/run/openrc/softlevel |
| 39 | + SOFTLEVEL_CREATED=1 |
| 40 | +fi |
| 41 | + |
| 42 | +REPO_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) |
| 43 | +INITD="/etc/init.d/mtproxymax" |
| 44 | +RUNLEVEL_LINK="/etc/runlevels/default/mtproxymax" |
| 45 | +DOCKER_STUB="/etc/init.d/docker" |
| 46 | +DOCKER_STUB_CREATED=0 |
| 47 | +FAKE="/usr/local/bin/mtproxymax" |
| 48 | +FAKE_LOG="/tmp/mtproxymax-fake.log" |
| 49 | +FAKE_BACKUP="/tmp/mtproxymax-fake.backup" |
| 50 | + |
| 51 | +FAKE_WAS_PRESENT=0 |
| 52 | +if [ -e "$FAKE" ]; then |
| 53 | + cp -a "$FAKE" "$FAKE_BACKUP" 2>/dev/null && FAKE_WAS_PRESENT=1 |
| 54 | +fi |
| 55 | + |
| 56 | +# The generated init script declares `depend() { need docker; }`. OpenRC refuses to start |
| 57 | +# a service whose hard dependencies cannot be resolved (`ERROR: mtproxymax needs service(s) |
| 58 | +# docker`), so a stub is required for the start assertion below to exercise OUR script |
| 59 | +# rather than failing on the host's dependency graph. |
| 60 | +if [ ! -e "$DOCKER_STUB" ]; then |
| 61 | + cat >"$DOCKER_STUB" <<'DOCKER_EOF' |
| 62 | +#!/sbin/openrc-run |
| 63 | +description="Docker stub (MTProxyMax integration test)" |
| 64 | +
|
| 65 | +start() { |
| 66 | + ebegin "Starting docker (stub)" |
| 67 | + eend 0 |
| 68 | +} |
| 69 | +
|
| 70 | +stop() { |
| 71 | + ebegin "Stopping docker (stub)" |
| 72 | + eend 0 |
| 73 | +} |
| 74 | +DOCKER_EOF |
| 75 | + chmod +x "$DOCKER_STUB" |
| 76 | + DOCKER_STUB_CREATED=1 |
| 77 | +fi |
| 78 | + |
| 79 | +cleanup() { |
| 80 | + rc-service mtproxymax stop >/dev/null 2>&1 |
| 81 | + rc-update del mtproxymax default >/dev/null 2>&1 |
| 82 | + rm -f "$INITD" "$RUNLEVEL_LINK" |
| 83 | + [ "$DOCKER_STUB_CREATED" -eq 1 ] && rm -f "$DOCKER_STUB" |
| 84 | + [ "$SOFTLEVEL_CREATED" -eq 1 ] && rm -f /run/openrc/softlevel |
| 85 | + if [ "$FAKE_WAS_PRESENT" -eq 1 ]; then |
| 86 | + cp -a "$FAKE_BACKUP" "$FAKE" 2>/dev/null |
| 87 | + else |
| 88 | + rm -f "$FAKE" |
| 89 | + fi |
| 90 | + rm -f "$FAKE_BACKUP" |
| 91 | +} |
| 92 | +trap cleanup EXIT |
| 93 | + |
| 94 | +cat >"$FAKE" <<'FAKE_EOF' |
| 95 | +#!/bin/bash |
| 96 | +# Test double installed by tests/integration/autostart_openrc.sh, so that the generated |
| 97 | +# init script's start()/stop() have something real to invoke. |
| 98 | +printf '%s\n' "$*" >> /tmp/mtproxymax-fake.log |
| 99 | +exit 0 |
| 100 | +FAKE_EOF |
| 101 | +chmod +x "$FAKE" |
| 102 | +: >"$FAKE_LOG" |
| 103 | + |
| 104 | +MTPROXYMAX_SOURCE_ONLY=true source "$REPO_ROOT/mtproxymax.sh" |
| 105 | +set +e |
| 106 | + |
| 107 | +TESTS_RUN=0 |
| 108 | +TESTS_FAILED=0 |
| 109 | + |
| 110 | +assert_eq() { |
| 111 | + local name="$1" want="$2" got="$3" |
| 112 | + TESTS_RUN=$((TESTS_RUN + 1)) |
| 113 | + if [ "$got" = "$want" ]; then |
| 114 | + printf ' PASS %s\n' "$name" |
| 115 | + else |
| 116 | + printf ' FAIL %s (got=%q want=%q)\n' "$name" "$got" "$want" |
| 117 | + TESTS_FAILED=$((TESTS_FAILED + 1)) |
| 118 | + fi |
| 119 | +} |
| 120 | + |
| 121 | +assert_ne() { |
| 122 | + local name="$1" unwanted="$2" got="$3" |
| 123 | + TESTS_RUN=$((TESTS_RUN + 1)) |
| 124 | + if [ "$got" != "$unwanted" ]; then |
| 125 | + printf ' PASS %s\n' "$name" |
| 126 | + else |
| 127 | + printf ' FAIL %s (unexpectedly %q)\n' "$name" "$got" |
| 128 | + TESTS_FAILED=$((TESTS_FAILED + 1)) |
| 129 | + fi |
| 130 | +} |
| 131 | + |
| 132 | +assert_cmd_ok() { |
| 133 | + local name="$1" |
| 134 | + shift |
| 135 | + local out rc |
| 136 | + out=$("$@" 2>&1) |
| 137 | + rc=$? |
| 138 | + # OpenRC tries to enrol each service in cgroup v1 hierarchies that are read-only in a |
| 139 | + # container, emitting one "can't create /sys/fs/cgroup/.../tasks" line per controller. |
| 140 | + # That is environmental noise rather than test signal, and it buries the real error. |
| 141 | + out=$(printf '%s\n' "$out" | grep -v '/sys/fs/cgroup/.*/tasks') |
| 142 | + TESTS_RUN=$((TESTS_RUN + 1)) |
| 143 | + if [ "$rc" -eq 0 ]; then |
| 144 | + printf ' PASS %s\n' "$name" |
| 145 | + else |
| 146 | + printf ' FAIL %s (exit %d)\n' "$name" "$rc" |
| 147 | + printf '%s\n' "$out" | sed 's/^/ | /' |
| 148 | + TESTS_FAILED=$((TESTS_FAILED + 1)) |
| 149 | + fi |
| 150 | +} |
| 151 | + |
| 152 | +assert_file_contains() { |
| 153 | + local name="$1" needle="$2" file="$3" |
| 154 | + TESTS_RUN=$((TESTS_RUN + 1)) |
| 155 | + if grep -qF -- "$needle" "$file" 2>/dev/null; then |
| 156 | + printf ' PASS %s\n' "$name" |
| 157 | + else |
| 158 | + printf ' FAIL %s (missing %q in %s)\n' "$name" "$needle" "$file" |
| 159 | + TESTS_FAILED=$((TESTS_FAILED + 1)) |
| 160 | + fi |
| 161 | +} |
| 162 | + |
| 163 | +file_state() { [ -e "$1" ] && echo present || echo absent; } |
| 164 | + |
| 165 | +# --- the host must actually be what we think it is ---------------------------- |
| 166 | +assert_eq "host detects openrc" "openrc" "$(detect_init_system)" |
| 167 | + |
| 168 | +# --- install ------------------------------------------------------------------ |
| 169 | +setup_autostart >/dev/null 2>&1 |
| 170 | +assert_eq "setup_autostart succeeds" "0" "$?" |
| 171 | +assert_eq "init script written" "present" "$(file_state "$INITD")" |
| 172 | +assert_eq "init script is executable" "yes" "$([ -x "$INITD" ] && echo yes || echo no)" |
| 173 | +assert_eq "init script has openrc-run shebang" "#!/sbin/openrc-run" "$(head -n1 "$INITD" 2>/dev/null)" |
| 174 | + |
| 175 | +# The dependency declarations matter independently of whether they can be resolved here; |
| 176 | +# rc-service refuses to start a service with unmet hard deps, so this is load-bearing. |
| 177 | +assert_file_contains "declares need docker" "need docker" "$INITD" |
| 178 | +assert_file_contains "declares need net" "need net" "$INITD" |
| 179 | + |
| 180 | +# The invariant openrc_enable_service() checks: rc-update's exit status is not trusted, |
| 181 | +# the runlevel symlink actually landing is. Guards the #130 bug class. |
| 182 | +assert_eq "runlevel symlink created" "present" "$(file_state "$RUNLEVEL_LINK")" |
| 183 | + |
| 184 | +# --- start / stop ------------------------------------------------------------- |
| 185 | +# OpenRC keeps a dependency cache; without it a service's state is indeterminate and |
| 186 | +# rc-service refuses every start with "already starting". On a real host the cache is |
| 187 | +# built during boot; in a container nothing boots OpenRC, so it has to be built by hand. |
| 188 | +# Note this must come *after* setup_autostart, so the new service is in the graph. |
| 189 | +rc-update -u >/dev/null 2>&1 |
| 190 | + |
| 191 | +assert_cmd_ok "rc-service start succeeds" rc-service mtproxymax start |
| 192 | +assert_eq "start() reached the mtproxymax binary" "start" "$(head -n1 "$FAKE_LOG" 2>/dev/null)" |
| 193 | + |
| 194 | +assert_cmd_ok "rc-service stop succeeds" rc-service mtproxymax stop |
| 195 | +assert_eq "stop() reached the mtproxymax binary" "stop" "$(tail -n1 "$FAKE_LOG" 2>/dev/null)" |
| 196 | + |
| 197 | +# --- remove ------------------------------------------------------------------- |
| 198 | +main_service_remove >/dev/null 2>&1 |
| 199 | +assert_eq "main_service_remove succeeds" "0" "$?" |
| 200 | +assert_eq "init script removed" "absent" "$(file_state "$INITD")" |
| 201 | +assert_eq "runlevel symlink removed" "absent" "$(file_state "$RUNLEVEL_LINK")" |
| 202 | +assert_ne "service no longer registered with rc-update" "mtproxymax" \ |
| 203 | + "$(rc-update show default 2>/dev/null | awk '{print $1}' | grep -x mtproxymax)" |
| 204 | + |
| 205 | +printf '\n%d tests, %d failures\n' "$TESTS_RUN" "$TESTS_FAILED" |
| 206 | +[ "$TESTS_FAILED" -eq 0 ] |
0 commit comments