On Fri, Dec 13, 2024 at 9:10 AM David Gow <[email protected]> wrote:
>
> +/// 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.

The documentation of items use the first paragraph as a "short
description" in some places, so ideally it should be as short as
possible (e.g. one line), similar to Git commit titles.

So what about:

    /// Returns whether we are currently running a KUnit test.
    ///
    /// In some cases, you need to call test-only code from outside
the test case, for example, to
    /// create a function mock. This function allows to change
behavior depending on whether we are
    /// currently running a KUnit test or not.

I tweaked the second sentence to avoid repetition, and to take the
chance to mention "allows to change behavior" instead, since that is
what we want to achieve.

> +/// #

Nit: currently the style we use doesn't keep empty `#` lines to separate.

> +/// fn fn_mock_example(n: i32) -> i32 {
> +///     if in_kunit_test() {
> +///         100
> +///     } else {
> +///         n + 1
> +///     }
> +/// }

Early return would look better here since we really want to do
something completely different, and would avoid indentation in the
"normal code".

> +/// // This module mocks `bindings`.

This could perhaps be documentation (`///`), but either way it is fine.

> +/// mod bindings_mock_example {

Could this get a `#[cfg(CONFIG_KUNIT)]` too?

> +///         if in_kunit_test() {
> +///             1234
> +///         } else {
> +///             // SAFETY: ktime_get_boot_fast_ns() is safe to call, and 
> just returns a u64.

Formatting: `ktime_get_boot_fast_ns()` and `u64`

Perhaps emphasize with "always safe"?

Also, why does the `u64` need to be part of the safety comment?

> +///             // Additionally, this is never actually called in this 
> example, as we're in a test
> +///             // and it's mocked out.

If the function is safe to call, should we have this as part of the
`SAFETY` comment then? We can move it above, if we need to keep it, or
we could just remove it.

In any case, if the `else` is dead code, why do we have it? i.e.
shouldn't the mock just return the 1234 value? (see below)

> +/// // This is the function we want to test. Since `bindings` has been 
> mocked, we can use its
> +/// // functions seamlessly.
> +/// fn get_boot_ns() -> u64 {
> +///     bindings::ktime_get_boot_fast_ns()

I think this wouldn't work: `ktime_get_boot_fast_ns()` is unsafe when
`CONFIG_KUNIT` is disabled, so it wouldn't build for an actual user.

Unless I am missing something, mocks should keep the `unsafe` status
(i.e. in general, the signature should be kept the same), and the
`SAFETY` comment should be here, i.e. in the "normal code", not above
in the mock (we should probably mention this as a guideline in
`Documentation/rust/testing.rst` too, when the docs are added there
for this).

And by doing that, we can remove all the usage of `bindings` inside
the mocking module, and we keep the "normal code" looking as normal as
possible, i.e. we don't hide `// SAFETY` comments inside mocking
modules.

With all put together, we get something like this:

    /// ```
    /// // Import our mock naming it as the real module.
    /// #[cfg(CONFIG_KUNIT)]
    /// use bindings_mock_example as bindings;
    /// #[cfg(not(CONFIG_KUNIT))]
    /// use kernel::bindings;
    ///
    /// // This module mocks `bindings`.
    /// #[cfg(CONFIG_KUNIT)]
    /// mod bindings_mock_example {
    ///     /// Mock `ktime_get_boot_fast_ns` to return a well-known
value when running a KUnit test.
    ///     pub(crate) fn ktime_get_boot_fast_ns() -> u64 {
    ///         1234
    ///     }
    /// }
    ///
    /// // This is the function we want to test. Since `bindings` has
been mocked, we can use its
    /// // functions seamlessly.
    /// fn get_boot_ns() -> u64 {
    ///     // SAFETY: `ktime_get_boot_fast_ns()` is always safe to call.
    ///     unsafe { bindings::ktime_get_boot_fast_ns() }
    /// }
    ///
    /// let time = get_boot_ns();
    /// assert_eq!(time, 1234);
    /// ```

I added a `#[cfg(CONFIG_KUNIT)]` for the mocking module here, like for
the other example.

> +pub fn in_kunit_test() -> bool {
> +    // SAFETY: kunit_get_current_test() is always safe to call from C (it 
> has fallbacks for

Formatting: `kunit_get_current_test()`

Also, I think we should remove "from C" since it may be confusing --
or what is it trying to say here? i.e. it is always safe to call from
both C and Rust, no? Or is there something I am missing?

> +    // when KUnit is not enabled), and we're only comparing the result to 
> NULL.

Does "and we're only comparing the result to NULL" need to be part of
the safety comment? i.e. comparing a pointer is safe (and `is_null()`
too).

> +        let in_kunit = in_kunit_test();
> +        assert!(in_kunit);

I would simplify and call directly the function.

Cheers,
Miguel

Reply via email to