fix(控制台): 未注册期裸发加"整行独占",让路整行入环不丢,修掉 O1 第二实例
台架一手证据(boot_log_data/20261026_prod_console_interleave_evidence.txt):两次 「仅复位不重烧」启动里,[PROT] 未标定 INFO 行内分别多出 3 个和 1 个字节,而紧邻其后 的 [NAND_FTL] 行正好缺自己行首同样数量的字节("[20" 与 "[")——串口 framing 错不会 让行变长,字节按原序出现在邻居里 ⇒ 两个写者并发逐字节写 USART1->DR。写者对: protectTask 的未标定闭锁边沿 × defaultTask 的 storage_bring_up(),两者都落在 dbg_console_writer_attach() 之前(attach 排在存储 bring-up 之后),该窗口 s_con_writer_up == 0 ⇒ 任何任务的整行都走零互斥裸发。这是控制台单写者设计说明在册 O1 的**第二个实例**;2026-10-12 那次只补了"自愿门控",而自愿门控挡不住业务边沿留痕 (业务无法预知某条边沿落在哪个窗口),所以这次被同一窗口第二次咬到。 修法是**非阻塞整行独占**:con_tx_try_claim/release 只在几条指令的临界区里 test-and-set,绝不把 7.8 ms 的逐字节发送包进关中断区、绝不等待;IPSR 上下文与调度 器未启动两种情况显式放行(崩溃遗言必须能出去;早期本就单执行流,一条不丢);同任务 按深度嵌套。抢不到独占的那条行**原文入环**延后发——前缀与时间戳在 DBG_* 调用点就 格式化进调用方缓冲,本模块与 drain 都不补前缀、不重打时间戳,所以延后发出的行与当场 直发逐字节相同;[ERR] 只在「抢不到独占且环也放不下」时才红,按 3 次启动命中 2 次的 频率,让路若计 [ERR] 就会把静默缺陷换成常红门禁。顺带堵掉第二处洞:drain 每轮先取 凭证,拿不到就让位且 tail 不推进(旧写法 attach 可以撞上别人已发出的半行)。 新导出 dbg_console_tx_begin()/tx_end() 供 task-26 收口故障钩子的多段裸发。 否决过的两条路写进文档 §4.4.1:提前 attach 会把等串口塞进 mount/mkfs 时序敏感段 或让生产者在整个 bring-up 里堵在满环上;整段进环会让「无看门狗窗口」死机时一个字都 不出(2026-10-02 那几轮正是靠 bring-up 期流式日志取证的)。 判据:test/host/console_log 127 → 152 全绿(M 族 TC-CONLOG-170..179 共 +25,既有 127 条零删除)。唯一换钉法的是 TC-CONLOG-015——旧断言钉的正是缺陷机理本身(裸发 0 次临界区 = 零互斥),现改钉「恰 2 次几条指令的临界区 + 每个上线字节都在临界区之外 + 零等待」;硬红线由假 USART 在每个上线字节处记录临界区深度水位恒 0 来机器守住。四处 注入反证各自转红 11 / 5 / 1 / 3 条并逐字节还原复跑;覆盖率 console_log.c 199/200 行 → 256/257 行、16/16 → 21/21 函数。邻域复跑:task_prof 27/27、sys_log_mcu 137/137、 iec104_serv 37/37。CONLOG 游标抬到 179。 台架复验判据(**本笔尚未上机**,下一轮请台架跑):连续 3~5 次「仅复位不重烧」启动 日志 [ERR] 零命中,且 [PROT] 未标定与 [NAND_FTL] Initializing 两行都完整。 成本:.bss +5 B、Flash 估 +100~150 B,零新增 DBG_* / 零新增缓冲 / 零堆 / 零内核对象。 文档:控制台单写者日志设计说明.md 升 v1.6(O1 改「已处置」含两实例与字节账、新增 §4.4.1、§3.4 并发区间、§4.4 取消「不加锁」口径、§7 失效表、§11 变更记录、API 表)。
This commit is contained in:
@@ -2,9 +2,12 @@
|
||||
* 模块名称:控制台单写者日志(console_log)主机侧单元测试
|
||||
* 模块功能:在 PC 上离线验证 App/log/console_log.c 的单写者契约(G1~G5 / K1~K5,见
|
||||
* docs/子系统设计/控制台单写者日志设计说明.md):环形缓冲入环与分块发送、
|
||||
* 行级原子、192B 截断、写者未注册/ISR 上下文的无锁直发、缓冲满有界等待与
|
||||
* 行级原子、192B 截断、写者未注册/ISR 上下文的直发(未注册期自 2026-10-26
|
||||
* 起是**整行独占**,不再是"无锁":M 族 TC-CONLOG-170..179 钉"别的写者插不进
|
||||
* 一行、让路行逐字节等同直发、让位不丢、真溢出才 [ERR]")、缓冲满有界等待与
|
||||
* 丢弃可观测、TX 僵尸通道软复位自愈 + 300ms 冷却、DWT 与指令计数两种硬上界、
|
||||
* huart1.Instance 未初始化的 NULL 短路、fputc 重定向、drain 250ms 预算。
|
||||
* huart1.Instance 未初始化的 NULL 短路、fputc 重定向、drain 250ms 预算、
|
||||
* 整行所有权(行级单一所有者,道G task-23)。
|
||||
*
|
||||
* 为什么要主机侧单测(而不是只用台架):
|
||||
* 本模块的风险全在"异常时刻"——UART 僵尸通道卡死、TIM7 停摆导致 uwTick 不往前走、
|
||||
@@ -63,7 +66,16 @@
|
||||
* 四条全退回 -> 红 22 条(再红 TC-CONLOG-140/141/143/145)。
|
||||
* 改回修复版即 127/127 全绿;每一步的失败集合都是上一步的超集,说明四组断言各自
|
||||
* 咬住自己的那条语义,没有互相顶包。
|
||||
* 也就是说:这四组断言不是"写在假件规则上"的自证,而是真能咬住生产语义的。
|
||||
* 2026-10-26(道G task-23,行级单一所有者)另做四处注入,逐个单独注入、各自归因、
|
||||
* 每次改完逐字节还原(152 条基线复跑绿):
|
||||
* 取消整行独占(退回"无条件直发") -> 红 11 条(含 178 = 台架原文形态那条行);
|
||||
* 让路改成静默丢弃(不入环也不计数) -> 红 5 条(170b/170c/171/178/179b);
|
||||
* 归还所有权时不比属主(两道卫兵都拆) -> 红 1 条(179c;只拆外层里层仍守住时
|
||||
* 测试全绿是**正确结果**,不是漏测);
|
||||
* 把整段发送包进临界区(关中断 7.8ms) -> 红 3 条(015/015b/177 与 176)。
|
||||
* ⚠ 顺带记一条注入方法论:第一版"取消整行独占"的锚点在 raw 与 drain 里各出现一次,
|
||||
* 被写入前的唯一性守卫挡下——模糊锚点会一改变两个地方,等于同时测两个变异。
|
||||
* 也就是说:这些断言不是"写在假件规则上"的自证,而是真能咬住生产语义的。
|
||||
*
|
||||
* 故意没测(逐行说明见交付报告):
|
||||
* - console_log.c:383 唯一未覆盖行:snprintf 溢出后的钳位 `n = sizeof(note)-1`。
|
||||
@@ -115,6 +127,9 @@ int con_uart_txe_ready_left;
|
||||
int con_uart_tc_after;
|
||||
int con_uart_tc_stuck;
|
||||
int con_uart_pending;
|
||||
uint32_t con_uart_tx_hook_at; /* 道G task-23:第 N 个字节上线后触发一次 */
|
||||
void (*con_uart_tx_hook)(void);
|
||||
uint32_t con_uart_crit_at_tx_max; /* 上线字节所见最大临界区深度(硬红线水位) */
|
||||
|
||||
uint32_t con_dwt_step;
|
||||
int con_dwt_ctrl_sticky;
|
||||
@@ -307,7 +322,16 @@ static void reset_all(void)
|
||||
s_con_writer_up = 0U;
|
||||
s_con_dwt_up = 0U;
|
||||
s_con_writer_task = 0;
|
||||
/* 道G task-23 的整行所有权凭证:跨场景必须显式清,否则上一场景留下的持有者会让
|
||||
* 下一场景的裸发全部走环,症状正是"单独跑绿、连跑偶红" */
|
||||
s_con_tx_owner = 0;
|
||||
s_con_tx_depth = 0U;
|
||||
|
||||
/* 一次性把手先归零:上一条用例装填的钩子如果留在这儿,会把"第二个生产者"塞进
|
||||
* 本条用例的某个随机字节之后(那种红比真缺陷还难查) */
|
||||
con_uart_tx_hook_at = 0U;
|
||||
con_uart_tx_hook = 0;
|
||||
con_uart_crit_at_tx_max = 0U;
|
||||
con_uart_reset();
|
||||
con_dwt_reset();
|
||||
|
||||
@@ -528,8 +552,21 @@ static void tc_unattached(void)
|
||||
CHECK(tx_is(0U, "hello"), "TC-CONLOG-012 direct send reaches the wire verbatim (never went through the ring)");
|
||||
CHECK(used_now() == 0U, "TC-CONLOG-013 direct send does not enter the ring (head == tail)");
|
||||
CHECK(con_evt_sets == 0U, "TC-CONLOG-014 direct send does not set SYS_EVT_CONSOLE_TX (no writer to wake yet)");
|
||||
CHECK(con_crit_enter == 0U,
|
||||
"TC-CONLOG-015 direct send uses zero critical sections = the 2026-10-10 byte-interleaving root cause (not whole-line loss)");
|
||||
/* ⚠ 本条判据在道G task-23(2026-10-26)**换了钉法**,这里留全过程:旧写法断言
|
||||
* "裸发用 0 次临界区",钉住的正是当时的**缺陷机理本身**(零互斥 => 多个任务可以
|
||||
* 在同一行里互相插字节;台架 20261012_B6 的 "st[art:" 与本次 protectTask 未标定
|
||||
* 行内多出 "[" "2" "0" 三字节都是它)。修复后裸发要取"整行所有权",取与还各一次、
|
||||
* 每次只有几条指令的 test-and-set,所以计数从 0 变 2。
|
||||
* 换来两条更硬的判据(缺一条就该红),断言总数只增不减:
|
||||
* ① 每个上线字节都发生在临界区**之外**(con_uart_crit_at_tx_max == 0)——整行
|
||||
* 独占绝不允许把 @115200 约 7.8 ms 的发送包进关中断区,那等于往启动窗口塞
|
||||
* 7.8 ms 中断延迟与最坏喂狗抖动,比交错本身更糟(Lead 红线);
|
||||
* ② 全程零次 vTaskDelay:拿不到所有权就当场让路,不排队(见 TC-CONLOG-170 族)。
|
||||
*/
|
||||
CHECK((con_crit_enter == 2U) && (con_crit_exit == 2U) && (con_crit_max_depth == 1U),
|
||||
"TC-CONLOG-015 direct send takes exactly two few-instruction critical sections (claim + return) and never nests them");
|
||||
CHECK((con_uart_crit_at_tx_max == 0U) && (con_delay_calls == 0U),
|
||||
"TC-CONLOG-015b whole-line ownership puts every wire byte outside any critical section and never waits");
|
||||
|
||||
reset_all();
|
||||
memset(big, 'B', sizeof(big));
|
||||
@@ -1171,6 +1208,179 @@ static void tc_check_then_act(void)
|
||||
"TC-CONLOG-167 no producer-side ring access ever happened outside a critical section");
|
||||
}
|
||||
|
||||
/* ===================== M 整行所有权(道G task-23,O1 第二个实例) ===================== */
|
||||
|
||||
/* 装进"A 行发送中途"的第二个生产者:换任务句柄后再裸发一行,精确模拟真实抢占
|
||||
* —— A(s_task_other)此刻持有整行所有权,B(s_task_writer)必须让路。
|
||||
* 这是本套唯一能把"两个生产者并发时,写进 DR 的字节流里不得出现另一条行的字节"
|
||||
* 做成必然失败断言的把手:修复后是**非阻塞**设计,永远不会调 vTaskDelay,所以既有的
|
||||
* con_delay_hook 在这条路径上根本不会被触发(拿它当注入点会静默地什么都测不到)。
|
||||
* 台架一手证据:docs/测试说明/boot_log_data/20261026_prod_console_interleave_evidence.txt
|
||||
* —— PROT 行内多出 [ 2 0 三字节,紧接其后的 NAND_FTL 行恰好缺这三个字节的行首。 */
|
||||
/* 台架 16:37 那次被插字的原文行(boot_log_data/20261026_prod_console_interleave_evidence.txt
|
||||
* OCCURRENCE 1 去掉三个外来字节之后的样子)。M 族拿它当"真实行长与真实前缀"的样本,
|
||||
* 免得用例自己造一个不像现场的理想行。 */
|
||||
static const char s_prot_line[] =
|
||||
"[2026-10-26 16:37:07][PROT] uncalibrated -> protection blocked"
|
||||
" (UA/UN/IA VALID not all set)\r\n";
|
||||
|
||||
static void s_tx_second_writer(void)
|
||||
{
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
dbg_console_write_raw("BBBBBB", 6U);
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
}
|
||||
|
||||
static void tc_ownership(void)
|
||||
{
|
||||
char fill[DBG_CON_LINE_MAX];
|
||||
uint32_t i;
|
||||
|
||||
printf("-- TC-CONLOG-170..179 whole-line ownership (O1 instance 2, bench byte account) --\n");
|
||||
|
||||
/* 170 A 行发到第 4 个字节时被 B 抢占:A 逐字节完整,B 整行让路入环 */
|
||||
reset_all();
|
||||
con_uart_tx_hook_at = 4U;
|
||||
con_uart_tx_hook = s_tx_second_writer;
|
||||
dbg_console_write_raw("AAAAAAAA", 8U);
|
||||
CHECK(tx_is(0U, "AAAAAAAA") && (con_uart_tx_n == 8U),
|
||||
"TC-CONLOG-170 the line already on its way out stays byte-intact when a second producer preempts mid-line");
|
||||
CHECK(used_now() == 6U,
|
||||
"TC-CONLOG-170b the preempted producer books its whole line into the ring instead of spraying bytes into another line");
|
||||
CHECK((dbg_console_dropped() == 0U) && (con_delay_calls == 0U),
|
||||
"TC-CONLOG-170c yielding is neither a drop nor a wait (this is what the earlier whole-line-loss variant would have cost)");
|
||||
|
||||
/* 171 判据⑦:延后发出去的内容 = 当场直发本会产出的同一批字节(同时间戳/单前缀/单 CRLF) */
|
||||
con_uart_tx_hook = 0;
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
dbg_console_writer_attach();
|
||||
writer_drain();
|
||||
CHECK((con_uart_tx_n == 14U) && tx_is(8U, "BBBBBB") && (used_now() == 0U),
|
||||
"TC-CONLOG-171 the deferred line reaches the wire byte-identical to immediate sending (no second prefix, no re-stamped time, single CRLF)");
|
||||
|
||||
/* 172 抢不到所有权 + 环也放不下 -> 才计真丢弃(真溢出必须仍然看得见) */
|
||||
reset_all();
|
||||
(void)memset(fill, 0x58, sizeof(fill));
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
dbg_console_writer_attach();
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
CHECK(dbg_console_tx_begin() == 1U, "TC-CONLOG-172 a task can take whole-line ownership while the wire is free");
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
for (i = 0U; i < 21U; i++) {
|
||||
(void)dbg_console_write_line(fill, sizeof(fill)); /* 21*192=4032,环可用 4095 -> 只剩 63 字节 */
|
||||
}
|
||||
CHECK((used_now() == (21U * (uint32_t)DBG_CON_LINE_MAX)) && (con_uart_tx_n == 0U),
|
||||
"TC-CONLOG-172b the writer yields the whole round while another task owns the wire: nothing sent, nothing dequeued");
|
||||
dbg_console_write_raw(fill, 70U);
|
||||
CHECK((dbg_console_dropped() == 1U) && (con_uart_tx_n == 0U)
|
||||
&& (used_now() == (21U * (uint32_t)DBG_CON_LINE_MAX)),
|
||||
"TC-CONLOG-172c only a line that can neither own the wire nor fit the ring counts as a real drop, and it never half-sends");
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
dbg_console_tx_end();
|
||||
writer_drain();
|
||||
CHECK((con_uart_tx_n >= (21U * (uint32_t)DBG_CON_LINE_MAX))
|
||||
&& tx_contains("[ERR] CONSOLE dropped 1"),
|
||||
"TC-CONLOG-172d once ownership frees up the deferred round goes out complete, and the true overflow surfaces as [ERR]");
|
||||
/* 172e 报出去就销账(既有语义,本卡不许退化):账清零 + 不重复报同一笔 */
|
||||
CHECK((dbg_console_dropped() == 0U) && (tx_count("[ERR] CONSOLE dropped 1") == 1U),
|
||||
"TC-CONLOG-172e the drop ledger is settled only after the [ERR] note actually left the wire, and it is not reported twice");
|
||||
|
||||
/* 173 写者一轮 drain 被所有权挡住:让位 != 丢 */
|
||||
reset_all();
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
(void)dbg_console_tx_begin();
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
dbg_console_writer_attach();
|
||||
(void)dbg_console_write_line("L1\r\n", 4U);
|
||||
CHECK((con_uart_tx_n == 0U) && (used_now() == 4U),
|
||||
"TC-CONLOG-173 while another task owns the wire the writer sends nothing and keeps every byte queued");
|
||||
writer_drain();
|
||||
CHECK((con_uart_tx_n == 0U) && (used_now() == 4U) && (dbg_console_dropped() == 0U),
|
||||
"TC-CONLOG-173b yielding a round is not a drop: the tail never advances, so no ledger moves");
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
dbg_console_tx_end();
|
||||
CHECK(s_con_tx_owner == 0,
|
||||
"TC-CONLOG-173c ownership is really returned (a leaked token would push every later line into the ring forever)");
|
||||
writer_drain();
|
||||
CHECK(tx_is(0U, "L1\r\n") && (used_now() == 0U) && (dbg_console_dropped() == 0U),
|
||||
"TC-CONLOG-173d the deferred round goes out complete as soon as the wire is free");
|
||||
|
||||
/* 174 同任务嵌套(写者一轮里再裸发留痕行)不得自我丢弃 */
|
||||
reset_all();
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
(void)dbg_console_tx_begin();
|
||||
dbg_console_write_raw("SEGA", 4U);
|
||||
dbg_console_write_raw("SEGB", 4U);
|
||||
dbg_console_tx_end();
|
||||
CHECK((con_uart_tx_n == 8U) && tx_is(0U, "SEGASEGB") && (dbg_console_dropped() == 0U)
|
||||
&& (used_now() == 0U),
|
||||
"TC-CONLOG-174 nested raw writes by the owner go out at once, in order, with zero drops");
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
dbg_console_write_raw("NEXT", 4U);
|
||||
CHECK((con_uart_tx_n == 12U) && tx_is(8U, "NEXT") && (used_now() == 0U),
|
||||
"TC-CONLOG-174b after tx_end the next task owns the wire again (no permanent ring detour)");
|
||||
|
||||
/* 175 异常上下文:放行,不等、不计丢弃(崩溃遗言必须能出去) */
|
||||
reset_all();
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
(void)dbg_console_tx_begin();
|
||||
con_ipsr = 1U;
|
||||
dbg_console_write_raw("[ERR] HARD", 10U);
|
||||
CHECK((con_uart_tx_n == 10U) && tx_is(0U, "[ERR] HARD"),
|
||||
"TC-CONLOG-175 an exception context sends at once even while a task owns the wire (crash logs are the last words)");
|
||||
CHECK((dbg_console_dropped() == 0U) && (used_now() == 0U)
|
||||
&& (s_con_tx_owner == (TaskHandle_t)&s_task_other),
|
||||
"TC-CONLOG-175b the bypass neither counts a drop nor touches the owner token (that would free a line in flight)");
|
||||
con_ipsr = 0U;
|
||||
|
||||
/* 176 调度器未启动:单执行流,不取凭证、不开临界区、一条不丢 */
|
||||
reset_all();
|
||||
con_task_self = 0;
|
||||
(void)dbg_console_tx_begin();
|
||||
dbg_console_write_raw("EARLY", 5U);
|
||||
CHECK((con_uart_tx_n == 5U) && tx_is(0U, "EARLY") && (dbg_console_dropped() == 0U)
|
||||
&& (s_con_tx_owner == 0) && (con_crit_enter == 0U),
|
||||
"TC-CONLOG-176 before the scheduler starts, the single execution stream sends directly, taking no token and no critical section");
|
||||
|
||||
/* 177 硬红线:整行独占绝不能把 @115200 约 7.8 ms 的发送包进关中断区 */
|
||||
reset_all();
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
dbg_console_writer_attach();
|
||||
(void)dbg_console_write_line(s_prot_line, (unsigned)strlen(s_prot_line));
|
||||
writer_drain();
|
||||
CHECK((con_uart_tx_n == (uint32_t)strlen(s_prot_line)) && (con_uart_crit_at_tx_max == 0U)
|
||||
&& tx_is(0U, s_prot_line),
|
||||
"TC-CONLOG-177 every byte reaches the wire with interrupts enabled (ownership never wraps the transmit)");
|
||||
|
||||
/* 178 台架原文形态复演:那条被插进 [20 的 PROT 行,现在要么完整、要么整条让路 */
|
||||
/* 逐字节照抄台架那一行的原文(含时间戳前缀与 CRLF),长度交给 strlen 现算:
|
||||
* 手数字节数自己就踩过一次坑(把 93 数成 39,用例先红的是我的算术) */
|
||||
reset_all();
|
||||
con_uart_tx_hook_at = (uint32_t)strlen(s_prot_line) - 30U;
|
||||
con_uart_tx_hook = s_tx_second_writer;
|
||||
dbg_console_write_raw(s_prot_line, (unsigned)strlen(s_prot_line));
|
||||
CHECK((con_uart_tx_n == (uint32_t)strlen(s_prot_line)) && tx_is(0U, s_prot_line)
|
||||
&& (used_now() == 6U) && (dbg_console_dropped() == 0U),
|
||||
"TC-CONLOG-178 the exact bench shape (that PROT line, interrupted mid-transmit) comes out unmangled: the [20 insertion is structurally gone");
|
||||
|
||||
/* 179 begin 失败不吞字、不越权归还 */
|
||||
reset_all();
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
(void)dbg_console_tx_begin();
|
||||
con_task_self = (TaskHandle_t)&s_task_writer;
|
||||
CHECK(dbg_console_tx_begin() == 0U,
|
||||
"TC-CONLOG-179 tx_begin reports failure instead of blocking when another task owns the wire");
|
||||
dbg_console_write_raw("FREE", 4U);
|
||||
CHECK((con_uart_tx_n == 0U) && (used_now() == 4U) && (dbg_console_dropped() == 0U),
|
||||
"TC-CONLOG-179b a failed begin still delivers the caller bytes (deferred, never swallowed)");
|
||||
dbg_console_tx_end();
|
||||
CHECK(s_con_tx_owner == (TaskHandle_t)&s_task_other,
|
||||
"TC-CONLOG-179c a non-owner tx_end cannot release someone else ownership (that would re-open this very hole)");
|
||||
con_task_self = (TaskHandle_t)&s_task_other;
|
||||
dbg_console_tx_end();
|
||||
CHECK(s_con_tx_owner == 0, "TC-CONLOG-179d the real owner releases normally");
|
||||
}
|
||||
|
||||
/* ===================== main ===================== */
|
||||
int main(void)
|
||||
{
|
||||
@@ -1189,6 +1399,7 @@ int main(void)
|
||||
tc_counted_paths();
|
||||
tc_three_ledgers();
|
||||
tc_check_then_act();
|
||||
tc_ownership();
|
||||
|
||||
printf("=== Console Log Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
|
||||
Reference in New Issue
Block a user