@ -54,8 +54,8 @@ testing facilities:
Testing Rust `#![no_std]` code like our kernel is, at the point of writing this tutorial, not an
easy endeavor. The short version is: We cannot use Rust's [native testing framework] straight away.
Utilizing the `#[test]` attribute macro and running `cargo test` (`xtest` in our case) would throw
compilation errors, because there are dependencies on the standard library.
Utilizing the `#[test]` attribute macro and running `cargo test` would throw compilation errors,
because there are dependencies on the standard library.
[native testing framework]: https://doc.rust-lang.org/book/ch11-00-testing.html
@ -88,9 +88,9 @@ We introduce a new `Makefile` target:
$ make test
```
In essence, `make test` will execute `cargo x test` instead of `cargo x rustc`. The details will be
In essence, `make test` will execute `cargo test` instead of `cargo rustc`. The details will be
explained in due course. The rest of the tutorial will explain as chronologically as possible what
happens when `make test` aka `cargo x test` runs.
happens when `make test` aka `cargo test` runs.
### Test Organization
@ -116,7 +116,7 @@ of the kernel code. The `main.rs` file is stripped down to the minimum. It only
`use` statements.
Since it is not possible to use `kernel` as the name for both the library and the binary part of the
crate, new entries in `Cargo.toml` are needed to differentiate the names. What's more, `cargo x test`
crate, new entries in `Cargo.toml` are needed to differentiate the names. What's more, `cargo test`
would try to compile and run `unit tests` for both. In our case, it will be sufficient to have all
the unit test code in `lib.rs` , so test generation for `main.rs` can be disabled in `Cargo.toml` as
well through the `test` flag:
@ -145,22 +145,22 @@ In `lib.rs`, we add the following headers to get started with `custom_test_frame
Since this is a library now, we do not keep the `#![no_main]` inner attribute that `main.rs` has,
because a library has no `main()` entry function, so the attribute does not apply. When compiling
for testing, though, it is still needed. The reason is that `cargo x test` basically turns `lib.rs`
for testing, though, it is still needed. The reason is that `cargo test` basically turns `lib.rs`
into a binary again by inserting a generated `main()` function (which is then calling a function
that runs all the unit tests, but more about that in a second...).
However, since our kernel code [overrides the compiler-inserted `main` shim] by way of using
`#![no_main]` , we need the same when `cargo x test` is producing its test kernel binary. After all,
`#![no_main]` , we need the same when `cargo test` is producing its test kernel binary. After all,
what we want is a minimal kernel that boots on the target and runs its own unit tests. Therefore, we
conditionally set this attribute (`#![cfg_attr(test, no_main)]`) when the `test` flag is set, which
it is when `cargo x test` runs.
it is when `cargo test` runs.
[overrides the compiler-inserted `main` shim]: https://doc.rust-lang.org/unstable-book/language-features/lang-items.html?highlight=no_main#writing-an-executable-without-stdlib
#### The Unit Test Runner
The `#![test_runner(crate::test_runner)]` attribute declares the path of the test runner function
that we are supposed to provide. This is the one that will be called by the `cargo x test` generated
that we are supposed to provide. This is the one that will be called by the `cargo test` generated
`main()` function. Here is the implementation in `lib.rs` :
```rust
@ -221,12 +221,12 @@ call chain during kernel boot:
| 4. | `kernel_init()` | `main.rs` |
| 5. | `kernel_main()` | `main.rs` |
A function named `main` is never called. Hence, the `main()` function generated by `cargo x test`
A function named `main` is never called. Hence, the `main()` function generated by `cargo test`
would be silently dropped, and therefore the tests would never be executed. As you can see,
`runtime_init()` is the last function residing in our carved-out `lib.rs` , and it calls into
`kernel_init()` . So in order to get the tests to execute, we add a test-environment version of
`kernel_init()` to `lib.rs` as well (conditional compilation ensures it is only present when the
test flag is set), and call the `cargo x test` generated `main()` function from there.
test flag is set), and call the `cargo test` generated `main()` function from there.
This is where `#![reexport_test_harness_main = "test_main"]` finally comes into picture. It declares
the name of the generated main function so that we can manually call it. Here is the final
@ -359,7 +359,7 @@ test: $(SOURCES)
@mkdir -p target
@echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh
@chmod +x target/kernel_test_runner.sh
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(X TEST_CMD) $(TEST_ARG)
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG)
```
It first does the standard `objcopy` step to strip the `ELF` down to a raw binary. Just like in all
@ -404,7 +404,7 @@ def exec
### Writing Unit Tests
Alright, that's a wrap for the whole chain from `make test` all the way to reporting the test exit
status back to `cargo x test`. It is a lot to digest already, but we haven't even learned to write
status back to `cargo test`. It is a lot to digest already, but we haven't even learned to write
`Unit Tests` yet.
In essence, it is almost like in `std` environments, with the difference that `#[test]` can't be
@ -490,7 +490,7 @@ We are still not done with the tutorial, though :scream:.
Integration tests need some special attention here and there too. As you already learned, they live
in `$CRATE/tests/` . Each `.rs` file in there gets compiled into its own test kernel binary and
executed separately by `cargo x test`. The code in the integration tests includes the library part of
executed separately by `cargo test`. The code in the integration tests includes the library part of
our kernel (`libkernel`) through `use` statements.
Also note that the entry point for each `integration test` must be the `kernel_init()` function
@ -498,7 +498,7 @@ again, just like in the `unit test` case.
#### Test Harness
By default, `cargo x test` will pull in the test harness (that's the official name for the generated
By default, `cargo test` will pull in the test harness (that's the official name for the generated
`main()` function) into integration tests as well. This gives you a further means of partitioning
your test code into individual chunks. For example, take a look at `tests/01_timer_sanity.rs` :
@ -701,7 +701,7 @@ Believe it or not, that is all. There are three ways you can run tests:
```console
$ make test
[...]
RUSTFLAGS="-C link-arg=-Tsrc/bsp/raspberrypi/link.ld -C target-cpu=cortex-a53 -D warnings -D missing_docs" cargo x test --target=aarch64-unknown-none-softfloat --features bsp_rpi3 --release
RUSTFLAGS="-C link-arg=-Tsrc/bsp/raspberrypi/link.ld -C target-cpu=cortex-a53 -D warnings -D missing_docs" cargo test --target=aarch64-unknown-none-softfloat --features bsp_rpi3 --release
Finished release [optimized] target(s) in 0.01s
Running target/aarch64-unknown-none-softfloat/release/deps/libkernel-4cc6412ddf631982
-------------------------------------------------------------------
@ -779,7 +779,7 @@ diff -uNr 12_exceptions_part1_groundwork/.cargo/config 13_integrated_testing/.ca
diff -uNr 12_exceptions_part1_groundwork/Cargo.toml 13_integrated_testing/Cargo.toml
--- 12_exceptions_part1_groundwork/Cargo.toml
+++ 13_integrated_testing/Cargo.toml
@@ -14,7 +14 ,35 @@
@@ -11,7 +11 ,35 @@
bsp_rpi4 = ["cortex-a", "register"]
[dependencies]
@ -828,7 +828,7 @@ diff -uNr 12_exceptions_part1_groundwork/Makefile 13_integrated_testing/Makefile
OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi3.cfg
JTAG_BOOT_IMAGE = jtag_boot_rpi3.img
LINKER_FILE = src/bsp/raspberrypi/link.ld
@@ -29,21 +30,3 4 @@
@@ -29,12 +30,2 4 @@
# QEMU_BINARY = qemu-system-aarch64
# QEMU_MACHINE_TYPE =
# QEMU_RELEASE_ARGS = -serial stdio -display none
@ -850,23 +850,18 @@ diff -uNr 12_exceptions_part1_groundwork/Makefile 13_integrated_testing/Makefile
+
+QEMU_MISSING_STRING = "This board is not yet supported for QEMU."
+
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
RUSTFLAGS_PEDANTIC = $(RUSTFLAGS) -D warnings -D missing_docs
SOURCES = $(wildcard ** /*.rs) $(wildcard ** /*.S) $(wildcard ** /*.ld)
-XRUSTC_CMD = cargo xrustc \
- --target=$(TARGET) \
- --features bsp_$(BSP) \
+X_CMD_ARGS = --target=$(TARGET) \
+ --features bsp_$(BSP) \
--release
+XRUSTC_CMD = cargo xrustc $(X_CMD_ARGS)
+XTEST_CMD = cargo xtest $(X_CMD_ARGS)
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
@@ -46,6 +59,7 @@
RUSTC_CMD = cargo rustc $(COMPILER_ARGS)
CLIPPY_CMD = cargo clippy $(COMPILER_ARGS)
+TEST_CMD = cargo test $(COMPILER_ARGS)
CARGO_OUTPUT = target/$(TARGET)/release/kernel
@@ -53,7 +67 ,8 @@
@@ -55,7 +69 ,8 @@
-O binary
DOCKER_IMAGE = rustembedded/osdev-utils
@ -876,7 +871,7 @@ diff -uNr 12_exceptions_part1_groundwork/Makefile 13_integrated_testing/Makefile
DOCKER_ARG_DIR_TUT = -v $(shell pwd):/work -w /work
DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/utils
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/jtag
@@ -62,7 +77 ,7 @@
@@ -64,7 +79 ,7 @@
DOCKER_EXEC_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
DOCKER_EXEC_MINIPUSH = ruby /utils/minipush.rb
@ -885,7 +880,7 @@ diff -uNr 12_exceptions_part1_groundwork/Makefile 13_integrated_testing/Makefile
all: clean $(OUTPUT)
@@ -78,32 +93 ,51 @@
@@ -80,32 +95 ,51 @@
ifeq ($(QEMU_MACHINE_TYPE),)
qemu:
@ -916,7 +911,7 @@ diff -uNr 12_exceptions_part1_groundwork/Makefile 13_integrated_testing/Makefile
+ @mkdir -p target
+ @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh
+ @chmod +x target/kernel_test_runner.sh
+ RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(X TEST_CMD) $(TEST_ARG)
+ RUSTFLAGS="$(RUSTFLAGS_PEDANTIC)" $(TEST_CMD) $(TEST_ARG)
endif
chainboot: all
@ -939,7 +934,7 @@ diff -uNr 12_exceptions_part1_groundwork/Makefile 13_integrated_testing/Makefile
openocd $(OPENOCD_ARG)
define gen_gdb
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC) $1" $(X RUSTC_CMD)
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC) $1" $(RUSTC_CMD)
cp $(CARGO_OUTPUT) kernel_for_jtag
- @$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) \
+ @$(DOCKER_CMD_USER) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) \