#!/usr/bin/env python3 # -*- coding: utf-8 -*- """FTP 文件 IO 层(lftpd_io + lftpd_string)的主机侧纯逻辑单元测试运行器(纯标准库)。 做什么: 用本机 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))