Skip to content

Commit e8fd415

Browse files
committed
rust: kunit: allow to know if we are in a test
In some cases, you need to call test-only code from outside the test case, for example, to mock a function or a module. In order to check whether we are in a test or not, we need to test if `CONFIG_KUNIT` is set. Unfortunately, we cannot rely only on this condition because some distros compile KUnit in production kernels, so checking at runtime that `current->kunit_test != NULL` is required. Note that the C function `kunit_get_current_test()` can not be used because it is not present in the current Rust tree yet. Once it is available we might want to change our Rust wrapper to use it. This patch adds a function to know whether we are in a KUnit test or not and examples showing how to mock a function and a module. Signed-off-by: José Expósito <[email protected]>
1 parent 3544d74 commit e8fd415

File tree

1 file changed

+77
-0
lines changed

1 file changed

+77
-0
lines changed

rust/kernel/kunit.rs

+77
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
//!
77
//! Reference: <https://www.kernel.org/doc/html/latest/dev-tools/kunit/index.html>
88
9+
use crate::task::Task;
10+
use core::ops::Deref;
911
use macros::kunit_tests;
1012

1113
/// Asserts that a boolean expression is `true` at runtime.
@@ -177,11 +179,86 @@ macro_rules! kunit_test_suite {
177179
};
178180
}
179181

182+
/// In some cases, you need to call test-only code from outside the test case, for example, to
183+
/// create a function mock. This function can be invoked to know whether we are currently running a
184+
/// KUnit test or not.
185+
///
186+
/// # Examples
187+
///
188+
/// This example shows how a function can be mocked to return a well-known value while testing:
189+
///
190+
/// ```
191+
/// # use kernel::kunit::is_kunit_test;
192+
/// #
193+
/// fn fn_mock_example(n: i32) -> i32 {
194+
/// if is_kunit_test() {
195+
/// 100
196+
/// } else {
197+
/// n + 1
198+
/// }
199+
/// }
200+
///
201+
/// let mock_res = fn_mock_example(5);
202+
/// assert_eq!(mock_res, 100);
203+
/// ```
204+
///
205+
/// Sometimes, you don't control the code that needs to be mocked. This example shows how the
206+
/// `bindings` module can be mocked:
207+
///
208+
/// ```
209+
/// // Import our mock naming it as the real module.
210+
/// #[cfg(CONFIG_KUNIT)]
211+
/// use bindings_mock_example as bindings;
212+
///
213+
/// // This module mocks `bindings`.
214+
/// mod bindings_mock_example {
215+
/// use kernel::kunit::is_kunit_test;
216+
/// use kernel::bindings::u64_;
217+
///
218+
/// // Make the other binding functions available.
219+
/// pub(crate) use kernel::bindings::*;
220+
///
221+
/// // Mock `ktime_get_boot_fast_ns` to return a well-known value when running a KUnit test.
222+
/// pub(crate) unsafe fn ktime_get_boot_fast_ns() -> u64_ {
223+
/// if is_kunit_test() {
224+
/// 1234
225+
/// } else {
226+
/// unsafe { kernel::bindings::ktime_get_boot_fast_ns() }
227+
/// }
228+
/// }
229+
/// }
230+
///
231+
/// // This is the function we want to test. Since `bindings` has been mocked, we can use its
232+
/// // functions seamlessly.
233+
/// fn get_boot_ns() -> u64 {
234+
/// unsafe { bindings::ktime_get_boot_fast_ns() }
235+
/// }
236+
///
237+
/// let time = get_boot_ns();
238+
/// assert_eq!(time, 1234);
239+
/// ```
240+
pub fn is_kunit_test() -> bool {
241+
if cfg!(CONFIG_KUNIT) {
242+
let test = unsafe { (*Task::current().deref().0.get()).kunit_test };
243+
!test.is_null()
244+
} else {
245+
false
246+
}
247+
}
248+
180249
#[kunit_tests(rust_kernel_kunit)]
181250
mod tests {
251+
use super::*;
252+
182253
#[test]
183254
fn rust_test_kunit_kunit_tests() {
184255
let running = true;
185256
assert_eq!(running, true);
186257
}
258+
259+
#[test]
260+
fn rust_test_kunit_is_kunit_test() {
261+
let is_kunit = is_kunit_test();
262+
assert_eq!(is_kunit, true);
263+
}
187264
}

0 commit comments

Comments
 (0)