162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci 362306a36Sopenharmony_ci//! Generates KUnit tests from saved `rustdoc`-generated tests. 462306a36Sopenharmony_ci//! 562306a36Sopenharmony_ci//! KUnit passes a context (`struct kunit *`) to each test, which should be forwarded to the other 662306a36Sopenharmony_ci//! KUnit functions and macros. 762306a36Sopenharmony_ci//! 862306a36Sopenharmony_ci//! However, we want to keep this as an implementation detail because: 962306a36Sopenharmony_ci//! 1062306a36Sopenharmony_ci//! - Test code should not care about the implementation. 1162306a36Sopenharmony_ci//! 1262306a36Sopenharmony_ci//! - Documentation looks worse if it needs to carry extra details unrelated to the piece 1362306a36Sopenharmony_ci//! being described. 1462306a36Sopenharmony_ci//! 1562306a36Sopenharmony_ci//! - Test code should be able to define functions and call them, without having to carry 1662306a36Sopenharmony_ci//! the context. 1762306a36Sopenharmony_ci//! 1862306a36Sopenharmony_ci//! - Later on, we may want to be able to test non-kernel code (e.g. `core`, `alloc` or 1962306a36Sopenharmony_ci//! third-party crates) which likely use the standard library `assert*!` macros. 2062306a36Sopenharmony_ci//! 2162306a36Sopenharmony_ci//! For this reason, instead of the passed context, `kunit_get_current_test()` is used instead 2262306a36Sopenharmony_ci//! (i.e. `current->kunit_test`). 2362306a36Sopenharmony_ci//! 2462306a36Sopenharmony_ci//! Note that this means other threads/tasks potentially spawned by a given test, if failing, will 2562306a36Sopenharmony_ci//! report the failure in the kernel log but will not fail the actual test. Saving the pointer in 2662306a36Sopenharmony_ci//! e.g. a `static` per test does not fully solve the issue either, because currently KUnit does 2762306a36Sopenharmony_ci//! not support assertions (only expectations) from other tasks. Thus leave that feature for 2862306a36Sopenharmony_ci//! the future, which simplifies the code here too. We could also simply not allow `assert`s in 2962306a36Sopenharmony_ci//! other tasks, but that seems overly constraining, and we do want to support them, eventually. 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ciuse std::{ 3262306a36Sopenharmony_ci fs, 3362306a36Sopenharmony_ci fs::File, 3462306a36Sopenharmony_ci io::{BufWriter, Read, Write}, 3562306a36Sopenharmony_ci path::{Path, PathBuf}, 3662306a36Sopenharmony_ci}; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci/// Find the real path to the original file based on the `file` portion of the test name. 3962306a36Sopenharmony_ci/// 4062306a36Sopenharmony_ci/// `rustdoc` generated `file`s look like `sync_locked_by_rs`. Underscores (except the last one) 4162306a36Sopenharmony_ci/// may represent an actual underscore in a directory/file, or a path separator. Thus the actual 4262306a36Sopenharmony_ci/// file might be `sync_locked_by.rs`, `sync/locked_by.rs`, `sync_locked/by.rs` or 4362306a36Sopenharmony_ci/// `sync/locked/by.rs`. This function walks the file system to determine which is the real one. 4462306a36Sopenharmony_ci/// 4562306a36Sopenharmony_ci/// This does require that ambiguities do not exist, but that seems fair, especially since this is 4662306a36Sopenharmony_ci/// all supposed to be temporary until `rustdoc` gives us proper metadata to build this. If such 4762306a36Sopenharmony_ci/// ambiguities are detected, they are diagnosed and the script panics. 4862306a36Sopenharmony_cifn find_real_path<'a>(srctree: &Path, valid_paths: &'a mut Vec<PathBuf>, file: &str) -> &'a str { 4962306a36Sopenharmony_ci valid_paths.clear(); 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci let potential_components: Vec<&str> = file.strip_suffix("_rs").unwrap().split('_').collect(); 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci find_candidates(srctree, valid_paths, Path::new(""), &potential_components); 5462306a36Sopenharmony_ci fn find_candidates( 5562306a36Sopenharmony_ci srctree: &Path, 5662306a36Sopenharmony_ci valid_paths: &mut Vec<PathBuf>, 5762306a36Sopenharmony_ci prefix: &Path, 5862306a36Sopenharmony_ci potential_components: &[&str], 5962306a36Sopenharmony_ci ) { 6062306a36Sopenharmony_ci // The base case: check whether all the potential components left, joined by underscores, 6162306a36Sopenharmony_ci // is a file. 6262306a36Sopenharmony_ci let joined_potential_components = potential_components.join("_") + ".rs"; 6362306a36Sopenharmony_ci if srctree 6462306a36Sopenharmony_ci .join("rust/kernel") 6562306a36Sopenharmony_ci .join(prefix) 6662306a36Sopenharmony_ci .join(&joined_potential_components) 6762306a36Sopenharmony_ci .is_file() 6862306a36Sopenharmony_ci { 6962306a36Sopenharmony_ci // Avoid `srctree` here in order to keep paths relative to it in the KTAP output. 7062306a36Sopenharmony_ci valid_paths.push( 7162306a36Sopenharmony_ci Path::new("rust/kernel") 7262306a36Sopenharmony_ci .join(prefix) 7362306a36Sopenharmony_ci .join(joined_potential_components), 7462306a36Sopenharmony_ci ); 7562306a36Sopenharmony_ci } 7662306a36Sopenharmony_ci 7762306a36Sopenharmony_ci // In addition, check whether each component prefix, joined by underscores, is a directory. 7862306a36Sopenharmony_ci // If not, there is no need to check for combinations with that prefix. 7962306a36Sopenharmony_ci for i in 1..potential_components.len() { 8062306a36Sopenharmony_ci let (components_prefix, components_rest) = potential_components.split_at(i); 8162306a36Sopenharmony_ci let prefix = prefix.join(components_prefix.join("_")); 8262306a36Sopenharmony_ci if srctree.join("rust/kernel").join(&prefix).is_dir() { 8362306a36Sopenharmony_ci find_candidates(srctree, valid_paths, &prefix, components_rest); 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci } 8662306a36Sopenharmony_ci } 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci assert!( 8962306a36Sopenharmony_ci valid_paths.len() > 0, 9062306a36Sopenharmony_ci "No path candidates found. This is likely a bug in the build system, or some files went \ 9162306a36Sopenharmony_ci away while compiling." 9262306a36Sopenharmony_ci ); 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci if valid_paths.len() > 1 { 9562306a36Sopenharmony_ci eprintln!("Several path candidates found:"); 9662306a36Sopenharmony_ci for path in valid_paths { 9762306a36Sopenharmony_ci eprintln!(" {path:?}"); 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci panic!( 10062306a36Sopenharmony_ci "Several path candidates found, please resolve the ambiguity by renaming a file or \ 10162306a36Sopenharmony_ci folder." 10262306a36Sopenharmony_ci ); 10362306a36Sopenharmony_ci } 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci valid_paths[0].to_str().unwrap() 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cifn main() { 10962306a36Sopenharmony_ci let srctree = std::env::var("srctree").unwrap(); 11062306a36Sopenharmony_ci let srctree = Path::new(&srctree); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci let mut paths = fs::read_dir("rust/test/doctests/kernel") 11362306a36Sopenharmony_ci .unwrap() 11462306a36Sopenharmony_ci .map(|entry| entry.unwrap().path()) 11562306a36Sopenharmony_ci .collect::<Vec<_>>(); 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci // Sort paths. 11862306a36Sopenharmony_ci paths.sort(); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci let mut rust_tests = String::new(); 12162306a36Sopenharmony_ci let mut c_test_declarations = String::new(); 12262306a36Sopenharmony_ci let mut c_test_cases = String::new(); 12362306a36Sopenharmony_ci let mut body = String::new(); 12462306a36Sopenharmony_ci let mut last_file = String::new(); 12562306a36Sopenharmony_ci let mut number = 0; 12662306a36Sopenharmony_ci let mut valid_paths: Vec<PathBuf> = Vec::new(); 12762306a36Sopenharmony_ci let mut real_path: &str = ""; 12862306a36Sopenharmony_ci for path in paths { 12962306a36Sopenharmony_ci // The `name` follows the `{file}_{line}_{number}` pattern (see description in 13062306a36Sopenharmony_ci // `scripts/rustdoc_test_builder.rs`). Discard the `number`. 13162306a36Sopenharmony_ci let name = path.file_name().unwrap().to_str().unwrap().to_string(); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci // Extract the `file` and the `line`, discarding the `number`. 13462306a36Sopenharmony_ci let (file, line) = name.rsplit_once('_').unwrap().0.rsplit_once('_').unwrap(); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci // Generate an ID sequence ("test number") for each one in the file. 13762306a36Sopenharmony_ci if file == last_file { 13862306a36Sopenharmony_ci number += 1; 13962306a36Sopenharmony_ci } else { 14062306a36Sopenharmony_ci number = 0; 14162306a36Sopenharmony_ci last_file = file.to_string(); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci // Figure out the real path, only once per file. 14462306a36Sopenharmony_ci real_path = find_real_path(srctree, &mut valid_paths, file); 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci // Generate a KUnit name (i.e. test name and C symbol) for this test. 14862306a36Sopenharmony_ci // 14962306a36Sopenharmony_ci // We avoid the line number, like `rustdoc` does, to make things slightly more stable for 15062306a36Sopenharmony_ci // bisection purposes. However, to aid developers in mapping back what test failed, we will 15162306a36Sopenharmony_ci // print a diagnostics line in the KTAP report. 15262306a36Sopenharmony_ci let kunit_name = format!("rust_doctest_kernel_{file}_{number}"); 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_ci // Read the test's text contents to dump it below. 15562306a36Sopenharmony_ci body.clear(); 15662306a36Sopenharmony_ci File::open(path).unwrap().read_to_string(&mut body).unwrap(); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci // Calculate how many lines before `main` function (including the `main` function line). 15962306a36Sopenharmony_ci let body_offset = body 16062306a36Sopenharmony_ci .lines() 16162306a36Sopenharmony_ci .take_while(|line| !line.contains("fn main() {")) 16262306a36Sopenharmony_ci .count() 16362306a36Sopenharmony_ci + 1; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci use std::fmt::Write; 16662306a36Sopenharmony_ci write!( 16762306a36Sopenharmony_ci rust_tests, 16862306a36Sopenharmony_ci r#"/// Generated `{name}` KUnit test case from a Rust documentation test. 16962306a36Sopenharmony_ci#[no_mangle] 17062306a36Sopenharmony_cipub extern "C" fn {kunit_name}(__kunit_test: *mut kernel::bindings::kunit) {{ 17162306a36Sopenharmony_ci /// Overrides the usual [`assert!`] macro with one that calls KUnit instead. 17262306a36Sopenharmony_ci #[allow(unused)] 17362306a36Sopenharmony_ci macro_rules! assert {{ 17462306a36Sopenharmony_ci ($cond:expr $(,)?) => {{{{ 17562306a36Sopenharmony_ci kernel::kunit_assert!("{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $cond); 17662306a36Sopenharmony_ci }}}} 17762306a36Sopenharmony_ci }} 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci /// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead. 18062306a36Sopenharmony_ci #[allow(unused)] 18162306a36Sopenharmony_ci macro_rules! assert_eq {{ 18262306a36Sopenharmony_ci ($left:expr, $right:expr $(,)?) => {{{{ 18362306a36Sopenharmony_ci kernel::kunit_assert_eq!("{kunit_name}", "{real_path}", __DOCTEST_ANCHOR - {line}, $left, $right); 18462306a36Sopenharmony_ci }}}} 18562306a36Sopenharmony_ci }} 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci // Many tests need the prelude, so provide it by default. 18862306a36Sopenharmony_ci #[allow(unused)] 18962306a36Sopenharmony_ci use kernel::prelude::*; 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci // Unconditionally print the location of the original doctest (i.e. rather than the location in 19262306a36Sopenharmony_ci // the generated file) so that developers can easily map the test back to the source code. 19362306a36Sopenharmony_ci // 19462306a36Sopenharmony_ci // This information is also printed when assertions fail, but this helps in the successful cases 19562306a36Sopenharmony_ci // when the user is running KUnit manually, or when passing `--raw_output` to `kunit.py`. 19662306a36Sopenharmony_ci // 19762306a36Sopenharmony_ci // This follows the syntax for declaring test metadata in the proposed KTAP v2 spec, which may 19862306a36Sopenharmony_ci // be used for the proposed KUnit test attributes API. Thus hopefully this will make migration 19962306a36Sopenharmony_ci // easier later on. 20062306a36Sopenharmony_ci kernel::kunit::info(format_args!(" # {kunit_name}.location: {real_path}:{line}\n")); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci /// The anchor where the test code body starts. 20362306a36Sopenharmony_ci #[allow(unused)] 20462306a36Sopenharmony_ci static __DOCTEST_ANCHOR: i32 = core::line!() as i32 + {body_offset} + 1; 20562306a36Sopenharmony_ci {{ 20662306a36Sopenharmony_ci {body} 20762306a36Sopenharmony_ci main(); 20862306a36Sopenharmony_ci }} 20962306a36Sopenharmony_ci}} 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci"# 21262306a36Sopenharmony_ci ) 21362306a36Sopenharmony_ci .unwrap(); 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci write!(c_test_declarations, "void {kunit_name}(struct kunit *);\n").unwrap(); 21662306a36Sopenharmony_ci write!(c_test_cases, " KUNIT_CASE({kunit_name}),\n").unwrap(); 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci let rust_tests = rust_tests.trim(); 22062306a36Sopenharmony_ci let c_test_declarations = c_test_declarations.trim(); 22162306a36Sopenharmony_ci let c_test_cases = c_test_cases.trim(); 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci write!( 22462306a36Sopenharmony_ci BufWriter::new(File::create("rust/doctests_kernel_generated.rs").unwrap()), 22562306a36Sopenharmony_ci r#"//! `kernel` crate documentation tests. 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ciconst __LOG_PREFIX: &[u8] = b"rust_doctests_kernel\0"; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci{rust_tests} 23062306a36Sopenharmony_ci"# 23162306a36Sopenharmony_ci ) 23262306a36Sopenharmony_ci .unwrap(); 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci write!( 23562306a36Sopenharmony_ci BufWriter::new(File::create("rust/doctests_kernel_generated_kunit.c").unwrap()), 23662306a36Sopenharmony_ci r#"/* 23762306a36Sopenharmony_ci * `kernel` crate documentation tests. 23862306a36Sopenharmony_ci */ 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci#include <kunit/test.h> 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci{c_test_declarations} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic struct kunit_case test_cases[] = {{ 24562306a36Sopenharmony_ci {c_test_cases} 24662306a36Sopenharmony_ci {{ }} 24762306a36Sopenharmony_ci}}; 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_cistatic struct kunit_suite test_suite = {{ 25062306a36Sopenharmony_ci .name = "rust_doctests_kernel", 25162306a36Sopenharmony_ci .test_cases = test_cases, 25262306a36Sopenharmony_ci}}; 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_cikunit_test_suite(test_suite); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 25762306a36Sopenharmony_ci"# 25862306a36Sopenharmony_ci ) 25962306a36Sopenharmony_ci .unwrap(); 26062306a36Sopenharmony_ci} 261