278 lines
10 KiB
Bash
278 lines
10 KiB
Bash
#!/usr/bin/env bash
|
|
# deploy.sh 中重启计划相关逻辑的单元测试
|
|
#
|
|
# 用法: bash vps-xray/tests/test-restart-schedule.sh
|
|
#
|
|
# 不需要 root,不接触真实 systemd:所有写入路径都被重定向到临时目录,
|
|
# systemctl 用 PATH 打桩拦截。
|
|
#
|
|
# 加载方式说明:deploy.sh 结尾会直接调用 main,且顶层有 root 检查与参数解析,
|
|
# 不能整份 source。这里只抽取「多行函数定义」(形如 `name() {` 开头、`}` 结尾)。
|
|
# 单行函数(log/warn/error/step)抽不到,正好由下面的桩覆盖。
|
|
|
|
set -u
|
|
|
|
TEST_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
DEPLOY="${TEST_DIR}/../deploy.sh"
|
|
|
|
if [ ! -f "$DEPLOY" ]; then
|
|
echo "找不到 $DEPLOY" >&2
|
|
exit 1
|
|
fi
|
|
|
|
eval "$(sed -n '/^[a-z_][a-z_]*() {$/,/^}$/p' "$DEPLOY")"
|
|
|
|
# 日志桩:error 保留输出便于断言,其余静音
|
|
log() { :; }
|
|
warn() { :; }
|
|
step() { :; }
|
|
error() { echo "ERROR: $*" >&2; }
|
|
|
|
PASS=0
|
|
FAIL=0
|
|
ok() { PASS=$((PASS + 1)); echo " ok - $1"; }
|
|
bad() { FAIL=$((FAIL + 1)); echo " FAIL - $1"; echo " 期望: $2"; echo " 实际: $3"; }
|
|
# is <描述> <实际值> <期望值>
|
|
is() { if [ "$2" = "$3" ]; then ok "$1"; else bad "$1" "$3" "$2"; fi; }
|
|
|
|
echo "== normalize_restart_time =="
|
|
is "04:00 原样通过" "$(normalize_restart_time 04:00)" "04:00"
|
|
is "4:00 补零" "$(normalize_restart_time 4:00)" "04:00"
|
|
is "08:30 不被当八进制" "$(normalize_restart_time 08:30)" "08:30"
|
|
is "23:59 边界" "$(normalize_restart_time 23:59)" "23:59"
|
|
is "00:00 边界" "$(normalize_restart_time 00:00)" "00:00"
|
|
|
|
normalize_restart_time "24:00" >/dev/null 2>&1
|
|
is "24:00 被拒" "$?" "1"
|
|
normalize_restart_time "04:60" >/dev/null 2>&1
|
|
is "04:60 被拒" "$?" "1"
|
|
normalize_restart_time "abc" >/dev/null 2>&1
|
|
is "abc 被拒" "$?" "1"
|
|
normalize_restart_time "" >/dev/null 2>&1
|
|
is "空串被拒" "$?" "1"
|
|
|
|
echo "== resolve_restart_config =="
|
|
|
|
reset_env() {
|
|
unset XRAY_RESTART_EVERY_DAYS XRAY_RESTART_TIME XRAY_RESTART_TIMEZONE XRAY_DAILY_RESTART
|
|
RESTART_EVERY_DAYS=""; RESTART_TIME=""; RESTART_TZ=""
|
|
}
|
|
|
|
reset_env
|
|
resolve_restart_config
|
|
is "全空时天数默认 0" "$RESTART_EVERY_DAYS" "0"
|
|
is "全空时时间默认" "$RESTART_TIME" "04:00"
|
|
is "全空时时区默认" "$RESTART_TZ" "Asia/Shanghai"
|
|
|
|
reset_env; XRAY_RESTART_EVERY_DAYS=7; XRAY_RESTART_TIME=5:30
|
|
resolve_restart_config
|
|
is "读取新字段天数" "$RESTART_EVERY_DAYS" "7"
|
|
is "读取新字段时间并补零" "$RESTART_TIME" "05:30"
|
|
|
|
reset_env; XRAY_DAILY_RESTART=true
|
|
resolve_restart_config
|
|
is "迁移 true → 1" "$RESTART_EVERY_DAYS" "1"
|
|
|
|
reset_env; XRAY_DAILY_RESTART=TRUE
|
|
resolve_restart_config
|
|
is "迁移 TRUE → 1" "$RESTART_EVERY_DAYS" "1"
|
|
|
|
reset_env; XRAY_DAILY_RESTART=false
|
|
resolve_restart_config
|
|
is "迁移 false → 0" "$RESTART_EVERY_DAYS" "0"
|
|
|
|
reset_env; XRAY_DAILY_RESTART=true; XRAY_RESTART_EVERY_DAYS=3
|
|
resolve_restart_config
|
|
is "新字段优先于旧字段" "$RESTART_EVERY_DAYS" "3"
|
|
|
|
reset_env; XRAY_DAILY_RESTART=true; XRAY_RESTART_EVERY_DAYS=0
|
|
resolve_restart_config
|
|
is "新字段显式 0 覆盖旧 true" "$RESTART_EVERY_DAYS" "0"
|
|
|
|
reset_env; XRAY_RESTART_EVERY_DAYS=abc
|
|
resolve_restart_config 2>/dev/null
|
|
is "天数非数字报错" "$?" "1"
|
|
|
|
reset_env; XRAY_RESTART_EVERY_DAYS=-1
|
|
resolve_restart_config 2>/dev/null
|
|
is "天数负数报错" "$?" "1"
|
|
|
|
reset_env; XRAY_RESTART_TIME=25:00
|
|
resolve_restart_config 2>/dev/null
|
|
is "时间非法报错" "$?" "1"
|
|
|
|
reset_env; XRAY_RESTART_TIMEZONE=""
|
|
resolve_restart_config 2>/dev/null
|
|
is "时区空串报错" "$?" "1"
|
|
|
|
echo "== update_env_field =="
|
|
|
|
TMP="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
cat > "${TMP}/.env" << 'ENVEOF'
|
|
# 这是用户自己加的注释
|
|
XRAY_PORT=443
|
|
XRAY_RESTART_EVERY_DAYS=0
|
|
# 尾部注释
|
|
ENVEOF
|
|
|
|
update_env_field "${TMP}/.env" XRAY_RESTART_EVERY_DAYS 7
|
|
is "替换已有字段" "$(grep '^XRAY_RESTART_EVERY_DAYS=' "${TMP}/.env")" "XRAY_RESTART_EVERY_DAYS=7"
|
|
is "保留用户注释" "$(grep -c '这是用户自己加的注释' "${TMP}/.env")" "1"
|
|
is "保留尾部注释" "$(grep -c '尾部注释' "${TMP}/.env")" "1"
|
|
is "不影响其它字段" "$(grep '^XRAY_PORT=' "${TMP}/.env")" "XRAY_PORT=443"
|
|
|
|
update_env_field "${TMP}/.env" XRAY_RESTART_TIME 05:30
|
|
is "追加缺失字段" "$(grep '^XRAY_RESTART_TIME=' "${TMP}/.env")" "XRAY_RESTART_TIME=05:30"
|
|
|
|
update_env_field "${TMP}/.env" XRAY_RESTART_TIMEZONE Asia/Shanghai
|
|
is "含斜杠的值不被 sed 分隔符干扰" \
|
|
"$(grep '^XRAY_RESTART_TIMEZONE=' "${TMP}/.env")" "XRAY_RESTART_TIMEZONE=Asia/Shanghai"
|
|
|
|
update_env_field "${TMP}/.env" XRAY_RESTART_TIMEZONE Europe/Berlin
|
|
is "含斜杠的值可被再次替换" \
|
|
"$(grep '^XRAY_RESTART_TIMEZONE=' "${TMP}/.env")" "XRAY_RESTART_TIMEZONE=Europe/Berlin"
|
|
|
|
is "字段总行数正确" "$(grep -c '^XRAY_RESTART_TIMEZONE=' "${TMP}/.env")" "1"
|
|
|
|
update_env_field "${TMP}/nonexistent" FOO bar 2>/dev/null
|
|
is "文件不存在返回 1" "$?" "1"
|
|
|
|
printf 'KEY=old' > "${TMP}/.env2"
|
|
update_env_field "${TMP}/.env2" NEWKEY val
|
|
is "缺尾换行时不黏连" "$(grep -c '^NEWKEY=val$' "${TMP}/.env2")" "1"
|
|
is "缺尾换行时原字段完好" "$(grep -c '^KEY=old$' "${TMP}/.env2")" "1"
|
|
|
|
echo "== render_restart_units =="
|
|
|
|
SYSTEMD_DIR="${TMP}/systemd"
|
|
GUARD_BIN="${TMP}/bin/xray-restart-guard"
|
|
RESTART_STATE_FILE="${TMP}/state/last-restart"
|
|
mkdir -p "${TMP}/systemd" "${TMP}/bin" "${TMP}/state"
|
|
|
|
# systemctl 打桩:记录被调用的参数,避免碰真实 systemd
|
|
STUB="${TMP}/stub"
|
|
mkdir -p "$STUB"
|
|
cat > "${STUB}/systemctl" << 'STUBEOF'
|
|
#!/bin/sh
|
|
echo "systemctl $*" >> "${SYSTEMCTL_LOG}"
|
|
STUBEOF
|
|
chmod 755 "${STUB}/systemctl"
|
|
export SYSTEMCTL_LOG="${TMP}/systemctl.log"
|
|
: > "$SYSTEMCTL_LOG"
|
|
export PATH="${STUB}:${PATH}"
|
|
|
|
render_restart_units 7 04:00 Asia/Shanghai
|
|
|
|
is "timer 文件存在" "$([ -f "${SYSTEMD_DIR}/xray-restart.timer" ] && echo yes)" "yes"
|
|
is "service 文件存在" "$([ -f "${SYSTEMD_DIR}/xray-restart.service" ] && echo yes)" "yes"
|
|
is "guard 可执行" "$([ -x "$GUARD_BIN" ] && echo yes)" "yes"
|
|
|
|
is "OnCalendar 每天触发" \
|
|
"$(grep -c '^OnCalendar=\*-\*-\* 04:00:00$' "${SYSTEMD_DIR}/xray-restart.timer")" "1"
|
|
is "TimeZone 写入" \
|
|
"$(grep -c '^TimeZone=Asia/Shanghai$' "${SYSTEMD_DIR}/xray-restart.timer")" "1"
|
|
is "Persistent 开启" \
|
|
"$(grep -c '^Persistent=true$' "${SYSTEMD_DIR}/xray-restart.timer")" "1"
|
|
is "service 指向 guard" \
|
|
"$(grep -c "^ExecStart=${GUARD_BIN}\$" "${SYSTEMD_DIR}/xray-restart.service")" "1"
|
|
is "guard 里天数正确" \
|
|
"$(grep -c '^DAYS=7$' "$GUARD_BIN")" "1"
|
|
is "guard 里状态文件路径正确" \
|
|
"$(grep -c "^STATE=\"${RESTART_STATE_FILE}\"\$" "$GUARD_BIN")" "1"
|
|
is "guard 用 PATH 查找 systemctl 而非绝对路径" \
|
|
"$(grep -c '^exec systemctl restart xray$' "$GUARD_BIN")" "1"
|
|
|
|
render_restart_units 3 23:05 Europe/Berlin
|
|
is "重复渲染覆盖天数" "$(grep -c '^DAYS=3$' "$GUARD_BIN")" "1"
|
|
is "重复渲染覆盖时间" \
|
|
"$(grep -c '^OnCalendar=\*-\*-\* 23:05:00$' "${SYSTEMD_DIR}/xray-restart.timer")" "1"
|
|
|
|
remove_restart_units
|
|
is "remove 后 timer 消失" "$([ -f "${SYSTEMD_DIR}/xray-restart.timer" ] && echo yes || echo no)" "no"
|
|
is "remove 后 service 消失" "$([ -f "${SYSTEMD_DIR}/xray-restart.service" ] && echo yes || echo no)" "no"
|
|
is "remove 后 guard 消失" "$([ -f "$GUARD_BIN" ] && echo yes || echo no)" "no"
|
|
|
|
remove_restart_units
|
|
is "重复 remove 返回 1" "$?" "1"
|
|
|
|
echo "== guard 脚本行为 =="
|
|
|
|
render_restart_units 7 04:00 Asia/Shanghai
|
|
rm -f "$RESTART_STATE_FILE"
|
|
: > "$SYSTEMCTL_LOG"
|
|
|
|
# 无状态文件 → 首次必须执行
|
|
out=$("$GUARD_BIN" 2>&1)
|
|
is "首次执行重启" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "1"
|
|
is "首次写入时间戳" "$([ -f "$RESTART_STATE_FILE" ] && echo yes)" "yes"
|
|
|
|
# 刚重启过 → 必须跳过
|
|
: > "$SYSTEMCTL_LOG"
|
|
out=$("$GUARD_BIN" 2>&1)
|
|
is "刚重启过则跳过" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0"
|
|
is "跳过时有提示" "$(echo "$out" | grep -c '跳过')" "1"
|
|
|
|
# 距上次 6 天 → 仍未满 7 天,跳过
|
|
echo "$(( $(date +%s) - 6 * 86400 ))" > "$RESTART_STATE_FILE"
|
|
: > "$SYSTEMCTL_LOG"
|
|
"$GUARD_BIN" > /dev/null 2>&1
|
|
is "6 天不足 7 天则跳过" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0"
|
|
|
|
# 距上次 7 天差 100 秒 → 300 秒容差内,必须执行
|
|
echo "$(( $(date +%s) - 7 * 86400 + 100 ))" > "$RESTART_STATE_FILE"
|
|
: > "$SYSTEMCTL_LOG"
|
|
"$GUARD_BIN" > /dev/null 2>&1
|
|
is "容差内视为已满 7 天" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "1"
|
|
|
|
# 距上次 7 天差 600 秒 → 超出容差,跳过
|
|
echo "$(( $(date +%s) - 7 * 86400 + 600 ))" > "$RESTART_STATE_FILE"
|
|
: > "$SYSTEMCTL_LOG"
|
|
"$GUARD_BIN" > /dev/null 2>&1
|
|
is "超出容差仍判不足" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "0"
|
|
|
|
# 状态文件内容被写坏 → 按 0 处理,立即执行
|
|
echo "garbage" > "$RESTART_STATE_FILE"
|
|
: > "$SYSTEMCTL_LOG"
|
|
"$GUARD_BIN" > /dev/null 2>&1
|
|
is "状态文件非法时立即执行" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "1"
|
|
is "非法内容被覆盖为数字" \
|
|
"$(grep -cE '^[0-9]+$' "$RESTART_STATE_FILE")" "1"
|
|
|
|
echo "garbage" > "$RESTART_STATE_FILE"
|
|
: > "$SYSTEMCTL_LOG"
|
|
guard_err=$("$GUARD_BIN" 2>&1 >/dev/null)
|
|
is "状态文件非法时不产生算术求值报错" \
|
|
"$(printf '%s' "$guard_err" | grep -c 'integer expression')" "0"
|
|
|
|
# 每日模式:距上次 1 天即执行
|
|
render_restart_units 1 04:00 Asia/Shanghai
|
|
echo "$(( $(date +%s) - 86400 ))" > "$RESTART_STATE_FILE"
|
|
: > "$SYSTEMCTL_LOG"
|
|
"$GUARD_BIN" > /dev/null 2>&1
|
|
is "N=1 时满一天即执行" "$(grep -c '^systemctl restart xray$' "$SYSTEMCTL_LOG")" "1"
|
|
|
|
# 时间戳先于重启写入:用一个必定失败的 systemctl 桩验证
|
|
cat > "${STUB}/systemctl" << 'STUBEOF'
|
|
#!/bin/sh
|
|
echo "systemctl $*" >> "${SYSTEMCTL_LOG}"
|
|
exit 1
|
|
STUBEOF
|
|
chmod 755 "${STUB}/systemctl"
|
|
rm -f "$RESTART_STATE_FILE"
|
|
: > "$SYSTEMCTL_LOG"
|
|
"$GUARD_BIN" > /dev/null 2>&1
|
|
is "重启失败时时间戳仍已写入" "$([ -s "$RESTART_STATE_FILE" ] && echo yes)" "yes"
|
|
|
|
# 恢复正常桩,避免影响后续
|
|
cat > "${STUB}/systemctl" << 'STUBEOF'
|
|
#!/bin/sh
|
|
echo "systemctl $*" >> "${SYSTEMCTL_LOG}"
|
|
STUBEOF
|
|
chmod 755 "${STUB}/systemctl"
|
|
|
|
echo ""
|
|
echo "通过 ${PASS},失败 ${FAIL}"
|
|
[ "$FAIL" -eq 0 ]
|