mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
synced 2024-12-28 16:52:18 +00:00
kunit: Add kunit_add_action() to defer a call until test exit
Many uses of the KUnit resource system are intended to simply defer calling a function until the test exits (be it due to success or failure). The existing kunit_alloc_resource() function is often used for this, but was awkward to use (requiring passing NULL init functions, etc), and returned a resource without incrementing its reference count, which -- while okay for this use-case -- could cause problems in others. Instead, introduce a simple kunit_add_action() API: a simple function (returning nothing, accepting a single void* argument) can be scheduled to be called when the test exits. Deferred actions are called in the opposite order to that which they were registered. This mimics the devres API, devm_add_action(), and also provides kunit_remove_action(), to cancel a deferred action, and kunit_release_action() to trigger one early. This is implemented as a resource under the hood, so the ordering between resource cleanup and deferred functions is maintained. Reviewed-by: Benjamin Berg <benjamin.berg@intel.com> Reviewed-by: Maxime Ripard <maxime@cerno.tech> Tested-by: Maxime Ripard <maxime@cerno.tech> Signed-off-by: David Gow <davidgow@google.com> Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
This commit is contained in:
parent
a5ce66ad29
commit
b9dce8a1ed
@ -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);
|
||||
|
||||
/* 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.
|
||||
* @func: 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.
|
||||
* @func: 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.
|
||||
* @func: 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.
|
||||
* @func: 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 */
|
||||
|
@ -112,7 +112,7 @@ struct kunit_test_resource_context {
|
||||
struct kunit test;
|
||||
bool is_resource_initialized;
|
||||
int allocate_order[2];
|
||||
int free_order[2];
|
||||
int free_order[4];
|
||||
};
|
||||
|
||||
static int fake_resource_init(struct kunit_resource *res, void *context)
|
||||
@ -403,6 +403,88 @@ static void kunit_resource_test_named(struct kunit *test)
|
||||
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)
|
||||
{
|
||||
struct kunit_test_resource_context *ctx =
|
||||
@ -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_static),
|
||||
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,
|
||||
return 0;
|
||||
}
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user