Skip to content

Commit dd595b0

Browse files
authored
Merge pull request #26 from RyanKoech/feature/testing
2 parents 8768617 + 75a89eb commit dd595b0

File tree

17 files changed

+287
-30
lines changed

17 files changed

+287
-30
lines changed

.github/workflows/makefile.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,8 @@ jobs:
2828
- name: Verify xorriso installation
2929
run: xorriso -version
3030

31-
- name: Run build
32-
run: make
31+
- name: Install qemu
32+
run: sudo apt-get install qemu-system
33+
34+
- name: Run build & test
35+
run: make test

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
*.iso
55
*.img
66
*.hdd
7+
/hexium_os-tests

GNUmakefile

+53-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,31 @@ run-x86_64: ovmf/ovmf-code-$(KARCH).fd ovmf/ovmf-vars-$(KARCH).fd $(IMAGE_NAME).
2828
-cdrom $(IMAGE_NAME).iso \
2929
$(QEMUFLAGS)
3030

31+
.PHONY: test
32+
test: test-iso
33+
@set -e; \
34+
FAILED=0; \
35+
echo "\n\n\n--------RUNNING KERNEL INTEGRATION TESTS-------\n\n"; \
36+
for iso in hexium_os-tests/*.iso; do \
37+
echo "==============================="; \
38+
echo "Running integration test: $$iso"; \
39+
echo "-------------------------------"; \
40+
if qemu-system-x86_64 \
41+
-cdrom "$$iso" \
42+
-device isa-debug-exit,iobase=0xf4,iosize=0x04 \
43+
-serial stdio -display none; \
44+
then \
45+
echo "✅ Integration Test passed: $$iso"; \
46+
elif [ $$? -eq 33 ]; then \
47+
echo "✅ Integration Test passed (exit 33): $$iso"; \
48+
else \
49+
echo "❌ Integration Test failed: $$iso"; \
50+
FAILED=1; \
51+
fi; \
52+
echo ""; \
53+
done; \
54+
exit $$FAILED
55+
3156
ovmf/ovmf-code-$(KARCH).fd:
3257
mkdir -p ovmf
3358
curl -Lo $@ https://github.com/osdev0/edk2-ovmf-nightly/releases/latest/download/ovmf-code-$(KARCH).fd
@@ -45,6 +70,10 @@ limine/limine:
4570
kernel:
4671
$(MAKE) -C kernel
4772

73+
.PHONY: kernel-test
74+
kernel-test:
75+
$(MAKE) -C kernel test
76+
4877
.PHONY: ramfs
4978
ramfs:
5079
mkdir -p initrd/
@@ -69,10 +98,33 @@ $(IMAGE_NAME).iso: limine/limine kernel ramfs
6998
./limine/limine bios-install $(IMAGE_NAME).iso
7099
rm -rf iso_root
71100

101+
.PHONY: test-iso
102+
test-iso: limine/limine ramfs kernel-test
103+
mkdir -p hexium_os-tests
104+
for testbin in kernel/kernel-test/*; do \
105+
testname=$$(basename $$testbin); \
106+
isodir=iso_root_$$testname; \
107+
mkdir -p $$isodir/boot/limine $$isodir/EFI/BOOT; \
108+
cp -v $$testbin $$isodir/boot/kernel; \
109+
cp -v ramfs.img $$isodir/boot/; \
110+
cp -v limine.conf $$isodir/boot/limine/; \
111+
cp -v limine/limine-bios.sys limine/limine-bios-cd.bin limine/limine-uefi-cd.bin $$isodir/boot/limine/; \
112+
cp -v limine/BOOTX64.EFI $$isodir/EFI/BOOT/; \
113+
cp -v limine/BOOTIA32.EFI $$isodir/EFI/BOOT/; \
114+
xorriso -as mkisofs -b boot/limine/limine-bios-cd.bin \
115+
-no-emul-boot -boot-load-size 4 -boot-info-table \
116+
--efi-boot boot/limine/limine-uefi-cd.bin \
117+
-efi-boot-part --efi-boot-image --protective-msdos-label \
118+
$$isodir -o hexium_os-tests/hexium_os-$$testname.iso; \
119+
./limine/limine bios-install hexium_os-tests/hexium_os-$$testname.iso; \
120+
rm -rf $$isodir; \
121+
done
122+
72123
.PHONY: clean
73124
clean:
74125
$(MAKE) -C kernel clean
75-
rm -rf iso_root $(IMAGE_NAME).iso
126+
rm -rf iso_root *.iso
127+
rm -rf hexium_os-tests
76128

77129
.PHONY: distclean
78130
distclean: clean

kernel/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
/kernel
22
/target
3+
/kernel-test
4+
.test-log.txt

kernel/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,7 @@ pc-keyboard = "0.7.0"
1818
x86 = "0.52.0"
1919
x86_64 = "0.14.2"
2020
uart_16550 = "0.3.2"
21+
22+
[[test]]
23+
name="should_panic"
24+
harness=false

kernel/GNUmakefile

+16
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,26 @@ all:
2828
RUSTFLAGS="-C relocation-model=static" cargo build --target $(RUST_TARGET) --profile $(RUST_PROFILE)
2929
cp target/$(RUST_TARGET)/$(RUST_PROFILE_SUBDIR)/hexium_os kernel
3030

31+
test:
32+
mkdir -p kernel-test
33+
RUSTFLAGS="-C relocation-model=static" cargo test --no-run --target $(RUST_TARGET) --profile $(RUST_PROFILE) --color always \
34+
2>&1 | tee .test-log.txt
35+
@grep -o 'target/[^ )]*' .test-log.txt | while read -r path; do \
36+
if [ -x "$$path" ]; then \
37+
echo "Copying $$path to kernel-test/"; \
38+
cp "$$path" kernel-test/; \
39+
else \
40+
echo "Skipping non-executable: $$path"; \
41+
fi \
42+
done
43+
44+
@rm .test-log.txt
45+
3146
.PHONY: clean
3247
clean:
3348
cargo clean
3449
rm -rf kernel
50+
rm -rf kernel-test
3551

3652
.PHONY: distclean
3753
distclean: clean

kernel/src/devices/keyboard/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ pub async fn trace_keypresses() {
9696
if let Some(key) = keyboard.process_keyevent(key_event) {
9797
match key {
9898
DecodedKey::Unicode(character) => {
99-
trace!("Received keyboard interrupt with key: {}\n", character)
99+
trace!("Received keyboard interrupt with key: {}", character)
100100
}
101101
DecodedKey::RawKey(key) => {
102-
trace!("Received keyboard interrupt with key: {:?}\n", key)
102+
trace!("Received keyboard interrupt with key: {:?}", key)
103103
}
104104
}
105105
}

kernel/src/fs/ramfs.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,12 @@ impl RamFs {
2020

2121
impl FileSystem for RamFs {
2222
fn mount(&mut self, _path: &str) -> Result<(), ()> {
23-
info!("RamFs mounted\n");
23+
info!("RamFs mounted");
2424
Ok(())
2525
}
2626

2727
fn unmount(&mut self) -> Result<(), String> {
28-
info!("RamFs unmounted\n");
28+
info!("RamFs unmounted");
2929
Ok(())
3030
}
3131

@@ -60,7 +60,7 @@ pub fn init(vfs: &mut VFS) {
6060
if let Some(module_response) = boot::MODULE_REQUEST.get_response() {
6161
let modules = module_response.modules();
6262
if !modules.is_empty() {
63-
trace!("Ramdisk information:\n");
63+
trace!("Ramdisk information:");
6464
print!(" Ramdisk address: {:?}\n", modules[0].addr());
6565
print!(" Ramdisk size (bytes): {:?}\n", modules[0].size());
6666
print!(" Ramdisk module path: {:?}\n", modules[0].path());

kernel/src/interrupts/idt.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub fn init() {
2929
}
3030

3131
extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
32-
debug!("EXCEPTION: BREAKPOINT\n{:#?}\n", stack_frame);
32+
debug!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
3333
}
3434

3535
extern "x86-interrupt" fn double_fault_handler(

kernel/src/lib.rs

+74-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
#![no_std]
2+
#![no_main]
23
#![feature(abi_x86_interrupt)]
4+
#![feature(custom_test_frameworks)]
5+
#![test_runner(crate::test_runner)]
6+
#![reexport_test_harness_main="test_main"]
37

48
extern crate alloc;
59

610
use alloc::string::String;
7-
use core::arch::asm;
11+
use core::{arch::asm, panic::PanicInfo};
812

913
pub mod boot;
1014
pub mod devices;
@@ -26,11 +30,13 @@ pub fn init() {
2630

2731
let mut vfs = fs::vfs::VFS::new(None);
2832
fs::ramfs::init(&mut vfs);
33+
2934
print_startup_message(&mut vfs);
3035

31-
let mut executor = crate::task::executor::Executor::new();
32-
let _ = executor.spawn(crate::task::Task::new(devices::keyboard::trace_keypresses()));
33-
executor.run();
36+
// Issue#30: Commented out for now as the code doesn't run past this section. Will return it back.
37+
// let mut executor = crate::task::executor::Executor::new();
38+
// let _ = executor.spawn(crate::task::Task::new(devices::keyboard::trace_keypresses()));
39+
// executor.run();
3440

3541
//vfs.unmount_fs();
3642
}
@@ -42,16 +48,16 @@ fn print_startup_message(vfs: &mut fs::vfs::VFS) -> [u8; 128] {
4248
Ok(vnode) => match vfs.read_file(&vnode, &mut buffer, 0) {
4349
Ok(_bytes_read) => {}
4450
Err(err) => {
45-
error!("Error reading file: {}\n", err);
51+
error!("Error reading file: {}", err);
4652
}
4753
},
4854
Err(err) => {
49-
error!("File not found: {}\n", err);
55+
error!("File not found: {}", err);
5056
}
5157
}
5258

5359
info!(
54-
"Hexium OS kernel v{} succesfully initialized at {}\n",
60+
"Hexium OS kernel v{} succesfully initialized at {}",
5561
env!("CARGO_PKG_VERSION"),
5662
unsafe { rtc::read_rtc() }
5763
);
@@ -72,3 +78,64 @@ pub fn hlt_loop() -> ! {
7278
}
7379
}
7480
}
81+
82+
pub fn test_panic_handler(info: &PanicInfo) -> ! {
83+
serial_println!("[failed]");
84+
serial_println!("Error: {}", info);
85+
exit_qemu(QemuExitCode::Failed);
86+
loop{}
87+
}
88+
89+
pub fn test_runner(tests: &[&dyn Testable]) {
90+
serial_println!("Running {} tests", tests.len());
91+
92+
for test in tests {
93+
test.run();
94+
}
95+
96+
exit_qemu(QemuExitCode::Success);
97+
}
98+
99+
100+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101+
#[repr(u32)]
102+
pub enum QemuExitCode {
103+
Success = 0x10,
104+
Failed = 0x11,
105+
}
106+
107+
pub fn exit_qemu(exit_code: QemuExitCode) {
108+
use x86_64::instructions::port::Port;
109+
110+
unsafe {
111+
let mut port = Port::new(0xf4);
112+
port.write(exit_code as u32);
113+
}
114+
}
115+
116+
pub trait Testable {
117+
fn run(&self);
118+
}
119+
120+
impl<T> Testable for T
121+
where T : Fn(),
122+
{
123+
fn run(&self) {
124+
serial_print!("{}...\t", core::any::type_name::<T>());
125+
self();
126+
serial_println!("[ok]");
127+
}
128+
}
129+
130+
#[cfg(test)]
131+
#[unsafe(no_mangle)]
132+
unsafe extern "C" fn kmain() -> ! {
133+
test_main();
134+
loop {}
135+
}
136+
137+
#[cfg(test)]
138+
#[panic_handler]
139+
fn panic(info: &PanicInfo) -> ! {
140+
test_panic_handler(info)
141+
}

kernel/src/log.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ macro_rules! log {
2626
$crate::log::LogLevel::Panic => ("PANIC", "\x1b[97;41m"), // White text on Red background
2727
};
2828

29-
$crate::print!("{}[{}]\x1b[0m {}", color_code, label, format_args!($($arg)*));
29+
$crate::println!("{}[{}]\x1b[0m {}", color_code, label, format_args!($($arg)*));
3030
}};
3131
}
3232

kernel/src/main.rs

+38-3
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,56 @@
11
#![no_std]
22
#![no_main]
3+
#![feature(custom_test_frameworks)]
4+
#![test_runner(hexium_os::test_runner)]
5+
#![reexport_test_harness_main = "test_main"]
36

7+
use core::panic::PanicInfo;
48
use hexium_os::{boot, hlt_loop, init, panic_log};
59

10+
#[test_case]
11+
fn test_example() {
12+
assert_eq!(1+1, 2);
13+
}
14+
15+
#[cfg(test)]
16+
#[unsafe(no_mangle)]
17+
unsafe extern "C" fn kmain() -> ! {
18+
assert!(boot::BASE_REVISION.is_supported());
19+
init();
20+
test_main();
21+
loop {}
22+
}
23+
24+
#[cfg(not(test))]
625
#[unsafe(no_mangle)]
726
unsafe extern "C" fn kmain() -> ! {
827
assert!(boot::BASE_REVISION.is_supported());
928

29+
/*
30+
Issue#30: The lines at the end of this comment below do not seem to have an effect after the init method above
31+
however calling them above the init method causes a boot-loop.
32+
NOTE: Calling them after the init method after the executor code has been commented back in,
33+
will cause them not to be run as the executor code seems to block the 'thread'.
34+
print!("Test");
35+
println!("Test2");
36+
*/
37+
1038
init();
1139

