Update testing posts

This commit is contained in:
Philipp Oppermann
2018-11-18 15:16:19 +01:00
parent bf413d3baa
commit f5aea8f015
4 changed files with 24 additions and 49 deletions

View File

@@ -111,13 +111,13 @@ test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
The test framework seems to work as intended. We don't have any tests yet, but we already get a test result summary. The test framework seems to work as intended. We don't have any tests yet, but we already get a test result summary.
### Silencing the Warnings ### Silencing the Warnings
We get a few warnings about unused items, because we no longer compile our `_start` function. To silence such unused code warnings, we can add the following to the top of our `main.rs`: We get a few warnings about unused imports, because we no longer compile our `_start` function. To silence such unused code warnings, we can add the following to the top of our `main.rs`:
``` ```
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] #![cfg_attr(test, allow(unused_imports))]
``` ```
Like before, the `cfg_attr` attribute sets the passed attribute if the passed condition holds. Here, we set the `allow(…)` attribute when compiling in test mode. We use the `allow` attribute to disable warnings for the `dead_code`, `unused_macro`, and `unused_import` _lints_. Like before, the `cfg_attr` attribute sets the passed attribute if the passed condition holds. Here, we set the `allow(…)` attribute when compiling in test mode. We use the `allow` attribute to disable warnings for the `unused_import` _lint_.
Lints are classes of warnings, for example `dead_code` for unused code or `missing-docs` for missing documentation. Lints can be set to four different states: Lints are classes of warnings, for example `dead_code` for unused code or `missing-docs` for missing documentation. Lints can be set to four different states:
@@ -131,15 +131,13 @@ Some lints are `allow` by default (such as `missing-docs`), others are `warn` by
[clippy]: https://github.com/rust-lang-nursery/rust-clippy [clippy]: https://github.com/rust-lang-nursery/rust-clippy
### Including the Standard Library ### Including the Standard Library
Unit tests run on the host machine, so it's possible to use the complete standard library inside them. To link the standard library in test mode, we can add the following to our `main.rs`: Unit tests run on the host machine, so it's possible to use the complete standard library inside them. To link the standard library in test mode, we can make the `#![no_std]` attribute conditional through `cfg_attr` too:
```rust ```diff
#[cfg(test)] -#![no_std]
extern crate std; +#![cfg_attr(not(test), no_std)]
``` ```
Rust knows where to find the `std` crate, so no modification to the `Cargo.toml` is required.
## Testing the VGA Module ## Testing the VGA Module
Now that we have set up the test framework, we can add a first unit test for our `vga_buffer` module: Now that we have set up the test framework, we can add a first unit test for our `vga_buffer` module:

View File

