Skip to content

Commit 0c7c104

Browse files
committed
Change on-target testing to use QEMU
1 parent 36fc08e commit 0c7c104

File tree

14 files changed

+203
-83
lines changed

14 files changed

+203
-83
lines changed

.github/workflows/on-target.yml

Lines changed: 0 additions & 26 deletions
This file was deleted.

.github/workflows/qemu.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
on:
2+
push:
3+
branches: [ staging, trying, master ]
4+
pull_request:
5+
6+
name: QEMU
7+
8+
jobs:
9+
qemu:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v2
13+
with:
14+
submodules: true
15+
- uses: actions-rs/toolchain@v1
16+
with:
17+
profile: minimal
18+
toolchain: stable
19+
override: true
20+
target: thumbv7m-none-eabi
21+
- name: Build testsuite
22+
run: cargo build -p testsuite --target thumbv7m-none-eabi --features testsuite/semihosting
23+
- name: Install QEMU
24+
run: sudo apt-get update && sudo apt-get install qemu qemu-system-arm
25+
- name: Run testsuite
26+
run: |
27+
qemu-system-arm \
28+
-cpu cortex-m3 \
29+
-machine lm3s6965evb \
30+
-nographic \
31+
-semihosting-config enable=on,target=native \
32+
-kernel target/thumbv7m-none-eabi/debug/testsuite

testsuite/Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@ publish = false
55
edition = "2018"
66
version = "0.1.0"
77

8+
[features]
9+
rtt = ["rtt-target", "minitest/rtt"]
10+
semihosting = ["cortex-m-semihosting", "minitest/semihosting"]
11+
812
[dependencies]
913
cortex-m-rt.path = "../cortex-m-rt"
1014
cortex-m.path = ".."
1115
minitest.path = "minitest"
12-
rtt-target = "0.3.1"
16+
17+
[dependencies.rtt-target]
18+
version = "0.3.1"
19+
optional = true
20+
21+
[dependencies.cortex-m-semihosting]
22+
path = "../cortex-m-semihosting"
23+
optional = true

testsuite/README.md

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,88 @@
11
# Testsuite
22

3-
This workspace contains on-target tests.
3+
This workspace contains tests that run on physical and simulated Cortex-M CPUs.
44

55
## Building
66

7+
Exactly one of these features are required:
8+
9+
* `semihosting` Use semihosting for logging, this is used for QEMU.
10+
* `rtt` Use RTT for logging, this is used with physical cortex-m CPUs.
11+
712
Assuming you are at the root of the repository you can build like this:
813

914
```console
10-
$ cargo build -p testsuite --target thumbv6m-none-eabi
15+
$ cargo build -p testsuite --target thumbv6m-none-eabi --features testsuite/rtt
1116
Compiling testsuite v0.1.0 (cortex-m/testsuite)
1217
Finished dev [unoptimized + debuginfo] target(s) in 0.08
1318
```
1419

15-
## Running
20+
## Running with QEMU
1621

17-
No implementation-specific features are tested right now; any `thumbv6m-none-eabi` target should work.
22+
* Use the `semihosting` feature for logging, QEMU does not have native support for RTT
23+
* Build for the `thumbv7m-none-eabi` target to match the Cortex-M3 lm3s6965evb machine
1824

