Merge pull request #223 from arihant2math/unit-testing

This commit is contained in:
Matthew Leach
2026-02-20 04:52:31 +00:00
committed by GitHub
7 changed files with 210 additions and 9 deletions

View File

@@ -23,20 +23,34 @@ jobs:
# If all features are disabled
- name: Run tests inside docker container
if: ${{ matrix.smp-feature == '' }}
run: docker run moss "/bin/bash" -c "cargo run -r --no-default-features -- /bin/usertest" >> out.log
run: |
docker run moss "/bin/bash" -c "cargo run -r --no-default-features -- /bin/usertest" >> usertest.log
docker run moss "/bin/bash" -c "cargo test -r --no-default-features" >> unittest.log
# If any feature is enabled
- name: Run tests inside docker container
if: ${{ matrix.smp-feature == 'smp' }}
run: docker run moss "/bin/bash" -c "cargo run -r --no-default-features --features "${{ matrix.smp-feature }}" -- /bin/usertest" >> out.log
- name: Display test output
run: cat out.log
- name: Check for success line
run: grep -q "All tests passed in " out.log || (echo "Tests failed" && exit 1)
- name: Upload test output as artifact
run: |
docker run moss "/bin/bash" -c "cargo run -r --no-default-features --features "${{ matrix.smp-feature }}" -- /bin/usertest" >> usertest.log
docker run moss "/bin/bash" -c "cargo test -r --no-default-features --features "${{ matrix.smp-feature }}"" >> unittest.log
- name: Display usertest output
run: cat usertest.log
- name: Display unit test output
run: cat unittest.log
- name: Check for usertest success line
run: grep -q "All tests passed in " usertest.log || (echo "Usertests failed" && exit 1)
- name: Check for unit test success line
run: |
grep -q "test result: .*ok.*\..*passed" unittest.log || (echo "Unit tests failed" && exit 1)
- name: Upload usertest output as artifact
uses: actions/upload-artifact@v6
with:
name: test-output-${{ matrix.smp-feature || 'up' }}
path: out.log
name: usertest-output-${{ matrix.smp-feature || 'up' }}
path: usertest.log
- name: Upload unittest output as artifact
uses: actions/upload-artifact@v6
with:
name: unittest-output-${{ matrix.smp-feature || 'up' }}
path: unittest.log
upload-image:
runs-on: ubuntu-latest

View File

@@ -10,6 +10,11 @@ name = "moss"
version = "0.1.0"
edition = "2024"
[[bin]]
name = "moss"
test = true
bench = false
[dependencies]
libkernel = { path = "libkernel" }
aarch64-cpu = "11.1.0"

View File

@@ -27,3 +27,20 @@ pub fn set_date(duration: Duration) {
// Represents a known duration since the epoch at the assoicated instant.
static EPOCH_DURATION: SpinLock<Option<(Duration, Instant)>> = SpinLock::new(None);
#[cfg(test)]
mod tests {
use super::*;
use crate::ktest;
ktest! {
fn test_date_and_set_date() {
let initial_date = date();
let new_date = Duration::from_secs(1_000_000);
set_date(new_date);
let updated_date = date();
assert_ne!(initial_date, updated_date, "Date should change after set_date");
assert!(updated_date >= new_date, "Updated date should be at least the new date set");
}
}
}

View File

@@ -626,3 +626,15 @@ impl VFS {
fs.sync().await
}
}
#[cfg(test)]
mod tests {
use crate::fs::VFS;
use crate::ktest;
ktest! {
async fn test_sync_all() {
VFS.sync_all().await.unwrap();
}
}
}

View File

@@ -101,3 +101,24 @@ impl KPipe {
self.inner.capacity()
}
}
#[cfg(test)]
mod tests {
use crate::kernel::kpipe::KPipe;
use crate::ktest;
ktest! {
async fn kpipe_basic() {
let pipe = KPipe::new().unwrap();
pipe.push(1).await;
pipe.push(2).await;
pipe.push(3).await;
let val1 = pipe.pop().await;
let val2 = pipe.pop().await;
let val3 = pipe.pop().await;
assert_eq!(val1, 1);
assert_eq!(val2, 2);
assert_eq!(val3, 3);
}
}
}

View File

