Re: [PATCH] kunit: Replace fixed-size log with dynamically-extending buffer

From: Richard Fitzgerald
Date: Mon Aug 07 2023 - 13:58:33 EST


On 5/8/23 10:17, David Gow wrote:
On Fri, 4 Aug 2023 at 22:14, Richard Fitzgerald
<rf@xxxxxxxxxxxxxxxxxxxxx> 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 that 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 for the new log
buffer and a new kunit_log_extend_test() case to test extending the buffer.

Signed-off-by: Richard Fitzgerald <rf@xxxxxxxxxxxxxxxxxxxxx>
---

+Rae, since she's the last person to touch the log code.

Thanks a bunch. Some way of handling longer logs has been on the to-do
list for a while, and this is probably better than the other options
of allowing per-test log sizes or using krealloc().

This mostly works okay here, but the test fails when running:
./tools/testing/kunit/kunit.py run --alltests
[17:05:12] # kunit_log_extend_test: EXPECTATION FAILED at
lib/kunit/kunit-test.c:624
[17:05:12] Expected sscanf(p, "The quick brown fox jumps over the
lazy penguin %d", &q) == 1, but
[17:05:12] sscanf(p, "The quick brown fox jumps over the lazy
penguin %d", &q) == 0 (0x0)

I haven't found the cause of it, yet, but it happens both on UML and
QEMU x86_64.


Strange, I haven't been able to reproduce this. But sscanf() isn't the
best function to use here because of how it treats whitespace.
'\n' doesn't match a newline, it mtaches any whitespace.
I'm working on a V2 chain, and one change I've made is to replace the
sscanf() with an exact match of what the string should be. I've also
added some test cases.

include/kunit/test.h | 25 +++++++++----
lib/kunit/debugfs.c | 65 ++++++++++++++++++++++++++-------
lib/kunit/kunit-test.c | 82 +++++++++++++++++++++++++++++++++++++-----
lib/kunit/test.c | 51 ++++++++++++++++----------
4 files changed, 177 insertions(+), 46 deletions(-)

diff --git a/include/kunit/test.h b/include/kunit/test.h
index 011e0d6bb506..907b30401669 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
+/* Initial size of log associated with test. */
+#define KUNIT_DEFAULT_LOG_SIZE 500

Would it make more sense to call this KUNIT_LOG_FRAGMENT_SIZE or
similar, as not only is this the default size, but also the amount by
which the size is incremented.

Also, there's plenty of scope for bikeshedding the exact value of
this, two considerations being:
- Most tests have very small logs: we possibly should look into what
the average log size is.
- It may make sense to tweak the log size to fit nicely within common
allocation/page sizes. e.g., PAGE_SIZE-sizeof(struct list_head) or
something. Though that is probably too big by default.


This initial implementation doesn't support splitting the log line over
multiple fragments (I like to avoid rewirting the whole world in one
patch.) So the fragment size also limits the maximum log line.

I've done some work in supporting long lines and will include it in
my V2 chain.

...

+ i = 0;
+ while ((pn = strchr(p, '\n')) != NULL) {
+ *pn = '\0';
+ KUNIT_ASSERT_EQ(test, sscanf(p,
+ "The quick brown fox jumps over the lazy penguin %d\n",

Do we want the '\n' at the end of this if we're overwriting it above?


Oh, that's an embarrassing error. But see my comment above about
sscanf().

+ &q), 1);
+ KUNIT_ASSERT_EQ(test, q, i);

Could we make these KUNIT_EXPECT_EQ? I'd rather not have this kill the
test thread.


Yes, that's a typo.
Personally, I find the choice of ASSERT/EXPECT confusing. Since people
are so used to _asserting_ the condition to be checked, the brain tends
to go for ASSERT out of habit. IMO it would have been better to use
ASSERT for the thing the test is checking (for consistency with normal
usage) and maybe KUNIT_NEEDS_xxx() for things that are prerequisites of
the test code itself.