diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 47e4f4ecdbf9ea..879a88ef72013c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -394,6 +394,9 @@ jobs: # Docs - run: make ${{ env.MAKE_ARCH }} ${{ env.MAKE_CROSS_COMPILE }} ${{ env.MAKE_TOOLCHAIN }} ${{ env.MAKE_OUTPUT }} ${{ env.MAKE_SYSROOT }} -j3 rustdoc + # Tests + - run: make ${{ env.MAKE_ARCH }} ${{ env.MAKE_CROSS_COMPILE }} ${{ env.MAKE_TOOLCHAIN }} ${{ env.MAKE_OUTPUT }} ${{ env.MAKE_SYSROOT }} -j3 rusttest + # Formatting - run: make rustfmtcheck diff --git a/Makefile b/Makefile index efbd76ba40ce90..99f11d8923dfa4 100644 --- a/Makefile +++ b/Makefile @@ -1739,6 +1739,8 @@ help: @echo ' is formatted, printing a diff otherwise.' @echo ' rustdoc - Generate Rust documentation' @echo ' (requires kernel .config)' + @echo ' rusttest - Runs the Rust tests' + @echo ' (requires kernel .config)' @echo ' rust-analyzer - Generate rust-project.json rust-analyzer support file' @echo ' (requires kernel .config)' @echo '' @@ -1824,6 +1826,11 @@ PHONY += rustdoc rustdoc: prepare0 $(Q)$(MAKE) $(build)=rust $@ +# Testing target +PHONY += rusttest +rusttest: prepare0 + $(Q)$(MAKE) $(build)=rust $@ + # Formatting targets PHONY += rustfmt rustfmtcheck diff --git a/rust/.gitignore b/rust/.gitignore index 8875e08ed0b17e..c6186b71e1c3fb 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -2,4 +2,5 @@ bindings_generated.rs exports_*_generated.h -doc/ \ No newline at end of file +doc/ +test/ diff --git a/rust/Makefile b/rust/Makefile index 3d8c54b808d744..0afdf6785497b4 100644 --- a/rust/Makefile +++ b/rust/Makefile @@ -19,39 +19,81 @@ obj-$(CONFIG_RUST) += exports.o RUSTDOC = rustdoc -quiet_cmd_rustdoc_host = RUSTDOC $< - cmd_rustdoc_host = \ - RUST_BINDINGS_FILE=$(abspath $(objtree)/rust/bindings_generated.rs) \ - $(RUSTDOC) $(filter-out --emit=%, $(rustc_flags)) \ - $(rustdoc_target_flags) -L $(objtree)/rust/ \ - --output $(objtree)/rust/doc --crate-name $(subst rustdoc-,,$@) \ - -Fmissing-docs @$(objtree)/include/generated/rustc_cfg $< - -quiet_cmd_rustdoc = RUSTDOC $< +quiet_cmd_rustdoc = RUSTDOC $(if $(filter --test,$(rustdoc_target_flags)),T, ) $(if $(rustdoc_host),H, ) $< cmd_rustdoc = \ RUST_BINDINGS_FILE=$(abspath $(objtree)/rust/bindings_generated.rs) \ - $(RUSTDOC) $(rustc_cross_flags) $(filter-out --emit=%, $(rustc_flags)) \ - $(rustdoc_target_flags) -L $(objtree)/rust/ \ - --output $(objtree)/rust/doc --crate-name $(subst rustdoc-,,$@) \ + $(RUSTDOC) $(if $(rustdoc_host),,$(rustc_cross_flags)) \ + $(filter-out --emit=%, $(rustc_flags)) $(rustc_target_flags) $(rustdoc_target_flags) \ + -L $(objtree)/rust/$(if $(filter --test,$(rustdoc_target_flags)),test/) \ + --output $(objtree)/rust/doc --crate-name $(subst rusttest-,,$(subst rustdoc-,,$@)) \ -Fmissing-docs @$(objtree)/include/generated/rustc_cfg $< rustdoc: rustdoc-macros rustdoc-compiler_builtins rustdoc-kernel -rustdoc-macros: private rustdoc_target_flags = --crate-type proc-macro \ +rustdoc-macros: private rustdoc_host = yes +rustdoc-macros: private rustc_target_flags = --crate-type proc-macro \ --extern proc_macro rustdoc-macros: $(srctree)/rust/macros/lib.rs FORCE - $(call if_changed,rustdoc_host) + $(call if_changed,rustdoc) rustdoc-compiler_builtins: $(srctree)/rust/compiler_builtins.rs FORCE $(call if_changed,rustdoc) -rustdoc-kernel: private rustdoc_target_flags = --extern alloc \ +rustdoc-kernel: private rustc_target_flags = --extern alloc \ --extern build_error \ --extern macros=$(objtree)/rust/libmacros.so rustdoc-kernel: $(srctree)/rust/kernel/lib.rs rustdoc-macros \ $(objtree)/rust/libmacros.so $(objtree)/rust/bindings_generated.rs FORCE $(call if_changed,rustdoc) +quiet_cmd_rustc_test_library = RUSTC TL $< + cmd_rustc_test_library = \ + RUST_BINDINGS_FILE=$(abspath $(objtree)/rust/bindings_generated.rs) \ + $(RUSTC) $(filter-out -Cpanic=abort, $(filter-out --emit=%, $(rustc_flags))) \ + $(rustc_target_flags) --crate-type $(if $(rustc_test_library_proc),proc-macro,rlib) \ + --out-dir $(objtree)/rust/test/ --cfg testlib \ + -L $(objtree)/rust/test/ --crate-name $(subst rusttest-,,$(subst rusttestlib-,,$@)) $< + +rusttestlib-build_error: $(srctree)/rust/build_error.rs FORCE + $(call if_changed,rustc_test_library) + +rusttestlib-macros: private rustc_target_flags = --extern proc_macro +rusttestlib-macros: private rustc_test_library_proc = yes +rusttestlib-macros: $(srctree)/rust/macros/lib.rs FORCE + $(call if_changed,rustc_test_library) + +# 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 $< + cmd_rustc_test = \ + RUST_BINDINGS_FILE=$(abspath $(objtree)/rust/bindings_generated.rs) \ + $(RUSTC) --test $(filter-out -Cpanic=abort, $(filter-out --emit=%, $(rustc_flags))) \ + $(rustc_target_flags) --out-dir $(objtree)/rust/test \ + -L $(objtree)/rust/test/ --crate-name $(subst rusttest-,,$@) $<; \ + $(objtree)/rust/test/$(subst rusttest-,,$@) $(rustc_test_run_flags) + +rusttest: rusttest-macros rusttest-kernel + +rusttest-macros: private rustc_target_flags = --extern proc_macro +rusttest-macros: private rustdoc_host = yes +rusttest-macros: private rustdoc_target_flags = --test --crate-type proc-macro +rusttest-macros: $(srctree)/rust/macros/lib.rs FORCE + $(call if_changed,rustc_test) + $(call if_changed,rustdoc) + +rusttest-kernel: private rustc_target_flags = --extern alloc \ + --extern build_error \ + --extern macros=$(objtree)/rust/test/libmacros.so +rusttest-kernel: private rustc_test_run_flags = \ + --skip bindgen_test_layout_ +rusttest-kernel: private rustdoc_host = yes +rusttest-kernel: private rustdoc_target_flags = --test +rusttest-kernel: $(srctree)/rust/kernel/lib.rs rusttestlib-build_error \ + rusttestlib-macros FORCE + $(call if_changed,rustc_test) + $(call if_changed,rustc_test_library) + $(call if_changed,rustdoc) + ifdef CONFIG_CC_IS_CLANG bindgen_c_flags = $(c_flags) else diff --git a/rust/bindgen_parameters b/rust/bindgen_parameters index 06ae9ddb29f085..c2cc4a88234ef8 100644 --- a/rust/bindgen_parameters +++ b/rust/bindgen_parameters @@ -8,3 +8,6 @@ # If SMP is disabled, `arch_spinlock_t` is defined as a ZST which triggers a Rust # warning. We don't need to peek into it anyway. --opaque-type spinlock + +# `seccomp`'s comment gets understood as a doctest +--no-doc-comments diff --git a/rust/kernel/allocator.rs b/rust/kernel/allocator.rs index 434a640b822537..a942f2a32f9569 100644 --- a/rust/kernel/allocator.rs +++ b/rust/kernel/allocator.rs @@ -24,6 +24,9 @@ unsafe impl GlobalAlloc for KernelAllocator { } } +#[global_allocator] +static ALLOCATOR: KernelAllocator = KernelAllocator; + #[alloc_error_handler] fn oom(_layout: Layout) -> ! { panic!("Out of memory!"); diff --git a/rust/kernel/bindings.rs b/rust/kernel/bindings.rs index fcfceac29808f2..93290926ceca14 100644 --- a/rust/kernel/bindings.rs +++ b/rust/kernel/bindings.rs @@ -4,6 +4,11 @@ //! //! Imports the generated bindings by `bindgen`. +// See https://github.com/rust-lang/rust-bindgen/issues/1651. +#![cfg_attr(test, allow(deref_nullptr))] +#![cfg_attr(test, allow(unaligned_references))] +#![cfg_attr(test, allow(unsafe_op_in_unsafe_fn))] + #[allow( clippy::all, non_camel_case_types, diff --git a/rust/kernel/build_assert.rs b/rust/kernel/build_assert.rs index 2c41d57f4661d4..f726927185c0d7 100644 --- a/rust/kernel/build_assert.rs +++ b/rust/kernel/build_assert.rs @@ -9,7 +9,8 @@ /// be called, a build error will be triggered. /// /// # Examples -/// ```no_run +/// ``` +/// # use kernel::build_error; /// #[inline] /// fn foo(a: usize) -> usize { /// a.checked_add(1).unwrap_or_else(|| build_error!("overflow")) @@ -38,7 +39,8 @@ macro_rules! build_error { /// These examples show that different types of [`assert!`] will trigger errors /// at different stage of compilation. It is preferred to err as early as /// possible, so [`static_assert!`] should be used whenever possible. -/// ```no_run +/// ```compile_fail +/// # use kernel::prelude::*; /// fn foo() { /// static_assert!(1 > 1); // Compile-time error /// build_assert!(1 > 1); // Build-time error @@ -49,6 +51,7 @@ macro_rules! build_error { /// When the condition refers to generic parameters or parameters of an inline function, /// [`static_assert!`] cannot be used. Use `build_assert!` in this scenario. /// ```no_run +/// # use kernel::prelude::*; /// fn foo() { /// // `static_assert!(N > 1);` is not allowed /// build_assert!(N > 1); // Build-time check diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs index 6841adaa31beb6..0b6e60adc9210c 100644 --- a/rust/kernel/error.rs +++ b/rust/kernel/error.rs @@ -190,7 +190,10 @@ where /// /// # Examples /// -/// ```rust,no_run +/// ```ignore +/// # use kernel::from_kernel_result; +/// # use kernel::c_types; +/// # use kernel::bindings; /// unsafe extern "C" fn probe_callback( /// pdev: *mut bindings::platform_device, /// ) -> c_types::c_int { @@ -219,7 +222,11 @@ macro_rules! from_kernel_result { /// /// # Examples /// -/// ```rust,no_run +/// ```ignore +/// # use kernel::prelude::*; +/// # use kernel::from_kernel_err_ptr; +/// # use kernel::c_types; +/// # use kernel::bindings; /// fn devm_platform_ioremap_resource( /// pdev: &mut PlatformDevice, /// index: u32, diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 14fb4023661c37..7ceb8985f83656 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -34,6 +34,8 @@ #[cfg(not(CONFIG_RUST))] compile_error!("Missing kernel configuration for conditional compilation"); +#[cfg(not(test))] +#[cfg(not(testlib))] mod allocator; #[doc(hidden)] @@ -155,6 +157,8 @@ impl<'a> Drop for KParamGuard<'a> { /// # Example /// /// ``` +/// # use kernel::prelude::*; +/// # use kernel::offset_of; /// struct Test { /// a: u64, /// b: u32, @@ -193,6 +197,8 @@ macro_rules! offset_of { /// # Example /// /// ``` +/// # use kernel::prelude::*; +/// # use kernel::container_of; /// struct Test { /// a: u64, /// b: u32, @@ -213,6 +219,3 @@ macro_rules! container_of { unsafe { ($ptr as *const _ as *const u8).offset(-offset) as *const $type } }} } - -#[global_allocator] -static ALLOCATOR: allocator::KernelAllocator = allocator::KernelAllocator; diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs index 195b03f1ec91b3..e32c2a5336eac0 100644 --- a/rust/kernel/module_param.rs +++ b/rust/kernel/module_param.rs @@ -210,7 +210,7 @@ macro_rules! impl_module_param { /// Generate a static [`kernel_param_ops`](../../../include/linux/moduleparam.h) struct. /// /// # Example -/// ```rust +/// ```ignore /// make_param_ops!( /// /// Documentation for new param ops. /// PARAM_OPS_MYTYPE, // Name for the static. diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs index 0322b6a2b91f50..38e9b4050363e9 100644 --- a/rust/kernel/prelude.rs +++ b/rust/kernel/prelude.rs @@ -7,7 +7,7 @@ //! //! # Examples //! -//! ```rust,no_run +//! ``` //! use kernel::prelude::*; //! ``` diff --git a/rust/kernel/print.rs b/rust/kernel/print.rs index b0f549717f6ae7..07cfbcb653a510 100644 --- a/rust/kernel/print.rs +++ b/rust/kernel/print.rs @@ -163,6 +163,7 @@ pub fn call_printk_cont(args: fmt::Arguments<'_>) { /// /// Public but hidden since it should only be used from public macros. #[doc(hidden)] +#[cfg(not(testlib))] #[macro_export] macro_rules! print_macro ( // The non-continuation cases (most of them, e.g. `INFO`). @@ -189,6 +190,15 @@ macro_rules! print_macro ( ); ); +// Stub for doctests +#[cfg(testlib)] +#[macro_export] +macro_rules! print_macro ( + ($format_string:path, $e:expr, $($arg:tt)+) => ( + () + ); +); + // We could use a macro to generate these macros. However, doing so ends // up being a bit ugly: it requires the dollar token trick to escape `$` as // well as playing with the `doc` attribute. Furthermore, they cannot be easily @@ -213,6 +223,7 @@ macro_rules! print_macro ( /// # Examples /// /// ``` +/// # use kernel::prelude::*; /// pr_emerg!("hello {}\n", "there"); /// ``` #[macro_export] @@ -237,6 +248,7 @@ macro_rules! pr_emerg ( /// # Examples /// /// ``` +/// # use kernel::prelude::*; /// pr_alert!("hello {}\n", "there"); /// ``` #[macro_export] @@ -261,6 +273,7 @@ macro_rules! pr_alert ( /// # Examples /// /// ``` +/// # use kernel::prelude::*; /// pr_crit!("hello {}\n", "there"); /// ``` #[macro_export] @@ -285,6 +298,7 @@ macro_rules! pr_crit ( /// # Examples /// /// ``` +/// # use kernel::prelude::*; /// pr_err!("hello {}\n", "there"); /// ``` #[macro_export] @@ -309,6 +323,7 @@ macro_rules! pr_err ( /// # Examples /// /// ``` +/// # use kernel::prelude::*; /// pr_warn!("hello {}\n", "there"); /// ``` #[macro_export] @@ -333,6 +348,7 @@ macro_rules! pr_warn ( /// # Examples /// /// ``` +/// # use kernel::prelude::*; /// pr_notice!("hello {}\n", "there"); /// ``` #[macro_export] @@ -357,6 +373,7 @@ macro_rules! pr_notice ( /// # Examples /// /// ``` +/// # use kernel::prelude::*; /// pr_info!("hello {}\n", "there"); /// ``` #[macro_export] @@ -382,6 +399,8 @@ macro_rules! pr_info ( /// # Examples /// /// ``` +/// # use kernel::prelude::*; +/// # use kernel::pr_cont; /// pr_info!("hello"); /// pr_cont!(" {}\n", "there"); /// ``` diff --git a/rust/kernel/static_assert.rs b/rust/kernel/static_assert.rs index 1d8f137155c681..a80d8ab57564f3 100644 --- a/rust/kernel/static_assert.rs +++ b/rust/kernel/static_assert.rs @@ -15,6 +15,7 @@ /// # Examples /// /// ``` +/// # use kernel::prelude::*; /// static_assert!(42 > 24); /// static_assert!(core::mem::size_of::() == 1); /// diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs index f45ff84ac789fb..5620080a8e81dc 100644 --- a/rust/kernel/str.rs +++ b/rust/kernel/str.rs @@ -19,7 +19,9 @@ pub type BStr = [u8]; /// /// # Examples /// -/// ```rust,no_run +/// ``` +/// # use kernel::b_str; +/// # use kernel::str::BStr; /// const MY_BSTR: &'static BStr = b_str!("My awesome BStr!"); /// ``` #[macro_export] @@ -242,7 +244,9 @@ where /// /// # Examples /// -/// ```rust,no_run +/// ``` +/// # use kernel::c_str; +/// # use kernel::str::CStr; /// const MY_CSTR: &'static CStr = c_str!("My awesome CStr!"); /// ``` #[macro_export] diff --git a/rust/kernel/sync/locked_by.rs b/rust/kernel/sync/locked_by.rs index 0312070db39350..d3e0b0d5e9b4ec 100644 --- a/rust/kernel/sync/locked_by.rs +++ b/rust/kernel/sync/locked_by.rs @@ -24,8 +24,8 @@ use core::{cell::UnsafeCell, ops::Deref, ptr}; /// locked; we enforce at run time that the right `InnerDirectory` is locked. /// /// ``` -/// use super::Mutex; -/// use alloc::{string::String, vec::Vec}; +/// # use kernel::prelude::*; +/// use kernel::sync::{LockedBy, Mutex}; /// /// struct InnerFile { /// bytes_used: u64, diff --git a/rust/kernel/sync/mod.rs b/rust/kernel/sync/mod.rs index 779d04e79e874d..946d0c4ec7d221 100644 --- a/rust/kernel/sync/mod.rs +++ b/rust/kernel/sync/mod.rs @@ -7,14 +7,15 @@ //! //! # Example //! -//! ``` -//! fn test() { -//! // SAFETY: `init` is called below. -//! let data = alloc::sync::Arc::pin(unsafe { Mutex::new(0) }); -//! mutex_init!(data.as_ref(), "test::data"); -//! *data.lock() = 10; -//! pr_info!("{}\n", *data.lock()); -//! } +//! ```no_run +//! # use kernel::prelude::*; +//! # use kernel::mutex_init; +//! # use kernel::sync::Mutex; +//! // SAFETY: `init` is called below. +//! let data = alloc::sync::Arc::pin(unsafe { Mutex::new(0) }); +//! mutex_init!(data.as_ref(), "test::data"); +//! *data.lock() = 10; +//! pr_info!("{}\n", *data.lock()); //! ``` use crate::str::CStr; diff --git a/rust/kernel/sysctl.rs b/rust/kernel/sysctl.rs index c219607a9a489f..b4b5e2e7835b87 100644 --- a/rust/kernel/sysctl.rs +++ b/rust/kernel/sysctl.rs @@ -184,3 +184,15 @@ impl Drop for Sysctl { self.header = ptr::null_mut(); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_trim_whitespace() { + assert_eq!(trim_whitespace(b"foo "), b"foo"); + assert_eq!(trim_whitespace(b" foo"), b"foo"); + assert_eq!(trim_whitespace(b" foo "), b"foo"); + } +} diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index e9a4d6852351fa..027a91812c7b0c 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -105,6 +105,8 @@ impl PointerWrapper for Pin { /// In the example below, we have multiple exit paths and we want to log regardless of which one is /// taken: /// ``` +/// # use kernel::prelude::*; +/// # use kernel::ScopeGuard; /// fn example1(arg: bool) { /// let _log = ScopeGuard::new(|| pr_info!("example1 completed\n")); /// @@ -119,6 +121,8 @@ impl PointerWrapper for Pin { /// 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::prelude::*; +/// # use kernel::ScopeGuard; /// fn example2(arg: bool) { /// let log = ScopeGuard::new(|| pr_info!("example2 returned early\n")); /// diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs index 07f1015157f913..fe39e5995f3600 100644 --- a/rust/macros/lib.rs +++ b/rust/macros/lib.rs @@ -22,7 +22,7 @@ use proc_macro::TokenStream; /// /// # Examples /// -/// ```rust,no_run +/// ```ignore /// use kernel::prelude::*; /// /// module!{ @@ -108,7 +108,7 @@ pub fn module(ts: TokenStream) -> TokenStream { /// /// # Examples /// -/// ```rust,no_run +/// ```ignore /// use kernel::prelude::*; /// /// module_misc_device! { diff --git a/rust/macros/module.rs b/rust/macros/module.rs index c950e758354f0e..3db45aed16187d 100644 --- a/rust/macros/module.rs +++ b/rust/macros/module.rs @@ -741,3 +741,32 @@ pub fn module_misc_device(ts: TokenStream) -> TokenStream { .parse() .expect("Error parsing formatted string into token stream.") } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_permissions_are_readonly() { + assert!(permissions_are_readonly("0b000000000")); + assert!(permissions_are_readonly("0o000")); + assert!(permissions_are_readonly("000")); + assert!(permissions_are_readonly("0x000")); + + assert!(!permissions_are_readonly("0b111111111")); + assert!(!permissions_are_readonly("0o777")); + assert!(!permissions_are_readonly("511")); + assert!(!permissions_are_readonly("0x1ff")); + + assert!(permissions_are_readonly("0o014")); + assert!(permissions_are_readonly("0o015")); + + assert!(!permissions_are_readonly("0o214")); + assert!(!permissions_are_readonly("0o024")); + assert!(!permissions_are_readonly("0o012")); + + assert!(!permissions_are_readonly("0o315")); + assert!(!permissions_are_readonly("0o065")); + assert!(!permissions_are_readonly("0o017")); + } +}