Skip to content

Commit

Permalink
Merge tag 'linux-kselftest-kunit-5.12-rc1' of git://git.kernel.org/pu…
Browse files Browse the repository at this point in the history
…b/scm/linux/kernel/git/shuah/linux-kselftest

Pull KUnit updates from Shuah Khan

 - support for filtering test suites using glob from Daniel Latypov.

     "kunit_filter.glob" command line option is passed to the UML
     kernel, which currently only supports filtering by suite name.
     This support allows running different subsets of tests, e.g.

      $ ./tools/testing/kunit/kunit.py build
      $ ./tools/testing/kunit/kunit.py exec 'list*'
      $ ./tools/testing/kunit/kunit.py exec 'kunit*'

 - several fixes and cleanups also from Daniel Latypov.

* tag 'linux-kselftest-kunit-5.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest:
  kunit: tool: fix unintentional statefulness in run_kernel()
  kunit: tool: add support for filtering suites by glob
  kunit: add kunit.filter_glob cmdline option to filter suites
  kunit: don't show `1 == 1` in failed assertion messages
  kunit: make kunit_tool accept optional path to .kunitconfig fragment
  Documentation: kunit: add tips.rst for small examples
  KUnit: Docs: make start.rst example Kconfig follow style.rst
  kunit: tool: simplify kconfig is_subset_of() logic
  minor: kunit: tool: fix unit test so it can run from non-root dir
  kunit: tool: use `with open()` in unit test
  kunit: tool: stop using bare asserts in unit test
  kunit: tool: fix unit test cleanup handling
  • Loading branch information
Linus Torvalds committed Feb 22, 2021
2 parents 8021509 + 7af2914 commit 783955f
Show file tree
Hide file tree
Showing 10 changed files with 390 additions and 132 deletions.
2 changes: 2 additions & 0 deletions Documentation/dev-tools/kunit/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ KUnit - Unit Testing for the Linux Kernel
api/index
style
faq
tips

What is KUnit?
==============
Expand Down Expand Up @@ -88,6 +89,7 @@ How do I use it?
================

* :doc:`start` - for new users of KUnit
* :doc:`tips` - for short examples of best practices
* :doc:`usage` - for a more detailed explanation of KUnit features
* :doc:`api/index` - for the list of KUnit APIs used for testing
* :doc:`kunit-tool` - for more information on the kunit_tool helper script
Expand Down
7 changes: 5 additions & 2 deletions Documentation/dev-tools/kunit/start.rst
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,9 @@ Now add the following to ``drivers/misc/Kconfig``:
.. code-block:: kconfig
config MISC_EXAMPLE_TEST
bool "Test for my example"
tristate "Test for my example" if !KUNIT_ALL_TESTS
depends on MISC_EXAMPLE && KUNIT=y
default KUNIT_ALL_TESTS
and the following to ``drivers/misc/Makefile``:

Expand Down Expand Up @@ -233,5 +234,7 @@ Congrats! You just wrote your first KUnit test!

Next Steps
==========
* Check out the :doc:`usage` page for a more
* Check out the :doc:`tips` page for tips on
writing idiomatic KUnit tests.
* Optional: see the :doc:`usage` page for a more
in-depth explanation of KUnit.
115 changes: 115 additions & 0 deletions Documentation/dev-tools/kunit/tips.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
.. SPDX-License-Identifier: GPL-2.0
============================
Tips For Writing KUnit Tests
============================

Exiting early on failed expectations
------------------------------------

``KUNIT_EXPECT_EQ`` and friends will mark the test as failed and continue
execution. In some cases, it's unsafe to continue and you can use the
``KUNIT_ASSERT`` variant to exit on failure.

.. code-block:: c
void example_test_user_alloc_function(struct kunit *test)
{
void *object = alloc_some_object_for_me();
/* Make sure we got a valid pointer back. */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, object);
do_something_with_object(object);
}
Allocating memory
-----------------

Where you would use ``kzalloc``, you should prefer ``kunit_kzalloc`` instead.
KUnit will ensure the memory is freed once the test completes.

