Commit b9dce8a1 authored by David Gow's avatar David Gow Committed by Shuah Khan
Browse files

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: default avatarBenjamin Berg <benjamin.berg@intel.com>
Reviewed-by: default avatarMaxime Ripard <maxime@cerno.tech>
Tested-by: default avatarMaxime Ripard <maxime@cerno.tech>
Signed-off-by: default avatarDavid Gow <davidgow@google.com>
Signed-off-by: default avatarShuah Khan <skhan@linuxfoundation.org>
parent a5ce66ad
Loading
Loading
Loading
Loading
+92 −0
Original line number Diff line number Diff line
@@ -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 */
+87 −1
Original line number Diff line number Diff line
@@ -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),
	{}
};

+99 −0
Original line number Diff line number Diff line
@@ -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);