测试:落地测试架构改进 T-1~T-5(套件开关门禁/汇总行校验/通用 harness/公共断言宏/多套件编排)

背景:L0.5 主机侧单测落地后复审测试架构,给出 6 条改进意见(P0~P3),
登记进 docs/待处理内容.md「测试与验证基础设施」后逐条实施。

- T-1(P0) 测试套件开关门禁:新增 scripts/check_test_suite.py + pre-commit 钩子
  test-suite-switch,默认要求产品构建(全部 TEST_SUITE_* 注释),跑套件回归用
  --allow-test-build / ALLOW_TEST_BUILD=1 显式放行。顺带把 test_config.h 中长期
  开启的 TEST_SUITE_CH395F 改回注释(仓库此前躺着的是测试固件)。
- T-2(P1) verify_log.py 增加 --require-suite-summary:要求
  "=== <名称>: N/M PASSED (failed=0) ===" 且 N>0,堵住"套件没跑到汇总行/
  断言数为 0 仍判 PASS"的盲区;run_boot_test.py 同名参数透传。不传参向后兼容。
- T-3(P1) 抽取通用 harness test/host/common/unit_test_harness.py(工具链探测/
  INCLUDE-LIB 拼接/wd4819/GBK 解码/退出码契约);watchdog 运行器 170→55 行,只声明 TestBuild。
- T-4(P2) 新增 test/board/test_common.h 集中断言内核(TEST_CHECK_IMPL/TEST_REPORT_IMPL),
  4 套件各用一行宏绑定自身统计变量(保持 g_xxx_stats 独立、防跨套件串数)。
  刻意不 include dbg_log.h——否则冻结 DBG_TAG 默认值触发 #47-D 重定义警告。
- T-5(P2) 新增 test/run_all_suites.py 多套件回归编排,try/finally 无条件还原套件开关。

