From 80ae717dd9af1e27e9846cc21923cf47e8e17474 Mon Sep 17 00:00:00 2001
From: funky <funkyeggdev@proton.me>
Date: Sun, 24 Nov 2024 11:21:39 +1100
Subject: [PATCH] Introduced kernel testing system ktest

This commit also fixed a small issue with panic handler formatting
---
 Cargo.lock                    |  9 ++++++++
 kernel/Cargo.toml             |  3 +++
 kernel/ktest_macro/Cargo.toml | 11 ++++++++++
 kernel/ktest_macro/src/lib.rs | 29 +++++++++++++++++++++++++
 kernel/lds/x86_64.ld          |  7 ++++++
 kernel/src/kmain.rs           | 15 +++++++------
 kernel/src/ktest.rs           | 38 +++++++++++++++++++++++++++++++++
 kernel/src/lib.rs             | 19 ++++++-----------
 repbuild/src/main.rs          | 40 ++++++++++++++++++++++++++++++++---
 9 files changed, 149 insertions(+), 22 deletions(-)
 create mode 100644 kernel/ktest_macro/Cargo.toml
 create mode 100644 kernel/ktest_macro/src/lib.rs
 create mode 100644 kernel/src/ktest.rs

diff --git a/Cargo.lock b/Cargo.lock
index 8dc4baa..baaf1a6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -392,6 +392,7 @@ dependencies = [
  "derive_more",
  "hashbrown",
  "hbvm",
+ "ktest_macro",
  "limine",
  "log",
  "sbi",
@@ -404,6 +405,14 @@ dependencies = [
  "xml",
 ]
 
+[[package]]
+name = "ktest_macro"
+version = "0.1.0"
+dependencies = [
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "lazy_static"
 version = "1.5.0"
diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml
index 9cbe895..9cee4de 100644
--- a/kernel/Cargo.toml
+++ b/kernel/Cargo.toml
@@ -3,12 +3,15 @@ edition = "2021"
 name = "kernel"
 version = "0.2.0"
 
+[features]
+ktest = []
 
 [dependencies]
 # embedded-graphics = "0.8"
 hbvm = { git = "https://git.ablecorp.us/AbleOS/holey-bytes.git", features = [
 	"nightly",
 ] }
+ktest_macro = { path = "ktest_macro" }
 log = "0.4"
 spin = "0.9"
 slab = { version = "0.4", default-features = false }
diff --git a/kernel/ktest_macro/Cargo.toml b/kernel/ktest_macro/Cargo.toml
new file mode 100644
index 0000000..5c85e6c
--- /dev/null
+++ b/kernel/ktest_macro/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+edition = "2021"
+name = "ktest_macro"
+version = "0.1.0"
+
+[lib]
+proc-macro = true
+
+[dependencies]
+quote = "1.0.37"
+syn = { version = "2.0.89", features = ["full"] }
\ No newline at end of file
diff --git a/kernel/ktest_macro/src/lib.rs b/kernel/ktest_macro/src/lib.rs
new file mode 100644
index 0000000..c1c3a8a
--- /dev/null
+++ b/kernel/ktest_macro/src/lib.rs
@@ -0,0 +1,29 @@
+extern crate proc_macro;
+extern crate quote;
+extern crate syn;
+use {
+    proc_macro::TokenStream,
+    quote::quote,
+    syn::{parse_macro_input, ItemFn}
+};
+
+#[proc_macro_attribute]
+pub fn ktest(_attr: TokenStream, item: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(item as ItemFn);
+    let test_name = &input.sig.ident;
+    let static_var_name = syn::Ident::new(
+        &format!("__ktest_{}", test_name),
+        test_name.span(),
+    );
+    let out = quote! {
+        // #[cfg(feature = "ktest")]
+        #input
+
+        // #[cfg(feature = "ktest")]
+        #[unsafe(link_section = ".note.ktest")]
+        #[used]
+        pub static #static_var_name: fn() = #test_name;
+    };
+
+    TokenStream::from(out)
+}
\ No newline at end of file
diff --git a/kernel/lds/x86_64.ld b/kernel/lds/x86_64.ld
index 19ee79f..84e8c4d 100644
--- a/kernel/lds/x86_64.ld
+++ b/kernel/lds/x86_64.ld
@@ -40,6 +40,13 @@ SECTIONS
         *(.data .data.*)
         *(.got .got.*)
     } :data
+    
+    /* Add the .ktest section for test functions */
+    .note.ktest : {
+        __ktest_start = .;   /* Mark the beginning of the section */
+        *(.note.ktest)            /* Include all items in the .ktest section */
+        __ktest_end = .;     /* Mark the end of the section */
+    }
 
     .bss : {
         *(COMMON)
diff --git a/kernel/src/kmain.rs b/kernel/src/kmain.rs
index ec87c6f..625b9cd 100644
--- a/kernel/src/kmain.rs
+++ b/kernel/src/kmain.rs
@@ -22,6 +22,14 @@ use {
 pub fn kmain(_cmdline: &str, boot_modules: BootModules) -> ! {
     debug!("Entered kmain");
 
+    #[cfg(feature = "ktest")] {
+        use crate::ktest;
+        debug!("TESTING");
+        ktest::test_main();
+
+        loop {};
+    }
+
     // let kcmd = build_cmd("Kernel Command Line", cmdline);
     // trace!("Cmdline: {kcmd:?}");
 
@@ -148,10 +156,3 @@ pub static IPC_BUFFERS: Lazy<Mutex<IpcBuffers>> = Lazy::new(|| {
 
     Mutex::new(bufs)
 });
-
-#[test_case]
-fn trivial_assertion() {
-    trace!("trivial assertion... ");
-    assert_eq!(1, 1);
-    info!("[ok]");
-}
diff --git a/kernel/src/ktest.rs b/kernel/src/ktest.rs
new file mode 100644
index 0000000..43a2a11
--- /dev/null
+++ b/kernel/src/ktest.rs
@@ -0,0 +1,38 @@
+pub use ktest_macro::ktest;
+use log::debug;
+
+extern "C" {
+    static __ktest_start: fn();
+    static __ktest_end: fn();
+}
+
+// TODO: Get test_fn linker name (may require no_mangle in macro)
+//       More info on tests (run the rest even if panic)
+//       Implement ktest for arm and riscv (Later problems, see below)
+//       Allow for arch specific tests (Leave for now)
+//       Allow for ktest test name attr
+//       Usefull message at the end of testing
+pub fn test_main() {
+    unsafe {
+        let mut current_test = &__ktest_start as *const fn();
+        let mut current = 1;
+        let test_end = &__ktest_end as *const fn();        
+
+        while current_test < test_end {
+            let test_fn = *current_test;
+
+            debug!("Running test {}", current);
+            
+            test_fn();
+            debug!("Test {} passed", current);
+
+            current_test = current_test.add(1);
+            current += 1;
+        }
+    }
+}
+
+#[ktest]
+pub fn trivial_assertion() {
+    assert_eq!(1, 1);
+}
\ No newline at end of file
diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs
index f5bf86f..52956ec 100644
--- a/kernel/src/lib.rs
+++ b/kernel/src/lib.rs
@@ -2,6 +2,7 @@
 //! Named akern.
 //! Akern is woefully undersupported at the moment but we are looking to add support improve hardware discovery and make our lives as kernel and operating system developers easier and better
 #![no_std]
+#![no_main]
 #![feature(
     slice_split_once,
     exclusive_wrapper,
@@ -10,11 +11,9 @@
     lazy_get,
     alloc_error_handler,
     ptr_sub_ptr,
-    custom_test_frameworks,
     naked_functions,
-    pointer_is_aligned_to
+    pointer_is_aligned_to,
 )]
-#![test_runner(crate::test_runner)]
 #![allow(dead_code, internal_features, static_mut_refs)]
 extern crate alloc;
 
@@ -33,6 +32,10 @@ mod memory;
 mod task;
 mod utils;
 
+// #[cfg(feature = "tests")]
+mod ktest;
+
+use alloc::string::ToString;
 use versioning::Version;
 
 /// Kernel's version
@@ -56,15 +59,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! {
         ));
     }
 
