Files
STM32F4-Base/test/host/common/README.md
T
wandering1 04997e48aa refactor(编号): test/host 与 App/Drivers 注释去测试层级号(73 站点 / 64 文件)
同族第二道:代码注释面。41 个 run_unit_test.py 文件头与引用行、common harness 与两份
README、4 个 *_test.c 文件头、App/** 14 处(含 BOM 文件 App/cfg/cfg_file.c)、
GD5F nand_ftl.c 1 处,共 73 站点 / 64 文件;措辞取「主机侧纯逻辑单元测试运行器
(纯标准库)」避免同行「主机侧」重复。

零行为改动(机器证据,不是口头):
- 17 个 .c/.h 剥掉注释后与 HEAD **逐字节相同**;
- 42 个 .py 走 ast.dump 比较、仅归一 docstring(真实字符串字面量参与比较,故旗标或
  输出文本被动过必然报红)→ 全部相等;behaviour_changed=0。
- py_compile 42/42;抽跑 watchdog 47/47、freq 46/46(动手前先取基线,改后同数);
  CR 字节 0、EF BF BD 0、BOM 与 HEAD 逐一一致、check_bom_drift rc=0。

刻意未动:6 处字符串字面量(FTP 应答 "215 UNIX Type: L8"、lftpd_test.c SYST 断言、
console_log_test.c 的 "L1\r\n"、phasor_test.c 的 "L2-in ring" 文案与 printf 标题)——
改了就是改测试输出/行为;UI 设计令牌层 L0(ui_tokens.h、ui_content_page.c)与
test/host/README.md 的算法层号是**另外的族**。

两条 SyntaxWarning(ftp_cfg / net_cfg 文件头 \S 转义)**HEAD 同样存在**,已对 HEAD 副本
复现证明非本次引入,未顺手改。
2026-10-28 17:52:55 +08:00

120 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# test/host/common/ — 主机侧测试公共库
> 主机侧纯逻辑单测的**通用 harness**:被各模块的单测运行器共享,
> 集中承载工具链探测与编译/运行/判定的公共细节。
## 文件一览
| 文件 | 作用 |
|---|---|
| `unit_test_harness.py` | **通用单测 harness**(纯标准库):探测本机 MSVC → 拼 `INCLUDE`/`LIB` → 编译测试单元 → 运行 → 判定退出码 |
## 为什么要有这层(2026-10-01 抽取)
看门狗单测最早的运行器(`test/host/watchdog/run_unit_test.py`)把工具链探测、
`INCLUDE`/`LIB` 拼接、GBK 解码、`/wd4819` 抑制、退出码契约**全写死在模块目录内**。
第二个主机侧纯逻辑单测出现时须整段复制——工具链细节一旦修正(新增 VS 版本、SDK 布局
变化、编码处理调整)就会发散。抽取后各模块运行器只剩"声明式"配置。
> **边界**:harness **不认识任何模块**——源码路径、桩目录、额外 include、断言
> 汇总行特征全部由调用方传入。新单测只写一个 `TestBuild(...)` + 一行 `sys.exit(...)`。
## 用法(模块侧 `run_unit_test.py` 模板)
```python
import os, 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
BUILD = TestBuild(
name="<模块名> Tests", # 人类可读名(打印用)
src=os.path.join(HERE, "<模块>_test.c"), # 测试单元(内部 #include 生产源)
include_dirs=[ # 顺序敏感:桩目录必须在生产目录之前
os.path.join(HERE, "stub"),
os.path.join(ROOT, "App", "util"),
HERE,
],
summary_marker="=== <模块名> Tests:", # 运行输出中必须出现的汇总行前缀
)
if __name__ == "__main__":
sys.exit(run_host_unit_test(BUILD))
```
另一写法(不落 `run_unit_test.py`,直接内联):
```python
from unit_test_harness import TestBuild, run_host_unit_test
raise SystemExit(run_host_unit_test(TestBuild(name="...", src="...", ...)))
```
## 退出码契约(各模块运行器统一遵循)
| 码 | 含义 |
|---|---|
| `0` | PASS——编译 + 运行成功,且日志含 `summary_marker` |
| `1` | FAIL——有测试用例未通过 |
| `2` | 环境错误——找不到编译器 / 编译失败 / 未产生汇总行 |
## `TestBuild` 字段
| 字段 | 必填 | 说明 |
|---|---|---|
| `name` | ✓ | 打印用名称;同时用于派生 exe 名与 obj 目录名 |
| `src` | ✓ | 测试单元 `.c` 的绝对路径(内部 `#include` 生产源,保证测的是真代码) |
| `include_dirs` | | `/I` 目录列表;**桩目录需排在前面**才能覆盖真实头 |
| `summary_marker` | | 运行输出中必须出现的汇总行前缀;空 = 不校验 |
| `extra_flags` | | 追加的 `cl.exe` 开关 |
| `exe_name` / `obj_tag` | | 覆盖派生的产物名 / 中间目录名(默认由 `name` 派生;落点见下节"产物目录归属") |
## 环境要求
同主机侧纯逻辑单测通用要求(见 [`../../../docs/测试说明/测试开发标准流程.md`](../../../docs/测试说明/测试开发标准流程.md) §4.2):
MSVC `cl.exe`(装了 VS 即有)+ Windows SDK + 纯标准库 Python;**不调 `vcvars64.bat`**
(内部 `reg.exe` 受限),直接拼环境变量调 `cl.exe`。
## 产物目录归属(跨工作树并行,2026-10-25)
**obj / exe / pdb 每次运行独占一个子目录**,按工作树分家:
```
%TEMP%\dtu_host\<仓库根路径 sha1 前 10 位>\runs\<pid>-<纳秒时间戳>\
<suite>_obj\*.obj ← 中间目标文件
<suite>_tests.exe ← 本次编译产物(跑完随目录一起删)
<suite>_tests.exe.owner.json ← 归属凭据:suite / repo_root / pid / started
```
为什么这么放(两条都是实测出来的,不是洁癖):
- **旧写法把 obj/exe 摊在全机共用的 `%TEMP%`,名字由套件名派生**(
`%TEMP%\net_socket_tests.exe`)。多棵工作树并行跑同名套件就会互相覆盖,而最坏情况
不是编不过:A 树编译完,B 树把同名 exe 又盖一遍,A 树随后执行的是 **B 树编出来的
二进制**——两棵树的 `summary_marker` 一模一样,harness 分辨不出来,等于拿别人的
运行结果给自己下结论。2026-10-25 三条道并行时全量 `ci_local` 里 `test-cppcheck` /
`test-coverage` 偶发 "files were modified by this hook"、单跑即绿,就是这一类。
- **按工作树分家 + 每次跑独占**同时解决"跨树互踩"和"同树并发"(覆盖率钩子与手工主机侧纯逻辑单测
同时跑同一棵树时也会撞)。不牺牲任何东西:每个测试单元就一个 `.c`,`cl` 每次整体
重编,"共享 obj 省编译"本来就是错觉。**没有加锁**——这里没有需要互斥的独占资源,
只是名字撞了;加锁等于把主机侧纯逻辑单测变成和 build/bench 一样的全局串行。
两个开关(都写在 harness 顶部注释里,判据以 `unit_test_harness.host_build_root()` 为准):
- `DTU_HOST_BUILD=<目录>`:改产物根(CI/容器要固定路径时用)。覆盖率侧同源——
`scripts/check_test_coverage.py` 复用同一个函数,不再用自己那套固定名
(旧 `%TEMP%\dtu_test_cov` 的共享名,正是 docs/测试说明/主机单测覆盖率口径.md 里
"并行期每人只跑 `--module` 自己模块"那条规避的由来;分家之后该规避不再必要)。
- `DTU_HOST_BUILD_KEEP=1`:保留本次独占目录不删(要单步调试那个 exe 时用,会打印路径)。
想知道当前树算到哪个目录:
```powershell
& '.venv\Scripts\python.exe' -c "import sys;sys.path.insert(0,'test/host/common');import unit_test_harness as U;print(U.host_build_root())"
```
## ⚠️ 注意
- 本目录在 pre-commit 的 cppcheck / 代码规范**排除范围**(`test/` 豁免)
- 改动本 harness 会触发 `watchdog-unit-test` 钩子(`files` 正则已含本文件)