取值依据是实测水位,不是预估:菜单根框 5,368 B、开页-关页往返
5,448 B、最重页面峰值 6,568 B(占 16 KB 的 40%)、真机开机
used=3,192 B。注意 16,384 B 建池后 lv_mem_monitor 报的 total 是
15,020 B(TLSF 控制块与对齐元数据 ≈1,364 B),核算余量用后者。
两端 lv_conf.h 同步改值(lv-conf-sync 生效值比对 163 项 PASS),
Lib/lv_conf.h 的 UTF-8 BOM 已按原样补回(Edit 会静默剥掉)。
验证:
- @build 0 错 0 警;ZI 154,456 → 129,880,精确 −24,576 B
- SRAM1 98,144/74.9% → 73,568/56.1%(余 57,504 B);CCM 不变 87.9%
- 模拟器菜单回归 PASS(8 状态像素 + 池基线 5,368 B 未变);11 页
黄金参考逐字节 10/11 一致,往返池水位 5,448 B 分毫未变
- 唯一差异 netcfg_save 保存态:用 40 KB 旧池做 A/B 复测同样失败,
确认与本改动无关(属 506eccc 的 cfg_file 行为变化),未刷黄金
参考掩盖,留给该改动属主处置
- @test PASS:fw build Oct 15 2026 07:31:34,无 [ERR]/Fault,
开机 622 ms 到 net init: OK,menu build done: used=3192
- 本地 CI 全绿(L0 + L0.5,15 runner / 955 断言)
- 台账受管区与内存快照区已 --write 回填,mem-budget / mem-numbers
由红转绿
连带结论:R-2 后半待迁体量 54,146 → 29,570 B,瓶颈由 SRAM1 换到
CCM(只剩 7,904 B);解锁链第 ① 步完成,下一步只差缩堆 56→32 KB
(B5 是反向扩容,缩回前须重跑 stack_report.py + 上板 min_ever)。
文档同步 20 处:本报告按修复即销项删除 R-9 行,并改写 §0.2/§0.3/
§0.4/§5.1/§5.2/§5.3/§6.4 与变更记录、去处表;RAM使用分析 §2.6.2
新增实测水位表、阶段 1.1 标已执行;台账 §3 解锁链、登记簿 N-27、
任务规划 README W3 行与链路图;UI 四篇、产品需求、内存内容分配
策略、运行与自检记录、项目部署环境要求、测试开发标准流程、
simulator/README、ui-verify skill 常量。顺带订正三处与脚本不符的
旧值:池水位常量 4784/4864 → 5368/5448、FreeRTOS 堆 48 → 56 KB。
46 KiB
测试开发标准流程(STM32F4-Base)
定位:本项目测试开发的总流程,覆盖"规划 → 板端套件 → 主机侧工具 → 验证门禁 → 提交沉淀"全链路,并给出未来测试设计的约束。 各环节的具体操作见下表对应文档,本文只讲流程、门禁与约定,不重复其内容。
关联文档
| 文档 | 内容 |
|---|---|
docs/测试说明/自动化测试流程.md |
@test 闭环细节(命令 / 标记 / 失败模式) |
docs/测试说明/测试文档模板规范.md |
测试文档模板、命名规则(TM / TC / DP) |
docs/测试说明/串口助手设计说明.md |
串口调试 / 采集工具与陷阱(Python 串口助手) |
docs/测试说明/串口助手使用指南.md |
串口助手命令用法 / 工作流 / 排障 |
docs/UI设计/UI_自动化测试流程.md |
UI 模拟器回归(ui-verify)细节 |
docs/代码规范/pre-commit使用说明.md |
pre-commit 门禁配置与范围 |
docs/环境与部署/项目部署环境要求.md |
构建 / 测试工具链环境 |
docs/CI/README.md |
远程 CI(Gitea Actions)唯一入口:服务端 / runner 部署与保活 / workflow / 排障(仅本机一台 runner,日常开发不必读) |
docs/测试说明/<模块>_Test_Guide.md |
各模块测试规范(CH395F / GD5F / SD2506 / FTP) |
1. 测试分层与门禁总览
测试体系分四层,门禁由便宜到昂贵依次布防,改动须过对应门禁方可提交:
| 层 | 名称 | 载体 | 抓住什么 |
|---|---|---|---|
| L0 | 静态门禁 | pre-commit 钩子(cppcheck / 代码规范 / 乱码 / 空白 / gitlint)+ @build |
编码规范、静态缺陷、任何编译警告 |
| L0.5 | 主机侧纯逻辑单测 | test/host/<模块>/run_unit_test.py + MSVC 离线编译真实源码(#include 纳入)+ 平台桩 |
纯逻辑模块的边界穷举(节流窗口、计数阈值、整数回绕)——板端无法覆盖、无需硬件 |
| L1 | 板端测试套件 | testTask + test/board/test_config.h 套件 |
驱动 / 协议功能(串口 [PASS]/[FAIL] + === ... PASSED 汇总) |
| L2 | 主机侧回归 | @test 串口日志标记 + LVGL 模拟器像素 / 池水位回归 |
启动序列、UI 渲染、LVGL 内存池水位 |
| L3 | 人工目检 | 上机屏幕、PC 端交互工具 | 视觉内容、自动化无法覆盖的场景 |
L0.5 与 L2 的区别:L2 的模拟器回归依赖 SDL / LVGL 与图形输出;L0.5 是纯计算单测 (无窗口、无硬件、秒级),专测"判定逻辑"。目前仅看门狗模块使用,见
看门狗单元测试说明.md。适格对象:算法 / 状态机 / 协议编解码 / 参数校验等无副作用的模块。CI:L0 + L0.5 已上 CI(Gitea Actions,
.gitea/workflows/ci.yml,2026-10-01 T-6); L1/L2/L3 需硬件,只在本地。本地等价命令scripts/ci_local.py。详见 §9。
1.1 门禁决策表(改动类型 → 必跑门禁)
| 改动类型 | 必跑门禁 |
|---|---|
| 任何源码提交 | G0 pre-commit(提交时自动触发)+ G1 @build |
| App / Drivers 功能代码(板端行为可观测) | G0 + G1 + G2 @test(模块标记) |
| 有 L0.5 单测的模块(如看门狗) | G0(含该模块单测钩子,自动)+ G1 @build(+ 台架项另按需 G2) |
| UI(菜单 / 内容页 / 字体 / 样式 / 几何) | G0 + G1 + G2 + G3 ui-verify 五步 |
| UI 新增中文文案 | 另需 font-regen(字体重生成 + 回归),先于提交 |
test/ 套件或 PC 端工具代码 |
G1 + G2(套件标题标记 / 工具自测) |
| 仅文档 | G0 |
G0~G3 命令详见 §6 / §7;ui-verify 完整命令见 UI_自动化测试流程.md。
2. 测试规划(文档先行)
2.1 何时必须写测试文档
新驱动 / 新模块 / 新功能 / 新协议 / 新接口:动手写代码前先立 docs/测试说明/<模块>_Test_Guide.md,结构遵循 测试文档模板规范.md(概述含设计原则覆盖表 → 阶段元信息表 → 测试用例表 → 脚本参考 → 已知陷阱 → 附录矩阵)。
2.2 命名(与模板规范一致)
| 对象 | 格式 | 现用示例 |
|---|---|---|
| 阶段 | TM-{MOD}-{NN} |
TM-PHY-01 |
| 测试用例 | TC-{MOD}-{NNN} |
TC-NET-601(CH395F)、TC-GD5F-205、TC-STO-03、TC-RTC-002 |
| 设计原则 | DP-{NN} |
DP-01(单连接 socket) |
模块缩写 {MOD} 在新模块测试文档立文档时确定(2~4 个大写字母);现有:NET=CH395F 网络、GD5F、STO=存储、RTC、ADC、RS485。
2.3 三条铁律
- 先写判定标准,再写测试代码 —— 文档里先写"什么算过 / 什么算失败"(精确到返回值 / 日志字符串 / 像素参考),再实现,避免"测了但不知道算不算过"。
- 判定标准必须可机器判定 —— 必须能落到:串口日志标记(→
@test -Required)、返回值 / 寄存器值(→TEST_CHECK)、像素参考(→ 模拟器回归)、池水位(→ 基线常量)之一。无法机器判定的标准只能放 L3 人工层。 - 每条设计原则必须有对应用例 —— 覆盖表中每个 DP-xx 至少一个 TC;新功能设计时先定 DP,再设计 TC。
3. 板端测试套件(test/)
3.1 统一入口与套件互斥
所有测试套件经唯一入口调度:testTask(test/board/test_config.c 的 start_test_task()),按 test/board/test_config.h 选中的 TEST_SUITE_* 分发到套件运行函数:
flowchart TD
T["testTask: start_test_task()(test/board/test_config.c)"] -->|TEST_SUITE_CH395F| A["ch395f_test_run()<br/>硬件层 1~5 / NET 层 6~11"]
T -->|TEST_SUITE_STORAGE| B["storage_test_run()<br/>FTL + FatFS"]
T -->|TEST_SUITE_GD5F| C["gd5f_test_run()<br/>10 阶段"]
T -->|TEST_SUITE_RTC| D["sd2506_test_run()<br/>2 个阶段组"]
T -->|全部注释=产品构建| E["空闲(仅打印 suite=NONE)"]
- 套件互斥:一次构建最多启用一个
TEST_SUITE_*,否则#error(编译期门禁);全部注释 = 正常产品构建。 - 测试固件不得静默提交:
TEST_SUITE_*开关受 pre-commit 钩子test-suite-switch门禁(默认要求全部注释)。跑完套件回归务必改回产品构建再提交;确需提交测试构建时用ALLOW_TEST_BUILD=1显式放行(会打[WARN]横幅)。见 §7.1。 - main.c / freertos.c 不含任何测试逻辑 —— 测试代码只能出现在
test/。
| 套件 | 宏 | 文件 | 阶段组织 | 备注 |
|---|---|---|---|---|
| CH395F | TEST_SUITE_CH395F |
ch395f_test.h/.c + net_test_task.c |
硬件层 1 |
两层互斥(#error);阶段 2/3/4/5 与 6/7/8/10/11 需 PC 端配合 |
| STORAGE | TEST_SUITE_STORAGE |
storage_test.h + storage_test_task.c |
FTL + FatFS 集成(TC-STO-01~04) | 恢复测试(重建 BBT)默认关闭 |
| GD5F | TEST_SUITE_GD5F |
gd5f_test.h + gd5f_test_task.c |
10 阶段(选中时自动全开) | NAND 驱动全覆盖 |
| RTC | TEST_SUITE_RTC |
sd2506_test.h + sd2506_test_task.c |
阶段 1 基本 / 阶段 2 API(默认全开) | SD2506 RTC |
3.2 套件文件模式
每个套件两个文件,模式一致(参照 ch395f_test.h / storage_test.h):
xxx_test.h—— 阶段使能开关(取消注释启用)+ 本套件独立统计结构体 +XXX_TEST_CHECK/XXX_TEST_REPORT宏 +xxx_test_run()声明;xxx_test_task.c——xxx_test_run()入口 + 每阶段一个static phaseN_run()。
断言宏(语义统一,统计变量按套件独立,防跨套件串数):
内核集中在 test_common.h(2026-10-01 抽取,T-4)——4 套件的宏此前逐字重复,改一处要改四处:
/* test/board/test_common.h */
typedef struct { uint16_t total, passed, failed; } test_stats_t;
#define TEST_CHECK_IMPL(stats, cond, fmt, ...) do { \
(stats).total++; \
if (cond) { (stats).passed++; DBG_INFO("[PASS] " fmt, ##__VA_ARGS__); } \
else { (stats).failed++; DBG_ERROR("[FAIL] " fmt, ##__VA_ARGS__); } \
} while (0)
#define TEST_REPORT_IMPL(stats, name) do { \
DBG_INFO("=== %s: %u/%u PASSED (failed=%u) ===", name, ...); \
} while (0)
各套件用一行宏把它绑定到自己的统计变量(保持 g_xxx_stats 独立):
/* 套件头文件内(如 gd5f_test.h) */
#include "test_common.h"
typedef test_stats_t gd5f_test_stats_t;
extern gd5f_test_stats_t g_gd5f_test_stats;
#define GD5F_TEST_CHECK(cond, fmt, ...) \
TEST_CHECK_IMPL(g_gd5f_test_stats, cond, fmt, ##__VA_ARGS__)
#define GD5F_TEST_REPORT(name) TEST_REPORT_IMPL(g_gd5f_test_stats, name)
⚠️
test_common.h刻意不 includedbg_log.h:dbg_log.h在未定义DBG_TAG时给出 默认值"",而本头文件被套件头在很靠前的位置包含——若在此处拉入,会把空DBG_TAG冻结,导致.c随后#define DBG_TAG "[TEST_TASK]"触发 ARMCC#47-D: incompatible redefinition(实测 8 条警告)。宏只在使用点展开(使用点在已按约定先定义DBG_TAG的.c内),故无需在此引入。
每 Phase 用标准注释头(与文档用例一一对应):
/*
* Phase N — 功能说明
* 测试目的 / 前置条件 / 通过 / 失败(四行,与 Test_Guide 文档一致)
*/
static void phaseN_run(void) {
DBG_INFO("=== XXX Phase N Tests ===");
/* ---- TC-XXX-NNN: 用例标题 ---- */
{
// 原理说明;通过:精确条件;失败:精确条件
ret = some_api();
DBG_INFO(" expect: ...");
DBG_INFO(" actual: ...");
XXX_TEST_CHECK(ret == EXPECTED, "description");
}
XXX_TEST_REPORT("Phase N");
}
套件运行结束行的 === <名称> Tests: N/N PASSED (failed=0) === 即该套件 @test 回归的默认标记(各套件确切字符串见对应模块 Test_Guide,如 === Storage Tests: / === GD5F2GQ5UE Tests:)。
3.3 编码约定
| # | 约定 | 说明 |
|---|---|---|
| 1 | 判定点三行输出 | expect: / actual: / [PASS]/[FAIL],一眼看出"期望什么、拿了什么、过没过" |
| 2 | 硬编码值加注释 | 协议字节 / 寄存器 / 阈值必须注释含义与来源(手册页 / 协议条款) |
| 3 | 日志字符串纯 ASCII | ARMCC v5 不识别 UTF-8 多字节字符(#870-D 警告);注释可中文,DBG_* 字符串不可 |
| 4 | 大缓冲用 static | 大缓冲(64KB 级)static uint8_t s_rx_buf[N],不放任务栈 |
| 5 | 条件编译包裹变量 | 仅特定阶段使用的变量 / 函数用 #if 包裹,消除 #177-D 未使用变量警告 |
| 6 | 就绪等待用就绪屏障 | 任务间时序依赖 SYS_EVT_*(osEventFlagsWait),禁止魔法延时 |
注意:
test/在 pre-commit 检查范围之外(工具代码豁免)——上表约定属自律要求,但 0 错 0 警的构建红线对测试代码同样生效。
3.4 测试构建下的 netTask(2026-09-15 已修复 P1-6,未来测试设计必读)
代码事实(2026-09-15 修复后):
netTask= 基础线程:定义于.ioc→Src/freertos.c的 CubeMX 生成区无条件创建(全构建唯一实例)。产品 / NET 层测试模式下正常运行:等存储就绪(≤10s)→net_stack_bring_up()(CH395F 初始化)→ 置SYS_EVT_NET_READY→net_poll()轮询循环。USER CODE 区在→ 2026-09-15 已删除(P1-6):NET 层测试构建不再重复创建。ENABLE_NET_LAYER_TESTS下另建一个 netTask(双 netTask 并存)- CH395F 硬件层模式下
net_task_func入口自挂起(2026-09-15 守卫,ENABLE_HW_LAYER_TESTS):不 bring_up、不轮询、不广播 NET_READY;testTask 自行net_stack_bring_up()后独占 CH395F 跑各阶段。任务仍被创建(基础线程策略:恒存在、该模式"不运行"),占 4KB 栈——彻底方案(.ioc移除 + 条件创建)经评估未采用:网络线程是基础线程,仅测试模式"不运行"。
由此产生的设计约束:
- 硬件层模式:netTask 已挂起,无人消费/清除芯片全局中断标志——"中断状态寄存器全程 0x0000"类断言在该模式下安全(测试入口的预读清除仍须保留,用于清除
net_stack_bring_up自身强制 PHY 重协商锁存的PHY_CHANGE残留)。 - NET 层模式 / 产品构建:netTask 恒存在且在轮询(全局中断由它消费清除)——新增测试用例不得断言"无人动芯片/中断"。
- 硬件层模式下挂起的 netTask 仍占 4KB 栈,为基础线程策略的接受成本(彻底方案可回收,暂无计划)。
3.5 新测试文件 / 新套件 checklist
test/下新建xxx_test.h/xxx_test_task.c(按 §3.2 模式);test/board/test_config.h:补TEST_SUITE_XXX注释开关 + 互斥#error加项 + 分发分支(include +TEST_SUITE_NAME);test/board/test_config.c:start_test_task()加分发分支;- 双端注册核查(
new-source-fileskill):新.c必须加入MDK-ARM/STM32F407-Demo.uvprojx;test/ 代码仅 MCU 端编译,不登记模拟器 CMakeLists(UI / App 共用代码才需双端); @build→ 0 错 0 警;- 立
docs/测试说明/<模块>_Test_Guide.md(§2 模板); @test以套件标题行为-Required标记跑通回归。
3.6 多套件回归编排(test/run_all_suites.py,2026-10-01 加 T-5)
套件互斥(编译期 #error)→ 跑 4 个套件的回归本来要手工循环 4 轮
(改宏 → 构建 → 烧录 → 采集 → 判读)。本脚本编排成一条命令:
& '.venv\Scripts\python.exe' test/run_all_suites.py # 全部套件
& '.venv\Scripts\python.exe' test/run_all_suites.py --suites STORAGE,GD5F
& '.venv\Scripts\python.exe' test/run_all_suites.py --dry-run # 只看计划
& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build # 只编译不烧录(S6)
核心保障:try/finally 无条件还原 test_config.h(含异常 / KeyboardInterrupt
路径)——跑完忘还原 = 测试固件进库。这与提交期门禁 test-suite-switch(§7.1)
是两条独立防线:前者是流程自动化、后者是门禁兜底。
⚠️ 完整回归不挂 pre-commit:每轮真机烧录 + 采集(8s 静默 + boot),4 套件约数分钟起, 且破坏性套件会改设备持久状态。仅回归窗口手工使用。
3.6.1 --check-build:只编译不烧录(2026-10-02 加 S6)
解决的问题:测试套件的构建可编译性没有独立门禁——产品构建 @build 天天跑,
但测试构建可能长期"编不过"而无人知,直到某次要用才发现(2026-08~09 CH395F 套件
7 个配置实例)。
& '.venv\Scripts\python.exe' test/run_all_suites.py --check-build
| 项 | 说明 |
|---|---|
| 做什么 | 逐套件:改 test_config.h 宏 → @build → 记 PASS/FAIL;结束后无条件还原 |
| 不需要 | MCU / 串口 / ST-LINK(纯离机,可在 CI 或任何有 Keil 的机器上跑) |
| 判定 | @build 退出码 0 = PASS;非 0(含 L6406E 链接失败)= FAIL |
| RAM 溢出 | P1-9 类"编译过但链接失败"会自然暴露为 L6406E——这正是想要的 |
| 退出码 | 同完整模式:0=全 PASS / 1=有套件编不过 / 2=用法缺文件 |
2026-10-02 实测:4 套件(CH395F / STORAGE / GD5F / RTC)全部
BUILD_OK: 0 errors, 0 warnings——即 T-4 提到的"CH395F 套件不可编译"问题已修复,现已纳入可自检范围。
⚠️ 隐藏陷阱:
.map必须复位为产品态(2026-10-02 实测踩到)。脚本跑完最后一轮留下的STM32F407-Demo.map是测试套件的体积(RTC 套件下 ER_IROM1 达 266 KB), 会被mem-numbers钩子判为"文档快照与.map不一致"→ 下次提交直接 FAIL。 故脚本在还原test_config.h之后追加一次产品构建([..] 复位构建产物为产品态)—— 仅还原源码不够,必须再编一次。同理适用于完整回归模式。
建议用法:改动 test/board/ 下任何测试代码、或改 test_config.h 后,跑一次
--check-build 再提交;比逐个手工改宏构建省事,且不会忘记还原(含 .map 复位)。
4. 主机侧测试工具
4.1 工具清单
| 工具 | 位置 | 作用 |
|---|---|---|
| Python 串口助手(serial_tool) | .dsh/skills/serial-assistant/scripts/serial_tool.py(.venv + pyserial) |
常驻 daemon + 薄 CLI:列端口 / 开关 / 收发 / 全量日志 / wait-idle |
.dsh/skills/serial-boot-test/scripts/run_boot_test.py |
test/host/ |
一键闭环:采集 → 烧录 → 提取日志 → 校验 |
test/run_all_suites.py |
test/ |
多套件回归编排(T-5)+ --check-build 只编译不烧录(S6,纯离机,验证 4 套件可编译性);见 §3.6 |
.dsh/skills/serial-boot-test/scripts/verify_log.py |
test/host/ |
日志标记校验(Required / Forbidden → PASS / FAIL)+ 可选套件汇总行校验(T-2) |
test/host/common/unit_test_harness.py |
test/host/common/ |
L0.5 通用单测 harness:工具链探测 / INCLUDE-LIB 拼接 / 编译运行 / 退出码契约。各模块运行器只做声明式配置(T-3) |
test/host/watchdog/run_unit_test.py |
test/host/watchdog/ |
L0.5 主机侧单测:声明 TestBuild 后调 harness,穷举看门狗判定状态机(零硬件、秒级)。需本机装有 VS(cl.exe),见 §4.2 环境要求 |
test/host/sys_clock/run_unit_test.py |
test/host/sys_clock/ |
L0.5 主机侧单测:穷举 sys_clock 时间换算 / 星期 / 惰性再基准化 / RTC 故障降级(100 断言,S1-a) |
test/host/cfg_file/run_unit_test.py |
test/host/cfg_file/ |
L0.5 主机侧单测:INI 解析 / 值编解码 / 段注册 / load-save-poll 契约,内存虚拟 FS 驱动(139 断言,S1-b) |
scripts/ci_local.py |
scripts/ |
本地跑与 CI 等价的门禁(L0 + L0.5):自动选用装了 pre-commit 的解释器;.map 自适应跳过 mem-numbers;退出码 0/1/2 与 CI 对齐。见 §9.3 |
.serialtest/config.json |
仓库 | 串口端口单一配置源(当前 COM6 / 115200);换端口只改这个文件 |
test/host/ftp_*.py(4 个)/ ch395f_socket_test.py / time_sync_server.py |
test/host/ |
PC 端交互测试工具(FTP 客户端 / socket 回显 / 对时推送) |
simulator/tests/*.py(4 个) |
simulator/tests/ |
LVGL 模拟器回归脚本(见 §5) |
4.2 L0.5 主机侧单元测试(纯逻辑,无硬件)
适用对象:无副作用的纯逻辑模块——算法、状态机、协议编解码、参数校验等 (例:看门狗的位图判定 / 掩码 / 计数阈值 / 整数回绕)。
做法(以看门狗为范式,见 看门狗单元测试说明.md):
- 模块的硬件访问收敛到薄平台层(
__weak钩子或后端函数)——参照App/cfg/cfg_file_store.h的"共享核心 + 平台后端"; - 测试单元
#include生产源文件(保证测的是真代码,非副本),用桩替换平台层; - 写模块侧运行器:只声明
TestBuild(源码 / 桩目录 / 汇总行标记)+ 调test/host/common/unit_test_harness.py(工具链探测与编译运行细节在 harness 内, 见../../test/host/common/README.md); - 挂 pre-commit 钩子,被改动时自动跑(只 commit 不烧录,秒级)。
环境要求(详见 ../环境与部署/项目部署环境要求.md §2.3 / §2.7):
| 项 | 要求 | 说明 |
|---|---|---|
MSVC cl.exe |
装了 VS(2019/2022/2026)即有 | 与 LVGL 模拟器共用同一套 VS,无需另装;脚本按本机 VS/SDK 目录自动拼 INCLUDE/LIB 后直接调 cl.exe |
| Windows SDK | 随 VS 勾选"使用 C++ 的桌面开发"+ SDK | cl.exe 编 stdio.h 等标准头从这里取 |
| Python | 仓库 .venv 或任意 python3 |
运行器纯标准库,无需第三方依赖 |
| 硬件 | 无 | 不需要开发板 / 串口 / ST-LINK |
| 缺失时 | 运行器报环境错误(退出码 2)→ pre-commit 钩子拦截提交 | 未装 VS 的机器改动被测文件时会被拦,须先装 VS |
⚠ 不调
vcvars64.bat:它内部调用reg.exe探测环境,部分受限机器(安全策略禁reg.exe) 会失败 → 无INCLUDE→fatal error C1083: 无法打开包括文件 "stdio.h"。 运行器因此直接拼INCLUDE/LIB环境变量 + 绝对路径调cl.exe,无副作用、可重复。
约束:
- 平台层的抽取必须零行为变化、零额外开销(MCU 端默认实现即原逻辑;
@build须 0/0); - 测试专用钩子(如静态态复位)用
#ifdef <模块>_UNIT_TEST守卫,MCU 构建不编入(零符号); - 单测不能替代板端验证:真机寄存器 / 复位原因 / 时序仍须 L1/L2 台架闭环。
4.2.1 L0.5 适格模块台账(2026-10-02 加,S2)
问题:L0.5 起步阶段只有"看门狗"一个先例,下一个该测谁靠临时判断,易遗漏高价值模块。
判据(是否适格 L0.5):模块的核心逻辑是否无副作用且与硬件解耦—— 满足"纯逻辑 + 已抽象平台层(或依赖全是外部符号/宏)"即可主机侧穷举。
| 模块 | 规模 | 状态 | 平台层形态 | 备注 |
|---|---|---|---|---|
App/util/task_watchdog.c |
— | ✅ 已覆盖(39 断言) | 内联 __weak 钩子 |
范式来源;#ifdef TASK_WATCHDOG_UNIT_TEST 复位钩子 |
App/util/sys_clock.c |
479 行 | ✅ 已覆盖(100 断言,2026-10-02 S1-a) | 外部符号(HAL_GetTick/sd2506_*)+ 宏 |
零侵入(无需 #ifdef);首测即抓出 Zeller 星期缺陷 |
App/cfg/cfg_file.c |
1250 行 | ✅ 已覆盖(139 断言,2026-10-02 S1-b) | 后端抽象(cfg_file_store.h) |
内存虚拟 FS 桩;首测即抓出外科手术保存死码 P0(见 §5) |
App/log/sys_log_mcu.c |
— | ⬜ 候选 | 记录编解码(定长 64B) | 编解码纯逻辑;写入端依赖 FatFS + 队列,测编解码即可 |
App/util/time_sync.c |
— | ⬜ 候选 | 协议帧编解码 | 报文组装/解析纯逻辑;网络收发不测 |
Drivers/BSP/NETCFG/net_cfg_legacy.c |
— | ⛔ 已删除(2026-10-10) | — | 旧档 NCFG 迁移整体移除(旧档 /cfg/netcfg.bin 不读、不删、不迁移),测试候选随之撤销 |
App/protocol/iec104/*(104 协议栈) |
— | ✅ 已覆盖(82 断言,2026-10-12 B4) | 数据源 vtable + tick 注入 + 发送回调注入 | APCI 重组/总召分片/COV/k-w-t1~t3/两段式全离线穷举;iec104-unit-test 钩子 |
App/util/rs485.c |
— | ⏸ 待功能实现 | — | 上游 B2;当前为空桩 |
Drivers/BSP/TPAFE5160/*.c |
— | ❌ 不适格 | — | 寄存器序列为主,需真机时序 |
| 其余驱动(CH395F/GD5F2GQ5/SD2506) | — | ❌ 不适格 | — | 需硬件时序 / SPI / I2C 总线 |
App/util/crc.c · ringbuf.c |
120/154 行 | ⛔ 已删除(2026-10-02 S8) | — | 孤儿文件,零引用,已 git rm |
使用方式:新增/改动上述模块前,按表判断是否需补 L0.5;⬜ 候选 表示"适格但未做",
在性价比允许时应排入(判据:边界密集、零硬件、单次真机回归 30s2min vs 单测 25s)。
⭐ 两条实证:
sys_clock(S1-a)与cfg_file(S1-b)首次落地即各抓出一处 生产缺陷(Zeller 星期公式错误 / 外科手术保存死码)。这印证了本台账的性价比判断—— 高价值纯逻辑模块的 L0.5 覆盖,是投入最小、产出最大的测试改进方向。
4.3 新工具约定
- test/ 自动化脚本为纯 Python 标准库(2026-09-20 由 PowerShell 迁移:绕开控制台代码页/执行策略陷阱,全部 I/O 显式 UTF-8/字节级处理;
requirements.txt仍须纯 ASCII——pip 按 GBK 代码页解码); - Python 统一用仓库
.venv;第三方依赖必须进requirements.txt(纯 ASCII); - 落位:板端交互工具 →
test/;模拟器回归 →simulator/tests/; - 新工具引入新的"通过标记"时,必须登记到 §6.2 标记表,供
@test闭环复用。
5. UI 测试门禁(ui-verify)
触发条件:任何 Drivers/BSP/UI/ 下改动、字体(ui_font_cn_12.c / ui_cn_chars.txt)、菜单 / 内容页。
5.1 五步门禁(顺序执行,全过)
cmake --build simulator/build --config Debug # 0) 构建模拟器(须 0 警告)
.venv\Scripts\python.exe simulator/tests/check_menu_rows.py # 1) 菜单 8 状态像素回归 + 池基线
.venv\Scripts\python.exe simulator/tests/check_content_pages.py # 2) 内容页黄金参考 + 往返池水位
& '.venv\Scripts\python.exe' .dsh\skills\keil-build\scripts\keil_build.py # 3) Keil 构建(UI 源码双端共用,改一端必过另一端)
.venv\Scripts\python.exe scripts/check_coding_standard.py # 4) 代码规范机械检查
远显推流通道另按需跑 simulator/tests/check_remo_sink.py。
5.2 三层断言(两个回归脚本内置)
| 层 | 载体 | 抓住什么 |
|---|---|---|
| 文本层 | check_content_pages.py 字形模板匹配(50 条,解析字体 cmap / glyph_dsc / bitmap) |
缺字显示空白 / 标签写错等静默失败(不依赖 ref/,先于像素比对运行、失败即中止防误刷参考) |
| 像素层 | 11 内容页 + 菜单 8 状态,与黄金参考逐字节比对 | 几何 / 样式 / 光标反显 / 整体外观 |
| 池水位层 | MENU_POOL_BASELINE=5368 B / POOL_AFTER_ROUNDTRIP=5448 B |
LVGL 内存池泄漏 / 结构性漂移(量的是 used,与 LV_MEM_SIZE 总大小无关) |
5.3 黄金参考与字体纪律
simulator/tests/ref/(.bin参考 +.png目检图)不入库:首次 / 换机先check_content_pages.py --update生成;--update有误刷保护(先跑文本层断言,失败即中止)——未有意改视觉而 FAIL = bug,修代码,禁止刷参考掩盖;- 刷新参考后必须重跑
regen_ref_png.py重生成目检 PNG(否则旧 PNG 看似乱码); - 门禁常量漂移:先确认新值合理(残留分解见
菜单页面设计.md§9.5)→ 更新常量 + 在UI_自动化测试流程.md变更记录登记; - UI 新增中文文案提交前必须先跑 font-regen(extract_cn → make_font → diff 检查):字符集
ui_cn_chars.txt缺字则上机显示空白。
6. 自动化闭环(@test)
6.1 流程
flowchart TD
A[代码改完] --> B["G1 @build(0 错 0 警,否则修复重编)"]
B --> C["run_boot_test.py:烧录前武装采集(daemon 全量日志兜底)"]
C --> D["@download 烧录(Reset & Run 触发重启)"]
D --> E[提取日志 + verify_log 标记校验]
E -->|exit 0| F["PASS → G3(UI 改动)→ L3 上机目检 → 提交"]
E -->|exit 1 FAIL| G["[MISS]/[HIT] + 日志尾部定位 → 修代码"]
G --> B
E -->|exit 2/3 环境错误| H["按 自动化测试流程.md 失败模式表处理 → 重试"]
6.2 判定标记
默认:-Required "App init OK;Network Init;net init: OK" / -Forbidden "FAIL;ERROR;HardFault;assert"。
| 回归场景 | -Required 定制 |
|---|---|
| 网络 | App init OK;Network Init;net init: OK;PHY: 100M FULL |
| UI 初始化 | UI port init: OK;UI menu init: OK;UI net sink: started |
| 开机全量(存储 / RTC / RS485 / ADC) | 默认 + RS485 init: OK;NAND init:;ADC init:;SD2506 ID: |
| 测试套件构建 | 套件结尾汇总行(=== Storage Tests: / === GD5F2GQ5UE Tests: 等,确切字符串见各模块 Test_Guide) |
6.2.1 套件汇总行校验(2026-10-01 加,T-2)
⚠️ 纯 Required 子串匹配有盲区:套件启动横幅命中即判 PASS——套件是否跑到汇总行、 断言数是否为 0 完全不可见。套件中途挂死/复位、或 Phase 宏全关(没编入任何断言)时, 会被误判为通过。测试套件构建的回归必须加
--require-suite-summary。
# 测试套件构建回归(要求套件跑到汇总行,且断言数 > 0)
& '.venv\Scripts\python.exe' .dsh/skills/serial-boot-test/scripts/run_boot_test.py `
--required "=== Storage Tests:" --forbidden "ERROR;HardFault;[ERR]" `
--require-suite-summary "Storage Tests"
校验语义(verify_log.py):
| 情形 | 判定 |
|---|---|
汇总行存在、N>0、failed=0、N==M |
PASS(打印 [OK] suite summary ok) |
| 汇总行缺失(套件没跑到/挂死) | FAIL [MISS] |
total == 0(没编入任何断言) |
FAIL [FAIL] total is 0 |
failed != 0 |
FAIL [FAIL] reports failures |
passed != total |
FAIL [FAIL] inconsistent |
未传
--require-suite-summary时行为与旧版逐字一致(向后兼容,产品构建回归不受影响)。
6.3 三条铁律
- 采集必须先于烧录武装 —— 开串口 + clear 在烧录前完成(daemon 常驻全量日志兜底,不会漏字节;
run_boot_test.py已保证顺序); - 端口以
.serialtest/config.json为准 —— 每次连接前先读它; - 静默判据覆盖时间窗 ——
-SettleMs=8000(覆盖 boot ~3-4s 自然静默间隙)、-MaxWaitMs=30000(整体超时兜底);
7. 提交门禁与沉淀
7.1 提交门禁(G0,pre-commit)
提交被钩子门禁,任一失败即拦截:
| 钩子 | 内容 |
|---|---|
| trailing-whitespace / end-of-file-fixer / check-added-large-files / check-merge-conflict / check-yaml | 官方空白 / 文件尾 / 大文件 / 冲突 / YAML 检查 |
| gitlint | commit message 标题 ≤100 字符 |
| cppcheck | 静态分析(warning / performance / portability / style,含 suppress 清单) |
| no-mojibake | 禁止 U+FFFD 乱码字节(保护中文注释) |
| coding-standard | 代码规范 V1.0 机械检查(S1~S19) |
| test-suite-switch | 测试套件开关=产品构建(test/board/test_config.h 的 TEST_SUITE_* 须全注释;跑套件回归确需提交测试构建时用 --allow-test-build 显式放行) |
范围:仅项目自有代码受检;第三方与生成物(Lib/、Middlewares/、CMSIS/HAL、lftpd、dhara、MDK-ARM/、test/、CubeMX 生成的 Src/ Inc/ 等)被排除(详见 pre-commit使用说明.md)。
7.2 提交纪律
- 逻辑提交:一笔提交只做一件事(代码 / 文档 / 工具分开);
- 编码铁律(最高优先级):含中文文件只用 Edit 工具精确替换修改;禁止 PowerShell / cmd 整文件重写(U+FFFD 乱码不可逆);BOM 状态按原样保持(UI 相关源 /
lv_conf.h带 BOM,其余无 BOM); - 内存预算:新增任务 / 缓冲 / 栈前必须先对照 RAM 红线(当前 SRAM 占用 95.4%、仅余 ~5.8KB,见
docs/内存管理/)。
7.3 沉淀(必须)
| 事件 | 动作 |
|---|---|
| 测试发现陷阱(硬件 / 协议 / 工具链 / 时序) | 写入 docs/问题记录/<模块>_Trap_Records.md:现象 / 根因 / 修复 / 规避 |
| 代码变更影响判定标准 | 立即同步更新模块 Test_Guide,并登记该文档变更记录 |
| 有意改视觉 | 刷黄金参考 + 重生成目检 PNG + UI_自动化测试流程.md 变更记录登记 |
| 新增测试工具 / 新标记 | 自动化测试流程.md 命令清单登记(§4.2) |
| 栈 / 堆预算相关改动或基线重采 | 把 @test 的 boot_log 快照(%TEMP%\serial-boot-test\boot_log_*.txt)存入 docs/测试说明/boot_log_data/ 并在其 §3 清单登记(防临时目录被清理 / 长跑日志截头丢 [STACK] 行) |
文档与代码一体两面:代码改了文档没同步 = 未完成。
8. 迭代节奏(完整闭环)
flowchart LR
A["文档:规划 + 判定标准<br/>(Test_Guide)"] --> B["代码:套件 / 工具实现<br/>+ 双端注册核查"]
B --> C["G1 @build<br/>0 错 0 警"]
C --> D["G2 @test<br/>采集→烧录→校验"]
D -->|PASS| E["G3 ui-verify<br/>(UI 改动时)"]
E --> F["L3 上机目检"]
F --> G["提交<br/>G0 门禁 + 逻辑提交"]
G --> H["沉淀:陷阱记录 /<br/>文档同步 / 变更记录"]
D -->|FAIL| I["[MISS]/[HIT] 定位"]
I --> B
各模块测试迭代都走这个闭环:文档先行、门禁验收、沉淀回灌。
9. CI 持续集成(Gitea Actions,2026-10-01 加 T-6)
远程仓库为 Gitea 1.25.4(gitea.bitnasdaq.vip/wandering1/STM32F4-Base.git),支持 Gitea Actions
(兼容 GitHub Actions 语法,workflow 置于 .gitea/workflows/)。落地文件 .gitea/workflows/ci.yml。
9.1 覆盖范围:只做"不依赖硬件"的层
| 层 | 上 CI? | 载体 | 理由 |
|---|---|---|---|
| L0 静态门禁 | ✅ | pre-commit run --all-files(全部钩子) |
纯 Python + cppcheck,零硬件 |
| L0.5 主机侧纯逻辑单测 | ✅ | test/host/**/run_unit_test.py(MSVC 离线编译) |
纯标准库 + cl.exe,零硬件 |
| L1 板端套件 | ⛔ | test/board/ |
需真机 + 串口 + ST-LINK |
L2 @test 闭环 |
⛔ | 烧录 + 串口采集 | 同上 |
| L3 人工目检 | ⛔ | 上机屏幕 | 人的判断 |
明确不做的两件事(都是刻意为之):
- 不跑
@build(Keil UV4)——需本机 Keil 授权,runner 上不具备。L0 的 cppcheck + 代码规范 已覆盖"编译前的静态问题",真编译仍是开发者的本地红线(AGENTS.md)。 mem-numbers钩子按.map有无自适应跳过——它的事实源是MDK-ARM/**/*.map(Keil 构建产物,已.gitignore),无@build即无.map。故 workflow 在检测不到.map时自动跳过该钩子(SKIP=mem-numbers,钩子显示Skipped而非Passed—— 是"不适用",不是"通过");有.map的 runner(如自建带 Keil 的机器)则照常跑。
🔴 门禁盲区(S14,2026-10-02):跳过
mem-numbers意味着 CI 不守 Flash/RAM 体积门禁—— 新增任务/栈/缓冲/代码导致的体积增长不会在 CI 被拦。故该跳过以::warning::明示 (不是弱化的 notice)并写入 step-summary。兜底必须靠本地:改代码后@build(0/0) →python scripts/check_mem_numbers.py --scan。详见../CI/远程CI完整指南.md§0.0。
🔴
gitlint属commit-msgstage,--all-files永远跑不到它(2026-10-11 审计):.pre-commit-config.yaml定义 18 个钩子,而 CI 日志只报 17 个(passed=16 skipped=1)—— 缺的正是 gitlint。即"单行中文标题 ≤100 字符"这条门禁本地有、CI 无,且表现为全绿 而非失败,所以长期无人发现。现由 CI 侧scripts/ci_gitlint_range.ps1(PR 用base..head、 push 用event.before..sha、跳过 merge 提交)与本地ci_local.py的 L0b 段分别逐条校验。 ⚠️ 机制根因(stages: [commit-msg]写在 gitlint 上游 的.pre-commit-hooks.yaml里, 项目配置中 grep 不到)与完整排障口径见../CI/远程CI完整指南.md§7"钩子条数对不上"行。
9.2 两个 job
| job | 覆盖层 | 一句话职责 |
|---|---|---|
l0-gates |
L0 | pre-commit run --all-files(cppcheck / 编码规范 / 文档一致性 / 主机侧单测钩子)+ 提交信息门禁(gitlint,见上方 🔴);无 .map 时自适应跳过 mem-numbers |
l05-host-unit-tests |
L0.5 | 探测 MSVC 后遍历 test/host/**/run_unit_test.py 逐个跑并聚合断言数;未装 MSVC 时降级为 WARNING 跳过(不硬失败,但等于该层裸奔) |
触发:push(paths-ignore: docs/** / **/*.md / .workbuddy-ai/** → 纯文档改动不会触发 CI)
与 pull_request,另留 workflow_dispatch 手动触发;同分支 cancel-in-progress: true。
两个 job 都写 step-summary + ::notice:: 汇总行,日志落 ci-artifacts/*.log。
📖 步骤级细节不在本文(探测链顺序、
GITHUB_OUTPUT跳过决策、Tee落盘、产物存档、 内网拉不到 action 的处置):见../CI/远程CI完整指南.md§5.5 / §5.2 / §5.3 / §5.4(依次为:两个 job 的步骤要点 / 触发条件 / 内网 action 拉取 / 产物与汇总)。
9.3 本地等价:scripts/ci_local.py
CI 只在 push / PR 时跑,且需 runner 就绪。提交前自查、或 runner 还没配好时,用同一套判定在本地跑:
& '.venv\Scripts\python.exe' scripts/ci_local.py # L0 + L0b + L0.5(与 CI 等价)
& '.venv\Scripts\python.exe' scripts/ci_local.py --l0-only # 只跑 L0(含 L0b 提交信息门禁)
& '.venv\Scripts\python.exe' scripts/ci_local.py --l05-only # 只跑 L0.5
& '.venv\Scripts\python.exe' scripts/ci_local.py --commits 20 # 提交信息校验最近 20 条
- 自动选用装了 pre-commit 的解释器(优先级:
DTU_PRECOMMIT_PYTHON显式指定 → 当前解释器(重建.venv已自带 pre-commit)→ 仓库.venv→ PATH 的python→ 常见机器级安装位置;显式值无效 → 硬报错并指明变量名,不静默回退); 仍不可用时退出码 2 并列出全部已尝试路径。 ⚠️ 该候选表与 CI 侧scripts/ci_find_python.ps1必须同序同步——2026-10-11 的 L0 全灭就是 workflow 单方面把候选收窄成"PATH python + 仓库.venv"(runner 上两者都是死路)导致的。 - L0b 提交信息门禁:与 CI 的
ci_gitlint_range.ps1同语义(--all-files跑不到 commit-msg stage)。 .map自适应与 CI 完全一致:本地有.map就照常跑mem-numbers,没有就跳过。- 退出码:
0通过 /1门禁或单测失败 /2环境错误。
⚠️ CI 不能替代本地门禁:
.map类体积门禁、@build、L1/L2 台架项都只存在于本地。 提交前跑一遍ci_local.py(或pre-commit run --all-files)仍是强制动作,见 §7.1。
9.4 部署前置(需人工确认)
CI 能不能跑起来,取决于 runner 机器上四件事是否就位:服务端开关 / runner 注册与 labels /
看门狗常驻 / 机器级 DTU_* 工具变量。清单与逐项部署步骤都在
../CI/远程CI完整指南.md:§0.2(前置清单)、§2(注册)、§2.4(看门狗)、§4.1.3(runner 以 SYSTEM 跑,必须写 MACHINE 级变量)。
注册细节:--instance 用回环、labels 必须精确匹配 runs-on;看门狗缺了它 Gitea 一重启
CI 就静默停摆(2026-10-11 实测 run 16~21 共 12 个 job 零日志)。
⚠️ 对测试流程只需记住一条:runner 不在线时,
ci_local.py就是 L0 / L0.5 的唯一保障。 "CI 显示绿"与"本地门禁通过"是两回事,见 §9.3 与 §7.1。
附录 A:test/ 文件清单(现状)
| 文件 | 作用 |
|---|---|
test_config.h / test_config.c |
套件选择(互斥 #error)+ 统一调度入口 start_test_task() |
test_common.h |
板端测试公共断言内核(test_stats_t + TEST_CHECK_IMPL/TEST_REPORT_IMPL) |
ch395f_test.h / ch395f_test_task.c |
CH395F 套件:阶段开关、断言宏、Phase 1~5 实现 |
net_test_task.h / net_test_task.c |
CH395F 套件 NET 层(6~11):TCP 监听循环 / Client 主动连接测试 |
storage_test.h / storage_test_task.c |
STORAGE 套件:FTL + FatFS 集成(TC-STO-01~04)+ 恢复测试 |
gd5f_test.h / gd5f_test_task.c |
GD5F 套件:NAND 驱动 10 阶段 |
sd2506_test.h / sd2506_test_task.c |
RTC 套件:SD2506 RTC 2 阶段组 |
run_boot_test.py / verify_log.py + serial_tool |
自动化闭环三件套(纯 Python 标准库 / 仓库内置) |
ftp_test.py / ftp_capture.py / ftp_up_dl_test.py / ftp_speed_test.py |
FTP PC 端测试工具 |
ch395f_socket_test.py |
CH395F PC 端 socket 回显 / 大文件传输工具 |
time_sync_server.py |
对时推送 PC 端服务器 |
附录 B:模块测试文档
| 文档 | 模块 / 套件 |
|---|---|
docs/测试说明/CH395F_Test_Guide.md |
CH395F(11 阶段,TC-NET-xxx) |
docs/测试说明/GD5F2GQ5UE_Test_Guide.md |
GD5F + 存储集成(TC-GD5F / TC-STO) |
docs/测试说明/SD2506_Test_Guide.md |
RTC(TC-RTC-xxx) |
docs/测试说明/FTP_Test_Guide.md |
FTP 服务(手动 + 阶段 8 自动化) |
docs/测试说明/串口助手设计说明.md |
串口调试工具(Python 串口助手) |
docs/测试说明/自动化测试流程.md |
@test 闭环细节 |
docs/测试说明/测试文档模板规范.md |
测试文档模板与命名 |
docs/测试说明/看门狗单元测试说明.md |
看门狗判定状态机(L0.5 主机侧) |
docs/测试说明/系统时钟单元测试说明.md |
系统时钟换算(L0.5 主机侧) |
docs/测试说明/配置文件单元测试说明.md |
配置引擎解析/契约(L0.5 主机侧) |
变更记录
| 日期 | 变更 |
|---|---|
| 2026-10-12 | §9 瘦身:机器侧内容迁出(远程 CI 文档独立成 docs/CI/)。§9.2 两个 job 的步骤级细节并入 docs/CI/远程CI完整指南.md §5.5、§9.4 部署前置表并入其 §0.2;本文 §9 只保留测试分层视角的内容(CI 覆盖哪些层、两个 job 各守哪层、触发与 paths-ignore 语义、本地等价 ci_local.py、"CI 不替代本地门禁")。§9.1 两个 🔴 块压缩为结论 + 指针(机制根因与排障口径归 docs/CI/)。相关文档表两行 CI 条目合并为一行指向 docs/CI/README.md |
| 2026-10-10 | §4.2.1 台账:Drivers/BSP/NETCFG/net_cfg_legacy.c 由 ⬜ 候选 改为 ⛔ 已删除——旧档 NCFG 迁移整体移除(net_cfg_legacy.{c,h} 删除、cfg_store_legacy_path() 撤销;旧档 /cfg/netcfg.bin 不读、不删、不迁移,配置回落工厂默认),该测试候选随之撤销 |
| 2026-10-02 | S6:test/run_all_suites.py 新增 --check-build(只编译不烧录,纯离机,不需要 MCU/串口/ST-LINK);新增 §3.6.1 说明其解决的问题(测试构建无独立门禁,2026-08~09 CH395F 套件实例)、判定语义与用法。实测 4 套件全部 BUILD_OK: 0 errors, 0 warnings(即 T-4 提到的"CH395F 不可编译"已修复) |
| 2026-10-02 | S1-b + S2:新增 §4.2.1「L0.5 适格模块台账」(判据 + 模块状态表 + "两条实证":sys_clock / cfg_file 首测即各抓一处生产缺陷);§4.1 登记 test/host/cfg_file/ 新单测。同步新增 配置文件单元测试说明.md(含抓出的 s_encode 返回值误判 P0 缺陷记录) |
| 2026-10-02 | S3 + S14:§9.1 第 2 条改正(原文"强制跑 mem-numbers"自相矛盾 → 改为"按 .map 有无自适应跳过")并加 🔴 门禁盲区声明;§9.2 补 S3 结果可见性(Tee-Object 落盘 + $GITHUB_STEP_SUMMARY + ::notice:: 汇总行,不用 actions/upload-artifact)。另:§4.1 登记 test/host/sys_clock/ 新单测(S1-a) |
| 2026-10-01 | 新增 §9 CI 持续集成(Gitea Actions,T-6):.gitea/workflows/ci.yml(l0-gates + l05-host-unit-tests,Windows runner);L0 + L0.5 上 CI、L1/L2/L3 留本地;刻意不跑 @build、.map 缺失时自适应跳过 mem-numbers;新增 scripts/ci_local.py 本地等价命令(§4.1 登记);§1 补 CI 说明;docs/任务规划/README.md T-6 销项 |
| 2026-10-01 | §3.6 新增 test/run_all_suites.py 多套件回归编排(try/finally 无条件还原套件开关;T-5);test/README.md 同步 |
| 2026-10-01 | §3.2 断言宏改为集中内核:新增 test/board/test_common.h(test_stats_t + TEST_CHECK_IMPL/TEST_REPORT_IMPL),4 套件各自用一行宏绑定自身统计变量(T-4;零行为变化、零 RAM 增量、@build 0/0,四套件均 0/0 验证)。附 test_common.h 不 include dbg_log.h 的原因(DBG_TAG 冻结 → #47-D) |
| 2026-10-01 | 新增 §7.1 钩子 test-suite-switch(测试套件开关=产品构建,防测试固件静默提交)+ §3.1 铁律(跑完套件回归务必改回产品构建);依据 docs/任务规划/README.md T-1(测试架构评审 P0)。同步更新 ../代码规范/pre-commit使用说明.md §2.13 |
| 2026-10-01 | §4.1/§4.2 更新:主机侧单测运行器统一走通用 harness test/host/common/unit_test_harness.py(T-3),模块运行器只做声明式配置;§6.2.1 新增套件汇总行校验(T-2) |
| 2026-10-01 | §4.2 补 L0.5 环境要求表(MSVC cl.exe = 装了 VS 即有、与模拟器共用;Windows SDK;纯标准库 Python;缺失时退出码 2 且 pre-commit 钩子拦截)+ "不调 vcvars64.bat(reg.exe 受限)"原因说明;§4.1 工具清单标注该运行器需 VS。同步更新 ../环境与部署/项目部署环境要求.md(§2.3 / §2.7 / 第 3、10 步 / 清单 / 下载表 / 速查表) |
| 2026-09-15 | §3.4 更新:P1-6 修复落地(基础线程策略)——USER CODE 区冗余 netTask 创建删除 + net_task_func 入口 ENABLE_HW_LAYER_TESTS 自挂起守卫;"代码事实/设计约束"按修复后现状重写(硬件层模式中断断言恢复安全、NET 层/产品仍按"netTask 恒在轮询"设计);彻底方案(.ioc 移除)经评估未采用。同日构建验证发现两处存量测试固件缺陷(与 P1-6 无关,A/B 对照确证):① net_test_task.c s_rx_buf 使用先于声明 → NET 层配置自 2026-08(88160a5)起不可编译;② 128KB 静态 RAM 预算溢出(产品镜像 123.6KB,HW 全阶段固件超 8.7KB、NET 固件超 ~28KB)——已立 P1-9/P1-10 跟踪 |
| 2026-09-13 | 重写:对齐现测试架构——统一 test_config.h 套件(旧版仅描述 CH395F 单套件与 ch395fTestTask 旧入口);新增 @test 自动化闭环、pre-commit 门禁、UI 模拟器五步门禁、主机侧工具约定;新增"测试构建下 netTask"已知问题(旧版"硬件层模式 netTask 不创建"表述与代码不符,改为设计约束并给出重构路径);修正附录路径(旧版 docs/测试说明/CH395F_Test_Guide.md 等已移入 docs/测试说明/);新增双端注册、内存预算、陷阱沉淀要求 |