-    let msg = info.message();
+    let msg = info.message().to_string().replace("\n", "\r\n");
     let _ = crate::arch::log(format_args!("{msg}\r\n"));
     loop {}
 }
-
-#[cfg(test)]
-fn test_runner(tests: &[&dyn Fn()]) {
-    println!("Running {} tests", tests.len());
-    for test in tests {
-        test();
-    }
-}
diff --git a/repbuild/src/main.rs b/repbuild/src/main.rs
index 2741ef5..5a80bf4 100644
--- a/repbuild/src/main.rs
+++ b/repbuild/src/main.rs
@@ -27,6 +27,7 @@ fn main() -> Result<(), Error> {
             let mut release = false;
             let mut debuginfo = false;
             let mut target = Target::X86_64;
+            let mut tests = false;
             for arg in args {
                 if arg == "-r" || arg == "--release" {
                     release = true;
@@ -38,17 +39,42 @@ fn main() -> Result<(), Error> {
                     target = Target::Aarch64;
                 } else if arg == "avx2" {
                     target = Target::X86_64Avx2;
+                } else if arg == "--ktest" {
+                    tests = true;
                 } else {
                     return Err(report!(Error::InvalidSubCom));
                 }
             }
 
-            build(release, target, debuginfo).change_context(Error::Build)
+            build(release, target, debuginfo, tests).change_context(Error::Build)
         }
+        // Some("test" | "t") => {
+        //     let mut release = false;
+        //     let mut debuginfo = false;
+        //     let mut target = Target::X86_64;
+        //     for arg in args {
+        //         if arg == "-r" || arg == "--release" {
+        //             release = true;
+        //         } else if arg == "-d" || arg == "--debuginfo" {
+        //             debuginfo = true;
+        //         } else if arg == "rv64" || arg == "riscv64" || arg == "riscv64-virt" {
+        //             target = Target::Riscv64Virt;
+        //         } else if arg == "arm64" || arg == "aarch64" || arg == "aarch64-virt" {
+        //             target = Target::Aarch64;
+        //         } else if arg == "avx2" {
+        //             target = Target::X86_64Avx2;
+        //         } else {
+        //             return Err(report!(Error::InvalidSubCom));
+        //         }
+        //     }
+
+        //     test(release, target, debuginfo).change_context(Error::Build)
+        // }
         Some("run" | "r") => {
             let mut release = false;
             let mut debuginfo = false;
             let mut target = Target::X86_64;
+            let mut tests = false;
             let mut do_accel = true;
             for arg in args {
                 if arg == "-r" || arg == "--release" {
@@ -63,12 +89,14 @@ fn main() -> Result<(), Error> {
                     do_accel = false;
                 } else if arg == "avx2" {
                     target = Target::X86_64Avx2;
+                } else if arg == "--ktest" {
+                    tests = true; 
                 } else {
                     return Err(report!(Error::InvalidSubCom));
                 }
             }
 
-            build(release, target, debuginfo)?;
+            build(release, target, debuginfo, tests)?;
             run(release, target, do_accel)
         }
         Some("help" | "h") => {
@@ -82,6 +110,7 @@ fn main() -> Result<(), Error> {
                 "        -r / --release: build in release mode\n",
                 "        -d / --debuginfo: build with debug info\n",
                 "        --noaccel: run without acceleration (e.g, no kvm)\n",
+                "        --ktest: Enables tests via ktest\n",
                 "[ rv64 / riscv64 / riscv64-virt / aarch64 / arm64 / aarch64-virt / avx2 ]: sets target"
             ),);
             Ok(())
@@ -310,7 +339,7 @@ fn copy_file_to_img(fpath: &str, fs: &FileSystem<File>) {
     .expect("Copy failed");
 }
 
-fn build(release: bool, target: Target, debuginfo: bool) -> Result<(), Error> {
+fn build(release: bool, target: Target, debuginfo: bool, tests: bool) -> Result<(), Error> {
     let fs = get_fs().change_context(Error::Io)?;
     let mut com = Command::new("cargo");
     com.current_dir("kernel");
@@ -322,6 +351,10 @@ fn build(release: bool, target: Target, debuginfo: bool) -> Result<(), Error> {
         com.env("RUSTFLAGS", "-Cdebug-assertions=true");
     }
 
+    if tests {
+        com.args(["--features", "ktest"]);
+    }
+
     if target == Target::Riscv64Virt {
         com.args(["--target", "targets/riscv64-virt-ableos.json"]);
     }
@@ -473,6 +506,7 @@ fn run(release: bool, target: Target, do_accel: bool) -> Result<(), Error> {
     }
 }
 
+
 fn fetch_ovmf(target: Target) -> Result<String, OvmfFetchError> {
     let (ovmf_url, ovmf_path) = match target {
         Target::X86_64 | Target::X86_64Avx2 => (