[Concept,12/33] test: Save and restore global DM state around UTF_DM tests
Commit Message
From: Simon Glass <sjg@chromium.org>
dm_test_pre_run() sets gd->dm_root to NULL and calls dm_init(), which
resets the uclass list head via INIT_LIST_HEAD(). That abandons the
global driver-model state built by sandbox at boot without freeing
it, leaving every uclass and device orphaned in the heap. At the end
of each ut_run_list(), dm_test_restore() rebuilds a fresh global
state with another dm_init() + dm_scan_plat() + dm_extended_scan(),
which the next test's dm_test_pre_run() also orphans. Each 'ut'
command therefore leaks several hundred allocations, and a long
pytest session eventually exhausts the mcheck registry.
The documented intent (see comment in test_pre_run()) is that UTF_DM
tests run against a scratch state and leave the global state
untouched. Implement that: snapshot gd->dm_root, gd->uclass_root_s,
and gd->uclass_root before wiping them in dm_test_pre_run(), and
write the snapshot back at the end of dm_test_post_run() once the
test's uclasses have been destroyed. The saved uclasses'
sibling_node pointers still reference the same &gd->uclass_root_s
address, so restoring the list head re-links the list without any
fix-up. The uclass_root pointer itself must also be restored since
tests like dm_test_uclass_before_ready zero it explicitly.
dm_test_restore() no longer needs to rebuild anything and only
resets the live-tree root.
Signed-off-by: Simon Glass <sjg@chromium.org>
---
include/test/test.h | 10 ++++++++++
test/test-main.c | 40 +++++++++++++++++++++++++++++++---------
2 files changed, 41 insertions(+), 9 deletions(-)
@@ -9,6 +9,7 @@
#include <abuf.h>
#include <malloc.h>
#include <linux/bitops.h>
+#include <linux/list.h>
#define UT_MAX_ARGS 8
#define UT_PRIV_SIZE 256
@@ -78,6 +79,12 @@ struct ut_arg {
* @start: Store the starting mallinfo when doing leak test
* @of_live: true to use livetree if available, false to use flattree
* @of_root: Record of the livetree root node (used for setting up tests)
+ * @saved_dm_root: Global dm_root swapped out while a UTF_DM test runs
+ * @saved_uclass_root: Global uclass-root list head swapped out while a
+ * UTF_DM test runs, so the pre-test state can be restored afterwards
+ * @saved_uclass_root_ptr: Saved value of gd->uclass_root (the pointer to
+ * the uclass-root list head). Restored alongside the list head above
+ * in case a test zeros the pointer (e.g. dm_test_uclass_before_ready)
* @root: Root device
* @testdev: Test device
* @force_fail_alloc: Force all memory allocs to fail
@@ -115,6 +122,9 @@ struct unit_test_state {
int worst_ms;
struct mallinfo start;
struct device_node *of_root;
+ struct udevice *saved_dm_root;
+ struct list_head saved_uclass_root;
+ struct list_head *saved_uclass_root_ptr;
bool of_live;
struct udevice *root;
struct udevice *testdev;
@@ -263,7 +263,20 @@ static int dm_test_pre_run(struct unit_test_state *uts)
if (fdt_action() == FDTCHK_CHECKSUM)
uts->fdt_chksum = crc8(0, gd->fdt_blob,
fdt_totalsize(gd->fdt_blob));
+
+ /*
+ * Save the global driver-model state so it can be restored after the
+ * test. Snapshot the uclass list head and the pointer to it, and
+ * detach the existing list. The old uclasses are left intact in
+ * memory but are no longer reachable, so a fresh dm_init() can build
+ * up a private list for the test without disturbing them.
+ */
+ uts->saved_dm_root = gd->dm_root;
+ uts->saved_uclass_root = gd->uclass_root_s;
+ uts->saved_uclass_root_ptr = gd->uclass_root;
gd->dm_root = NULL;
+ INIT_LIST_HEAD(&gd->uclass_root_s);
+
malloc_disable_testing();
if (CONFIG_IS_ENABLED(UT_DM) && !CONFIG_IS_ENABLED(OF_PLATDATA))
memset(dm_testdrv_op_count, '\0', sizeof(dm_testdrv_op_count));
@@ -330,6 +343,19 @@ static int dm_test_post_run(struct unit_test_state *uts)
}
}
+ /*
+ * Restore the global driver-model state that was saved by
+ * dm_test_pre_run(). Writing the saved list_head back reconnects
+ * the saved list because the uclasses' sibling_node pointers still
+ * reference &gd->uclass_root_s at the same address. A test may have
+ * zeroed gd->uclass_root (e.g. dm_test_uclass_before_ready) so put
+ * the pointer back as well.
+ */
+ gd->dm_root = uts->saved_dm_root;
+ gd->uclass_root_s = uts->saved_uclass_root;
+ gd->uclass_root = uts->saved_uclass_root_ptr;
+ uts->saved_dm_root = NULL;
+
return 0;
}
@@ -434,21 +460,17 @@ static bool ut_list_has_dm_tests(struct unit_test *tests, int count,
/**
* dm_test_restore() Put things back to normal so sandbox works as expected
*
+ * dm_test_pre_run()/dm_test_post_run() save and restore the global driver
+ * model state across each UTF_DM test, so the global root is already back
+ * in place by the time we get here. Only restore the live-tree root, which
+ * per-test setup leaves pointing at the test's own tree.
+ *
* @of_root: Value to set for of_root
* Return: 0 if OK, -ve on error
*/
static int dm_test_restore(struct device_node *of_root)
{
- int ret;
-
gd_set_of_root(of_root);
- gd->dm_root = NULL;
- ret = dm_init(CONFIG_IS_ENABLED(OF_LIVE));
- if (ret)
- return ret;
- dm_scan_plat(false);
- if (!CONFIG_IS_ENABLED(OF_PLATDATA))
- dm_extended_scan(false);
return 0;
}