Files
MeowOS-For-R3P2/patch_v2/ntpsync/tests/run_tests.sh
T
AskaEth c9f0cbaab6 feat(ntp): M22 网络校时 — 复用固件自带 ntpd + hwclock 回写 RTC
真机实测(0.8.47,2026-11-06)定位到的根因不是「没有 NTP 客户端」:
固件自带 ntp-4.2.8p14(/usr/sbin/ntpd)和 /etc/ntp.conf(4 个 pool.ntp.org),
但没有任何 init 脚本/进程启动它 —— 有客户端、没启动。

实测证据:
- RTC 存在且可读(/dev/rtc0,10003000.rtc),内核开机 hctosys=1,
  冷启动不回到 1970 → 掉电保持;RTC 存 UTC(since_epoch == date +%s)。
- 但 RTC 会走慢且无人纠正:实测系统时间比 NTP 慢 49 s;把系统时间人为
  拨错 4 h 后观察 65 s 无人纠正,RTC 也不跟随系统时间。
- UDP/123 出网正常(ntpd -q 一次成功 time set +48.877s);DNS/HTTP 正常。
- udhcpc 的 default.script 不处理 option 42,实测热点也不下发。
- ntpd -q 不会回写 RTC(步进后 sys-rtc 仍差 49 s)→ 必须 hwclock -w -u。
- ntpd 没有超时参数,无服务器时永久存活 → 必须自带看门狗。
- -p 形式的 server 首个 poll 要 ~64 s;配置里加 iburst 后 8 s 内返回。

实现(新增,不改任何基线文件):
- /usr/bin/meow_ntpsync:一次性校时器。复用 ntpd -q -g + 运行时生成的
  iburst 配置;内置 IP 兜底(阿里/腾讯/Cloudflare)→ DNS 名兜底;
  单次 15 s 看门狗、默认路由轮询、1 h 短路、进程锁、失败静默。
  成功严格校时后 hwclock -w -u 回写 RTC(RTC 存 UTC,必须 -u)。
- /etc/init.d/S45meow_ntpsync:开机只 setsid 后台拉起并立即返回
  (关键路径 +0 秒;后台最长约 150 s 自行退出),失败不阻断 init。
- /usr/share/udhcpc/default.script.d/50meow_ntpsync:只在 bound/renew
  触发(设备上唯一可靠的「网络就绪」事件),后台拉起后立即 exit 0;
  有 $ntpsrv(option 42)就优先用。基线 default.script 本就有 HOOK_DIR
  循环,目录原本为空。
- tests/run_tests.sh:宿主沙箱单测(假 ntpd/hwclock/网络探测)。

验证:dash 与 busybox sh 各 sh -n 通过;单测两套 shell 各 23/23 通过;
真机(/tmp 下运行,未装系统目录)首跑 8 s rc=0、ntpd slew 成功、
RTC 由 1793940395 写成 1793940452(49 s 偏差被纠正)、hwclock -u -r 吻合;
二次运行 0 s 短路;-b -t 3 无网络 5 s 退出 rc=1;
DHCP 灌入不可达服务器 192.0.2.x 仍靠 IP 兜底在 ~10 s 内成功。
2026-11-06 12:49:13 +08:00

