Commit 9ba92dc1 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'linux-kselftest-kunit-6.5-rc1' of...

Merge tag 'linux-kselftest-kunit-6.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest

Pull KUnit updates from Shuah Khan:

 - kunit_add_action() API to defer a call until test exit

 - Update document to add kunit_add_action() usage notes

 - Changes to always run cleanup from a test kthread

 - Documentation updates to clarify cleanup usage (assertions should not
   be used in cleanup)

 - Documentation update to clearly indicate that exit functions should
   run even if init fails

 - Several fixes and enhancements to existing tests

* tag 'linux-kselftest-kunit-6.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest:
  MAINTAINERS: Add source tree entry for kunit
  Documentation: kunit: Rename references to kunit_abort()
  kunit: Move kunit_abort() call out of kunit_do_failed_assertion()
  kunit: Fix obsolete name in documentation headers (func->action)
  Documentation: Kunit: add MODULE_LICENSE to sample code
  kunit: Update kunit_print_ok_not_ok function
  kunit: Fix reporting of the skipped parameterized tests
  kunit/test: Add example test showing parameterized testing
  Documentation: kunit: Add usage notes for kunit_add_action()
  kunit: kmalloc_array: Use kunit_add_action()
  kunit: executor_test: Use kunit_add_action()
  kunit: Add kunit_add_action() to defer a call until test exit
  kunit: example: Provide example exit functions
  Documentation: kunit: Warn that exit functions run even if init fails
  Documentation: kunit: Note that assertions should not be used in cleanup
  kunit: Always run cleanup from a test kthread
  Documentation: kunit: Modular tests should not depend on KUNIT=y
  kunit: tool: undo type subscripts for subprocess.Popen
