From: Simon Glass <sjg@chromium.org> Add a -L flag to the ut command that checks for memory leaks around each test. When enabled, mallinfo() is called at the start and end of ut_run_test() and any difference in allocated bytes is reported. This is useful for finding memory leaks in driver model and other subsystems that should be fully cleaned up between tests. Usage: ut -L dm [test_name] Signed-off-by: Simon Glass <sjg@chromium.org> --- doc/develop/malloc.rst | 22 +++++++++++++++++++++- include/test/test.h | 2 ++ test/cmd_ut.c | 8 +++++++- test/test-main.c | 15 +++++++++++++++ 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/doc/develop/malloc.rst b/doc/develop/malloc.rst index 7e46c05dfde..74acc3220af 100644 --- a/doc/develop/malloc.rst +++ b/doc/develop/malloc.rst @@ -500,9 +500,29 @@ by checking ``malloc_get_info()`` before and after:: assert(before.malloc_count == after.malloc_count); assert(before.in_use_bytes == after.in_use_bytes); +**Automatic leak checking with ut -L** + +The ``ut`` command accepts a ``-L`` flag that checks for memory leaks around +each test. It takes a ``mallinfo()`` snapshot at the start of ``ut_run_test()`` +and compares it at the end, after both ``test_pre_run()`` and +``test_post_run()`` have completed:: + + => ut -L dm dm_test_acpi_bgrt + Test: acpi_bgrt: acpi.c + Leak: 448 bytes: acpi_bgrt + ... + +When using uman, pass ``--leak-check``:: + + $ um t --leak-check dm_test_acpi_bgrt + +This makes it easy to scan an entire test suite for leaks:: + + $ um t --leak-check -V dm + **Practical workflow** -1. Add ``ut_check_delta()`` assertions to your test to detect leaks +1. Run ``um t --leak-check -V dm`` (or another suite) to find leaky tests 2. When a leak is detected, add ``malloc_dump_to_file()`` calls before and after the leaking operation 3. Run the test and compare the dump files to identify leaked allocations diff --git a/include/test/test.h b/include/test/test.h index b81cab4d7a4..7d28948eb5f 100644 --- a/include/test/test.h +++ b/include/test/test.h @@ -102,6 +102,7 @@ struct ut_arg { * @arg_error: Set if ut_str/int/bool() detects a type mismatch * @keep_record: Preserve console recording when ut_fail() is called * @emit_result: Emit result line after each test completes + * @leak_check: Check for memory leaks around each test * @video_ctx: Vidconsole context for test message display (allocated on use) * @video_save: Saved framebuffer region for video tests * @priv: Private data for tests to use as needed @@ -139,6 +140,7 @@ struct unit_test_state { bool arg_error; bool keep_record; bool emit_result; + bool leak_check; void *video_ctx; struct abuf video_save; char priv[UT_PRIV_SIZE]; diff --git a/test/cmd_ut.c b/test/cmd_ut.c index f8ff02bdbc7..7de10b32065 100644 --- a/test/cmd_ut.c +++ b/test/cmd_ut.c @@ -258,6 +258,7 @@ static int do_ut(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) bool force_run = false; bool keep_record = false; bool emit_result = false; + bool leak_check = false; int runs_per_text = 1; int workers = 0, worker_id = 0; struct suite *ste; @@ -283,6 +284,9 @@ static int do_ut(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) case 'm': force_run = true; break; + case 'L': + leak_check = true; + break; case 'I': test_insert = str + 1; if (!strchr(test_insert, ':')) @@ -316,6 +320,7 @@ next_arg: ut_init_state(&uts); uts.keep_record = keep_record; uts.emit_result = emit_result; + uts.leak_check = leak_check; uts.workers = workers; uts.worker_id = worker_id; name = argv[0]; @@ -363,9 +368,10 @@ next_arg: } U_BOOT_LONGHELP(ut, - "[-Efmrs] [-R] [-I<n>:<one_test>] [-P<n>:<w>] <suite> [<test> [<args>...]]\n" + "[-ELfmrs] [-R] [-I<n>:<one_test>] [-P<n>:<w>] <suite> [<test> [<args>...]]\n" " - run unit tests\n" " -E Emit result line after each test\n" + " -L Check for memory leaks around each test\n" " -r<runs> Number of times to run each test\n" " -f/-m Force 'manual' tests to run as well\n" " -I Test to run after <n> other tests have run\n" diff --git a/test/test-main.c b/test/test-main.c index 77223cfbcb7..be13084ed92 100644 --- a/test/test-main.c +++ b/test/test-main.c @@ -631,6 +631,7 @@ static int ut_run_test(struct unit_test_state *uts, struct unit_test *test, { const char *fname = strrchr(test->file, '/') + 1; const char *note = ""; + struct mallinfo leak_before; int old_fail_count; int ret; @@ -638,6 +639,9 @@ static int ut_run_test(struct unit_test_state *uts, struct unit_test *test, note = " (flat tree)"; printf("Test: %s: %s%s\n", test_name, fname, note); + if (uts->leak_check) + leak_before = mallinfo(); + /* Allow access to test state from drivers */ ut_set_state(uts); @@ -659,6 +663,17 @@ static int ut_run_test(struct unit_test_state *uts, struct unit_test *test, ut_set_state(NULL); + if (uts->leak_check) { + struct mallinfo leak_after; + int diff; + + leak_after = mallinfo(); + diff = leak_after.uordblks - leak_before.uordblks; + if (diff) + printf("Leak: %d bytes: %s%s\n", diff, test_name, + note); + } + if (uts->emit_result) { bool passed = uts->cur.fail_count == old_fail_count; -- 2.43.0