#!/usr/bin/env python3 # -*- coding: utf-8 -*- """控制台单写者日志(console_log)主机侧单元测试运行器(L0.5,纯标准库)。 做什么: 用本机 MSVC 编译 test/host/console_log/console_log_test.c(以 #include 纳入生产源 App/log/console_log.c),在 PC 上离线验证单写者环形缓冲的全套契约:192B 行截断、 256B 分块、跨环尾两段拷贝、写者未注册/ISR 上下文的无锁直发、缓冲满两条上界 (DWT 周期 / 200 tick)与丢弃计数、[ERR] 告警行不经环(不递归)、TX 僵尸通道软复位 自愈 + 故障即止 + 300ms 冷却(含无符号回绕)、DWT 使能失败退回 20000 次指令计数、 huart1.Instance==NULL 短路、fputc 重定向、drain 250ms 预算。 工具链与判定委托 test/host/common/unit_test_harness.py。 为什么要主机侧单测(而不是只用台架): 本模块的坑全是"异常时刻 + 节奏":ORE 僵尸通道在软复位后毫秒级重锁、TIM7 停摆让 HAL 的 tick 超时永不成立(round 8a 台架:唯一喂狗者死等 TXE -> IWDG 误复位)、 背靠背块超时把主循环拉到 450~500ms 饿死喂狗(台架实测的停喂误复位)。这些在台架上 要么一年一遇、要么现场已被复位冲掉;把 100/250/300ms 三条时间线做成显式量之后, 299ms 与 300ms 的差别就是一行断言。详见 docs/子系统设计/控制台单写者日志设计说明.md(G1~G5 / K1~K5 / §12 ORE 僵尸通道)。 桩的边界:console_log.h 与 sys_events.h 用真实头(SYS_EVT_CONSOLE_TX 位定义一改即 编译期报错);usart.h 是桩但只暴露本模块访问的 huart1 + MX_USART1_UART_Init, USART SR 位号、DWT/CoreDebug 位掩码、HAL_StatusTypeDef 枚举值均照抄真实头, 宿主编不动的那条 main.h -> stm32f4xx_hal.h -> core_cm4.h 链由 stub/stm32f4_fake.h 以普通结构体替代(理由写在该文件头注释里)。 ⚠ 测试单元刻意不定义任何 syslog_* / DBG_* 符号:生产源哪天恢复"在 drain 里调日志 子系统",本套件会以链接失败告警(那是设计上必须挡住的成环)。 用法(仓库根目录): python test/host/console_log/run_unit_test.py 退出码:0=PASS / 1=测试失败 / 2=环境错误。 设计依据:docs/子系统设计/控制台单写者日志设计说明.md、docs/测试说明/主机单测覆盖率口径.md §9「P2 落地」清单第 3 条 创建日期:2026-10-24 """ 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 必须最先(覆盖 usart.h / FreeRTOS.h / task.h / cmsis_os.h / cmsis_os2.h), # 其余是真实头目录(console_log.h 在 App/log,sys_events.h 在 App/app)。 BUILD = TestBuild( name="Console Log Tests", src=os.path.join(HERE, "console_log_test.c"), include_dirs=[ os.path.join(HERE, "stub"), os.path.join(ROOT, "App", "log"), os.path.join(ROOT, "App", "app"), HERE, ], summary_marker="=== Console Log Tests:", ) if __name__ == "__main__": sys.exit(run_host_unit_test(BUILD))