真机实测(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 内成功。
163 lines
5.4 KiB
Bash
163 lines
5.4 KiB
Bash
#!/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
|