163 lines
5.4 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
#
# run_tests.sh — meow_ntpsync 宿主沙箱单测(M22)
#
# 不碰真机、不联网:用假的 ntpd / hwclock / 网络探测注入,
# 验证「成功 / 超时 / 失败 / 网络未就绪 / 短路 / 返回码」这些分支。
#
# 用法: sh patch_v2/ntpsync/tests/run_tests.sh [meow_ntpsync 路径]
#
set -u
HERE=$(cd "$(dirname "$0")" && pwd)
SUT=${1:-"$HERE/../meow_ntpsync"}
W=$(mktemp -d)
trap 'rm -rf "$W"' EXIT
PASS=0
FAIL=0
ok() { PASS=$((PASS + 1)); printf ' ok %s\n' "$1"; }
bad() { FAIL=$((FAIL + 1)); printf ' FAIL %s\n' "$1"; }
check(){ if [ "$2" = "$3" ]; then ok "$1"; else bad "$1 (want=$3 got=$2)"; fi; }
# ---- 假 ntpd:行为由 MODE 决定 ----
cat >"$W/ntpd" <<'EOF'
#!/bin/sh
echo "invoked $(date +%s)" >> "$FAKE_NTPD_CALLS"
# 断言调用方必须给 -c <临时配置>
case " $* " in *" -c "*) : ;; *) echo "no -c" >&2; exit 9 ;; esac
case "$FAKE_NTPD_MODE" in
ok) echo "ntpd: time set +1.000000 s"; exit 0 ;;
fail) echo "ntpd: no server suitable" >&2; exit 3 ;;
hang) sleep 30; exit 0 ;;
slew) echo "ntpd: time slew +0.000042 s"; exit 0 ;;
esac
exit 1
EOF
chmod +x "$W/ntpd"
# ---- 假 hwclock ----
cat >"$W/hwclock" <<'EOF'
#!/bin/sh
echo "$*" >> "$FAKE_HWCLOCK_CALLS"
[ "$FAKE_HWCLOCK_MODE" = fail ] && exit 1
exit 0
EOF
chmod +x "$W/hwclock"
# 每种模式都跑在一个干净的沙箱里
run_case() { # $1=名字 其余=meow_ntpsync 参数;通过环境变量控制
name=$1
shift
CALLS="$W/calls.$name"
HW="$W/hw.$name"
: >"$CALLS"
: >"$HW"
export FAKE_NTPD_CALLS="$CALLS" FAKE_HWCLOCK_CALLS="$HW"
export MEOW_NTPD="$W/ntpd" MEOW_HWCLOCK="$W/hwclock"
export MEOW_NTPSYNC_STATE="$W/state.$name" MEOW_NTPSYNC_LOCK="$W/lock.$name"
export MEOW_NTPSYNC_LOG="$W/log.$name"
export MEOW_NTPSYNC_ATTEMPT_TIMEOUT="${CASE_TIMEOUT:-2}"
}
echo "== meow_ntpsync 沙箱单测 ($SUT)"
# 1) 正常同步
rm -f "$W/state.1"
run_case 1
FAKE_NTPD_MODE=ok MEOW_NETCHECK=true sh "$SUT"; rc=$?
check "同步成功返回 0" "$rc" 0
check "hwclock 被调用一次" "$(wc -l <"$W/hw.1" | tr -d ' ')" 1
check "hwclock 参数为 -w -u" "$(cat "$W/hw.1")" "-w -u"
check "写了 state(epoch)" "$(grep -c '^epoch=' "$W/state.1" 2>/dev/null || echo 0)" 1
# 1b) 单独验证 gen_conf 的内容(截获 -c 指向的文件)
cat >"$W/ntpd2" <<'EOF'
#!/bin/sh
while [ $# -gt 0 ]; do [ "$1" = "-c" ] && cp "$2" "$FAKE_CONF_DUMP"; shift; done
exit 0
EOF
chmod +x "$W/ntpd2"
rm -f "$W/state.1b"
run_case 1b
FAKE_CONF_DUMP="$W/conf.1b" MEOW_NTPD="$W/ntpd2" FAKE_NTPD_MODE=ok MEOW_NETCHECK=true sh "$SUT"
check "配置每个 server 都带 iburst" \
"$(grep -c '^server .* iburst$' "$W/conf.1b" 2>/dev/null)" \
"$(grep -c '^server ' "$W/conf.1b" 2>/dev/null)"
# 2) ntpd 挂死 → 看门狗必须杀掉并返回 1
run_case 2
t0=$(date +%s)
FAKE_NTPD_MODE=hang MEOW_NETCHECK=true sh "$SUT"; rc=$?
t1=$(date +%s); el=$((t1 - t0))
check "挂死返回 1" "$rc" 1
if [ "$el" -ge 2 ] && [ "$el" -le 6 ]; then ok "看门狗在 2~6s 内掐断 (实测 ${el}s)"
else bad "看门狗时长异常 (实测 ${el}s, 期望 2~6s)"; fi
check "挂死不写 RTC" "$(wc -l <"$W/hw.2" | tr -d ' ')" 0
# 3) ntpd 明确失败 → 返回 1,不写 RTC
run_case 3
FAKE_NTPD_MODE=fail MEOW_NETCHECK=true sh "$SUT"; rc=$?
check "失败返回 1" "$rc" 1
check "失败不写 RTC" "$(wc -l <"$W/hw.3" | tr -d ' ')" 0
# 4) 单次模式网络未就绪 → 立刻返回 1,且不去打 ntpd
run_case 4
t0=$(date +%s)
FAKE_NTPD_MODE=ok MEOW_NETCHECK=false sh "$SUT"; rc=$?
t1=$(date +%s); el=$((t1 - t0))
check "网络未就绪返回 1" "$rc" 1
check "网络未就绪不调 ntpd" "$(wc -l <"$W/calls.4" | tr -d ' ')" 0
if [ "$el" -le 2 ]; then ok "单次模式不做无谓等待 (${el}s)"; else bad "单次模式等了 ${el}s"; fi
# 5) 开机模式:网络一直不来,-t 3 → 按时退出
run_case 5
t0=$(date +%s)
FAKE_NTPD_MODE=ok MEOW_NETCHECK=false sh "$SUT" -b -t 3; rc=$?
t1=$(date +%s); el=$((t1 - t0))
check "开机模式超时返回 1" "$rc" 1
if [ "$el" -ge 3 ] && [ "$el" -le 8 ]; then ok "开机模式在 -t 内退出 (${el}s)"
else bad "开机模式退出时长异常 (${el}s)"; fi
# 6) 开机模式:网络晚到 → 仍能同步成功
cat >"$W/latecheck" <<'EOF'
#!/bin/sh
n=$(cat "$LATE_FILE" 2>/dev/null || echo 0)
n=$((n + 1)); echo "$n" >"$LATE_FILE"
[ "$n" -gt 2 ]
EOF
chmod +x "$W/latecheck"
rm -f "$W/late.n" "$W/state.6"
run_case 6
LATE_FILE="$W/late.n" FAKE_NTPD_MODE=ok MEOW_NETCHECK="$W/latecheck" sh "$SUT" -b -t 20; rc=$?
check "网络晚到仍返回 0" "$rc" 0
# 7) 非法参数
sh "$SUT" --bogus >/dev/null 2>&1; check "未知参数返回 2" "$?" 2
sh "$SUT" -t abc >/dev/null 2>&1; check "非数字 -t 返回 2" "$?" 2
# 8) 1h 内已同步 → 短路,不调 ntpd
run_case 8
echo "epoch=$(date +%s)" >"$W/state.8"
FAKE_NTPD_MODE=ok MEOW_NETCHECK=true sh "$SUT"; rc=$?
check "已同步短路返回 0" "$rc" 0
check "短路不调 ntpd" "$(wc -l <"$W/calls.8" | tr -d ' ')" 0
# 9) RTC 写失败:时间已对,返回 0,但日志要留痕
rm -f "$W/state.9"
run_case 9
FAKE_NTPD_MODE=slew FAKE_HWCLOCK_MODE=fail MEOW_NETCHECK=true sh "$SUT"; rc=$?
check "RTC 写失败仍返回 0" "$rc" 0
check "日志记录 RTC 写失败" "$(grep -c 'rtc write FAILED' "$W/log.9" 2>/dev/null)" 1
# 10) -S 打印状态
run_case 10
echo "epoch=12345" >"$W/state.10"
out=$(sh "$SUT" -S)
check "-S 打印上次记录" "$(echo "$out" | grep -c '^epoch=12345$')" 1
echo
echo "== 结果: PASS=$PASS FAIL=$FAIL"
[ "$FAIL" -eq 0 ] || exit 1