Skip to content

Commit 80e6dc2

Browse files
committed
fix(test): 收紧 should_panic 判定
R4 第一阶段先修测试可信度问题,避免 QEMU guest 退出码没有传递到宿主时,should_panic 未触发也被 xtask 误判为通过。 - 让 normal/should_panic 测试分别输出 TEST OK 和 SHOULD_PANIC OK success sentinel - 让 xtask 的 --name 和全量测试路径统一捕获串口输出并校验 sentinel - 补充 xtask 单测覆盖缺失 sentinel、normal 成功、should_panic 成功和 timeout - 移除 test_harness 多余 alloc_error_handler feature gate,避免新增 warning - 更新 R4 audit-progress,记录第一阶段修复、验证结果和下一步低耦合切片 验证: - docker exec simplekernel-dev sh -lc 'cd /workspace && cargo fmt --all -- --check' - docker exec simplekernel-dev sh -lc 'cd /workspace && cargo test -p xtask' - docker exec simplekernel-dev sh -lc 'cd /workspace && cargo xtask check --arch riscv64' - docker exec simplekernel-dev sh -lc 'cd /workspace && cargo xtask check --arch aarch64' - docker exec simplekernel-dev sh -lc 'cd /workspace && cargo xtask test --arch riscv64 --name device-test --timeout 30' - docker exec simplekernel-dev sh -lc 'cd /workspace && cargo xtask test --arch riscv64 --name memory-types-test/align-down-canonical-panic --timeout 30' Signed-off-by: Niu Zhihong <zhihong@nzhnb.com>
1 parent e4d2975 commit 80e6dc2

4 files changed

Lines changed: 193 additions & 36 deletions

File tree

docs/audit/audit-progress.md

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,34 @@
55
66
## 当前状态
77

8-
**当前 Phase**: R4 — 架构层只读复审已完成,并已将问题原因、可能触发路径、修复方案和验证方向写入 `docs/audit/2026-05-08-r4-architecture-review-findings.md`。本轮未修改实现代码;R4 修复尚未开始。
8+
**当前 Phase**: R4 — 架构层第一阶段测试可信度修复已完成。`R4-09 should_panic harness/xtask 假阳性`
9+
已通过串口 success sentinel 和 xtask 输出判定收口,后续 panic 回归不再只依赖 QEMU 宿主退出码。
910
**下一个目标**(按优先级):
10-
1. **测试可信度优先**:修复 R4-09 `should_panic` harness/xtask 假阳性问题,否则后续 panic 回归可能继续误判通过。
11-
2. **R4 低耦合修复切片**:R4-03 RISC-V 从核 `gp` 初始化、R4-07 任务栈 16 字节对齐、R4-08 `ArchOps::dtb_addr()` unsafe 边界、R4-10 AArch64 `TCR_EL1.IPS`、R4-15 timer fail-fast。
12-
3. **R4 时序与跨核协议设计**:R4-01/R4-02 timer 与调度边界、R4-04/R4-12 CPU topology 与 SGI、R4-05 TLB shootdown 协议、R4-11 PLIC context 与启用顺序、R4-13 discovered/online core count、R4-14 timekeeper core。
13-
4. **R3 遗留设计跟踪**`PageTable::update_range_flags()` 若进入运行期路径,需要并发写者证明;完整多 bank RAM、真机设备/DMA 语义继续按 `docs/audit/2026-05-07-device-dma-rdrive-tracking.md` 跟踪。
11+
1. **R4 低耦合修复切片**:R4-03 RISC-V 从核 `gp` 初始化、R4-07 任务栈 16 字节对齐、R4-08 `ArchOps::dtb_addr()` unsafe 边界、R4-10 AArch64 `TCR_EL1.IPS`、R4-15 timer fail-fast。
12+
2. **R4 时序与跨核协议设计**:R4-01/R4-02 timer 与调度边界、R4-04/R4-12 CPU topology 与 SGI、R4-05 TLB shootdown 协议、R4-11 PLIC context 与启用顺序、R4-13 discovered/online core count、R4-14 timekeeper core。
13+
3. **R3 遗留设计跟踪**`PageTable::update_range_flags()` 若进入运行期路径,需要并发写者证明;完整多 bank RAM、真机设备/DMA 语义继续按 `docs/audit/2026-05-07-device-dma-rdrive-tracking.md` 跟踪。
1414

1515
验证计划:后续设计边界改动仍需先补目标回归测试,再按变更面执行
1616
`cargo fmt --all -- --check``cargo xtask check --arch riscv64`
1717
`cargo xtask check --arch aarch64`。涉及 QEMU 的命令必须使用 30 秒超时并在
1818
超时后清理残留 `qemu-system` 进程。
1919