parents b19edac5 2e668335
...@@ -119,9 +119,9 @@ All expectations/assertions are formatted as: ...@@ -119,9 +119,9 @@ All expectations/assertions are formatted as:
terminated immediately. terminated immediately.
- Assertions call the function: - Assertions call the function:
``void __noreturn kunit_abort(struct kunit *)``. ``void __noreturn __kunit_abort(struct kunit *)``.
- ``kunit_abort`` calls the function: - ``__kunit_abort`` calls the function:
``void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch)``. ``void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch)``.
- ``kunit_try_catch_throw`` calls the function: - ``kunit_try_catch_throw`` calls the function:
......
...@@ -250,15 +250,20 @@ Now we are ready to write the test cases. ...@@ -250,15 +250,20 @@ Now we are ready to write the test cases.
}; };
kunit_test_suite(misc_example_test_suite); kunit_test_suite(misc_example_test_suite);
MODULE_LICENSE("GPL");
2. Add the following lines to ``drivers/misc/Kconfig``: 2. Add the following lines to ``drivers/misc/Kconfig``:
.. code-block:: kconfig .. code-block:: kconfig
config MISC_EXAMPLE_TEST config MISC_EXAMPLE_TEST
tristate "Test for my example" if !KUNIT_ALL_TESTS tristate "Test for my example" if !KUNIT_ALL_TESTS
depends on MISC_EXAMPLE && KUNIT=y depends on MISC_EXAMPLE && KUNIT
default KUNIT_ALL_TESTS default KUNIT_ALL_TESTS
Note: If your test does not support being built as a loadable module (which is
discouraged), replace tristate by bool, and depend on KUNIT=y instead of KUNIT.
3. Add the following lines to ``drivers/misc/Makefile``: 3. Add the following lines to ``drivers/misc/Makefile``:
.. code-block:: make .. code-block:: make
......
...@@ -121,6 +121,12 @@ there's an allocation error. ...@@ -121,6 +121,12 @@ there's an allocation error.
``return`` so they only work from the test function. In KUnit, we stop the ``return`` so they only work from the test function. In KUnit, we stop the
current kthread on failure, so you can call them from anywhere. current kthread on failure, so you can call them from anywhere.
.. note::
Warning: There is an exception to the above rule. You shouldn't use assertions
in the suite's exit() function, or in the free function for a resource. These
run when a test is shutting down, and an assertion here prevents further
cleanup code from running, potentially leading to a memory leak.
Customizing error messages Customizing error messages
-------------------------- --------------------------
...@@ -160,7 +166,12 @@ many similar tests. In order to reduce duplication in these closely related ...@@ -160,7 +166,12 @@ many similar tests. In order to reduce duplication in these closely related
tests, most unit testing frameworks (including KUnit) provide the concept of a tests, most unit testing frameworks (including KUnit) provide the concept of a
*test suite*. A test suite is a collection of test cases for a unit of code *test suite*. A test suite is a collection of test cases for a unit of code
with optional setup and teardown functions that run before/after the whole with optional setup and teardown functions that run before/after the whole
suite and/or every test case. For example: suite and/or every test case.
.. note::
A test case will only run if it is associated with a test suite.
For example:
.. code-block:: c .. code-block:: c
...@@ -190,7 +201,10 @@ after everything else. ``kunit_test_suite(example_test_suite)`` registers the ...@@ -190,7 +201,10 @@ after everything else. ``kunit_test_suite(example_test_suite)`` registers the
test suite with the KUnit test framework. test suite with the KUnit test framework.
.. note:: .. note::
A test case will only run if it is associated with a test suite. The ``exit`` and ``suite_exit`` functions will run even if ``init`` or
``suite_init`` fail. Make sure that they can handle any inconsistent
state which may result from ``init`` or ``suite_init`` encountering errors
or exiting early.
``kunit_test_suite(...)`` is a macro which tells the linker to put the ``kunit_test_suite(...)`` is a macro which tells the linker to put the
specified test suite in a special linker section so that it can be run by KUnit specified test suite in a special linker section so that it can be run by KUnit
...@@ -601,6 +615,57 @@ For example: ...@@ -601,6 +615,57 @@ For example:
KUNIT_ASSERT_STREQ(test, buffer, ""); KUNIT_ASSERT_STREQ(test, buffer, "");
} }
Registering Cleanup Actions
---------------------------
If you need to perform some cleanup beyond simple use of ``kunit_kzalloc``,
you can register a custom "deferred action", which is a cleanup function
run when the test exits (whether cleanly, or via a failed assertion).
Actions are simple functions with no return value, and a single ``void*``
context argument, and fulfill the same role as "cleanup" functions in Python
and Go tests, "defer" statements in languages which support them, and
(in some cases) destructors in RAII languages.
These are very useful for unregistering things from global lists, closing
files or other resources, or freeing resources.
For example:
.. code-block:: C
static void cleanup_device(void *ctx)
{
struct device *dev = (struct device *)ctx;
device_unregister(dev);
}
void example_device_test(struct kunit *test)
{
struct my_device dev;
device_register(&dev);
kunit_add_action(test, &cleanup_device, &dev);
}
Note that, for functions like device_unregister which only accept a single
pointer-sized argument, it's possible to directly cast that function to
a ``kunit_action_t`` rather than writing a wrapper function, for example:
.. code-block:: C
kunit_add_action(test, (kunit_action_t *)&device_unregister, &dev);
``kunit_add_action`` can fail if, for example, the system is out of memory.
You can use ``kunit_add_action_or_reset`` instead which runs the action
immediately if it cannot be deferred.
If you need more control over when the cleanup function is called, you
can trigger it early using ``kunit_release_action``, or cancel it entirely
with ``kunit_remove_action``.
Testing Static Functions Testing Static Functions
------------------------ ------------------------
......
...@@ -11356,6 +11356,8 @@ L: linux-kselftest@vger.kernel.org ...@@ -11356,6 +11356,8 @@ L: linux-kselftest@vger.kernel.org
L: kunit-dev@googlegroups.com L: kunit-dev@googlegroups.com
S: Maintained S: Maintained
W: https://google.github.io/kunit-docs/third_party/kernel/docs/ W: https://google.github.io/kunit-docs/third_party/kernel/docs/
T: git git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit
T: git git://git.kernel.org/pub/scm/linux/kernel/git/shuah/linux-kselftest.git kunit-fixes
F: Documentation/dev-tools/kunit/ F: Documentation/dev-tools/kunit/
F: include/kunit/ F: include/kunit/
F: lib/kunit/ F: lib/kunit/
......
...@@ -387,4 +387,96 @@ static inline int kunit_destroy_named_resource(struct kunit *test, ...@@ -387,4 +387,96 @@ static inline int kunit_destroy_named_resource(struct kunit *test,
*/ */
void kunit_remove_resource(struct kunit *test, struct kunit_resource *res); void kunit_remove_resource(struct kunit *test, struct kunit_resource *res);
/* A 'deferred action' function to be used with kunit_add_action. */
typedef void (kunit_action_t)(void *);
/**
* kunit_add_action() - Call a function when the test ends.
* @test: Test case to associate the action with.
* @action: The function to run on test exit
* @ctx: Data passed into @func
*
* Defer the execution of a function until the test exits, either normally or
* due to a failure. @ctx is passed as additional context. All functions
* registered with kunit_add_action() will execute in the opposite order to that
* they were registered in.
*
* This is useful for cleaning up allocated memory and resources, as these
* functions are called even if the test aborts early due to, e.g., a failed
* assertion.
*
* See also: devm_add_action() for the devres equivalent.
*
* Returns:
* 0 on success, an error if the action could not be deferred.
*/
int kunit_add_action(struct kunit *test, kunit_action_t *action, void *ctx);
/**
* kunit_add_action_or_reset() - Call a function when the test ends.
* @test: Test case to associate the action with.
* @action: The function to run on test exit
* @ctx: Data passed into @func
*
* Defer the execution of a function until the test exits, either normally or
* due to a failure. @ctx is passed as additional context. All functions
* registered with kunit_add_action() will execute in the opposite order to that
* they were registered in.
*
* This is useful for cleaning up allocated memory and resources, as these
* functions are called even if the test aborts early due to, e.g., a failed
* assertion.
*
* If the action cannot be created (e.g., due to the system being out of memory),
* then action(ctx) will be called immediately, and an error will be returned.
*
* See also: devm_add_action_or_reset() for the devres equivalent.
*
* Returns:
* 0 on success, an error if the action could not be deferred.
*/
int kunit_add_action_or_reset(struct kunit *test, kunit_action_t *action,
void *ctx);
/**
* kunit_remove_action() - Cancel a matching deferred action.
* @test: Test case the action is associated with.
* @action: The deferred function to cancel.
* @ctx: The context passed to the deferred function to trigger.
*
* Prevent an action deferred via kunit_add_action() from executing when the
* test terminates.
*
* If the function/context pair was deferred multiple times, only the most
* recent one will be cancelled.
*
* See also: devm_remove_action() for the devres equivalent.
*/
void kunit_remove_action(struct kunit *test,
kunit_action_t *action,
void *ctx);
/**
* kunit_release_action() - Run a matching action call immediately.
* @test: Test case the action is associated with.
* @action: The deferred function to trigger.
* @ctx: The context passed to the deferred function to trigger.
*
* Execute a function deferred via kunit_add_action()) immediately, rather than
* when the test ends.
*
* If the function/context pair was deferred multiple times, it will only be
* executed once here. The most recent deferral will no longer execute when
* the test ends.
*
* kunit_release_action(test, func, ctx);
* is equivalent to
* func(ctx);
* kunit_remove_action(test, func, ctx);
*
* See also: devm_release_action() for the devres equivalent.
*/
void kunit_release_action(struct kunit *test,
kunit_action_t *action,
void *ctx);
#endif /* _KUNIT_RESOURCE_H */ #endif /* _KUNIT_RESOURCE_H */
...@@ -47,6 +47,7 @@ struct kunit; ...@@ -47,6 +47,7 @@ struct kunit;
* sub-subtest. See the "Subtests" section in * sub-subtest. See the "Subtests" section in
* https://node-tap.org/tap-protocol/ * https://node-tap.org/tap-protocol/
*/ */
#define KUNIT_INDENT_LEN 4
#define KUNIT_SUBTEST_INDENT " " #define KUNIT_SUBTEST_INDENT " "
#define KUNIT_SUBSUBTEST_INDENT " " #define KUNIT_SUBSUBTEST_INDENT " "
...@@ -168,6 +169,9 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) ...@@ -168,6 +169,9 @@ static inline char *kunit_status_to_ok_not_ok(enum kunit_status status)
* test case, similar to the notion of a *test fixture* or a *test class* * test case, similar to the notion of a *test fixture* or a *test class*
* in other unit testing frameworks like JUnit or Googletest. * in other unit testing frameworks like JUnit or Googletest.
* *
* Note that @exit and @suite_exit will run even if @init or @suite_init
* fail: make sure they can handle any inconsistent state which may result.
*
* Every &struct kunit_case must be associated with a kunit_suite for KUnit * Every &struct kunit_case must be associated with a kunit_suite for KUnit
* to run it. * to run it.
*/ */
...@@ -321,8 +325,11 @@ enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite); ...@@ -321,8 +325,11 @@ enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite);
* @gfp: flags passed to underlying kmalloc(). * @gfp: flags passed to underlying kmalloc().
* *
* Just like `kmalloc_array(...)`, except the allocation is managed by the test case * Just like `kmalloc_array(...)`, except the allocation is managed by the test case
* and is automatically cleaned up after the test case concludes. See &struct * and is automatically cleaned up after the test case concludes. See kunit_add_action()
* kunit_resource for more information. * for more information.
*
* Note that some internal context data is also allocated with GFP_KERNEL,
* regardless of the gfp passed in.
*/ */
void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp); void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp);
...@@ -333,6 +340,9 @@ void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp); ...@@ -333,6 +340,9 @@ void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp);
* @gfp: flags passed to underlying kmalloc(). * @gfp: flags passed to underlying kmalloc().
* *
* See kmalloc() and kunit_kmalloc_array() for more information. * See kmalloc() and kunit_kmalloc_array() for more information.
*
* Note that some internal context data is also allocated with GFP_KERNEL,
* regardless of the gfp passed in.
*/ */
static inline void *kunit_kmalloc(struct kunit *test, size_t size, gfp_t gfp) static inline void *kunit_kmalloc(struct kunit *test, size_t size, gfp_t gfp)
{ {
...@@ -472,7 +482,9 @@ void __printf(2, 3) kunit_log_append(char *log, const char *fmt, ...); ...@@ -472,7 +482,9 @@ void __printf(2, 3) kunit_log_append(char *log, const char *fmt, ...);
*/ */
#define KUNIT_SUCCEED(test) do {} while (0) #define KUNIT_SUCCEED(test) do {} while (0)
void kunit_do_failed_assertion(struct kunit *test, void __noreturn __kunit_abort(struct kunit *test);
void __kunit_do_failed_assertion(struct kunit *test,
const struct kunit_loc *loc, const struct kunit_loc *loc,
enum kunit_assert_type type, enum kunit_assert_type type,
const struct kunit_assert *assert, const struct kunit_assert *assert,
...@@ -482,13 +494,15 @@ void kunit_do_failed_assertion(struct kunit *test, ...@@ -482,13 +494,15 @@ void kunit_do_failed_assertion(struct kunit *test,
#define _KUNIT_FAILED(test, assert_type, assert_class, assert_format, INITIALIZER, fmt, ...) do { \ #define _KUNIT_FAILED(test, assert_type, assert_class, assert_format, INITIALIZER, fmt, ...) do { \
static const struct kunit_loc __loc = KUNIT_CURRENT_LOC; \ static const struct kunit_loc __loc = KUNIT_CURRENT_LOC; \
const struct assert_class __assertion = INITIALIZER; \ const struct assert_class __assertion = INITIALIZER; \
kunit_do_failed_assertion(test, \ __kunit_do_failed_assertion(test, \
&__loc, \ &__loc, \
assert_type, \ assert_type, \
&__assertion.assert, \ &__assertion.assert, \
assert_format, \ assert_format, \
fmt, \ fmt, \
##__VA_ARGS__); \ ##__VA_ARGS__); \
if (assert_type == KUNIT_ASSERTION) \
__kunit_abort(test); \
} while (0) } while (0)
......
...@@ -125,11 +125,6 @@ kunit_test_suites(&executor_test_suite); ...@@ -125,11 +125,6 @@ kunit_test_suites(&executor_test_suite);
/* Test helpers */ /* Test helpers */
static void kfree_res_free(struct kunit_resource *res)
{
kfree(res->data);
}
/* Use the resource API to register a call to kfree(to_free). /* Use the resource API to register a call to kfree(to_free).
* Since we never actually use the resource, it's safe to use on const data. * Since we never actually use the resource, it's safe to use on const data.
*/ */
...@@ -138,7 +133,9 @@ static void kfree_at_end(struct kunit *test, const void *to_free) ...@@ -138,7 +133,9 @@ static void kfree_at_end(struct kunit *test, const void *to_free)
/* kfree() handles NULL already, but avoid allocating a no-op cleanup. */ /* kfree() handles NULL already, but avoid allocating a no-op cleanup. */
if (IS_ERR_OR_NULL(to_free)) if (IS_ERR_OR_NULL(to_free))
return; return;
kunit_alloc_resource(test, NULL, kfree_res_free, GFP_KERNEL,
kunit_add_action(test,
(kunit_action_t *)kfree,
(void *)to_free); (void *)to_free);
} }
......
...@@ -41,6 +41,16 @@ static int example_test_init(struct kunit *test) ...@@ -41,6 +41,16 @@ static int example_test_init(struct kunit *test)
return 0; return 0;
} }
/*
* This is run once after each test case, see the comment on
* example_test_suite for more information.
*/
static void example_test_exit(struct kunit *test)
{
kunit_info(test, "cleaning up\n");
}
/* /*
* This is run once before all test cases in the suite. * This is run once before all test cases in the suite.
* See the comment on example_test_suite for more information. * See the comment on example_test_suite for more information.
...@@ -52,6 +62,16 @@ static int example_test_init_suite(struct kunit_suite *suite) ...@@ -52,6 +62,16 @@ static int example_test_init_suite(struct kunit_suite *suite)
return 0; return 0;
} }
/*
* This is run once after all test cases in the suite.
* See the comment on example_test_suite for more information.
*/
static void example_test_exit_suite(struct kunit_suite *suite)
{
kunit_info(suite, "exiting suite\n");
}
/* /*
* This test should always be skipped. * This test should always be skipped.
*/ */
...@@ -167,6 +187,39 @@ static void example_static_stub_test(struct kunit *test) ...@@ -167,6 +187,39 @@ static void example_static_stub_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, add_one(1), 2); KUNIT_EXPECT_EQ(test, add_one(1), 2);
} }
static const struct example_param {
int value;
} example_params_array[] = {
{ .value = 2, },
{ .value = 1, },
{ .value = 0, },
};
static void example_param_get_desc(const struct example_param *p, char *desc)
{
snprintf(desc, KUNIT_PARAM_DESC_SIZE, "example value %d", p->value);
}
KUNIT_ARRAY_PARAM(example, example_params_array, example_param_get_desc);
/*
* This test shows the use of params.
*/
static void example_params_test(struct kunit *test)
{
const struct example_param *param = test->param_value;
/* By design, param pointer will not be NULL */
KUNIT_ASSERT_NOT_NULL(test, param);
/* Test can be skipped on unsupported param values */
if (!param->value)
kunit_skip(test, "unsupported param value");
/* You can use param values for parameterized testing */
KUNIT_EXPECT_EQ(test, param->value % param->value, 0);
}
/* /*
* Here we make a list of all the test cases we want to add to the test suite * Here we make a list of all the test cases we want to add to the test suite
* below. * below.
...@@ -183,6 +236,7 @@ static struct kunit_case example_test_cases[] = { ...@@ -183,6 +236,7 @@ static struct kunit_case example_test_cases[] = {
KUNIT_CASE(example_mark_skipped_test), KUNIT_CASE(example_mark_skipped_test),
KUNIT_CASE(example_all_expect_macros_test), KUNIT_CASE(example_all_expect_macros_test),
KUNIT_CASE(example_static_stub_test), KUNIT_CASE(example_static_stub_test),
KUNIT_CASE_PARAM(example_params_test, example_gen_params),
{} {}
}; };
...@@ -211,7 +265,9 @@ static struct kunit_case example_test_cases[] = { ...@@ -211,7 +265,9 @@ static struct kunit_case example_test_cases[] = {
static struct kunit_suite example_test_suite = { static struct kunit_suite example_test_suite = {
.name = "example", .name = "example",
.init = example_test_init, .init = example_test_init,
.exit = example_test_exit,
.suite_init = example_test_init_suite, .suite_init = example_test_init_suite,
.suite_exit = example_test_exit_suite,
.test_cases = example_test_cases, .test_cases = example_test_cases,
}; };
......
...@@ -112,7 +112,7 @@ struct kunit_test_resource_context { ...@@ -112,7 +112,7 @@ struct kunit_test_resource_context {
struct kunit test; struct kunit test;
bool is_resource_initialized; bool is_resource_initialized;
int allocate_order[2]; int allocate_order[2];
int free_order[2]; int free_order[4];
}; };
static int fake_resource_init(struct kunit_resource *res, void *context) static int fake_resource_init(struct kunit_resource *res, void *context)
...@@ -403,6 +403,88 @@ static void kunit_resource_test_named(struct kunit *test) ...@@ -403,6 +403,88 @@ static void kunit_resource_test_named(struct kunit *test)
KUNIT_EXPECT_TRUE(test, list_empty(&test->resources)); KUNIT_EXPECT_TRUE(test, list_empty(&test->resources));
} }
static void increment_int(void *ctx)
{
int *i = (int *)ctx;
(*i)++;
}
static void kunit_resource_test_action(struct kunit *test)
{
int num_actions = 0;
kunit_add_action(test, increment_int, &num_actions);
KUNIT_EXPECT_EQ(test, num_actions, 0);
kunit_cleanup(test);
KUNIT_EXPECT_EQ(test, num_actions, 1);
/* Once we've cleaned up, the action queue is empty. */
kunit_cleanup(test);
KUNIT_EXPECT_EQ(test, num_actions, 1);
/* Check the same function can be deferred multiple times. */
kunit_add_action(test, increment_int, &num_actions);
kunit_add_action(test, increment_int, &num_actions);
kunit_cleanup(test);
KUNIT_EXPECT_EQ(test, num_actions, 3);
}
static void kunit_resource_test_remove_action(struct kunit *test)
{
int num_actions = 0;
kunit_add_action(test, increment_int, &num_actions);
KUNIT_EXPECT_EQ(test, num_actions, 0);
kunit_remove_action(test, increment_int, &num_actions);
kunit_cleanup(test);
KUNIT_EXPECT_EQ(test, num_actions, 0);
}
static void kunit_resource_test_release_action(struct kunit *test)
{
int num_actions = 0;
kunit_add_action(test, increment_int, &num_actions);
KUNIT_EXPECT_EQ(test, num_actions, 0);
/* Runs immediately on trigger. */
kunit_release_action(test, increment_int, &num_actions);
KUNIT_EXPECT_EQ(test, num_actions, 1);
/* Doesn't run again on test exit. */
kunit_cleanup(test);
KUNIT_EXPECT_EQ(test, num_actions, 1);
}
static void action_order_1(void *ctx)
{
struct kunit_test_resource_context *res_ctx = (struct kunit_test_resource_context *)ctx;
KUNIT_RESOURCE_TEST_MARK_ORDER(res_ctx, free_order, 1);
kunit_log(KERN_INFO, current->kunit_test, "action_order_1");
}
static void action_order_2(void *ctx)
{
struct kunit_test_resource_context *res_ctx = (struct kunit_test_resource_context *)ctx;
KUNIT_RESOURCE_TEST_MARK_ORDER(res_ctx, free_order, 2);
kunit_log(KERN_INFO, current->kunit_test, "action_order_2");
}
static void kunit_resource_test_action_ordering(struct kunit *test)
{
struct kunit_test_resource_context *ctx = test->priv;
kunit_add_action(test, action_order_1, ctx);
kunit_add_action(test, action_order_2, ctx);
kunit_add_action(test, action_order_1, ctx);
kunit_add_action(test, action_order_2, ctx);
kunit_remove_action(test, action_order_1, ctx);
kunit_release_action(test, action_order_2, ctx);
kunit_cleanup(test);
/* [2 is triggered] [2], [(1 is cancelled)] [1] */
KUNIT_EXPECT_EQ(test, ctx->free_order[0], 2);
KUNIT_EXPECT_EQ(test, ctx->free_order[1], 2);
KUNIT_EXPECT_EQ(test, ctx->free_order[2], 1);
}
static int kunit_resource_test_init(struct kunit *test) static int kunit_resource_test_init(struct kunit *test)
{ {
struct kunit_test_resource_context *ctx = struct kunit_test_resource_context *ctx =
...@@ -434,6 +516,10 @@ static struct kunit_case kunit_resource_test_cases[] = { ...@@ -434,6 +516,10 @@ static struct kunit_case kunit_resource_test_cases[] = {
KUNIT_CASE(kunit_resource_test_proper_free_ordering), KUNIT_CASE(kunit_resource_test_proper_free_ordering),
KUNIT_CASE(kunit_resource_test_static), KUNIT_CASE(kunit_resource_test_static),
KUNIT_CASE(kunit_resource_test_named), KUNIT_CASE(kunit_resource_test_named),
KUNIT_CASE(kunit_resource_test_action),
KUNIT_CASE(kunit_resource_test_remove_action),
KUNIT_CASE(kunit_resource_test_release_action),
KUNIT_CASE(kunit_resource_test_action_ordering),
{} {}
}; };
......
...@@ -77,3 +77,102 @@ int kunit_destroy_resource(struct kunit *test, kunit_resource_match_t match, ...@@ -77,3 +77,102 @@ int kunit_destroy_resource(struct kunit *test, kunit_resource_match_t match,
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(kunit_destroy_resource); EXPORT_SYMBOL_GPL(kunit_destroy_resource);
struct kunit_action_ctx {
struct kunit_resource res;
kunit_action_t *func;
void *ctx;
};
static void __kunit_action_free(struct kunit_resource *res)
{
struct kunit_action_ctx *action_ctx = container_of(res, struct kunit_action_ctx, res);
action_ctx->func(action_ctx->ctx);
}
int kunit_add_action(struct kunit *test, void (*action)(void *), void *ctx)
{
struct kunit_action_ctx *action_ctx;
KUNIT_ASSERT_NOT_NULL_MSG(test, action, "Tried to action a NULL function!");
action_ctx = kzalloc(sizeof(*action_ctx), GFP_KERNEL);
if (!action_ctx)
return -ENOMEM;
action_ctx->func = action;
action_ctx->ctx = ctx;
action_ctx->res.should_kfree = true;
/* As init is NULL, this cannot fail. */
__kunit_add_resource(test, NULL, __kunit_action_free, &action_ctx->res, action_ctx);
return 0;
}
EXPORT_SYMBOL_GPL(kunit_add_action);
int kunit_add_action_or_reset(struct kunit *test, void (*action)(void *),
void *ctx)
{
int res = kunit_add_action(test, action, ctx);
if (res)
action(ctx);
return res;
}
EXPORT_SYMBOL_GPL(kunit_add_action_or_reset);
static bool __kunit_action_match(struct kunit *test,
struct kunit_resource *res, void *match_data)
{
struct kunit_action_ctx *match_ctx = (struct kunit_action_ctx *)match_data;
struct kunit_action_ctx *res_ctx = container_of(res, struct kunit_action_ctx, res);
/* Make sure this is a free function. */
if (res->free != __kunit_action_free)
return false;
/* Both the function and context data should match. */
return (match_ctx->func == res_ctx->func) && (match_ctx->ctx == res_ctx->ctx);
}
void kunit_remove_action(struct kunit *test,
kunit_action_t *action,
void *ctx)
{
struct kunit_action_ctx match_ctx;
struct kunit_resource *res;
match_ctx.func = action;
match_ctx.ctx = ctx;
res = kunit_find_resource(test, __kunit_action_match, &match_ctx);
if (res) {
/* Remove the free function so we don't run the action. */
res->free = NULL;
kunit_remove_resource(test, res);
kunit_put_resource(res);
}
}
EXPORT_SYMBOL_GPL(kunit_remove_action);
void kunit_release_action(struct kunit *test,
kunit_action_t *action,
void *ctx)
{
struct kunit_action_ctx match_ctx;
struct kunit_resource *res;
match_ctx.func = action;
match_ctx.ctx = ctx;
res = kunit_find_resource(test, __kunit_action_match, &match_ctx);
if (res) {
kunit_remove_resource(test, res);
/* We have to put() this here, else free won't be called. */
kunit_put_resource(res);
}
}
EXPORT_SYMBOL_GPL(kunit_release_action);
...@@ -185,16 +185,28 @@ static void kunit_print_suite_start(struct kunit_suite *suite) ...@@ -185,16 +185,28 @@ static void kunit_print_suite_start(struct kunit_suite *suite)
kunit_suite_num_test_cases(suite)); kunit_suite_num_test_cases(suite));
} }
static void kunit_print_ok_not_ok(void *test_or_suite, /* Currently supported test levels */
bool is_test, enum {
KUNIT_LEVEL_SUITE = 0,
KUNIT_LEVEL_CASE,
KUNIT_LEVEL_CASE_PARAM,
};
static void kunit_print_ok_not_ok(struct kunit *test,
unsigned int test_level,
enum kunit_status status, enum kunit_status status,
size_t test_number, size_t test_number,
const char *description, const char *description,
const char *directive) const char *directive)
{ {
struct kunit_suite *suite = is_test ? NULL : test_or_suite;
struct kunit *test = is_test ? test_or_suite : NULL;
const char *directive_header = (status == KUNIT_SKIPPED) ? " # SKIP " : ""; const char *directive_header = (status == KUNIT_SKIPPED) ? " # SKIP " : "";
const char *directive_body = (status == KUNIT_SKIPPED) ? directive : "";
/*
* When test is NULL assume that results are from the suite
* and today suite results are expected at level 0 only.
*/
WARN(!test && test_level, "suite test level can't be %u!\n", test_level);
/* /*
* We do not log the test suite results as doing so would * We do not log the test suite results as doing so would
...@@ -203,17 +215,18 @@ static void kunit_print_ok_not_ok(void *test_or_suite, ...@@ -203,17 +215,18 @@ static void kunit_print_ok_not_ok(void *test_or_suite,
* separately seq_printf() the suite results for the debugfs * separately seq_printf() the suite results for the debugfs
* representation. * representation.
*/ */
if (suite) if (!test)
pr_info("%s %zd %s%s%s\n", pr_info("%s %zd %s%s%s\n",
kunit_status_to_ok_not_ok(status), kunit_status_to_ok_not_ok(status),
test_number, description, directive_header, test_number, description, directive_header,
(status == KUNIT_SKIPPED) ? directive : ""); directive_body);
else else
kunit_log(KERN_INFO, test, kunit_log(KERN_INFO, test,
KUNIT_SUBTEST_INDENT "%s %zd %s%s%s", "%*s%s %zd %s%s%s",
KUNIT_INDENT_LEN * test_level, "",
kunit_status_to_ok_not_ok(status), kunit_status_to_ok_not_ok(status),
test_number, description, directive_header, test_number, description, directive_header,
(status == KUNIT_SKIPPED) ? directive : ""); directive_body);
} }
enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite) enum kunit_status kunit_suite_has_succeeded(struct kunit_suite *suite)
...@@ -239,7 +252,7 @@ static size_t kunit_suite_counter = 1; ...@@ -239,7 +252,7 @@ static size_t kunit_suite_counter = 1;
static void kunit_print_suite_end(struct kunit_suite *suite) static void kunit_print_suite_end(struct kunit_suite *suite)
{ {
kunit_print_ok_not_ok((void *)suite, false, kunit_print_ok_not_ok(NULL, KUNIT_LEVEL_SUITE,
kunit_suite_has_succeeded(suite), kunit_suite_has_succeeded(suite),
kunit_suite_counter++, kunit_suite_counter++,
suite->name, suite->name,
...@@ -310,7 +323,7 @@ static void kunit_fail(struct kunit *test, const struct kunit_loc *loc, ...@@ -310,7 +323,7 @@ static void kunit_fail(struct kunit *test, const struct kunit_loc *loc,
string_stream_destroy(stream); string_stream_destroy(stream);
} }
static void __noreturn kunit_abort(struct kunit *test) void __noreturn __kunit_abort(struct kunit *test)
{ {
kunit_try_catch_throw(&test->try_catch); /* Does not return. */ kunit_try_catch_throw(&test->try_catch); /* Does not return. */
...@@ -322,8 +335,9 @@ static void __noreturn kunit_abort(struct kunit *test) ...@@ -322,8 +335,9 @@ static void __noreturn kunit_abort(struct kunit *test)
*/ */
WARN_ONCE(true, "Throw could not abort from test!\n"); WARN_ONCE(true, "Throw could not abort from test!\n");
} }
EXPORT_SYMBOL_GPL(__kunit_abort);
void kunit_do_failed_assertion(struct kunit *test, void __kunit_do_failed_assertion(struct kunit *test,
const struct kunit_loc *loc, const struct kunit_loc *loc,
enum kunit_assert_type type, enum kunit_assert_type type,
const struct kunit_assert *assert, const struct kunit_assert *assert,
...@@ -340,11 +354,8 @@ void kunit_do_failed_assertion(struct kunit *test, ...@@ -340,11 +354,8 @@ void kunit_do_failed_assertion(struct kunit *test,
kunit_fail(test, loc, type, assert, assert_format, &message); kunit_fail(test, loc, type, assert, assert_format, &message);
va_end(args); va_end(args);
if (type == KUNIT_ASSERTION)
kunit_abort(test);
} }
EXPORT_SYMBOL_GPL(kunit_do_failed_assertion); EXPORT_SYMBOL_GPL(__kunit_do_failed_assertion);
void kunit_init_test(struct kunit *test, const char *name, char *log) void kunit_init_test(struct kunit *test, const char *name, char *log)
{ {
...@@ -419,15 +430,54 @@ static void kunit_try_run_case(void *data) ...@@ -419,15 +430,54 @@ static void kunit_try_run_case(void *data)
* thread will resume control and handle any necessary clean up. * thread will resume control and handle any necessary clean up.
*/ */
kunit_run_case_internal(test, suite, test_case); kunit_run_case_internal(test, suite, test_case);
/* This line may never be reached. */ }
static void kunit_try_run_case_cleanup(void *data)
{
struct kunit_try_catch_context *ctx = data;
struct kunit *test = ctx->test;
struct kunit_suite *suite = ctx->suite;
current->kunit_test = test;
kunit_run_case_cleanup(test, suite); kunit_run_case_cleanup(test, suite);
} }
static void kunit_catch_run_case_cleanup(void *data)
{
struct kunit_try_catch_context *ctx = data;
struct kunit *test = ctx->test;
int try_exit_code = kunit_try_catch_get_result(&test->try_catch);
/* It is always a failure if cleanup aborts. */
kunit_set_failure(test);
if (try_exit_code) {
/*
* Test case could not finish, we have no idea what state it is
* in, so don't do clean up.
*/
if (try_exit_code == -ETIMEDOUT) {
kunit_err(test, "test case cleanup timed out\n");
/*
* Unknown internal error occurred preventing test case from
* running, so there is nothing to clean up.
*/
} else {
kunit_err(test, "internal error occurred during test case cleanup: %d\n",
try_exit_code);
}
return;
}
kunit_err(test, "test aborted during cleanup. continuing without cleaning up\n");
}
static void kunit_catch_run_case(void *data) static void kunit_catch_run_case(void *data)
{ {
struct kunit_try_catch_context *ctx = data; struct kunit_try_catch_context *ctx = data;
struct kunit *test = ctx->test; struct kunit *test = ctx->test;
struct kunit_suite *suite = ctx->suite;
int try_exit_code = kunit_try_catch_get_result(&test->try_catch); int try_exit_code = kunit_try_catch_get_result(&test->try_catch);
if (try_exit_code) { if (try_exit_code) {
...@@ -448,12 +498,6 @@ static void kunit_catch_run_case(void *data) ...@@ -448,12 +498,6 @@ static void kunit_catch_run_case(void *data)
} }
return; return;
} }
/*
* Test case was run, but aborted. It is the test case's business as to
* whether it failed or not, we just need to clean up.
*/
kunit_run_case_cleanup(test, suite);
} }
/* /*
...@@ -478,6 +522,13 @@ static void kunit_run_case_catch_errors(struct kunit_suite *suite, ...@@ -478,6 +522,13 @@ static void kunit_run_case_catch_errors(struct kunit_suite *suite,
context.test_case = test_case; context.test_case = test_case;
kunit_try_catch_run(try_catch, &context); kunit_try_catch_run(try_catch, &context);
/* Now run the cleanup */
kunit_try_catch_init(try_catch,
test,
kunit_try_run_case_cleanup,
kunit_catch_run_case_cleanup);
kunit_try_catch_run(try_catch, &context);
/* Propagate the parameter result to the test case. */ /* Propagate the parameter result to the test case. */
if (test->status == KUNIT_FAILURE) if (test->status == KUNIT_FAILURE)
test_case->status = KUNIT_FAILURE; test_case->status = KUNIT_FAILURE;
...@@ -585,11 +636,11 @@ int kunit_run_tests(struct kunit_suite *suite) ...@@ -585,11 +636,11 @@ int kunit_run_tests(struct kunit_suite *suite)
"param-%d", test.param_index); "param-%d", test.param_index);
} }
kunit_log(KERN_INFO, &test, kunit_print_ok_not_ok(&test, KUNIT_LEVEL_CASE_PARAM,
KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT test.status,
"%s %d %s", test.param_index + 1,
kunit_status_to_ok_not_ok(test.status), param_desc,
test.param_index + 1, param_desc); test.status_comment);
/* Get next param. */ /* Get next param. */
param_desc[0] = '\0'; param_desc[0] = '\0';
...@@ -603,7 +654,7 @@ int kunit_run_tests(struct kunit_suite *suite) ...@@ -603,7 +654,7 @@ int kunit_run_tests(struct kunit_suite *suite)
kunit_print_test_stats(&test, param_stats); kunit_print_test_stats(&test, param_stats);
kunit_print_ok_not_ok(&test, true, test_case->status, kunit_print_ok_not_ok(&test, KUNIT_LEVEL_CASE, test_case->status,
kunit_test_case_num(suite, test_case), kunit_test_case_num(suite, test_case),
test_case->name, test_case->name,
test.status_comment); test.status_comment);
...@@ -712,58 +763,28 @@ static struct notifier_block kunit_mod_nb = { ...@@ -712,58 +763,28 @@ static struct notifier_block kunit_mod_nb = {
}; };
#endif #endif
struct kunit_kmalloc_array_params { void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp)
size_t n;
size_t size;
gfp_t gfp;
};
static int kunit_kmalloc_array_init(struct kunit_resource *res, void *context)
{ {
struct kunit_kmalloc_array_params *params = context; void *data;
res->data = kmalloc_array(params->n, params->size, params->gfp); data = kmalloc_array(n, size, gfp);
if (!res->data)
return -ENOMEM;
return 0;
}
static void kunit_kmalloc_array_free(struct kunit_resource *res) if (!data)
{ return NULL;
kfree(res->data);
}
void *kunit_kmalloc_array(struct kunit *test, size_t n, size_t size, gfp_t gfp) if (kunit_add_action_or_reset(test, (kunit_action_t *)kfree, data) != 0)
{ return NULL;
struct kunit_kmalloc_array_params params = {
.size = size,
.n = n,
.gfp = gfp
};
return kunit_alloc_resource(test, return data;
kunit_kmalloc_array_init,
kunit_kmalloc_array_free,
gfp,
&params);
} }
EXPORT_SYMBOL_GPL(kunit_kmalloc_array); EXPORT_SYMBOL_GPL(kunit_kmalloc_array);
static inline bool kunit_kfree_match(struct kunit *test,
struct kunit_resource *res, void *match_data)
{
/* Only match resources allocated with kunit_kmalloc() and friends. */
return res->free == kunit_kmalloc_array_free && res->data == match_data;
}
void kunit_kfree(struct kunit *test, const void *ptr) void kunit_kfree(struct kunit *test, const void *ptr)
{ {
if (!ptr) if (!ptr)
return; return;
if (kunit_destroy_resource(test, kunit_kfree_match, (void *)ptr)) kunit_release_action(test, (kunit_action_t *)kfree, (void *)ptr);
KUNIT_FAIL(test, "kunit_kfree: %px already freed or not allocated by kunit", ptr);
} }
EXPORT_SYMBOL_GPL(kunit_kfree); EXPORT_SYMBOL_GPL(kunit_kfree);
......
...@@ -198,6 +198,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func, ...@@ -198,6 +198,7 @@ static bool __dead_end_function(struct objtool_file *file, struct symbol *func,
*/ */
static const char * const global_noreturns[] = { static const char * const global_noreturns[] = {
"__invalid_creds", "__invalid_creds",
"__kunit_abort",
"__module_put_and_kthread_exit", "__module_put_and_kthread_exit",
"__reiserfs_panic", "__reiserfs_panic",
"__stack_chk_fail", "__stack_chk_fail",
......
...@@ -92,7 +92,7 @@ class LinuxSourceTreeOperations: ...@@ -92,7 +92,7 @@ class LinuxSourceTreeOperations:
if stderr: # likely only due to build warnings if stderr: # likely only due to build warnings
print(stderr.decode()) print(stderr.decode())
def start(self, params: List[str], build_dir: str) -> subprocess.Popen[str]: def start(self, params: List[str], build_dir: str) -> subprocess.Popen:
raise RuntimeError('not implemented!') raise RuntimeError('not implemented!')
...@@ -113,7 +113,7 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations): ...@@ -113,7 +113,7 @@ class LinuxSourceTreeOperationsQemu(LinuxSourceTreeOperations):
kconfig.merge_in_entries(base_kunitconfig) kconfig.merge_in_entries(base_kunitconfig)
return kconfig return kconfig
def start(self, params: List[str], build_dir: str) -> subprocess.Popen[str]: def start(self, params: List[str], build_dir: str) -> subprocess.Popen:
kernel_path = os.path.join(build_dir, self._kernel_path) kernel_path = os.path.join(build_dir, self._kernel_path)
qemu_command = ['qemu-system-' + self._qemu_arch, qemu_command = ['qemu-system-' + self._qemu_arch,
'-nodefaults', '-nodefaults',
...@@ -142,7 +142,7 @@ class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations): ...@@ -142,7 +142,7 @@ class LinuxSourceTreeOperationsUml(LinuxSourceTreeOperations):
kconfig.merge_in_entries(base_kunitconfig) kconfig.merge_in_entries(base_kunitconfig)
return kconfig return kconfig
def start(self, params: List[str], build_dir: str) -> subprocess.Popen[str]: def start(self, params: List[str], build_dir: str) -> subprocess.Popen:
"""Runs the Linux UML binary. Must be named 'linux'.""" """Runs the Linux UML binary. Must be named 'linux'."""
linux_bin = os.path.join(build_dir, 'linux') linux_bin = os.path.join(build_dir, 'linux')
params.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt']) params.extend(['mem=1G', 'console=tty', 'kunit_shutdown=halt'])
......
[mypy]
strict = True
# E.g. we can't write subprocess.Popen[str] until Python 3.9+.
# But kunit.py tries to support Python 3.7+, so let's disable it.
disable_error_code = type-arg
...@@ -23,7 +23,7 @@ commands: Dict[str, Sequence[str]] = { ...@@ -23,7 +23,7 @@ commands: Dict[str, Sequence[str]] = {
'kunit_tool_test.py': ['./kunit_tool_test.py'], 'kunit_tool_test.py': ['./kunit_tool_test.py'],
'kunit smoke test': ['./kunit.py', 'run', '--kunitconfig=lib/kunit', '--build_dir=kunit_run_checks'], 'kunit smoke test': ['./kunit.py', 'run', '--kunitconfig=lib/kunit', '--build_dir=kunit_run_checks'],
'pytype': ['/bin/sh', '-c', 'pytype *.py'], 'pytype': ['/bin/sh', '-c', 'pytype *.py'],
'mypy': ['mypy', '--strict', '--exclude', '_test.py$', '--exclude', 'qemu_configs/', '.'], 'mypy': ['mypy', '--config-file', 'mypy.ini', '--exclude', '_test.py$', '--exclude', 'qemu_configs/', '.'],
} }
# The user might not have mypy or pytype installed, skip them if so. # The user might not have mypy or pytype installed, skip them if so.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment