6bdf063f50
新增 test/host/lftpd_io/:一个 TU 同时纳入生产源 lftpd_io.c 与 lftpd_string.c (两者静态符号不冲突,省一套桩),33 条断言全部 MSVC 编译通过并 PASS。 钉住的契约(都是真机上偶发、日志看不出根因的那类): - 路径规范化:**".." 上跳过头仍被钳在根内**("/a" + "../../x" = "/x")——FTP 客户端 可发任意路径,这是安全属性;绝对名忽略 base、"." 丢弃、段间多余斜杠折叠、 NULL 入参容忍、全空结果归 "/"。 - 分配失败:这个函数返回 pvPortMalloc 出来的指针(历史上正是它落在 C 库 malloc 上, RAM 方案 §4.2.1)。用**可注入失败的假堆**跑两条分支:首块失败 → NULL;结果缓冲 失败 → NULL **且第一块已 vPortFree**(不泄漏堆)。模拟器宿主 CRT 永不失败,这条 只有假堆能构造。 - 卷锁时机:open 成功**故意不释放**(整个传输期独占卷,由 close 释放),失败必须 释放并留 ERROR;单文件槽守卫 s_file_open 命中时**不碰卷锁也不碰 FatFs**。 - 等卷锁循环每轮喂看门狗(lftpd_task_wd_beat):defaultTask 与 FTP 争卷时这里可能 空转上百毫秒,漏喂就是整机复位。脚本化 vol_lock_wait 的返回序列直接构造 2 轮空等。 - lftpd_string_trim:行首空白由**返回指针**跳过、原缓冲不搬移(调用点 lftpd.c 用的 正是返回值);全空白串返回空串。 **本批照出一处,未改语义、只登记**:lftpd_string_trim 的尾部循环写成 for (i = strlen(p); i >= 0 && isspace(p[i]); i--),i 起步那格是 NUL、isspace(0) 为假 → 循环一次都不执行,**尾部空白与 CRLF 从未被裁掉**。今天不发作是因为 CRLF 在 lftpd_inet_read_line 里已用 strstr 截断、且唯一调用点只关心行首;风险在于"以后有人 拿它去尾部空白"不会生效。修法只要一行(i 从 strlen(p)-1 起),但那会改变 FTP 参数 (含空格的文件名/口令)的可见行为,需要 FTP 域一起过,**留给用户裁定**;当前用 TC-LFTSTR-005 把现状钉成"改了会响"(明确标注是现状桩、不是期望行为)。 未覆盖也写在文件头:lftpd_io_read/write/close/stat/unlink/opendir/readdir/closedir 要先把假 FIL/FILINFO 的字段语义(fptr、obj.objsize、fname 等)做实,否则断言会落在 假件自己写的规则上;这 8 个 f_* 只给了可链接的 FR_INT_ERR 实现,走进即失败不静默。 覆盖率:套件 20 → 21 全绿;files_tested 23 → 25;未触及 .c 43 → 41;分母 3,163 → 3,332 行;lftpd_io.c 85/159(53.5%)、lftpd_string.c 8/10。总体 line_rate 88.9% → 87.2% —— **这是摊薄不是回退**(新进来的文件只测了一半,分母涨得比分子快),已按 §5 口径写进口径文档 §9 的落地进度表;恒等式更新为 41 = 7(P1 剩) + 5(P2) + 29(P3)。 编号:按 B 层口径补建 2 族 TC-HOST-LFTPIO(游标 037)与 TC-HOST-LFTSTR(005), TC-HOST 族现共 **15** 族;id_registry.json 仍只做增量插入(无重排)。 自查:test-cppcheck Passed;coding-standard Passed;check_ids --strict 通过(无台账外 新账);check_doc_links --all = 35 处(= 存量,本分支贡献 0);新增/改动文件 BOM/CR/ U+FFFD/行尾卫生逐项净。**本增量零生产代码改动**(只加测试、注册表与文档),故未跑 @build。pre-commit --all-files 仍只有三条失败且与本分支无关:mem-numbers / mem-budget (本 worktree 上次 @build 生成的 .map 与受管快照漂移,增量 ER_IROM1 +12B、RW_IRAM1 +8B、CCM 0,按并行分支约定不在分支里 --write,等 Lead 合并后统一回填)与 ui-golden (simulator/tests/ref/*.bin 在 .gitignore 内,新 worktree 天生没有黄金参考)。
64 lines
3.2 KiB
Python
64 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""FTP 文件 IO 层(lftpd_io + lftpd_string)的主机侧单元测试运行器(L0.5,纯标准库)。
|
||
|
||
做什么:
|
||
用本机 MSVC 编译 test/host/lftpd_io/lftpd_io_test.c(同时纳入生产源
|
||
Drivers/BSP/NET/lftpd/lftpd_io.c 与 lftpd_string.c),在 PC 上离线验证路径规范化
|
||
(含 ".." 越权钳位)、FatFs 路径映射、单文件槽守卫、卷锁获取/释放时机、等锁期间
|
||
喂狗、分配失败返回 NULL 且不泄漏,以及 FTP 命令行 trim。失败即非 0 退出。
|
||
工具链与判定委托 test/host/common/unit_test_harness.py。
|
||
|
||
为什么要主机侧单测(而不是只用台架):
|
||
越权钳位是安全属性(FTP 客户端可发任意带 ".." 的路径),真机上没有第二个文件系统
|
||
可逃、测不出来;卷锁"成功不释放、失败必释放"写反了就是 defaultTask 永远拿不到卷
|
||
或传输中途卷被人改;等锁循环漏喂狗就是整机复位——三条都只在真机偶发、日志看不出
|
||
根因。分配失败分支更只有假堆能构造(模拟器宿主 CRT 永不失败)。
|
||
|
||
桩的边界:ff.h / vol_lock.h / lftpd.h / private/*.h 全用**真实头**(FRESULT、FA_*、
|
||
函数签名一律原汁);FreeRTOS.h 与 dbg_log.h 用本目录桩;f_open、卷锁、喂狗、
|
||
pvPortMalloc/vPortFree 由测试单元给可控实现(假堆可注入分配失败)。
|
||
read/write/close/stat/unlink/readdir 未列入本批:要先把假 FIL/FILINFO 的字段语义
|
||
做实,否则断言落在假件自己写的规则上(详见测试单元文件头"未覆盖")。
|
||
|
||
用法(仓库根目录):
|
||
python test/host/lftpd_io/run_unit_test.py
|
||
|
||
退出码:0=PASS / 1=测试失败 / 2=环境错误。
|
||
|
||
设计依据:docs/内存管理/RAM使用分析与优化方案.md §4.2.1、docs/测试说明/主机单测覆盖率口径.md §9 P1
|
||
创建日期:2026-10-23
|
||
"""
|
||
import os
|
||
import sys
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||
|
||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||
|
||
# 顺序敏感:stub 必须最先(覆盖 FreeRTOS.h / dbg_log.h)。
|
||
# lftpd_io.c 里 include 的是 "private/lftpd_io.h" 与 "lftpd.h",故给出 lftpd 目录;
|
||
# vol_lock.h 在 App/fs,ff.h 在 Lib/FatFs(与 test/host/nand_port 同一份真实头)。
|
||
BUILD = TestBuild(
|
||
name="LFTPD IO Tests",
|
||
src=os.path.join(HERE, "lftpd_io_test.c"),
|
||
include_dirs=[
|
||
os.path.join(HERE, "stub"),
|
||
os.path.join(ROOT, "Drivers", "BSP", "NET", "lftpd"),
|
||
os.path.join(ROOT, "App", "fs"),
|
||
os.path.join(ROOT, "Lib", "FatFs"),
|
||
HERE,
|
||
],
|
||
# /D_CRT_SECURE_NO_WARNINGS:生产 lftpd_io.c 里的 strcpy/strcat/strncpy 在
|
||
# MSVC 下刷 C4996 弃用告警(ARMCC 不报,且 lftpd 不在 coding-standard 范围内),
|
||
# 这是纯主机侧噪音;关掉它才能让本模块的编译输出保持可读。
|
||
extra_flags=["/D_CRT_SECURE_NO_WARNINGS"],
|
||
summary_marker="=== LFTPD IO Tests:",
|
||
)
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(run_host_unit_test(BUILD))
|
||
|