[PATCH v2] kunit: add test duration attribute

From: Rae Moar
Date: Wed Jul 31 2024 - 16:17:11 EST


Add a new test duration attribute to print the duration of a test run.

Example:
KTAP version 1
# Subtest: memcpy
# module: memcpy_kunit
1..4
# memcpy_large_test.speed: slow
# memcpy_large_test.duration: 1.134787584s
ok 1 memcpy_large_test
...

This attribute is printed for each test (excluding parameterized tests).

Add documentation for this new attribute to KUnit docs.

In order to save the timespec64 object, add the ability to save a memory
allocated object to the attributes framework.

Signed-off-by: Rae Moar <rmoar@xxxxxxxxxx>
---
Changes v1->v2:
- Change sprintf to kasprintf

.../dev-tools/kunit/running_tips.rst | 7 +++
include/kunit/attributes.h | 5 ++
include/kunit/test.h | 1 +
lib/kunit/attributes.c | 54 ++++++++++++++++++-
lib/kunit/test.c | 25 +++++++--
5 files changed, 86 insertions(+), 6 deletions(-)

diff --git a/Documentation/dev-tools/kunit/running_tips.rst b/Documentation/dev-tools/kunit/running_tips.rst
index bd689db6fdd2..a528d92e5d8f 100644
--- a/Documentation/dev-tools/kunit/running_tips.rst
+++ b/Documentation/dev-tools/kunit/running_tips.rst
@@ -446,3 +446,10 @@ This attribute indicates whether the test uses init data or functions.

This attribute is automatically saved as a boolean and tests can also be
filtered using this attribute.
+
+``duration``
+
+This attribute indicates the length of time in seconds of the test execution.
+
+This attribute is automatically saved as a timespec64 and printed for each test
+(excluding parameterized tests).
diff --git a/include/kunit/attributes.h b/include/kunit/attributes.h
index bc76a0b786d2..89ca54ef380d 100644
--- a/include/kunit/attributes.h
+++ b/include/kunit/attributes.h
@@ -18,6 +18,11 @@ struct kunit_attr_filter {
char *input;
};

+/*
+ * Frees all of a test's allocated attributes.
+ */
+void kunit_free_attr(void *test_or_suite, bool is_test);
+
/*
* Returns the name of the filter's attribute.
*/
diff --git a/include/kunit/test.h b/include/kunit/test.h
index ec61cad6b71d..dca78d9bd3f6 100644
--- a/include/kunit/test.h
+++ b/include/kunit/test.h
@@ -82,6 +82,7 @@ enum kunit_speed {
/* Holds attributes for each test case and suite */
struct kunit_attributes {
enum kunit_speed speed;
+ struct timespec64 *duration;
};

/**
diff --git a/lib/kunit/attributes.c b/lib/kunit/attributes.c
index 2cf04cc09372..fd01d54e52d7 100644
--- a/lib/kunit/attributes.c
+++ b/lib/kunit/attributes.c
@@ -40,6 +40,7 @@ struct kunit_attr {
int (*filter)(void *attr, const char *input, int *err);
void *attr_default;
enum print_ops print;
+ bool to_free;
};

/* String Lists for enum Attributes */
@@ -79,8 +80,22 @@ static const char *attr_string_to_string(void *attr, bool *to_free)
return (char *) attr;
}

+static const char *attr_duration_to_string(void *attr, bool *to_free)
+{
+ struct timespec64 *val = (struct timespec64 *)attr;
+ char *str = kasprintf(GFP_KERNEL, "%lld.%09lds", val->tv_sec, val->tv_nsec);
+
+ *to_free = true;
+ return str;
+}
+
/* Filter Methods */

+static int attr_default_filter(void *attr, const char *input, int *err)
+{
+ return false;
+}
+
static const char op_list[] = "<>!=";

/*
@@ -246,8 +261,20 @@ static void *attr_is_init_get(void *test_or_suite, bool is_test)
return ((void *) suite->is_init);
}

+static void *attr_duration_get(void *test_or_suite, bool is_test)
+{
+ struct kunit_case *test = is_test ? test_or_suite : NULL;
+
+ if (test && !test->generate_params)
+ return ((void *) test->attr.duration);
+ else
+ return ((void *) NULL);
+}
+
/* List of all Test Attributes */

