Open Rain336 opened 10 months ago
Considering we are running in a VM, getting a known good timer is actually easy, since the testing framework only needs to support one timer that is guaranteed to be provided by QEMU. For x86_64
this will most likely be the TimeStamp Counter (TSC) which can be easily used to implement a monotonic clock, similar to Instant
. This is basically all we need for time tracking.
Feature: DragonTest Unit Testing Framework
Microdragon needs unit testing, but the default libtest framework cannot run without libstd, so a new testing framework needs to be written using the
custom_test_frameworks
nightly feature. This new test framework should offer most of the features of libtest and maybe some extensions specific to testing kernels.Writing Tests
Writing tests should be as similar to libtest as possible. To do this we need to overwrite a few builtin macros normally used by libtest with our own
proc-macros
.#[test]
and#[bench]
#[test]
and#[bench]
are used to write tests and benchmarks respectively. Our macros, similar to their builtin counterparts, will generate astatic
item with a#[test_case]
attribute macro. This item will be of typeTestSpec
, our version ofTestDescAndFn
from libtest, but more simplified, since we don't need to support dynamic tests.pub enum ShouldPanic { No, Yes, YesWithMessage(&'static str), }
pub struct TestSpec { pub func: TestFn, pub name: &'static str, pub ignore: bool, pub ignore_message: Option<&'static str>, pub source_file: &'static str, pub should_panic: ShouldPanic, }
#[shuld_panic]
#[shuld_panic]
is an additional attribute macro that can be used in combination with#[test]
and#[bench]
. It cannot stand alone, so no proc-macro function is written, instead the#[test]
and#[bench]
macros check for it. It controls theshould_panic
field in theTestSpec
item. Here a few examples of how functions would be translated:[test]
[shuld_panic(expected = "panic message contains this")]
fn sample_test_message() {}
#[ignore]
#[ignore]
, similar to#[shuld_panic]
is an additional attribute macro that can be used in combination with#[test]
and#[bench]
. It also has no proc-macro function and instead the#[test]
and#[bench]
macros check for it. It controls theignore
andignore_message
fields in theTestSpec
item. Here a few examples of how functions would be translated:[test]
[ignore = "This test is ignored"]
fn sample_test_message() {}
assert!
,assert_eq!
,assert_ne!
andassert_match!
The default implementation forassert!
,assert_eq!
,assert_ne!
andassert_match!
can be used, but a more verbose and pretty version similar to pretty_assertions should be considered.Running Tests
The plan is to integrate with the
cargo test
command, which requires us to write a runner that can package and iso and start qemu for running the tests. This would require either a complete reimplementation of theJustfile
in rust or modifying theJustfile
to be able to act as our test runner. I would go the route of rewriting theJustfile
in rust and actually replacing it completely with it. Then we could also define a runner for normal builds and makecargo run
work.Additionally,
cargo test
accepts different options that are processed by libtest, most of these have to be implemented by us too to provide adequate support. The following are not supported:--force-run-in-process
--logfile <PATH>
--nocapture
print!
andprintln!
macros could be provided--test-threads
-Z
For the command line options to be considered, they also need to be passed into the VM, this can be done by passing the options as bootloader kernel arguments. Most bootloaders support this and it can be easily prepared by the test runner's packing step.
Integrating the Test Framework
The test framework supplies a main entry point that can be passed to the
#![test_runner]
macro. Then the re-exported test main function just has to be called to start the test framework.Handling panics requires another function from the test framework to process the panic and continue testing.
Open Questions