rust-for-linux.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Boqun Feng <boqun.feng@gmail.com>
To: David Gow <davidgow@google.com>
Cc: "Brendan Higgins" <brendan.higgins@linux.dev>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Alex Gaynor" <alex.gaynor@gmail.com>,
	"Wedson Almeida Filho" <wedsonaf@gmail.com>,
	"Gary Guo" <gary@garyguo.net>,
	"Benno Lossin" <benno.lossin@proton.me>,
	"José Expósito" <jose.exposito89@gmail.com>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	linux-kselftest@vger.kernel.org, kunit-dev@googlegroups.com,
	rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH 3/3] rust: kunit: allow to know if we are in a test
Date: Tue, 25 Jul 2023 16:34:43 -0700	[thread overview]
Message-ID: <ZMBcE8WABtx7GD2R@boqun-archlinux> (raw)
In-Reply-To: <20230720-rustbind-v1-3-c80db349e3b5@google.com>

On Thu, Jul 20, 2023 at 02:38:54PM +0800, David Gow wrote:
> From: José Expósito <jose.exposito89@gmail.com>
> 
> 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.
> 
> Reviewed-by: David Gow <davidgow@google.com>
> Signed-off-by: José Expósito <jose.exposito89@gmail.com>
> ---
>  rust/kernel/kunit.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 78 insertions(+)
> 
> diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
> index 44ea67028316..dcaac19bb108 100644
> --- a/rust/kernel/kunit.rs
> +++ b/rust/kernel/kunit.rs
> @@ -40,6 +40,8 @@ pub fn info(args: fmt::Arguments<'_>) {
>      }
>  }
>  
> +use crate::task::Task;
> +use core::ops::Deref;
>  use macros::kunit_tests;
>  
>  /// Asserts that a boolean expression is `true` at runtime.
> @@ -256,11 +258,87 @@ macro_rules! kunit_unsafe_test_suite {
>      };
>  }
>  
> +/// In some cases, you need to call test-only code from outside the test case, for example, to
> +/// create a function mock. This function can be invoked to know whether we are currently running a
> +/// KUnit test or not.
> +///
> +/// # Examples
> +///
> +/// This example shows how a function can be mocked to return a well-known value while testing:
> +///
> +/// ```
> +/// # use kernel::kunit::in_kunit_test;
> +/// #
> +/// fn fn_mock_example(n: i32) -> i32 {
> +///     if in_kunit_test() {
> +///         100
> +///     } else {
> +///         n + 1
> +///     }
> +/// }
> +///
> +/// let mock_res = fn_mock_example(5);
> +/// assert_eq!(mock_res, 100);
> +/// ```
> +///
> +/// Sometimes, you don't control the code that needs to be mocked. This example shows how the
> +/// `bindings` module can be mocked:
> +///
> +/// ```
> +/// // Import our mock naming it as the real module.
> +/// #[cfg(CONFIG_KUNIT)]
> +/// use bindings_mock_example as bindings;
> +///
> +/// // This module mocks `bindings`.
> +/// mod bindings_mock_example {
> +///     use kernel::kunit::in_kunit_test;
> +///     use kernel::bindings::u64_;
> +///
> +///     // Make the other binding functions available.
> +///     pub(crate) use kernel::bindings::*;
> +///
> +///     // Mock `ktime_get_boot_fast_ns` to return a well-known value when running a KUnit test.
> +///     pub(crate) unsafe fn ktime_get_boot_fast_ns() -> u64_ {
> +///         if in_kunit_test() {
> +///             1234
> +///         } else {
> +///             unsafe { kernel::bindings::ktime_get_boot_fast_ns() }
> +///         }
> +///     }
> +/// }
> +///
> +/// // This is the function we want to test. Since `bindings` has been mocked, we can use its
> +/// // functions seamlessly.
> +/// fn get_boot_ns() -> u64 {
> +///     unsafe { bindings::ktime_get_boot_fast_ns() }
> +/// }
> +///
> +/// let time = get_boot_ns();
> +/// assert_eq!(time, 1234);
> +/// ```
> +pub fn in_kunit_test() -> bool {
> +    if cfg!(CONFIG_KUNIT) {
> +        // SAFETY: By the type invariant, we know that `*Task::current().deref().0` is valid.
> +        let test = unsafe { (*Task::current().deref().0.get()).kunit_test };

Note here are two unsafe operations: `Task::current()` and the pointer
dereference. You can use the `current!()` macro here to avoid the first
unsafe operation here. Besides I think it'll be better if
in_kunit_test() is a safe method for `Task`? That will be easier for us
to track the usage of task_struct fields in Rust side. But I'm OK with
either way.

Regards,
Boqun

> +        !test.is_null()
> +    } else {
> +        false
> +    }
> +}
> +
>  #[kunit_tests(rust_kernel_kunit)]
>  mod tests {
> +    use super::*;
> +
>      #[test]
>      fn rust_test_kunit_kunit_tests() {
>          let running = true;
>          assert_eq!(running, true);
>      }
> +
> +    #[test]
> +    fn rust_test_kunit_in_kunit_test() {
> +        let in_kunit = in_kunit_test();
> +        assert_eq!(in_kunit, true);
> +    }
>  }
> 
> -- 
> 2.41.0.255.g8b1d071c50-goog
> 

      reply	other threads:[~2023-07-25 23:35 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-07-20  6:38 [PATCH 0/3] rust: kunit: Support KUnit tests with a user-space like syntax David Gow
2023-07-20  6:38 ` [PATCH 1/3] rust: kunit: add KUnit case and suite macros David Gow
2023-07-25 18:07   ` Boqun Feng
2023-07-20  6:38 ` [PATCH 2/3] rust: macros: add macro to easily run KUnit tests David Gow
2023-07-30 21:49   ` Boqun Feng
2023-08-01 14:44   ` Miguel Ojeda
2023-07-20  6:38 ` [PATCH 3/3] rust: kunit: allow to know if we are in a test David Gow
2023-07-25 23:34   ` Boqun Feng [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=ZMBcE8WABtx7GD2R@boqun-archlinux \
    --to=boqun.feng@gmail.com \
    --cc=alex.gaynor@gmail.com \
    --cc=benno.lossin@proton.me \
    --cc=bjorn3_gh@protonmail.com \
    --cc=brendan.higgins@linux.dev \
    --cc=davidgow@google.com \
    --cc=gary@garyguo.net \
    --cc=jose.exposito89@gmail.com \
    --cc=kunit-dev@googlegroups.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=wedsonaf@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).