20+
验证结果(2026-05-08 R4 第一阶段测试可信度修复):容器 `simplekernel-dev`
21+
`cargo fmt --all -- --check``cargo test -p xtask``cargo xtask check --arch riscv64`
22+
`cargo xtask check --arch aarch64` 通过(保留既有 warning)。新增 xtask 单测覆盖:
23+
QEMU 进程状态成功但缺少 success sentinel 时必须失败、normal `TEST OK` 成功、
24+
`should_panic``SHOULD_PANIC OK` 成功、timeout 即使带 success sentinel 也失败。
25+
RISC-V QEMU 30 秒超时下定点验证通过:`memory-types-test/codec`
26+
`memory-types-test/align-down-canonical-panic``panic-test``device-test`
27+
随后运行 `cargo xtask test --arch riscv64 --timeout 30`,30 个独立测试全部通过
28+
(30 passed, 0 failed, 0 timed out)。另用 AArch64 目标构建 `device-test`
29+
`memory-types-test/align-down-canonical-panic`,确认 test harness normal / should_panic
30+
路径在 AArch64 也能编译。第一次全量 RISC-V 回归曾暴露 `device-test` 的裸串口
31+
`TEST OK` 被从核日志穿插打碎,最终改为通过 `log` 后端输出 success sentinel,
32+
由 console lock 保证单条 sentinel 不被并发日志破坏。提交前复查 warning 时,
33+
清理了 `tests/test_harness` 中新触发的 `unused feature: alloc_error_handler`
34+
复跑验证后,本轮改动文件不再产生新的编译 warning,剩余 warning 均来自既有内核路径。
35+
2036
验证结果(2026-05-08 R4 复审文档化):本轮只新增审计文档并更新进度文件,
2137
未修改实现代码,未运行构建或 QEMU 系统测试。R4 问题详情见
2238
`docs/audit/2026-05-08-r4-architecture-review-findings.md`
@@ -49,6 +65,35 @@ frame allocator 后端已在 hard IRQ 上下文分配/释放时 fail-fast。
4965

5066
## 上次对话摘要
5167

68+
**日期**:2026-05-08(R4 第一阶段测试可信度修复)
69+
70+
### 已完成
71+
72+
本轮按 `docs/audit/2026-05-08-r4-architecture-review-findings.md` 的处理顺序,
73+
先修复 R4-09 `should_panic` harness/xtask 假阳性问题:
74+
75+
1. normal `test_main!` 测试成功后输出 `TEST OK` sentinel。
76+
2. should_panic 和手写 `panic-test` 的成功路径统一输出 `SHOULD_PANIC OK` sentinel。
77+
3. `cargo xtask test --name` 与全量测试均改用捕获输出路径,并由 xtask 同时检查 QEMU 进程状态、
78+
timeout、失败 sentinel 和 success sentinel。
79+
4. success sentinel 改用 `log` 后端输出,避免 SMP 从核日志与裸串口 sentinel 交错导致误判。
80+
81+
### 关键结论
82+
83+
| # | 结论 | 状态 | ADR |
84+
|---|------|------|-----|
85+
| R4-09 `should_panic` harness 假阳性 | 已通过统一 success sentinel + xtask 输出判定修复 | 已修复 ||
86+
| QEMU guest `exit_qemu(code)` 仍不应作为唯一可信信号 | xtask 现在要求串口 success sentinel;缺失即失败 | 已收口 ||
87+
| SMP 日志可打碎裸串口 sentinel | success sentinel 必须走日志锁或等价原子输出路径 | 已修复 ||
88+
| R4-03/R4-07/R4-08/R4-10/R4-15 | 下一组低耦合修复切片 | 待修复 | 部分待定 |
89+
90+
### 下一步
91+
92+
进入 R4 低耦合修复切片:优先处理 R4-03 RISC-V 从核 `gp` 初始化、R4-07 任务栈 16 字节对齐、
93+
R4-08 `ArchOps::dtb_addr()` unsafe 边界、R4-10 AArch64 `TCR_EL1.IPS`、R4-15 timer fail-fast。
94+
95+
---
96+
5297
**日期**:2026-05-08(R4 架构层复审文档化)
5398

5499
### 已完成

