| 1 | |
| 2 |
|
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | |
| 23 | |
| 24 | |
| 25 | |
| 26 | |
| 27 | |
| 28 | |
| 29 | |
| 30 |
|
| 31 | use std::{
|
| 32 | fs,
|
| 33 | fs::File,
|
| 34 | io::{
|
| 35 | BufWriter,
|
| 36 | Read,
|
| 37 | Write, |
| 38 | },
|
| 39 | path::{
|
| 40 | Path,
|
| 41 | PathBuf, |
| 42 | }, |
| 43 | };
|
| 44 |
|
| 45 | |
| 46 | |
| 47 | |
| 48 | |
| 49 | |
| 50 | |
| 51 | |
| 52 | |
| 53 | |
| 54 | |
| 55 | fn find_real_path<'a>(srctree: &Path, valid_paths: &'a mut Vec<PathBuf>, file: &str) -> &'a str {
|
| 56 | valid_paths.clear();
|
| 57 |
|
| 58 | let potential_components: Vec<&str> = file.strip_suffix("_rs").unwrap().split('_').collect();
|
| 59 |
|
| 60 | find_candidates(srctree, valid_paths, Path::new(""), &potential_components);
|
| 61 | fn find_candidates(
|
| 62 | srctree: &Path,
|
| 63 | valid_paths: &mut Vec<PathBuf>,
|
| 64 | prefix: &Path,
|
| 65 | potential_components: &[&str],
|
| 66 | ) {
|
| 67 | |
| 68 | |
| 69 | let joined_potential_components = potential_components.join("_") + ".rs";
|
| 70 | if srctree
|
| 71 | .join("rust/kernel")
|
| 72 | .join(prefix)
|
| 73 | .join(&joined_potential_components)
|
| 74 | .is_file()
|
| 75 | {
|
| 76 | |
| 77 | valid_paths.push(
|
| 78 | Path::new("rust/kernel")
|
| 79 | .join(prefix)
|
| 80 | .join(joined_potential_components),
|
| 81 | );
|
| 82 | }
|
| 83 |
|
| 84 | |
| 85 | |
| 86 | for i in 1..potential_components.len() {
|
| 87 | let (components_prefix, components_rest) = potential_components.split_at(i);
|
| 88 | let prefix = prefix.join(components_prefix.join("_"));
|
| 89 | if srctree.join("rust/kernel").join(&prefix).is_dir() {
|
| 90 | find_candidates(srctree, valid_paths, &prefix, components_rest);
|
| 91 | }
|
| 92 | }
|
| 93 | }
|
| 94 |
|
| 95 | match valid_paths.as_slice() {
|
| 96 | [] => panic!(
|
| 97 | "No path candidates found for `{file}`. This is likely a bug in the build system, or \
|
| 98 | some files went away while compiling."
|
| 99 | ),
|
| 100 | [valid_path] => valid_path.to_str().unwrap(),
|
| 101 | valid_paths => {
|
| 102 | use std::fmt::Write;
|
| 103 |
|
| 104 | let mut candidates = String::new();
|
| 105 | for path in valid_paths {
|
| 106 | writeln!(&mut candidates, " {path:?}").unwrap();
|
| 107 | }
|
| 108 | panic!(
|
| 109 | "Several path candidates found for `{file}`, please resolve the ambiguity by \
|
| 110 | renaming a file or folder. Candidates:\n{candidates}",
|
| 111 | );
|
| 112 | }
|
| 113 | }
|
| 114 | }
|
| 115 |
|
| 116 | fn main() {
|
| 117 | let srctree = std::env::var("srctree").unwrap();
|
| 118 | let srctree = Path::new(&srctree);
|
| 119 |
|
| 120 | let mut paths = fs::read_dir("rust/test/doctests/kernel")
|
| 121 | .unwrap()
|
| 122 | .map(|entry| entry.unwrap().path())
|
| 123 | .collect::<Vec<_>>();
|
| 124 |
|
| 125 | |
| 126 | paths.sort();
|
| 127 |
|
| 128 | let mut rust_tests = String::new();
|
| 129 | let mut c_test_declarations = String::new();
|
| 130 | let mut c_test_cases = String::new();
|
| 131 | let mut body = String::new();
|
| 132 | let mut last_file = String::new();
|
| 133 | let mut number = 0;
|
| 134 | let mut valid_paths: Vec<PathBuf> = Vec::new();
|
| 135 | let mut real_path: &str = "";
|
| 136 | for path in paths {
|
| 137 | |
| 138 | |
| 139 | let name = path.file_name().unwrap().to_str().unwrap().to_string();
|
| 140 |
|
| 141 | |
| 142 | let (file, line) = name.rsplit_once('_').unwrap().0.rsplit_once('_').unwrap();
|
| 143 |
|
| 144 | |
| 145 | if file == last_file {
|
| 146 | number += 1;
|
| 147 | } else {
|
| 148 | number = 0;
|
| 149 | last_file = file.to_string();
|
| 150 |
|
| 151 | |
| 152 | real_path = find_real_path(srctree, &mut valid_paths, file);
|
| 153 | }
|
| 154 |
|
| 155 | |
| 156 | |
| 157 | |
| 158 | |
| 159 | |
| 160 | let kunit_name = format!("rust_doctest_kernel_{file}_{number}");
|
| 161 |
|
| 162 | |
| 163 | body.clear();
|
| 164 | File::open(path).unwrap().read_to_string(&mut body).unwrap();
|
| 165 |
|
| 166 | |
| 167 | let body_offset = body
|
| 168 | .lines()
|
| 169 | .take_while(|line| !line.contains("fn main() {"))
|
| 170 | .count()
|
| 171 | + 1;
|
| 172 |
|
| 173 | use std::fmt::Write;
|
| 174 | write!(
|
| 175 | rust_tests,
|
| 176 | r#"/// Generated `{name}` KUnit test case from a Rust documentation test.
|
| 177 | #[no_mangle]
|
| 178 | pub extern "C" fn {kunit_name}(__kunit_test: *mut ::kernel::bindings::kunit) {{
|
| 179 | /// Overrides the usual [`assert!`] macro with one that calls KUnit instead.
|
| 180 | #[allow(unused)]
|
| 181 | macro_rules! assert {{
|
| 182 | ($cond:expr $(,)?) => {{{{
|
| 183 | ::kernel::kunit_assert!(
|
| 184 | "{kunit_name}", c"{real_path}", __DOCTEST_ANCHOR - {line}, $cond
|
| 185 | );
|
| 186 | }}}}
|
| 187 | }}
|
| 188 |
|
| 189 | /// Overrides the usual [`assert_eq!`] macro with one that calls KUnit instead.
|
| 190 | #[allow(unused)]
|
| 191 | macro_rules! assert_eq {{
|
| 192 | ($left:expr, $right:expr $(,)?) => {{{{
|
| 193 | ::kernel::kunit_assert_eq!(
|
| 194 | "{kunit_name}", c"{real_path}", __DOCTEST_ANCHOR - {line}, $left, $right
|
| 195 | );
|
| 196 | }}}}
|
| 197 | }}
|
| 198 |
|
| 199 | // Many tests need the prelude, so provide it by default.
|
| 200 | #[allow(unused)]
|
| 201 | use ::kernel::prelude::*;
|
| 202 |
|
| 203 | // Unconditionally print the location of the original doctest (i.e. rather than the location in
|
| 204 | // the generated file) so that developers can easily map the test back to the source code.
|
| 205 | //
|
| 206 | // This information is also printed when assertions fail, but this helps in the successful cases
|
| 207 | // when the user is running KUnit manually, or when passing `--raw_output` to `kunit.py`.
|
| 208 | //
|
| 209 | // This follows the syntax for declaring test metadata in the proposed KTAP v2 spec, which may
|
| 210 | // be used for the proposed KUnit test attributes API. Thus hopefully this will make migration
|
| 211 | // easier later on.
|
| 212 | ::kernel::kunit::info(fmt!(" # {kunit_name}.location: {real_path}:{line}\n"));
|
| 213 |
|
| 214 | /// The anchor where the test code body starts.
|
| 215 | #[allow(unused)]
|
| 216 | static __DOCTEST_ANCHOR: i32 = ::core::line!() as i32 + {body_offset} + 2;
|
| 217 | {{
|
| 218 | #![allow(unreachable_pub, clippy::disallowed_names)]
|
| 219 | {body}
|
| 220 | main();
|
| 221 | }}
|
| 222 | }}
|
| 223 |
|
| 224 | "#
|
| 225 | )
|
| 226 | .unwrap();
|
| 227 |
|
| 228 | write!(c_test_declarations, "void {kunit_name}(struct kunit *);\n").unwrap();
|
| 229 | write!(c_test_cases, " KUNIT_CASE({kunit_name}),\n").unwrap();
|
| 230 | }
|
| 231 |
|
| 232 | let rust_tests = rust_tests.trim();
|
| 233 | let c_test_declarations = c_test_declarations.trim();
|
| 234 | let c_test_cases = c_test_cases.trim();
|
| 235 |
|
| 236 | write!(
|
| 237 | BufWriter::new(File::create("rust/doctests_kernel_generated.rs").unwrap()),
|
| 238 | r#"//! `kernel` crate documentation tests.
|
| 239 |
|
| 240 | const __LOG_PREFIX: &[u8] = b"rust_doctests_kernel\0";
|
| 241 |
|
| 242 | /// Dummy module type for doctest context.
|
| 243 | struct LocalModule;
|
| 244 |
|
| 245 | use kernel::{{
|
| 246 | str::CStr,
|
| 247 | ModuleMetadata,
|
| 248 | ThisModule, //
|
| 249 | }};
|
| 250 | use core::ptr::null_mut;
|
| 251 |
|
| 252 | impl ModuleMetadata for LocalModule {{
|
| 253 | const NAME: &'static CStr = c"rust_doctests_kernel";
|
| 254 | const THIS_MODULE: ThisModule = {{
|
| 255 | // SAFETY: `try_module_get`/`module_put` handle null module pointers gracefully.
|
| 256 | unsafe {{ ThisModule::from_ptr(null_mut()) }}
|
| 257 | }};
|
| 258 | }}
|
| 259 |
|
| 260 | {rust_tests}
|
| 261 | "#
|
| 262 | )
|
| 263 | .unwrap();
|
| 264 |
|
| 265 | write!(
|
| 266 | BufWriter::new(File::create("rust/doctests_kernel_generated_kunit.c").unwrap()),
|
| 267 | r#"/*
|
| 268 | * `kernel` crate documentation tests.
|
| 269 | */
|
| 270 |
|
| 271 | #include <kunit/test.h>
|
| 272 |
|
| 273 | {c_test_declarations}
|
| 274 |
|
| 275 | static struct kunit_case test_cases[] = {{
|
| 276 | {c_test_cases}
|
| 277 | {{ }}
|
| 278 | }};
|
| 279 |
|
| 280 | static struct kunit_suite test_suite = {{
|
| 281 | .name = "rust_doctests_kernel",
|
| 282 | .test_cases = test_cases,
|
| 283 | }};
|
| 284 |
|
| 285 | kunit_test_suite(test_suite);
|
| 286 |
|
| 287 | MODULE_LICENSE("GPL");
|
| 288 | "#
|
| 289 | )
|
| 290 | .unwrap();
|
| 291 | }
|