[Concept,06/17] test: Add memory leak checking option to ut command

Message ID 20260316183050.3855921-7-sjg@u-boot.org
State New
Headers
Series Add automatic memory-leak detection to U-Boot tests |

Commit Message

Simon Glass March 16, 2026, 6:30 p.m. UTC
  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(-)
  

Patch

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;