深入解析Phil-opp的Blog OS项目:实现裸机环境下的测试框架
blog_os Writing an OS in Rust 项目地址: https://gitcode.com/gh_mirrors/bl/blog_os
引言
在操作系统开发中,测试是一个至关重要的环节。本文将深入探讨如何在Phil-opp的Blog OS项目中实现一个裸机环境下的测试框架。这个项目是一个用Rust编写的简单操作系统内核,运行在裸机环境中,没有标准库支持。
Rust测试框架的挑战
标准测试框架的限制
Rust内置的测试框架依赖于标准库,这对于我们的no_std
内核来说是个问题。当我们尝试在项目中运行cargo test
时,会遇到找不到test
库的错误,因为该库需要标准库支持。
自定义测试框架解决方案
幸运的是,Rust提供了custom_test_frameworks
特性,允许我们替换默认的测试框架。这个特性不需要外部库,因此可以在#[no_std]
环境中工作。
实现步骤:
- 在
main.rs
中添加特性声明 - 定义测试运行器函数
- 使用
#[test_case]
属性标记测试函数
#![feature(custom_test_frameworks)]
#![test_runner(crate::test_runner)]
#[cfg(test)]
pub fn test_runner(tests: &[&dyn Fn()]) {
println!("Running {} tests", tests.len());
for test in tests {
test();
}
}
测试执行流程
入口点调整
由于我们使用自定义入口点_start
,需要调整测试执行流程:
- 使用
reexport_test_harness_main
属性重命名生成的测试主函数 - 在
_start
函数中调用重命名后的测试主函数
#![reexport_test_harness_main = "test_main"]
#[no_mangle]
pub extern "C" fn _start() -> ! {
#[cfg(test)]
test_main();
loop {}
}
QEMU退出机制
isa-debug-exit设备
为了在测试完成后自动退出QEMU,我们使用QEMU的isa-debug-exit
设备:
- 在
Cargo.toml
中配置QEMU参数 - 实现端口I/O操作来与设备通信
[package.metadata.bootimage]
test-args = ["-device", "isa-debug-exit,iobase=0xf4,iosize=0x04"]
退出代码实现
定义退出代码枚举并使用x86_64
库进行端口操作:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum QemuExitCode {
Success = 0x10,
Failed = 0x11,
}
pub fn exit_qemu(exit_code: QemuExitCode) {
unsafe {
let mut port = Port::new(0xf4);
port.write(exit_code as u32);
}
}
串口输出
16550 UART实现
为了在测试后查看输出,我们实现串口输出:
- 添加
uart_16550
依赖 - 创建串口模块并初始化
lazy_static! {
pub static ref SERIAL1: Mutex<SerialPort> = {
let mut serial_port = unsafe { SerialPort::new(0x3F8) };
serial_port.init();
Mutex::new(serial_port)
};
}
输出宏
为了方便使用,实现serial_print!
和serial_println!
宏:
#[macro_export]
macro_rules! serial_print {
($($arg:tt)*) => {
$crate::serial::_print(format_args!($($arg)*));
};
}
完整测试框架
测试运行器改进
整合所有功能后的测试运行器:
pub fn test_runner(tests: &[&dyn Testable]) {
serial_println!("Running {} tests", tests.len());
for test in tests {
test.run();
}
exit_qemu(QemuExitCode::Success);
}
测试特征
定义Testable
特征统一测试接口:
pub trait Testable {
fn run(&self);
}
impl<T> Testable for T
where
T: Fn(),
{
fn run(&self) {
serial_print!("{}...\t", core::any::type_name::<T>());
self();
serial_println!("[ok]");
}
}
实际应用
示例测试
#[test_case]
fn trivial_assertion() {
assert_eq!(1, 1);
}
#[test_case]
fn test_breakpoint_exception() {
x86_64::instructions::interrupts::int3();
}
异常处理测试
#[test_case]
fn test_breakpoint_exception() {
// 触发断点异常
x86_64::instructions::interrupts::int3();
}
结论
通过本文,我们实现了一个完整的裸机测试框架,具有以下特点:
- 不依赖标准库的自定义测试框架
- 测试完成后自动退出QEMU
- 通过串口输出测试结果
- 统一的测试接口和漂亮的输出格式
这个框架为后续操作系统开发提供了可靠的测试基础,使得我们能够在不依赖标准库的情况下进行单元测试和集成测试。
blog_os Writing an OS in Rust 项目地址: https://gitcode.com/gh_mirrors/bl/blog_os
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考