@ -54,8 +54,8 @@ testing facilities:
Testing Rust `#![no_std]` code like our kernel is, at the point of writing this tutorial, not an
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.
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
Utilizing the `#[test]` attribute macro and running `cargo test` would throw compilation errors,
compilation errors, because there are dependencies on the standard library.
because there are dependencies on the standard library.
[native testing framework]: https://doc.rust-lang.org/book/ch11-00-testing.html
[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
$ 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
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
### Test Organization
@ -116,7 +116,7 @@ of the kernel code. The `main.rs` file is stripped down to the minimum. It only
`use` statements.
`use` statements.
Since it is not possible to use `kernel` as the name for both the library and the binary part of the
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
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
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:
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,
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
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
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...).
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
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
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
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
[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 Unit Test Runner
The `#![test_runner(crate::test_runner)]` attribute declares the path of the test runner function
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` :
`main()` function. Here is the implementation in `lib.rs` :
```rust
```rust
@ -221,12 +221,12 @@ call chain during kernel boot:
| 4. | `kernel_init()` | `main.rs` |
| 4. | `kernel_init()` | `main.rs` |
| 5. | `kernel_main()` | `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,
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
`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()` . 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
`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
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
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
@mkdir -p target
@echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh
@echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh
@chmod +x 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
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
### Writing Unit Tests
Alright, that's a wrap for the whole chain from `make test` all the way to reporting the test exit
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.
`Unit Tests` yet.
In essence, it is almost like in `std` environments, with the difference that `#[test]` can't be
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
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
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.
our kernel (`libkernel`) through `use` statements.
Also note that the entry point for each `integration test` must be the `kernel_init()` function
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
#### 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
`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` :
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
```console
$ make test
$ 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
Finished release [optimized] target(s) in 0.01s
Running target/aarch64-unknown-none-softfloat/release/deps/libkernel-4cc6412ddf631982
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
diff -uNr 12_exceptions_part1_groundwork/Cargo.toml 13_integrated_testing/Cargo.toml
--- 12_exceptions_part1_groundwork/Cargo.toml
--- 12_exceptions_part1_groundwork/Cargo.toml
+++ 13_integrated_testing/Cargo.toml
+++ 13_integrated_testing/Cargo.toml
@@ -14,7 +14 ,35 @@
@@ -11,7 +11 ,35 @@
bsp_rpi4 = ["cortex-a", "register"]
bsp_rpi4 = ["cortex-a", "register"]
[dependencies]
[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
OPENOCD_ARG = -f /openocd/tcl/interface/ftdi/olimex-arm-usb-tiny-h.cfg -f /openocd/rpi3.cfg
JTAG_BOOT_IMAGE = jtag_boot_rpi3.img
JTAG_BOOT_IMAGE = jtag_boot_rpi3.img
LINKER_FILE = src/bsp/raspberrypi/link.ld
LINKER_FILE = src/bsp/raspberrypi/link.ld
@@ -29,21 +30,3 4 @@
@@ -29,12 +30,2 4 @@
# QEMU_BINARY = qemu-system-aarch64
# QEMU_BINARY = qemu-system-aarch64
# QEMU_MACHINE_TYPE =
# QEMU_MACHINE_TYPE =
# QEMU_RELEASE_ARGS = -serial stdio -display none
# 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."
+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)
SOURCES = $(wildcard ** /*.rs) $(wildcard ** /*.S) $(wildcard ** /*.ld)
-XRUSTC_CMD = cargo xrustc \
RUSTFLAGS = -C link-arg=-T$(LINKER_FILE) $(RUSTC_MISC_ARGS)
- --target=$(TARGET) \
@@ -46,6 +59,7 @@
- --features bsp_$(BSP) \
+X_CMD_ARGS = --target=$(TARGET) \
RUSTC_CMD = cargo rustc $(COMPILER_ARGS)
+ --features bsp_$(BSP) \
CLIPPY_CMD = cargo clippy $(COMPILER_ARGS)
--release
+TEST_CMD = cargo test $(COMPILER_ARGS)
+XRUSTC_CMD = cargo xrustc $(X_CMD_ARGS)
+XTEST_CMD = cargo xtest $(X_CMD_ARGS)
CARGO_OUTPUT = target/$(TARGET)/release/kernel
CARGO_OUTPUT = target/$(TARGET)/release/kernel
@@ -53,7 +67 ,8 @@
@@ -55,7 +69 ,8 @@
-O binary
-O binary
DOCKER_IMAGE = rustembedded/osdev-utils
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_TUT = -v $(shell pwd):/work -w /work
DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/utils
DOCKER_ARG_DIR_UTILS = -v $(shell pwd)/../utils:/utils
DOCKER_ARG_DIR_JTAG = -v $(shell pwd)/../X1_JTAG_boot:/jtag
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_QEMU = $(QEMU_BINARY) -M $(QEMU_MACHINE_TYPE)
DOCKER_EXEC_MINIPUSH = ruby /utils/minipush.rb
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)
all: clean $(OUTPUT)
@@ -78,32 +93 ,51 @@
@@ -80,32 +95 ,51 @@
ifeq ($(QEMU_MACHINE_TYPE),)
ifeq ($(QEMU_MACHINE_TYPE),)
qemu:
qemu:
@ -916,7 +911,7 @@ diff -uNr 12_exceptions_part1_groundwork/Makefile 13_integrated_testing/Makefile
+ @mkdir -p target
+ @mkdir -p target
+ @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh
+ @echo "$$KERNEL_TEST_RUNNER" > target/kernel_test_runner.sh
+ @chmod +x 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
endif
chainboot: all
chainboot: all
@ -939,7 +934,7 @@ diff -uNr 12_exceptions_part1_groundwork/Makefile 13_integrated_testing/Makefile
openocd $(OPENOCD_ARG)
openocd $(OPENOCD_ARG)
define gen_gdb
define gen_gdb
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC) $1" $(X RUSTC_CMD)
RUSTFLAGS="$(RUSTFLAGS_PEDANTIC) $1" $(RUSTC_CMD)
cp $(CARGO_OUTPUT) kernel_for_jtag
cp $(CARGO_OUTPUT) kernel_for_jtag
- @$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) \
- @$(DOCKER_CMD) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) \
+ @$(DOCKER_CMD_USER) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) \
+ @$(DOCKER_CMD_USER) $(DOCKER_ARG_DIR_TUT) $(DOCKER_ARG_NET) $(DOCKER_IMAGE) \