mocking.rst: add sections on comptime and linktime redirection
Give examples of using #ifdef and __weak symbols to add a layer of
indirection to function calls.
Also show how we can combine link/compile-time indirection w/ runtime
indirection to have zero-overhead for normal builds, while still
limiting the scope of the changes.
Change-Id: Iaccc1c1708ebbf9cb2662743aaee8fd3ccb38b03
Signed-off-by: Daniel Latypov <dlatypov@google.com>
diff --git a/mocking.rst b/mocking.rst
index 2791e87..7f17c9e 100644
--- a/mocking.rst
+++ b/mocking.rst
@@ -197,32 +197,228 @@
Compile time
------------
-TODO(dlatypov@google.com): write me
+This involves using compiler directive, macros, etc. to change the code when
+compiling KUnit tests, or for your specific test. E.g. a straightforward
+approach could be
+
+.. code-block:: c
+
+ void send_data_to_hardware(const char *str)
+ {
+ #ifdef CONFIG_MY_KUNIT_TEST /* or some other, more generic check */
+ test_send_data(str);
+ return;
+ #endif
+
+ /* real implementation */
+ }
+
+
+
+This pattern is generally useful and recommended `here
+<third_party/kernel/docs/tips.html#injecting-test-only-code>`__ for injecting
+other kinds of test-only code.
+
+.. note::
+ This example makes it so that ``send_data_to_hardware()`` no longer
+ works for the whole kernel, including other tests if they happen to
+ compiled along with ``CONFIG_MY_KUNIT``, even if it's set as
+ ``CONFIG_MY_KUNIT_TEST=m`` (!). See :ref:`hybrid-approaches` for a
+ discussion on how to mitigate that.
Pros:
~~~~~
-- TODO
+- Also easy to understand.
+- No runtime overhead, unlike the option above.
Cons:
~~~~~
-- TODO
+- Requires invasive changes to the function we're trying to stub as opposed to
+ the coder-under-test itself.
+
+ - So it's not suitable if you don't own ``send_data_to_hardware()``.
+
+- We don't want to pollute normal code by doing this at a large scale.
+
+ - So it probably should only be used sparingly in narrow contexts, e.g.
+ in static functions.
+
Link time (__weak symbols)
--------------------------
-TODO(dlatypov@google.com): write me
+We can avoid refactoring to the code-under-test and only have minimal changes
+to ``send_data_to_hardware()`` by pushing the indirection into the linker.
+
+More specifically, we can make use of "weak symbols", which would allow tests
+to define their own definitions and override the original
+``send_data_to_hardware()``, e.g.
+
+.. code-block:: c
+
+ void __weak send_data_to_hardware(const char *str)
+ {
+ /* real implementation */
+ }
+
+ /* In test file */
+ /* Since the real function is __weak, we can override it here. */
+ void send_data_to_hardware(const char *str)
+ {
+ /* fake implementation */
+ }
+
+We can minimize the risk of accidentally overriding functions by using a macro
+like
+
+.. code-block:: c
+
+ /* Now we can mark functions __weak only while building tests */
+ #ifdef CONFIG_KUNIT
+ #define __mockable __weak
+ #else
+ #define __mockable
+ #endif
+
Pros:
~~~~~
-- TODO
+- No runtime overhead.
+- No changes needed to the code-under-test, ``func_under_test()``.
+- And overall, very minimal changes needed to non-test code.
Cons:
~~~~~
-- TODO
+- Initial feedback when something similar was included in the initial KUnit RFC
+ is that this is adding more complexity.
+
+ - ``ftrace`` and friends exist and allow for patching binaries.
+ - Note that this is a much more stripped-down version of what the KUnit
+ RFC called "function mocking," so it's not as big of a concern.
+
+- **Not possible to limit the scope of the redirection.** The real definition
+ is discarded by the linker.
+
+ - Can also only have one fake definition at any time.
+ - See :ref:`hybrid-approaches`.
+
+- Won't work with tests built as modules.
+
+- More complicated, harder to understand, and less explicit.
+
+ - E.g. if you forgot to include the replacement definition in the
+ compilation unit, the code will happily call the original one without
+ any warning or indication.
+ - Your test might want the real function to be called, so building your
+ test together with someone else's might cause failures if they
+ redirected it.
+
+.. _hybrid-approaches:
+
+Hybrid approaches (limiting scope)
+----------------------------------
+
+Summarizing, the main issue with the two approaches above is that they have
+effects globally and for the full life of the kernel, not just during tests.
+
+How can we get around this? Why, of course, by adding another layer of
+indirection. Using some cleverness, we can use runtime indirection while
+building tests, but not pay the cost for normal builds.
+
+Say we want to redirect/intercept calls to ``kmalloc()``. Too many callsites
+need to use it so that stubbing it out is unwise (and would break KUnit
+itself). So we can introduce a ``__weak`` wrapper around it like so:
+
+.. code-block:: c
+
+ #ifdef CONFIG_KUNIT
+ #define __mockable_wrapper __weak
+ #else /* avoid the performance overhead for real builds */
+ #define __mockable_wrapper __always_inline
+ #endif
+
+ void __mockable_wrapper kmalloc_wrapper(size_t n, gfp_t gfp)
+ {
+ return kmalloc(n, gfp);
+ }
+
+
+This adds more boilerplate, but lets us manage the scope of the code affected
+by the redirection. We can also limit the temporal scope of the redirection if
+our replacement is defined like:
+
+.. code-block:: c
+
+ /* In the test file. This is the definition that overrides the __weak version */
+ static void* (*kmalloc_ptr)(size_t n, gfp_t gfp) = kmalloc;
+ void* kmalloc_wrapper(size_t n, gfp_t gfp) {
+ return kmalloc_ptr(n, gfp);
+ }
+
+ /* elsewhere in test case: use a fake implementation and then revert back */
+ kmalloc_ptr = my_fake_kmalloc;
+ KUNIT_EXPECT_EQ(some_test_function(), 42);
+ kmalloc_ptr = kmalloc;
+
+Note that we can do the same thing using compile-time indirection
+
+.. code-block:: c
+
+ #ifdef CONFIG_MY_KUNIT_TEST /* or some other, more generic check */
+ static void* (*kmalloc_ptr)(size_t n, gfp_t gfp) = kmalloc;
+ #endif
+
+ void* kmalloc_wrapper(size_t n, gfp_t gfp) {
+ #ifdef CONFIG_MY_KUNIT_TEST
+ return kmalloc_ptr(n, gfp);
+ #endif
+ return kmalloc(n, gfp);
+ }
+
+ /* in test file, use the fake for one call */
+ kmalloc_ptr = my_fake_kmalloc;
+ KUNIT_EXPECT_EQ(some_test_function(), 42);
+ kmalloc_ptr = kmalloc;
+
+
+See :ref:`managing-state` for more details on cleanly handling state in test
+doubles. In particular, one could use "named resources" instead of global
+variables like ``kmalloc_ptr``. That approach would also be thread-safe, unlike
+this example.
+
+Pros:
+~~~~~
+
+- No runtime overhead outside of tests.
+
+ - For the link-time based approach ``__always_inline`` should eliminate
+ the potential function call overhead.
+ - With the compile-time approach, the code is unchanged unless the
+ relevant tests are being built.
+
+- Able to limit the scope of the redirection to code we care about.
+- Are also able to limit how long the redirection is in place.
+
+Cons:
+~~~~~
+
+- The code-under-test (``func_under_test()``) needs to be changed to use this
+ new wrapper, somewhat hurting legibility.
+
+- Requires a decent amount of boilerplate for every single function we might
+ want to stub.
+
+ - Probably best reserved for only a few key functions. We could have
+ shared implementations for some, e.g. ``kmalloc()``, if they're
+ flexible enough (e.g. allow assigning arbitrary function pointers
+ like in the second example).
+
+- Inherits issues of the other approach, e.g. ``__weak`` won't work with
+ modules, etc.
Binary-level (ftrace et. al)
----------------------------
@@ -239,9 +435,11 @@
- TODO
-TODO(dlatypov@google.com): include discussion on global functions/general statefulness.
TODO(dlatypov@google.com): include section on worked example use cases.
+
+.. _managing-state:
+
Storing and accessing state for fakes/mocks
===========================================