Parcourir la source

Merge branch '2021-03-12-test-improvements' into next

- Assorted improvements to the pytest framework
Tom Rini il y a 3 ans
Parent
commit
c57ec2c2ba

+ 5 - 3
arch/sandbox/cpu/os.c

@@ -777,7 +777,7 @@ int os_jump_to_image(const void *dest, int size)
 	return os_jump_to_file(fname);
 }
 
-int os_find_u_boot(char *fname, int maxlen)
+int os_find_u_boot(char *fname, int maxlen, bool use_img)
 {
 	struct sandbox_state *state = state_get_current();
 	const char *progname = state->argv[0];
@@ -801,8 +801,8 @@ int os_find_u_boot(char *fname, int maxlen)
 			return 0;
 		}
 
-		/* Look for 'u-boot-tpl' in the tpl/ directory */
-		p = strstr(fname, "/tpl/");
+		/* Look for 'u-boot-spl' in the spl/ directory */
+		p = strstr(fname, "/spl/");
 		if (p) {
 			p[1] = 's';
 			fd = os_open(fname, O_RDONLY);
@@ -829,6 +829,8 @@ int os_find_u_boot(char *fname, int maxlen)
 	if (p) {
 		/* Remove the "spl" characters */
 		memmove(p, p + 4, strlen(p + 4) + 1);
+		if (use_img)
+			strcat(p, ".img");
 		fd = os_open(fname, O_RDONLY);
 		if (fd >= 0) {
 			close(fd);

+ 6 - 3
arch/sandbox/cpu/spl.c

@@ -13,7 +13,7 @@
 #include <asm/global_data.h>
 #include <asm/spl.h>
 #include <asm/state.h>
-#include <test/test.h>
+#include <test/ut.h>
 
 DECLARE_GLOBAL_DATA_PTR;
 
@@ -37,7 +37,7 @@ static int spl_board_load_image(struct spl_image_info *spl_image,
 	char fname[256];
 	int ret;
 
-	ret = os_find_u_boot(fname, sizeof(fname));
+	ret = os_find_u_boot(fname, sizeof(fname), false);
 	if (ret) {
 		printf("(%s not found, error %d)\n", fname, ret);
 		return ret;
@@ -63,9 +63,12 @@ void spl_board_init(void)
 	preloader_console_init();
 
 	if (state->run_unittests) {
+		struct unit_test *tests = UNIT_TEST_ALL_START();
+		const int count = UNIT_TEST_ALL_COUNT();
 		int ret;
 
-		ret = dm_test_main(state->select_unittests);
+		ret = ut_run_list("spl", NULL, tests, count,
+				  state->select_unittests);
 		/* continue execution into U-Boot */
 	}
 }

+ 0 - 1
arch/sandbox/cpu/start.c

@@ -491,7 +491,6 @@ int main(int argc, char *argv[])
 	gd->reloc_off = (ulong)gd->arch.text_base;
 
 	/* sandbox test: log functions called before log_init in board_init_f */
-	log_info("sandbox: starting...\n");
 	log_debug("debug: %s\n", __func__);
 
 	/* Do pre- and post-relocation init */

+ 2 - 1
common/spl/spl.c

@@ -16,6 +16,7 @@
 #include <init.h>
 #include <irq_func.h>
 #include <log.h>
+#include <mapmem.h>
 #include <serial.h>
 #include <spl.h>
 #include <asm/global_data.h>
@@ -168,7 +169,7 @@ __weak void spl_board_prepare_for_boot(void)
 
 __weak struct image_header *spl_get_load_buffer(ssize_t offset, size_t size)
 {
-	return (struct image_header *)(CONFIG_SYS_TEXT_BASE + offset);
+	return map_sysmem(CONFIG_SYS_TEXT_BASE + offset, 0);
 }
 
 void spl_set_header_raw_uboot(struct spl_image_info *spl_image)

+ 15 - 12
common/spl/spl_fit.c

@@ -11,6 +11,7 @@
 #include <image.h>
 #include <log.h>
 #include <malloc.h>
+#include <mapmem.h>
 #include <spl.h>
 #include <sysinfo.h>
 #include <asm/cache.h>
@@ -235,11 +236,11 @@ static int spl_load_fit_image(struct spl_load_info *info, ulong sector,
 	size_t length;
 	int len;
 	ulong size;
-	ulong load_addr, load_ptr;
+	ulong load_addr;
+	void *load_ptr;
 	void *src;
 	ulong overhead;
 	int nr_sectors;
-	int align_len = ARCH_DMA_MINALIGN - 1;
 	uint8_t image_comp = -1, type = -1;
 	const void *data;
 	const void *fit = ctx->fit;
@@ -269,11 +270,13 @@ static int spl_load_fit_image(struct spl_load_info *info, ulong sector,
 	}
 
 	if (external_data) {
+		void *src_ptr;
+
 		/* External data */
 		if (fit_image_get_data_size(fit, node, &len))
 			return -ENOENT;
 
-		load_ptr = (load_addr + align_len) & ~align_len;
+		src_ptr = map_sysmem(ALIGN(load_addr, ARCH_DMA_MINALIGN), len);
 		length = len;
 
 		overhead = get_aligned_image_overhead(info, offset);
@@ -281,12 +284,12 @@ static int spl_load_fit_image(struct spl_load_info *info, ulong sector,
 
 		if (info->read(info,
 			       sector + get_aligned_image_offset(info, offset),
-			       nr_sectors, (void *)load_ptr) != nr_sectors)
+			       nr_sectors, src_ptr) != nr_sectors)
 			return -EIO;
 
-		debug("External data: dst=%lx, offset=%x, size=%lx\n",
-		      load_ptr, offset, (unsigned long)length);
-		src = (void *)load_ptr + overhead;
+		debug("External data: dst=%p, offset=%x, size=%lx\n",
+		      src_ptr, offset, (unsigned long)length);
+		src = src_ptr + overhead;
 	} else {
 		/* Embedded data */
 		if (fit_image_get_data(fit, node, &data, &length)) {
@@ -295,7 +298,7 @@ static int spl_load_fit_image(struct spl_load_info *info, ulong sector,
 		}
 		debug("Embedded data: dst=%lx, size=%lx\n", load_addr,
 		      (unsigned long)length);
-		src = (void *)data;
+		src = (void *)data;	/* cast away const */
 	}
 
 	if (CONFIG_IS_ENABLED(FIT_SIGNATURE)) {
@@ -309,16 +312,16 @@ static int spl_load_fit_image(struct spl_load_info *info, ulong sector,
 	if (CONFIG_IS_ENABLED(FIT_IMAGE_POST_PROCESS))
 		board_fit_image_post_process(&src, &length);
 
+	load_ptr = map_sysmem(load_addr, length);
 	if (IS_ENABLED(CONFIG_SPL_GZIP) && image_comp == IH_COMP_GZIP) {
 		size = length;
-		if (gunzip((void *)load_addr, CONFIG_SYS_BOOTM_LEN,
-			   src, &size)) {
+		if (gunzip(load_ptr, CONFIG_SYS_BOOTM_LEN, src, &size)) {
 			puts("Uncompressing error\n");
 			return -EIO;
 		}
 		length = size;
 	} else {
-		memcpy((void *)load_addr, src, length);
+		memcpy(load_ptr, src, length);
 	}
 
 	if (image_info) {
@@ -383,7 +386,7 @@ static int spl_fit_append_fdt(struct spl_image_info *spl_image,
 	}
 
 	/* Make the load-address of the FDT available for the SPL framework */
-	spl_image->fdt_addr = (void *)image_info.load_addr;
+	spl_image->fdt_addr = map_sysmem(image_info.load_addr, 0);
 	if (CONFIG_IS_ENABLED(FIT_IMAGE_TINY))
 		return 0;
 

+ 1 - 1
configs/sandbox_spl_defconfig

@@ -1,4 +1,4 @@
-CONFIG_SYS_TEXT_BASE=0
+CONFIG_SYS_TEXT_BASE=0x200000
 CONFIG_SPL_LIBCOMMON_SUPPORT=y
 CONFIG_SPL_LIBGENERIC_SUPPORT=y
 CONFIG_NR_DRAM_BANKS=1

+ 12 - 36
doc/arch/sandbox.rst

@@ -17,6 +17,12 @@ of the sandbox U-Boot. The purpose of running U-Boot under Linux is to test
 all the generic code, not specific to any one architecture. The idea is to
 create unit tests which we can run to test this upper level code.
 
+Sandbox allows development of many types of new features in a traditional way,
+rather than needing to test each iteration on real hardware. Many U-Boot
+features were developed on sandbox, including the core driver model, most
+uclasses, verified boot, bloblist, logging and dozens of others. Sandbox has
+enabled many large-scale code refactors as well.
+
 CONFIG_SANDBOX is defined when building a native board.
 
 The board name is 'sandbox' but the vendor name is unset, so there is a
@@ -486,42 +492,10 @@ Testing
 -------
 
 U-Boot sandbox can be used to run various tests, mostly in the test/
-directory. These include:
-
-command_ut:
-  Unit tests for command parsing and handling
-compression:
-  Unit tests for U-Boot's compression algorithms, useful for
-  security checking. It supports gzip, bzip2, lzma and lzo.
-driver model:
-  Run this pytest::
-
-   ./test/py/test.py --bd sandbox --build -k ut_dm -v
-
-image:
-  Unit tests for images:
-  test/image/test-imagetools.sh - multi-file images
-  test/image/test-fit.py        - FIT images
-tracing:
-  test/trace/test-trace.sh tests the tracing system (see README.trace)
-verified boot:
-  See test/vboot/vboot_test.sh for this
-
-If you change or enhance any of the above subsystems, you shold write or
-expand a test and include it with your patch series submission. Test
-coverage in U-Boot is limited, as we need to work to improve it.
-
-Note that many of these tests are implemented as commands which you can
-run natively on your board if desired (and enabled).
-
-To run all tests use "make check".
-
-To run a single test in an existing sandbox build, you can use -T to use the
-test device tree, and -c to select the test:
-
-  /tmp/b/sandbox/u-boot -T -c "ut dm pci_busdev"
+directory.
 
-This runs dm_test_pci_busdev() which is in test/dm/pci.c
+See :doc:`../develop/tests_sandbox` for more information and
+:doc:`../develop/testing` for information about testing generally.
 
 
 Memory Map
@@ -537,5 +511,7 @@ Addr      Config                     Usage
    e000   CONFIG_BLOBLIST_ADDR       Blob list
   10000   CONFIG_MALLOC_F_ADDR       Early memory allocation
   f0000   CONFIG_PRE_CON_BUF_ADDR    Pre-console buffer
- 100000   CONFIG_TRACE_EARLY_ADDR    Early trace buffer (if enabled)
+ 100000   CONFIG_TRACE_EARLY_ADDR    Early trace buffer (if enabled). Also used
+                                     as the SPL load buffer in spl_test_load().
+ 200000   CONFIG_SYS_TEXT_BASE       Load buffer for U-Boot (sandbox_spl only)
 =======   ========================   ===============================

+ 2 - 0
doc/develop/index.rst

@@ -33,3 +33,5 @@ Testing
    coccinelle
    testing
    py_testing
+   tests_writing
+   tests_sandbox

+ 2 - 1
doc/develop/py_testing.rst

@@ -13,7 +13,8 @@ results. Advantages of this approach are:
   U-Boot; there can be no disconnect.
 - There is no need to write or embed test-related code into U-Boot itself.
   It is asserted that writing test-related code in Python is simpler and more
-  flexible than writing it all in C.
+  flexible than writing it all in C. But see :doc:`tests_writing` for caveats
+  and more discussion / analysis.
 - It is reasonably simple to interact with U-Boot in this way.
 
 Requirements

+ 38 - 8
doc/develop/testing.rst

@@ -8,17 +8,27 @@ tested and what tests you should write when adding a new feature.
 Running tests
 -------------
 
-To run most tests on sandbox, type this:
+To run most tests on sandbox, type this::
 
     make check
 
 in the U-Boot directory. Note that only the pytest suite is run using this
 command.
 
-Some tests take ages to run. To run just the quick ones, type this:
+Some tests take ages to run and are marked with @pytest.mark.slow. To run just
+the quick ones, type this::
 
     make qcheck
 
+It is also possible to run just the tests for tools (patman, binman, etc.).
+Such tests are included with those tools, i.e. no actual U-Boot unit tests are
+run. Type this::
+
+    make tcheck
+
+All of the above use the test/run script with a paremeter to select which tests
+are run.
+
 
 Sandbox
 -------
@@ -26,6 +36,7 @@ U-Boot can be built as a user-space application (e.g. for Linux). This
 allows test to be executed without needing target hardware. The 'sandbox'
 target provides this feature and it is widely used in tests.
 
+See :doc:`tests_sandbox` for more information.
 
 Pytest Suite
 ------------
@@ -35,14 +46,27 @@ either on sandbox or on real hardware. It relies on the U-Boot console to
 inject test commands and check the result. It is slower to run than C code,
 but provides the ability to unify lots of tests and summarise their results.
 
-You can run the tests on sandbox with:
+You can run the tests on sandbox with::
 
-	./test/py/test.py --bd sandbox --build
+   ./test/py/test.py --bd sandbox --build
 
 This will produce HTML output in build-sandbox/test-log.html
 
+Some tests run with other versions of sandbox. For example sandbox_flattree
+runs the tests with livetree (the hierachical devicetree) disabled. You can
+also select particular tests with -k::
+
+   ./test/py/test.py --bd sandbox_flattree --build -k hello
+
+There are some special tests that run in SPL. For this you need the sandbox_spl
+build::
+
+   ./test/py/test.py --bd sandbox_spl --build -k test_spl
+
 See test/py/README.md for more information about the pytest suite.
 
+See :doc:`tests_sandbox` for how to run tests directly (not through pytest).
+
 
 tbot
 ----
@@ -58,10 +82,14 @@ Ad-hoc tests
 
 There are several ad-hoc tests which run outside the pytest environment:
 
-   test/fs	- File system test (shell script)
-   test/image	- FIT and legacy image tests (shell script and Python)
-   test/stdint	- A test that stdint.h can be used in U-Boot (shell script)
-   trace	- Test for the tracing feature (shell script)
+test/fs
+   File system test (shell script)
+test/image
+   FIT and legacy image tests (shell script and Python)
+test/stdint
+   A test that stdint.h can be used in U-Boot (shell script)
+trace
+   Test for the tracing feature (shell script)
 
 TODO: Move these into pytest.
 
@@ -89,6 +117,8 @@ or is covered sparingly. So here are some suggestions:
   is much easier to add onto a test - writing a new large test can seem
   daunting to most contributors.
 
+See doc:`tests_writing` for how to write tests.
+
 
 Future work
 -----------

+ 209 - 0
doc/develop/tests_sandbox.rst

@@ -0,0 +1,209 @@
+.. SPDX-License-Identifier: GPL-2.0+
+
+Sandbox tests
+=============
+
+Test Design
+-----------
+
+Most uclasses and many functions of U-Boot have sandbox tests. This allows much
+of the code to be checked in an developer-friendly environment.
+
+Sandbox provides a way to write and run unit tests. The traditional approach to
+unit tests is to build lots of little executables, one for each test or
+category of tests. With sandbox, so far as possible, all the tests share a
+small number of executables (e.g. 'u-boot' for sandbox, 'u-boot-spl' and
+'u-boot' for sandbox_spl) and can be run very quickly. The vast majority of
+tests can run on the 'sandbox' build,
+
+Available tests
+---------------
+
+Some of the available tests are:
+
+  - command_ut: Unit tests for command parsing and handling
+  - compression: Unit tests for U-Boot's compression algorithms, useful for
+      security checking. It supports gzip, bzip2, lzma and lzo.
+  - image: Unit tests for images:
+
+     - test/image/test-imagetools.sh - multi-file images
+     - test/py/tests/test-fit.py     - FIT images
+  - tracing: test/trace/test-trace.sh tests the tracing system (see
+      README.trace)
+  - verified boot: test/py/tests/test_vboot.py
+
+If you change or enhance any U-Boot subsystem, you should write or expand a
+test and include it with your patch series submission. Test coverage in some
+older areas of U-Boot is still somewhat limited and we need to work to improve
+it.
+
+Note that many of these tests are implemented as commands which you can
+run natively on your board if desired (and enabled).
+
+To run all tests, use 'make check'.
+
+
+Running sandbox tests directly
+------------------------------
+
+Typically tests are run using the pytest suite. Running pytests on sandbox is
+easy and always gets things right. For example some tests require files to be
+set up before they can work.
+
+But it is also possible to run some sandbox tests directly. For example, this
+runs the dm_test_gpio() test which you can find in test/dm/gpio.c::
+
+   $ ./u-boot -T -c "ut dm gpio"
+
+
+   U-Boot 2021.01
+
+   Model: sandbox
+   DRAM:  128 MiB
+   WDT:   Started with servicing (60s timeout)
+   MMC:   mmc2: 2 (SD), mmc1: 1 (SD), mmc0: 0 (SD)
+   In:    serial
+   Out:   vidconsole
+   Err:   vidconsole
+   Model: sandbox
+   SCSI:
+   Net:   eth0: eth@10002000, eth5: eth@10003000, eth3: sbe5, eth6: eth@10004000
+   Test: dm_test_gpio: gpio.c
+   Test: dm_test_gpio: gpio.c (flat tree)
+   Failures: 0
+
+The -T option tells the U-Boot sandbox to run with the 'test' devicetree
+(test.dts) instead of -D which selects the normal sandbox.dts - this is
+necessary because many tests rely on nodes or properties in the test devicetree.
+If you try running tests without -T then you may see failures, like::
+
+   $ ./u-boot -c "ut dm gpio"
+
+
+   U-Boot 2021.01
+
+   DRAM:  128 MiB
+   WDT:   Not found!
+   MMC:
+   In:    serial
+   Out:   serial
+   Err:   serial
+   SCSI:
+   Net:   No ethernet found.
+   Please run with test device tree:
+       ./u-boot -d arch/sandbox/dts/test.dtb
+   Test: dm_test_gpio: gpio.c
+   test/dm/gpio.c:37, dm_test_gpio(): 0 == gpio_lookup_name("b4", &dev, &offset, &gpio): Expected 0x0 (0), got 0xffffffea (-22)
+   Test: dm_test_gpio: gpio.c (flat tree)
+   test/dm/gpio.c:37, dm_test_gpio(): 0 == gpio_lookup_name("b4", &dev, &offset, &gpio): Expected 0x0 (0), got 0xffffffea (-22)
+   Failures: 2
+
+The message above should provide a hint if you forget to use the -T flag. Even
+running with -D will produce different results.
+
+You can easily use gdb on these tests, without needing --gdbserver::
+
+   $ gdb u-boot --args -T -c "ut dm gpio"
+   ...
+   (gdb) break dm_test_gpio
+   Breakpoint 1 at 0x1415bd: file test/dm/gpio.c, line 37.
+   (gdb) run -T -c "ut dm gpio"
+   Starting program: u-boot -T -c "ut dm gpio"
+   Test: dm_test_gpio: gpio.c
+
+   Breakpoint 1, dm_test_gpio (uts=0x5555558029a0 <global_dm_test_state>)
+       at files/test/dm/gpio.c:37
+   37		ut_assertok(gpio_lookup_name("b4", &dev, &offset, &gpio));
+   (gdb)
+
+You can then single-step and look at variables as needed.
+
+
+Running sandbox_spl tests directly
+----------------------------------
+
+SPL is the phase before U-Boot proper. It is present in the sandbox_spl build,
+so you can run SPL like this::
+
+   ./spl/u-boot-spl
+
+SPL tests are special in that they run (only in the SPL phase, of course) if the
+-u flag is given::
+
+   ./spl/u-boot-spl -u
+
+   U-Boot SPL 2021.01-00723-g43c77b51be5-dirty (Jan 24 2021 - 16:38:24 -0700)
+   Running 5 driver model tests
+   Test: dm_test_of_plat_base: of_platdata.c (flat tree)
+   Test: dm_test_of_plat_dev: of_platdata.c (flat tree)
+   Test: dm_test_of_plat_parent: of_platdata.c (flat tree)
+   Test: dm_test_of_plat_phandle: of_platdata.c (flat tree)
+   Test: dm_test_of_plat_props: of_platdata.c (flat tree)
+   Failures: 0
+
+
+   U-Boot 2021.01-00723-g43c77b51be5-dirty (Jan 24 2021 - 16:38:24 -0700)
+
+   DRAM:  128 MiB
+   ...
+
+It is not possible to run SPL tests in U-Boot proper, firstly because they are
+not built into U-Boot proper and secondly because the environment is very
+different, e.g. some SPL tests rely on of-platdata which is only available in
+SPL.
+
+Note that after running, SPL continues to boot into U-Boot proper. You can add
+'-c exit' to make U-Boot quit without doing anything further. It is not
+currently possible to run SPL tests and then stop, since the pytests require
+that U-Boot produces the expected banner.
+
+You can use the -k flag to select which tests run::
+
+   ./spl/u-boot-spl -u -k dm_test_of_plat_parent
+
+Of course you can use gdb with sandbox_spl, just as with sandbox.
+
+
+Running all tests directly
+--------------------------
+
+A fast way to run all sandbox tests is::
+
+   ./u-boot -T -c "ut all"
+
+It typically runs single-thread in 6 seconds on 2021 hardware, with 2s of that
+to the delays in the time test.
+
+This should not be considered a substitute for 'make check', but can be helpful
+for git bisect, etc.
+
+
+What tests are built in?
+------------------------
+
+Whatever sandbox build is used, which tests are present is determined by which
+source files are built. For sandbox_spl, the of_platdata tests are built
+because of the build rule in test/dm/Makefile::
+
+   ifeq ($(CONFIG_SPL_BUILD),y)
+   obj-$(CONFIG_SPL_OF_PLATDATA) += of_platdata.o
+   else
+   ...other tests for non-spl
+   endif
+
+You can get a list of tests in a U-Boot ELF file by looking for the
+linker_list::
+
+   $ nm /tmp/b/sandbox_spl/spl/u-boot-spl |grep 2_dm_test
+   000000000001f200 D _u_boot_list_2_dm_test_2_dm_test_of_plat_base
+   000000000001f220 D _u_boot_list_2_dm_test_2_dm_test_of_plat_dev
+   000000000001f240 D _u_boot_list_2_dm_test_2_dm_test_of_plat_parent
+   000000000001f260 D _u_boot_list_2_dm_test_2_dm_test_of_plat_phandle
+   000000000001f280 D _u_boot_list_2_dm_test_2_dm_test_of_plat_props
+
+
+Writing tests
+-------------
+
+See :doc:`tests_writing` for how to write new tests.
+

+ 346 - 0
doc/develop/tests_writing.rst

@@ -0,0 +1,346 @@
+.. SPDX-License-Identifier: GPL-2.0+
+.. Copyright 2021 Google LLC
+.. sectionauthor:: Simon Glass <sjg@chromium.org>
+
+Writing Tests
+=============
+
+This describes how to write tests in U-Boot and describes the possible options.
+
+Test types
+----------
+
+There are two basic types of test in U-Boot:
+
+  - Python tests, in test/py/tests
+  - C tests, in test/ and its subdirectories
+
+(there are also UEFI tests in lib/efi_selftest/ not considered here.)
+
+Python tests talk to U-Boot via the command line. They support both sandbox and
+real hardware. They typically do not require building test code into U-Boot
+itself. They are fairly slow to run, due to the command-line interface and there
+being two separate processes. Python tests are fairly easy to write. They can
+be a little tricky to debug sometimes due to the voluminous output of pytest.
+
+C tests are written directly in U-Boot. While they can be used on boards, they
+are more commonly used with sandbox, as they obviously add to U-Boot code size.
+C tests are easy to write so long as the required facilities exist. Where they
+do not it can involve refactoring or adding new features to sandbox. They are
+fast to run and easy to debug.
+
+Regardless of which test type is used, all tests are collected and run by the
+pytest framework, so there is typically no need to run them separately. This
+means that C tests can be used when it makes sense, and Python tests when it
+doesn't.
+
+
+This table shows how to decide whether to write a C or Python test:
+
+=====================  ===========================  =============================
+Attribute              C test                       Python test
+=====================  ===========================  =============================
+Fast to run?           Yes                          No (two separate processes)
+Easy to write?         Yes, if required test        Yes
+                       features exist in sandbox
+                       or the target system
+Needs code in U-Boot?  Yes                          No, provided the test can be
+                                                    executed and the result
+                                                    determined using the command
+                                                    line
+Easy to debug?         Yes                          No, since access to the U-Boot
+                                                    state is not available and the
+                                                    amount of output can
+                                                    sometimes require a bit of
+                                                    digging
+Can use gdb?           Yes, directly                Yes, with --gdbserver
+Can run on boards?     Some can, but only if        Some
+                       compiled in and not
+                       dependent on sandboxau
+=====================  ===========================  =============================
+
+
+Python or C
+-----------
+
+Typically in U-Boot we encourage C test using sandbox for all features. This
+allows fast testing, easy development and allows contributors to make changes
+without needing dozens of boards to test with.
+
+When a test requires setup or interaction with the running host (such as to
+generate images and then running U-Boot to check that they can be loaded), or
+cannot be run on sandbox, Python tests should be used. These should typically
+NOT rely on running with sandbox, but instead should function correctly on any
+board supported by U-Boot.
+
+
+How slow are Python tests?
+--------------------------
+
+Under the hood, when running on sandbox, Python tests work by starting a sandbox
+test and connecting to it via a pipe. Each interaction with the U-Boot process
+requires at least a context switch to handle the pipe interaction. The test
+sends a command to U-Boot, which then reacts and shows some output, then the
+test sees that and continues. Of course on real hardware, communications delays
+(e.g. with a serial console) make this slower.
+
+For comparison, consider a test that checks the 'md' (memory dump). All times
+below are approximate, as measured on an AMD 2950X system. Here is is the test
+in Python::
+
+   @pytest.mark.buildconfigspec('cmd_memory')
+   def test_md(u_boot_console):
+       """Test that md reads memory as expected, and that memory can be modified
+       using the mw command."""
+
+       ram_base = u_boot_utils.find_ram_base(u_boot_console)
+       addr = '%08x' % ram_base
+       val = 'a5f09876'
+       expected_response = addr + ': ' + val
+       u_boot_console.run_command('mw ' + addr + ' 0 10')
+       response = u_boot_console.run_command('md ' + addr + ' 10')
+       assert(not (expected_response in response))
+       u_boot_console.run_command('mw ' + addr + ' ' + val)
+       response = u_boot_console.run_command('md ' + addr + ' 10')
+       assert(expected_response in response)
+
+This runs a few commands and checks the output. Note that it runs a command,
+waits for the response and then checks it agains what is expected. If run by
+itself it takes around 800ms, including test collection. For 1000 runs it takes
+19 seconds, or 19ms per run. Of course 1000 runs it not that useful since we
+only want to run it once.
+
+There is no exactly equivalent C test, but here is a similar one that tests 'ms'
+(memory search)::
+
+   /* Test 'ms' command with bytes */
+   static int mem_test_ms_b(struct unit_test_state *uts)
+   {
+      u8 *buf;
+
+      buf = map_sysmem(0, BUF_SIZE + 1);
+      memset(buf, '\0', BUF_SIZE);
+      buf[0x0] = 0x12;
+      buf[0x31] = 0x12;
+      buf[0xff] = 0x12;
+      buf[0x100] = 0x12;
+      ut_assertok(console_record_reset_enable());
+      run_command("ms.b 1 ff 12", 0);
+      ut_assert_nextline("00000030: 00 12 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................");
+      ut_assert_nextline("--");
+      ut_assert_nextline("000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12    ................");
+      ut_assert_nextline("2 matches");
+      ut_assert_console_end();
+
+      ut_asserteq(2, env_get_hex("memmatches", 0));
+      ut_asserteq(0xff, env_get_hex("memaddr", 0));
+      ut_asserteq(0xfe, env_get_hex("mempos", 0));
+
+      unmap_sysmem(buf);
+
+      return 0;
+   }
+   MEM_TEST(mem_test_ms_b, UT_TESTF_CONSOLE_REC);
+
+This runs the command directly in U-Boot, then checks the console output, also
+directly in U-Boot. If run by itself this takes 100ms. For 1000 runs it takes
+660ms, or 0.66ms per run.
+
+So overall running a C test is perhaps 8 times faster individually and the
+interactions are perhaps 25 times faster.
+
+It should also be noted that the C test is fairly easy to debug. You can set a
+breakpoint on do_mem_search(), which is what implements the 'ms' command,
+single step to see what might be wrong, etc. That is also possible with the
+pytest, but requires two terminals and --gdbserver.
+
+
+Why does speed matter?
+----------------------
+
+Many development activities rely on running tests:
+
+  - 'git bisect run make qcheck' can be used to find a failing commit
+  - test-driven development relies on quick iteration of build/test
+  - U-Boot's continuous integration (CI) systems make use of tests. Running
+      all sandbox tests typically takes 90 seconds and running each qemu test
+      takes about 30 seconds. This is currently dwarfed by the time taken to
+      build all boards
+
+As U-Boot continues to grow its feature set, fast and reliable tests are a
+critical factor factor in developer productivity and happiness.
+
+
+Writing C tests
+---------------
+
+C tests are arranged into suites which are typically executed by the 'ut'
+command. Each suite is in its own file. This section describes how to accomplish
+some common test tasks.
+
+(there are also UEFI C tests in lib/efi_selftest/ not considered here.)
+
+Add a new driver model test
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use this when adding a test for a new or existing uclass, adding new operations
+or features to a uclass, adding new ofnode or dev_read_() functions, or anything
+else related to driver model.
+
+Find a suitable place for your test, perhaps near other test functions in
+existing code, or in a new file. Each uclass should have its own test file.
+
+Declare the test with::
+
+   /* Test that ... */
+   static int dm_test_uclassname_what(struct unit_test_state *uts)
+   {
+      /* test code here */
+
+      return 0;
+   }
+   DM_TEST(dm_test_uclassname_what, UT_TESTF_SCAN_FDT);
+
+Replace 'uclassname' with the name of your uclass, if applicable. Replace 'what'
+with what you are testing.
+
+The flags for DM_TEST() are defined in test/test.h and you typically want
+UT_TESTF_SCAN_FDT so that the devicetree is scanned and all devices are bound
+and ready for use. The DM_TEST macro adds UT_TESTF_DM automatically so that
+the test runner knows it is a driver model test.
+
+Driver model tests are special in that the entire driver model state is
+recreated anew for each test. This ensures that if a previous test deletes a
+device, for example, it does not affect subsequent tests. Driver model tests
+also run both with livetree and flattree, to ensure that both devicetree
+implementations work as expected.
+
+Example commit: c48cb7ebfb4 ("sandbox: add ADC unit tests") [1]
+
+[1] https://gitlab.denx.de/u-boot/u-boot/-/commit/c48cb7ebfb4
+
+
+Add a C test to an existing suite
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use this when you are adding to or modifying an existing feature outside driver
+model. An example is bloblist.
+
+Add a new function in the same file as the rest of the suite and register it
+with the suite. For example, to add a new mem_search test::
+
+   /* Test 'ms' command with 32-bit values */
+   static int mem_test_ms_new_thing(struct unit_test_state *uts)
+   {
+         /* test code here*/
+
+         return 0;
+   }
+   MEM_TEST(mem_test_ms_new_thing, UT_TESTF_CONSOLE_REC);
+
+Note that the MEM_TEST() macros is defined at the top of the file.
+
+Example commit: 9fe064646d2 ("bloblist: Support relocating to a larger space") [1]
+
+[1] https://gitlab.denx.de/u-boot/u-boot/-/commit/9fe064646d2
+
+
+Add a new test suite
+~~~~~~~~~~~~~~~~~~~~
+
+Each suite should focus on one feature or subsystem, so if you are writing a
+new one of those, you should add a new suite.
+
+Create a new file in test/ or a subdirectory and define a macro to register the
+suite. For example::
+
+   #include <common.h>
+   #include <console.h>
+   #include <mapmem.h>
+   #include <dm/test.h>
+   #include <test/ut.h>
+
+   /* Declare a new wibble test */
+   #define WIBBLE_TEST(_name, _flags)   UNIT_TEST(_name, _flags, wibble_test)
+
+   /* Tetss go here */
+
+   /* At the bottom of the file: */
+
+   int do_ut_wibble(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+   {
+     struct unit_test *tests = UNIT_TEST_SUITE_START(wibble_test);
+     const int n_ents = UNIT_TEST_SUITE_COUNT(wibble_test);
+
+     return cmd_ut_category("cmd_wibble", "wibble_test_", tests, n_ents, argc, argv);
+   }
+
+Then add new tests to it as above.
+
+Register this new suite in test/cmd_ut.c by adding to cmd_ut_sub[]::
+
+  /* Within cmd_ut_sub[]... */
+
+  U_BOOT_CMD_MKENT(wibble, CONFIG_SYS_MAXARGS, 1, do_ut_wibble, "", ""),
+
+and adding new help to ut_help_text[]::
+
+  "ut wibble - Test the wibble feature\n"
+
+If your feature is conditional on a particular Kconfig, then you can use #ifdef
+to control that.
+
+Finally, add the test to the build by adding to the Makefile in the same
+directory::
+
+  obj-$(CONFIG_$(SPL_)CMDLINE) += wibble.o
+
+Note that CMDLINE is never enabled in SPL, so this test will only be present in
+U-Boot proper. See below for how to do SPL tests.
+
+As before, you can add an extra Kconfig check if needed::
+
+  ifneq ($(CONFIG_$(SPL_)WIBBLE),)
+  obj-$(CONFIG_$(SPL_)CMDLINE) += wibble.o
+  endif
+
+
+Example commit: 919e7a8fb64 ("test: Add a simple test for bloblist") [1]
+
+[1] https://gitlab.denx.de/u-boot/u-boot/-/commit/919e7a8fb64
+
+
+Making the test run from pytest
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+All C tests must run from pytest. Typically this is automatic, since pytest
+scans the U-Boot executable for available tests to run. So long as you have a
+'ut' subcommand for your test suite, it will run. The same applies for driver
+model tests since they use the 'ut dm' subcommand.
+
+See test/py/tests/test_ut.py for how unit tests are run.
+
+
+Add a C test for SPL
+~~~~~~~~~~~~~~~~~~~~
+
+Note: C tests are only available for sandbox_spl at present. There is currently
+no mechanism in other boards to existing SPL tests even if they are built into
+the image.
+
+SPL tests cannot be run from the 'ut' command since there are no commands
+available in SPL. Instead, sandbox (only) calls ut_run_list() on start-up, when
+the -u flag is given. This runs the available unit tests, no matter what suite
+they are in.
+
+To create a new SPL test, follow the same rules as above, either adding to an
+existing suite or creating a new one.
+
+An example SPL test is spl_test_load().
+
+
+Writing Python tests
+--------------------
+
+See :doc:`py_testing` for brief notes how to write Python tests. You
+should be able to use the existing tests in test/py/tests as examples.

+ 13 - 4
drivers/core/device.c

@@ -92,15 +92,19 @@ static int device_bind_common(struct udevice *parent, const struct driver *drv,
 	if (auto_seq && !(uc->uc_drv->flags & DM_UC_FLAG_NO_AUTO_SEQ))
 		dev->seq_ = uclass_find_next_free_seq(uc);
 
+	/* Check if we need to allocate plat */
 	if (drv->plat_auto) {
 		bool alloc = !plat;
 
+		/*
+		 * For of-platdata, we try use the existing data, but if
+		 * plat_auto is larger, we must allocate a new space
+		 */
 		if (CONFIG_IS_ENABLED(OF_PLATDATA)) {
-			if (of_plat_size) {
+			if (of_plat_size)
 				dev_or_flags(dev, DM_FLAG_OF_PLATDATA);
-				if (of_plat_size < drv->plat_auto)
-					alloc = true;
-			}
+			if (of_plat_size < drv->plat_auto)
+				alloc = true;
 		}
 		if (alloc) {
 			dev_or_flags(dev, DM_FLAG_ALLOC_PDATA);
@@ -109,6 +113,11 @@ static int device_bind_common(struct udevice *parent, const struct driver *drv,
 				ret = -ENOMEM;
 				goto fail_alloc1;
 			}
+
+			/*
+			 * For of-platdata, copy the old plat into the new
+			 * space
+			 */
 			if (CONFIG_IS_ENABLED(OF_PLATDATA) && plat)
 				memcpy(ptr, plat, of_plat_size);
 			dev_set_plat(dev, ptr);

+ 2 - 18
include/dm/test.h

@@ -125,25 +125,9 @@ extern int dm_testdrv_op_count[DM_TEST_OP_COUNT];
 
 extern struct unit_test_state global_dm_test_state;
 
-/*
- * struct dm_test_state - Entire state of dm test system
- *
- * This is often abreviated to dms.
- *
- * @root: Root device
- * @testdev: Test device
- * @force_fail_alloc: Force all memory allocs to fail
- * @skip_post_probe: Skip uclass post-probe processing
- */
-struct dm_test_state {
-	struct udevice *root;
-	struct udevice *testdev;
-	int force_fail_alloc;
-	int skip_post_probe;
-};
-
 /* Declare a new driver model test */
-#define DM_TEST(_name, _flags)	UNIT_TEST(_name, _flags, dm_test)
+#define DM_TEST(_name, _flags) \
+	UNIT_TEST(_name, UT_TESTF_DM | UT_TESTF_CONSOLE_REC | (_flags), dm_test)
 
 /*
  * struct sandbox_sdl_plat - Platform data for the SDL video driver

+ 2 - 1
include/os.h

@@ -324,9 +324,10 @@ int os_jump_to_image(const void *dest, int size);
  *
  * @fname:	place to put full path to U-Boot
  * @maxlen:	maximum size of @fname
+ * @use_img:	select the 'u-boot.img' file instead of the 'u-boot' ELF file
  * Return:	0 if OK, -NOSPC if the filename is too large, -ENOENT if not found
  */
-int os_find_u_boot(char *fname, int maxlen);
+int os_find_u_boot(char *fname, int maxlen, bool use_img);
 
 /**
  * os_spl_to_uboot() - Run U-Boot proper

+ 9 - 0
include/spl.h

@@ -222,6 +222,15 @@ struct spl_load_info {
 	void *priv;
 	int bl_len;
 	const char *filename;
+	/**
+	 * read() - Read from device
+	 *
+	 * @load: Information about the load state
+	 * @sector: Sector number to read from (each @load->bl_len bytes)
+	 * @count: Number of sectors to read
+	 * @buf: Buffer to read into
+	 * @return number of sectors read, 0 on error
+	 */
 	ulong (*read)(struct spl_load_info *load, ulong sector, ulong count,
 		      void *buf);
 };

+ 24 - 14
include/test/test.h

@@ -14,16 +14,24 @@
  *
  * @fail_count: Number of tests that failed
  * @start: Store the starting mallinfo when doing leak test
- * @priv: A pointer to some other info some suites want to track
+ * @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)
+ * @root: Root device
+ * @testdev: Test device
+ * @force_fail_alloc: Force all memory allocs to fail
+ * @skip_post_probe: Skip uclass post-probe processing
  * @expect_str: Temporary string used to hold expected string value
  * @actual_str: Temporary string used to hold actual string value
  */
 struct unit_test_state {
 	int fail_count;
 	struct mallinfo start;
-	void *priv;
 	struct device_node *of_root;
+	bool of_live;
+	struct udevice *root;
+	struct udevice *testdev;
+	int force_fail_alloc;
+	int skip_post_probe;
 	char expect_str[256];
 	char actual_str[256];
 };
@@ -36,6 +44,8 @@ enum {
 	UT_TESTF_FLAT_TREE	= BIT(3),	/* test needs flat DT */
 	UT_TESTF_LIVE_TREE	= BIT(4),	/* needs live device tree */
 	UT_TESTF_CONSOLE_REC	= BIT(5),	/* needs console recording */
+	/* do extra driver model init and uninit */
+	UT_TESTF_DM		= BIT(6),
 };
 
 /**
@@ -76,13 +86,24 @@ struct unit_test {
  * @_suite:	name of the test suite concatenated with "_test"
  */
 #define UNIT_TEST(_name, _flags, _suite)				\
-	ll_entry_declare(struct unit_test, _name, _suite) = {		\
+	ll_entry_declare(struct unit_test, _name, ut_ ## _suite) = {	\
 		.file = __FILE__,					\
 		.name = #_name,						\
 		.flags = _flags,					\
 		.func = _name,						\
 	}
 
+/* Get the start of a list of unit tests for a particular suite */
+#define UNIT_TEST_SUITE_START(_suite) \
+	ll_entry_start(struct unit_test, ut_ ## _suite)
+#define UNIT_TEST_SUITE_COUNT(_suite) \
+	ll_entry_count(struct unit_test, ut_ ## _suite)
+
+/* Use ! and ~ so that all tests will be sorted between these two values */
+#define UNIT_TEST_ALL_START()	ll_entry_start(struct unit_test, ut_!)
+#define UNIT_TEST_ALL_END()	ll_entry_start(struct unit_test, ut_~)
+#define UNIT_TEST_ALL_COUNT()	(UNIT_TEST_ALL_END() - UNIT_TEST_ALL_START())
+
 /* Sizes for devres tests */
 enum {
 	TEST_DEVRES_SIZE	= 100,
@@ -103,15 +124,4 @@ enum {
  */
 struct udevice *testbus_get_clear_removed(void);
 
-/**
- * dm_test_main() - Run driver model tests
- *
- * Run all the available driver model tests, or a selection
- *
- * @test_name: Name of single test to run (e.g. "dm_test_fdt_pre_reloc" or just
- *	"fdt_pre_reloc"), or NULL to run all
- * @return 0 if all tests passed, 1 if not
- */
-int dm_test_main(const char *test_name);
-
 #endif /* __TEST_TEST_H */

+ 45 - 0
include/test/ut.h

@@ -356,4 +356,49 @@ void ut_silence_console(struct unit_test_state *uts);
  */
 void ut_unsilence_console(struct unit_test_state *uts);
 
+/**
+ * ut_set_skip_delays() - Sets whether delays should be skipped
+ *
+ * Normally functions like mdelay() cause U-Boot to wait for a while. This
+ * allows all such delays to be skipped on sandbox, to speed up tests
+ *
+ * @uts: Test state (in case in future we want to keep state here)
+ * @skip_delays: true to skip delays, false to process them normally
+ */
+void ut_set_skip_delays(struct unit_test_state *uts, bool skip_delays);
+
+/**
+ * test_get_state() - Get the active test state
+ *
+ * @return the currently active test state, or NULL if none
+ */
+struct unit_test_state *test_get_state(void);
+
+/**
+ * test_set_state() - Set the active test state
+ *
+ * @uts: Test state to use as currently active test state, or NULL if none
+ */
+void test_set_state(struct unit_test_state *uts);
+
+/**
+ * ut_run_tests() - Run a set of tests
+ *
+ * This runs the test, handling any preparation and clean-up needed. It prints
+ * the name of each test before running it.
+ *
+ * @category: Category of these tests. This is a string printed at the start to
+ *	announce the the number of tests
+ * @prefix: String prefix for the tests. Any tests that have this prefix will be
+ *	printed without the prefix, so that it is easier to see the unique part
+ *	of the test name. If NULL, no prefix processing is done
+ * @tests: List of tests to run
+ * @count: Number of tests to run
+ * @select_name: Name of a single test to run (from the list provided). If NULL
+ *	then all tests are run
+ * @return 0 if all tests passed, -1 if any failed
+ */
+int ut_run_list(const char *name, const char *prefix, struct unit_test *tests,
+		int count, const char *select_name);
+
 #endif

+ 3 - 0
test/Makefile

@@ -2,6 +2,9 @@
 #
 # (C) Copyright 2012 The Chromium Authors
 
+obj-y += test-main.o
+obj-$(CONFIG_SANDBOX) += image/
+
 ifneq ($(CONFIG_$(SPL_)BLOBLIST),)
 obj-$(CONFIG_$(SPL_)CMDLINE) += bloblist.o
 obj-$(CONFIG_$(SPL_)CMDLINE) += bootm.o

+ 2 - 3
test/bloblist.c

@@ -387,9 +387,8 @@ BLOBLIST_TEST(bloblist_test_reloc, 0);
 int do_ut_bloblist(struct cmd_tbl *cmdtp, int flag, int argc,
 		   char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test,
-						 bloblist_test);
-	const int n_ents = ll_entry_count(struct unit_test, bloblist_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(bloblist_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(bloblist_test);
 
 	return cmd_ut_category("bloblist", "bloblist_test_",
 			       tests, n_ents, argc, argv);

+ 2 - 2
test/bootm.c

@@ -240,8 +240,8 @@ BOOTM_TEST(bootm_test_subst_both, 0);
 
 int do_ut_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test, bootm_test);
-	const int n_ents = ll_entry_count(struct unit_test, bootm_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(bootm_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(bootm_test);
 
 	return cmd_ut_category("bootm", "bootm_test_", tests, n_ents,
 			       argc, argv);

+ 2 - 2
test/cmd/mem.c

@@ -12,8 +12,8 @@
 
 int do_ut_mem(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test, mem_test);
-	const int n_ents = ll_entry_count(struct unit_test, mem_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(mem_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(mem_test);
 
 	return cmd_ut_category("cmd_mem", "mem_test_", tests, n_ents, argc,
 			       argv);

+ 21 - 7
test/cmd/setexpr.c

@@ -306,8 +306,8 @@ static int setexpr_test_str(struct unit_test_state *uts)
 	ut_asserteq(1, run_command("setexpr.s fred 0", 0));
 	ut_assertok(ut_check_delta(start_mem));
 
-	start_mem = ut_check_free();
 	ut_assertok(env_set("fred", "12345"));
+	start_mem = ut_check_free();
 	ut_assertok(run_command("setexpr.s fred *0", 0));
 	ut_asserteq_str("hello", env_get("fred"));
 	ut_assertok(ut_check_delta(start_mem));
@@ -345,7 +345,22 @@ static int setexpr_test_str_oper(struct unit_test_state *uts)
 	start_mem = ut_check_free();
 	ut_assertok(run_command("setexpr.s fred *0 + *10", 0));
 	ut_asserteq_str("hello there", env_get("fred"));
-	ut_assertok(ut_check_delta(start_mem));
+
+	/*
+	 * This check does not work with sandbox_flattree, apparently due to
+	 * memory allocations in env_set().
+	 *
+	 * The truetype console produces lots of memory allocations even though
+	 * the LCD display is not visible. But even without these, it does not
+	 * work.
+	 *
+	 * A better test would be for dlmalloc to record the allocs and frees
+	 * for a particular caller, but that is not supported.
+	 *
+	 * For now, drop this test.
+	 *
+	 * ut_assertok(ut_check_delta(start_mem));
+	 */
 
 	unmap_sysmem(buf);
 
@@ -375,10 +390,9 @@ SETEXPR_TEST(setexpr_test_str_long, UT_TESTF_CONSOLE_REC);
 
 int do_ut_setexpr(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test,
-						 setexpr_test);
-	const int n_ents = ll_entry_count(struct unit_test, setexpr_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(setexpr_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(setexpr_test);
 
-	return cmd_ut_category("cmd_setexpr", "cmd_mem_", tests, n_ents, argc,
-			       argv);
+	return cmd_ut_category("cmd_setexpr", "setexpr_test_", tests, n_ents,
+			       argc, argv);
 }

+ 5 - 33
test/cmd_ut.c

@@ -9,6 +9,7 @@
 #include <console.h>
 #include <test/suites.h>
 #include <test/test.h>
+#include <test/ut.h>
 
 static int do_ut_all(struct cmd_tbl *cmdtp, int flag, int argc,
 		     char *const argv[]);
@@ -17,41 +18,12 @@ int cmd_ut_category(const char *name, const char *prefix,
 		    struct unit_test *tests, int n_ents,
 		    int argc, char *const argv[])
 {
-	struct unit_test_state uts = { .fail_count = 0 };
-	struct unit_test *test;
-	int prefix_len = prefix ? strlen(prefix) : 0;
+	int ret;
 
-	if (argc == 1)
-		printf("Running %d %s tests\n", n_ents, name);
+	ret = ut_run_list(name, prefix, tests, n_ents,
+			  argc > 1 ? argv[1] : NULL);
 
-	for (test = tests; test < tests + n_ents; test++) {
-		const char *test_name = test->name;
-
-		/* Remove the prefix */
-		if (prefix && !strncmp(test_name, prefix, prefix_len))
-			test_name += prefix_len;
-
-		if (argc > 1 && strcmp(argv[1], test_name))
-			continue;
-		printf("Test: %s\n", test->name);
-
-		if (test->flags & UT_TESTF_CONSOLE_REC) {
-			int ret = console_record_reset_enable();
-
-			if (ret) {
-				printf("Skipping: Console recording disabled\n");
-				continue;
-			}
-		}
-
-		uts.start = mallinfo();
-
-		test->func(&uts);
-	}
-
-	printf("Failures: %d\n", uts.fail_count);
-
-	return uts.fail_count ? CMD_RET_FAILURE : 0;
+	return ret ? CMD_RET_FAILURE : 0;
 }
 
 static struct cmd_tbl cmd_ut_sub[] = {

+ 2 - 3
test/compression.c

@@ -539,9 +539,8 @@ COMPRESSION_TEST(compression_test_bootm_none, 0);
 int do_ut_compression(struct cmd_tbl *cmdtp, int flag, int argc,
 		      char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test,
-						 compression_test);
-	const int n_ents = ll_entry_count(struct unit_test, compression_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(compression_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(compression_test);
 
 	return cmd_ut_category("compression", "compression_test_",
 			       tests, n_ents, argc, argv);

+ 1 - 1
test/dm/Makefile

@@ -2,7 +2,7 @@
 #
 # Copyright (c) 2013 Google, Inc
 
-obj-$(CONFIG_UT_DM) += test-main.o
+obj-$(CONFIG_UT_DM) += test-dm.o
 
 # Tests for particular subsystems - when enabling driver model for a new
 # subsystem you must add sandbox tests here.

+ 24 - 34
test/dm/core.c

@@ -117,14 +117,13 @@ int dm_leak_check_end(struct unit_test_state *uts)
 /* Test that binding with plat occurs correctly */
 static int dm_test_autobind(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	struct udevice *dev;
 
 	/*
 	 * We should have a single class (UCLASS_ROOT) and a single root
 	 * device with no children.
 	 */
-	ut_assert(dms->root);
+	ut_assert(uts->root);
 	ut_asserteq(1, list_count_items(gd->uclass_root));
 	ut_asserteq(0, list_count_items(&gd->dm_root->child_head));
 	ut_asserteq(0, dm_testdrv_op_count[DM_TEST_OP_POST_BIND]);
@@ -207,7 +206,6 @@ DM_TEST(dm_test_autobind_uclass_pdata_valid, UT_TESTF_SCAN_PDATA);
 /* Test that autoprobe finds all the expected devices */
 static int dm_test_autoprobe(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	int expected_base_add;
 	struct udevice *dev;
 	struct uclass *uc;
@@ -221,7 +219,7 @@ static int dm_test_autoprobe(struct unit_test_state *uts)
 	ut_asserteq(0, dm_testdrv_op_count[DM_TEST_OP_POST_PROBE]);
 
 	/* The root device should not be activated until needed */
-	ut_assert(dev_get_flags(dms->root) & DM_FLAG_ACTIVATED);
+	ut_assert(dev_get_flags(uts->root) & DM_FLAG_ACTIVATED);
 
 	/*
 	 * We should be able to find the three test devices, and they should
@@ -241,7 +239,7 @@ static int dm_test_autoprobe(struct unit_test_state *uts)
 
 		/* Activating a device should activate the root device */
 		if (!i)
-			ut_assert(dev_get_flags(dms->root) & DM_FLAG_ACTIVATED);
+			ut_assert(dev_get_flags(uts->root) & DM_FLAG_ACTIVATED);
 	}
 
 	/*
@@ -293,7 +291,6 @@ DM_TEST(dm_test_plat, UT_TESTF_SCAN_PDATA);
 /* Test that we can bind, probe, remove, unbind a driver */
 static int dm_test_lifecycle(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	int op_count[DM_TEST_OP_COUNT];
 	struct udevice *dev, *test_dev;
 	int pingret;
@@ -301,7 +298,7 @@ static int dm_test_lifecycle(struct unit_test_state *uts)
 
 	memcpy(op_count, dm_testdrv_op_count, sizeof(op_count));
 
-	ut_assertok(device_bind_by_name(dms->root, false, &driver_info_manual,
+	ut_assertok(device_bind_by_name(uts->root, false, &driver_info_manual,
 					&dev));
 	ut_assert(dev);
 	ut_assert(dm_testdrv_op_count[DM_TEST_OP_BIND]
@@ -309,7 +306,7 @@ static int dm_test_lifecycle(struct unit_test_state *uts)
 	ut_assert(!dev_get_priv(dev));
 
 	/* Probe the device - it should fail allocating private data */
-	dms->force_fail_alloc = 1;
+	uts->force_fail_alloc = 1;
 	ret = device_probe(dev);
 	ut_assert(ret == -ENOMEM);
 	ut_assert(dm_testdrv_op_count[DM_TEST_OP_PROBE]
@@ -317,7 +314,7 @@ static int dm_test_lifecycle(struct unit_test_state *uts)
 	ut_assert(!dev_get_priv(dev));
 
 	/* Try again without the alloc failure */
-	dms->force_fail_alloc = 0;
+	uts->force_fail_alloc = 0;
 	ut_assertok(device_probe(dev));
 	ut_assert(dm_testdrv_op_count[DM_TEST_OP_PROBE]
 			== op_count[DM_TEST_OP_PROBE] + 2);
@@ -349,19 +346,18 @@ DM_TEST(dm_test_lifecycle, UT_TESTF_SCAN_PDATA | UT_TESTF_PROBE_TEST);
 /* Test that we can bind/unbind and the lists update correctly */
 static int dm_test_ordering(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	struct udevice *dev, *dev_penultimate, *dev_last, *test_dev;
 	int pingret;
 
-	ut_assertok(device_bind_by_name(dms->root, false, &driver_info_manual,
+	ut_assertok(device_bind_by_name(uts->root, false, &driver_info_manual,
 					&dev));
 	ut_assert(dev);
 
 	/* Bind two new devices (numbers 4 and 5) */
-	ut_assertok(device_bind_by_name(dms->root, false, &driver_info_manual,
+	ut_assertok(device_bind_by_name(uts->root, false, &driver_info_manual,
 					&dev_penultimate));
 	ut_assert(dev_penultimate);
-	ut_assertok(device_bind_by_name(dms->root, false, &driver_info_manual,
+	ut_assertok(device_bind_by_name(uts->root, false, &driver_info_manual,
 					&dev_last));
 	ut_assert(dev_last);
 
@@ -376,7 +372,7 @@ static int dm_test_ordering(struct unit_test_state *uts)
 	ut_assert(dev_last == test_dev);
 
 	/* Add back the original device 3, now in position 5 */
-	ut_assertok(device_bind_by_name(dms->root, false, &driver_info_manual,
+	ut_assertok(device_bind_by_name(uts->root, false, &driver_info_manual,
 					&dev));
 	ut_assert(dev);
 
@@ -568,7 +564,6 @@ static int create_children(struct unit_test_state *uts, struct udevice *parent,
 
 static int dm_test_children(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	struct udevice *top[NODE_COUNT];
 	struct udevice *child[NODE_COUNT];
 	struct udevice *grandchild[NODE_COUNT];
@@ -578,12 +573,12 @@ static int dm_test_children(struct unit_test_state *uts)
 	int i;
 
 	/* We don't care about the numbering for this test */
-	dms->skip_post_probe = 1;
+	uts->skip_post_probe = 1;
 
 	ut_assert(NODE_COUNT > 5);
 
 	/* First create 10 top-level children */
-	ut_assertok(create_children(uts, dms->root, NODE_COUNT, 0, top));
+	ut_assertok(create_children(uts, uts->root, NODE_COUNT, 0, top));
 
 	/* Now a few have their own children */
 	ut_assertok(create_children(uts, top[2], NODE_COUNT, 2, NULL));
@@ -654,7 +649,6 @@ DM_TEST(dm_test_children, 0);
 
 static int dm_test_device_reparent(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	struct udevice *top[NODE_COUNT];
 	struct udevice *child[NODE_COUNT];
 	struct udevice *grandchild[NODE_COUNT];
@@ -664,12 +658,12 @@ static int dm_test_device_reparent(struct unit_test_state *uts)
 	int i;
 
 	/* We don't care about the numbering for this test */
-	dms->skip_post_probe = 1;
+	uts->skip_post_probe = 1;
 
 	ut_assert(NODE_COUNT > 5);
 
 	/* First create 10 top-level children */
-	ut_assertok(create_children(uts, dms->root, NODE_COUNT, 0, top));
+	ut_assertok(create_children(uts, uts->root, NODE_COUNT, 0, top));
 
 	/* Now a few have their own children */
 	ut_assertok(create_children(uts, top[2], NODE_COUNT, 2, NULL));
@@ -815,15 +809,14 @@ DM_TEST(dm_test_device_reparent, 0);
 /* Test that pre-relocation devices work as expected */
 static int dm_test_pre_reloc(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	struct udevice *dev;
 
 	/* The normal driver should refuse to bind before relocation */
-	ut_asserteq(-EPERM, device_bind_by_name(dms->root, true,
+	ut_asserteq(-EPERM, device_bind_by_name(uts->root, true,
 						&driver_info_manual, &dev));
 
 	/* But this one is marked pre-reloc */
-	ut_assertok(device_bind_by_name(dms->root, true,
+	ut_assertok(device_bind_by_name(uts->root, true,
 					&driver_info_pre_reloc, &dev));
 
 	return 0;
@@ -836,10 +829,9 @@ DM_TEST(dm_test_pre_reloc, 0);
  */
 static int dm_test_remove_active_dma(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	struct udevice *dev;
 
-	ut_assertok(device_bind_by_name(dms->root, false, &driver_info_act_dma,
+	ut_assertok(device_bind_by_name(uts->root, false, &driver_info_act_dma,
 					&dev));
 	ut_assert(dev);
 
@@ -872,7 +864,7 @@ static int dm_test_remove_active_dma(struct unit_test_state *uts)
 	 * the active DMA remove call
 	 */
 	ut_assertok(device_unbind(dev));
-	ut_assertok(device_bind_by_name(dms->root, false, &driver_info_manual,
+	ut_assertok(device_bind_by_name(uts->root, false, &driver_info_manual,
 					&dev));
 	ut_assert(dev);
 
@@ -895,25 +887,24 @@ DM_TEST(dm_test_remove_active_dma, 0);
 /* Test removal of 'vital' devices */
 static int dm_test_remove_vital(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	struct udevice *normal, *dma, *vital, *dma_vital;
 
 	/* Skip the behaviour in test_post_probe() */
-	dms->skip_post_probe = 1;
+	uts->skip_post_probe = 1;
 
-	ut_assertok(device_bind_by_name(dms->root, false, &driver_info_manual,
+	ut_assertok(device_bind_by_name(uts->root, false, &driver_info_manual,
 					&normal));
 	ut_assertnonnull(normal);
 
-	ut_assertok(device_bind_by_name(dms->root, false, &driver_info_act_dma,
+	ut_assertok(device_bind_by_name(uts->root, false, &driver_info_act_dma,
 					&dma));
 	ut_assertnonnull(dma);
 
-	ut_assertok(device_bind_by_name(dms->root, false,
+	ut_assertok(device_bind_by_name(uts->root, false,
 					&driver_info_vital_clk, &vital));
 	ut_assertnonnull(vital);
 
-	ut_assertok(device_bind_by_name(dms->root, false,
+	ut_assertok(device_bind_by_name(uts->root, false,
 					&driver_info_act_dma_vital_clk,
 					&dma_vital));
 	ut_assertnonnull(dma_vital);
@@ -1133,11 +1124,10 @@ DM_TEST(dm_test_uclass_names, UT_TESTF_SCAN_PDATA);
 
 static int dm_test_inactive_child(struct unit_test_state *uts)
 {
-	struct dm_test_state *dms = uts->priv;
 	struct udevice *parent, *dev1, *dev2;
 
 	/* Skip the behaviour in test_post_probe() */
-	dms->skip_post_probe = 1;
+	uts->skip_post_probe = 1;
 
 	ut_assertok(uclass_first_device_err(UCLASS_TEST, &parent));
 

+ 51 - 0
test/dm/test-dm.c

@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2013 Google, Inc
+ */
+
+#include <common.h>
+#include <command.h>
+#include <console.h>
+#include <dm.h>
+#include <errno.h>
+#include <log.h>
+#include <malloc.h>
+#include <asm/global_data.h>
+#include <asm/state.h>
+#include <dm/root.h>
+#include <dm/uclass-internal.h>
+#include <test/test.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/**
+ * dm_test_run() - Run driver model tests
+ *
+ * Run all the available driver model tests, or a selection
+ *
+ * @test_name: Name of single test to run (e.g. "dm_test_fdt_pre_reloc" or just
+ *	"fdt_pre_reloc"), or NULL to run all
+ * @return 0 if all tests passed, 1 if not
+ */
+static int dm_test_run(const char *test_name)
+{
+	struct unit_test *tests = UNIT_TEST_SUITE_START(dm_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(dm_test);
+	int ret;
+
+	ret = ut_run_list("driver model", "dm_test_", tests, n_ents, test_name);
+
+	return ret ? CMD_RET_FAILURE : 0;
+}
+
+int do_ut_dm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+	const char *test_name = NULL;
+
+	if (argc > 1)
+		test_name = argv[1];
+
+	return dm_test_run(test_name);
+}

+ 9 - 3
test/dm/test-driver.c

@@ -18,7 +18,6 @@
 #include <test/ut.h>
 
 int dm_testdrv_op_count[DM_TEST_OP_COUNT];
-static struct unit_test_state *uts = &global_dm_test_state;
 
 static int testdrv_ping(struct udevice *dev, int pingval, int *pingret)
 {
@@ -37,6 +36,8 @@ static const struct test_ops test_ops = {
 
 static int test_bind(struct udevice *dev)
 {
+	struct unit_test_state *uts = test_get_state();
+
 	/* Private data should not be allocated */
 	ut_assert(!dev_get_priv(dev));
 
@@ -46,6 +47,7 @@ static int test_bind(struct udevice *dev)
 
 static int test_probe(struct udevice *dev)
 {
+	struct unit_test_state *uts = test_get_state();
 	struct dm_test_priv *priv = dev_get_priv(dev);
 
 	/* Private data should be allocated */
@@ -58,6 +60,8 @@ static int test_probe(struct udevice *dev)
 
 static int test_remove(struct udevice *dev)
 {
+	struct unit_test_state *uts = test_get_state();
+
 	/* Private data should still be allocated */
 	ut_assert(dev_get_priv(dev));
 
@@ -67,6 +71,8 @@ static int test_remove(struct udevice *dev)
 
 static int test_unbind(struct udevice *dev)
 {
+	struct unit_test_state *uts = test_get_state();
+
 	/* Private data should not be allocated */
 	ut_assert(!dev_get_priv(dev));
 
@@ -116,10 +122,10 @@ static int test_manual_bind(struct udevice *dev)
 
 static int test_manual_probe(struct udevice *dev)
 {
-	struct dm_test_state *dms = uts->priv;
+	struct unit_test_state *uts = test_get_state();
 
 	dm_testdrv_op_count[DM_TEST_OP_PROBE]++;
-	if (!dms->force_fail_alloc)
+	if (!uts->force_fail_alloc)
 		dev_set_priv(dev, calloc(1, sizeof(struct dm_test_priv)));
 	if (!dev_get_priv(dev))
 		return -ENOMEM;

+ 0 - 230
test/dm/test-main.c

@@ -1,230 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0+
-/*
- * Copyright (c) 2013 Google, Inc
- */
-
-#include <common.h>
-#include <command.h>
-#include <console.h>
-#include <dm.h>
-#include <errno.h>
-#include <log.h>
-#include <malloc.h>
-#include <asm/global_data.h>
-#include <asm/state.h>
-#include <dm/test.h>
-#include <dm/root.h>
-#include <dm/uclass-internal.h>
-#include <test/test.h>
-#include <test/test.h>
-#include <test/ut.h>
-
-DECLARE_GLOBAL_DATA_PTR;
-
-struct unit_test_state global_dm_test_state;
-static struct dm_test_state _global_priv_dm_test_state;
-
-/* Get ready for testing */
-static int dm_test_init(struct unit_test_state *uts, bool of_live)
-{
-	struct dm_test_state *dms = uts->priv;
-
-	memset(dms, '\0', sizeof(*dms));
-	gd->dm_root = NULL;
-	if (!CONFIG_IS_ENABLED(OF_PLATDATA))
-		memset(dm_testdrv_op_count, '\0', sizeof(dm_testdrv_op_count));
-	state_reset_for_test(state_get_current());
-
-	/* Determine whether to make the live tree available */
-	gd_set_of_root(of_live ? uts->of_root : NULL);
-	ut_assertok(dm_init(of_live));
-	dms->root = dm_root();
-
-	return 0;
-}
-
-/* Ensure all the test devices are probed */
-static int do_autoprobe(struct unit_test_state *uts)
-{
-	struct udevice *dev;
-	int ret;
-
-	/* Scanning the uclass is enough to probe all the devices */
-	for (ret = uclass_first_device(UCLASS_TEST, &dev);
-	     dev;
-	     ret = uclass_next_device(&dev))
-		;
-
-	return ret;
-}
-
-static int dm_test_destroy(struct unit_test_state *uts)
-{
-	int id;
-
-	for (id = 0; id < UCLASS_COUNT; id++) {
-		struct uclass *uc;
-
-		/*
-		 * If the uclass doesn't exist we don't want to create it. So
-		 * check that here before we call uclass_find_device().
-		 */
-		uc = uclass_find(id);
-		if (!uc)
-			continue;
-		ut_assertok(uclass_destroy(uc));
-	}
-
-	return 0;
-}
-
-static int dm_do_test(struct unit_test_state *uts, struct unit_test *test,
-		      bool of_live)
-{
-	struct sandbox_state *state = state_get_current();
-	const char *fname = strrchr(test->file, '/') + 1;
-
-	printf("Test: %s: %s%s\n", test->name, fname,
-	       !of_live ? " (flat tree)" : "");
-	ut_assertok(dm_test_init(uts, of_live));
-
-	uts->start = mallinfo();
-	if (test->flags & UT_TESTF_SCAN_PDATA)
-		ut_assertok(dm_scan_plat(false));
-	if (test->flags & UT_TESTF_PROBE_TEST)
-		ut_assertok(do_autoprobe(uts));
-	if (!CONFIG_IS_ENABLED(OF_PLATDATA) &&
-	    (test->flags & UT_TESTF_SCAN_FDT))
-		ut_assertok(dm_extended_scan(false));
-
-	/*
-	 * Silence the console and rely on console recording to get
-	 * our output.
-	 */
-	console_record_reset_enable();
-	if (!state->show_test_output)
-		gd->flags |= GD_FLG_SILENT;
-	test->func(uts);
-	gd->flags &= ~(GD_FLG_SILENT | GD_FLG_RECORD);
-	state_set_skip_delays(false);
-
-	ut_assertok(dm_test_destroy(uts));
-
-	return 0;
-}
-
-/**
- * dm_test_run_on_flattree() - Check if we should run a test with flat DT
- *
- * This skips long/slow tests where there is not much value in running a flat
- * DT test in addition to a live DT test.
- *
- * @return true to run the given test on the flat device tree
- */
-static bool dm_test_run_on_flattree(struct unit_test *test)
-{
-	const char *fname = strrchr(test->file, '/') + 1;
-
-	return !strstr(fname, "video") || strstr(test->name, "video_base");
-}
-
-static bool test_matches(const char *test_name, const char *find_name)
-{
-	if (!find_name)
-		return true;
-
-	if (!strcmp(test_name, find_name))
-		return true;
-
-	/* All tests have this prefix */
-	if (!strncmp(test_name, "dm_test_", 8))
-		test_name += 8;
-
-	if (!strcmp(test_name, find_name))
-		return true;
-
-	return false;
-}
-
-int dm_test_main(const char *test_name)
-{
-	struct unit_test *tests = ll_entry_start(struct unit_test, dm_test);
-	const int n_ents = ll_entry_count(struct unit_test, dm_test);
-	struct unit_test_state *uts = &global_dm_test_state;
-	struct unit_test *test;
-	int found;
-
-	uts->priv = &_global_priv_dm_test_state;
-	uts->fail_count = 0;
-
-	if (!CONFIG_IS_ENABLED(OF_PLATDATA)) {
-		/*
-		 * If we have no device tree, or it only has a root node, then
-		 * these * tests clearly aren't going to work...
-		 */
-		if (!gd->fdt_blob || fdt_next_node(gd->fdt_blob, 0, NULL) < 0) {
-			puts("Please run with test device tree:\n"
-			     "    ./u-boot -d arch/sandbox/dts/test.dtb\n");
-			ut_assert(gd->fdt_blob);
-		}
-	}
-
-	if (!test_name)
-		printf("Running %d driver model tests\n", n_ents);
-	else
-
-	found = 0;
-	uts->of_root = gd_of_root();
-	for (test = tests; test < tests + n_ents; test++) {
-		const char *name = test->name;
-		int runs;
-
-		if (!test_matches(name, test_name))
-			continue;
-
-		/* Run with the live tree if possible */
-		runs = 0;
-		if (CONFIG_IS_ENABLED(OF_LIVE)) {
-			if (!(test->flags & UT_TESTF_FLAT_TREE)) {
-				ut_assertok(dm_do_test(uts, test, true));
-				runs++;
-			}
-		}
-
-		/*
-		 * Run with the flat tree if we couldn't run it with live tree,
-		 * or it is a core test.
-		 */
-		if (!(test->flags & UT_TESTF_LIVE_TREE) &&
-		    (!runs || dm_test_run_on_flattree(test))) {
-			ut_assertok(dm_do_test(uts, test, false));
-			runs++;
-		}
-		found++;
-	}
-
-	if (test_name && !found)
-		printf("Test '%s' not found\n", test_name);
-	else
-		printf("Failures: %d\n", uts->fail_count);
-
-	/* Put everything back to normal so that sandbox works as expected */
-	gd_set_of_root(uts->of_root);
-	gd->dm_root = NULL;
-	ut_assertok(dm_init(CONFIG_IS_ENABLED(OF_LIVE)));
-	dm_scan_plat(false);
-	if (!CONFIG_IS_ENABLED(OF_PLATDATA))
-		dm_scan_fdt(false);
-
-	return uts->fail_count ? CMD_RET_FAILURE : 0;
-}
-
-int do_ut_dm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
-{
-	const char *test_name = NULL;
-
-	if (argc > 1)
-		test_name = argv[1];
-
-	return dm_test_main(test_name);
-}

+ 6 - 4
test/dm/test-uclass.c

@@ -17,8 +17,6 @@
 #include <test/test.h>
 #include <test/ut.h>
 
-static struct unit_test_state *uts = &global_dm_test_state;
-
 int test_ping(struct udevice *dev, int pingval, int *pingret)
 {
 	const struct test_ops *ops = device_get_ops(dev);
@@ -31,6 +29,7 @@ int test_ping(struct udevice *dev, int pingval, int *pingret)
 
 static int test_post_bind(struct udevice *dev)
 {
+	struct unit_test_state *uts = test_get_state();
 	struct dm_test_perdev_uc_pdata *uc_pdata;
 
 	dm_testdrv_op_count[DM_TEST_OP_POST_BIND]++;
@@ -56,6 +55,7 @@ static int test_pre_unbind(struct udevice *dev)
 static int test_pre_probe(struct udevice *dev)
 {
 	struct dm_test_uclass_perdev_priv *priv = dev_get_uclass_priv(dev);
+	struct unit_test_state *uts = test_get_state();
 
 	dm_testdrv_op_count[DM_TEST_OP_PRE_PROBE]++;
 	ut_assert(priv);
@@ -66,18 +66,18 @@ static int test_pre_probe(struct udevice *dev)
 
 static int test_post_probe(struct udevice *dev)
 {
+	struct unit_test_state *uts = test_get_state();
 	struct udevice *prev = list_entry(dev->uclass_node.prev,
 					    struct udevice, uclass_node);
 
 	struct dm_test_uclass_perdev_priv *priv = dev_get_uclass_priv(dev);
 	struct uclass *uc = dev->uclass;
-	struct dm_test_state *dms = uts->priv;
 
 	dm_testdrv_op_count[DM_TEST_OP_POST_PROBE]++;
 	ut_assert(priv);
 	ut_assert(device_active(dev));
 	priv->base_add = 0;
-	if (dms->skip_post_probe)
+	if (uts->skip_post_probe)
 		return 0;
 	if (&prev->uclass_node != &uc->dev_head) {
 		struct dm_test_uclass_perdev_priv *prev_uc_priv
@@ -101,6 +101,8 @@ static int test_pre_remove(struct udevice *dev)
 
 static int test_init(struct uclass *uc)
 {
+	struct unit_test_state *uts = test_get_state();
+
 	dm_testdrv_op_count[DM_TEST_OP_INIT]++;
 	ut_assert(uclass_get_priv(uc));
 

+ 2 - 2
test/env/cmd_ut_env.c

@@ -12,8 +12,8 @@
 
 int do_ut_env(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test, env_test);
-	const int n_ents = ll_entry_count(struct unit_test, env_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(env_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(env_test);
 
 	return cmd_ut_category("environment", "env_test_",
 			       tests, n_ents, argc, argv);

+ 5 - 0
test/image/Makefile

@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright 2021 Google LLC
+
+obj-$(CONFIG_SPL_BUILD) += spl_load.o

+ 91 - 0
test/image/spl_load.c

@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <image.h>
+#include <mapmem.h>
+#include <os.h>
+#include <spl.h>
+#include <test/ut.h>
+
+/* Declare a new SPL test */
+#define SPL_TEST(_name, _flags)		UNIT_TEST(_name, _flags, spl_test)
+
+/* Context used for this test */
+struct text_ctx {
+	int fd;
+};
+
+static ulong read_fit_image(struct spl_load_info *load, ulong sector,
+			    ulong count, void *buf)
+{
+	struct text_ctx *text_ctx = load->priv;
+	off_t offset, ret;
+	ssize_t res;
+
+	offset = sector * load->bl_len;
+	ret = os_lseek(text_ctx->fd, offset, OS_SEEK_SET);
+	if (ret != offset) {
+		printf("Failed to seek to %zx, got %zx (errno=%d)\n", offset,
+		       ret, errno);
+		return 0;
+	}
+
+	res = os_read(text_ctx->fd, buf, count * load->bl_len);
+	if (res == -1) {
+		printf("Failed to read %lx bytes, got %ld (errno=%d)\n",
+		       count * load->bl_len, res, errno);
+		return 0;
+	}
+
+	return count;
+}
+
+int board_fit_config_name_match(const char *name)
+{
+	return 0;
+}
+
+struct image_header *spl_get_load_buffer(ssize_t offset, size_t size)
+{
+	return map_sysmem(0x100000, 0);
+}
+
+static int spl_test_load(struct unit_test_state *uts)
+{
+	struct spl_image_info image;
+	struct image_header *header;
+	struct text_ctx text_ctx;
+	struct spl_load_info load;
+	char fname[256];
+	int ret;
+	int fd;
+
+	memset(&load, '\0', sizeof(load));
+	load.bl_len = 512;
+	load.read = read_fit_image;
+
+	ret = os_find_u_boot(fname, sizeof(fname), true);
+	if (ret) {
+		printf("(%s not found, error %d)\n", fname, ret);
+		return ret;
+	}
+	load.filename = fname;
+
+	header = spl_get_load_buffer(-sizeof(*header), sizeof(*header));
+
+	fd = os_open(fname, OS_O_RDONLY);
+	ut_assert(fd >= 0);
+	ut_asserteq(512, os_read(fd, header, 512));
+	text_ctx.fd = fd;
+
+	load.priv = &text_ctx;
+
+	ut_assertok(spl_load_simple_fit(&image, &load, 0, header));
+
+	return 0;
+}
+SPL_TEST(spl_test_load, 0);

+ 2 - 2
test/lib/cmd_ut_lib.c

@@ -13,8 +13,8 @@
 
 int do_ut_lib(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test, lib_test);
-	const int n_ents = ll_entry_count(struct unit_test, lib_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(lib_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(lib_test);
 
 	return cmd_ut_category("lib", "lib_test_", tests, n_ents, argc, argv);
 }

+ 1 - 1
test/log/Makefile

@@ -7,7 +7,7 @@ obj-$(CONFIG_CMD_LOG) += log_filter.o
 
 ifdef CONFIG_UT_LOG
 
-obj-y += test-main.o
+obj-y += log_ut.o
 
 ifdef CONFIG_SANDBOX
 obj-$(CONFIG_LOG_SYSLOG) += syslog_test.o

+ 2 - 2
test/log/test-main.c → test/log/log_ut.c

@@ -13,8 +13,8 @@
 
 int do_ut_log(struct cmd_tbl *cmdtp, int flag, int argc, char * const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test, log_test);
-	const int n_ents = ll_entry_count(struct unit_test, log_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(log_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(log_test);
 
 	return cmd_ut_category("log", "log_test_",
 			       tests, n_ents, argc, argv);

+ 2 - 3
test/optee/cmd_ut_optee.c

@@ -94,9 +94,8 @@ OPTEE_TEST(optee_fdt_protected_memory, 0);
 
 int do_ut_optee(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test,
-						 optee_test);
-	const int n_ents = ll_entry_count(struct unit_test, optee_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(optee_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(optee_test);
 	struct unit_test_state *uts;
 	void *fdt_optee = &__dtb_test_optee_optee_begin;
 	void *fdt_no_optee = &__dtb_test_optee_no_optee_begin;

+ 2 - 3
test/overlay/cmd_ut_overlay.c

@@ -213,9 +213,8 @@ OVERLAY_TEST(fdt_overlay_stacked, 0);
 
 int do_ut_overlay(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test,
-						 overlay_test);
-	const int n_ents = ll_entry_count(struct unit_test, overlay_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(overlay_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(overlay_test);
 	struct unit_test_state *uts;
 	void *fdt_base = &__dtb_test_fdt_base_begin;
 	void *fdt_overlay = &__dtb_test_fdt_overlay_begin;

+ 1 - 1
test/py/conftest.py

@@ -226,7 +226,7 @@ def pytest_configure(config):
         import u_boot_console_exec_attach
         console = u_boot_console_exec_attach.ConsoleExecAttach(log, ubconfig)
 
-re_ut_test_list = re.compile(r'_u_boot_list_2_(.*)_test_2_\1_test_(.*)\s*$')
+re_ut_test_list = re.compile(r'_u_boot_list_2_ut_(.*)_test_2_\1_test_(.*)\s*$')
 def generate_ut_subtest(metafunc, fixture_name, sym_path):
     """Provide parametrization for a ut_subtest fixture.
 

+ 0 - 1
test/py/tests/test_log.py

@@ -45,5 +45,4 @@ def test_log_dropped(u_boot_console):
     cons = u_boot_console
     cons.restart_uboot()
     output = cons.get_spawn_output().replace('\r', '')
-    assert 'sandbox: starting...' in output
     assert (not 'debug: main' in output)

+ 1 - 1
test/py/tests/test_ofplatdata.py

@@ -4,7 +4,7 @@
 import pytest
 import u_boot_utils as util
 
-@pytest.mark.boardspec('sandbox')
+@pytest.mark.boardspec('sandbox_spl')
 @pytest.mark.buildconfigspec('spl_of_platdata')
 def test_spl_devicetree(u_boot_console):
     """Test content of spl device-tree"""

+ 2 - 3
test/str_ut.c

@@ -107,9 +107,8 @@ STR_TEST(str_simple_strtoul, 0);
 
 int do_ut_str(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test,
-						 str_test);
-	const int n_ents = ll_entry_count(struct unit_test, str_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(str_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(str_test);
 
 	return cmd_ut_category("str", "str_", tests, n_ents, argc, argv);
 }

+ 426 - 0
test/test-main.c

@@ -0,0 +1,426 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2021 Google LLC
+ * Written by Simon Glass <sjg@chromium.org>
+ */
+
+#include <common.h>
+#include <console.h>
+#include <dm.h>
+#include <asm/state.h>
+#include <dm/root.h>
+#include <dm/test.h>
+#include <dm/uclass-internal.h>
+#include <test/test.h>
+#include <test/ut.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* This is valid when a test is running, NULL otherwise */
+static struct unit_test_state *cur_test_state;
+
+struct unit_test_state *test_get_state(void)
+{
+	return cur_test_state;
+}
+
+void test_set_state(struct unit_test_state *uts)
+{
+	cur_test_state = uts;
+}
+
+/**
+ * dm_test_pre_run() - Get ready to run a driver model test
+ *
+ * This clears out the driver model data structures. For sandbox it resets the
+ * state structure
+ *
+ * @uts: Test state
+ */
+static int dm_test_pre_run(struct unit_test_state *uts)
+{
+	bool of_live = uts->of_live;
+
+	uts->root = NULL;
+	uts->testdev = NULL;
+	uts->force_fail_alloc = false;
+	uts->skip_post_probe = false;
+	gd->dm_root = NULL;
+	if (!CONFIG_IS_ENABLED(OF_PLATDATA))
+		memset(dm_testdrv_op_count, '\0', sizeof(dm_testdrv_op_count));
+	state_reset_for_test(state_get_current());
+
+	/* Determine whether to make the live tree available */
+	gd_set_of_root(of_live ? uts->of_root : NULL);
+	ut_assertok(dm_init(of_live));
+	uts->root = dm_root();
+
+	return 0;
+}
+
+static int dm_test_post_run(struct unit_test_state *uts)
+{
+	int id;
+
+	for (id = 0; id < UCLASS_COUNT; id++) {
+		struct uclass *uc;
+
+		/*
+		 * If the uclass doesn't exist we don't want to create it. So
+		 * check that here before we call uclass_find_device().
+		 */
+		uc = uclass_find(id);
+		if (!uc)
+			continue;
+		ut_assertok(uclass_destroy(uc));
+	}
+
+	return 0;
+}
+
+/* Ensure all the test devices are probed */
+static int do_autoprobe(struct unit_test_state *uts)
+{
+	struct udevice *dev;
+	int ret;
+
+	/* Scanning the uclass is enough to probe all the devices */
+	for (ret = uclass_first_device(UCLASS_TEST, &dev);
+	     dev;
+	     ret = uclass_next_device(&dev))
+		;
+
+	return ret;
+}
+
+/*
+ * ut_test_run_on_flattree() - Check if we should run a test with flat DT
+ *
+ * This skips long/slow tests where there is not much value in running a flat
+ * DT test in addition to a live DT test.
+ *
+ * @return true to run the given test on the flat device tree
+ */
+static bool ut_test_run_on_flattree(struct unit_test *test)
+{
+	const char *fname = strrchr(test->file, '/') + 1;
+
+	if (!(test->flags & UT_TESTF_DM))
+		return false;
+
+	return !strstr(fname, "video") || strstr(test->name, "video_base");
+}
+
+/**
+ * test_matches() - Check if a test should be run
+ *
+ * This checks if the a test should be run. In the normal case of running all
+ * tests, @select_name is NULL.
+ *
+ * @prefix: String prefix for the tests. Any tests that have this prefix will be
+ *	printed without the prefix, so that it is easier to see the unique part
+ *	of the test name. If NULL, any suite name (xxx_test) is considered to be
+ *	a prefix.
+ * @test_name: Name of current test
+ * @select_name: Name of test to run (or NULL for all)
+ * @return true to run this test, false to skip it
+ */
+static bool test_matches(const char *prefix, const char *test_name,
+			 const char *select_name)
+{
+	if (!select_name)
+		return true;
+
+	if (!strcmp(test_name, select_name))
+		return true;
+
+	if (!prefix) {
+		const char *p = strstr(test_name, "_test_");
+
+		/* convert xxx_test_yyy to yyy, i.e. remove the suite name */
+		if (p)
+			test_name = p + 6;
+	} else {
+		/* All tests have this prefix */
+		if (!strncmp(test_name, prefix, strlen(prefix)))
+			test_name += strlen(prefix);
+	}
+
+	if (!strcmp(test_name, select_name))
+		return true;
+
+	return false;
+}
+
+/**
+ * ut_list_has_dm_tests() - Check if a list of tests has driver model ones
+ *
+ * @tests: List of tests to run
+ * @count: Number of tests to ru
+ * @return true if any of the tests have the UT_TESTF_DM flag
+ */
+static bool ut_list_has_dm_tests(struct unit_test *tests, int count)
+{
+	struct unit_test *test;
+
+	for (test = tests; test < tests + count; test++) {
+		if (test->flags & UT_TESTF_DM)
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * dm_test_restore() Put things back to normal so sandbox works as expected
+ *
+ * @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_scan_fdt(false);
+
+	return 0;
+}
+
+/**
+ * test_pre_run() - Handle any preparation needed to run a test
+ *
+ * @uts: Test state
+ * @test: Test to prepare for
+ * @return 0 if OK, -EAGAIN to skip this test since some required feature is not
+ *	available, other -ve on error (meaning that testing cannot likely
+ *	continue)
+ */
+static int test_pre_run(struct unit_test_state *uts, struct unit_test *test)
+{
+	if (test->flags & UT_TESTF_DM)
+		ut_assertok(dm_test_pre_run(uts));
+
+	ut_set_skip_delays(uts, false);
+
+	uts->start = mallinfo();
+
+	if (test->flags & UT_TESTF_SCAN_PDATA)
+		ut_assertok(dm_scan_plat(false));
+
+	if (test->flags & UT_TESTF_PROBE_TEST)
+		ut_assertok(do_autoprobe(uts));
+
+	if (!CONFIG_IS_ENABLED(OF_PLATDATA) &&
+	    (test->flags & UT_TESTF_SCAN_FDT))
+		ut_assertok(dm_extended_scan(false));
+
+	if (test->flags & UT_TESTF_CONSOLE_REC) {
+		int ret = console_record_reset_enable();
+
+		if (ret) {
+			printf("Skipping: Console recording disabled\n");
+			return -EAGAIN;
+		}
+	}
+	ut_silence_console(uts);
+
+	return 0;
+}
+
+/**
+ * test_post_run() - Handle cleaning up after a test
+ *
+ * @uts: Test state
+ * @test: Test to clean up after
+ * @return 0 if OK, -ve on error (meaning that testing cannot likely continue)
+ */
+static int test_post_run(struct unit_test_state *uts, struct unit_test *test)
+{
+	ut_unsilence_console(uts);
+	if (test->flags & UT_TESTF_DM)
+		ut_assertok(dm_test_post_run(uts));
+
+	return 0;
+}
+
+/**
+ * ut_run_test() - Run a single test
+ *
+ * This runs the test, handling any preparation and clean-up needed. It prints
+ * the name of each test before running it.
+ *
+ * @uts: Test state to update. The caller should ensure that this is zeroed for
+ *	the first call to this function. On exit, @uts->fail_count is
+ *	incremented by the number of failures (0, one hopes)
+ * @test_name: Test to run
+ * @name: Name of test, possibly skipping a prefix that should not be displayed
+ * @return 0 if all tests passed, -EAGAIN if the test should be skipped, -1 if
+ *	any failed
+ */
+static int ut_run_test(struct unit_test_state *uts, struct unit_test *test,
+		       const char *test_name)
+{
+	const char *fname = strrchr(test->file, '/') + 1;
+	const char *note = "";
+	int ret;
+
+	if ((test->flags & UT_TESTF_DM) && !uts->of_live)
+		note = " (flat tree)";
+	printf("Test: %s: %s%s\n", test_name, fname, note);
+
+	/* Allow access to test state from drivers */
+	test_set_state(uts);
+
+	ret = test_pre_run(uts, test);
+	if (ret == -EAGAIN)
+		return -EAGAIN;
+	if (ret)
+		return ret;
+
+	test->func(uts);
+
+	ret = test_post_run(uts, test);
+	if (ret)
+		return ret;
+
+	test_set_state( NULL);
+
+	return 0;
+}
+
+/**
+ * ut_run_test_live_flat() - Run a test with both live and flat tree
+ *
+ * This calls ut_run_test() with livetree enabled, which is the standard setup
+ * for runnig tests. Then, for driver model test, it calls it again with
+ * livetree disabled. This allows checking of flattree being used when OF_LIVE
+ * is enabled, as is the case in U-Boot proper before relocation, as well as in
+ * SPL.
+ *
+ * @uts: Test state to update. The caller should ensure that this is zeroed for
+ *	the first call to this function. On exit, @uts->fail_count is
+ *	incremented by the number of failures (0, one hopes)
+ * @test: Test to run
+ * @name: Name of test, possibly skipping a prefix that should not be displayed
+ * @return 0 if all tests passed, -EAGAIN if the test should be skipped, -1 if
+ *	any failed
+ */
+static int ut_run_test_live_flat(struct unit_test_state *uts,
+				 struct unit_test *test, const char *name)
+{
+	int runs;
+
+	/* Run with the live tree if possible */
+	runs = 0;
+	if (CONFIG_IS_ENABLED(OF_LIVE)) {
+		if (!(test->flags & UT_TESTF_FLAT_TREE)) {
+			uts->of_live = true;
+			ut_assertok(ut_run_test(uts, test, test->name));
+			runs++;
+		}
+	}
+
+	/*
+	 * Run with the flat tree if we couldn't run it with live tree,
+	 * or it is a core test.
+	 */
+	if (!(test->flags & UT_TESTF_LIVE_TREE) &&
+	    (!runs || ut_test_run_on_flattree(test))) {
+		uts->of_live = false;
+		ut_assertok(ut_run_test(uts, test, test->name));
+		runs++;
+	}
+
+	return 0;
+}
+
+/**
+ * ut_run_tests() - Run a set of tests
+ *
+ * This runs the tests, handling any preparation and clean-up needed. It prints
+ * the name of each test before running it.
+ *
+ * @uts: Test state to update. The caller should ensure that this is zeroed for
+ *	the first call to this function. On exit, @uts->fail_count is
+ *	incremented by the number of failures (0, one hopes)
+ * @prefix: String prefix for the tests. Any tests that have this prefix will be
+ *	printed without the prefix, so that it is easier to see the unique part
+ *	of the test name. If NULL, no prefix processing is done
+ * @tests: List of tests to run
+ * @count: Number of tests to run
+ * @select_name: Name of a single test to run (from the list provided). If NULL
+ *	then all tests are run
+ * @return 0 if all tests passed, -ENOENT if test @select_name was not found,
+ *	-EBADF if any failed
+ */
+static int ut_run_tests(struct unit_test_state *uts, const char *prefix,
+			struct unit_test *tests, int count,
+			const char *select_name)
+{
+	struct unit_test *test;
+	int found = 0;
+
+	for (test = tests; test < tests + count; test++) {
+		const char *test_name = test->name;
+		int ret;
+
+		if (!test_matches(prefix, test_name, select_name))
+			continue;
+		ret = ut_run_test_live_flat(uts, test, select_name);
+		found++;
+		if (ret == -EAGAIN)
+			continue;
+		if (ret)
+			return ret;
+	}
+	if (select_name && !found)
+		return -ENOENT;
+
+	return uts->fail_count ? -EBADF : 0;
+}
+
+int ut_run_list(const char *category, const char *prefix,
+		struct unit_test *tests, int count, const char *select_name)
+{
+	struct unit_test_state uts = { .fail_count = 0 };
+	bool has_dm_tests = false;
+	int ret;
+
+	if (!CONFIG_IS_ENABLED(OF_PLATDATA) &&
+	    ut_list_has_dm_tests(tests, count)) {
+		has_dm_tests = true;
+		/*
+		 * If we have no device tree, or it only has a root node, then
+		 * these * tests clearly aren't going to work...
+		 */
+		if (!gd->fdt_blob || fdt_next_node(gd->fdt_blob, 0, NULL) < 0) {
+			puts("Please run with test device tree:\n"
+			     "    ./u-boot -d arch/sandbox/dts/test.dtb\n");
+			return CMD_RET_FAILURE;
+		}
+	}
+
+	if (!select_name)
+		printf("Running %d %s tests\n", count, category);
+
+	uts.of_root = gd_of_root();
+	ret = ut_run_tests(&uts, prefix, tests, count, select_name);
+
+	if (ret == -ENOENT)
+		printf("Test '%s' not found\n", select_name);
+	else
+		printf("Failures: %d\n", uts.fail_count);
+
+	/* Best efforts only...ignore errors */
+	if (has_dm_tests)
+		dm_test_restore(uts.of_root);
+
+	return ret;
+}

+ 2 - 2
test/unicode_ut.c

@@ -615,8 +615,8 @@ UNICODE_TEST(unicode_test_efi_create_indexed_name);
 
 int do_ut_unicode(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {
-	struct unit_test *tests = ll_entry_start(struct unit_test, unicode_test);
-	const int n_ents = ll_entry_count(struct unit_test, unicode_test);
+	struct unit_test *tests = UNIT_TEST_SUITE_START(unicode_test);
+	const int n_ents = UNIT_TEST_SUITE_COUNT(unicode_test);
 
 	return cmd_ut_category("Unicode", "unicode_test_",
 			       tests, n_ents, argc, argv);

+ 7 - 0
test/ut.c

@@ -133,3 +133,10 @@ void ut_unsilence_console(struct unit_test_state *uts)
 {
 	gd->flags &= ~(GD_FLG_SILENT | GD_FLG_RECORD);
 }
+
+void ut_set_skip_delays(struct unit_test_state *uts, bool skip_delays)
+{
+#ifdef CONFIG_SANDBOX
+	state_set_skip_delays(skip_delays);
+#endif
+}