Test
OS in Rust
Announcements
- Action Items:
- How is graphics?
- Are you learning NumPy?
- Anyone using CUDA?
- How is graphics?
Citations
- I am geniunely convinced of the usefulness of automated testing when developing a system.
- The most high profile case of this is the testing framework for
4096_t.c - Hard to persuade students of the usefulness of this on small class assignments.
- \(\therefore\) I’ve never written anything of any reasonable size in Rust, so
- Testing
Today
- Testing under
no_std - QEMU
bootimage- Still not in love with this dependency, but we do what we can.
Pre-work
- Make sure you have a
.cargo/config.toml - No idea how you wouldn’t have one of these yet!
- Just make sure you have it.
Modern Language
- Like Python with Pytest, and unlike the only other systems language, Rust has a testing framework.
- It’s covered in Rust book
- Naturally I skipped this chapter.
Framework
- Use the
#[test]attribute on some functions. - Including assertions within those functions.
cargo testor, if you see someone around,cargo t, will check the assertions.- The crate will otherwise be non-impacted.
Review
- To enable testing for our kernel binary, we can set the
testflag in the Cargo.toml totrue. - We recall our
Cargo.toml- mine no longer has an allusions to panic with the introduction of a JSON target.
Enable
- This is necessary but not sufficient.
On [[bin]]
- Just one for us for now.
Working within no_std
- Testing is complicated by
no_std. - Rust implicitly uses the
testlibrary, which depends onstd
$ cargo t
Compiling bootloader v0.9.34
error[E0463]: can't find crate for `core`
|
= note: the `x86_64-osirs` target may not be installed
= help: consider downloading the target with `rustup target add x86_64-osirs`
For more information about this error, try `rustc --explain E0463`.
error: could not compile `bootloader` (lib) due to 1 previous errorWhat We Lose
- Vs.
std, we lose e.g.should_panictests. - That’s okay, we are crafty (and wrote our own panic handler anyway).
To Main
- My main looks like this:
Just a note
- When you make a new version for today (
60)- Probably leave out colors (
52) and - Instead start with something with
println!(likely51)
- Probably leave out colors (
Today’s Starting Point
Adding to Main
src/main.rs
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(crate::test_runner)]
mod vga;
#[cfg(test)]
pub fn test_runner(tests: &[&dyn Fn()]) {
println!("Running {} tests", tests.len());
for test in tests {
test();
}
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
println!("{}", info);
loop {}
}
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
println!("Hello world!");
loop {}
}Wait a minute
- What is
&dyn? - “Keyword
dyn
The
dynkeyword is used to highlight that calls to methods on the associatedTraitare dynamically dispatched.
Dynamic Dispatch
OOP
- Yeah I’m not doing that.
- We’ll maintain a list of functions.
- If you want to learn about OOP, here is a tutorial on writing a Java app for Android.
- Create your first Android app
- Whoops its Kotlin (multi-paradigm).
- Yet more evidence of the correctness of OOP.
Write Two Functions
- Doesn’t matter but nice to have externally observable functions.
Make an array
- Just in
_startfor now.
Loop over the array
- Same as any other for each loop.
- I am willing to use the for each loop, but still regard it with suspicion.
Call each function
- What do you see?
Sample out
Aside: Bits
Pulling back the curtain
- I’m insane.
- I ran this then screendumped (in QEMU) to
fs.ppm - I converted to
.png
- I
base64the PNG.- See next slide.
It’s tiny!
$ base64 fs.png | wc
25 25 1861
$ base64 fs.png
iVBORw0KGgoAAAANSUhEUgAAAtAAAAGQCAIAAAAIhcA6AAAFJ0lEQVR4nO3dyXaDIBQAUMzp//9y
ujAlVgw+p4TovasaFKEb8DGlBAAAAPDtuvv9nlLquq6/Hl3W9TdP3l9JakS9hLNVa7ZeANCg25aH
K41u++1xvYSvUnNHJP8BAMza1OEAAIj4qSePvuO3xy1WZFgOYQx/eZVhvmfF4I7oBQDs6xHhuP8Z
puU2u5c2t8TvzHB4uehd9Ska+ff2x4wAoB2PCMcoMDD07Z/7i3oGJoQCwBFmhlRSS63vlt5AO7UA
gAua73Awou8CAEstWKVSTvJ4p3bWo372/wAA32hm46/6opJhajBp3bKXcu7nbAkrZQuWMK9wmaya
OAcAAAA0pKuMDviIBwB2YadRAAAAAAAAAIDPiy4iXaE8NW3FOWqnYTEtAFd2S/+PQEv77axVNq6X
bW7b2bUMAD7iNvryvmyfAAA4zsRZKsGNQeObkJbK8YUtG57W31I+lTOfHOIJbp9aeSpeQgC4iNo+
HMOhltFoSyUpBeYrLBq7qb9rxVP5cjLD2Uot/W8kASQALu8Z4dhr3mhwdmQfYOhb/Xi2caP84zUK
vqjM0PwMAHjl2eHYd8bo7lb0gXKfI/54eUrcoSUEgIs4+dbmuy+92V4SALigWyPtcVwOWsRvO6KO
lWJMJgWLDQCnNLEl13Bexbp1GaPchqnxDINPVXyw8K9meAhyAEBDjo4HiDcAwDu1OIdDMAAATqaV
Rv2dG2cdcXAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZ/ULzGetS97yp6YAAAAA
SUVORK5CYII=To HTML
- I read this stackoverflow answer
- It contains this snippet
<div>
<p>Taken from wikpedia</p>
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />
</div>- I only need this:
Here it is
- And then I placed that exact HTML within my Markdown.
- With the
base64PNG data within.
- With the
By the way
- You can use that as a URL.
- Click me
- Okay back to work.
Wait Just Kidding
- Manually edit the data.
- Delete exactly 24
Avalues in the big block.- 24 values at 6 bits of information each yields 144 bits or 18 bytes.
- What do you see?
- What if you delete not a multiple of 24?
- What if you delete a different 24?
Quote Me
“They’re my bits, in my computer - let me use them!”
Back to Work
Recall
- Rust is trying to get us to write Java instead of C.
Outsmart Rust
- Ignore the argument via
_ - Include the stuff from inside
_start.
src/main.rs
- Probably change
maintoo so you know what is running, mine says “I’m main”.
Check it
- You can run the tests via:
- Wow that
+nightlyis getting annoying.- Time for an aside.
Aside: Nightly
Back to Stack
- I love stackoverflow.
- Too bad it is slowly being incinerated by generative AI
- Surely this will lead to no long-term problems.
- Check out this answer
It states
you can add a rust-toolchain.toml file, for example
- I am doing this now.
What I see
- I removed my
targetfor clarity.
The other way
- If you wish to use nightly in all projects, you can check this answer.
- There are a variety of intermediate ways, of course.
- Okay back to work.
Back to Work
Recall
- We were trying to run tests.
- You may notice this is just running
_start. - That is because
src/main.rsis a fan of the Black Eyed Peas.- Learn more
- Note to Calvin - do not click that in class.
Surprise!
- Something not going as planned on the first try?
- Our
_startfunction is still used as entry point.- It is, after all, the
_startfunction.
- It is, after all, the
The Problem
- The custom test frameworks feature generates a
mainfunction that callstest_runner- What is it for? Software?
- Might as well use Kotlin!
- Our OS, of course, never calls
main.
Maybe problem
- If you see “duplicate lang item” expand:
Note: There is currently a bug in cargo that leads to “duplicate lang item” errors on cargo test in some cases. It occurs when you have set panic = "abort" for a profile in your Cargo.toml. Try removing it, then cargo test should work. Alternatively, if that doesn’t work, then add panic-abort-tests = true to the [unstable] section of your .cargo/config.toml file. See the cargo issue for more information on this.
Fine, I’ll do it myself
- Simply call
test_runnerfrom_start- It needs a borrowed array of whatevers, so give it one.
Only problem
- What if we
runinstead oftest?
$ cargo r
Compiling osirs v0.1.0 (/home/user/tmp/work)
error[E0423]: expected function, found built-in attribute `test_runner`
--> src/main.rs:26:5
|
26 | test_runner(&[]);
| ^^^^^^^^^^^ not a function
|
note: found an item that was configured out
--> src/main.rs:9:4
|
8 | #[cfg(test)]
| ---- the item is gated here
9 | fn test_runner(_tests: &[&dyn Fn()]) {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0423`.
error: could not compile `osirs` (bin "osirs") due to 1 previous errorConditional Compilation
- We need to conditionally compile
_startto either calltest_runneror not. - No problem, we already know how to do this!
#[cfg(test)]
Only problem
- What if we
runinstead oftest?
Correct Way
- There is a correct way to solve this.
- Separately, there is the opposite: my way.
- I renamed them with a
_prefix.
- The correct way is left as an exercise to the interested student.
Writing Tests
- By convention, we don’t simply print nonsense as a test.
- It so happens in our OS, printing was a whole thing, but this is atypical for non-OS applications.
- A much more common thing to test is an assertion.
- An expression we claim to evaluate to
true
- An expression we claim to evaluate to
A Test
- Let’s just make a sample assertion.
- I will take expression that will evaluate to
true - I will apply the
assert!macro to the expression - I will invoke this macro within a function.
- I will include the function in the
test_runnerarray of functions.
- I will take expression that will evaluate to
Example
- There’s a minor annoyance here.
- We can’t tell if it’s running at all.
Update test_runner
- By the way, I remove
_hiand_byeat this time. - I then just enumerate my tests in an array.
- Loop over the range of the length of the array.
- Say I’m running test \(i\)
- Run test \(i\)
- Say it’s gucci
My test_runner
- For each could never.
Now what?
test_runnerreturns to_startfunction_startcontains an infinite loop- We recall the entry point function is not allowed to return.
- We probably want
cargo tto exit after running all tests.
Winners Never
- We want to quit QEMU.
- Yes, quitting, the thing winners never do.
- This is extremely helpful if both:
- Have code you want to test, and
- Have a finite life expectancy.
OS Shutdown
QEMU to the RESQ
- QEMU includes a debug exit!
isa-debug-exit.- We pass a
-deviceargument toqemu - We pass via the bootimage crate.
- We specify in
Cargo.toml
- We pass a
- This only occurs while testing!
Make Sure This Works
- You can just
cargo t(and alsocargo r) and verify theqemucommand differs. - (Scroll)
$ cargo t
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.06s
Running unittests src/main.rs (target/x86_64-osirs/debug/deps/osirs-577ad3ee7fc7cc13)
Building bootloader
Compiling bootloader v0.9.34 (/home/calvin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bootloader-0.9.34)
Finished `release` profile [optimized + debuginfo] target(s) in 0.83s
Running: `qemu-system-x86_64 -drive format=raw,file=/home/calvin/tmp/work/target/x86_64-osirs/debug/deps/bootimage-osirs-577ad3ee7fc7cc13.bin -no-reboot -device isa-debug-exit,iobase=0xf4,iosize=0x04`$ cargo r
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s
Running `bootimage runner target/x86_64-osirs/debug/osirs`
Building bootloader
Compiling bootloader v0.9.34 (/home/calvin/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/bootloader-0.9.34)
Finished `release` profile [optimized + debuginfo] target(s) in 0.84s
Running: `qemu-system-x86_64 -drive format=raw,file=target/x86_64-osirs/debug/bootimage-osirs.bin`I/O Ports
- Recall memory-mapped I/O.
- There is also port-mapped I/O.
- This uses a separate I/O bus for communication.
- It makes sense QEMU uses this as it is not actual physical hardware!
Imagine
- Each connected peripheral (mouse, monitor, power supply) has one or more port numbers.
- To communicate with such an I/O port, use special hardware level instructions.
inandout
- These take a port number and a data byte
Usage
- The
isa-debug-exitdevice uses port-mapped I/O.isa= instruction set architecture- An example instruction is
inorout
- The
iobaseparameter specifies the port address of the (imaginary) device 0xf4is a generally unused port- The
iosizespecifies the port size (0x04means four bytes).
The Device
isa-debug-exitis simple.- When a value
vis written to the I/O port specified byiobase, QEMU to exits with exit status(v << 1) | 1. - So when we write
0to the port, QEMU will exit with(0 << 1) | 1 = 1, - Exit status is more interesting to C coders, and not used much anymore.
Handwave assembly
- Vs. manually invoking
inandout- That is for the compilers class
- Live on YT Sp27 unless I get fired for not liking AI.
- We use the
x86_64crate. - Add to
[dependencies]inCargo.toml:
Port
- Not just a type of land!
- We will use “Port” to gracefully exit.
- Make one:
Write to it
- I use
0xA, you can use anything.- Could e.g. return number of failed test.
- Zero will collide with a general QEMU error.
- Does need to be a
u32because we said we would.
Test Exit
- Just add those lines to the end of
test_runnerand you should be good to go!
$ cargo t
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.07s
Running unittests src/main.rs (target/x86_64-osirs/debug/deps/osirs-429ccf0d82ba9f9c)
Building bootloader
Finished `release` profile [optimized + debuginfo] target(s) in 0.07s
Running: `qemu-system-x86_64 -drive format=raw,file=/home/calvin/tmp/work/target/x86_64-osirs/debug/deps/bootimage-osirs-429ccf0d82ba9f9c.bin -no-reboot -device isa-debug-exit,iobase=0xf4,iosize=0x04`
error: test failed, to rerun pass `--bin osirs`
Caused by:
process didn't exit successfully: `bootimage runner /home/calvin/tmp/work/target/x86_64-osirs/debug/deps/osirs-429ccf0d82ba9f9c` (exit status: 21)
note: test exited abnormally; to see the full output pass --no-capture to the harness.
$ Exit Status
- We are providing an exit status other than
cargo texpects and being admonished.- The audacity.
- We just specify the expected exit status in
Cargo.toml
Cargo.toml
- Absolute geniuses note
((0xA<<1)|1)==21
Annoyance
- There remains two annoying things here:
- The QEMU window opens and then closes before we can see it, so we can’t see results.
- The QEMU window opens so we are opening a graphics window unnecessarily, which inhibits automation.
Printing to the Console
- To see the test output on the console, we need to send the data from our kernel to the host system somehow.
- There are various ways to achieve this, for example, by sending the data over a TCP network interface.
- Networks, live on YouTube Spring 2028.
Fin
Serial Ports
- I expect we will be out of time by here.
- We continue these efforts in the lab: Serial.