验证:@build 产品构建 0 错 0 警;4 套件逐一构建均 0/0;看门狗单测 39/39 PASS;
pre-commit 14 钩子全通过;编排脚本 dry-run/开关切换/异常还原均实测通过。
This commit is contained in:
2026-10-01 07:28:57 +08:00
parent 6b90fa0581
commit 2055de0303
24 changed files with 1135 additions and 261 deletions
+37 -3
View File
@@ -21,7 +21,9 @@ git commit
├─ doc-links 文档链接存在性(D-3/D-7,.md)
├─ task-table FreeRTOS 任务表一致性(D-2/D-7)
├─ mem-numbers 内存/Flash 数字一致性(D-7 体积档)
└─ cubemx-survival CubeMX 重生成存活审计(易失定制清单 + 基线漂移)
├─ cubemx-survival CubeMX 重生成存活审计(易失定制清单 + 基线漂移)
├─ watchdog-unit-test 看门狗判定状态机主机侧单测(L0.5)
└─ test-suite-switch 测试套件开关=产品构建(防测试固件静默提交)
└─ commit-msg(.git/hooks/commit-msg)
└─ gitlint 校验 commit message 格式
```
@@ -40,6 +42,7 @@ git commit
| scripts/gen_task_table.py | 随仓库提交 | FreeRTOS 任务表生成/校验(纯 Python,无依赖) |
| scripts/check_mem_numbers.py | 随仓库提交 | 内存/Flash 数字一致性(纯 Python,无依赖) |
| scripts/check_cubemx_survival.py | 随仓库提交 | CubeMX 重生成存活审计(纯 Python,无依赖) |
| scripts/check_test_suite.py | 随仓库提交 | 测试套件开关门禁(纯 Python,无依赖) |
> **PATH 要求**:cppcheck 安装后需在系统 PATH 中(`C:\Program Files\Cppcheck`)。winget 自动写入 User PATH,但当前终端不会立即生效,需**重开终端**。
@@ -260,7 +263,37 @@ cppcheck 是开源 C/C++ 静态分析工具,**不运行代码**,通过语法
> 方法论(L1 写 `.ioc` > L2 搬进 `USER CODE` 区 > L3 审计兜底)详见
> [`../子系统设计/看门狗设计说明.md`](../子系统设计/看门狗设计说明.md) §10.2。
### 2.13 gitlint — Commit Message 格式
### 2.13 test-suite-switch — 测试套件开关=产品构建
用 `scripts/check_test_suite.py` 检查 `test/board/test_config.h` 的 `TEST_SUITE_*` 开关
是否已全部注释(= 产品构建),防止**测试固件被静默提交**。
| 项 | 说明 |
|---|---|
| **触发条件** | 改动 `test/board/test_config.h`,且其中有 `TEST_SUITE_*` 处于**启用**状态 |
| **处理方式** | **阻止提交**(注释掉开关,或显式放行测试构建) |
| **典型报错** | `[FAIL] 默认要求产品构建:请注释掉 test/board/test_config.h 中的 TEST_SUITE_* 开关。` |
| **放行方式** | `python scripts/check_test_suite.py --allow-test-build` 或 `ALLOW_TEST_BUILD=1 git commit ...`(放行后仍打 `[WARN]` 横幅提醒) |
**为什么需要这个钩子**(2026-10-01 实测):
`test_config.h` 的 `TEST_SUITE_CH395F/STORAGE/GD5F/RTC` 决定固件里编入哪个测试套件,
**全部注释 = 产品构建**——但此前 `grep TEST_SUITE scripts/*.py` **零命中**,没有任何门禁
检查它。实测已提交的 HEAD 里 `TEST_SUITE_CH395F` 就是开启状态(`.map` 含 34 处 test 代码),
即仓库里躺着的是**测试固件**而非产品固件,纯靠开发者记得改回去。
**判定实现**:先剥掉块注释与行注释再匹配 `#define`,所以注释掉的开关
(`//#define TEST_SUITE_GD5F`)不会误判;同文件派生的 `TEST_SUITE_SELECTED` /
`TEST_SUITE_NAME` 由**白名单**排除(它们是开关的推导结果,非用户可选项)。
```powershell
& '.venv\Scripts\python.exe' scripts/check_test_suite.py # 门禁判定
& '.venv\Scripts\python.exe' scripts/check_test_suite.py --allow-test-build # 显式放行
```
> 套件互斥(一次最多启用一个)由 `test_config.h:45` 的 `#error` 在**编译期**兜底;
> 本钩子在**提交期**再拦一道(`#error` 只在真正构建时暴露,测试固件仍可能被提交)。
### 2.14 gitlint — Commit Message 格式
| 项 | 说明 |
|---|---|
@@ -383,7 +416,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env
| 文件 | 位置 | 说明 |
|---|---|---|
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(13 个钩子 + 1 个预留) |
| `.pre-commit-config.yaml` | 仓库根 | 钩子配置(15 个钩子 + 1 个预留) |
| `.gitlint` | 仓库根 | gitlint 规则(宽松,兼容中文) |
| `.cubemx_survival_baseline.json` | 仓库根 | CubeMX 易失定制基线(哈希;`--write` 重建) |
| `scripts/check_mojibake.py` | scripts/ | U+FFFD 检测脚本 |
@@ -392,6 +425,7 @@ $env:PATH = [Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [Env
| `scripts/gen_task_table.py` | scripts/ | FreeRTOS 任务表生成/校验(D-2/D-7) |
| `scripts/check_mem_numbers.py` | scripts/ | 内存/Flash 数字一致性(D-7 体积档) |
| `scripts/check_cubemx_survival.py` | scripts/ | CubeMX 重生成存活审计 |
| `scripts/check_test_suite.py` | scripts/ | 测试套件开关门禁(防测试固件静默提交) |
| `docs/代码规范/嵌入式C语言代码规范_V1.0.md` | docs/代码规范/ | 代码规范原文(S1~S19 规则来源) |
| `docs/代码规范/cppcheck使用说明.md` | docs/代码规范/ | cppcheck 钩子详解(规则/配置/抑制) |
| `docs/文档规范.md` | docs/ | 文档体系规范(链接形态/命名/门禁要求) |
@@ -36,10 +36,10 @@
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
| 区域 | 用量 | 容量 | 占比 |
|---|---|---|---|
| ER_IROM1 | 265,036 B (258.82 KB) | 1,048,576 B (1024 KB) | 25.3% |
| RW_IRAM1 | 87,080 B (85.04 KB) | 131,072 B (128 KB) | 66.4% |
| ER_IROM1 | 261,312 B (255.19 KB) | 1,048,576 B (1024 KB) | 24.9% |
| RW_IRAM1 | 84,864 B (82.88 KB) | 131,072 B (128 KB) | 64.7% |
| RW_CCM | 49,184 B (48.03 KB) | 65,536 B (64 KB) | 75.0% |
| **Flash (ROM 合计)** | **265,792 B (259.6 KB)** | 1,048,576 B (1024 KB) | **25.3%** |
| **Flash (ROM 合计)** | **262,012 B (255.9 KB)** | 1,048,576 B (1024 KB) | **25.0%** |
<!-- MEM_SNAPSHOT:END -->
各区的容量与常量来源:
+21
View File
@@ -36,3 +36,24 @@
| N-03c | **CubeMX 重生成会覆盖 `USER CODE` 标记区之外的定制** | ⚠️ **2026-09-29 两次重生成实测确认**:首次覆盖 4 处、第二次覆盖 5 处(P0 `Src/iwdg.c` 自动启动、P1 `HardFault_Handler` 打回空死循环、P1 `FreeRTOSConfig.h` 两个加固宏被删、P2 `freertos.c` 中文注释)。✅ **已建立自动检出**:`scripts/check_cubemx_survival.py`(18 项清单 + 哈希基线,已挂 pre-commit)——第二次实测**准确检出全部 5 处**。⚠️ 关键发现:CubeMX **换了一代中断模板**,"生成区外的自造标记区"同样不被保留(详见设计文档 §10.1)。**今后每次重生成后跑一次该脚本即可** | 本次实施记录 |
| N-03e | 判定状态机**缺主机侧纯逻辑回归**(改动后只能靠真机/模拟器才发现回归) | ✅ **已落地(2026-10-01)**:硬件访问收敛到薄平台层(`App/util/task_watchdog.h` 末段声明 3 个 `__weak` 钩子),新建 `test/host/watchdog/` 单元测试(39 用例,`#include` 生产源码 + 强符号覆盖钩子),挂 pre-commit `watchdog-unit-test` 钩子。MCU 端行为零变化(`@build` 0/0)。新增测试分层 **L0.5** | 见 [`测试说明/看门狗单元测试说明.md`](测试说明/看门狗单元测试说明.md) |
| N-03d | 3 个次要 Fault(`MemManage`/`BusFault`/`UsageFault`)为空 `while(1)`,死机静默 | ✅ **已修(2026-09-29)**:升级为与 `HardFault` 同一套崩溃采集(手工 `__asm` Handler → per-fault C 入口 → `hard_fault_report(tag, frame)`,日志打 `[ERR] MEMFAULT/BUSFAULT/USAGEFAULT + 全量现场`);新增 `fault_handler_enable()` 使能 `SHCSR` 后三者(默认只开 HardFault,否则故障被升级、看不出根因),在 `app_main_init()` 第一行调用。**刻意不启用** `UNALIGN_TRP`/`DIV_0_TRP`(避免把普通 bug 升级成死机)。`@build` 0/0;审计 18 通过 / 1 警告 / 0 失败 | 见 [`子系统设计/看门狗设计说明.md`](子系统设计/看门狗设计说明.md) §10.2 |
## 测试与验证基础设施
> **来源**:2026-10-01 测试架构评审(L0.5 主机侧单测落地后的复审)。
> P0/P1 属闭环缺口(会漏检真实故障),P2 属工程效率(减重复、降误提交概率)。
> 实施依据见 [`测试说明/测试开发标准流程.md`](测试说明/测试开发标准流程.md);消化即销项。
>
> **已销项(2026-10-01,同日实施)**:
> - **T-1(P0)**:`scripts/check_test_suite.py` + pre-commit 钩子 `test-suite-switch`
> ——默认要求产品构建,跑套件回归用 `--allow-test-build` 显式放行。
> **顺带修复**:`test/board/test_config.h` 长期开启的 `TEST_SUITE_CH395F` 已注释(现为产品构建)。
> - **T-2(P1)**:`verify_log.py` / `run_boot_test.py` 新增 `--require-suite-summary`
> ——要求 `=== <名称>: N/M PASSED (failed=0) ===` 且 `N>0`。
> - **T-3(P1)**:通用 harness `test/host/common/unit_test_harness.py`;
> 看门狗运行器 170 行 → 55 行声明式。
> - **T-4(P2)**:`test/board/test_common.h` 集中断言内核,4 套件绑定自身统计变量。
> - **T-5(P2)**:`test/run_all_suites.py` 多套件编排(`try/finally` 无条件还原开关)。
| # | 事项 | 优先级 | 问题与证据 | 处置方向 | 出处 |
|---|---|---|---|---|---|
| T-6 | **无 CI——L0/L0.5 已具备上 CI 条件** | P3 | L0(pre-commit 门禁,纯 Python + cppcheck)与 L0.5(MSVC 单测,纯标准库运行器)**均不依赖硬件**,可直接在 CI runner 跑;L1/L2 需真机与 ST-LINK,只能本地。当前全靠开发者本地 `pre-commit install`,新机器漏装即裸奔 | 待有 CI 平台时:挂 L0(`pre-commit run --all-files`)+ L0.5;L1/L2 保留本地。前置:CI runner 需装 cppcheck + VS BuildTools(或自建带工具链镜像) | 2026-10-01 评审;`测试说明/测试开发标准流程.md` §1 |
+85 -12
View File
@@ -91,6 +91,7 @@ flowchart TD
```
- **套件互斥**:一次构建最多启用一个 `TEST_SUITE_*`,否则 `#error`(编译期门禁);全部注释 = 正常产品构建。
- **测试固件不得静默提交**:`TEST_SUITE_*` 开关受 pre-commit 钩子 `test-suite-switch` 门禁(默认要求全部注释)。跑完套件回归**务必改回产品构建**再提交;确需提交测试构建时用 `ALLOW_TEST_BUILD=1` 显式放行(会打 `[WARN]` 横幅)。见 §7.1。
- **main.c / freertos.c 不含任何测试逻辑** —— 测试代码只能出现在 `test/`。
| 套件 | 宏 | 文件 | 阶段组织 | 备注 |
@@ -109,20 +110,41 @@ flowchart TD
断言宏(语义统一,统计变量按套件独立,防跨套件串数):
```c
typedef struct { uint16_t total, passed, failed; } xxx_test_stats_t;
**内核集中在 `test_common.h`**(2026-10-01 抽取,T-4)——4 套件的宏此前逐字重复,改一处要改四处:
#define XXX_TEST_CHECK(cond, fmt, ...) do { \
g_xxx_stats.total++; \
if (cond) { g_xxx_stats.passed++; DBG_INFO(" [PASS] " fmt, ##__VA_ARGS__); } \
else { g_xxx_stats.failed++; DBG_ERROR(" [FAIL] " fmt, ##__VA_ARGS__); } \
```c
/* 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 XXX_TEST_REPORT(name) do { \
DBG_INFO("=== %s: %d/%d PASSED (failed=%d) ===", name, ...); \
#define TEST_REPORT_IMPL(stats, name) do { \
DBG_INFO("=== %s: %u/%u PASSED (failed=%u) ===", name, ...); \
} while (0)
```
各套件**用一行宏把它绑定到自己的统计变量**(保持 `g_xxx_stats` 独立):
```c
/* 套件头文件内(如 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` 刻意不 include `dbg_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 用标准注释头(与文档用例一一对应):
```c
@@ -174,7 +196,6 @@ static void phaseN_run(void) {
- 硬件层模式下挂起的 netTask 仍占 4KB 栈,为基础线程策略的接受成本(彻底方案可回收,暂无计划)。
### 3.5 新测试文件 / 新套件 checklist
1. `test/` 下新建 `xxx_test.h` / `xxx_test_task.c`(按 §3.2 模式);
2. `test/board/test_config.h`:补 `TEST_SUITE_XXX` 注释开关 + 互斥 `#error` 加项 + 分发分支(include + `TEST_SUITE_NAME`);
3. `test/board/test_config.c`:`start_test_task()` 加分发分支;
@@ -183,6 +204,24 @@ static void phaseN_run(void) {
6. 立 `docs/测试说明/<模块>_Test_Guide.md`(§2 模板);
7. `@test` 以套件标题行为 `-Required` 标记跑通回归。
### 3.6 多套件回归编排(`test/run_all_suites.py`,2026-10-01 加 T-5)
套件互斥(编译期 `#error`)→ 跑 4 个套件的回归本来要手工循环 4 轮
(改宏 → 构建 → 烧录 → 采集 → 判读)。本脚本编排成一条命令:
```powershell
& '.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 # 只看计划
```
**核心保障:`try/finally` 无条件还原 `test_config.h`**(含异常 / `KeyboardInterrupt`
路径)——跑完忘还原 = **测试固件进库**。这与提交期门禁 `test-suite-switch`(§7.1)
是**两条独立防线**:前者是流程自动化、后者是门禁兜底。
> ⚠️ **不挂 pre-commit**:每轮真机烧录 + 采集(8s 静默 + boot),4 套件约数分钟起,
> 且破坏性套件会改设备持久状态。仅回归窗口手工使用。
---
## 4. 主机侧测试工具
@@ -193,8 +232,9 @@ static void phaseN_run(void) {
|---|---|---|
| Python 串口助手(serial_tool) | `tools/serial_tool/serial_tool.py`(.venv + pyserial) | 常驻 daemon + 薄 CLI:列端口 / 开关 / 收发 / 全量日志 / wait-idle |
| `test/host/run_boot_test.py` | `test/host/` | 一键闭环:采集 → 烧录 → 提取日志 → 校验 |
| `test/host/verify_log.py` | `test/host/` | 日志标记校验(Required / Forbidden → PASS / FAIL) |
| **`test/host/watchdog/run_unit_test.py`** | `test/host/watchdog/` | **L0.5 主机侧单测**:MSVC 离线编译真实 `task_watchdog.c` + 可观测桩,穷举判定状态机(零硬件、秒级)。**需本机装有 VS(`cl.exe`)**,见 §4.2 环境要求 |
| `test/host/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 环境要求 |
| `.opencode/serial_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) |
@@ -209,7 +249,9 @@ static void phaseN_run(void) {
1. 模块的**硬件访问**收敛到薄平台层(`__weak` 钩子或后端函数)——参照
`App/cfg/cfg_file_store.h` 的"共享核心 + 平台后端";
2. 测试单元 `#include` **生产源文件**(保证测的是真代码,非副本),用桩替换平台层;
3. 用 MSVC 离线编译运行(`run_unit_test.py` 自动探测工具链);
3. 写模块侧运行器:**只声明** `TestBuild`(源码 / 桩目录 / 汇总行标记)+ 调
`test/host/common/unit_test_harness.py`(工具链探测与编译运行细节在 harness 内,
见 [`../../test/host/common/README.md`](../../test/host/common/README.md));
4. 挂 pre-commit 钩子,被改动时自动跑(**只 commit 不烧录**,秒级)。
**环境要求**(详见 [`../环境与部署/项目部署环境要求.md`](../环境与部署/项目部署环境要求.md) §2.3 / §2.7):
@@ -302,6 +344,31 @@ flowchart TD
| 开机全量(存储 / 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`。
```powershell
# 测试套件构建回归(要求套件跑到汇总行,且断言数 > 0)
& '.venv\Scripts\python.exe' test/host/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 三条铁律
1. **采集必须先于烧录武装** —— 开串口 + clear 在烧录前完成(daemon 常驻全量日志兜底,不会漏字节;`run_boot_test.py` 已保证顺序);
@@ -323,6 +390,7 @@ flowchart TD
| 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`)。
@@ -369,6 +437,7 @@ flowchart LR
| 文件 | 作用 |
|---|---|
| `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)+ 恢复测试 |
@@ -395,6 +464,10 @@ flowchart LR
| 日期 | 变更 |
|---|---|
| 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/待处理内容.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/测试说明/`);新增双端注册、内存预算、陷阱沉淀要求 |
@@ -68,8 +68,13 @@ test/host/watchdog/watchdog_test.c
| 硬件 | 无(不需要开发板 / 串口 / ST-LINK) |
> **为何不调 `vcvars64.bat`**:它内部调用 `reg.exe` 探测环境,本机安全策略禁止
> `reg.exe`。`run_unit_test.py` 因此**直接拼 `INCLUDE`/`LIB` 环境变量**并用绝对
> `reg.exe`。运行器因此**直接拼 `INCLUDE`/`LIB` 环境变量**并用绝对
> 路径调 `cl.exe`(只需 MSVC 版本目录 + Windows SDK 的 include/lib),无副作用、可重复。
>
> 该工具链逻辑**不在本模块目录内**(2026-10-01 T-3 抽取):已沉淀为通用 harness
> [`../../test/host/common/unit_test_harness.py`](../../test/host/common/unit_test_harness.py),
> 本模块的 `run_unit_test.py` 只**声明** `TestBuild`(源文件 / 桩目录 / 汇总行标记)。
> 新增 L0.5 单测时直接复用 harness,见 [`../../test/host/common/README.md`](../../test/host/common/README.md)。
### 1.4 运行方式
@@ -78,13 +83,13 @@ test/host/watchdog/watchdog_test.c
& '.venv\Scripts\python.exe' test/host/watchdog/run_unit_test.py
```
退出码:`0`=PASS / `1`=测试失败 / `2`=环境错误(找不到编译器 / 编译失败)。
退出码:`0`=PASS / `1`=测试失败 / `2`=环境错误(找不到编译器 / 编译失败 / 无汇总行)。
### 1.5 回归接入
| 项 | 说明 |
|---|---|
| **触发范围** | `App/util/task_watchdog.c` / `task_watchdog.h`,或 `test/host/watchdog/**` 变动 |
| **触发范围** | `App/util/task_watchdog.c` / `task_watchdog.h`,或 `test/host/watchdog/**`、`test/host/common/unit_test_harness.py` 变动 |
| **门禁层级** | **L0.5 层**(介于 L0 静态门禁与 L1 板端套件之间):主机侧、秒级、纯逻辑,比板端套件便宜、比静态检查更强 |
| **pre-commit 钩子** | `watchdog-unit-test`(本地钩子,`.pre-commit-config.yaml` §7) |
| **汇总行** | `=== Task Watchdog Tests: N/N PASSED (failed=0) ===` |
@@ -175,4 +180,5 @@ if (s_miss_streak >= WD_MISS_TOLERANCE) { DBG_ERROR("[WD] task stall: ..."); }
| 日期 | 变更 |
|---|---|
| 2026-10-01 | §1.3/§1.4/§1.5 更新:工具链逻辑抽取到通用 harness `test/host/common/unit_test_harness.py`(T-3),本模块 `run_unit_test.py` 改为**声明式** `TestBuild`(原 170 行 → 55 行);hooked 触发范围加 `test/host/common/unit_test_harness.py`。行为与退出码契约不变(39/39 PASSED) |
| 2026-10-01 | 创建:看门狗判定状态机主机侧单元测试(39 用例)+ 平台层抽取(`task_watchdog.h` 末段三个 `__weak` 钩子)+ pre-commit 钩子 `watchdog-unit-test`;记录两处已知行为(stall 复报、测试隔离) |