Add embedded unittesting framework
Change-Id: Ie0b9fb17baba5acde9f491b8b0360005152ddb4a
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/maize/+/256319
Reviewed-by: Travis Geiselbrecht <travisg@google.com>
Commit-Queue: Erik Gilling <konkers@google.com>
Lint: Lint 🤖 <android-build-ayeaye@system.gserviceaccount.com>
diff --git a/.bazelrc b/.bazelrc
index 5a09641..c3dc1ee 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -17,22 +17,28 @@
build --output_groups=+clippy_checks
# Enforce rustfmt formatting
-# TODO - konkers: Fix rustfmt
-# build --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect
-# build --output_groups=+rustfmt_checks
+build --aspects=@rules_rust//rust:defs.bzl%rustfmt_aspect
+build --output_groups=+rustfmt_checks
# TODO - konkers: fix upstream stable toolchains
-build --@@rules_rust+//rust/toolchain/channel=nightly
+common --@@rules_rust+//rust/toolchain/channel=nightly
-build:qemu-microbit --platforms=//target/qemu:microbit
+common --@pigweed//pw_log/rust:pw_log_backend=@pigweed//pw_log/rust:pw_log_backend_println
+
+# Clippy broken with embedded tests
+build:qemu-microbit --output_groups=-clippy_checks
+build:qemu-microbit --platforms=//target/qemu:microbit --output_groups=-clippy_checks
run:qemu-microbit --run_under="@qemu//:qemu-system-arm \
-cpu cortex-m0 \
-machine microbit \
-nographic \
-semihosting-config \
enable=on,target=native \
+-serial mon:stdio \
-kernel "
+# Clippy broken with embedded tests
+build:qemu-lm3s6965evb --output_groups=-clippy_checks
build:qemu-lm3s6965evb --platforms=//target/qemu:lm3s6965evb
run:qemu-lm3s6965evb --run_under="@qemu//:qemu-system-arm \
-cpu cortex-m3 \
@@ -40,6 +46,15 @@
-nographic \
-semihosting-config \
enable=on,target=native \
+-serial mon:stdio \
+-kernel "
+test:qemu-lm3s6965evb --run_under="@qemu//:qemu-system-arm \
+-cpu cortex-m3 \
+-machine lm3s6965evb \
+-nographic \
+-semihosting-config \
+enable=on,target=native \
+-serial mon:stdio \
-kernel "
# Remote execution config definitions
diff --git a/MODULE.bazel b/MODULE.bazel
index 612b179..aa83822 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -59,6 +59,11 @@
dev_dependency = True,
)
+# Disable rules_rust toolchains
+rust = use_extension("//rust:extensions.bzl", "rust")
+rust.toolchain(versions = [])
+use_repo(rust, "rust_toolchains")
+
pw_rust = use_extension("@pigweed//pw_toolchain/rust:extensions.bzl", "pw_rust")
pw_rust.toolchain(cipd_tag = "rust_revision:bf9c7a64ad222b85397573668b39e6d1ab9f4a72")
use_repo(pw_rust, "pw_rust_toolchains")
diff --git a/build/BUILD.bazel b/build/BUILD.bazel
index 8499b5d..443fbb6 100644
--- a/build/BUILD.bazel
+++ b/build/BUILD.bazel
@@ -13,8 +13,3 @@
# the License.
package(default_visibility = ["//visibility:public"])
-
-label_flag(
- name = "linker_script",
- build_setting_default = "@platforms//:incompatible",
-)
diff --git a/build/crates_io/crates_no_std/Cargo.lock b/build/crates_io/crates_no_std/Cargo.lock
index 04c6f50..ba09986 100644
--- a/build/crates_io/crates_no_std/Cargo.lock
+++ b/build/crates_io/crates_no_std/Cargo.lock
@@ -3,6 +3,12 @@
version = 3
[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -65,6 +71,8 @@
"cortex-m",
"cortex-m-rt",
"cortex-m-semihosting",
+ "embedded-io",
+ "intrusive-collections",
"panic-halt",
]
@@ -79,6 +87,30 @@
]
[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "intrusive-collections"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86"
+dependencies = [
+ "memoffset",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/build/crates_io/crates_no_std/Cargo.toml b/build/crates_io/crates_no_std/Cargo.toml
index d149a2f..b901aef 100644
--- a/build/crates_io/crates_no_std/Cargo.toml
+++ b/build/crates_io/crates_no_std/Cargo.toml
@@ -24,6 +24,8 @@
cortex-m = "0.7.7"
cortex-m-rt = "0.7.5"
cortex-m-semihosting = "0.5.0"
+embedded-io = "0.6.1"
+intrusive-collections = { version = "0.9.7", default-features = false }
panic-halt = "1.0.0"
[features]
diff --git a/build/crates_io/crates_std/Cargo.lock b/build/crates_io/crates_std/Cargo.lock
index 43a15a4..36daa3d 100644
--- a/build/crates_io/crates_std/Cargo.lock
+++ b/build/crates_io/crates_std/Cargo.lock
@@ -3,6 +3,12 @@
version = 3
[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -65,6 +71,8 @@
"cortex-m",
"cortex-m-rt",
"cortex-m-semihosting",
+ "embedded-io",
+ "intrusive-collections",
"nom",
"panic-halt",
"proc-macro2",
@@ -83,12 +91,36 @@
]
[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "intrusive-collections"
+version = "0.9.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86"
+dependencies = [
+ "memoffset",
+]
+
+[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/build/crates_io/crates_std/Cargo.toml b/build/crates_io/crates_std/Cargo.toml
index 1c8a336..4bf12ff 100644
--- a/build/crates_io/crates_std/Cargo.toml
+++ b/build/crates_io/crates_std/Cargo.toml
@@ -24,6 +24,8 @@
cortex-m = "0.7.7"
cortex-m-rt = "0.7.5"
cortex-m-semihosting = "0.5.0"
+embedded-io = "0.6.1"
+intrusive-collections = "0.9.7"
nom = "7.1.3"
panic-halt = "1.0.0"
proc-macro2 = "1.0.92"
diff --git a/build/crates_io/rust_crates/aliases.bzl b/build/crates_io/rust_crates/aliases.bzl
index 7151e53..d83e57e 100644
--- a/build/crates_io/rust_crates/aliases.bzl
+++ b/build/crates_io/rust_crates/aliases.bzl
@@ -59,6 +59,34 @@
)
native.alias (
+ name = "embedded-io",
+ target_compatible_with = select({
+ ":no_std": [],
+ ":std": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ }),
+ actual = select({
+ ":no_std": "@crates_no_std//:embedded-io",
+ ":std": "@crates_std//:embedded-io",
+ }),
+ visibility = ["//visibility:public"],
+ )
+
+ native.alias (
+ name = "intrusive-collections",
+ target_compatible_with = select({
+ ":no_std": [],
+ ":std": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ }),
+ actual = select({
+ ":no_std": "@crates_no_std//:intrusive-collections",
+ ":std": "@crates_std//:intrusive-collections",
+ }),
+ visibility = ["//visibility:public"],
+ )
+
+ native.alias (
name = "nom",
target_compatible_with = select({
":std": [],
diff --git a/build/test/rust/BUILD.bazel b/build/test/rust/BUILD.bazel
index ffc8769..843054d 100644
--- a/build/test/rust/BUILD.bazel
+++ b/build/test/rust/BUILD.bazel
@@ -32,13 +32,13 @@
name = "embedded_hello",
srcs = ["embedded_hello.rs"],
edition = "2021",
- linker_script = "//build:linker_script",
target_compatible_with = select({
"@pigweed//pw_build/constraints/chipset:lm3s6965evb": [],
"@pigweed//pw_build/constraints/chipset:nrf52833": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
deps = [
+ "//target:linker_script",
"@rust_crates//:cortex-m",
"@rust_crates//:cortex-m-rt",
"@rust_crates//:cortex-m-semihosting",
diff --git a/lib/unittest/BUILD.bazel b/lib/unittest/BUILD.bazel
new file mode 100644
index 0000000..4183b61
--- /dev/null
+++ b/lib/unittest/BUILD.bazel
@@ -0,0 +1,117 @@
+# Copyright 2024 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+load("@pigweed//pw_build:pw_linker_script.bzl", "pw_linker_script")
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_proc_macro", "rust_test")
+load("@pigweed//pw_build:compatibility.bzl", "incompatible_with_mcu")
+
+package(default_visibility = ["//visibility:public"])
+
+rust_proc_macro(
+ name = "unittest_proc_macro",
+ srcs = [
+ "unittest_proc_macro.rs",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ "@rust_crates//:proc-macro2",
+ "@rust_crates//:quote",
+ "@rust_crates//:syn",
+ ],
+)
+
+rust_library(
+ name = "unittest",
+ srcs = ["unittest.rs"],
+ proc_macro_deps = [
+ ":unittest_proc_macro",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":unittest_core",
+ ":unittest_runner",
+ ],
+)
+
+rust_library(
+ name = "unittest_core",
+ srcs = ["unittest_core.rs"],
+ target_compatible_with = select({
+ # The intrusive collections crate uses atomics which are not available
+ # on the M0.
+ "@pigweed//pw_build/constraints/chipset:nrf52833": ["@platforms//:incompatible"],
+ "//conditions:default": [],
+ }),
+ visibility = ["//visibility:public"],
+ deps = [
+ "@pigweed//pw_bytes/rust:pw_bytes",
+ "@rust_crates//:intrusive-collections",
+ ],
+)
+
+rust_library(
+ name = "unittest_runner_host",
+ srcs = ["unittest_runner_host.rs"],
+ crate_name = "unittest_runner",
+ target_compatible_with = incompatible_with_mcu(),
+ visibility = ["//visibility:public"],
+ deps = [
+ ":unittest_core",
+ "@pigweed//pw_log/rust:pw_log",
+ ],
+)
+
+rust_library(
+ name = "unittest_runner_cortex_m",
+ srcs = ["unittest_runner_cortex_m.rs"],
+ crate_name = "unittest_runner",
+ target_compatible_with = select({
+ "@pigweed//pw_build/constraints/arm:cortex-m0": [],
+ "@pigweed//pw_build/constraints/arm:cortex-m3": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+ }),
+ deps = [
+ ":unittest_core",
+ "@pigweed//pw_log/rust:pw_log",
+ "@rust_crates//:cortex-m-rt",
+ "@rust_crates//:cortex-m-semihosting",
+ "@rust_crates//:panic-halt",
+ ],
+)
+
+rust_library(
+ name = "unittest_test",
+ srcs = ["unittest_test.rs"],
+ visibility = ["//visibility:public"],
+)
+
+rust_test(
+ name = "unittest_test_test",
+ crate = ":unittest_test",
+ use_libtest_harness = False,
+ visibility = ["//visibility:public"],
+ deps = [
+ ":unittest",
+ "//target:linker_script",
+ "@pigweed//pw_log/rust:pw_log",
+ "@rust_crates//:cortex-m-rt",
+ "@rust_crates//:cortex-m-semihosting",
+ "@rust_crates//:panic-halt",
+ ],
+)
+
+label_flag(
+ name = "unittest_runner",
+ build_setting_default = ":unittest_runner_host",
+)
diff --git a/lib/unittest/unittest.rs b/lib/unittest/unittest.rs
new file mode 100644
index 0000000..2aaa9da
--- /dev/null
+++ b/lib/unittest/unittest.rs
@@ -0,0 +1,4 @@
+#![no_std]
+pub use unittest_core::*;
+pub use unittest_proc_macro::*;
+pub use unittest_runner::*;
diff --git a/lib/unittest/unittest_core.rs b/lib/unittest/unittest_core.rs
new file mode 100644
index 0000000..4e3fa03
--- /dev/null
+++ b/lib/unittest/unittest_core.rs
@@ -0,0 +1,101 @@
+#![no_std]
+
+use core::ptr::addr_of_mut;
+use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink};
+
+pub use pw_bytes;
+
+intrusive_adapter!(pub TestDescAndFnAdapter<'a> = &'a TestDescAndFn: TestDescAndFn { link: LinkedListLink });
+
+static mut TEST_LIST: Option<LinkedList<TestDescAndFnAdapter>> = None;
+
+// All accesses to test list go through this function. This gives us a
+// single point of ownership of TEST_LIST and keeps us from leaking references
+// to it.
+fn access_test_list<F>(callback: F)
+where
+ F: FnOnce(&mut LinkedList<TestDescAndFnAdapter>),
+{
+ // Safety: Tests are single threaded for now. This assumption needs to be
+ // revisited.
+ let test_list: &mut Option<LinkedList<TestDescAndFnAdapter>> =
+ unsafe { addr_of_mut!(TEST_LIST).as_mut().unwrap_unchecked() };
+ let list = test_list.get_or_insert_with(|| LinkedList::new(TestDescAndFnAdapter::new()));
+ callback(list)
+}
+
+pub fn add_test(test: &'static mut TestDescAndFn) {
+ access_test_list(|test_list| test_list.push_back(test))
+}
+
+pub fn for_each_test<F>(mut callback: F)
+where
+ F: FnMut(&TestDescAndFn),
+{
+ access_test_list(|test_list| {
+ for test in test_list.iter() {
+ callback(test);
+ }
+ });
+}
+
+pub struct TestError {
+ pub file: &'static str,
+ pub line: u32,
+ pub message: &'static str,
+}
+
+pub type Result<T> = core::result::Result<T, TestError>;
+
+pub enum TestFn {
+ StaticTestFn(fn() -> Result<()>),
+}
+
+pub struct TestDesc {
+ pub name: &'static str,
+}
+
+pub struct TestDescAndFn {
+ pub desc: TestDesc,
+ pub test_fn: TestFn,
+ pub link: LinkedListLink,
+}
+
+impl TestDescAndFn {
+ pub const fn new(desc: TestDesc, test_fn: TestFn) -> Self {
+ Self {
+ desc,
+ test_fn,
+ link: LinkedListLink::new(),
+ }
+ }
+}
+
+// We're marking these as send and sync so that we can declare statics with.
+// them. They're not actually Send and Sync because they contain linked list
+// pointers but in practice tests are single threaded and these are never sent
+// between threads.
+//
+// A better pattern here must be worked out with intrusive lists of static data
+// (for statically declared threads for instance) so we'll revisit this later.
+unsafe impl Send for TestDescAndFn {}
+unsafe impl Sync for TestDescAndFn {}
+
+#[macro_export]
+macro_rules! assert_eq {
+ ($a:expr, $b:expr) => {
+ if $a != $b {
+ return Err(unittest::TestError {
+ file: file!(),
+ line: line!(),
+ message: unittest::pw_bytes::concat_static_strs!(
+ "assert_eq!(",
+ stringify!($a),
+ ", ",
+ stringify!($b),
+ ") failed"
+ ),
+ });
+ }
+ };
+}
diff --git a/lib/unittest/unittest_proc_macro.rs b/lib/unittest/unittest_proc_macro.rs
new file mode 100644
index 0000000..a2ac9ea
--- /dev/null
+++ b/lib/unittest/unittest_proc_macro.rs
@@ -0,0 +1,40 @@
+use proc_macro::TokenStream;
+use quote::{format_ident, quote};
+use syn::{parse_macro_input, ItemFn};
+
+#[proc_macro_attribute]
+pub fn test(_attr: TokenStream, item: TokenStream) -> TokenStream {
+ let item: ItemFn = parse_macro_input!(item as ItemFn);
+ let fn_ident = item.sig.ident.clone();
+ let fn_name = item.sig.ident.to_string();
+ let ctor_fn_ident = format_ident!("__mz_unittest_ctor_fn_{}__", fn_name);
+ let ctor_ident = format_ident!("__mz_unittest_ctor_{}__", fn_name);
+ let desc_ident = format_ident!("__MZ_UNITTEST_DESC_{}__", fn_name.to_uppercase());
+ quote! {
+ static mut #desc_ident: unittest::TestDescAndFn = unittest::TestDescAndFn::new(
+ unittest::TestDesc{
+ name: #fn_name,
+ },
+ unittest::TestFn::StaticTestFn(#fn_ident),
+ );
+
+ extern "C" fn #ctor_fn_ident() -> usize {
+ use core::ptr::addr_of_mut;
+ // Safety: We're only ever mutating this at constructor time which
+ // is single threaded.
+ let desc = unsafe { addr_of_mut!(#desc_ident).as_mut().unwrap_unchecked() };
+ unittest::add_test(desc);
+ 0
+ }
+
+ #[used]
+ #[cfg_attr(target_os = "linux", link_section = ".init_array")]
+ #[cfg_attr(target_os = "none", link_section = ".init_array")]
+ #[cfg_attr(target_vendor = "apple", link_section = "__DATA,__mod_init_func")]
+ #[cfg_attr(windows, link_section = ".CRT$XCU")]
+ static #ctor_ident: extern "C" fn() -> usize = #ctor_fn_ident;
+
+ #item
+ }
+ .into()
+}
diff --git a/lib/unittest/unittest_runner_cortex_m.rs b/lib/unittest/unittest_runner_cortex_m.rs
new file mode 100644
index 0000000..de96c47
--- /dev/null
+++ b/lib/unittest/unittest_runner_cortex_m.rs
@@ -0,0 +1,63 @@
+#![no_std]
+#![feature(type_alias_impl_trait)]
+
+use panic_halt as _;
+
+use cortex_m_rt::entry;
+use cortex_m_semihosting::debug;
+use pw_log::{error, info};
+
+// #[no_mangle]
+// pub extern "C" fn _exit(_status: u32) {
+// debug::exit(debug::EXIT_SUCCESS);
+// }
+
+type CtorFn = unsafe extern "C" fn() -> usize;
+extern "C" {
+ static __init_array_start: CtorFn;
+ static __init_array_end: CtorFn;
+}
+
+fn run_ctors() {
+ unsafe {
+ let start_ptr: *const CtorFn = &__init_array_start;
+ let end_ptr: *const CtorFn = &__init_array_end;
+ let num_ctors = end_ptr.offset_from(start_ptr) as usize;
+ let ctors = core::slice::from_raw_parts(start_ptr, num_ctors);
+ for ctor in ctors {
+ let _ = ctor();
+ }
+ }
+}
+
+#[entry]
+fn main() -> ! {
+ // cortex_m_rt does not run ctors so we need to the that manually.
+ run_ctors();
+
+ let mut success = true;
+
+ unittest_core::for_each_test(|test| {
+ info!("[{}] running", test.desc.name);
+ match test.test_fn {
+ unittest_core::TestFn::StaticTestFn(f) => {
+ if let Err(e) = f() {
+ error!(
+ "[{}] FAILED: {}:{} - {}",
+ test.desc.name, e.file, e.line, e.message
+ );
+ success = false;
+ } else {
+ info!("[{}] PASSED", test.desc.name);
+ }
+ }
+ };
+ });
+ match success {
+ true => debug::exit(debug::EXIT_SUCCESS),
+ false => debug::exit(debug::EXIT_FAILURE),
+ }
+
+ #[allow(clippy::empty_loop)]
+ loop {}
+}
diff --git a/lib/unittest/unittest_runner_host.rs b/lib/unittest/unittest_runner_host.rs
new file mode 100644
index 0000000..5115930
--- /dev/null
+++ b/lib/unittest/unittest_runner_host.rs
@@ -0,0 +1,27 @@
+#![feature(type_alias_impl_trait)]
+use pw_log::{error, info};
+
+#[no_mangle]
+pub extern "C" fn main() {
+ let mut success = true;
+ unittest_core::for_each_test(|test| {
+ info!("[{}] running", test.desc.name);
+ match test.test_fn {
+ unittest_core::TestFn::StaticTestFn(f) => {
+ if let Err(e) = f() {
+ error!(
+ "[{}] FAILED: {}:{} - {}",
+ test.desc.name, e.file, e.line, e.message
+ );
+ success = false;
+ } else {
+ info!("[{}] PASSED", test.desc.name);
+ }
+ }
+ }
+ });
+
+ if !success {
+ std::process::exit(1);
+ }
+}
diff --git a/lib/unittest/unittest_test.rs b/lib/unittest/unittest_test.rs
new file mode 100644
index 0000000..9d83535
--- /dev/null
+++ b/lib/unittest/unittest_test.rs
@@ -0,0 +1,18 @@
+#![no_std]
+#![cfg_attr(test, no_main)]
+
+pub fn add(a: u32, b: u32) -> u32 {
+ a + b
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use unittest::test;
+
+ #[test]
+ fn test_add() -> unittest::Result<()> {
+ unittest::assert_eq!(add(1, 2), 3);
+ Ok(())
+ }
+}
diff --git a/pigweed.json b/pigweed.json
index c25af0d..c563a8b 100644
--- a/pigweed.json
+++ b/pigweed.json
@@ -19,9 +19,19 @@
"//..."
],
[
+ "test",
+ "--config=qemu-lm3s6965evb",
+ "//..."
+ ],
+ [
"build",
"--config=qemu-microbit",
"//..."
+ ],
+ [
+ "test",
+ "--config=qemu-microbit",
+ "//..."
]
]
}
diff --git a/sandbox/logging/BUILD.bazel b/sandbox/logging/BUILD.bazel
index e718e5c..d9dcbea 100644
--- a/sandbox/logging/BUILD.bazel
+++ b/sandbox/logging/BUILD.bazel
@@ -12,19 +12,19 @@
# License for the specific language governing permissions and limitations under
# the License.
-load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test")
+load("@rules_rust//rust:defs.bzl", "rust_binary")
rust_binary(
name = "logging",
srcs = ["main.rs"],
edition = "2021",
- linker_script = "//build:linker_script",
target_compatible_with = select({
"@pigweed//pw_build/constraints/chipset:lm3s6965evb": [],
"@pigweed//pw_build/constraints/chipset:nrf52833": [],
"//conditions:default": ["@platforms//:incompatible"],
}),
deps = [
+ "//target:linker_script",
"@pigweed//pw_log/rust:pw_log",
"@rust_crates//:cortex-m",
"@rust_crates//:cortex-m-rt",
diff --git a/sandbox/logging/main.rs b/sandbox/logging/main.rs
index cf1d5b1..5dc55a3 100644
--- a/sandbox/logging/main.rs
+++ b/sandbox/logging/main.rs
@@ -20,6 +20,7 @@
//! how the tokenized backend is setup.
#![no_main]
#![no_std]
+#![allow(unused_imports)]
// Panic handler that halts the CPU on panic.
use panic_halt as _;
@@ -32,7 +33,13 @@
use pw_log::{critical, infof, warnf};
+#[no_mangle]
+pub extern "C" fn _exit(_status: i32) {
+ debug::exit(debug::EXIT_SUCCESS);
+}
+
#[entry]
+#[cfg(not(test))]
fn main() -> ! {
// Plain text printout without `pw_log`
hprintln!("Hello, Pigweed!");
@@ -55,3 +62,9 @@
#[allow(clippy::empty_loop)]
loop {}
}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn test_test() {}
+}
diff --git a/target/BUILD.bazel b/target/BUILD.bazel
new file mode 100644
index 0000000..a37c205
--- /dev/null
+++ b/target/BUILD.bazel
@@ -0,0 +1,24 @@
+# Copyright 2024 The Pigweed Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+package(default_visibility = ["//visibility:public"])
+
+label_flag(
+ name = "linker_script",
+ build_setting_default = ":no_linker_script",
+)
+
+cc_library(
+ name = "no_linker_script",
+)
diff --git a/target/qemu/BUILD.bazel b/target/qemu/BUILD.bazel
index 0706bb3..f44b533 100644
--- a/target/qemu/BUILD.bazel
+++ b/target/qemu/BUILD.bazel
@@ -26,7 +26,8 @@
"@rust_crates//:no_std",
],
flags = flags_from_dict({
- "//build:linker_script": "//target/qemu/linker_scripts:qemu-lm3s6965",
+ "//target:linker_script": "//target/qemu/linker_scripts:qemu_lm3s6965_linker_script",
+ "//lib/unittest:unittest_runner": "//lib/unittest:unittest_runner_cortex_m",
"@pigweed//pw_log/rust:pw_log_backend": "//target/qemu/pw_log_backend_qemu:pw_log_backend",
}),
)
@@ -41,7 +42,7 @@
"@rust_crates//:no_std",
],
flags = flags_from_dict({
- "//build:linker_script": "//target/qemu/linker_scripts:qemu-nrf51823",
+ "//target:linker_script": "//target/qemu/linker_scripts:qemu_nrf51823_linker_script",
"@pigweed//pw_log/rust:pw_log_backend": "//target/qemu/pw_log_backend_qemu:pw_log_backend",
}),
)
diff --git a/target/qemu/linker_scripts/BUILD.bazel b/target/qemu/linker_scripts/BUILD.bazel
index ac9e2ad..cf8bced 100644
--- a/target/qemu/linker_scripts/BUILD.bazel
+++ b/target/qemu/linker_scripts/BUILD.bazel
@@ -14,12 +14,20 @@
package(default_visibility = ["//visibility:public"])
-filegroup(
- name = "qemu-nrf51823",
- srcs = ["qemu-nrf51823.ld"],
+cc_library(
+ name = "qemu_nrf51823_linker_script",
+ linkopts = ["-T$(location qemu-nrf51823.ld)"],
+ target_compatible_with = ["@pigweed//pw_build/constraints/chipset:nrf52833"],
+ deps = [
+ "qemu-nrf51823.ld",
+ ],
)
-filegroup(
- name = "qemu-lm3s6965",
- srcs = ["qemu-lm3s6965.ld"],
+cc_library(
+ name = "qemu_lm3s6965_linker_script",
+ linkopts = ["-T$(location qemu-lm3s6965.ld)"],
+ target_compatible_with = ["@pigweed//pw_build/constraints/chipset:lm3s6965evb"],
+ deps = [
+ "qemu-lm3s6965.ld",
+ ],
)