This is particularly useful since it lets you use the ``KUNIT_ASSERT_EQ``
macros to exit early from a test without having to worry about remembering to
call ``kfree``.

Example:

.. code-block:: c
void example_test_allocation(struct kunit *test)
{
char *buffer = kunit_kzalloc(test, 16, GFP_KERNEL);
/* Ensure allocation succeeded. */
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buffer);
KUNIT_ASSERT_STREQ(test, buffer, "");
}
Testing static functions
------------------------

If you don't want to expose functions or variables just for testing, one option
is to conditionally ``#include`` the test file at the end of your .c file, e.g.

.. code-block:: c
/* In my_file.c */
static int do_interesting_thing();
#ifdef CONFIG_MY_KUNIT_TEST
#include "my_kunit_test.c"
#endif
Injecting test-only code
------------------------

Similarly to the above, it can be useful to add test-specific logic.

.. code-block:: c
/* In my_file.h */
#ifdef CONFIG_MY_KUNIT_TEST
/* Defined in my_kunit_test.c */
void test_only_hook(void);
#else
void test_only_hook(void) { }
#endif
TODO(dlatypov@google.com): add an example of using ``current->kunit_test`` in
such a hook when it's not only updated for ``CONFIG_KASAN=y``.

Customizing error messages
--------------------------

Each of the ``KUNIT_EXPECT`` and ``KUNIT_ASSERT`` macros have a ``_MSG`` variant.
These take a format string and arguments to provide additional context to the automatically generated error messages.

.. code-block:: c
char some_str[41];
generate_sha1_hex_string(some_str);
/* Before. Not easy to tell why the test failed. */
KUNIT_EXPECT_EQ(test, strlen(some_str), 40);
/* After. Now we see the offending string. */
KUNIT_EXPECT_EQ_MSG(test, strlen(some_str), 40, "some_str='%s'", some_str);
Alternatively, one can take full control over the error message by using ``KUNIT_FAIL()``, e.g.

.. code-block:: c
/* Before */
KUNIT_EXPECT_EQ(test, some_setup_function(), 0);
/* After: full control over the failure message. */
if (some_setup_function())
KUNIT_FAIL(test, "Failed to setup thing for testing");
Next Steps
==========
* Optional: see the :doc:`usage` page for a more
in-depth explanation of KUnit.
1 change: 1 addition & 0 deletions lib/kunit/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

menuconfig KUNIT
tristate "KUnit - Enable support for unit tests"
select GLOB if KUNIT=y
help
Enables support for kernel unit tests (KUnit), a lightweight unit
testing and mocking framework for the Linux kernel. These tests are
Expand Down
39 changes: 33 additions & 6 deletions lib/kunit/assert.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,29 @@ void kunit_ptr_not_err_assert_format(const struct kunit_assert *assert,
}
EXPORT_SYMBOL_GPL(kunit_ptr_not_err_assert_format);

/* Checks if `text` is a literal representing `value`, e.g. "5" and 5 */
static bool is_literal(struct kunit *test, const char *text, long long value,
gfp_t gfp)
{
char *buffer;
int len;
bool ret;

len = snprintf(NULL, 0, "%lld", value);
if (strlen(text) != len)
return false;

buffer = kunit_kmalloc(test, len+1, gfp);
if (!buffer)
return false;

snprintf(buffer, len+1, "%lld", value);
ret = strncmp(buffer, text, len) == 0;

kunit_kfree(test, buffer);
return ret;
}

