diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2023-08-28 18:56:38 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2023-08-28 18:56:38 -0700 |
commit | 815c24a085dd8ab9bb7381e455afdb3f9c260e38 (patch) | |
tree | 46cf481382c497cbbb47de63ca0a75183fc25875 /rust | |
parent | 5a31cc7297072a7266a910ca5266b640d27803b4 (diff) | |
parent | 25e324bc9cf2ee956eec1db384c39c1a17b7c44a (diff) |
Merge tag 'linux-kselftest-kunit-6.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest
Pull kunit updates from Shuah Khan:
- add support for running Rust documentation tests as KUnit tests
- make init, str, sync, types doctests compilable/testable
- add support for attributes API which include speed, modules
attributes, ability to filter and report attributes
- add support for marking tests slow using attributes API
- add attributes API documentation
- fix a wild-memory-access bug in kunit_filter_suites() and a possible
memory leak in kunit_filter_suites()
- add support for counting number of test suites in a module, list
action to kunit test modules, and test filtering on module tests
* tag 'linux-kselftest-kunit-6.6-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest: (25 commits)
kunit: fix struct kunit_attr header
kunit: replace KUNIT_TRIGGER_STATIC_STUB maro with KUNIT_STATIC_STUB_REDIRECT
kunit: Allow kunit test modules to use test filtering
kunit: Make 'list' action available to kunit test modules
kunit: Report the count of test suites in a module
kunit: fix uninitialized variables bug in attributes filtering
kunit: fix possible memory leak in kunit_filter_suites()
kunit: fix wild-memory-access bug in kunit_filter_suites()
kunit: Add documentation of KUnit test attributes
kunit: add tests for filtering attributes
kunit: time: Mark test as slow using test attributes
kunit: memcpy: Mark tests as slow using test attributes
kunit: tool: Add command line interface to filter and report attributes
kunit: Add ability to filter attributes
kunit: Add module attribute
kunit: Add speed attribute
kunit: Add test attributes API structure
MAINTAINERS: add Rust KUnit files to the KUnit entry
rust: support running Rust documentation tests as KUnit ones
rust: types: make doctests compilable/testable
...
Diffstat (limited to 'rust')
-rw-r--r-- | rust/.gitignore | 2 | ||||
-rw-r--r-- | rust/Makefile | 29 | ||||
-rw-r--r-- | rust/bindings/bindings_helper.h | 1 | ||||
-rw-r--r-- | rust/helpers.c | 7 | ||||
-rw-r--r-- | rust/kernel/init.rs | 26 | ||||
-rw-r--r-- | rust/kernel/kunit.rs | 163 | ||||
-rw-r--r-- | rust/kernel/lib.rs | 2 | ||||
-rw-r--r-- | rust/kernel/str.rs | 4 | ||||
-rw-r--r-- | rust/kernel/sync/arc.rs | 9 | ||||
-rw-r--r-- | rust/kernel/sync/lock/mutex.rs | 1 | ||||
-rw-r--r-- | rust/kernel/sync/lock/spinlock.rs | 1 | ||||
-rw-r--r-- | rust/kernel/types.rs | 6 |
12 files changed, 236 insertions, 15 deletions
diff --git a/rust/.gitignore b/rust/.gitignore index 21552992b401..d3829ffab80b 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -2,6 +2,8 @@ bindings_generated.rs bindings_helpers_generated.rs +doctests_kernel_generated.rs +doctests_kernel_generated_kunit.c uapi_generated.rs exports_*_generated.h doc/ diff --git a/rust/Makefile b/rust/Makefile index 4124bfa01798..30b71af119a2 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -27,6 +27,12 @@ endif obj-$(CONFIG_RUST) += exports.o +always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.rs +always-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated_kunit.c + +obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated.o +obj-$(CONFIG_RUST_KERNEL_DOCTESTS) += doctests_kernel_generated_kunit.o + # Avoids running `$(RUSTC)` for the sysroot when it may not be available. ifdef CONFIG_RUST @@ -39,9 +45,11 @@ ifeq ($(quiet),silent_) cargo_quiet=-q rust_test_quiet=-q rustdoc_test_quiet=--test-args -q +rustdoc_test_kernel_quiet=>/dev/null else ifeq ($(quiet),quiet_) rust_test_quiet=-q rustdoc_test_quiet=--test-args -q +rustdoc_test_kernel_quiet=>/dev/null else cargo_quiet=--verbose endif @@ -157,6 +165,27 @@ quiet_cmd_rustdoc_test = RUSTDOC T $< -L$(objtree)/$(obj)/test --output $(objtree)/$(obj)/doc \ --crate-name $(subst rusttest-,,$@) $< +quiet_cmd_rustdoc_test_kernel = RUSTDOC TK $< + cmd_rustdoc_test_kernel = \ + rm -rf $(objtree)/$(obj)/test/doctests/kernel; \ + mkdir -p $(objtree)/$(obj)/test/doctests/kernel; \ + OBJTREE=$(abspath $(objtree)) \ + $(RUSTDOC) --test $(rust_flags) \ + @$(objtree)/include/generated/rustc_cfg \ + -L$(objtree)/$(obj) --extern alloc --extern kernel \ + --extern build_error --extern macros \ + --extern bindings --extern uapi \ + --no-run --crate-name kernel -Zunstable-options \ + --test-builder $(objtree)/scripts/rustdoc_test_builder \ + $< $(rustdoc_test_kernel_quiet); \ + $(objtree)/scripts/rustdoc_test_gen + +%/doctests_kernel_generated.rs %/doctests_kernel_generated_kunit.c: \ + $(src)/kernel/lib.rs $(obj)/kernel.o \ + $(objtree)/scripts/rustdoc_test_builder \ + $(objtree)/scripts/rustdoc_test_gen FORCE + $(call if_changed,rustdoc_test_kernel) + # We cannot use `-Zpanic-abort-tests` because some tests are dynamic, # so for the moment we skip `-Cpanic=abort`. quiet_cmd_rustc_test = RUSTC T $< diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 058954961bfc..c91a3c24f607 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -6,6 +6,7 @@ * Sorted alphabetically. */ +#include <kunit/test.h> #include <linux/errname.h> #include <linux/slab.h> #include <linux/refcount.h> diff --git a/rust/helpers.c b/rust/helpers.c index bb594da56137..49a5e1a4f0ae 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -18,6 +18,7 @@ * accidentally exposed. */ +#include <kunit/test-bug.h> #include <linux/bug.h> #include <linux/build_bug.h> #include <linux/err.h> @@ -135,6 +136,12 @@ void rust_helper_put_task_struct(struct task_struct *t) } EXPORT_SYMBOL_GPL(rust_helper_put_task_struct); +struct kunit *rust_helper_kunit_get_current_test(void) +{ + return kunit_get_current_test(); +} +EXPORT_SYMBOL_GPL(rust_helper_kunit_get_current_test); + /* * We use `bindgen`'s `--size_t-is-usize` option to bind the C `size_t` type * as the Rust `usize` type, so we can use it in contexts where Rust diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs index b4332a4ec1f4..e05563aad2ed 100644 --- a/rust/kernel/init.rs +++ b/rust/kernel/init.rs @@ -120,14 +120,24 @@ //! `slot` gets called. //! //! ```rust -//! use kernel::{prelude::*, init}; +//! # #![allow(unreachable_pub, clippy::disallowed_names)] +//! use kernel::{prelude::*, init, types::Opaque}; //! use core::{ptr::addr_of_mut, marker::PhantomPinned, pin::Pin}; //! # mod bindings { +//! # #![allow(non_camel_case_types)] //! # pub struct foo; //! # pub unsafe fn init_foo(_ptr: *mut foo) {} //! # pub unsafe fn destroy_foo(_ptr: *mut foo) {} //! # pub unsafe fn enable_foo(_ptr: *mut foo, _flags: u32) -> i32 { 0 } //! # } +//! # // `Error::from_errno` is `pub(crate)` in the `kernel` crate, thus provide a workaround. +//! # trait FromErrno { +//! # fn from_errno(errno: core::ffi::c_int) -> Error { +//! # // Dummy error that can be constructed outside the `kernel` crate. +//! # Error::from(core::fmt::Error) +//! # } +//! # } +//! # impl FromErrno for Error {} //! /// # Invariants //! /// //! /// `foo` is always initialized @@ -158,7 +168,7 @@ //! if err != 0 { //! // Enabling has failed, first clean up the foo and then return the error. //! bindings::destroy_foo(Opaque::raw_get(foo)); -//! return Err(Error::from_kernel_errno(err)); +//! return Err(Error::from_errno(err)); //! } //! //! // All fields of `RawFoo` have been initialized, since `_p` is a ZST. @@ -226,8 +236,7 @@ pub mod macros; /// /// ```rust /// # #![allow(clippy::disallowed_names, clippy::new_ret_no_self)] -/// # use kernel::{init, pin_init, stack_pin_init, init::*, sync::Mutex, new_mutex}; -/// # use macros::pin_data; +/// # use kernel::{init, macros::pin_data, pin_init, stack_pin_init, init::*, sync::Mutex, new_mutex}; /// # use core::pin::Pin; /// #[pin_data] /// struct Foo { @@ -277,7 +286,7 @@ macro_rules! stack_pin_init { /// /// # Examples /// -/// ```rust +/// ```rust,ignore /// # #![allow(clippy::disallowed_names, clippy::new_ret_no_self)] /// # use kernel::{init, pin_init, stack_try_pin_init, init::*, sync::Mutex, new_mutex}; /// # use macros::pin_data; @@ -303,7 +312,7 @@ macro_rules! stack_pin_init { /// pr_info!("a: {}", &*foo.a.lock()); /// ``` /// -/// ```rust +/// ```rust,ignore /// # #![allow(clippy::disallowed_names, clippy::new_ret_no_self)] /// # use kernel::{init, pin_init, stack_try_pin_init, init::*, sync::Mutex, new_mutex}; /// # use macros::pin_data; @@ -513,8 +522,7 @@ macro_rules! stack_try_pin_init { /// For instance: /// /// ```rust -/// # use kernel::pin_init; -/// # use macros::pin_data; +/// # use kernel::{macros::pin_data, pin_init}; /// # use core::{ptr::addr_of_mut, marker::PhantomPinned}; /// #[pin_data] /// struct Buf { @@ -841,7 +849,7 @@ macro_rules! init { /// # Examples /// /// ```rust -/// use kernel::{init::PinInit, error::Error, InPlaceInit}; +/// use kernel::{init::{PinInit, zeroed}, error::Error}; /// struct BigBuf { /// big: Box<[u8; 1024 * 1024 * 1024]>, /// small: [u8; 1024 * 1024], diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs new file mode 100644 index 000000000000..722655b2d62d --- /dev/null +++ b/rust/kernel/kunit.rs @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! KUnit-based macros for Rust unit tests. +//! +//! C header: [`include/kunit/test.h`](../../../../../include/kunit/test.h) +//! +//! Reference: <https://docs.kernel.org/dev-tools/kunit/index.html> + +use core::{ffi::c_void, fmt}; + +/// Prints a KUnit error-level message. +/// +/// Public but hidden since it should only be used from KUnit generated code. +#[doc(hidden)] +pub fn err(args: fmt::Arguments<'_>) { + // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we + // are passing. + #[cfg(CONFIG_PRINTK)] + unsafe { + bindings::_printk( + b"\x013%pA\0".as_ptr() as _, + &args as *const _ as *const c_void, + ); + } +} + +/// Prints a KUnit info-level message. +/// +/// Public but hidden since it should only be used from KUnit generated code. +#[doc(hidden)] +pub fn info(args: fmt::Arguments<'_>) { + // SAFETY: The format string is null-terminated and the `%pA` specifier matches the argument we + // are passing. + #[cfg(CONFIG_PRINTK)] + unsafe { + bindings::_printk( + b"\x016%pA\0".as_ptr() as _, + &args as *const _ as *const c_void, + ); + } +} + +/// Asserts that a boolean expression is `true` at runtime. +/// +/// Public but hidden since it should only be used from generated tests. +/// +/// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit +/// facilities. See [`assert!`] for more details. +#[doc(hidden)] +#[macro_export] +macro_rules! kunit_assert { + ($name:literal, $file:literal, $diff:expr, $condition:expr $(,)?) => { + 'out: { + // Do nothing if the condition is `true`. + if $condition { + break 'out; + } + + static FILE: &'static $crate::str::CStr = $crate::c_str!($file); + static LINE: i32 = core::line!() as i32 - $diff; + static CONDITION: &'static $crate::str::CStr = $crate::c_str!(stringify!($condition)); + + // SAFETY: FFI call without safety requirements. + let kunit_test = unsafe { $crate::bindings::kunit_get_current_test() }; + if kunit_test.is_null() { + // The assertion failed but this task is not running a KUnit test, so we cannot call + // KUnit, but at least print an error to the kernel log. This may happen if this + // macro is called from an spawned thread in a test (see + // `scripts/rustdoc_test_gen.rs`) or if some non-test code calls this macro by + // mistake (it is hidden to prevent that). + // + // This mimics KUnit's failed assertion format. + $crate::kunit::err(format_args!( + " # {}: ASSERTION FAILED at {FILE}:{LINE}\n", + $name + )); + $crate::kunit::err(format_args!( + " Expected {CONDITION} to be true, but is false\n" + )); + $crate::kunit::err(format_args!( + " Failure not reported to KUnit since this is a non-KUnit task\n" + )); + break 'out; + } + + #[repr(transparent)] + struct Location($crate::bindings::kunit_loc); + + #[repr(transparent)] + struct UnaryAssert($crate::bindings::kunit_unary_assert); + + // SAFETY: There is only a static instance and in that one the pointer field points to + // an immutable C string. + unsafe impl Sync for Location {} + + // SAFETY: There is only a static instance and in that one the pointer field points to + // an immutable C string. + unsafe impl Sync for UnaryAssert {} + + static LOCATION: Location = Location($crate::bindings::kunit_loc { + file: FILE.as_char_ptr(), + line: LINE, + }); + static ASSERTION: UnaryAssert = UnaryAssert($crate::bindings::kunit_unary_assert { + assert: $crate::bindings::kunit_assert {}, + condition: CONDITION.as_char_ptr(), + expected_true: true, + }); + + // SAFETY: + // - FFI call. + // - The `kunit_test` pointer is valid because we got it from + // `kunit_get_current_test()` and it was not null. This means we are in a KUnit + // test, and that the pointer can be passed to KUnit functions and assertions. + // - The string pointers (`file` and `condition` above) point to null-terminated + // strings since they are `CStr`s. + // - The function pointer (`format`) points to the proper function. + // - The pointers passed will remain valid since they point to `static`s. + // - The format string is allowed to be null. + // - There are, however, problems with this: first of all, this will end up stopping + // the thread, without running destructors. While that is problematic in itself, + // it is considered UB to have what is effectively a forced foreign unwind + // with `extern "C"` ABI. One could observe the stack that is now gone from + // another thread. We should avoid pinning stack variables to prevent library UB, + // too. For the moment, given that test failures are reported immediately before the + // next test runs, that test failures should be fixed and that KUnit is explicitly + // documented as not suitable for production environments, we feel it is reasonable. + unsafe { + $crate::bindings::__kunit_do_failed_assertion( + kunit_test, + core::ptr::addr_of!(LOCATION.0), + $crate::bindings::kunit_assert_type_KUNIT_ASSERTION, + core::ptr::addr_of!(ASSERTION.0.assert), + Some($crate::bindings::kunit_unary_assert_format), + core::ptr::null(), + ); + } + + // SAFETY: FFI call; the `test` pointer is valid because this hidden macro should only + // be called by the generated documentation tests which forward the test pointer given + // by KUnit. + unsafe { + $crate::bindings::__kunit_abort(kunit_test); + } + } + }; +} + +/// Asserts that two expressions are equal to each other (using [`PartialEq`]). +/// +/// Public but hidden since it should only be used from generated tests. +/// +/// Unlike the one in `core`, this one does not panic; instead, it is mapped to the KUnit +/// facilities. See [`assert!`] for more details. +#[doc(hidden)] +#[macro_export] +macro_rules! kunit_assert_eq { + ($name:literal, $file:literal, $diff:expr, $left:expr, $right:expr $(,)?) => {{ + // For the moment, we just forward to the expression assert because, for binary asserts, + // KUnit supports only a few types (e.g. integers). + $crate::kunit_assert!($name, $file, $diff, $left == $right); + }}; +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 85b261209977..3642cadc34b1 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -34,6 +34,8 @@ mod build_assert; pub mod error; pub mod init; pub mod ioctl; +#[cfg(CONFIG_KUNIT)] +pub mod kunit; pub mod prelude; pub mod print; mod static_assert; diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs index c9dd3bf59e34..c41607b2e4fe 100644 --- a/rust/kernel/str.rs +++ b/rust/kernel/str.rs @@ -213,6 +213,7 @@ impl fmt::Display for CStr { /// /// ``` /// # use kernel::c_str; + /// # use kernel::fmt; /// # use kernel::str::CStr; /// # use kernel::str::CString; /// let penguin = c_str!("🐧"); @@ -241,6 +242,7 @@ impl fmt::Debug for CStr { /// /// ``` /// # use kernel::c_str; + /// # use kernel::fmt; /// # use kernel::str::CStr; /// # use kernel::str::CString; /// let penguin = c_str!("🐧"); @@ -529,7 +531,7 @@ impl fmt::Write for Formatter { /// # Examples /// /// ``` -/// use kernel::str::CString; +/// use kernel::{str::CString, fmt}; /// /// let s = CString::try_from_fmt(fmt!("{}{}{}", "abc", 10, 20)).unwrap(); /// assert_eq!(s.as_bytes_with_nul(), "abc1020\0".as_bytes()); diff --git a/rust/kernel/sync/arc.rs b/rust/kernel/sync/arc.rs index 172f563976a9..3d496391a9bd 100644 --- a/rust/kernel/sync/arc.rs +++ b/rust/kernel/sync/arc.rs @@ -73,6 +73,7 @@ mod std_vendor; /// assert_eq!(cloned.b, 20); /// /// // The refcount drops to zero when `cloned` goes out of scope, and the memory is freed. +/// # Ok::<(), Error>(()) /// ``` /// /// Using `Arc<T>` as the type of `self`: @@ -98,6 +99,7 @@ mod std_vendor; /// let obj = Arc::try_new(Example { a: 10, b: 20 })?; /// obj.use_reference(); /// obj.take_over(); +/// # Ok::<(), Error>(()) /// ``` /// /// Coercion from `Arc<Example>` to `Arc<dyn MyTrait>`: @@ -121,6 +123,7 @@ mod std_vendor; /// /// // `coerced` has type `Arc<dyn MyTrait>`. /// let coerced: Arc<dyn MyTrait> = obj; +/// # Ok::<(), Error>(()) /// ``` pub struct Arc<T: ?Sized> { ptr: NonNull<ArcInner<T>>, @@ -336,7 +339,7 @@ impl<T: ?Sized> From<Pin<UniqueArc<T>>> for Arc<T> { /// # Example /// /// ``` -/// use crate::sync::{Arc, ArcBorrow}; +/// use kernel::sync::{Arc, ArcBorrow}; /// /// struct Example; /// @@ -349,12 +352,13 @@ impl<T: ?Sized> From<Pin<UniqueArc<T>>> for Arc<T> { /// /// // Assert that both `obj` and `cloned` point to the same underlying object. /// assert!(core::ptr::eq(&*obj, &*cloned)); +/// # Ok::<(), Error>(()) /// ``` /// /// Using `ArcBorrow<T>` as the type of `self`: /// /// ``` -/// use crate::sync::{Arc, ArcBorrow}; +/// use kernel::sync::{Arc, ArcBorrow}; /// /// struct Example { /// a: u32, @@ -369,6 +373,7 @@ impl<T: ?Sized> From<Pin<UniqueArc<T>>> for Arc<T> { /// /// let obj = Arc::try_new(Example { a: 10, b: 20 })?; /// obj.as_arc_borrow().use_reference(); +/// # Ok::<(), Error>(()) /// ``` pub struct ArcBorrow<'a, T: ?Sized + 'a> { inner: NonNull<ArcInner<T>>, diff --git a/rust/kernel/sync/lock/mutex.rs b/rust/kernel/sync/lock/mutex.rs index 923472f04af4..09276fedc091 100644 --- a/rust/kernel/sync/lock/mutex.rs +++ b/rust/kernel/sync/lock/mutex.rs @@ -63,6 +63,7 @@ macro_rules! new_mutex { /// assert_eq!(e.c, 10); /// assert_eq!(e.d.lock().a, 20); /// assert_eq!(e.d.lock().b, 30); +/// # Ok::<(), Error>(()) /// ``` /// /// The following example shows how to use interior mutability to modify the contents of a struct diff --git a/rust/kernel/sync/lock/spinlock.rs b/rust/kernel/sync/lock/spinlock.rs index 979b56464a4e..91eb2c9e9123 100644 --- a/rust/kernel/sync/lock/spinlock.rs +++ b/rust/kernel/sync/lock/spinlock.rs @@ -61,6 +61,7 @@ macro_rules! new_spinlock { /// assert_eq!(e.c, 10); /// assert_eq!(e.d.lock().a, 20); /// assert_eq!(e.d.lock().b, 30); +/// # Ok::<(), Error>(()) /// ``` /// /// The following example shows how to use interior mutability to modify the contents of a struct diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index d479f8da8f38..e33b901ab0a4 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -91,7 +91,7 @@ impl ForeignOwnable for () { /// In the example below, we have multiple exit paths and we want to log regardless of which one is /// taken: /// ``` -/// # use kernel::ScopeGuard; +/// # use kernel::types::ScopeGuard; /// fn example1(arg: bool) { /// let _log = ScopeGuard::new(|| pr_info!("example1 completed\n")); /// @@ -109,7 +109,7 @@ impl ForeignOwnable for () { /// In the example below, we want to log the same message on all early exits but a different one on /// the main exit path: /// ``` -/// # use kernel::ScopeGuard; +/// # use kernel::types::ScopeGuard; /// fn example2(arg: bool) { /// let log = ScopeGuard::new(|| pr_info!("example2 returned early\n")); /// @@ -130,7 +130,7 @@ impl ForeignOwnable for () { /// In the example below, we need a mutable object (the vector) to be accessible within the log /// function, so we wrap it in the [`ScopeGuard`]: /// ``` -/// # use kernel::ScopeGuard; +/// # use kernel::types::ScopeGuard; /// fn example3(arg: bool) -> Result { /// let mut vec = /// ScopeGuard::new_with_data(Vec::new(), |v| pr_info!("vec had {} elements\n", v.len())); |