Skip to content

Commit a7fed23

Browse files
committed
rust: kunit: Add test/test_suite macros, sample test
This is an in-progress patch to allow (and demonstrate) the creation of 100% rust KUnit tests and test modules. Unlike the current doctests implementation, the various test case and test suite structures (as well as the entires in a special section) are generated from rust code, rather than a c helper file. Note that this uses a bunch of unsafe, the const_mut_ref feature, and the test suite macro doesn't fully work yet. (The whole thing is also a bit outdated). This is largely due to me not knowing enough rust yet to deal with the weirder unsafe / ffi bits. Signed-off-by: David Gow <[email protected]>
1 parent 5423795 commit a7fed23

File tree

7 files changed

+134
-3
lines changed

7 files changed

+134
-3
lines changed

include/kunit/test.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
168168
* to run it.
169169
*/
170170
struct kunit_suite {
171-
const char name[256];
171+
const char *name;
172172
int (*suite_init)(struct kunit_suite *suite);
173173
void (*suite_exit)(struct kunit_suite *suite);
174174
int (*init)(struct kunit *test);

lib/kunit/executor_test.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ static struct kunit_suite *alloc_fake_suite(struct kunit *test,
150150

151151
/* We normally never expect to allocate suites, hence the non-const cast. */
152152
suite = kunit_kzalloc(test, sizeof(*suite), GFP_KERNEL);
153-
strncpy((char *)suite->name, suite_name, sizeof(suite->name) - 1);
153+
suite->name = suite_name;
154154
suite->test_cases = test_cases;
155155

156156
return suite;

rust/kernel/kunit.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,57 @@ macro_rules! kunit_assert_eq {
8989
$crate::kunit_assert!($test, $left == $right);
9090
}};
9191
}
92+
93+
#[macro_export]
94+
macro_rules! kunit_case {
95+
($name:ident) => {{
96+
$crate::bindings::kunit_case {
97+
run_case: Some($name),
98+
name: c_str!(core::stringify!($name)).as_char_ptr(),
99+
// Not a parameterised test.
100+
generate_params: None,
101+
// Private fields, all null.
102+
status: kunit_status_KUNIT_SUCCESS,
103+
log: core::ptr::null_mut(),
104+
}
105+
}};
106+
}
107+
108+
/* We need this to NULL-terminate the list of test cases. */
109+
#[used]
110+
pub static mut kunit_null_test_case : bindings::kunit_case = bindings::kunit_case {
111+
run_case: None,
112+
name: core::ptr::null_mut(),
113+
generate_params: None,
114+
// Internal only
115+
status: bindings::kunit_status_KUNIT_SUCCESS,
116+
log: core::ptr::null_mut(),
117+
};
118+
119+
120+
#[macro_export]
121+
macro_rules! kunit_test_suite {
122+
($name:ident, suite_init $suite_init:ident, suite_exit $suite_exit:ident, init $init:ident, exit $exit:ident, $($test_cases:ident),+) => {
123+
static mut testsuite : $crate::bindings::kunit_suite = unsafe { $crate::bindings::kunit_suite {
124+
name: c_str!(core::stringify!($name)).as_char_ptr(),
125+
suite_init: $suite_init,
126+
suite_exit: $suite_exit,
127+
init: $init,
128+
exit: $exit,
129+
test_cases: unsafe { static mut testcases : &mut[$crate::bindings::kunit_case] = &mut[ $($test_cases,)+ $crate::kunit::kunit_null_test_case ]; testcases.as_mut_ptr() },
130+
status_comment: [0; 256usize],
131+
debugfs: core::ptr::null_mut(),
132+
log: core::ptr::null_mut(),
133+
suite_init_err: 0,
134+
}};
135+
136+
#[used]
137+
#[link_section = ".kunit_test_suites"]
138+
static mut test_suite_entry : *const kernel::bindings::kunit_suite = unsafe { &testsuite };
139+
};
140+
141+
($name:ident, $($test_cases:ident),+) => {
142+
$crate::kunit_test_suite!{$name, suite_init None, suite_exit None, init None, exit None $(,$test_cases)+}
143+
}
144+
145+
}

samples/rust/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,12 @@ config SAMPLE_RUST_SELFTESTS
163163