void kunit_binary_assert_format(const struct kunit_assert *assert,
struct string_stream *stream)
{
Expand All @@ -97,12 +120,16 @@ void kunit_binary_assert_format(const struct kunit_assert *assert,
binary_assert->left_text,
binary_assert->operation,
binary_assert->right_text);
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld\n",
binary_assert->left_text,
binary_assert->left_value);
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld",
binary_assert->right_text,
binary_assert->right_value);
if (!is_literal(stream->test, binary_assert->left_text,
binary_assert->left_value, stream->gfp))
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld\n",
binary_assert->left_text,
binary_assert->left_value);
if (!is_literal(stream->test, binary_assert->right_text,
binary_assert->right_value, stream->gfp))
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld",
binary_assert->right_text,
binary_assert->right_value);
kunit_assert_print_msg(assert, stream);
}
EXPORT_SYMBOL_GPL(kunit_binary_assert_format);
Expand Down
93 changes: 84 additions & 9 deletions lib/kunit/executor.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: GPL-2.0

#include <kunit/test.h>
#include <linux/glob.h>
#include <linux/moduleparam.h>

/*
* These symbols point to the .kunit_test_suites section and are defined in
Expand All @@ -11,14 +13,81 @@ extern struct kunit_suite * const * const __kunit_suites_end[];

#if IS_BUILTIN(CONFIG_KUNIT)

static void kunit_print_tap_header(void)
static char *filter_glob;
module_param(filter_glob, charp, 0);
MODULE_PARM_DESC(filter_glob,
"Filter which KUnit test suites run at boot-time, e.g. list*");

static struct kunit_suite * const *
kunit_filter_subsuite(struct kunit_suite * const * const subsuite)
{
int i, n = 0;
struct kunit_suite **filtered;

n = 0;
for (i = 0; subsuite[i] != NULL; ++i) {
if (glob_match(filter_glob, subsuite[i]->name))
++n;
}

if (n == 0)
return NULL;

filtered = kmalloc_array(n + 1, sizeof(*filtered), GFP_KERNEL);
if (!filtered)
return NULL;

n = 0;
for (i = 0; subsuite[i] != NULL; ++i) {
if (glob_match(filter_glob, subsuite[i]->name))
filtered[n++] = subsuite[i];
}
filtered[n] = NULL;

return filtered;
}

struct suite_set {
struct kunit_suite * const * const *start;
struct kunit_suite * const * const *end;
};

static struct suite_set kunit_filter_suites(void)
{
int i;
struct kunit_suite * const **copy, * const *filtered_subsuite;
struct suite_set filtered;

const size_t max = __kunit_suites_end - __kunit_suites_start;

if (!filter_glob) {
filtered.start = __kunit_suites_start;
filtered.end = __kunit_suites_end;
return filtered;
}

copy = kmalloc_array(max, sizeof(*filtered.start), GFP_KERNEL);
filtered.start = copy;
if (!copy) { /* won't be able to run anything, return an empty set */
filtered.end = copy;
return filtered;
}

for (i = 0; i < max; ++i) {
filtered_subsuite = kunit_filter_subsuite(__kunit_suites_start[i]);
if (filtered_subsuite)
*copy++ = filtered_subsuite;
}
filtered.end = copy;
return filtered;
}

static void kunit_print_tap_header(struct suite_set *suite_set)
{
struct kunit_suite * const * const *suites, * const *subsuite;
int num_of_suites = 0;

for (suites = __kunit_suites_start;
suites < __kunit_suites_end;
suites++)
for (suites = suite_set->start; suites < suite_set->end; suites++)
for (subsuite = *suites; *subsuite != NULL; subsuite++)
num_of_suites++;

Expand All @@ -30,12 +99,18 @@ int kunit_run_all_tests(void)
{
struct kunit_suite * const * const *suites;

kunit_print_tap_header();
struct suite_set suite_set = kunit_filter_suites();

kunit_print_tap_header(&suite_set);

for (suites = suite_set.start; suites < suite_set.end; suites++)
__kunit_test_suites_init(*suites);

for (suites = __kunit_suites_start;
suites < __kunit_suites_end;
suites++)
__kunit_test_suites_init(*suites);
if (filter_glob) { /* a copy was made of each array */
for (suites = suite_set.start; suites < suite_set.end; suites++)
kfree(*suites);
kfree(suite_set.start);
}

return 0;
}
Expand Down
Loading

0 comments on commit 783955f

Please sign in to comment.