fix(测试工具): FTP 脚本与 @test 抓取改用设备鉴权账号(修 11 天假绿)

设备 FTP 自鉴权落地起就**没有匿名通道**(实测 `USER anonymous` → 530、`admin`+错口令 →
530、`admin`/`admin` → 230;`lftpd` 源码里也没有 anonymous 特判),但三个老主机脚本、
`@test` 的自检记录抓取、以及测试指南都还写着匿名——同目录的 `ftp_auth_bench.py` /
`ftp_abort_probe.py` 反而早就记着"匿名一律 530",可见当时只改了新增件。

**改动**:
· `test/host/ftp_test.py`、`ftp_up_dl_test.py`、`ftp_speed_test.py`:写死的
  `USER anonymous` / `PASS test@test.com` 改为 `--user` / `--password`(默认出厂
  admin/admin);**USER 的期望码从 230 修成"230 或 331"**——要口令的服务器在 USER 后
  回 331,脚本原先要求 USER 直接给 230,这是第二个 bug(就算口令对也过不了);登录
  失败时打印口径提示(`/config.ini` 的 `[ftp]`、无匿名通道、连续 5 次失败锁 60 秒)。
· `.dsh/skills/serial-boot-test/scripts/run_boot_test.py`:`ftp.login()`(匿名)→
  `ftp.login(user, pass)`,新增 `--ftp-user/--ftp-pass` 与 config 的 `ftp_user`/
  `ftp_pass`(默认 admin);函数文档与 `--device-ip` 的 help 去掉 "(anonymous)"。
· `docs/测试说明/FTP_Test_Guide.md`:§1 的"匿名登录(`USER anonymous` / `PASS` 任意)"
  改为鉴权口径(`user` / `pwd_hash` = SHA-256(user‖password) / `write`,出厂
  admin/admin,默认口令在用有启动告警,5 次失败锁 60 秒,**无匿名通道**),§4 参数说明
  补两个新参数,§4.1 期望改为 `USER admin`→331、`PASS admin`→230,FileZilla 的用户
  由 `anonymous` 改 `admin`。

**这才是真正的危害**:`@test --require-selftest-pass` 的抓取走匿名登录 → 必然 530 → 落
进 `RUN_TEST_WARN … treated as no record (S7 PASS path)` → **每次都判 PASS**。也就是
说这条"联合判定自检记录"的门禁从写下那天起就没真正看过文件(假绿)。修完凭据后第一次
真抓到 `/log/selftest.log`(1856 B),立刻报出 29 条故障记录——而它们的**时间戳全是
2026-10-10 ~ 10-14**,没有一条来自本次启动:`/log/selftest.log` 是 512 条环形累计历史,
而 S7 的判据是"有任何 64B 记录就 FAIL" → **在任何做过故障注入或长跑的设备上,这条
门禁都不可能 PASS**。判据本身要改成"只看本次启动新增",已单独提出,不在本笔。

**验证(全部真机实跑)**:`ftp_up_dl_test.py` → TEST PASSED(10 项全过,含 STOR/LIST/
RETR/逐字节比对/DELE);`ftp_speed_test.py --size 128` → TEST PASSED(上行 225.8 KB/s、
下行 250.9 KB/s,131,072 B 逐字节一致);`@test` → PASS(625 ms 到 `net init: OK`);
`@test --require-selftest-pass --device-ip 192.168.1.100` →
`SELFTEST_FETCH: … (1856 bytes)`,抓取路径真跑通(旧代码在此步必 530)。
`py_compile` 四个脚本全过;`check_doc_links.py --all --sections-strict` 146 文档 0 问题;
`check_ids.py --strict` 增量拦截通过;5 个文件字节卫生 BOM 无 / CR 0 / U+FFFD 0 /
无行尾空格。