@@ -103,12 +103,13 @@ Like with the [VGA text buffer][vga lazy-static], we use `lazy_static` and a spi
To make the serial port easily usable, we add `serial_print!` and `serial_println!` macros: To make the serial port easily usable, we add `serial_print!` and `serial_println!` macros:
```rust ```rust
pub fn print(args: ::core::fmt::Arguments) { pub fn _print(args: ::core::fmt::Arguments) {
use core::fmt::Write; use core::fmt::Write;
SERIAL1.lock().write_fmt(args).expect("Printing to serial failed"); SERIAL1.lock().write_fmt(args).expect("Printing to serial failed");
} }
/// Prints to the host through the serial interface. /// Prints to the host through the serial interface.
#[macro_export]
macro_rules! serial_print { macro_rules! serial_print {
($($arg:tt)*) => { ($($arg:tt)*) => {
$crate::serial::print(format_args!($($arg)*)); $crate::serial::print(format_args!($($arg)*));
@@ -116,10 +117,11 @@ macro_rules! serial_print {
} }
/// Prints to the host through the serial interface, appending a newline. /// Prints to the host through the serial interface, appending a newline.
#[macro_export]
macro_rules! serial_println { macro_rules! serial_println {
() => (serial_print!("\n")); () => ($crate::serial_print!("\n"));
($fmt:expr) => (serial_print!(concat!($fmt, "\n"))); ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n")));
($fmt:expr, $($arg:tt)*) => (serial_print!(concat!($fmt, "\n"), $($arg)*)); ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!(concat!($fmt, "\n"), $($arg)*));
} }
``` ```
@@ -132,7 +134,8 @@ Now we can print to the serial interface in our `main.rs`:
```rust ```rust
// in src/main.rs // in src/main.rs
#[macro_use] use crate::serial_println;
mod serial; mod serial;
#[cfg(not(test))] #[cfg(not(test))]
@@ -145,7 +148,7 @@ pub extern "C" fn _start() -> ! {
} }
``` ```
Note that we need to add the `#[macro_use]` attribute to the `mod serial` declaration, because otherwise the `serial_println` macro is not imported. Note that the `serial_println` macro lives directly under the root namespace because we used the `#[macro_export]` attribute, so importing it through `use crate::serial::serial_println` will not work.
### QEMU Arguments ### QEMU Arguments
@@ -371,7 +374,7 @@ Cargo supports hybrid projects that are both a library and a binary. We only nee
```rust ```rust
// src/lib.rs // src/lib.rs
#![no_std] // don't link the Rust standard library #![cfg_attr(not(test), no_std)] // don't link the Rust standard library
extern crate bootloader; extern crate bootloader;
extern crate spin; extern crate spin;
@@ -382,8 +385,6 @@ extern crate x86_64;
#[cfg(test)] #[cfg(test)]
extern crate array_init; extern crate array_init;
#[cfg(test)]
extern crate std;
// NEW: We need to add `pub` here to make them accessible from the outside // NEW: We need to add `pub` here to make them accessible from the outside
pub mod vga_buffer; pub mod vga_buffer;
@@ -405,10 +406,10 @@ pub unsafe fn exit_qemu() {
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))]
// NEW: Add the library as dependency (same crate name as executable) // NEW: Add the library as dependency (same crate name as executable)
#[macro_use]
extern crate blog_os; extern crate blog_os;
use core::panic::PanicInfo; use core::panic::PanicInfo;
use blog_os::println;
/// This function is the entry point, since the linker looks for a function /// This function is the entry point, since the linker looks for a function
/// named `_start` by default. /// named `_start` by default.
@@ -429,29 +430,7 @@ fn panic(info: &PanicInfo) -> ! {
} }
``` ```
So we move everything except `_start` and `panic` to `lib.rs`, make the `vga_buffer` and `serial` modules public, and add an `extern crate` definition to our `main.rs`. So we move everything except `_start` and `panic` to `lib.rs`, make the `vga_buffer` and `serial` modules public, and add an `extern crate` definition to our `main.rs`. Everything should work exactly as before, including `bootimage run` and `cargo test`.
This doesn't compile yet, because Rust's macros are not exported over crate boundaries by default. To export our printing macros, we need to add the `#[macro_export]` attribute to them:
```rust
// in src/vga_buffer.rs
#[macro_export]
macro_rules! print {}
#[macro_export]
macro_rules! println {}
// in src/serial.rs
#[macro_export]
macro_rules! serial_print {}
#[macro_export]
macro_rules! serial_println {}
```
Now everything should work exactly as before, including `bootimage run` and `cargo test`.
### Test Basic Boot ### Test Basic Boot
@@ -465,11 +444,10 @@ We are finally able to create our first integration test executable. We start si
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))]
// add the library as dependency (same crate name as executable) // add the library as dependency (same crate name as executable)
#[macro_use]
extern crate blog_os; extern crate blog_os;
use core::panic::PanicInfo; use core::panic::PanicInfo;
use blog_os::exit_qemu; use blog_os::{exit_qemu, serial_println};
/// This function is the entry point, since the linker looks for a function /// This function is the entry point, since the linker looks for a function
/// named `_start` by default. /// named `_start` by default.
@@ -529,11 +507,10 @@ To test that our panic handler is really invoked on a panic, we create a `test-p
#![cfg_attr(not(test), no_main)] #![cfg_attr(not(test), no_main)]
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] #![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))]
#[macro_use]
extern crate blog_os; extern crate blog_os;
use core::panic::PanicInfo; use core::panic::PanicInfo;
use blog_os::exit_qemu; use blog_os::{exit_qemu, serial_println};
#[cfg(not(test))] #[cfg(not(test))]
#[no_mangle] #[no_mangle]

View File

@@ -1,6 +1,6 @@
#![no_std] // don't link the Rust standard library #![no_std] // don't link the Rust standard library
#![cfg_attr(not(test), no_main)] // disable all Rust-level entry points #![cfg_attr(not(test), no_main)] // disable all Rust-level entry points
#![cfg_attr(test, allow(dead_code, unused_macros, unused_imports))] #![cfg_attr(test, allow(unused_imports))]
use core::panic::PanicInfo; use core::panic::PanicInfo;

View File

@@ -10,7 +10,7 @@ lazy_static! {
}; };
} }
pub fn print(args: ::core::fmt::Arguments) { pub fn _print(args: ::core::fmt::Arguments) {
use core::fmt::Write; use core::fmt::Write;
use x86_64::instructions::interrupts; use x86_64::instructions::interrupts;
@@ -26,7 +26,7 @@ pub fn print(args: ::core::fmt::Arguments) {
#[macro_export] #[macro_export]
macro_rules! serial_print { macro_rules! serial_print {
($($arg:tt)*) => { ($($arg:tt)*) => {
$crate::serial::print(format_args!($($arg)*)); $crate::serial::_print(format_args!($($arg)*));
}; };
} }