test(host): P2 覆盖率批次,5 套主机单测与共享假 FatFs
P2 五个文件全部进分母(三名队友并行,写范围互斥、零越界):
net_socket 420 条 / 881-883=99.8%、52-52 函数(S1~S6 分片)
sys_log_mcu 75 条 / 310-313=99.0%、12-12
console_log 104 条 / 188-189=99.5%、15-15
cfg_file_store_mcu 34 条 / 97-97=100%、10-10
soe_store 26 条 / 78-79=98.7%、4-4
合计 659 条断言,P2 本体 1554-1561=99.6%、93-93 函数。
账:套件 28→33 全绿;files_tested 32→37;未触及 112→107(.c 34→29,
恒等式 29 = 0(P1剩) + 0(P2) + 29(P3) 咬合,§9 终点判据达成);分母
4684→6245 插桩行;line 85.8%→89.3%;func 90.1%→92.2%(这批 5 个文件
全都以 ≥98.7% 进分母,故未摊薄)。--update-baseline 前后逐文件比对:
下降 0 个文件。
基建:新建 test/host/common/fatfs_fake.{h,c}(以真实 ff.h 为声明源、只
定义三文件实际调用的 9 个 f_*,缺哪个链接期响;卷忙/短写/FR_DENIED
可注入;短名违规分档);扩展 ch395f_fake 为纯新增(g_fx/g_fxchip 另开
一份世界 + ext_reset 挂进既有复位,既有三套面一字不动),并按真头修正
两个错值常量(SOCKET_OPEN 0x01→0x05、TCP_ESTABLISHED 0x05→0x04),
另在 net_socket runner 加真头/假件同名常量文本核对闸(负例验证过)。
存量回归:net_listener 34、net_events 34、net_pending 43、net_select 22、
nand_port 149、lftpd_io 33、vol_lock 33、soe 32、cfg_file 151 全绿。
编号:补 TC-HOST-{NETSOCK,SYSLOG,CFGFST,SOESTORE,CONLOG} 五族(22→27,
先补族再引号);net_socket 模块码由自取的两字母 NS 正名为 NETSOCK;
id-check 增量拦截无台账外新账,可判定比例 83.27%(基线 82.6%)。
文档:覆盖率口径 §9 新增「P2 落地」(含两套分母口径差、10 条现状表、
12 条可复用手法与并发红线),§8 第 1 条改「清单已吃穿」,补变更记录。
本提交未改任何生产代码(App/Drivers/Src/Lib/MDK-ARM 零触碰)。
10 条现状已逐条登记在 §9(含 sys_log_mcu.c:509~537 卷忙整批丢日志、
net_socket.c 同文件两套 IP 字节序、UDP 两 API 绕过 netTask),已获指示
全部修复,另开一轮(含测试字面量统一纯 ASCII)。
gate: pre-commit run --all-files → 39 hooks, 38 Passed; only mem-numbers
Failed(本工作树从未 @build,无 .map 事实源,属分支期已知陷阱);
mem-budget Passed;test-coverage 与 id-check Passed。RAM/Flash 增量 = 0
(纯测试批次)。按仓内分支约定走 --no-verify,台账回填留待主树合并后。
This commit is contained in:
@@ -0,0 +1,922 @@
|
||||
/*
|
||||
* 模块名称:控制台单写者日志(console_log)主机侧单元测试
|
||||
* 模块功能:在 PC 上离线验证 App/log/console_log.c 的单写者契约(G1~G5 / K1~K5,见
|
||||
* docs/子系统设计/控制台单写者日志设计说明.md):环形缓冲入环与分块发送、
|
||||
* 行级原子、192B 截断、写者未注册/ISR 上下文的无锁直发、缓冲满有界等待与
|
||||
* 丢弃可观测、TX 僵尸通道软复位自愈 + 300ms 冷却、DWT 与指令计数两种硬上界、
|
||||
* huart1.Instance 未初始化的 NULL 短路、fputc 重定向、drain 250ms 预算。
|
||||
*
|
||||
* 为什么要主机侧单测(而不是只用台架):
|
||||
* 本模块的风险全在"异常时刻"——UART 僵尸通道卡死、TIM7 停摆导致 uwTick 不往前走、
|
||||
* 开机 1.7s 喂狗容限被背靠背块超时吃掉(台架实测的停喂误复位)、写者注册前多任务裸发
|
||||
* 逐字节交错(2026-10-10/10-12 台架实证)。台架上这些要么一年一遇、要么现场已被
|
||||
* 复位冲掉;而它们的判据全是"数得清的数":192B 上限、256B 分块、4095B 可用环、
|
||||
* 200 tick 等待、20000 次计数上界、100/250/300ms 三条时间线。把它们做成断言,是这台
|
||||
* 设备上唯一能把这几条线钉死的办法(板端套件只能"在真机上跑一遍")。
|
||||
*
|
||||
* 桩的边界(细节见 stub/stm32f4_fake.h 头注释):
|
||||
* console_log.h / sys_events.h(含真实 SYS_EVT_CONSOLE_TX 位)用**真实头**;
|
||||
* usart.h 用桩,但只暴露本模块访问的 huart1 + MX_USART1_UART_Init,寄存器位号 /
|
||||
* 枚举值照抄真实头;USART SR 的 TXE/TC、DWT CYCCNT、HAL_GetTick、临界区、
|
||||
* __get_IPSR、vTaskDelay、osEventFlagsSet 做成可控 + 可观测的量。
|
||||
* ⚠ 本文件**刻意不定义**任何 syslog_* / DBG_* 符号:console_log.c 哪天把"TX 故障
|
||||
* 留痕"改回直接调日志子系统,链接就失败——那正是必须挡住的(write_line →
|
||||
* con_append → drain → syslog_post → write_line 成环,静态栈深在每个任务上虚高)。
|
||||
*
|
||||
* 覆盖点(编号族 TC-CONLOG-*,属主登记处 = 本文件;实占 104 号 / 104 条断言):
|
||||
* 000..006 宿主 printf 与本模块 fputc 的隔离 + 参数校验与 192B 截断
|
||||
* 010..018 写者未注册 = 无锁直发(不入环、不置事件位、零临界区、不等待、256B 有界)
|
||||
* 020..038 注册后走环形:入环不发送、按 256B 分块、多生产者不交错、跨环尾两段拷贝、
|
||||
* 入环长度钳 RING/2
|
||||
* 040..044 写者自记录先排空(不自等)/ drain 重入保护 / 未注册时 drain 让位
|
||||
* 050..062 缓冲满:DWT 硬上界丢弃、不覆盖旧行、等待中出现空间、无 DWT 时 200 tick
|
||||
* 上界、[ERR] dropped 直发不经环、读后即清
|
||||
* 070..088 TX 僵尸通道:软复位自愈序列、故障即止、WARN 只一次、tx_fault_log_take
|
||||
* 读后清、300ms 冷却两端、无符号回绕、tick==0 哨兵现状、半块/已上线仍判失败
|
||||
* 090..104 DWT 惰性使能三处寄存器写、使能失败退回 20000 次指令计数、超时判据不读
|
||||
* HAL_GetTick(round 8b 动机)、huart1.Instance == NULL 短路两路
|
||||
* 110..117 fputc 重定向(未注册直发 / 注册逐字节入环 / ISR 上下文裸发 / 环满时 ISR
|
||||
* 仍不等待)
|
||||
* 120..122 drain 单次 250ms 预算与下轮续排(让位喂狗但不丢字节)
|
||||
* 130..135 计数支路也能把整块发完(不只会超时)+ 其 TC 计数上界 + DWT 支路 TC 抖动
|
||||
* 后正常收尾 + con_append 直调时的自有参数校验
|
||||
*
|
||||
* 实测覆盖率(scripts/check_test_coverage.py --module console_log,/Od + /Zi 口径):
|
||||
* App/log/console_log.c **188/189 行 = 99.5%,函数 15/15 = 100%**。
|
||||
*
|
||||
* 故意没测(逐行说明见交付报告):
|
||||
* - console_log.c:352 唯一未覆盖行:snprintf 溢出后的钳位 `n = sizeof(note)-1`。
|
||||
* note[64],前缀 24B + "%lu"(unsigned long 最多 10 位)+ 后缀 8B = 最长 42B
|
||||
* < 63B,任何输入都到不了这一行 —— 纯防御,不是漏测。
|
||||
* - 真实周期时间量级:主机没有真 DWT CYCCNT 也没有 TIM7 的 uwTick,只能钉"判据在
|
||||
* 第几次读成立 / 自旋几次放弃",不能声称"实测 100ms 就返回"。
|
||||
* - ISR 与写者同时裸发的真总线交错(docs §13-O1):宿主单线程,造不出两个执行流。
|
||||
* - osEventFlagsWait 唤醒写者与 50ms 兜底:写者任务体在 App/task/default_task.c。
|
||||
* - MicroLIB 真实 retarget 链接语义:宿主 CRT 有自己的 fputc(实测与本模块符号
|
||||
* 冲突 -> LNK2005),故本 TU 把被测 fputc 改名纳入,只验函数体不验链接钩子。
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 入口:int main(void),返回 0=PASS / 1=FAIL;跑飞保险 exit(2)。
|
||||
*
|
||||
* 创建日期:2026-10-24
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* 平台假件先纳入:它给出 USART/DWT/HAL/内核入口的宿主等价声明(stub 目录排第一) */
|
||||
#include "stm32f4_fake.h"
|
||||
|
||||
/* ===================== 假件全局量(声明见 stub/stm32f4_fake.h) ===================== */
|
||||
uint32_t SystemCoreClock = 168000000U; /* 与 MCU 端一致(168 MHz) */
|
||||
|
||||
USART_TypeDef g_con_uart;
|
||||
CoreDebug_Type g_con_core_debug;
|
||||
DWT_Type g_con_dwt;
|
||||
UART_HandleTypeDef huart1;
|
||||
|
||||
uint32_t con_uart_tx_n;
|
||||
uint8_t con_uart_tx[CON_UART_TX_MAX];
|
||||
uint32_t con_uart_block_n;
|
||||
uint32_t con_uart_block_len[CON_UART_BLOCK_MAX];
|
||||
uint32_t con_uart_cur_len;
|
||||
uint32_t con_uart_txe_reads;
|
||||
uint32_t con_uart_tc_reads;
|
||||
int con_uart_txe_stuck;
|
||||
int con_uart_txe_after;
|
||||
int con_uart_txe_ready_left;
|
||||
int con_uart_tc_after;
|
||||
int con_uart_tc_stuck;
|
||||
int con_uart_pending;
|
||||
|
||||
uint32_t con_dwt_step;
|
||||
int con_dwt_ctrl_sticky;
|
||||
uint32_t con_dwt_reads;
|
||||
|
||||
uint32_t con_tick_now;
|
||||
uint32_t con_tick_step;
|
||||
uint32_t con_tick_reads;
|
||||
|
||||
static char s_task_writer; /* 假写者任务句柄(取地址当不透明句柄) */
|
||||
static char s_task_other; /* 假"别的任务"句柄 */
|
||||
TaskHandle_t con_task_self;
|
||||
|
||||
uint32_t con_delay_calls;
|
||||
void (*con_delay_hook)(void);
|
||||
|
||||
uint32_t con_crit_enter;
|
||||
uint32_t con_crit_exit;
|
||||
uint32_t con_crit_max_depth;
|
||||
static uint32_t s_crit_depth;
|
||||
|
||||
uint32_t con_ipsr;
|
||||
|
||||
osEventFlagsId_t sys_evtHandle; /* 真实 sys_events.h 声明的全局句柄 */
|
||||
static char s_evt_obj; /* 假事件组对象(非 NULL 即可) */
|
||||
uint32_t con_evt_sets;
|
||||
uint32_t con_evt_flags;
|
||||
|
||||
uint32_t con_nvic_clears;
|
||||
uint32_t con_rcc_disables;
|
||||
uint32_t con_rcc_enables;
|
||||
static uint32_t s_mx_init_calls;
|
||||
static int s_mx_repair; /* 1 = 重初始化把僵尸通道治好(模型行为) */
|
||||
|
||||
/* ===================== 假件实现 ===================== */
|
||||
|
||||
uint32_t HAL_GetTick(void)
|
||||
{
|
||||
uint32_t v = con_tick_now;
|
||||
|
||||
con_tick_reads++;
|
||||
if (con_tick_reads > 2000000U) {
|
||||
printf("[FATAL] fake HAL_GetTick called %lu times (runaway)\n",
|
||||
(unsigned long)con_tick_reads);
|
||||
exit(2);
|
||||
}
|
||||
con_tick_now += con_tick_step;
|
||||
return v;
|
||||
}
|
||||
|
||||
TaskHandle_t xTaskGetCurrentTaskHandle(void)
|
||||
{
|
||||
return con_task_self;
|
||||
}
|
||||
|
||||
void vTaskDelay(TickType_t ticks)
|
||||
{
|
||||
(void)ticks;
|
||||
con_delay_calls++;
|
||||
if (con_delay_calls > 5000U) {
|
||||
printf("[FATAL] fake vTaskDelay called %lu times (runaway)\n",
|
||||
(unsigned long)con_delay_calls);
|
||||
exit(2);
|
||||
}
|
||||
if (con_delay_hook != 0) {
|
||||
con_delay_hook();
|
||||
}
|
||||
}
|
||||
|
||||
void con_crit_push(void)
|
||||
{
|
||||
con_crit_enter++;
|
||||
s_crit_depth++;
|
||||
if (s_crit_depth > con_crit_max_depth) {
|
||||
con_crit_max_depth = s_crit_depth;
|
||||
}
|
||||
}
|
||||
|
||||
void con_crit_pop(void)
|
||||
{
|
||||
con_crit_exit++;
|
||||
if (s_crit_depth == 0U) {
|
||||
printf("[FATAL] critical section exit without enter\n");
|
||||
exit(2);
|
||||
}
|
||||
s_crit_depth--;
|
||||
}
|
||||
|
||||
osStatus_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags)
|
||||
{
|
||||
(void)ef_id;
|
||||
con_evt_sets++;
|
||||
con_evt_flags = flags;
|
||||
return (osStatus_t)flags;
|
||||
}
|
||||
|
||||
/* 真实版是 CubeMX 生成的 HAL_UART_Init 封装;桩按脚本决定"软复位到底治没治好" */
|
||||
void MX_USART1_UART_Init(void)
|
||||
{
|
||||
s_mx_init_calls++;
|
||||
huart1.Instance = &g_con_uart;
|
||||
huart1.gState = HAL_UART_STATE_READY;
|
||||
huart1.ErrorCode = HAL_UART_ERROR_NONE;
|
||||
if (s_mx_repair != 0) {
|
||||
con_uart_txe_stuck = 0;
|
||||
con_uart_txe_after = 0;
|
||||
con_uart_txe_ready_left = -1;
|
||||
con_uart_tc_after = 0;
|
||||
con_uart_tc_stuck = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void con_uart_reset(void)
|
||||
{
|
||||
memset(&g_con_uart, 0xFF, sizeof(g_con_uart)); /* SR 恒全 1:就绪与否由钩子决定 */
|
||||
con_uart_tx_n = 0U;
|
||||
con_uart_block_n = 0U;
|
||||
con_uart_cur_len = 0U;
|
||||
con_uart_txe_reads = 0U;
|
||||
con_uart_tc_reads = 0U;
|
||||
con_uart_txe_stuck = 0;
|
||||
con_uart_txe_after = 0;
|
||||
con_uart_txe_ready_left = -1;
|
||||
con_uart_tc_after = 0;
|
||||
con_uart_tc_stuck = 0;
|
||||
con_uart_pending = 0;
|
||||
}
|
||||
|
||||
void con_dwt_reset(void)
|
||||
{
|
||||
memset(&g_con_dwt, 0, sizeof(g_con_dwt));
|
||||
memset(&g_con_core_debug, 0, sizeof(g_con_core_debug));
|
||||
con_dwt_step = 0U;
|
||||
con_dwt_ctrl_sticky = 1;
|
||||
con_dwt_reads = 0U;
|
||||
}
|
||||
|
||||
/* ===================== 断言 ===================== */
|
||||
static int s_total, s_passed, s_failed;
|
||||
|
||||
#define CHECK(cond, name) do { \
|
||||
s_total++; \
|
||||
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
|
||||
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
|
||||
} while (0)
|
||||
|
||||
/* ===================== 被测代码(原样纳入生产源) =====================
|
||||
* ⚠ 唯一一处生产符号改名:**宿主 CRT 自己占用了 fputc 这个全局符号**(实测
|
||||
* libucrt.lib(fputc.obj) 与本 TU 冲突 -> LNK2005 / LNK1169,ARMCC + MicroLIB 上
|
||||
* 没有这件事,那里 fputc 正是留给用户实现的 retarget 钩子)。所以把被测函数临时
|
||||
* 改名后再纳入,函数体与行覆盖统计一字未动,测试直接调 con_fputc_under_test 验它。
|
||||
* 顺带回答"测试自己的 printf 会不会被这个钩子吞掉":不会——宿主 CRT 用的是内部
|
||||
* _fputc_nolock/_output 那条链,不解析用户态 fputc;TC-CONLOG-000 再钉一次。 */
|
||||
#define fputc con_fputc_under_test
|
||||
#include "console_log.c"
|
||||
#undef fputc
|
||||
|
||||
/* ===================== 复位:生产 static + 假件,一次不漏 ===================== */
|
||||
static void reset_all(void)
|
||||
{
|
||||
/* 生产模块的 static(10 个数据项 + 1 个任务句柄)——漏一个就会跨场景污染 */
|
||||
memset(s_con_ring, 0, sizeof(s_con_ring));
|
||||
s_con_head = 0U;
|
||||
s_con_tail = 0U;
|
||||
s_con_drop = 0U;
|
||||
s_con_tx_fault = 0U;
|
||||
s_con_tx_fault_log = 0U;
|
||||
s_con_cool_until = 0U;
|
||||
s_con_draining = 0U;
|
||||
s_con_writer_up = 0U;
|
||||
s_con_dwt_up = 0U;
|
||||
s_con_writer_task = 0;
|
||||
|
||||
con_uart_reset();
|
||||
con_dwt_reset();
|
||||
|
||||
con_tick_now = 0U;
|
||||
con_tick_step = 0U;
|
||||
con_tick_reads = 0U;
|
||||
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
con_delay_calls = 0U;
|
||||
con_delay_hook = 0;
|
||||
|
||||
con_crit_enter = 0U;
|
||||
con_crit_exit = 0U;
|
||||
con_crit_max_depth = 0U;
|
||||
s_crit_depth = 0U;
|
||||
|
||||
con_ipsr = 0U;
|
||||
|
||||
sys_evtHandle = (osEventFlagsId_t)&s_evt_obj;
|
||||
con_evt_sets = 0U;
|
||||
con_evt_flags = 0U;
|
||||
|
||||
con_nvic_clears = 0U;
|
||||
con_rcc_disables = 0U;
|
||||
con_rcc_enables = 0U;
|
||||
s_mx_init_calls = 0U;
|
||||
s_mx_repair = 0;
|
||||
|
||||
huart1.Instance = &g_con_uart;
|
||||
huart1.gState = HAL_UART_STATE_READY;
|
||||
huart1.RxState = HAL_UART_STATE_READY;
|
||||
huart1.ErrorCode = HAL_UART_ERROR_NONE;
|
||||
}
|
||||
|
||||
/* ===================== 观察辅助 ===================== */
|
||||
static uint32_t used_now(void)
|
||||
{
|
||||
return con_used();
|
||||
}
|
||||
|
||||
static const char *tx_at(uint32_t skip)
|
||||
{
|
||||
return (skip >= con_uart_tx_n) ? "" : (const char *)&con_uart_tx[skip];
|
||||
}
|
||||
|
||||
static int tx_is(uint32_t skip, const char *s)
|
||||
{
|
||||
size_t n = strlen(s);
|
||||
|
||||
if ((size_t)con_uart_tx_n < (size_t)skip + n) {
|
||||
return 0;
|
||||
}
|
||||
return memcmp(tx_at(skip), s, n) == 0;
|
||||
}
|
||||
|
||||
/* 在已上线字节里找一段(tx 是二进制缓冲,不依赖 NUL 结尾) */
|
||||
static int tx_contains(const char *needle)
|
||||
{
|
||||
size_t nl = strlen(needle);
|
||||
uint32_t i;
|
||||
|
||||
if ((size_t)con_uart_tx_n < nl) {
|
||||
return 0;
|
||||
}
|
||||
for (i = 0U; (size_t)i + nl <= (size_t)con_uart_tx_n; i++) {
|
||||
if (memcmp(&con_uart_tx[i], needle, nl) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static uint32_t tx_count(const char *needle)
|
||||
{
|
||||
size_t nl = strlen(needle);
|
||||
uint32_t i;
|
||||
uint32_t hits = 0U;
|
||||
|
||||
if ((size_t)con_uart_tx_n < nl) {
|
||||
return 0U;
|
||||
}
|
||||
for (i = 0U; (size_t)i + nl <= (size_t)con_uart_tx_n; i++) {
|
||||
if (memcmp(&con_uart_tx[i], needle, nl) == 0) {
|
||||
hits++;
|
||||
}
|
||||
}
|
||||
return hits;
|
||||
}
|
||||
|
||||
/* 写者任务上下文里排空(生产契约:drain 只允许写者任务调) */
|
||||
static void writer_drain(void)
|
||||
{
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
dbg_console_drain();
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
}
|
||||
|
||||
/* 后台排空(不改当前任务句柄):模拟"生产者等一轮,写者腾出了空间" */
|
||||
static void bg_drain_hook(void)
|
||||
{
|
||||
dbg_console_drain();
|
||||
}
|
||||
|
||||
/* 把环填到只剩 want_free 字节可用:走真实入环路径(顺带踩到回绕)。
|
||||
* 填充期间把 DWT 上界调大——万一算差一位也不会卡在等待里出不来。 */
|
||||
static void fill_ring(uint32_t want_free)
|
||||
{
|
||||
char pad[DBG_CON_LINE_MAX];
|
||||
uint32_t i;
|
||||
|
||||
memset(pad, 'Z', sizeof(pad));
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
for (i = 0U; i < 40U; i++) {
|
||||
uint32_t free_space = (uint32_t)((DBG_CON_RING_SIZE - 1U) - used_now());
|
||||
uint32_t n;
|
||||
|
||||
if (free_space <= want_free) {
|
||||
break;
|
||||
}
|
||||
n = free_space - want_free;
|
||||
if (n > DBG_CON_LINE_MAX) {
|
||||
n = DBG_CON_LINE_MAX;
|
||||
}
|
||||
if (dbg_console_write_line(pad, n) != (int)n) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
con_dwt_step = 0U;
|
||||
s_con_drop = 0U; /* 填充过程中的边角丢弃不算场景断言 */
|
||||
}
|
||||
|
||||
/* 注册写者(defaultTask),随后把"当前任务"换回别的任务 */
|
||||
static void attach_writer(void)
|
||||
{
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
dbg_console_writer_attach();
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
}
|
||||
/* ============ TC-CONLOG-000..006 参数校验与截断 ============ */
|
||||
static void tc_params(void)
|
||||
{
|
||||
char buf[DBG_CON_LINE_MAX + 128U];
|
||||
|
||||
printf("-- TC-CONLOG-000..006 参数校验与截断 --\n");
|
||||
reset_all();
|
||||
printf("(probe) 这行走宿主 CRT\n");
|
||||
fflush(stdout);
|
||||
CHECK(con_uart_tx_n == 0U && used_now() == 0U,
|
||||
"TC-CONLOG-000 宿主 printf 不经过本模块 fputc(否则测试输出会被吞进环)");
|
||||
|
||||
reset_all();
|
||||
con_uart_txe_stuck = 1; /* 通道卡死:任何字节都不该上线 */
|
||||
CHECK(dbg_console_write_line(0, 5U) == -1, "TC-CONLOG-001 write_line(NULL) 返回 -1");
|
||||
CHECK(dbg_console_write_line("abc", 0U) == -1, "TC-CONLOG-002 write_line(len=0) 返回 -1");
|
||||
dbg_console_write_raw(0, 5U);
|
||||
dbg_console_write_raw("abc", 0U);
|
||||
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-003 两路 NULL/0 都不碰 UART");
|
||||
CHECK(dbg_console_dropped() == 0U, "TC-CONLOG-004 参数非法不计入丢弃");
|
||||
|
||||
reset_all();
|
||||
memset(buf, 'A', sizeof(buf));
|
||||
CHECK(dbg_console_write_line(buf, sizeof(buf)) == (int)DBG_CON_LINE_MAX,
|
||||
"TC-CONLOG-005 超过 DBG_CON_LINE_MAX 的行按 192 截断(返回值即交付字节数)");
|
||||
CHECK(con_uart_tx_n == DBG_CON_LINE_MAX, "TC-CONLOG-006 截断后恰好上线 192 字节");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-010..018 写者未注册 = 无锁直发 ============ */
|
||||
static void tc_unattached(void)
|
||||
{
|
||||
char big[DBG_CON_CHUNK_MAX + 64U];
|
||||
|
||||
printf("-- TC-CONLOG-010..018 写者未注册:无锁直发 --\n");
|
||||
reset_all();
|
||||
CHECK(dbg_console_writer_ready() == 0U, "TC-CONLOG-010 attach 之前 writer_ready() == 0");
|
||||
CHECK(dbg_console_write_line("hello", 5U) == 5, "TC-CONLOG-011 未注册时整行直发,返回行长");
|
||||
CHECK(tx_is(0U, "hello"), "TC-CONLOG-012 直发内容原样上线(没经过环形缓冲)");
|
||||
CHECK(used_now() == 0U, "TC-CONLOG-013 直发不入环(head == tail)");
|
||||
CHECK(con_evt_sets == 0U, "TC-CONLOG-014 直发不置 SYS_EVT_CONSOLE_TX(还没写者可唤醒)");
|
||||
CHECK(con_crit_enter == 0U,
|
||||
"TC-CONLOG-015 直发零临界区 = 2026-10-10 逐字节交错的根因(不是丢整行)");
|
||||
|
||||
reset_all();
|
||||
memset(big, 'B', sizeof(big));
|
||||
dbg_console_write_raw(big, sizeof(big));
|
||||
CHECK(con_uart_tx_n == DBG_CON_CHUNK_MAX, "TC-CONLOG-016 write_raw 有界:只发 256 字节");
|
||||
|
||||
reset_all();
|
||||
con_uart_txe_after = 3; /* 前 3 次不就绪(TXE 抖动,未超上界) */
|
||||
CHECK(dbg_console_write_line("CD", 2U) == 2, "TC-CONLOG-017 TXE 抖动后仍发全");
|
||||
CHECK(tx_is(0U, "CD") && con_delay_calls == 0U,
|
||||
"TC-CONLOG-018 直发既不等 tick 也不入环(ISR/崩溃钩子可用的前提)");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-020..037 注册后走环形缓冲 ============ */
|
||||
static void tc_ring(void)
|
||||
{
|
||||
char line[DBG_CON_LINE_MAX];
|
||||
static char oversize[3000]; /* 超长入环的源(>= 钳位后的 2048B) */
|
||||
uint32_t i;
|
||||
|
||||
printf("-- TC-CONLOG-020..027 注册后:生产者入环、写者发送 --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
CHECK(dbg_console_writer_ready() == 1U, "TC-CONLOG-020 attach 之后 writer_ready() == 1");
|
||||
CHECK(s_con_writer_task == (TaskHandle_t)&s_task_writer,
|
||||
"TC-CONLOG-021 attach 记下的正是调用方的任务句柄(自等保护的依据)");
|
||||
CHECK(dbg_console_write_line("netTask up\r\n", 12U) == 12, "TC-CONLOG-022 入环成功返回行长");
|
||||
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-023 生产者绝不碰 UART");
|
||||
CHECK(used_now() == 12U, "TC-CONLOG-024 整行一次入环(used = 12)");
|
||||
CHECK(con_evt_sets == 1U && con_evt_flags == SYS_EVT_CONSOLE_TX,
|
||||
"TC-CONLOG-025 入环后置真实事件位 SYS_EVT_CONSOLE_TX(bit6)唤醒写者");
|
||||
CHECK(con_crit_enter == 1U && con_crit_exit == 1U && con_crit_max_depth == 1U,
|
||||
"TC-CONLOG-026 整行入环恰好一层临界区(行级原子的实现依据)");
|
||||
|
||||
writer_drain();
|
||||
CHECK(tx_is(0U, "netTask up\r\n"), "TC-CONLOG-027 写者排空:上线字节与入环内容逐字节相同");
|
||||
|
||||
printf("-- TC-CONLOG-028..032 分块 256B 与多生产者不交错 --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
memset(line, 'C', sizeof(line));
|
||||
for (i = 0U; i < 5U; i++) {
|
||||
(void)dbg_console_write_line(line, sizeof(line));
|
||||
}
|
||||
writer_drain();
|
||||
CHECK(con_uart_block_n == 4U, "TC-CONLOG-028 960B 按 DBG_CON_CHUNK_MAX 切成 4 块");
|
||||
CHECK(con_uart_block_len[0] == 256U && con_uart_block_len[1] == 256U
|
||||
&& con_uart_block_len[2] == 256U && con_uart_block_len[3] == 192U,
|
||||
"TC-CONLOG-029 块长 256/256/256/192(单次发送上限真被执行)");
|
||||
CHECK(con_uart_tx_n == 960U && con_uart_txe_reads == 960U,
|
||||
"TC-CONLOG-030 每字节恰好装载一次 DR(就绪即写,没有重复发)");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
(void)dbg_console_write_line("[A] 11111\r\n", 11U);
|
||||
con_task_self = (TaskHandle_t)&s_task_writer; /* 写者自己也记一行 -> 先排空 */
|
||||
(void)dbg_console_write_line("[B] 22222\r\n", 11U);
|
||||
con_task_self = (TaskHandle_t)0; /* 连"无句柄"也算一个生产者 */
|
||||
(void)dbg_console_write_line("[C] 33333\r\n", 11U);
|
||||
writer_drain();
|
||||
CHECK(tx_is(0U, "[A] 11111\r\n[B] 22222\r\n[C] 33333\r\n"),
|
||||
"TC-CONLOG-031 三个生产者各写一行 -> 上线仍是三行连续原文(G1 行级原子)");
|
||||
CHECK(strstr((const char *)&con_uart_tx[5], "[B] 22222") != 0,
|
||||
"TC-CONLOG-032 行边界没被打断(没有任何一行被插进另一行中间)");
|
||||
|
||||
printf("-- TC-CONLOG-033..037 跨环尾:两段拷贝 + 按连续段分块 --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
s_con_head = 4090U;
|
||||
s_con_tail = 4090U;
|
||||
(void)dbg_console_write_line("0123456789ABCDEF", 16U);
|
||||
CHECK(s_con_head == 10U && used_now() == 16U,
|
||||
"TC-CONLOG-033 跨环尾入环后 head 回绕到 10(4090+16-4096)");
|
||||
CHECK(memcmp(&s_con_ring[4090], "012345", 6) == 0
|
||||
&& s_con_ring[0] == (uint8_t)'6' && s_con_ring[9] == (uint8_t)'F'
|
||||
&& s_con_ring[10] == 0U,
|
||||
"TC-CONLOG-034 回绕两段拷贝位置正确(前 6B 填到环尾、余 10B 从 0 起)");
|
||||
writer_drain();
|
||||
CHECK(con_uart_block_n == 2U && con_uart_block_len[0] == 6U
|
||||
&& con_uart_block_len[1] == 10U,
|
||||
"TC-CONLOG-035 drain 按连续段分块:6 + 10(绝不把环中间当连续段读越界)");
|
||||
CHECK(tx_is(0U, "0123456789ABCDEF"), "TC-CONLOG-036 回绕行的上线内容与顺序都对");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
/* 源缓冲必须给到 3000B:con_append 只钳长度,不会钳读取范围,
|
||||
* 拿 192B 的栈数组当 2048B 源就是越界读(插桩跑直接 0xC0000005) */
|
||||
memset(oversize, 'D', sizeof(oversize));
|
||||
CHECK(con_append(oversize, (uint16_t)3000U) == 0, "TC-CONLOG-037 直调 con_append 超长仍成功");
|
||||
CHECK(used_now() == (DBG_CON_RING_SIZE / 2U),
|
||||
"TC-CONLOG-038 入环长度钳在 RING/2=2048(保证任何一行都恒可入环)");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-040..042 写者自记录 / 重入 / 未注册 drain ============ */
|
||||
static void tc_writer_self(void)
|
||||
{
|
||||
char line[DBG_CON_LINE_MAX];
|
||||
uint32_t i;
|
||||
|
||||
printf("-- TC-CONLOG-040..042 写者自记录先排空 / drain 重入保护 --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
memset(line, 'E', sizeof(line));
|
||||
for (i = 0U; i < 21U; i++) { /* 21*192 = 4032,只剩 63B 可用 */
|
||||
(void)dbg_console_write_line(line, sizeof(line));
|
||||
}
|
||||
con_task_self = (TaskHandle_t)&s_task_writer; /* 写者自己记一行 */
|
||||
CHECK(dbg_console_write_line("F", 1U) == 1, "TC-CONLOG-040 写者自记录不丢行");
|
||||
CHECK(con_delay_calls == 0U,
|
||||
"TC-CONLOG-041 写者自记录先排空再入环:绝不进等待(否则就是等自己 -> 死锁)");
|
||||
CHECK(con_uart_tx_n == 4032U && used_now() == 1U && dbg_console_dropped() == 0U,
|
||||
"TC-CONLOG-042 老行全部上线、新行入环,零丢弃");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
(void)dbg_console_write_line("hold", 4U);
|
||||
s_con_draining = 1U; /* 模拟正处于 drain 之内 */
|
||||
writer_drain();
|
||||
CHECK(con_uart_tx_n == 0U && used_now() == 4U,
|
||||
"TC-CONLOG-043 drain 重入保护:嵌套调用直接让位(写者自记录不触发递归 drain)");
|
||||
|
||||
reset_all();
|
||||
(void)dbg_console_write_line("early\r\n", 7U); /* 未注册 -> 已直发 */
|
||||
con_uart_tx_n = 0U;
|
||||
writer_drain();
|
||||
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-044 写者未注册时 drain 不做任何事");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-050..062 缓冲满 / 丢弃 / 可观测 ============ */
|
||||
static void tc_full_drop(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-050..053 缓冲满:DWT 硬上界丢弃且不覆盖旧行 --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(10U); /* 只剩 10B 可用 */
|
||||
con_dwt_step = SystemCoreClock / 4U; /* 一次判据 = 250ms > 200ms 上界 */
|
||||
CHECK(dbg_console_write_line("0123456789ABCDEFGHIJ", 20U) == -1,
|
||||
"TC-CONLOG-050 环满 + DWT 上界到 -> 丢这一行(返回 -1)");
|
||||
CHECK(dbg_console_dropped() == 1U, "TC-CONLOG-051 丢弃计数 +1");
|
||||
CHECK(con_delay_calls == 0U,
|
||||
"TC-CONLOG-052 有 DWT 时用周期判据,不必先烧 200 个 tick(K4)");
|
||||
CHECK(s_con_head == 4085U && used_now() == 4085U && con_uart_tx_n == 0U,
|
||||
"TC-CONLOG-053 丢弃不推进 head、也不生产者自己发(没半写、更没覆盖未发出的旧行)");
|
||||
|
||||
printf("-- TC-CONLOG-054..056 等待中出现空间 -> 入环成功 --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(10U);
|
||||
con_dwt_step = 0U; /* 时间静止:只能靠写者腾地方 */
|
||||
con_delay_hook = bg_drain_hook; /* 每等一轮 = 写者排空一次 */
|
||||
CHECK(dbg_console_write_line("0123456789ABCDEFGHIJ", 20U) == 20,
|
||||
"TC-CONLOG-054 等一轮后腾出空间 -> 整行入环成功");
|
||||
CHECK(con_delay_calls == 1U && dbg_console_dropped() == 0U,
|
||||
"TC-CONLOG-055 只等了一轮,且不算丢弃");
|
||||
con_delay_hook = 0;
|
||||
CHECK(con_uart_tx_n == 4085U, "TC-CONLOG-056 腾地方的正是写者(老行已上线)");
|
||||
|
||||
printf("-- TC-CONLOG-057..059 无 DWT 支路:等待上界 = DBG_CON_WAIT_MS 个 tick --\n");
|
||||
reset_all();
|
||||
con_dwt_ctrl_sticky = 0; /* 调试单元被关 -> DWT 使能失败 */
|
||||
attach_writer();
|
||||
fill_ring(10U);
|
||||
CHECK(s_con_dwt_up == 0U, "TC-CONLOG-057 DWT 使能失败时 s_con_dwt_up 保持 0");
|
||||
CHECK(dbg_console_write_line("0123456789ABCDEFGHIJ", 20U) == -1,
|
||||
"TC-CONLOG-058 无 DWT 时仍会丢(判据退回 tick 计数,不会无限等)");
|
||||
CHECK(con_delay_calls == DBG_CON_WAIT_MS && dbg_console_dropped() == 1U,
|
||||
"TC-CONLOG-059 无 DWT 的等待上界恰为 DBG_CON_WAIT_MS(200) 次 vTaskDelay");
|
||||
|
||||
printf("-- TC-CONLOG-060..062 丢弃可观测:[ERR] 行直发不经环 --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(10U);
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
(void)dbg_console_write_line("0123456789ABCDEFGHIJ", 20U); /* 丢 1 行 */
|
||||
con_dwt_step = 0U;
|
||||
writer_drain();
|
||||
CHECK(tx_contains("[ERR] CONSOLE dropped 1 lines"),
|
||||
"TC-CONLOG-060 排空末尾刷出 [ERR] CONSOLE dropped 1 lines(丢行永不静默,@test 判 FAIL)");
|
||||
CHECK(dbg_console_dropped() == 0U, "TC-CONLOG-061 刷完即清零(下一轮不重复告警)");
|
||||
CHECK(used_now() == 0U,
|
||||
"TC-CONLOG-062 告警行走裸发:不入环 -> 不会 con_append->drain 递归");
|
||||
}
|
||||
/* ============ TC-CONLOG-070..081 TX 僵尸通道:自愈 / 冷却 / 半块 ============ */
|
||||
|
||||
/* 公共前置:注册写者 + 把环填满 + 让通道卡死 */
|
||||
static void fault_setup(uint32_t tick0, int repair)
|
||||
{
|
||||
attach_writer();
|
||||
fill_ring(0U);
|
||||
con_tick_now = tick0;
|
||||
con_uart_txe_stuck = 1;
|
||||
con_dwt_step = SystemCoreClock / 4U; /* 一次判据就跨过 CON_TX_TIMEOUT_CYC */
|
||||
s_mx_repair = repair;
|
||||
writer_drain();
|
||||
}
|
||||
|
||||
static void tc_tx_fault(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-070..074 软复位没治好:故障即止 + 留痕只能靠标志 --\n");
|
||||
reset_all();
|
||||
fault_setup(5000U, 0);
|
||||
CHECK(dbg_console_tx_fault() == 1U, "TC-CONLOG-070 发送超时 -> tx_fault 计数 +1");
|
||||
CHECK(s_mx_init_calls == 1U && con_nvic_clears == 1U
|
||||
&& con_rcc_disables == 1U && con_rcc_enables == 1U,
|
||||
"TC-CONLOG-071 自愈完整一遍:清 pending -> 断时钟 -> 供时钟 -> 重初始化");
|
||||
CHECK(used_now() == (DBG_CON_RING_SIZE - 1U) - DBG_CON_CHUNK_MAX,
|
||||
"TC-CONLOG-072 故障即止:只丢当前 256B 块,其余留环里等下轮(让位喂狗)");
|
||||
CHECK(con_uart_tx_n == 0U,
|
||||
"TC-CONLOG-073 通道没治好时连那条 WARN 也上不了线(裸发同样有界、不重试)");
|
||||
CHECK(dbg_console_tx_fault_log_take() == 1U && dbg_console_tx_fault_log_take() == 0U,
|
||||
"TC-CONLOG-074 留痕标志读后清:写者据此在 drain 之外补记 run.log(断调用图成环)");
|
||||
|
||||
printf("-- TC-CONLOG-075..077 软复位治好了:WARN 上线且只一次 --\n");
|
||||
reset_all();
|
||||
con_tick_now = 7000U;
|
||||
attach_writer();
|
||||
(void)dbg_console_write_line("X", 1U);
|
||||
con_uart_txe_ready_left = 0; /* 一装载就卡死 */
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
s_mx_repair = 1; /* 重初始化把通道治好 */
|
||||
writer_drain();
|
||||
CHECK(tx_contains("[WARN] CONSOLE tx fault"), "TC-CONLOG-075 首次故障的 WARN 行成功上线");
|
||||
CHECK(used_now() == 0U && s_con_cool_until == 7000U && dbg_console_tx_fault() == 1U,
|
||||
"TC-CONLOG-076 故障块丢弃(行已出环不可重放)+ 冷却截止记为当时 tick");
|
||||
con_uart_txe_ready_left = -1;
|
||||
con_dwt_step = 0U;
|
||||
writer_drain(); /* tick 未推进 -> 仍在冷却 */
|
||||
CHECK(s_con_cool_until == 7000U && s_mx_init_calls == 1U,
|
||||
"TC-CONLOG-077 冷却期内 drain 直接让位:不重试、不自愈第二次");
|
||||
}
|
||||
|
||||
static void tc_tx_cool(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-078..084 300ms 冷却两端 + 回绕 + tick==0 现状 --\n");
|
||||
reset_all();
|
||||
fault_setup(7000U, 1); /* 治好的那次故障 */
|
||||
CHECK(s_con_cool_until == 7000U && used_now() == 3839U,
|
||||
"TC-CONLOG-078 故障后剩 3839B 在环里(本块被丢)");
|
||||
con_dwt_step = 0U;
|
||||
con_uart_txe_stuck = 0;
|
||||
con_tick_now = 7299U; /* 差 1ms */
|
||||
writer_drain();
|
||||
CHECK(s_con_cool_until == 7000U && used_now() == 3839U && s_mx_init_calls == 1U,
|
||||
"TC-CONLOG-079 差 1ms 仍在冷却:环不动、不重复自愈(冷却即节奏保护)");
|
||||
con_tick_now = 7300U; /* 到点 */
|
||||
writer_drain();
|
||||
CHECK(s_con_cool_until == 0U && used_now() == 0U && dbg_console_tx_fault() == 1U,
|
||||
"TC-CONLOG-080 到点即清冷却、放行本轮排空,且没有新增故障");
|
||||
|
||||
reset_all();
|
||||
fault_setup(0U, 0);
|
||||
CHECK(s_con_cool_until == 0U && dbg_console_tx_fault() == 1U,
|
||||
"TC-CONLOG-081 现状:tick==0 时记出的 cool_until 撞上 0 哨兵 = 等于没冷却");
|
||||
writer_drain();
|
||||
CHECK(dbg_console_tx_fault() == 2U
|
||||
&& used_now() == (DBG_CON_RING_SIZE - 1U) - 2U * DBG_CON_CHUNK_MAX,
|
||||
"TC-CONLOG-082 上一条的后果:下一轮立刻再耗一次块超时(节奏保护在这一 tick 失效)");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(0U);
|
||||
s_con_cool_until = 0xFFFFFFFFU; /* 上轮故障记在回绕点 */
|
||||
con_tick_now = 100U; /* 现在刚回绕到 100 */
|
||||
writer_drain();
|
||||
CHECK(s_con_cool_until == 0xFFFFFFFFU && used_now() == 4095U,
|
||||
"TC-CONLOG-083 跨 49.7 天回绕仍判在冷却内(无符号差值 101 < 300)");
|
||||
con_tick_now = 401U; /* 无符号差值 402 >= 300 */
|
||||
writer_drain();
|
||||
CHECK(s_con_cool_until == 0U && used_now() == 0U,
|
||||
"TC-CONLOG-084 回绕后到点 -> 清冷却并把 4095B 全排空");
|
||||
}
|
||||
|
||||
static void tc_tx_half(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-085..088 超时判据的真实后果:半块 / 已上线仍判失败 --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
(void)dbg_console_write_line("ABCDEFGH", 8U);
|
||||
con_uart_tc_stuck = 1; /* 字节都进了 DR,只有 TC 不置位 */
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
writer_drain();
|
||||
CHECK(tx_is(0U, "ABCDEFGH"), "TC-CONLOG-085 TC 卡死:8 字节都已上线");
|
||||
CHECK(dbg_console_tx_fault() == 1U && con_uart_block_n == 0U,
|
||||
"TC-CONLOG-086 已上线仍整块判失败(docs §13-O2:丢弃/故障两本账没分开的现状)");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
(void)dbg_console_write_line("ABCDEFGH", 8U);
|
||||
con_uart_txe_ready_left = 3; /* 只挤得出 3 个字节 */
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
writer_drain();
|
||||
CHECK(tx_is(0U, "ABC"), "TC-CONLOG-087 TXE 中途卡死 -> 只有已写进 DR 的 3 字节上线");
|
||||
CHECK(dbg_console_tx_fault() == 1U && used_now() == 0U,
|
||||
"TC-CONLOG-088 半块同样走自愈分支,整块出环(尾部 5 字节随块作废)");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-090..099 DWT 使能 / 指令计数上界 ============ */
|
||||
static void tc_dwt(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-090..093 DWT 惰性使能与就绪即走 --\n");
|
||||
reset_all();
|
||||
(void)dbg_console_write_line("ok\r\n", 4U); /* 走一次直发 -> 顺带使能 DWT */
|
||||
CHECK(s_con_dwt_up == 1U, "TC-CONLOG-090 con_dwt_ensure 成功使能后置 s_con_dwt_up");
|
||||
CHECK((g_con_core_debug.DEMCR & CoreDebug_DEMCR_TRCENA_Msk) != 0U,
|
||||
"TC-CONLOG-091 真的写了 DEMCR.TRCENA(位号与 core_cm4.h 一致)");
|
||||
CHECK((g_con_dwt.CTRL & DWT_CTRL_CYCCNTENA_Msk) != 0U,
|
||||
"TC-CONLOG-092 真的写了 DWT.CTRL.CYCCNTENA");
|
||||
CHECK(tx_is(0U, "ok\r\n") && con_dwt_reads == 4U,
|
||||
"TC-CONLOG-093 通道就绪时发送循环没再读 CYCCNT(3 次 ensure + 1 次取基准 t0)");
|
||||
|
||||
printf("-- TC-CONLOG-094..097 DWT 使能失败 -> con_tx_counted 计数上界 --\n");
|
||||
reset_all();
|
||||
con_dwt_ctrl_sticky = 0; /* CYCCNTENA 写不进去 */
|
||||
con_uart_txe_stuck = 1;
|
||||
dbg_console_write_raw("Q", 1U);
|
||||
CHECK(s_con_dwt_up == 0U, "TC-CONLOG-094 DWT 使能失败 -> 走计数上界支路");
|
||||
CHECK(con_uart_txe_reads == CON_TX_GUARD_MAX,
|
||||
"TC-CONLOG-095 每字节最多自旋 CON_TX_GUARD_MAX(20000) 次即放弃(有界,非死等)");
|
||||
CHECK(con_uart_tx_n == 0U, "TC-CONLOG-096 计数上界内没发出去就整块放弃");
|
||||
CHECK(con_dwt_reads == 3U,
|
||||
"TC-CONLOG-097 计数支路一次都不读 CYCCNT(不依赖 DWT,也就不依赖时间源)");
|
||||
|
||||
printf("-- TC-CONLOG-098..099 超时判据不依赖 HAL_GetTick(round 8b 的动机) --\n");
|
||||
reset_all();
|
||||
con_uart_txe_stuck = 1; /* tick 冻结在 0,模拟 TIM7 停摆 */
|
||||
con_dwt_step = SystemCoreClock / 10U; /* 一次判据正好 = 100ms 上界 */
|
||||
dbg_console_write_raw("Q", 1U);
|
||||
CHECK(con_tick_reads == 0U && con_uart_tx_n == 0U,
|
||||
"TC-CONLOG-098 tick 完全不走也能退出:裸发一次都没读 HAL_GetTick");
|
||||
reset_all();
|
||||
con_uart_txe_stuck = 1;
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
dbg_console_write_raw("Z", 1U);
|
||||
CHECK(con_uart_txe_reads == 1U,
|
||||
"TC-CONLOG-099 DWT 支路第一次自旋就判超时(上界真的起作用,不会拖满 20000 次)");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-100..104 huart1.Instance == NULL 短路 ============ */
|
||||
static void tc_null_uart(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-100..104 USART1 未初始化:安全让位,不再触发二次故障 --\n");
|
||||
reset_all();
|
||||
huart1.Instance = 0;
|
||||
dbg_console_write_raw("boom", 5U);
|
||||
CHECK(con_uart_tx_n == 0U && con_uart_txe_reads == 0U,
|
||||
"TC-CONLOG-100 Instance==NULL 时一次寄存器都不碰(不解引用地址 0)");
|
||||
CHECK(con_dwt_reads == 3U,
|
||||
"TC-CONLOG-101 仍然先惰性使能 DWT 再短路(ensure 在判空之前,顺序照旧)");
|
||||
|
||||
reset_all();
|
||||
huart1.Instance = 0;
|
||||
CHECK(dbg_console_write_line("early", 5U) == 5,
|
||||
"TC-CONLOG-102 未注册路径(内核前)撞上无串口也不崩、仍返回行长");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
(void)dbg_console_write_line("abc", 3U);
|
||||
huart1.Instance = 0; /* 写者起来了但串口被毁 */
|
||||
s_mx_repair = 1;
|
||||
writer_drain();
|
||||
CHECK(dbg_console_tx_fault() == 1U && s_mx_init_calls == 1U && huart1.Instance != 0,
|
||||
"TC-CONLOG-103 未初始化也被当作僵尸通道自愈:重初始化把句柄装回来");
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
writer_drain(); /* 仍在冷却 -> 让位 */
|
||||
CHECK(used_now() == 0U, "TC-CONLOG-104 自愈那一轮已把出环字节丢掉,没留下脏数据");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-110..115 fputc 重定向 ============ */
|
||||
static void tc_fputc(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-110..115 fputc 重定向(printf 遗留路径) --\n");
|
||||
reset_all();
|
||||
CHECK(con_fputc_under_test('Q', stdout) == 'Q', "TC-CONLOG-110 fputc 原样返回字符(C 库约定)");
|
||||
CHECK(tx_is(0U, "Q") && used_now() == 0U,
|
||||
"TC-CONLOG-111 未注册写者:printf 的字节直发(与旧行为一致)");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
(void)con_fputc_under_test('R', stdout);
|
||||
CHECK(con_uart_tx_n == 0U && used_now() == 1U && s_con_ring[0] == (uint8_t)'R',
|
||||
"TC-CONLOG-112 注册后 fputc 也收敛到单写者路径(逐字节入环)");
|
||||
writer_drain();
|
||||
CHECK(tx_is(0U, "R"), "TC-CONLOG-113 写者把 printf 字节发出");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
con_ipsr = 12U; /* 假异常号:任意非 0 */
|
||||
CHECK(dbg_console_write_line("ISR", 3U) == 3, "TC-CONLOG-114 中断上下文写整行返回行长");
|
||||
CHECK(tx_is(0U, "ISR") && used_now() == 0U && con_delay_calls == 0U,
|
||||
"TC-CONLOG-115 中断上下文一律裸发:不入环、不等待(否则在 ISR 里睡死)");
|
||||
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(0U); /* 环全满 */
|
||||
con_ipsr = 5U;
|
||||
con_dwt_step = SystemCoreClock / 4U;
|
||||
CHECK(dbg_console_write_line("0123456789", 10U) == 10,
|
||||
"TC-CONLOG-116 环全满 + 中断上下文:仍直发成功(ISR 绝不进丢弃等待)");
|
||||
CHECK(dbg_console_dropped() == 0U && con_delay_calls == 0U,
|
||||
"TC-CONLOG-117 这条路径既不丢行也不记丢弃(K1:内核前/ISR 不依赖任何内核对象)");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-120..122 drain 250ms 预算 ============ */
|
||||
static void tc_budget(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-120..122 单次 drain 预算 250ms(让位喂狗轮询) --\n");
|
||||
reset_all();
|
||||
attach_writer();
|
||||
fill_ring(0U); /* 4095B = 16 块 */
|
||||
con_tick_step = 130U; /* 每次读 tick 走 130ms */
|
||||
writer_drain();
|
||||
CHECK(con_uart_block_n == 2U, "TC-CONLOG-120 一轮只发 2 块就因预算让位(不是发完)");
|
||||
CHECK(used_now() == (DBG_CON_RING_SIZE - 1U) - (2U * DBG_CON_CHUNK_MAX),
|
||||
"TC-CONLOG-121 让位时余下 3583B 留在环里(下轮续排,不丢)");
|
||||
con_tick_step = 0U;
|
||||
writer_drain(); /* 预算内发完余下 3583B */
|
||||
CHECK(used_now() == 0U && con_uart_tx_n == (DBG_CON_RING_SIZE - 1U),
|
||||
"TC-CONLOG-122 下轮把余下的 3583B 也发完:预算让位不丢字节(累计 4095)");
|
||||
}
|
||||
|
||||
/* ============ TC-CONLOG-130..135 计数支路的成功路径 / TC 抖动 / 直调 NULL ============ */
|
||||
static void tc_counted_paths(void)
|
||||
{
|
||||
printf("-- TC-CONLOG-130..132 DWT 不可用 -> con_tx_counted 也能发全(不只会超时) --\n");
|
||||
reset_all();
|
||||
con_dwt_ctrl_sticky = 0; /* CYCCNTENA 落不住 */
|
||||
con_tick_now = 9000U; /* tick 冻结:证明与本支路无关 */
|
||||
dbg_console_write_raw("AB", 2U);
|
||||
CHECK(s_con_dwt_up == 0U && tx_is(0U, "AB"),
|
||||
"TC-CONLOG-130 计数支路把整块发完并返回成功(回退路径没退化)");
|
||||
CHECK(con_uart_tc_reads == 1U && con_dwt_reads == 3U && con_tick_reads == 0U,
|
||||
"TC-CONLOG-131 计数支路既不等 tick 也不再读 CYCCNT(零外部时间源依赖)");
|
||||
|
||||
reset_all();
|
||||
con_dwt_ctrl_sticky = 0;
|
||||
con_uart_tc_stuck = 1; /* 字节能进 DR,TC 永不置位 */
|
||||
dbg_console_write_raw("CD", 2U);
|
||||
CHECK(tx_is(0U, "CD") && con_uart_tc_reads == CON_TX_GUARD_MAX,
|
||||
"TC-CONLOG-132 TC 卡死时计数支路同样最多自旋 CON_TX_GUARD_MAX 次即放弃");
|
||||
|
||||
printf("-- TC-CONLOG-133..135 DWT 支路 TC 抖动 / con_append 直调参数校验 --\n");
|
||||
reset_all();
|
||||
con_dwt_step = 1U; /* 时间走得极慢:不会误判超时 */
|
||||
con_uart_tc_after = 2; /* 前两轮 TC 不就绪 */
|
||||
dbg_console_write_raw("EF", 2U);
|
||||
CHECK(tx_is(0U, "EF") && con_uart_tc_reads == 3U,
|
||||
"TC-CONLOG-133 TC 抖动(未超上界)后仍算整块成功:TC 循环能正常退出");
|
||||
reset_all();
|
||||
CHECK(con_append(0, 3U) == -1, "TC-CONLOG-134 con_append(NULL) 自己也要挡住(不只靠上层)");
|
||||
CHECK(con_append("abc", 0U) == -1, "TC-CONLOG-135 con_append(len=0) 同样拒绝");
|
||||
}
|
||||
|
||||
/* ===================== main ===================== */
|
||||
int main(void)
|
||||
{
|
||||
/* 先验一次宿主 CRT 与本模块 fputc 的关系(被 tc_params 记成 TC-CONLOG-000) */
|
||||
tc_params();
|
||||
tc_unattached();
|
||||
tc_ring();
|
||||
tc_writer_self();
|
||||
tc_full_drop();
|
||||
tc_tx_fault();
|
||||
tc_tx_cool();
|
||||
tc_tx_half();
|
||||
tc_dwt();
|
||||
tc_null_uart();
|
||||
tc_fputc();
|
||||
tc_budget();
|
||||
tc_counted_paths();
|
||||
|
||||
printf("=== Console Log Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
fflush(stdout);
|
||||
return (s_failed == 0) ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user