**顺带暴露、未在本笔处理**:① `ftp_test.py` 的 `LIST 150->226` 断言失败——lftpd 的 LIST
数据本身正确(1512 B 全收到),但传输结束后控制通道回的是 **550** 而不是 226,需单独查
收尾路径;② 设备 `/` 根目录残留约 24 个旧探针文件(AB*/E12A/SZ*/A000x/R000x/DIAG_A);
③ `MDK-ARM/` 下 4 个一次性探针脚本仍写匿名,现在必然登录不上。未推送。
This commit is contained in:
2026-10-22 21:39:25 +08:00
parent e7f28502bc
commit 15d01d4dc1
5 changed files with 80 additions and 28 deletions
@@ -240,8 +240,8 @@ def _prune_out_dir(out_dir, keep):
return removed
def _fetch_selftest_log(device_ip, out_dir, stamp):
"""从设备 FTP 抓取 /log/selftest.log(lftpd :21 匿名,根目录 = 卷根)。
def _fetch_selftest_log(device_ip, out_dir, stamp, ftp_user="admin", ftp_pass="admin"):
"""从设备 FTP 抓取 /log/selftest.log(lftpd :21 需鉴权,根目录 = 卷根)。
成功返回本地路径字符串;失败(网络不可达 / FTP 错误 / 设备侧文件缺失)返回 ""
(按 S7 语义视为无故障记录;此处打 WARN,调用方不判整体 FAIL)。
@@ -256,7 +256,9 @@ def _fetch_selftest_log(device_ip, out_dir, stamp):
ftp = ftplib.FTP()
try:
ftp.connect(device_ip, 21, timeout=10)
ftp.login() # 匿名(lftpd 接受 USER anonymous / 空 PASS)
# lftpd 按 /config.ini 的 [ftp] 校验账号(user / pwd_hash),出厂 admin/admin;
# 设备没有匿名通道——匿名登录一律 530,所以这里必须带凭据。
ftp.login(ftp_user, ftp_pass)
except (OSError, ftplib.Error) as e:
print("RUN_TEST_WARN: selftest fetch failed: {} "
"(按 S7 语义视为无记录;设备网络/FTP 需另行排查)".format(e))
@@ -315,7 +317,11 @@ def main(argv=None):
ap.add_argument("--selftest-log", default=None,
help="local copy of /log/selftest.log (skips device-side fetch when given)")
ap.add_argument("--device-ip", default=None,
help="device IP for fetching /log/selftest.log via FTP (anonymous)")
help="device IP for fetching /log/selftest.log via FTP")
ap.add_argument("--ftp-user", default=None,
help="FTP user for the selftest fetch (config 'ftp_user'; default admin)")
ap.add_argument("--ftp-pass", default=None,
help="FTP password for the selftest fetch (config 'ftp_pass'; default admin)")
ap.add_argument("--out-dir", default=None)
ap.add_argument("--show-tail-lines", type=int, default=None)
ap.add_argument("--fw-hex", default=None,
@@ -388,6 +394,8 @@ def main(argv=None):
"require_selftest_pass", default=False))
args.selftest_log = str(_pick(args.selftest_log, "selftest_log", default="") or "")
args.device_ip = str(_pick(args.device_ip, "device_ip", default="") or "")
args.ftp_user = str(_pick(args.ftp_user, "ftp_user", default="admin") or "admin")
args.ftp_pass = str(_pick(args.ftp_pass, "ftp_pass", default="admin") or "admin")
args.out_dir = str(_pick(args.out_dir, "out_dir",
default=str(Path(tempfile.gettempdir()) / "serial-boot-test")))
args.show_tail_lines = int(_pick(args.show_tail_lines, "show_tail_lines", default=15))
@@ -596,7 +604,8 @@ def main(argv=None):
print("RUN_TEST_WARN: require_selftest_pass but no device_ip/selftest_log "
"-> treated as no record (S7 PASS path)")
if args.require_selftest_pass and not selftest_local and args.device_ip:
selftest_local = _fetch_selftest_log(args.device_ip, out_dir, stamp)
selftest_local = _fetch_selftest_log(args.device_ip, out_dir, stamp,
args.ftp_user, args.ftp_pass)
# 6. 标记校验(含固件身份戳、启动锚点计数、可选的自检记录联合判定)
# 启动锚点(config "boot_marker",如本仓库的 'reset cause:')未配置 =