19-
Tests are executed with [probe-run](https://github.com/knurling-rs/probe-run):
25+
For more information on QEMU reference the QEMU section in [The Embedded Rust Book].
2026

2127
```console
28+
$ cargo build -p testsuite --target thumbv7m-none-eabi --features testsuite/semihosting
29+
Compiling testsuite v0.1.0 (cortex-m/testsuite)
30+
Finished dev [unoptimized + debuginfo] target(s) in 0.08
31+
$ qemu-system-arm \
32+
-cpu cortex-m3 \
33+
-machine lm3s6965evb \
34+
-nographic \
35+
-semihosting-config enable=on,target=native \
36+
-kernel target/thumbv7m-none-eabi/debug/testsuite
37+
Timer with period zero, disabling
38+
(1/3) running `assert_true`...
39+
(2/3) running `assert_flag`...
40+
(3/3) running `printing`...
41+
Hello, World!
42+
all tests passed!
43+
```
44+
45+
## Running with Physical Hardware
46+
47+
No implementation-specific features are tested right now; any physical `thumbv6m-none-eabi` target should work.
48+
49+
Tests are executed with [probe-run](https://github.com/knurling-rs/probe-run).
50+
51+
This example is provided for the `STM32F070RBTx`, if using a different target:
52+
53+
* Change the `probe-run` chip argument to match your chip, supported chips can be found with `probe-run --list-chips`
54+
* Modify `stm32-memory.x` to match the memory layout of your target
55+
* Change the build target to match your CPU
56+
57+
```console
58+
$ cargo build -p testsuite --target thumbv6m-none-eabi --features testsuite/rtt
59+
Compiling testsuite v0.1.0 (cortex-m/testsuite)
60+
Finished dev [unoptimized + debuginfo] target(s) in 0.08
2261
$ probe-run --chip STM32F070RBTx --connect-under-reset target/thumbv6m-none-eabi/debug/testsuite
23-
(HOST) INFO flashing program (12.80 KiB)
62+
(HOST) INFO flashing program (14.26 KiB)
2463
(HOST) INFO success!
2564
────────────────────────────────────────────────────────────────────────────────
26-
(1/2) running `assert_true`...
27-
(2/2) running `assert_flag`...
65+
(1/3) running `assert_true`...
66+
(2/3) running `assert_flag`...
67+
(3/3) running `printing`...
68+
Hello, World!
2869
all tests passed!
2970
────────────────────────────────────────────────────────────────────────────────
3071
stack backtrace:
3172
0: lib::inline::__bkpt
3273
at ./asm/inline.rs:13:5
3374
1: __bkpt
3475
at ./asm/lib.rs:49:17
35-
2: minitest::export::exit
36-
at testsuite/minitest/src/export.rs:6:9
37-
3: main
38-
at testsuite/src/main.rs:23:1
39-
4: Reset
76+
2: minitest::exit
77+
at testsuite/minitest/src/lib.rs:55:9
78+
3: testsuite::tests::__cortex_m_rt___minitest_entry
79+
at testsuite/src/main.rs:19:1
80+
4: main
81+
at testsuite/src/main.rs:19:1
82+
5: Reset
4083
(HOST) WARN call stack was corrupted; unwinding could not be completed
4184
(HOST) INFO device halted without error
4285
```
4386

44-
## CI
45-
46-
These tests are run automatically in continuous integration. A self-hosted github actions runner with a NUCLEO-F070RB board is hosted by [newAM](https://github.com/newAM).
47-
48-
The runner is equiped with a limited selection of packages:
49-
* [Unix Commands](https://en.wikipedia.org/wiki/List_of_Unix_commands)
50-
* clang
51-
* gcc-arm-embedded
52-
* git
53-
* probe-run
54-
* rustup
55-
56-
The complete host configuration can be found here [here](https://github.com/newAM/nixfiles/blob/main/t430s/configuration.nix).
57-
58-
⚠️ Hosting a github actions runner in a public repository goes directly against [github's advice](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners#self-hosted-runner-security-with-public-repositories), do not do this yourself unless you are prepared to accept the risks.
87+
[The Embedded Rust Book]: https://docs.rust-embedded.org/book/start/qemu.html
88+
[probe-run]: https://github.com/knurling-rs/probe-run

testsuite/build.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use std::{env, fs, path::PathBuf};
2+
3+
fn main() {
4+
#[cfg(any(
5+
not(any(feature = "semihosting", feature = "rtt")),
6+
all(feature = "semihosting", feature = "rtt")
7+
))]
8+
{
9+
eprintln!("Exactly one of these features must be enabled: rtt, semihosting");
10+
std::process::exit(1);
11+
}
12+
13+
println!("cargo:rerun-if-changed=qemu-memory.x");
14+
println!("cargo:rerun-if-changed=stm32-memory.x");
15+
16+
let out_dir: PathBuf =
17+
PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR environment variable not set"));
18+
19+
println!("cargo:rustc-link-search={}", out_dir.display());
20+
21+
#[cfg(feature = "semihosting")]
22+
fs::copy("qemu-memory.x", out_dir.join("memory.x")).unwrap();
23+
24+
#[cfg(feature = "rtt")]
25+
fs::copy("stm32-memory.x", out_dir.join("memory.x")).unwrap();
26+
}

testsuite/minitest/Cargo.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@ publish = false
55
edition = "2018"
66
version = "0.1.0"
77

8+
[features]
9+
semihosting = ["cortex-m-semihosting", "minitest-macros/semihosting"]
10+
rtt = ["rtt-target", "minitest-macros/rtt"]
11+
812
[dependencies]
913
cortex-m.path = "../.."
1014
cortex-m-rt.path = "../../cortex-m-rt"
11-
minitest-macros = { version = "=0.1.0", path = "macros" }
12-
rtt-target = "0.3.1"
15+
minitest-macros.path = "macros"
16+
17+
[dependencies.rtt-target]
18+
version = "0.3.1"
19+
optional = true
20+
21+
[dependencies.cortex-m-semihosting]
22+
path = "../../cortex-m-semihosting"
23+
optional = true

testsuite/minitest/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# rtt-test
1+
# mini-test
22

33
This is an embedded test framework forked from knurling's excellent [`defmt-test`] crate.
44

testsuite/minitest/macros/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ version = "0.1.0"
88
[lib]
99
proc-macro = true
1010

11+
[features]
12+
semihosting = []
13+
rtt = []
14+
1115
[dependencies]
1216
proc-macro2 = "1.0.29"
1317
quote = "1.0.10"

testsuite/minitest/macros/src/lib.rs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,9 @@ fn tests_impl(args: TokenStream, input: TokenStream) -> parse::Result<TokenStrea
201201
)
202202
};
203203

204-
let init_rtt = quote!({
205-
let channels = rtt_target::rtt_init! {
204+
#[cfg(feature = "rtt")]
205+
let init_logging = quote!({
206+
let channels = ::rtt_target::rtt_init! {
206207
up: {
207208
0: {
208209
size: 256
@@ -212,31 +213,31 @@ fn tests_impl(args: TokenStream, input: TokenStream) -> parse::Result<TokenStrea
212213
}
213214
};
214215
unsafe {
215-
rtt_target::set_print_channel_cs(
216+
::rtt_target::set_print_channel_cs(
216217
channels.up.0,
217218
&((|arg, f| cortex_m::interrupt::free(|_| f(arg)))
218219
as rtt_target::CriticalSectionFunc),
219220
);
220221
}
221222
});
222223

224+
#[cfg(not(feature = "rtt"))]
225+
let init_logging = quote!({});
226+
223227
let unit_test_progress = tests
224228
.iter()
225229
.map(|test| {
226-
let message = format!(
227-
"({{}}/{{}}) running `{}`...",
228-
test.func.sig.ident
229-
);
230+
let message = format!("({{}}/{{}}) running `{}`...", test.func.sig.ident);
230231
quote_spanned! {
231-
test.func.sig.ident.span() => rtt_target::rprintln!(#message, __minitest_number, __MINITEST_COUNT);
232+
test.func.sig.ident.span() => #krate::log!(#message, __minitest_number, __MINITEST_COUNT);
232233
}
233234
})
234235
.collect::<Vec<_>>();
235236
Ok(quote!(mod #ident {
236237
#(#untouched_tokens)*
237238
#[cortex_m_rt::entry]
238239
fn __minitest_entry() -> ! {
239-
#init_rtt
240+
#init_logging
240241
#declare_test_count
241242
#init_expr
242243

@@ -250,8 +251,8 @@ fn tests_impl(args: TokenStream, input: TokenStream) -> parse::Result<TokenStrea
250251
}
251252
)*
252253

253-
rtt_target::rprintln!("all tests passed!");
254-
#krate::export::exit()
254+
#krate::log!("all tests passed!");
255+
#krate::exit()
255256
}
256257

257258
#init_fn

testsuite/minitest/src/export.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
use crate::TestOutcome;
22
use cortex_m_rt as _;
33

4-
pub fn exit() -> ! {
5-
loop {
6-
cortex_m::asm::bkpt()
7-
}
8-
}
9-
104
pub fn check_outcome<T: TestOutcome>(outcome: T, should_error: bool) {
115
if outcome.is_success() == should_error {
126
let note: &str = if should_error {

testsuite/minitest/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,37 @@ impl<T: Debug, E: Debug> TestOutcome for Result<T, E> {
3333
self.is_ok()
3434
}
3535
}
36+
37+
#[macro_export]
38+
macro_rules! log {
39+
($s:literal $(, $x:expr)* $(,)?) => {
40+
{
41+
#[cfg(feature = "semihosting")]
42+
::cortex_m_semihosting::hprintln!($s $(, $x)*);
43+
#[cfg(feature = "rtt")]
44+
::rtt_target::rprintln!($s $(, $x)*);
45+
#[cfg(not(any(feature = "semihosting", feature="rtt")))]
46+
let _ = ($( & $x ),*);
47+
}
48+
};
49+
}
50+
51+
/// Stop all tests without failure.
52+
pub fn exit() -> ! {
53+
loop {
54+
#[cfg(feature = "rtt")]
55+
cortex_m::asm::bkpt();
56+
#[cfg(feature = "semihosting")]
57+
cortex_m_semihosting::debug::exit(cortex_m_semihosting::debug::EXIT_SUCCESS);
58+
}
59+
}
60+
61+
/// Stop all tests and report a failure.
62+
pub fn fail() -> ! {
63+
loop {
64+
#[cfg(feature = "rtt")]
65+
cortex_m::asm::udf();
66+
#[cfg(feature = "semihosting")]
67+
cortex_m_semihosting::debug::exit(cortex_m_semihosting::debug::EXIT_FAILURE);
68+
}
69+
}

testsuite/qemu-memory.x

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
MEMORY
2+
{
3+
/* NOTE 1 K = 1 KiBi = 1024 bytes */
4+
/* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */
5+
FLASH : ORIGIN = 0x00000000, LENGTH = 256K
6+
RAM : ORIGIN = 0x20000000, LENGTH = 64K
7+
}

0 commit comments

Comments
 (0)