@@ -3,6 +3,11 @@
#![feature(used_with_arg)]
#![feature(likely_unlikely)]
#![feature(box_as_ptr)]
#![expect(internal_features)]
#![feature(core_intrinsics)]
#![feature(custom_test_frameworks)]
#![reexport_test_harness_main = "test_main"]
#![test_runner(crate::testing::test_runner)]
use alloc::{
boxed::Box,
@@ -45,6 +50,8 @@ mod memory;
mod process;
mod sched;
mod sync;
#[cfg(test)]
pub mod testing;
#[panic_handler]
fn on_panic(info: &PanicInfo) -> ! {
@@ -161,6 +168,9 @@ async fn launch_init(mut opts: KOptions) {
.expect("Could not clone FD");
}
#[cfg(test)]
test_main();
drop(task);
let mut init_args = vec![init.as_str().to_string()];

122
src/testing/mod.rs Normal file
View File

@@ -0,0 +1,122 @@
use crate::arch::{Arch, ArchImpl};
use crate::console::write_fmt;
use crate::drivers::timer::uptime;
use alloc::format;
use core::fmt::Display;
const TEXT_GREEN: &str = "\x1b[32m";
const TEXT_RED: &str = "\x1b[31m";
const TEXT_RESET: &str = "\x1b[0m";
pub enum TestResult {
Ok,
Failed,
Skipped,
}
impl Display for TestResult {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
TestResult::Ok => write!(f, "{TEXT_GREEN}ok{TEXT_RESET}"),
TestResult::Failed => write!(f, "{TEXT_GREEN}failed{TEXT_RESET}"),
TestResult::Skipped => write!(f, "skipped"),
}
}
}
pub struct Test {
pub name: &'static str,
pub test_fn: fn() -> TestResult,
}
#[cfg(test)]
pub fn test_runner(tests: &[&Test]) {
write_fmt(format_args!("\nrunning {} tests\n", tests.len())).unwrap();
let mut passed = 0;
let mut failed = 0;
let mut ignored = 0;
let start = uptime();
for test in tests {
let result = (test.test_fn)();
match result {
TestResult::Ok => passed += 1,
TestResult::Failed => failed += 1,
TestResult::Skipped => ignored += 1,
}
write_fmt(format_args!("test {} ... {}\n", test.name, result)).unwrap();
}
let duration = uptime() - start;
write_fmt(format_args!(
"\ntest result: {}. {passed} passed; {failed} failed; {ignored} ignored; finished in {}.{}s\n",
if failed == 0 {
format!("{TEXT_GREEN}ok{TEXT_RESET}")
} else {
format!("{TEXT_RED}FAILED{TEXT_RESET}")
},
duration.as_secs(),
duration.subsec_millis() / 10
))
.unwrap();
ArchImpl::power_off();
}
pub fn panic_noop(_: *mut u8, _: *mut u8) {}
#[macro_export]
macro_rules! ktest {
($name:ident, fn $fn_name:ident() $body:block) => {
#[cfg(test)]
fn $fn_name(_: *mut u8) {
$body
}
paste::paste! {
#[cfg(test)]
#[test_case]
static [<__TEST_ $name>]: crate::testing::Test = crate::testing::Test {
name: concat!(module_path!(), "::", stringify!($name)),
test_fn: || {
let result = unsafe {
core::intrinsics::catch_unwind(
$fn_name as fn(*mut u8),
core::ptr::null_mut(),
crate::testing::panic_noop,
)
};
match result {
0 => crate::testing::TestResult::Ok,
1 => crate::testing::TestResult::Failed,
_ => unreachable!("catch_unwind should only return 0 or 1"),
}
},
};
}
};
(fn $name:ident() $body:block) => {
crate::ktest!($name, fn $name() $body);
};
(async fn $name:ident() $body:block) => {
async fn $name() {
$body
}
paste::paste! {
crate::ktest! {
$name,
fn [<__sync_ $name>]() {
let mut fut = alloc::boxed::Box::pin($name());
let desc = crate::process::TaskDescriptor::from_tgid_tid(crate::process::thread_group::Tgid(0), crate::process::Tid(0));
let waker = crate::sched::waker::create_waker(desc);
let mut ctx = core::task::Context::from_waker(&waker);
loop {
match fut.as_mut().poll(&mut ctx) {
core::task::Poll::Ready(()) => break,
_ => {},
}
}
}
}
}
}
}