1240
hlt_loop();
1341
}
1442

43+
#[cfg(not(test))]
1544
#[panic_handler]
16-
fn rust_panic(info: &core::panic::PanicInfo) -> ! {
17-
use hexium_os::utils::registers::*;
45+
fn panic(info: &PanicInfo) -> ! {
46+
use hexium_os::utils::registers::{print_register_dump, get_registers};
1847
panic_log!("{}\n", info);
1948
print_register_dump(&get_registers());
20-
hlt_loop();
49+
loop {}
2150
}
51+
52+
#[cfg(test)]
53+
#[panic_handler]
54+
fn panic(info: &PanicInfo) -> ! {
55+
hexium_os::test_panic_handler(info)
56+
}

kernel/src/memory/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ pub mod paging;
1010
static PHYS_MEM_OFFSET: Once<VirtAddr> = Once::new();
1111
static mut MEM_MAPPER: Option<OffsetPageTable<'static>> = None;
1212

13-
pub fn init() -> () {
13+
pub fn init() {
1414
if let Some(hhdm_response) = boot::HHDM_REQUEST.get_response() {
1515
PHYS_MEM_OFFSET.call_once(|| VirtAddr::new(hhdm_response.offset()));
1616
}
17-
trace!("Hhdm offset: {:#x}\n", phys_mem_offset());
17+
trace!("Hhdm offset: {:#x}", phys_mem_offset());
1818

1919
// Create frame allocator
2020
let mut frame_allocator =

kernel/src/memory/paging.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ pub extern "x86-interrupt" fn page_fault_handler(
2828
) {
2929
use x86_64::registers::control::Cr2;
3030

31-
error!("EXCEPTION: PAGE FAULT\n");
32-
error!("Accessed Address: {:?}\n", Cr2::read());
33-
error!("Error Code: {:?}\n", error_code);
31+
error!("EXCEPTION: PAGE FAULT");
32+
error!("Accessed Address: {:?}", Cr2::read());
33+
error!("Error Code: {:?}", error_code);
3434
print!("\n{:#?}\n", stack_frame);
3535
hlt_loop();
3636
}

0 commit comments

Comments
 (0)