On Fri, Apr 14, 2023 at 01:00:26PM +0200, Benjamin Berg wrote:
Hi,
On Fri, 2023-04-14 at 12:01 +0200, maxime@cerno.tech wrote:
Hi David,
On Fri, Mar 31, 2023 at 04:04:09PM +0800, David Gow wrote:
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.
Signed-off-by: David Gow davidgow@google.com
Changes since RFC v1: https://lore.kernel.org/linux-kselftest/20230325043104.3761770-2-davidgow@go...
- Rename functions to better match the devm_* APIs. (Thanks Maxime)
- Embed the kunit_resource in struct kunit_action_ctx to avoid an extra
allocation (Thanks Benjamin)
- Use 'struct kunit_action_ctx' as the type for cancellation tokens
(Thanks Benjamin)
- Add tests.
include/kunit/resource.h | 89 ++++++++++++++++++++++++++++ lib/kunit/kunit-test.c | 123 ++++++++++++++++++++++++++++++++++++++- lib/kunit/resource.c | 99 +++++++++++++++++++++++++++++++ 3 files changed, 310 insertions(+), 1 deletion(-)
diff --git a/include/kunit/resource.h b/include/kunit/resource.h index c0d88b318e90..15efd8924666 100644 --- a/include/kunit/resource.h +++ b/include/kunit/resource.h @@ -387,4 +387,93 @@ static inline int kunit_destroy_named_resource(struct kunit *test, */ void kunit_remove_resource(struct kunit *test, struct kunit_resource *res); +typedef void (*kunit_defer_function_t)(void *ctx);
+/* An opaque token to a deferred action. */ +struct kunit_action_ctx;
+/**
- kunit_add_action() - Defer an 'action' (function call) until the test ends.
- @test: Test case to associate the action with.
- @func: The function to run on test exit
- @ctx: Data passed into @func
- @internal_gfp: gfp to use for internal allocations, if unsure, use GFP_KERNEL
- 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.
- Returns:
- * An opaque "cancellation token", or NULL on error. Pass this token to
- * kunit_remove_action_token() in order to cancel the deferred execution of
- * func().
- */
+struct kunit_action_ctx *kunit_add_action(struct kunit *test, kunit_defer_function_t func, + void *ctx, gfp_t internal_gfp);
I've tried to leverage kunit_add_action() today, and I'm wondering if passing the struct kunit pointer to the deferred function would help.
The code I'm struggling with is something like:
static int test_init(struct kunit *test) { priv = kunit_kzalloc(sizeof(*priv), GFP_KERNEL); KUNIT_ASSERT_NOT_NULL(test, priv); test->priv = priv;
priv->dev = alloc_device();
return 0; }
and then in the test itself:
static void actual_test(struct kunit *test) { struct test_priv *priv = test->priv;
id = allocate_buffer(priv->dev);
KUNIT_EXPECT_EQ(test, id, 42);
free_buffer(priv->dev, id); }
I'd like to turn free_buffer an action registered right after allocate buffer. However, since it takes several arguments and kunit_add_action expects a single pointer, we would need to create a structure for it, allocate it, fill it, and then free it when the action has ran.
It creates a lot of boilerplate, while if we were passing the pointer to struct kunit we could access the context of the test as well, and things would be much simpler.
The question seems to be what about the typical use-case. I was always imagining calling functions like kfree/kfree_skb which often only require a single argument.
I guess we can have a look at the devm stuff. I'd expect the scope of things that will eventually tie their resource to kunit would be similar. "Straight" allocation/deallocation functions are the obvious first candidates, but there's a lot of other use cases as well.
I guess my main point is that it assumes that most function to clean things up will take the resource as its only argument, which isn't always the case. I guess it's reasonable to optimize for the most trivial case, but we should strive to keep the boilerplate down as much as we can in the other case too.
For arbitrary arguments, a struct and custom free function will be needed. At that point, maybe it is fair to assume that API users will use the resource API directly, doing the same trick as kunit_add_action and storing the arguments together with struct kunit_resource.
kunit_add_resource adds tons of boilerplate as well:
struct test_buffer_priv { struct device *dev; }
struct test_alloc_params { struct device *dev; void *buffer; }
static int __alloc_buffer(struct kunit_resource *res, void *ptr) { struct test_alloc_params *params = ptr; void *buffer;
params->buffer = allocate_buffer(params->dev, params->size); res->data = params;
return 0; }
static void __free_buffer(struct kunit_resource *res) { struct test_alloc_params *params = res->data;
free_buffer(params->dev, params->buffer); }
void actual_test(struct kunit_test *test) { struct test_alloc_params *params = test->priv;
kunit_alloc_resource(test, __alloc_buffer, __free_buffer, GFP_KERNEL, params); KUNIT_EXPECT_NOT_NULL(params->buffer); }
int test_init(struct kunit_test *test) { struct test_alloc_params *params = kunit_kmalloc(test, sizeof(*params), GFP_KERNEL);
test->priv = params;
params->dev = kunit_allocate_device(...);
return 0; }
while we could have something like:
struct test_buffer_priv { struct device *dev; }
static void free_buffer_action(struct kunit *test, void *ptr) { struct test_buffer_priv *priv = test->priv;
free_buffer(params->dev, ptr); }
void actual_test(struct kunit_test *test) { struct test_buffer_priv *priv = test->priv; void *buffer;
buffer = allocate_buffer(priv->dev, 42); kunit_add_action(test, free_buffer_action, buffer);
KUNIT_EXPECT_NOT_NULL(buffer); }
int test_init(struct kunit_test *test) { struct test_buffer_priv *priv = kunit_kmalloc(test, sizeof(*priv), GFP_KERNEL);
test->priv = priv;
priv->dev = kunit_allocate_device(...);
return 0; }
Which is much more compact, more readable, and less error prone.
And sure, kfree and free_skb would need some intermediate function, but it's like 3 more lines, which can even be shared at the framework or kunit level, so shouldn't really impact the tests themselves.
That said, maybe one could add it as a second argument? It is a little bit weird API wise, but it would allow simply casting single-argument functions in order to ignore "struct kunit *" argument.
I guess. I'd find it a bit weird to have that one function with the argument order reversed compared to everything else though.
Maxime