diff options
Diffstat (limited to 'scripts/rustdoc_test_gen.rs')
-rw-r--r-- | scripts/rustdoc_test_gen.rs | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/scripts/rustdoc_test_gen.rs b/scripts/rustdoc_test_gen.rs new file mode 100644 index 000000000000..1d967162d32d --- /dev/null +++ b/scripts/rustdoc_test_gen.rs @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Generates KUnit tests from saved `rustdoc`-generated tests. + +use std::io::{BufWriter, Read, Write}; +use std::{fs, fs::File}; + +fn main() { + let mut dirs = fs::read_dir("rust/test/doctests/kernel") + .unwrap() + .map(|p| p.unwrap().path()) + .collect::<Vec<_>>(); + dirs.sort(); + + let mut rust_tests = String::new(); + let mut c_test_declarations = String::new(); + let mut c_test_cases = String::new(); + let mut content = String::new(); + for path in dirs { + content.clear(); + + File::open(path) + .unwrap() + .read_to_string(&mut content) + .unwrap(); + + let (name, body) = content.split_once("\n").unwrap(); + + use std::fmt::Write; + write!( + rust_tests, + r#"/// Generated `{name}` KUnit test case from a Rust documentation test. +#[no_mangle] +pub fn {name}(__kunit_test: *mut kernel::bindings::kunit) {{ + /// Provides mutual exclusion (see `# Implementation` notes). + static __KUNIT_TEST_MUTEX: kernel::sync::smutex::Mutex<()> = + kernel::sync::smutex::Mutex::new(()); + + /// Saved argument (see `# Implementation` notes). + static __KUNIT_TEST: core::sync::atomic::AtomicPtr<kernel::bindings::kunit> = + core::sync::atomic::AtomicPtr::new(core::ptr::null_mut()); + + let __kunit_test_mutex_guard = __KUNIT_TEST_MUTEX.lock(); + __KUNIT_TEST.store(__kunit_test, core::sync::atomic::Ordering::SeqCst); + + /// Overrides the usual [`assert!`] macro with one that calls KUnit instead. + #[allow(unused)] + macro_rules! assert {{ + ($cond:expr $(,)?) => {{{{ + kernel::kunit_assert!( + __KUNIT_TEST.load(core::sync::atomic::Ordering::SeqCst), + $cond + ); + }}}} + }} + + /// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead. + #[allow(unused)] + macro_rules! assert_eq {{ + ($left:expr, $right:expr $(,)?) => {{{{ + kernel::kunit_assert_eq!( + __KUNIT_TEST.load(core::sync::atomic::Ordering::SeqCst), + $left, + $right + ); + }}}} + }} + + // Many tests need the prelude, so provide it by default. + #[allow(unused)] + use kernel::prelude::*; + + {{ + {body} + main(); + }} +}} + +"# + ) + .unwrap(); + + write!(c_test_declarations, "void {name}(struct kunit *);\n").unwrap(); + write!(c_test_cases, " KUNIT_CASE({name}),\n").unwrap(); + } + + let rust_tests = rust_tests.trim(); + let c_test_declarations = c_test_declarations.trim(); + let c_test_cases = c_test_cases.trim(); + + write!( + BufWriter::new(File::create("rust/doctests_kernel_generated.rs").unwrap()), + r#"// SPDX-License-Identifier: GPL-2.0 + +//! `kernel` crate documentation tests. + +// # Implementation +// +// KUnit gives us a context in the form of the `kunit_test` parameter that one +// needs to pass back to other KUnit functions and macros. +// +// However, we want to keep this as an implementation detail because: +// +// - Test code should not care about the implementation. +// +// - Documentation looks worse if it needs to carry extra details unrelated +// to the piece being described. +// +// - Test code should be able to define functions and call them, without +// having to carry the context (since functions cannot capture dynamic +// environment). +// +// - Later on, we may want to be able to test non-kernel code (e.g. `core`, +// `alloc` or external crates) which likely use the standard library +// `assert*!` macros. +// +// For this reason, `static`s are used in the generated code to save the +// argument which then gets read by the asserting macros. These macros then +// call back into KUnit, instead of panicking. +// +// To avoid depending on whether KUnit allows to run tests concurrently and/or +// reentrantly, we ensure mutual exclusion on our end. To ensure a single test +// being killed does not trigger failure of every other test (timing out), +// we provide different `static`s per test (which also allow for concurrent +// execution, though KUnit runs them sequentially). +// +// Furthermore, since test code may create threads and assert from them, we use +// an `AtomicPtr` to hold the context (though each test only writes once before +// threads may be created). + +const __LOG_PREFIX: &[u8] = b"rust_kernel_doctests\0"; + +{rust_tests} +"# + ) + .unwrap(); + + write!( + BufWriter::new(File::create("rust/doctests_kernel_generated_kunit.c").unwrap()), + r#"// SPDX-License-Identifier: GPL-2.0 +/* + * `kernel` crate documentation tests. + */ + +#include <kunit/test.h> + +{c_test_declarations} + +static struct kunit_case test_cases[] = {{ + {c_test_cases} + {{ }} +}}; + +static struct kunit_suite test_suite = {{ + .name = "rust_kernel_doctests", + .test_cases = test_cases, +}}; + +kunit_test_suite(test_suite); + +MODULE_LICENSE("GPL"); +"# + ) + .unwrap(); +} |