tests/panic_test/src/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,14 @@ fn exit_qemu(code: u32) -> ! {
9595
#[panic_handler]
9696
fn panic(_info: &PanicInfo<'_>) -> ! {
9797
// 打印触发标记——测试运行器通过此字符串判断测试通过
98-
simplekernel::logging::raw_put("PANIC_TEST_TRIGGERED\n");
98+
log::info!("SHOULD_PANIC OK: PANIC_TEST_TRIGGERED");
9999
// 成功退出——触发 panic 就是期望行为
100100
exit_qemu(0);
101101
}
102102

103103
#[alloc_error_handler]
104104
fn alloc_error(layout: Layout) -> ! {
105-
simplekernel::logging::raw_put("PANIC_TEST: alloc error\n");
105+
simplekernel::logging::raw_put("TEST PANIC: alloc error\n");
106106
let _ = layout;
107107
exit_qemu(1);
108108
}

tests/test_harness/src/lib.rs

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
//! (`_start`、`panic_handler`、`exit_qemu`、`kernel_thread_bootstrap`)。
55
66
#![no_std]
7-
#![feature(alloc_error_handler)]
87

98
extern crate alloc;
109

@@ -86,6 +85,7 @@ macro_rules! test_main {
8685
simplekernel::boot::kernel_init(argc, argv, $level);
8786
}
8887
$test_fn();
88+
log::info!("TEST OK");
8989
$crate::exit_qemu(0);
9090
} else {
9191
// SAFETY: 从核入口,汇编已设置栈和寄存器
@@ -149,21 +149,16 @@ macro_rules! test_main {
149149

150150
#[panic_handler]
151151
fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
152-
// 打印 panic 信息供 xtask 检查
153-
simplekernel::logging::raw_put("\x1b[32mSHOULD_PANIC OK\x1b[0m: ");
154152
if let Some(loc) = info.location() {
155-
let mut buf = heapless::String::<256>::new();
156-
let _ = core::fmt::Write::write_fmt(
157-
&mut buf,
158-
format_args!("{}:{}", loc.file(), loc.line()),
153+
log::info!(
154+
"SHOULD_PANIC OK: {}:{}: {}",
155+
loc.file(),
156+
loc.line(),
157+
info.message()
159158
);
160-
simplekernel::logging::raw_put(buf.as_str());
159+
} else {
160+
log::info!("SHOULD_PANIC OK: <unknown>: {}", info.message());
161161
}
162-
simplekernel::logging::raw_put(": ");
163-
let mut msg_buf = heapless::String::<256>::new();
164-
let _ = core::fmt::Write::write_fmt(&mut msg_buf, format_args!("{}", info.message()));
165-
simplekernel::logging::raw_put(msg_buf.as_str());
166-
simplekernel::logging::raw_put("\n");
167162
// panic 发生 = should_panic 测试成功
168163
$crate::exit_qemu(0);
169164
}

xtask/src/test.rs

Lines changed: 133 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,69 @@ pub struct TestBinary {
4545
pub display_name: String,
4646
}
4747

48+
/// 根据 QEMU 进程状态和串口 sentinel 得出的测试判定。
49+
struct TestVerdict {
50+
passed: bool,
51+
status: &'static str,
52+
failure_reason: Option<String>,
53+
}
54+
55+
const SUCCESS_SENTINELS: [&str; 2] = ["TEST OK", "SHOULD_PANIC OK"];
56+
57+
const FAILURE_SENTINELS: [&str; 3] = [
58+
"SHOULD_PANIC test returned without panic",
59+
"TEST PANIC:",
60+
"PANIC_TEST: alloc error",
61+
];
62+
63+
/// QEMU 的宿主退出码不一定携带 guest `exit_qemu(code)`,因此测试必须以串口
64+
/// sentinel 作为最终成功条件。
65+
fn evaluate_qemu_test_result(result: &qemu::QemuTestResult) -> TestVerdict {
66+
if result.timed_out {
67+
return TestVerdict {
68+
passed: false,
69+
status: "TIMEOUT",
70+
failure_reason: Some("QEMU 运行超时".to_string()),
71+
};
72+
}
73+
74+
if !result.success {
75+
return TestVerdict {
76+
passed: false,
77+
status: "FAIL",
78+
failure_reason: Some("QEMU 进程非零退出".to_string()),
79+
};
80+
}
81+
82+
if let Some(marker) = FAILURE_SENTINELS
83+
.iter()
84+
.find(|marker| result.output.contains(**marker))
85+
{
86+
return TestVerdict {
87+
passed: false,
88+
status: "FAIL",
89+
failure_reason: Some(format!("串口输出包含失败标记 `{marker}`")),
90+
};
91+
}
92+
93+
if SUCCESS_SENTINELS
94+
.iter()
95+
.any(|marker| result.output.contains(marker))
96+
{
97+
return TestVerdict {
98+
passed: true,
99+
status: "PASS",
100+
failure_reason: None,
101+
};
102+
}
103+
104+
TestVerdict {
105+
passed: false,
106+
status: "FAIL",
107+
failure_reason: Some("串口输出缺少 `TEST OK` 或 `SHOULD_PANIC OK` 成功标记".to_string()),
108+
}
109+
}
110+
48111
/// 运行指定测试
49112
#[expect(
50113
clippy::too_many_arguments,
@@ -79,21 +142,26 @@ pub fn run_test(
79142
"[xtask] Running test '{}' (timeout: {}s)...",
80143
display_name, timeout_secs
81144
);
82-
let result = qemu::launch_qemu(
83-
sh,
145+
let result = qemu::launch_qemu_captured(
84146
arch,
85147
project_root,
86148
&env.boot_dir,
87149
&kernel_elf_path,
88150
&env.rootfs_path,
89-
false,
90-
Some(timeout_secs),
151+
timeout_secs,
91152
);
92153

93154
match result {
94-
Ok(()) => {
95-
println!("[xtask] Test '{}' completed", display_name);
96-
Ok(true)
155+
Ok(qemu_result) => {
156+
let verdict = evaluate_qemu_test_result(&qemu_result);
157+
println!("[xtask] Test '{}' {}", display_name, verdict.status);
158+
if let Some(reason) = verdict.failure_reason {
159+
eprintln!("[xtask] Test '{}' failed: {}", display_name, reason);
160+
println!("---------- {} output ----------", display_name);
161+
println!("{}", qemu_result.output);
162+
println!("---------- end {} ----------", display_name);
163+
}
164+
Ok(verdict.passed)
97165
}
98166
Err(e) => {
99167
eprintln!("[xtask] Test '{}' failed: {}", display_name, e);
@@ -212,6 +280,7 @@ struct TestResult {
212280
timed_out: bool,
213281
duration: std::time::Duration,
214282
output: String,
283+
failure_reason: Option<String>,
215284
}
216285

217286
/// 运行所有测试:构建全部二进制,顺序执行并捕获输出,打印汇总。
@@ -277,24 +346,20 @@ pub fn run_all_tests(
277346
)?;
278347

279348
let duration = start.elapsed();
280-
let status = if qemu_result.timed_out {
281-
"TIMEOUT"
282-
} else if qemu_result.success {
283-
"PASS"
284-
} else {
285-
"FAIL"
286-
};
349+
let verdict = evaluate_qemu_test_result(&qemu_result);
287350
println!(
288-
"[xtask] {name}: {status} ({:.1}s)",
351+
"[xtask] {name}: {} ({:.1}s)",
352+
verdict.status,
289353
duration.as_secs_f64()
290354
);
291355

292356
results.push(TestResult {
293357
name: name.clone(),
294-
passed: qemu_result.success,
358+
passed: verdict.passed,
295359
timed_out: qemu_result.timed_out,
296360
duration,
297361
output: qemu_result.output,
362+
failure_reason: verdict.failure_reason,
298363
});
299364
}
300365

@@ -333,10 +398,62 @@ pub fn run_all_tests(
333398
if !r.passed {
334399
println!();
335400
println!("---------- {} output ----------", r.name);
401+
if let Some(reason) = &r.failure_reason {
402+
println!("failure reason: {reason}");
403+
}
336404
println!("{}", r.output);
337405
println!("---------- end {} ----------", r.name);
338406
}
339407
}
340408

341409
Ok(failed == 0 && timed_out == 0)
342410
}
411+
412+
#[cfg(test)]
413+
mod tests {
414+
use super::*;
415+
416+
fn qemu_result(success: bool, timed_out: bool, output: &str) -> qemu::QemuTestResult {
417+
qemu::QemuTestResult {
418+
success,
419+
timed_out,
420+
output: output.to_string(),
421+
}
422+
}
423+
424+
#[test]
425+
fn missing_success_sentinel_fails_even_when_qemu_status_is_success() {
426+
let result = qemu_result(true, false, "SHOULD_PANIC test returned without panic\n");
427+
428+
let verdict = evaluate_qemu_test_result(&result);
429+
430+
assert!(!verdict.passed);
431+
}
432+
433+
#[test]
434+
fn normal_success_sentinel_passes() {
435+
let result = qemu_result(true, false, "boot log\nTEST OK\n");
436+
437+
let verdict = evaluate_qemu_test_result(&result);
438+
439+
assert!(verdict.passed);
440+
}
441+
442+
#[test]
443+
fn should_panic_success_sentinel_passes() {
444+
let result = qemu_result(true, false, "\u{1b}[32mSHOULD_PANIC OK\u{1b}[0m: panic\n");
445+
446+
let verdict = evaluate_qemu_test_result(&result);
447+
448+
assert!(verdict.passed);
449+
}
450+
451+
#[test]
452+
fn timeout_fails_even_with_success_sentinel() {
453+
let result = qemu_result(false, true, "TEST OK\n[TIMEOUT after 30s]");
454+
455+
let verdict = evaluate_qemu_test_result(&result);
456+
457+
assert!(!verdict.passed);
458+
}
459+
}

0 commit comments

Comments
 (0)