Replace the original fixed-size log buffer with a dynamically- extending log.
Patch 1 provides the basic implementation. The following patches add test cases, support for logging long strings, and an optimization to the string formatting that is now more thoroughly testable.
Richard Fitzgerald (6): kunit: Replace fixed-size log with dynamically-extending buffer kunit: kunit-test: Add test cases for extending log buffer kunit: Handle logging of lines longer than the fragment buffer size kunit: kunit-test: Add test cases for logging very long lines kunit: kunit-test: Add test of logging only a newline kunit: Don't waste first attempt to format string in kunit_log_append()
include/kunit/test.h | 25 +++- lib/kunit/debugfs.c | 65 +++++++-- lib/kunit/kunit-test.c | 321 ++++++++++++++++++++++++++++++++++++++++- lib/kunit/test.c | 127 +++++++++++++--- 4 files changed, 489 insertions(+), 49 deletions(-)
Re-implement the log buffer as a list of buffer fragments that can be extended as the size of the log info grows.
When using parameterization the test case can run many times and create a large amount of log. It's not really practical to keep increasing the size of the fixed buffer every time a test needs more space. And a big fixed buffer wastes memory.
The original char *log pointer is replaced by a pointer to a list of struct kunit_log_frag, each containing a fixed-size buffer.
kunit_log_append() now attempts to append to the last kunit_log_frag in the list. If there isn't enough space it will append a new kunit_log_frag to the list. This simple implementation does not attempt to completely fill the buffer in every kunit_log_frag.
The 'log' member of kunit_suite, kunit_test_case and kunit_suite must be a pointer because the API of kunit_log() requires that is the same type in all three structs. As kunit.log is a pointer to the 'log' of the current kunit_case, it must be a pointer in the other two structs.
The existing kunit-test.c log tests have been updated to build against the new fragmented log implementation.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- include/kunit/test.h | 25 +++++++++++----- lib/kunit/debugfs.c | 65 ++++++++++++++++++++++++++++++++++-------- lib/kunit/kunit-test.c | 29 +++++++++++++------ lib/kunit/test.c | 63 ++++++++++++++++++++++++++++------------ 4 files changed, 136 insertions(+), 46 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 011e0d6bb506..ef8e09aafe1b 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -33,8 +33,8 @@ DECLARE_STATIC_KEY_FALSE(kunit_running);
struct kunit;
-/* Size of log associated with test. */ -#define KUNIT_LOG_SIZE 2048 +/* Size of log buffer fragments. */ +#define KUNIT_LOG_FRAGMENT_SIZE (256 - sizeof(struct list_head))
/* Maximum size of parameter description string. */ #define KUNIT_PARAM_DESC_SIZE 128 @@ -85,6 +85,11 @@ struct kunit_attributes { enum kunit_speed speed; };
+struct kunit_log_frag { + struct list_head list; + char buf[KUNIT_LOG_FRAGMENT_SIZE]; +}; + /** * struct kunit_case - represents an individual test case. * @@ -132,7 +137,7 @@ struct kunit_case { /* private: internal use only. */ enum kunit_status status; char *module_name; - char *log; + struct list_head *log; };
static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) @@ -252,7 +257,7 @@ struct kunit_suite { /* private: internal use only */ char status_comment[KUNIT_STATUS_COMMENT_SIZE]; struct dentry *debugfs; - char *log; + struct list_head *log; int suite_init_err; };
@@ -272,7 +277,7 @@ struct kunit {
/* private: internal use only. */ const char *name; /* Read only after initialization! */ - char *log; /* Points at case log after initialization */ + struct list_head *log; /* Points at case log after initialization */ struct kunit_try_catch try_catch; /* param_value is the current parameter value for a test case. */ const void *param_value; @@ -304,7 +309,7 @@ static inline void kunit_set_failure(struct kunit *test)
bool kunit_enabled(void);
-void kunit_init_test(struct kunit *test, const char *name, char *log); +void kunit_init_test(struct kunit *test, const char *name, struct list_head *log);
int kunit_run_tests(struct kunit_suite *suite);
@@ -317,6 +322,12 @@ int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_
void __kunit_test_suites_exit(struct kunit_suite **suites, int num_suites);
+static inline void kunit_init_log_frag(struct kunit_log_frag *frag) +{ + INIT_LIST_HEAD(&frag->list); + frag->buf[0] = '\0'; +} + #if IS_BUILTIN(CONFIG_KUNIT) int kunit_run_all_tests(void); #else @@ -451,7 +462,7 @@ static inline void *kunit_kcalloc(struct kunit *test, size_t n, size_t size, gfp
void kunit_cleanup(struct kunit *test);
-void __printf(2, 3) kunit_log_append(char *log, const char *fmt, ...); +void __printf(2, 3) kunit_log_append(struct list_head *log, const char *fmt, ...);
/** * kunit_mark_skipped() - Marks @test_or_suite as skipped diff --git a/lib/kunit/debugfs.c b/lib/kunit/debugfs.c index 22c5c496a68f..a26b6d31bd2f 100644 --- a/lib/kunit/debugfs.c +++ b/lib/kunit/debugfs.c @@ -5,6 +5,7 @@ */
#include <linux/debugfs.h> +#include <linux/list.h> #include <linux/module.h>
#include <kunit/test.h> @@ -37,14 +38,15 @@ void kunit_debugfs_init(void) debugfs_rootdir = debugfs_create_dir(KUNIT_DEBUGFS_ROOT, NULL); }
-static void debugfs_print_result(struct seq_file *seq, - struct kunit_suite *suite, - struct kunit_case *test_case) +static void debugfs_print_log(struct seq_file *seq, const struct list_head *log) { - if (!test_case || !test_case->log) + struct kunit_log_frag *frag; + + if (!log) return;
- seq_printf(seq, "%s", test_case->log); + list_for_each_entry(frag, log, list) + seq_puts(seq, frag->buf); }
/* @@ -69,10 +71,9 @@ static int debugfs_print_results(struct seq_file *seq, void *v) seq_printf(seq, KUNIT_SUBTEST_INDENT "1..%zd\n", kunit_suite_num_test_cases(suite));
kunit_suite_for_each_test_case(suite, test_case) - debugfs_print_result(seq, suite, test_case); + debugfs_print_log(seq, test_case->log);
- if (suite->log) - seq_printf(seq, "%s", suite->log); + debugfs_print_log(seq, suite->log);
seq_printf(seq, "%s %d %s\n", kunit_status_to_ok_not_ok(success), 1, suite->name); @@ -100,14 +101,53 @@ static const struct file_operations debugfs_results_fops = { .release = debugfs_release, };
+static struct list_head *kunit_debugfs_alloc_log(void) +{ + struct list_head *log; + struct kunit_log_frag *frag; + + log = kzalloc(sizeof(*log), GFP_KERNEL); + if (!log) + return NULL; + + INIT_LIST_HEAD(log); + + frag = kmalloc(sizeof(*frag), GFP_KERNEL); + if (!frag) { + kfree(log); + return NULL; + } + + kunit_init_log_frag(frag); + list_add_tail(&frag->list, log); + + return log; +} + +static void kunit_debugfs_free_log(struct list_head *log) +{ + struct kunit_log_frag *frag, *n; + + if (!log) + return; + + list_for_each_entry_safe(frag, n, log, list) { + list_del(&frag->list); + kfree(frag); + } + + kfree(log); +} + void kunit_debugfs_create_suite(struct kunit_suite *suite) { struct kunit_case *test_case;
/* Allocate logs before creating debugfs representation. */ - suite->log = kzalloc(KUNIT_LOG_SIZE, GFP_KERNEL); + suite->log = kunit_debugfs_alloc_log(); + kunit_suite_for_each_test_case(suite, test_case) - test_case->log = kzalloc(KUNIT_LOG_SIZE, GFP_KERNEL); + test_case->log = kunit_debugfs_alloc_log();
suite->debugfs = debugfs_create_dir(suite->name, debugfs_rootdir);
@@ -121,7 +161,8 @@ void kunit_debugfs_destroy_suite(struct kunit_suite *suite) struct kunit_case *test_case;
debugfs_remove_recursive(suite->debugfs); - kfree(suite->log); + kunit_debugfs_free_log(suite->log); + kunit_suite_for_each_test_case(suite, test_case) - kfree(test_case->log); + kunit_debugfs_free_log(test_case->log); } diff --git a/lib/kunit/kunit-test.c b/lib/kunit/kunit-test.c index 83d8e90ca7a2..54dc011c8980 100644 --- a/lib/kunit/kunit-test.c +++ b/lib/kunit/kunit-test.c @@ -533,9 +533,16 @@ static struct kunit_suite kunit_resource_test_suite = { static void kunit_log_test(struct kunit *test) { struct kunit_suite suite; + struct kunit_log_frag *frag;
- suite.log = kunit_kzalloc(test, KUNIT_LOG_SIZE, GFP_KERNEL); + suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log); + INIT_LIST_HEAD(suite.log); + frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag); + kunit_init_log_frag(frag); + KUNIT_EXPECT_EQ(test, frag->buf[0], '\0'); + list_add_tail(&frag->list, suite.log);
kunit_log(KERN_INFO, test, "put this in log."); kunit_log(KERN_INFO, test, "this too."); @@ -543,14 +550,17 @@ static void kunit_log_test(struct kunit *test) kunit_log(KERN_INFO, &suite, "along with this.");
#ifdef CONFIG_KUNIT_DEBUGFS + frag = list_first_entry(test->log, struct kunit_log_frag, list); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, - strstr(test->log, "put this in log.")); + strstr(frag->buf, "put this in log.")); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, - strstr(test->log, "this too.")); + strstr(frag->buf, "this too.")); + + frag = list_first_entry(suite.log, struct kunit_log_frag, list); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, - strstr(suite.log, "add to suite log.")); + strstr(frag->buf, "add to suite log.")); KUNIT_EXPECT_NOT_ERR_OR_NULL(test, - strstr(suite.log, "along with this.")); + strstr(frag->buf, "along with this.")); #else KUNIT_EXPECT_NULL(test, test->log); #endif @@ -558,11 +568,14 @@ static void kunit_log_test(struct kunit *test)
static void kunit_log_newline_test(struct kunit *test) { + struct kunit_log_frag *frag; + kunit_info(test, "Add newline\n"); if (test->log) { - KUNIT_ASSERT_NOT_NULL_MSG(test, strstr(test->log, "Add newline\n"), - "Missing log line, full log:\n%s", test->log); - KUNIT_EXPECT_NULL(test, strstr(test->log, "Add newline\n\n")); + frag = list_first_entry(test->log, struct kunit_log_frag, list); + KUNIT_ASSERT_NOT_NULL_MSG(test, strstr(frag->buf, "Add newline\n"), + "Missing log line, full log:\n%s", frag->buf); + KUNIT_EXPECT_NULL(test, strstr(frag->buf, "Add newline\n\n")); } else { kunit_skip(test, "only useful when debugfs is enabled"); } diff --git a/lib/kunit/test.c b/lib/kunit/test.c index cb9797fa6303..bdb361741214 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -11,6 +11,7 @@ #include <kunit/test-bug.h> #include <kunit/attributes.h> #include <linux/kernel.h> +#include <linux/list.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/panic.h> @@ -114,46 +115,66 @@ static void kunit_print_test_stats(struct kunit *test, * already present. * @log: The log to add the newline to. */ -static void kunit_log_newline(char *log) +static void kunit_log_newline(struct kunit_log_frag *frag) { int log_len, len_left;
- log_len = strlen(log); - len_left = KUNIT_LOG_SIZE - log_len - 1; + log_len = strlen(frag->buf); + len_left = sizeof(frag->buf) - log_len - 1;
- if (log_len > 0 && log[log_len - 1] != '\n') - strncat(log, "\n", len_left); + if (log_len > 0 && frag->buf[log_len - 1] != '\n') + strncat(frag->buf, "\n", len_left); }
-/* - * Append formatted message to log, size of which is limited to - * KUNIT_LOG_SIZE bytes (including null terminating byte). - */ -void kunit_log_append(char *log, const char *fmt, ...) +static struct kunit_log_frag *kunit_log_extend(struct list_head *log) +{ + struct kunit_log_frag *frag; + + frag = kmalloc(sizeof(*frag), GFP_KERNEL); + if (!frag) + return NULL; + + kunit_init_log_frag(frag); + list_add_tail(&frag->list, log); + + return frag; +} + +/* Append formatted message to log, extending the log buffer if necessary. */ +void kunit_log_append(struct list_head *log, const char *fmt, ...) { va_list args; + struct kunit_log_frag *frag; int len, log_len, len_left;
if (!log) return;
- log_len = strlen(log); - len_left = KUNIT_LOG_SIZE - log_len - 1; - if (len_left <= 0) - return; + frag = list_last_entry(log, struct kunit_log_frag, list); + log_len = strlen(frag->buf); + len_left = sizeof(frag->buf) - log_len - 1;
/* Evaluate length of line to add to log */ va_start(args, fmt); len = vsnprintf(NULL, 0, fmt, args) + 1; va_end(args);
+ if (len > len_left) { + frag = kunit_log_extend(log); + if (!frag) + return; + + len_left = sizeof(frag->buf) - 1; + log_len = 0; + } + /* Print formatted line to the log */ va_start(args, fmt); - vsnprintf(log + log_len, min(len, len_left), fmt, args); + vsnprintf(frag->buf + log_len, min(len, len_left), fmt, args); va_end(args);
/* Add newline to end of log if not already present. */ - kunit_log_newline(log); + kunit_log_newline(frag); } EXPORT_SYMBOL_GPL(kunit_log_append);
@@ -359,14 +380,18 @@ void __kunit_do_failed_assertion(struct kunit *test, } 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, struct list_head *log) { spin_lock_init(&test->lock); INIT_LIST_HEAD(&test->resources); test->name = name; test->log = log; - if (test->log) - test->log[0] = '\0'; + if (test->log) { + struct kunit_log_frag *frag = list_first_entry(test->log, + struct kunit_log_frag, + list); + frag->buf[0] = '\0'; + } test->status = KUNIT_SUCCESS; test->status_comment[0] = '\0'; }
On Tue, 8 Aug 2023 at 20:35, Richard Fitzgerald rf@opensource.cirrus.com wrote:
Re-implement the log buffer as a list of buffer fragments that can be extended as the size of the log info grows.
When using parameterization the test case can run many times and create a large amount of log. It's not really practical to keep increasing the size of the fixed buffer every time a test needs more space. And a big fixed buffer wastes memory.
The original char *log pointer is replaced by a pointer to a list of struct kunit_log_frag, each containing a fixed-size buffer.
kunit_log_append() now attempts to append to the last kunit_log_frag in the list. If there isn't enough space it will append a new kunit_log_frag to the list. This simple implementation does not attempt to completely fill the buffer in every kunit_log_frag.
The 'log' member of kunit_suite, kunit_test_case and kunit_suite must be a pointer because the API of kunit_log() requires that is the same type in all three structs. As kunit.log is a pointer to the 'log' of the current kunit_case, it must be a pointer in the other two structs.
The existing kunit-test.c log tests have been updated to build against the new fragmented log implementation.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
Looks good to me.
A few small notes inline below, mostly around the possibility of either embedding the list_head in the kunit_case struct directly (rather than using a pointer), or of pointing directly to the first fragment, rather than a separately-allocated struct list_head. Neither are showstoppers, though (and if it increases complexity at all, it's possibly premature optimization).
Otherwise, some test nitpicks and the fact that this will need a trivial rebase due to the module filtering stuff landing in kselftest/kunit.
Reviewed-by: David Gow davidgow@google.com
The other patches in the series pass the initial sniff test: I'll try to get a more thorough review done in the next day or two.
Cheers, -- David
include/kunit/test.h | 25 +++++++++++----- lib/kunit/debugfs.c | 65 ++++++++++++++++++++++++++++++++++-------- lib/kunit/kunit-test.c | 29 +++++++++++++------ lib/kunit/test.c | 63 ++++++++++++++++++++++++++++------------ 4 files changed, 136 insertions(+), 46 deletions(-)
diff --git a/include/kunit/test.h b/include/kunit/test.h index 011e0d6bb506..ef8e09aafe1b 100644 --- a/include/kunit/test.h +++ b/include/kunit/test.h @@ -33,8 +33,8 @@ DECLARE_STATIC_KEY_FALSE(kunit_running);
struct kunit;
-/* Size of log associated with test. */ -#define KUNIT_LOG_SIZE 2048 +/* Size of log buffer fragments. */ +#define KUNIT_LOG_FRAGMENT_SIZE (256 - sizeof(struct list_head))
/* Maximum size of parameter description string. */ #define KUNIT_PARAM_DESC_SIZE 128 @@ -85,6 +85,11 @@ struct kunit_attributes { enum kunit_speed speed; };
+struct kunit_log_frag {
struct list_head list;
char buf[KUNIT_LOG_FRAGMENT_SIZE];
+};
/**
- struct kunit_case - represents an individual test case.
@@ -132,7 +137,7 @@ struct kunit_case { /* private: internal use only. */ enum kunit_status status; char *module_name;
char *log;
struct list_head *log;
I wonder if this has to be a pointer? Would it make more sense to embed the struct list_head (or possibly a whole struct kunit_log_fragment if we weren't worried about kernel image size, see below) here, to avoid an extra allocation and a bunch of extra indirect memory accesses.
Even if we still want to pass a pointer to a struct list_head around, we could just take the address of this one, rather than allocating it separately. We'd have to copy the whole struct list_head around, rather than just the pointer, and it'd increase the size of struct kunit_case and similar, but struct list_head is just two pointers, so it shouldn't be drastic enough to matter.
Not a problem either way: I doubt this would be a performance or memory bottleneck, so if it's simpler to keep it as a pointer I don't mind, but if it's an easy enough change, it may be worth it.
};
static inline char *kunit_status_to_ok_not_ok(enum kunit_status status) @@ -252,7 +257,7 @@ struct kunit_suite { /* private: internal use only */ char status_comment[KUNIT_STATUS_COMMENT_SIZE]; struct dentry *debugfs;
char *log;
struct list_head *log;
As above, should this be a pointer?
int suite_init_err;
};
@@ -272,7 +277,7 @@ struct kunit {
/* private: internal use only. */ const char *name; /* Read only after initialization! */
char *log; /* Points at case log after initialization */
struct list_head *log; /* Points at case log after initialization */
I could imagine this either being a pointer to &(case.log), or a copy of the list_head which is then copied back into the case structure if we went with a less pointer-y implementation.
struct kunit_try_catch try_catch; /* param_value is the current parameter value for a test case. */ const void *param_value;
@@ -304,7 +309,7 @@ static inline void kunit_set_failure(struct kunit *test)
bool kunit_enabled(void);
-void kunit_init_test(struct kunit *test, const char *name, char *log); +void kunit_init_test(struct kunit *test, const char *name, struct list_head *log);
int kunit_run_tests(struct kunit_suite *suite);
@@ -317,6 +322,12 @@ int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_
void __kunit_test_suites_exit(struct kunit_suite **suites, int num_suites);
+static inline void kunit_init_log_frag(struct kunit_log_frag *frag) +{
INIT_LIST_HEAD(&frag->list);
frag->buf[0] = '\0';
+}
There's now a (trivial) conflict between this and the latest kselftest/kunit branch (with the module filtering patches). If you're doing a v3, could you rebase?
#if IS_BUILTIN(CONFIG_KUNIT) int kunit_run_all_tests(void); #else @@ -451,7 +462,7 @@ static inline void *kunit_kcalloc(struct kunit *test, size_t n, size_t size, gfp
void kunit_cleanup(struct kunit *test);
-void __printf(2, 3) kunit_log_append(char *log, const char *fmt, ...); +void __printf(2, 3) kunit_log_append(struct list_head *log, const char *fmt, ...);
/**
- kunit_mark_skipped() - Marks @test_or_suite as skipped
diff --git a/lib/kunit/debugfs.c b/lib/kunit/debugfs.c index 22c5c496a68f..a26b6d31bd2f 100644 --- a/lib/kunit/debugfs.c +++ b/lib/kunit/debugfs.c @@ -5,6 +5,7 @@ */
#include <linux/debugfs.h> +#include <linux/list.h> #include <linux/module.h>
#include <kunit/test.h> @@ -37,14 +38,15 @@ void kunit_debugfs_init(void) debugfs_rootdir = debugfs_create_dir(KUNIT_DEBUGFS_ROOT, NULL); }
-static void debugfs_print_result(struct seq_file *seq,
struct kunit_suite *suite,
struct kunit_case *test_case)
+static void debugfs_print_log(struct seq_file *seq, const struct list_head *log) {
if (!test_case || !test_case->log)
struct kunit_log_frag *frag;
if (!log) return;
seq_printf(seq, "%s", test_case->log);
list_for_each_entry(frag, log, list)
seq_puts(seq, frag->buf);
}
/* @@ -69,10 +71,9 @@ static int debugfs_print_results(struct seq_file *seq, void *v) seq_printf(seq, KUNIT_SUBTEST_INDENT "1..%zd\n", kunit_suite_num_test_cases(suite));
kunit_suite_for_each_test_case(suite, test_case)
debugfs_print_result(seq, suite, test_case);
debugfs_print_log(seq, test_case->log);
if (suite->log)
seq_printf(seq, "%s", suite->log);
debugfs_print_log(seq, suite->log); seq_printf(seq, "%s %d %s\n", kunit_status_to_ok_not_ok(success), 1, suite->name);
@@ -100,14 +101,53 @@ static const struct file_operations debugfs_results_fops = { .release = debugfs_release, };
+static struct list_head *kunit_debugfs_alloc_log(void) +{
struct list_head *log;
struct kunit_log_frag *frag;
log = kzalloc(sizeof(*log), GFP_KERNEL);
if (!log)
return NULL;
INIT_LIST_HEAD(log);
frag = kmalloc(sizeof(*frag), GFP_KERNEL);
If we're always allocating at least one fragment, would it make sense to embed a while kunit_log_frag in the test struct, rather than just a list_head (so the first fragment doesn't need allocating separately)?
Of course, that could bloat the kunit_case / kunit_suite structs too much (and therefore the .kunit_test_suites section). But maybe even a pointer to a kunit_log_frag would work.
Probably not worth the extra complexity, but it's a thought...
if (!frag) {
kfree(log);
return NULL;
}
kunit_init_log_frag(frag);
list_add_tail(&frag->list, log);
return log;
+}
+static void kunit_debugfs_free_log(struct list_head *log) +{
struct kunit_log_frag *frag, *n;
if (!log)
return;
list_for_each_entry_safe(frag, n, log, list) {
list_del(&frag->list);
kfree(frag);
}
kfree(log);
+}
void kunit_debugfs_create_suite(struct kunit_suite *suite) { struct kunit_case *test_case;
/* Allocate logs before creating debugfs representation. */
suite->log = kzalloc(KUNIT_LOG_SIZE, GFP_KERNEL);
suite->log = kunit_debugfs_alloc_log();
kunit_suite_for_each_test_case(suite, test_case)
test_case->log = kzalloc(KUNIT_LOG_SIZE, GFP_KERNEL);
test_case->log = kunit_debugfs_alloc_log(); suite->debugfs = debugfs_create_dir(suite->name, debugfs_rootdir);
@@ -121,7 +161,8 @@ void kunit_debugfs_destroy_suite(struct kunit_suite *suite) struct kunit_case *test_case;
debugfs_remove_recursive(suite->debugfs);
kfree(suite->log);
kunit_debugfs_free_log(suite->log);
kunit_suite_for_each_test_case(suite, test_case)
kfree(test_case->log);
kunit_debugfs_free_log(test_case->log);
} diff --git a/lib/kunit/kunit-test.c b/lib/kunit/kunit-test.c index 83d8e90ca7a2..54dc011c8980 100644 --- a/lib/kunit/kunit-test.c +++ b/lib/kunit/kunit-test.c @@ -533,9 +533,16 @@ static struct kunit_suite kunit_resource_test_suite = { static void kunit_log_test(struct kunit *test) { struct kunit_suite suite;
struct kunit_log_frag *frag;
suite.log = kunit_kzalloc(test, KUNIT_LOG_SIZE, GFP_KERNEL);
suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log);
INIT_LIST_HEAD(suite.log);
frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag);
kunit_init_log_frag(frag);
KUNIT_EXPECT_EQ(test, frag->buf[0], '\0');
list_add_tail(&frag->list, suite.log); kunit_log(KERN_INFO, test, "put this in log."); kunit_log(KERN_INFO, test, "this too.");
@@ -543,14 +550,17 @@ static void kunit_log_test(struct kunit *test) kunit_log(KERN_INFO, &suite, "along with this.");
#ifdef CONFIG_KUNIT_DEBUGFS
frag = list_first_entry(test->log, struct kunit_log_frag, list); KUNIT_EXPECT_NOT_ERR_OR_NULL(test,
strstr(test->log, "put this in log."));
strstr(frag->buf, "put this in log.")); KUNIT_EXPECT_NOT_ERR_OR_NULL(test,
strstr(test->log, "this too."));
strstr(frag->buf, "this too."));
frag = list_first_entry(suite.log, struct kunit_log_frag, list); KUNIT_EXPECT_NOT_ERR_OR_NULL(test,
strstr(suite.log, "add to suite log."));
strstr(frag->buf, "add to suite log.")); KUNIT_EXPECT_NOT_ERR_OR_NULL(test,
strstr(suite.log, "along with this."));
strstr(frag->buf, "along with this."));
#else KUNIT_EXPECT_NULL(test, test->log); #endif @@ -558,11 +568,14 @@ static void kunit_log_test(struct kunit *test)
static void kunit_log_newline_test(struct kunit *test) {
struct kunit_log_frag *frag;
kunit_info(test, "Add newline\n"); if (test->log) {
KUNIT_ASSERT_NOT_NULL_MSG(test, strstr(test->log, "Add newline\n"),
"Missing log line, full log:\n%s", test->log);
KUNIT_EXPECT_NULL(test, strstr(test->log, "Add newline\n\n"));
frag = list_first_entry(test->log, struct kunit_log_frag, list);
KUNIT_ASSERT_NOT_NULL_MSG(test, strstr(frag->buf, "Add newline\n"),
"Missing log line, full log:\n%s", frag->buf);
I'm not super thrilled that this only operates on the first fragment. Could we at least note that this is not the "full log" in the assertion message here, and maybe also assert that the log hasn't grown to a second fragment?
KUNIT_EXPECT_NULL(test, strstr(frag->buf, "Add newline\n\n")); } else { kunit_skip(test, "only useful when debugfs is enabled"); }
diff --git a/lib/kunit/test.c b/lib/kunit/test.c index cb9797fa6303..bdb361741214 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -11,6 +11,7 @@ #include <kunit/test-bug.h> #include <kunit/attributes.h> #include <linux/kernel.h> +#include <linux/list.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/panic.h> @@ -114,46 +115,66 @@ static void kunit_print_test_stats(struct kunit *test,
- already present.
- @log: The log to add the newline to.
*/ -static void kunit_log_newline(char *log) +static void kunit_log_newline(struct kunit_log_frag *frag) { int log_len, len_left;
log_len = strlen(log);
len_left = KUNIT_LOG_SIZE - log_len - 1;
log_len = strlen(frag->buf);
len_left = sizeof(frag->buf) - log_len - 1;
if (log_len > 0 && log[log_len - 1] != '\n')
strncat(log, "\n", len_left);
if (log_len > 0 && frag->buf[log_len - 1] != '\n')
strncat(frag->buf, "\n", len_left);
}
-/*
- Append formatted message to log, size of which is limited to
- KUNIT_LOG_SIZE bytes (including null terminating byte).
- */
-void kunit_log_append(char *log, const char *fmt, ...) +static struct kunit_log_frag *kunit_log_extend(struct list_head *log) +{
struct kunit_log_frag *frag;
frag = kmalloc(sizeof(*frag), GFP_KERNEL);
if (!frag)
return NULL;
kunit_init_log_frag(frag);
list_add_tail(&frag->list, log);
return frag;
+}
+/* Append formatted message to log, extending the log buffer if necessary. */ +void kunit_log_append(struct list_head *log, const char *fmt, ...) { va_list args;
struct kunit_log_frag *frag; int len, log_len, len_left; if (!log) return;
log_len = strlen(log);
len_left = KUNIT_LOG_SIZE - log_len - 1;
if (len_left <= 0)
return;
frag = list_last_entry(log, struct kunit_log_frag, list);
log_len = strlen(frag->buf);
I was going to wonder whether or not we should cache the length of the current fragment somewhere, but thinking about it, it's probably not worth it given we're only measuring a single fragment, and it's capped at 256 bytes.
len_left = sizeof(frag->buf) - log_len - 1; /* Evaluate length of line to add to log */ va_start(args, fmt); len = vsnprintf(NULL, 0, fmt, args) + 1; va_end(args);
if (len > len_left) {
frag = kunit_log_extend(log);
if (!frag)
return;
len_left = sizeof(frag->buf) - 1;
log_len = 0;
}
/* Print formatted line to the log */ va_start(args, fmt);
vsnprintf(log + log_len, min(len, len_left), fmt, args);
vsnprintf(frag->buf + log_len, min(len, len_left), fmt, args); va_end(args); /* Add newline to end of log if not already present. */
kunit_log_newline(log);
kunit_log_newline(frag);
} EXPORT_SYMBOL_GPL(kunit_log_append);
@@ -359,14 +380,18 @@ void __kunit_do_failed_assertion(struct kunit *test, } 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, struct list_head *log) { spin_lock_init(&test->lock); INIT_LIST_HEAD(&test->resources); test->name = name; test->log = log;
if (test->log)
test->log[0] = '\0';
if (test->log) {
struct kunit_log_frag *frag = list_first_entry(test->log,
struct kunit_log_frag,
list);
frag->buf[0] = '\0';
} test->status = KUNIT_SUCCESS; test->status_comment[0] = '\0';
}
2.30.2
On 9/8/23 13:11, David Gow wrote:
On Tue, 8 Aug 2023 at 20:35, Richard Fitzgerald rf@opensource.cirrus.com wrote:
Re-implement the log buffer as a list of buffer fragments that can be extended as the size of the log info grows.
When using parameterization the test case can run many times and create a large amount of log. It's not really practical to keep increasing the size of the fixed buffer every time a test needs more space. And a big fixed buffer wastes memory.
The original char *log pointer is replaced by a pointer to a list of struct kunit_log_frag, each containing a fixed-size buffer.
kunit_log_append() now attempts to append to the last kunit_log_frag in the list. If there isn't enough space it will append a new kunit_log_frag to the list. This simple implementation does not attempt to completely fill the buffer in every kunit_log_frag.
The 'log' member of kunit_suite, kunit_test_case and kunit_suite must be a pointer because the API of kunit_log() requires that is the same type in all three structs. As kunit.log is a pointer to the 'log' of the current kunit_case, it must be a pointer in the other two structs.
The existing kunit-test.c log tests have been updated to build against the new fragmented log implementation.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
Looks good to me.
A few small notes inline below, mostly around the possibility of either embedding the list_head in the kunit_case struct directly (rather than using a pointer), or of pointing directly to the first fragment, rather than a separately-allocated struct list_head. Neither are showstoppers, though (and if it increases complexity at all, it's possibly premature optimization).
I did start out trying to use the first fragment as the list head. Trouble with this is that the functions in list.h expect to have a dummy list_head node that is only the head, but not an actual list member. It's possible to workaround this but the shenanigans involved is likely to trip someone up later so reverted to doing the list the way the API intended.
For the pointers, I did consider embedding the list_head instead of using a pointer. But then the struct kunit can't refer to the kunit_case list, it can only take it over. There can only be one list head because the ->prev and ->next pointers of the first and last members in the list can only point to one head.
After playing around with it I decided that it wasn't worth trying to avoid the pointers. At least... it wasn't worth spending a lot of time trying to avoid them for an initial implementation.
Maybe some magic with typeof() in the kunit_log() would let us use different types for the members of kunit_suite, kunit_case, kunit? Then the list_head can be directly embedded in the first two but a pointer in kunit?
Otherwise, some test nitpicks and the fact that this will need a trivial rebase due to the module filtering stuff landing in kselftest/kunit.
Reviewed-by: David Gow davidgow@google.com
...
static void kunit_log_newline_test(struct kunit *test) {
struct kunit_log_frag *frag;
kunit_info(test, "Add newline\n"); if (test->log) {
KUNIT_ASSERT_NOT_NULL_MSG(test, strstr(test->log, "Add newline\n"),
"Missing log line, full log:\n%s", test->log);
KUNIT_EXPECT_NULL(test, strstr(test->log, "Add newline\n\n"));
frag = list_first_entry(test->log, struct kunit_log_frag, list);
KUNIT_ASSERT_NOT_NULL_MSG(test, strstr(frag->buf, "Add newline\n"),
"Missing log line, full log:\n%s", frag->buf);
I'm not super thrilled that this only operates on the first fragment. Could we at least note that this is not the "full log" in the assertion message here, and maybe also assert that the log hasn't grown to a second fragment?
The only aim in this first patch is to make sure that kunit-test.c still builds. I've added extra newline test cases in later patches.
...
I was going to wonder whether or not we should cache the length of the current fragment somewhere, but thinking about it, it's probably not worth it given we're only measuring a single fragment, and it's capped at 256 bytes.
Yes, I had the same thought but decided to leave it as something that can be done later. But as you say it's doubtful whether it's worth the extra storage space when the buffer fragments are small. On x86_64 simply adding a length member could add 8 bytes per fragment (because of rounding). If the size of the fragment buffer is capped at 256 we could use single byte for the length and hope the compiler doesn't insert padding between a char and a char[] array.
Take a look at what happens when you log a message to the kernel log and by comparison this kunit logging is super-lightweight.
(I did look at whether we could re-use the existing kernel log implementation but decided it was too heavily hardcoded to how the kernel log works.)
Add test cases for the dynamically-extending log buffer.
kunit_log_extend_test_1() logs a series of numbered lines then tests that the resulting log contains all the lines.
kunit_log_extend_test_2() logs a large number of lines of varying length to create many fragments, then tests that all lines are present.
kunit_log_frag_sized_line_test() logs a line that exactly fills a fragment. This should not cause an extension of the log or truncation of the line.
kunit_log_newline_test() has a new test to append a line that is exactly the length of the available space in the current fragment and check that the resulting log has a trailing '\n'.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- lib/kunit/kunit-test.c | 204 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+)
diff --git a/lib/kunit/kunit-test.c b/lib/kunit/kunit-test.c index 54dc011c8980..48967d12e053 100644 --- a/lib/kunit/kunit-test.c +++ b/lib/kunit/kunit-test.c @@ -7,6 +7,7 @@ */ #include <kunit/test.h> #include <kunit/test-bug.h> +#include <linux/prandom.h>
#include "try-catch-impl.h"
@@ -530,6 +531,31 @@ static struct kunit_suite kunit_resource_test_suite = { .test_cases = kunit_resource_test_cases, };
+static char *get_concatenated_log(struct kunit *test, const struct list_head *log, + int *num_frags) +{ + struct kunit_log_frag *frag; + size_t len = 0; + int frag_count = 0; + char *p; + + list_for_each_entry(frag, log, list) + len += strlen(frag->buf); + + len++; /* for terminating '\0' */ + p = kunit_kmalloc(test, len, GFP_KERNEL); + + list_for_each_entry(frag, log, list) { + strlcat(p, frag->buf, len); + ++frag_count; + } + + if (num_frags) + *num_frags = frag_count; + + return p; +} + static void kunit_log_test(struct kunit *test) { struct kunit_suite suite; @@ -568,7 +594,9 @@ static void kunit_log_test(struct kunit *test)
static void kunit_log_newline_test(struct kunit *test) { + struct kunit_suite suite; struct kunit_log_frag *frag; + char *p;
kunit_info(test, "Add newline\n"); if (test->log) { @@ -576,14 +604,190 @@ static void kunit_log_newline_test(struct kunit *test) KUNIT_ASSERT_NOT_NULL_MSG(test, strstr(frag->buf, "Add newline\n"), "Missing log line, full log:\n%s", frag->buf); KUNIT_EXPECT_NULL(test, strstr(frag->buf, "Add newline\n\n")); + + suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log); + INIT_LIST_HEAD(suite.log); + frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag); + kunit_init_log_frag(frag); + list_add_tail(&frag->list, suite.log); + + /* String that exactly fills fragment leaving no room for \n */ + memset(frag->buf, 0, sizeof(frag->buf)); + memset(frag->buf, 'x', sizeof(frag->buf) - 9); + kunit_log_append(suite.log, "12345678"); + p = get_concatenated_log(test, suite.log, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(p, "x12345678\n"), + "Newline not appended when fragment is full. Log is:\n'%s'", p); } else { kunit_skip(test, "only useful when debugfs is enabled"); } }
+static void kunit_log_extend_test_1(struct kunit *test) +{ +#ifdef CONFIG_KUNIT_DEBUGFS + struct kunit_suite suite; + struct kunit_log_frag *frag; + char line[60]; + char *p, *pn; + size_t len, n; + int num_lines, num_frags, i; + + suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log); + INIT_LIST_HEAD(suite.log); + frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag); + kunit_init_log_frag(frag); + KUNIT_EXPECT_EQ(test, frag->buf[0], '\0'); + list_add_tail(&frag->list, suite.log); + + i = 0; + len = 0; + do { + n = snprintf(line, sizeof(line), + "The quick brown fox jumps over the lazy penguin %d\n", i); + KUNIT_ASSERT_LT(test, n, sizeof(line)); + kunit_log_append(suite.log, line); + ++i; + len += n; + } while (len < (sizeof(frag->buf) * 30)); + num_lines = i; + + p = get_concatenated_log(test, suite.log, &num_frags); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + KUNIT_EXPECT_GT(test, num_frags, 1); + + kunit_info(test, "num lines:%d num_frags:%d total len:%zu\n", + num_lines, num_frags, strlen(p)); + + i = 0; + while ((pn = strchr(p, '\n')) != NULL) { + *pn = '\0'; + snprintf(line, sizeof(line), + "The quick brown fox jumps over the lazy penguin %d", i); + KUNIT_EXPECT_STREQ(test, p, line); + p = pn + 1; + ++i; + } + KUNIT_EXPECT_EQ(test, i, num_lines); +#else + kunit_skip(test, "only useful when debugfs is enabled"); +#endif +} + +static void kunit_log_extend_test_2(struct kunit *test) +{ +#ifdef CONFIG_KUNIT_DEBUGFS + struct kunit_suite suite; + struct kunit_log_frag *frag; + struct rnd_state rnd; + char line[101]; + char *p, *pn; + size_t len; + int num_lines, num_frags, n, i; + + suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log); + INIT_LIST_HEAD(suite.log); + frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag); + kunit_init_log_frag(frag); + KUNIT_EXPECT_EQ(test, frag->buf[0], '\0'); + list_add_tail(&frag->list, suite.log); + + /* Build log line of varying content */ + line[0] = '\0'; + i = 0; + do { + char tmp[9]; + + snprintf(tmp, sizeof(tmp), "%x", i++); + len = strlcat(line, tmp, sizeof(line)); + } while (len < sizeof(line) - 1); + + /* + * Log lines of different lengths until we have created + * many fragments. + * The "randomness" must be repeatable. + */ + prandom_seed_state(&rnd, 3141592653589793238ULL); + i = 0; + len = 0; + num_lines = 0; + do { + kunit_log_append(suite.log, "%s\n", &line[i]); + len += sizeof(line) - i; + num_lines++; + i = prandom_u32_state(&rnd) % (sizeof(line) - 1); + } while (len < (sizeof(frag->buf) * 30)); + + /* There must be more than one buffer fragment now */ + KUNIT_EXPECT_FALSE(test, list_is_singular(suite.log)); + + p = get_concatenated_log(test, suite.log, &num_frags); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + KUNIT_EXPECT_GT(test, num_frags, 1); + + kunit_info(test, "num lines:%d num_frags:%d total len:%zu\n", + num_lines, num_frags, strlen(p)); + + prandom_seed_state(&rnd, 3141592653589793238ULL); + i = 0; + n = 0; + while ((pn = strchr(p, '\n')) != NULL) { + *pn = '\0'; + KUNIT_EXPECT_STREQ(test, p, &line[i]); + p = pn + 1; + n++; + i = prandom_u32_state(&rnd) % (sizeof(line) - 1); + } + KUNIT_EXPECT_EQ_MSG(test, n, num_lines, "Not enough lines."); +#else + kunit_skip(test, "only useful when debugfs is enabled"); +#endif +} + +static void kunit_log_frag_sized_line_test(struct kunit *test) +{ +#ifdef CONFIG_KUNIT_DEBUGFS + struct kunit_suite suite; + struct kunit_log_frag *frag, *src; + + suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log); + INIT_LIST_HEAD(suite.log); + frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag); + kunit_init_log_frag(frag); + KUNIT_EXPECT_EQ(test, frag->buf[0], '\0'); + list_add_tail(&frag->list, suite.log); + + src = kunit_kzalloc(test, sizeof(*src), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, src); + memset(src->buf, 'x', sizeof(src->buf) - 2); + KUNIT_ASSERT_EQ(test, strlen(src->buf), sizeof(src->buf) - 2); + + /* Log a string that exactly fills the fragment */ + kunit_log_append(suite.log, "%s\n", src->buf); + KUNIT_EXPECT_TRUE(test, list_is_singular(suite.log)); + KUNIT_EXPECT_EQ(test, strlen(frag->buf), sizeof(frag->buf) - 1); + strlcat(src->buf, "\n", sizeof(src->buf)); + KUNIT_EXPECT_STREQ(test, frag->buf, src->buf); +#else + kunit_skip(test, "only useful when debugfs is enabled"); +#endif +} + static struct kunit_case kunit_log_test_cases[] = { KUNIT_CASE(kunit_log_test), KUNIT_CASE(kunit_log_newline_test), + KUNIT_CASE(kunit_log_extend_test_1), + KUNIT_CASE(kunit_log_extend_test_2), + KUNIT_CASE(kunit_log_frag_sized_line_test), {} };
On Tue, Aug 8, 2023 at 8:35 AM Richard Fitzgerald rf@opensource.cirrus.com wrote:
Add test cases for the dynamically-extending log buffer.
kunit_log_extend_test_1() logs a series of numbered lines then tests that the resulting log contains all the lines.
kunit_log_extend_test_2() logs a large number of lines of varying length to create many fragments, then tests that all lines are present.
kunit_log_frag_sized_line_test() logs a line that exactly fills a fragment. This should not cause an extension of the log or truncation of the line.
kunit_log_newline_test() has a new test to append a line that is exactly the length of the available space in the current fragment and check that the resulting log has a trailing '\n'.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
Hello!
I am happy to see so many tests in this patch series. I've been working with these patches and the debugfs logs seem to be working well.
However, when I ran the new kunit-log-test tests three of the tests failed: kunit_log_extend_test_1(), kunit_log_extend_test_2(), and kunit_log_newline_test().
The diagnostic info for kunit_log_extend_test_1() reports:
[20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "xxxxxx…xxxx12345678" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 0" … [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "The quick brown fox jumps over the lazy penguin 1" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 4" [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "The quick brown fox jumps over the lazy penguin 2" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 5" … [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:709 [20:55:27] Expected i == num_lines, but [20:55:27] i == 64 (0x40) [20:55:27] num_lines == 141 (0x8d)
So it looks like the log contains a different number of lines than expected which is causing the difference of 3 between expected and what was obtained. Potentially the log is not getting cleared/freed properly in between test cases?
The diagnostic info for kunit_log_extend_test_2() reports:
[20:55:27] # kunit_log_extend_test_2: EXPECTATION FAILED at lib/kunit/kunit-test.c:776 [20:55:27] Expected p == &line[i], but [20:55:27] p == "xxxxx...xxxxx123456780123456789abcdef101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839" [20:55:27] &line[i] == "0123456789abcdef101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839" [20:55:27] # kunit_log_extend_test_2: EXPECTATION FAILED at lib/kunit/kunit-test.c:781 [20:55:27] Expected n == num_lines, but [20:55:27] n == 147 (0x93) [20:55:27] num_lines == 155 (0x9b) [20:55:27] Not enough lines.
Similar difference in the number of lines here.
The diagnostic info for kunit_log_newline_test() reports that the test fails on this line:
KUNIT_EXPECT_EQ(test, p[strlen(p) - 1], '\n');
Let me know if you are seeing similar errors. I can post the full log if that would be helpful.
-Rae
lib/kunit/kunit-test.c | 204 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+)
diff --git a/lib/kunit/kunit-test.c b/lib/kunit/kunit-test.c index 54dc011c8980..48967d12e053 100644 --- a/lib/kunit/kunit-test.c +++ b/lib/kunit/kunit-test.c @@ -7,6 +7,7 @@ */ #include <kunit/test.h> #include <kunit/test-bug.h> +#include <linux/prandom.h>
#include "try-catch-impl.h"
@@ -530,6 +531,31 @@ static struct kunit_suite kunit_resource_test_suite = { .test_cases = kunit_resource_test_cases, };
+static char *get_concatenated_log(struct kunit *test, const struct list_head *log,
int *num_frags)
+{
struct kunit_log_frag *frag;
size_t len = 0;
int frag_count = 0;
char *p;
list_for_each_entry(frag, log, list)
len += strlen(frag->buf);
len++; /* for terminating '\0' */
p = kunit_kmalloc(test, len, GFP_KERNEL);
list_for_each_entry(frag, log, list) {
strlcat(p, frag->buf, len);
++frag_count;
}
if (num_frags)
*num_frags = frag_count;
return p;
+}
static void kunit_log_test(struct kunit *test) { struct kunit_suite suite; @@ -568,7 +594,9 @@ static void kunit_log_test(struct kunit *test)
static void kunit_log_newline_test(struct kunit *test) {
struct kunit_suite suite; struct kunit_log_frag *frag;
char *p; kunit_info(test, "Add newline\n"); if (test->log) {
@@ -576,14 +604,190 @@ static void kunit_log_newline_test(struct kunit *test) KUNIT_ASSERT_NOT_NULL_MSG(test, strstr(frag->buf, "Add newline\n"), "Missing log line, full log:\n%s", frag->buf); KUNIT_EXPECT_NULL(test, strstr(frag->buf, "Add newline\n\n"));
suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log);
INIT_LIST_HEAD(suite.log);
frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag);
kunit_init_log_frag(frag);
list_add_tail(&frag->list, suite.log);
/* String that exactly fills fragment leaving no room for \n */
memset(frag->buf, 0, sizeof(frag->buf));
memset(frag->buf, 'x', sizeof(frag->buf) - 9);
kunit_log_append(suite.log, "12345678");
p = get_concatenated_log(test, suite.log, NULL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(p, "x12345678\n"),
"Newline not appended when fragment is full. Log is:\n'%s'", p); } else { kunit_skip(test, "only useful when debugfs is enabled"); }
}
+static void kunit_log_extend_test_1(struct kunit *test) +{ +#ifdef CONFIG_KUNIT_DEBUGFS
struct kunit_suite suite;
struct kunit_log_frag *frag;
char line[60];
char *p, *pn;
size_t len, n;
int num_lines, num_frags, i;
suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log);
INIT_LIST_HEAD(suite.log);
frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag);
kunit_init_log_frag(frag);
KUNIT_EXPECT_EQ(test, frag->buf[0], '\0');
list_add_tail(&frag->list, suite.log);
i = 0;
len = 0;
do {
n = snprintf(line, sizeof(line),
"The quick brown fox jumps over the lazy penguin %d\n", i);
KUNIT_ASSERT_LT(test, n, sizeof(line));
kunit_log_append(suite.log, line);
++i;
len += n;
} while (len < (sizeof(frag->buf) * 30));
num_lines = i;
p = get_concatenated_log(test, suite.log, &num_frags);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
KUNIT_EXPECT_GT(test, num_frags, 1);
kunit_info(test, "num lines:%d num_frags:%d total len:%zu\n",
num_lines, num_frags, strlen(p));
i = 0;
while ((pn = strchr(p, '\n')) != NULL) {
*pn = '\0';
snprintf(line, sizeof(line),
"The quick brown fox jumps over the lazy penguin %d", i);
KUNIT_EXPECT_STREQ(test, p, line);
p = pn + 1;
++i;
}
KUNIT_EXPECT_EQ(test, i, num_lines);
+#else
kunit_skip(test, "only useful when debugfs is enabled");
+#endif +}
+static void kunit_log_extend_test_2(struct kunit *test) +{ +#ifdef CONFIG_KUNIT_DEBUGFS
struct kunit_suite suite;
struct kunit_log_frag *frag;
struct rnd_state rnd;
char line[101];
char *p, *pn;
size_t len;
int num_lines, num_frags, n, i;
suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log);
INIT_LIST_HEAD(suite.log);
frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag);
kunit_init_log_frag(frag);
KUNIT_EXPECT_EQ(test, frag->buf[0], '\0');
list_add_tail(&frag->list, suite.log);
/* Build log line of varying content */
line[0] = '\0';
i = 0;
do {
char tmp[9];
snprintf(tmp, sizeof(tmp), "%x", i++);
len = strlcat(line, tmp, sizeof(line));
} while (len < sizeof(line) - 1);
/*
* Log lines of different lengths until we have created
* many fragments.
* The "randomness" must be repeatable.
*/
prandom_seed_state(&rnd, 3141592653589793238ULL);
i = 0;
len = 0;
num_lines = 0;
do {
kunit_log_append(suite.log, "%s\n", &line[i]);
len += sizeof(line) - i;
num_lines++;
i = prandom_u32_state(&rnd) % (sizeof(line) - 1);
} while (len < (sizeof(frag->buf) * 30));
/* There must be more than one buffer fragment now */
KUNIT_EXPECT_FALSE(test, list_is_singular(suite.log));
p = get_concatenated_log(test, suite.log, &num_frags);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p);
KUNIT_EXPECT_GT(test, num_frags, 1);
kunit_info(test, "num lines:%d num_frags:%d total len:%zu\n",
num_lines, num_frags, strlen(p));
prandom_seed_state(&rnd, 3141592653589793238ULL);
i = 0;
n = 0;
while ((pn = strchr(p, '\n')) != NULL) {
*pn = '\0';
KUNIT_EXPECT_STREQ(test, p, &line[i]);
p = pn + 1;
n++;
i = prandom_u32_state(&rnd) % (sizeof(line) - 1);
}
KUNIT_EXPECT_EQ_MSG(test, n, num_lines, "Not enough lines.");
+#else
kunit_skip(test, "only useful when debugfs is enabled");
+#endif +}
+static void kunit_log_frag_sized_line_test(struct kunit *test) +{ +#ifdef CONFIG_KUNIT_DEBUGFS
struct kunit_suite suite;
struct kunit_log_frag *frag, *src;
suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log);
INIT_LIST_HEAD(suite.log);
frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag);
kunit_init_log_frag(frag);
KUNIT_EXPECT_EQ(test, frag->buf[0], '\0');
list_add_tail(&frag->list, suite.log);
src = kunit_kzalloc(test, sizeof(*src), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, src);
memset(src->buf, 'x', sizeof(src->buf) - 2);
KUNIT_ASSERT_EQ(test, strlen(src->buf), sizeof(src->buf) - 2);
/* Log a string that exactly fills the fragment */
kunit_log_append(suite.log, "%s\n", src->buf);
KUNIT_EXPECT_TRUE(test, list_is_singular(suite.log));
KUNIT_EXPECT_EQ(test, strlen(frag->buf), sizeof(frag->buf) - 1);
strlcat(src->buf, "\n", sizeof(src->buf));
KUNIT_EXPECT_STREQ(test, frag->buf, src->buf);
+#else
kunit_skip(test, "only useful when debugfs is enabled");
+#endif +}
static struct kunit_case kunit_log_test_cases[] = { KUNIT_CASE(kunit_log_test), KUNIT_CASE(kunit_log_newline_test),
KUNIT_CASE(kunit_log_extend_test_1),
KUNIT_CASE(kunit_log_extend_test_2),
KUNIT_CASE(kunit_log_frag_sized_line_test), {}
};
-- 2.30.2
On 8/8/23 22:16, Rae Moar wrote:
On Tue, Aug 8, 2023 at 8:35 AM Richard Fitzgerald rf@opensource.cirrus.com wrote:
Add test cases for the dynamically-extending log buffer.
kunit_log_extend_test_1() logs a series of numbered lines then tests that the resulting log contains all the lines.
kunit_log_extend_test_2() logs a large number of lines of varying length to create many fragments, then tests that all lines are present.
kunit_log_frag_sized_line_test() logs a line that exactly fills a fragment. This should not cause an extension of the log or truncation of the line.
kunit_log_newline_test() has a new test to append a line that is exactly the length of the available space in the current fragment and check that the resulting log has a trailing '\n'.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
Hello!
I am happy to see so many tests in this patch series. I've been working with these patches and the debugfs logs seem to be working well.
However, when I ran the new kunit-log-test tests three of the tests failed: kunit_log_extend_test_1(), kunit_log_extend_test_2(), and kunit_log_newline_test().
The diagnostic info for kunit_log_extend_test_1() reports:
[20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "xxxxxx…xxxx12345678" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 0" … [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "The quick brown fox jumps over the lazy penguin 1" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 4" [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "The quick brown fox jumps over the lazy penguin 2" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 5" … [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:709 [20:55:27] Expected i == num_lines, but [20:55:27] i == 64 (0x40) [20:55:27] num_lines == 141 (0x8d)
So it looks like the log contains a different number of lines than expected which is causing the difference of 3 between expected and what was obtained. Potentially the log is not getting cleared/freed properly in between test cases?
The diagnostic info for kunit_log_extend_test_2() reports:
[20:55:27] # kunit_log_extend_test_2: EXPECTATION FAILED at lib/kunit/kunit-test.c:776 [20:55:27] Expected p == &line[i], but [20:55:27] p == "xxxxx...xxxxx123456780123456789abcdef101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839" [20:55:27] &line[i] == "0123456789abcdef101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839" [20:55:27] # kunit_log_extend_test_2: EXPECTATION FAILED at lib/kunit/kunit-test.c:781 [20:55:27] Expected n == num_lines, but [20:55:27] n == 147 (0x93) [20:55:27] num_lines == 155 (0x9b) [20:55:27] Not enough lines.
Similar difference in the number of lines here.
The diagnostic info for kunit_log_newline_test() reports that the test fails on this line:
KUNIT_EXPECT_EQ(test, p[strlen(p) - 1], '\n');
Let me know if you are seeing similar errors. I can post the full log if that would be helpful.
-Rae
Ah, I see a bug in get_concatenated_log(). Does this change fix it for you?
len++; /* for terminating '\0' */ - p = kunit_kmalloc(test, len, GFP_KERNEL); + p = kunit_kzalloc(test, len, GFP_KERNEL);
On Wed, 9 Aug 2023 at 17:39, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 8/8/23 22:16, Rae Moar wrote:
On Tue, Aug 8, 2023 at 8:35 AM Richard Fitzgerald rf@opensource.cirrus.com wrote:
Add test cases for the dynamically-extending log buffer.
kunit_log_extend_test_1() logs a series of numbered lines then tests that the resulting log contains all the lines.
kunit_log_extend_test_2() logs a large number of lines of varying length to create many fragments, then tests that all lines are present.
kunit_log_frag_sized_line_test() logs a line that exactly fills a fragment. This should not cause an extension of the log or truncation of the line.
kunit_log_newline_test() has a new test to append a line that is exactly the length of the available space in the current fragment and check that the resulting log has a trailing '\n'.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
Hello!
I am happy to see so many tests in this patch series. I've been working with these patches and the debugfs logs seem to be working well.
However, when I ran the new kunit-log-test tests three of the tests failed: kunit_log_extend_test_1(), kunit_log_extend_test_2(), and kunit_log_newline_test().
The diagnostic info for kunit_log_extend_test_1() reports:
[20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "xxxxxx…xxxx12345678" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 0" … [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "The quick brown fox jumps over the lazy penguin 1" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 4" [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "The quick brown fox jumps over the lazy penguin 2" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 5" … [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:709 [20:55:27] Expected i == num_lines, but [20:55:27] i == 64 (0x40) [20:55:27] num_lines == 141 (0x8d)
So it looks like the log contains a different number of lines than expected which is causing the difference of 3 between expected and what was obtained. Potentially the log is not getting cleared/freed properly in between test cases?
The diagnostic info for kunit_log_extend_test_2() reports:
[20:55:27] # kunit_log_extend_test_2: EXPECTATION FAILED at lib/kunit/kunit-test.c:776 [20:55:27] Expected p == &line[i], but [20:55:27] p == "xxxxx...xxxxx123456780123456789abcdef101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839" [20:55:27] &line[i] == "0123456789abcdef101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839" [20:55:27] # kunit_log_extend_test_2: EXPECTATION FAILED at lib/kunit/kunit-test.c:781 [20:55:27] Expected n == num_lines, but [20:55:27] n == 147 (0x93) [20:55:27] num_lines == 155 (0x9b) [20:55:27] Not enough lines.
Similar difference in the number of lines here.
The diagnostic info for kunit_log_newline_test() reports that the test fails on this line:
KUNIT_EXPECT_EQ(test, p[strlen(p) - 1], '\n');
Let me know if you are seeing similar errors. I can post the full log if that would be helpful.
-Rae
Ah, I see a bug in get_concatenated_log(). Does this change fix it for you?
len++; /* for terminating '\0' */
p = kunit_kmalloc(test, len, GFP_KERNEL);
p = kunit_kzalloc(test, len, GFP_KERNEL);
This fixes what seems to be the same issue for me, under x86_64/qemu.
Thanks, -- David
On 9/8/23 13:11, David Gow wrote:
On Wed, 9 Aug 2023 at 17:39, Richard Fitzgerald rf@opensource.cirrus.com wrote:
On 8/8/23 22:16, Rae Moar wrote:
On Tue, Aug 8, 2023 at 8:35 AM Richard Fitzgerald rf@opensource.cirrus.com wrote:
Add test cases for the dynamically-extending log buffer.
kunit_log_extend_test_1() logs a series of numbered lines then tests that the resulting log contains all the lines.
kunit_log_extend_test_2() logs a large number of lines of varying length to create many fragments, then tests that all lines are present.
kunit_log_frag_sized_line_test() logs a line that exactly fills a fragment. This should not cause an extension of the log or truncation of the line.
kunit_log_newline_test() has a new test to append a line that is exactly the length of the available space in the current fragment and check that the resulting log has a trailing '\n'.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com
Hello!
I am happy to see so many tests in this patch series. I've been working with these patches and the debugfs logs seem to be working well.
However, when I ran the new kunit-log-test tests three of the tests failed: kunit_log_extend_test_1(), kunit_log_extend_test_2(), and kunit_log_newline_test().
The diagnostic info for kunit_log_extend_test_1() reports:
[20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "xxxxxx…xxxx12345678" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 0" … [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "The quick brown fox jumps over the lazy penguin 1" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 4" [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:705 [20:55:27] Expected p == line, but [20:55:27] p == "The quick brown fox jumps over the lazy penguin 2" [20:55:27] line == "The quick brown fox jumps over the lazy penguin 5" … [20:55:27] # kunit_log_extend_test_1: EXPECTATION FAILED at lib/kunit/kunit-test.c:709 [20:55:27] Expected i == num_lines, but [20:55:27] i == 64 (0x40) [20:55:27] num_lines == 141 (0x8d)
So it looks like the log contains a different number of lines than expected which is causing the difference of 3 between expected and what was obtained. Potentially the log is not getting cleared/freed properly in between test cases?
The diagnostic info for kunit_log_extend_test_2() reports:
[20:55:27] # kunit_log_extend_test_2: EXPECTATION FAILED at lib/kunit/kunit-test.c:776 [20:55:27] Expected p == &line[i], but [20:55:27] p == "xxxxx...xxxxx123456780123456789abcdef101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839" [20:55:27] &line[i] == "0123456789abcdef101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f30313233343536373839" [20:55:27] # kunit_log_extend_test_2: EXPECTATION FAILED at lib/kunit/kunit-test.c:781 [20:55:27] Expected n == num_lines, but [20:55:27] n == 147 (0x93) [20:55:27] num_lines == 155 (0x9b) [20:55:27] Not enough lines.
Similar difference in the number of lines here.
The diagnostic info for kunit_log_newline_test() reports that the test fails on this line:
KUNIT_EXPECT_EQ(test, p[strlen(p) - 1], '\n');
Let me know if you are seeing similar errors. I can post the full log if that would be helpful.
-Rae
Ah, I see a bug in get_concatenated_log(). Does this change fix it for you?
len++; /* for terminating '\0' */
p = kunit_kmalloc(test, len, GFP_KERNEL);
p = kunit_kzalloc(test, len, GFP_KERNEL);
This fixes what seems to be the same issue for me, under x86_64/qemu.
Thanks, -- David
Good. It seems that the various memory security options have the side-effect of covering up this bug. I don't know which one exactly (I've just turned them all off). I had been testing with them on.
I'll send a V3.
Add handling to kunit_log_append() for log messages that are longer than a single buffer fragment.
The initial implementation of fragmented buffers did not change the logic of the original kunit_log_append(). A consequence was that it still had the original assumption that a log line will fit into one buffer.
This patch checks for log messages that are larger than one fragment buffer. In that case, kvasprintf() is used to format it into a temporary buffer and that content is then split across as many fragments as necessary.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- lib/kunit/test.c | 65 +++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-)
diff --git a/lib/kunit/test.c b/lib/kunit/test.c index bdb361741214..b00f077314e3 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -140,25 +140,82 @@ static struct kunit_log_frag *kunit_log_extend(struct list_head *log) return frag; }
+static void kunit_log_append_string(struct list_head *log, const char *src) +{ + struct kunit_log_frag *frag, *new_frag; + int log_len, bytes_left; + ssize_t written; + char *p; + + frag = list_last_entry(log, struct kunit_log_frag, list); + log_len = strlen(frag->buf); + bytes_left = sizeof(frag->buf) - log_len; + + written = strscpy(frag->buf + log_len, src, bytes_left); + if (written != -E2BIG) + goto newline; + + src += bytes_left - 1; + do { + new_frag = kunit_log_extend(log); + if (!new_frag) + goto newline; + + frag = new_frag; + written = strscpy(frag->buf, src, sizeof(frag->buf)); + src += sizeof(frag->buf) - 1; + } while (written == -E2BIG); + +newline: + if (written == -E2BIG) + written = strlen(frag->buf); + + p = &frag->buf[written - 1]; + if (*p != '\n') { + if (strlcat(frag->buf, "\n", sizeof(frag->buf)) >= sizeof(frag->buf)) { + frag = kunit_log_extend(log); + if (!frag) { + *p = '\n'; + return; + } + + frag->buf[0] = '\n'; + frag->buf[1] = '\0'; + } + } +} + /* Append formatted message to log, extending the log buffer if necessary. */ void kunit_log_append(struct list_head *log, const char *fmt, ...) { va_list args; struct kunit_log_frag *frag; int len, log_len, len_left; + char *tmp = NULL;
if (!log) return;
- frag = list_last_entry(log, struct kunit_log_frag, list); - log_len = strlen(frag->buf); - len_left = sizeof(frag->buf) - log_len - 1; - /* Evaluate length of line to add to log */ va_start(args, fmt); len = vsnprintf(NULL, 0, fmt, args) + 1; va_end(args);
+ if (len > sizeof(frag->buf) - 1) { + va_start(args, fmt); + tmp = kvasprintf(GFP_KERNEL, fmt, args); + va_end(args); + + kunit_log_append_string(log, tmp); + kfree(tmp); + + return; + } + + frag = list_last_entry(log, struct kunit_log_frag, list); + log_len = strlen(frag->buf); + len_left = sizeof(frag->buf) - log_len - 1; + if (len > len_left) { frag = kunit_log_extend(log); if (!frag)
Add kunit_log_long_line_test() to test that logging a line longer than the buffer fragment size doesn't truncate the line.
Add extra tests to kunit_log_newline_test() for lines longer than the buffer fragment size.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- lib/kunit/kunit-test.c | 84 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-)
diff --git a/lib/kunit/kunit-test.c b/lib/kunit/kunit-test.c index 48967d12e053..621acdb5385e 100644 --- a/lib/kunit/kunit-test.c +++ b/lib/kunit/kunit-test.c @@ -596,7 +596,7 @@ static void kunit_log_newline_test(struct kunit *test) { struct kunit_suite suite; struct kunit_log_frag *frag; - char *p; + char *p, *line;
kunit_info(test, "Add newline\n"); if (test->log) { @@ -621,6 +621,33 @@ static void kunit_log_newline_test(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); KUNIT_EXPECT_NOT_NULL_MSG(test, strstr(p, "x12345678\n"), "Newline not appended when fragment is full. Log is:\n'%s'", p); + kunit_kfree(test, p); + + /* String that is much longer than a fragment */ + line = kunit_kzalloc(test, sizeof(frag->buf) * 6, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, line); + memset(line, 'x', (sizeof(frag->buf) * 6) - 1); + kunit_log_append(suite.log, "%s", line); + p = get_concatenated_log(test, suite.log, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + KUNIT_EXPECT_EQ(test, p[strlen(p) - 1], '\n'); + KUNIT_EXPECT_NULL(test, strstr(p, "\n\n")); + kunit_kfree(test, p); + + kunit_log_append(suite.log, "%s\n", line); + p = get_concatenated_log(test, suite.log, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + KUNIT_EXPECT_EQ(test, p[strlen(p) - 1], '\n'); + KUNIT_EXPECT_NULL(test, strstr(p, "\n\n")); + kunit_kfree(test, p); + + line[strlen(line) - 1] = '\n'; + kunit_log_append(suite.log, "%s", line); + p = get_concatenated_log(test, suite.log, NULL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + KUNIT_EXPECT_EQ(test, p[strlen(p) - 1], '\n'); + KUNIT_EXPECT_NULL(test, strstr(p, "\n\n")); + kunit_kfree(test, p); } else { kunit_skip(test, "only useful when debugfs is enabled"); } @@ -782,12 +809,67 @@ static void kunit_log_frag_sized_line_test(struct kunit *test) #endif }
+static void kunit_log_long_line_test(struct kunit *test) +{ +#ifdef CONFIG_KUNIT_DEBUGFS + struct kunit_suite suite; + struct kunit_log_frag *frag; + struct rnd_state rnd; + char *line, *p, *pn; + size_t line_buf_size, len; + int num_frags, i; + + suite.log = kunit_kzalloc(test, sizeof(*suite.log), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, suite.log); + INIT_LIST_HEAD(suite.log); + frag = kunit_kmalloc(test, sizeof(*frag), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, frag); + kunit_init_log_frag(frag); + KUNIT_EXPECT_EQ(test, frag->buf[0], '\0'); + list_add_tail(&frag->list, suite.log); + + /* Create a very long string to be logged */ + line_buf_size = sizeof(frag->buf) * 6; + line = kunit_kmalloc(test, line_buf_size, GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, line); + line[0] = '\0'; + + prandom_seed_state(&rnd, 3141592653589793238ULL); + len = 0; + do { + static const char fill[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + i = prandom_u32_state(&rnd) % (sizeof(fill) - 1); + len = strlcat(line, &fill[i], line_buf_size); + } while (len < line_buf_size); + + kunit_log_append(suite.log, "%s\n", line); + + p = get_concatenated_log(test, suite.log, &num_frags); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p); + KUNIT_EXPECT_GT(test, num_frags, 1); + + kunit_info(test, "num_frags:%d total len:%zu\n", num_frags, strlen(p)); + + /* Don't compare the trailing '\n' */ + pn = strrchr(p, '\n'); + KUNIT_EXPECT_NOT_ERR_OR_NULL(test, pn); + *pn = '\0'; + KUNIT_EXPECT_EQ(test, strlen(p), strlen(line)); + KUNIT_EXPECT_STREQ(test, p, line); +#else + kunit_skip(test, "only useful when debugfs is enabled"); +#endif +} + static struct kunit_case kunit_log_test_cases[] = { KUNIT_CASE(kunit_log_test), KUNIT_CASE(kunit_log_newline_test), KUNIT_CASE(kunit_log_extend_test_1), KUNIT_CASE(kunit_log_extend_test_2), KUNIT_CASE(kunit_log_frag_sized_line_test), + KUNIT_CASE(kunit_log_long_line_test), {} };
Add a test that logging a string containing only a newline appends one newline to the log.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- lib/kunit/kunit-test.c | 6 ++++++ 1 file changed, 6 insertions(+)
diff --git a/lib/kunit/kunit-test.c b/lib/kunit/kunit-test.c index 621acdb5385e..ce80fb42128c 100644 --- a/lib/kunit/kunit-test.c +++ b/lib/kunit/kunit-test.c @@ -613,6 +613,12 @@ static void kunit_log_newline_test(struct kunit *test) kunit_init_log_frag(frag); list_add_tail(&frag->list, suite.log);
+ /* Log only a newline */ + kunit_log_append(suite.log, "\n"); + KUNIT_EXPECT_TRUE(test, list_is_singular(suite.log)); + KUNIT_EXPECT_STREQ(test, frag->buf, "\n"); + frag->buf[0] = '\0'; + /* String that exactly fills fragment leaving no room for \n */ memset(frag->buf, 0, sizeof(frag->buf)); memset(frag->buf, 'x', sizeof(frag->buf) - 9);
It's wasteful to call vsnprintf() only to figure out the length of the string. The string might fit in the available buffer space but we have to vsnprintf() again to do that.
Instead, attempt to format the string to the available buffer at the same time as finding the string length. Only if the string didn't fit the available space is it necessary to extend the log and format the string again to a new fragment buffer.
Signed-off-by: Richard Fitzgerald rf@opensource.cirrus.com --- lib/kunit/test.c | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-)
diff --git a/lib/kunit/test.c b/lib/kunit/test.c index b00f077314e3..e7f0a46d184a 100644 --- a/lib/kunit/test.c +++ b/lib/kunit/test.c @@ -196,11 +196,21 @@ void kunit_log_append(struct list_head *log, const char *fmt, ...) if (!log) return;
- /* Evaluate length of line to add to log */ + frag = list_last_entry(log, struct kunit_log_frag, list); + log_len = strlen(frag->buf); + len_left = sizeof(frag->buf) - log_len - 1; + + /* Attempt to print formatted line to current fragment */ va_start(args, fmt); - len = vsnprintf(NULL, 0, fmt, args) + 1; + len = vsnprintf(frag->buf + log_len, len_left, fmt, args) + 1; va_end(args);
+ if (len <= len_left) + goto out_newline; + + /* Abandon the truncated result of vsnprintf */ + frag->buf[log_len] = '\0'; + if (len > sizeof(frag->buf) - 1) { va_start(args, fmt); tmp = kvasprintf(GFP_KERNEL, fmt, args); @@ -212,24 +222,15 @@ void kunit_log_append(struct list_head *log, const char *fmt, ...) return; }
- frag = list_last_entry(log, struct kunit_log_frag, list); - log_len = strlen(frag->buf); - len_left = sizeof(frag->buf) - log_len - 1; - - if (len > len_left) { - frag = kunit_log_extend(log); - if (!frag) - return; - - len_left = sizeof(frag->buf) - 1; - log_len = 0; - } + frag = kunit_log_extend(log); + if (!frag) + return;
/* Print formatted line to the log */ va_start(args, fmt); - vsnprintf(frag->buf + log_len, min(len, len_left), fmt, args); + vsnprintf(frag->buf, sizeof(frag->buf) - 1, fmt, args); va_end(args); - +out_newline: /* Add newline to end of log if not already present. */ kunit_log_newline(frag); }
linux-kselftest-mirror@lists.linaro.org