+static struct timespec64 duration_default = {0, 0};
+
static struct kunit_attr kunit_attr_list[] = {
{
.name = "speed",
@@ -256,6 +283,7 @@ static struct kunit_attr kunit_attr_list[] = {
.filter = attr_speed_filter,
.attr_default = (void *)KUNIT_SPEED_NORMAL,
.print = PRINT_ALWAYS,
+ .to_free = false,
},
{
.name = "module",
@@ -264,6 +292,7 @@ static struct kunit_attr kunit_attr_list[] = {
.filter = attr_string_filter,
.attr_default = (void *)"",
.print = PRINT_SUITE,
+ .to_free = false,
},
{
.name = "is_init",
@@ -272,10 +301,33 @@ static struct kunit_attr kunit_attr_list[] = {
.filter = attr_bool_filter,
.attr_default = (void *)false,
.print = PRINT_SUITE,
+ .to_free = false,
+ },
+ {
+ .name = "duration",
+ .get_attr = attr_duration_get,
+ .to_string = attr_duration_to_string,
+ .filter = attr_default_filter,
+ .attr_default = (void *)(&duration_default),
+ .print = PRINT_ALWAYS,
+ .to_free = true,
}
};

-/* Helper Functions to Access Attributes */
+/* Helper Functions to Access/Free Attributes */
+
+void kunit_free_attr(void *test_or_suite, bool is_test)
+{
+ int i;
+ void *attr;
+
+ for (i = 0; i < ARRAY_SIZE(kunit_attr_list); i++) {
+ if (kunit_attr_list[i].to_free) {
+ attr = kunit_attr_list[i].get_attr(test_or_suite, is_test);
+ kfree(attr);
+ }
+ }
+}

const char *kunit_attr_filter_name(struct kunit_attr_filter filter)
{
diff --git a/lib/kunit/test.c b/lib/kunit/test.c
index e8b1b52a19ab..0d18e4969015 100644
--- a/lib/kunit/test.c
+++ b/lib/kunit/test.c
@@ -376,11 +376,11 @@ static void kunit_run_case_check_speed(struct kunit *test,
/*
* Initializes and runs test case. Does not clean up or do post validations.
*/
-static void kunit_run_case_internal(struct kunit *test,
+static struct timespec64 kunit_run_case_internal(struct kunit *test,
struct kunit_suite *suite,
struct kunit_case *test_case)
{
- struct timespec64 start, end;
+ struct timespec64 start, end, duration;

if (suite->init) {
int ret;
@@ -389,7 +389,9 @@ static void kunit_run_case_internal(struct kunit *test,
if (ret) {
kunit_err(test, "failed to initialize: %d\n", ret);
kunit_set_failure(test);
- return;
+ duration.tv_sec = 0;
+ duration.tv_nsec = 0;
+ return duration;
}
}

@@ -399,7 +401,11 @@ static void kunit_run_case_internal(struct kunit *test,

ktime_get_ts64(&end);

- kunit_run_case_check_speed(test, test_case, timespec64_sub(end, start));
+ duration = timespec64_sub(end, start);
+
+ kunit_run_case_check_speed(test, test_case, duration);
+
+ return duration;
}

static void kunit_case_internal_cleanup(struct kunit *test)
@@ -424,6 +430,7 @@ struct kunit_try_catch_context {
struct kunit *test;
struct kunit_suite *suite;
struct kunit_case *test_case;
+ struct timespec64 duration;
};

static void kunit_try_run_case(void *data)
@@ -440,7 +447,7 @@ static void kunit_try_run_case(void *data)
* abort will be called, this thread will exit, and finally the parent
* thread will resume control and handle any necessary clean up.
*/
- kunit_run_case_internal(test, suite, test_case);
+ ctx->duration = kunit_run_case_internal(test, suite, test_case);
}

static void kunit_try_run_case_cleanup(void *data)
@@ -521,6 +528,7 @@ static void kunit_run_case_catch_errors(struct kunit_suite *suite,
{
struct kunit_try_catch_context context;
struct kunit_try_catch *try_catch;
+ struct timespec64 *duration = kmalloc(sizeof(struct timespec64), GFP_KERNEL);

try_catch = &test->try_catch;

@@ -533,6 +541,10 @@ static void kunit_run_case_catch_errors(struct kunit_suite *suite,
context.test_case = test_case;
kunit_try_catch_run(try_catch, &context);

+ duration->tv_sec = context.duration.tv_sec;
+ duration->tv_nsec = context.duration.tv_nsec;
+ test_case->attr.duration = duration;
+
/* Now run the cleanup */
kunit_try_catch_init(try_catch,
test,
@@ -670,6 +682,7 @@ int kunit_run_tests(struct kunit_suite *suite)
}

kunit_print_attr((void *)test_case, true, KUNIT_LEVEL_CASE);
+ kunit_free_attr((void *)test_case, true);

kunit_print_test_stats(&test, param_stats);

@@ -680,6 +693,7 @@ int kunit_run_tests(struct kunit_suite *suite)

kunit_update_stats(&suite_stats, test_case->status);
kunit_accumulate_stats(&total_stats, param_stats);
+
}

if (suite->suite_exit)
@@ -688,6 +702,7 @@ int kunit_run_tests(struct kunit_suite *suite)
kunit_print_suite_stats(suite, suite_stats, total_stats);
suite_end:
kunit_print_suite_end(suite);
+ kunit_free_attr((void *)suite, false);

return 0;
}

base-commit: 67c9971cd6d309ecbcb87b942e22ffc194d7a376
--
2.46.0.rc2.264.g509ed76dc8-goog