164164
If unsure, say N.
165165

166+
config SAMPLE_RUST_KUNIT
167+
tristate "KUnit test"
168+
depends on KUNIT
169+
default KUNIT_ALL_TESTS
170+
help
171+
This option builds a sample KUnit test written in Rust.
172+
173+
166174
endif # SAMPLES_RUST

samples/rust/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,6 @@ obj-$(CONFIG_SAMPLE_RUST_NETFILTER) += rust_netfilter.o
1515
obj-$(CONFIG_SAMPLE_RUST_ECHO_SERVER) += rust_echo_server.o
1616
obj-$(CONFIG_SAMPLE_RUST_FS) += rust_fs.o
1717
obj-$(CONFIG_SAMPLE_RUST_SELFTESTS) += rust_selftests.o
18+
obj-$(CONFIG_SAMPLE_RUST_KUNIT) += rust_kunit.o
1819

1920
subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs

samples/rust/rust_kunit.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Rust KUnit test sample.
4+
//! Note that this uses KUnit directly, not via Rust tests
5+
#![allow(warnings, unused)]
6+
7+
use kernel::c_str;
8+
use kernel::prelude::*;
9+
use kernel::bindings::*;
10+
11+
module! {
12+
type: RustMinimal,
13+
name: "rust_kunit",
14+
author: "David Gow [email protected]>",
15+
description: "KUnit test written in Rust",
16+
license: "GPL",
17+
}
18+
19+
struct RustMinimal {
20+
message: String,
21+
}
22+
23+
impl kernel::Module for RustMinimal {
24+
fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> {
25+
pr_info!("Rust minimal sample (init)\n");
26+
pr_info!("Am I built-in? {}\n", !cfg!(MODULE));
27+
28+
Ok(RustMinimal {
29+
message: "on the heap!".try_to_owned()?,
30+
})
31+
}
32+
}
33+
34+
impl Drop for RustMinimal {
35+
fn drop(&mut self) {
36+
pr_info!("My message is {}\n", self.message);
37+
pr_info!("Rust minimal sample (exit)\n");
38+
}
39+
}
40+
41+
unsafe extern "C" fn rust_sample_test(test: *mut kernel::bindings::kunit) {
42+
//pr_info!("Running test!\n");
43+
}
44+
45+
static mut testcase : kernel::bindings::kunit_case = kernel::kunit_case!(rust_sample_test);
46+
47+
static mut testcases : &mut[kernel::bindings::kunit_case] = unsafe { &mut[ testcase, kernel::kunit::kunit_null_test_case] };
48+
49+
// TODO: This macro fails so far...
50+
//kernel::kunit_test_suite!(rust_sample2, testcase);
51+
52+
static mut testsuite : kernel::bindings::kunit_suite = unsafe{ kernel::bindings::kunit_suite {
53+
name: c_str!(core::stringify!(rust_sample2)).as_char_ptr(),
54+
suite_init: None,
55+
suite_exit: None,
56+
init: None,
57+
exit: None,
58+
test_cases: unsafe { testcases.as_mut_ptr() },
59+
status_comment: [0; 256usize],
60+
debugfs: core::ptr::null_mut(),
61+
log: core::ptr::null_mut(),
62+
suite_init_err: 0,
63+
}};
64+
65+
#[used]
66+
#[link_section = ".kunit_test_suites"]
67+
static mut test_suite_entry : *const kernel::bindings::kunit_suite = unsafe { &testsuite };
68+

scripts/Makefile.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ $(obj)/%.lst: $(src)/%.c FORCE
275275
# Compile Rust sources (.rs)
276276
# ---------------------------------------------------------------------------
277277

278-
rust_allowed_features := allocator_api,bench_black_box,core_ffi_c,generic_associated_types,const_ptr_offset_from,const_refs_to_cell
278+
rust_allowed_features := allocator_api,bench_black_box,core_ffi_c,generic_associated_types,const_ptr_offset_from,const_refs_to_cell,const_mut_refs
279279

280280
rust_common_cmd = \
281281
RUST_MODFILE=$(modfile) $(RUSTC_OR_CLIPPY) $(rust_flags) \

0 commit comments

Comments
 (0)