Re: [PATCH 2/2] Documentation: Add Function Redirection API docs

From: Bagas Sanjaya
Date: Thu Dec 08 2022 - 05:07:25 EST


On Thu, Dec 08, 2022 at 02:18:41PM +0800, David Gow wrote:
> From: Sadiya Kazi <sadiyakazi@xxxxxxxxxx>
>
> Added a new page (functionredirection.rst) that describes the Function
> Redirection (static stubbing) API. This page will be expanded if we add,
> for example, ftrace-based stubbing.

s/Added/Add

> diff --git a/Documentation/dev-tools/kunit/api/functionredirection.rst b/Documentation/dev-tools/kunit/api/functionredirection.rst
> new file mode 100644
> index 000000000000..fc7644dfea65
> --- /dev/null
> +++ b/Documentation/dev-tools/kunit/api/functionredirection.rst
> @@ -0,0 +1,162 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +========================
> +Function Redirection API
> +========================
> +
> +Overview
> +========
> +
> +When writing unit tests, it's important to be able to isolate the code being
> +tested from other parts of the kernel. This ensures the reliability of the test
> +(it won't be affected by external factors), reduces dependencies on specific
> +hardware or config options (making the test easier to run), and protects the
> +stability of the rest of the system (making it less likely for test-specific
> +state to interfere with the rest of the system).

Test reliability is test independence, right?

> +
> +While for some code (typically generic data structures, helpers, and toher
> +"pure function") this is trivial, for others (like device drivers, filesystems,
> +core subsystems) the code is heavily coupled with other parts of the kernel.
> +
> +This often involves global state in some way: be it global lists of devices,
> +the filesystem, or hardware state, this needs to be either carefully managed,
> +isolated, and restored, or avoided altogether by replacing access to and
> +mutation of this state with a "fake" or "mock" variant.

"... or hardware state; this needs ..."

> +
> +This can be done by refactoring the code to abstract out access to such state,
> +by introducing a layer of indirection which can use or emulate a separate set of
> +test state. However, such refactoring comes with its own costs (and undertaking
> +significant refactoring before being able to write tests is suboptimal).
> +
> +A simpler way to intercept some of the function calls is to use function
> +redirection via static stubs.
> +
> +
> +Static Stubs
> +============
> +
> +Static stubs are a way of redirecting calls to one function (the "real"
> +function) to another function (the "replacement" function).
> +
> +It works by adding a macro to the "real" function which checks to see if a test
> +is running, and if a replacement function is available. If so, that function is
> +called in place of the original.
> +
> +Using static stubs is pretty straightforward:
> +
> +1. Add the KUNIT_STATIC_STUB_REDIRECT() macro to the start of the "real"
> + function.
> +
> + This should be the first statement in the function, after any variable
> + declarations. KUNIT_STATIC_STUB_REDIRECT() takes the name of the
> + function, followed by all of the arguments passed to the real function.
> +
> + For example:
> +
> + .. code-block:: c
> +
> + void send_data_to_hardware(const char *str)
> + {
> + KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
> + /* real implementation */
> + }
> +
> +2. Write one or more replacement functions.
> +
> + These functions should have the same function signature as the real function.
> + In the event they need to access or modify test-specific state, they can use
> + kunit_get_current_test() to get a struct kunit pointer. This can then
> + be passed to the expectation/assertion macros, or used to look up KUnit
> + resources.
> +
> + For example:
> +
> + .. code-block:: c
> +
> + void fake_send_data_to_hardware(const char *str)
> + {
> + struct kunit *test = kunit_get_current_test();
> + KUNIT_EXPECT_STREQ(test, str, "Hello World!");
> + }
> +
> +3. Activate the static stub from your test.
> +
> + From within a test, the redirection can be enabled with
> + kunit_activate_static_stub(), which accepts a struct kunit pointer,
> + the real function, and the replacement function. You can call this several
> + times with different replacement functions to swap out implementations of the
> + function.
> +
> + In our example, this would be
> +
> + .. code-block:: c
> +
> + kunit_activate_static_stub(test,
> + send_data_to_hardware,
> + fake_send_data_to_hardware);
> +
> +4. Call (perhaps indirectly) the real function.
> +
> + Once the redirection is activated, any call to the real function will call
> + the replacement function instead. Such calls may be buried deep in the
> + implementation of another function, but must occur from the test's kthread.
> +
> + For example:
> +
> + .. code-block:: c
> +
> + send_data_to_hardware("Hello World!"); /* Succeeds */
> + send_data_to_hardware("Something else"); /* Fails the test. */
> +
> +5. (Optionally) disable the stub.
> +
> + When you no longer need it, the redirection can be disabled (and hence the
> + original behaviour of the 'real' function resumed) using
> + kunit_deactivate_static_stub(). If the stub is not manually deactivated, it
> + will nevertheless be disabled when the test finishes.
> +
> + For example:
> +
> + .. code-block:: c
> +
> + kunit_deactivate_static_stub(test, send_data_to_hardware);
> +
> +
> +It's also possible to use these replacement functions to test to see if a
> +function is called at all, for example:
> +
> +.. code-block:: c
> +
> + void send_data_to_hardware(const char *str)
> + {
> + KUNIT_STATIC_STUB_REDIRECT(send_data_to_hardware, str);
> + /* real implementation */
> + }
> +
> + /* In test file */
> + int times_called = 0;
> + void fake_send_data_to_hardware(const char *str)
> + {
> + /* fake implementation */
> + times_called++;
> + }
> + ...
> + /* In the test case, redirect calls for the duration of the test */
> + kunit_activate_static_stub(test, send_data_to_hardware, fake_send_data_to_hardware);
> +
> + send_data_to_hardware("hello");
> + KUNIT_EXPECT_EQ(test, times_called, 1);
> +
> + /* Can also deactivate the stub early, if wanted */
> + kunit_deactivate_static_stub(test, send_data_to_hardware);
> +
> + send_data_to_hardware("hello again");
> + KUNIT_EXPECT_EQ(test, times_called, 1);
> +
> +
> +
> +API Reference
> +=============
> +
> +.. kernel-doc:: include/kunit/static_stub.h
> + :internal:
> diff --git a/Documentation/dev-tools/kunit/api/index.rst b/Documentation/dev-tools/kunit/api/index.rst
> index 45ce04823f9f..2d8f756aab56 100644
> --- a/Documentation/dev-tools/kunit/api/index.rst
> +++ b/Documentation/dev-tools/kunit/api/index.rst
> @@ -4,17 +4,24 @@
> API Reference
> =============
> .. toctree::
> + :hidden:
>
> test
> resource
> + functionredirection
>
> -This section documents the KUnit kernel testing API. It is divided into the
> +
> +This page documents the KUnit kernel testing API. It is divided into the
> following sections:
>
> Documentation/dev-tools/kunit/api/test.rst
>
> - - documents all of the standard testing API
> + - Documents all of the standard testing API
>
> Documentation/dev-tools/kunit/api/resource.rst
>
> - - documents the KUnit resource API
> + - Documents the KUnit resource API
> +
> +Documentation/dev-tools/kunit/api/functionredirection.rst
> +
> + - Documents the KUnit Function Redirection API

Otherwise LGTM.

--
An old man doll... just what I always wanted! - Clara

Attachment: signature.asc
Description: PGP signature