[PATCH 2/2] samples: rust: add `SgTable` and `ScatterList` selftests

From: Qingsong Chen
Date: Tue May 30 2023 - 02:49:07 EST


Add a selftest module to provide a temporary place to put "pure tests"
for Rust funtionality and wrappers.

Add test cases for `SgTable` and `ScatterList`.

Co-developed-by: Boqun Feng <boqun.feng@xxxxxxxxx>
Signed-off-by: Boqun Feng <boqun.feng@xxxxxxxxx>
Signed-off-by: Qingsong Chen <changxian.cqs@xxxxxxxxxxxx>
---
rust/kernel/error.rs | 2 +-
samples/rust/Kconfig | 10 ++
samples/rust/Makefile | 1 +
samples/rust/rust_selftests.rs | 186 +++++++++++++++++++++++++++++++++
4 files changed, 198 insertions(+), 1 deletion(-)
create mode 100644 samples/rust/rust_selftests.rs

diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index 5f4114b30b94..40f2db9d57a6 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -68,7 +68,7 @@ pub mod code {
/// # Invariants
///
/// The value is a valid `errno` (i.e. `>= -MAX_ERRNO && < 0`).
-#[derive(Clone, Copy, PartialEq, Eq)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Error(core::ffi::c_int);

impl Error {
diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
index b0f74a81c8f9..a1f29e5aaf83 100644
--- a/samples/rust/Kconfig
+++ b/samples/rust/Kconfig
@@ -30,6 +30,16 @@ config SAMPLE_RUST_PRINT

If unsure, say N.

+config SAMPLE_RUST_SELFTESTS
+ tristate "Self tests"
+ help
+ This option builds the self test cases for Rust.
+
+ To compile this as a module, choose M here:
+ the module will be called rust_selftests.
+
+ If unsure, say N.
+
config SAMPLE_RUST_HOSTPROGS
bool "Host programs"
help
diff --git a/samples/rust/Makefile b/samples/rust/Makefile
index 03086dabbea4..4519a567db7c 100644
--- a/samples/rust/Makefile
+++ b/samples/rust/Makefile
@@ -2,5 +2,6 @@

obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o
obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o
+obj-$(CONFIG_SAMPLE_RUST_SELFTESTS) += rust_selftests.o

subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs
diff --git a/samples/rust/rust_selftests.rs b/samples/rust/rust_selftests.rs
new file mode 100644
index 000000000000..843d53159ac8
--- /dev/null
+++ b/samples/rust/rust_selftests.rs
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Self test cases for Rust.
+
+use kernel::prelude::*;
+// Keep the `use` for a test in its test function. Module-level `use`s are only for the test
+// framework.
+
+module! {
+ type: RustSelftests,
+ name: "rust_selftests",
+ author: "Rust for Linux Contributors",
+ description: "Self test cases for Rust",
+ license: "GPL",
+}
+
+struct RustSelftests;
+
+/// A summary of testing.
+///
+/// A test can
+///
+/// * pass (successfully), or
+/// * fail (without hitting any error), or
+/// * hit an error (interrupted).
+///
+/// This is the type that differentiates the first two (pass and fail) cases.
+///
+/// When a test hits an error, the test function should skip and return the error. Note that this
+/// doesn't mean the test fails, for example if the system doesn't have enough memory for
+/// testing, the test function may return an `Err(ENOMEM)` and skip.
+#[allow(dead_code)]
+enum TestSummary {
+ Pass,
+ Fail,
+}
+
+use TestSummary::Fail;
+use TestSummary::Pass;
+
+macro_rules! do_tests {
+ ($($name:ident),*) => {
+ let mut total = 0;
+ let mut pass = 0;
+ let mut fail = 0;
+
+ $({
+ total += 1;
+
+ match $name() {
+ Ok(Pass) => {
+ pass += 1;
+ pr_info!("{} passed!", stringify!($name));
+ },
+ Ok(Fail) => {
+ fail += 1;
+ pr_info!("{} failed!", stringify!($name));
+ },
+ Err(err) => {
+ pr_info!("{} hit error {:?}", stringify!($name), err);
+ }
+ }
+ })*
+
+ pr_info!("{} tests run, {} passed, {} failed, {} hit errors\n",
+ total, pass, fail, total - pass - fail);
+
+ if total == pass {
+ pr_info!("All tests passed. Congratulations!\n");
+ }
+ }
+}
+
+/// An example of test.
+#[allow(dead_code)]
+fn test_example() -> Result<TestSummary> {
+ // `use` declarations for the test can be put here, e.g. `use foo::bar;`.
+
+ // Always pass.
+ Ok(Pass)
+}
+
+/// A selftest case for `SgTable`.
+fn test_sgtable() -> Result<TestSummary> {
+ use core::pin::pin;
+ use kernel::scatterlist::*;
+
+ // Prepare memory buffers.
+ let buf0: Pin<&mut [u8]> = pin!([0u8; 512]);
+ let buf1: Pin<&mut [u8]> = pin!([1u8; 512]);
+ let mut entries = Vec::<Pin<&mut [u8]>>::new();
+ entries.try_push(buf0)?;
+ entries.try_push(buf1)?;
+
+ // Construct two `SgTable` instances. And the first is chainable, which
+ // requires one entry reserved.
+ let mut sgt0: Pin<Box<SgTable<'_, 3>>> =
+ Box::pin_init(SgTable::<'_, 3>::new(entries.as_slice(), true))?;
+ let mut sgt1: Pin<Box<SgTable<'_, 2>>> =
+ Box::pin_init(SgTable::<'_, 2>::new(entries.as_slice(), false))?;
+
+ // Assert the total count of entries in the table.
+ assert_eq!(sgt0.count(), 2);
+ assert_eq!(sgt1.count(), 2);
+
+ // Chain two `SgTable` together.
+ sgt0.chain_sgt(sgt1.as_mut())?;
+ assert_eq!(sgt0.count(), 4);
+ assert_eq!(sgt1.count(), 2);
+
+ // Get the immutable reference to the entry in the table.
+ let sgl1: Pin<&ScatterList<'_>> = sgt0.get(1).ok_or(EINVAL)?;
+ assert_eq!(sgl1.count(), 3);
+
+ // Get the mutable reference to the entry in the table.
+ let sgl3: Pin<&mut ScatterList<'_>> = sgt0.get_mut(3).ok_or(EINVAL)?;
+ assert_eq!(sgl3.count(), 1);
+
+ // Try to get a non-exist entry from the table.
+ let sgl4 = sgt0.get(4);
+ assert_eq!(sgl4.is_none(), true);
+
+ // Split the first table from chained scatterlist.
+ sgt0.split()?;
+ assert_eq!(sgt0.count(), 2);
+ assert_eq!(sgt1.count(), 2);
+
+ // Chain `SgTable` with single `ScatterList`.
+ let mut sgl: Pin<Box<ScatterList<'_>>> = Box::pin_init(ScatterList::new(&entries[0]))?;
+ sgt0.chain_sgl(sgl.as_mut())?;
+ assert_eq!(sgt0.count(), 3);
+
+ Ok(Pass)
+}
+
+/// A selftest case for `ScatterList`.
+fn test_scatterlist() -> Result<TestSummary> {
+ use core::pin::pin;
+ use kernel::scatterlist::*;
+
+ // Prepare memory buffers.
+ let buf0: Pin<&mut [u8]> = pin!([0u8; 512]);
+ let buf1: Pin<&mut [u8]> = pin!([1u8; 512]);
+
+ // Construct a `ScatterList` instance. And assert its attributes.
+ let mut sgl: Pin<Box<ScatterList<'_>>> = Box::pin_init(ScatterList::new(&buf0))?;
+ assert_eq!(sgl.length(), 512);
+ assert_eq!(sgl.is_dma_bus_address(), false);
+ assert_eq!(sgl.dma_address(), 0);
+ assert_eq!(sgl.dma_length(), 0);
+ assert_eq!(sgl.is_chain(), false);
+ assert_eq!(sgl.is_last(), true);
+ assert_eq!(sgl.count(), 1);
+
+ // Get an immutable reference to the memory buffer.
+ assert_eq!(sgl.get_buf(), [0u8; 512]);
+
+ // Reset the memory buffer.
+ sgl.set_buf(&buf1);
+ assert_eq!(sgl.get_buf(), [1u8; 512]);
+
+ // Get a mutable reference to memory buffer.
+ sgl.get_mut_buf().fill(2);
+ assert_eq!(sgl.get_buf(), [2u8; 512]);
+
+ Ok(Pass)
+}
+
+impl kernel::Module for RustSelftests {
+ fn init(_module: &'static ThisModule) -> Result<Self> {
+ pr_info!("Rust self tests (init)\n");
+
+ do_tests! {
+ test_sgtable,
+ test_scatterlist
+ };
+
+ Ok(RustSelftests)
+ }
+}
+
+impl Drop for RustSelftests {
+ fn drop(&mut self) {
+ pr_info!("Rust self tests (exit)\n");
+ }
+}
--
2.40.1