All of lore.kernel.org
 help / color / mirror / Atom feed
From: Simon Glass <sjg@chromium.org>
To: u-boot@lists.denx.de
Subject: [PATCH v2 40/40] doc: Explain briefly how to write new tests
Date: Sat, 30 Jan 2021 20:32:48 -0700	[thread overview]
Message-ID: <20210131033248.1502385-41-sjg@chromium.org> (raw)
In-Reply-To: <20210131033248.1502385-1-sjg@chromium.org>

Add a second on writing tests, covering when to use Python and C, where
to put the tests, etc. Add a link to the existing Python test
documentation.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

Changes in v2:
- Add new patches to cover running an SPL test

 doc/develop/index.rst         |   1 +
 doc/develop/py_testing.rst    |   3 +-
 doc/develop/testing.rst       |   2 +
 doc/develop/tests_sandbox.rst |   7 +
 doc/develop/tests_writing.rst | 335 ++++++++++++++++++++++++++++++++++
 5 files changed, 347 insertions(+), 1 deletion(-)
 create mode 100644 doc/develop/tests_writing.rst

diff --git a/doc/develop/index.rst b/doc/develop/index.rst
index a81b0de6884..2c5f3c39d79 100644
--- a/doc/develop/index.rst
+++ b/doc/develop/index.rst
@@ -31,6 +31,7 @@ Testing
 
    testing
    py_testing
+   tests_writing
    tests_sandbox
 
 Refactoring
diff --git a/doc/develop/py_testing.rst b/doc/develop/py_testing.rst
index f71e837aa96..c5f5adb7bed 100644
--- a/doc/develop/py_testing.rst
+++ b/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
diff --git a/doc/develop/testing.rst b/doc/develop/testing.rst
index b181c2e2e41..ced13ac8bb4 100644
--- a/doc/develop/testing.rst
+++ b/doc/develop/testing.rst
@@ -117,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
 -----------
diff --git a/doc/develop/tests_sandbox.rst b/doc/develop/tests_sandbox.rst
index 500be46eaed..097d5b50d79 100644
--- a/doc/develop/tests_sandbox.rst
+++ b/doc/develop/tests_sandbox.rst
@@ -194,3 +194,10 @@ linker_list::
    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.
+
diff --git a/doc/develop/tests_writing.rst b/doc/develop/tests_writing.rst
new file mode 100644
index 00000000000..a518bba5639
--- /dev/null
+++ b/doc/develop/tests_writing.rst
@@ -0,0 +1,335 @@
+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
+
+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
+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                          Yes, but 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        Yes
+                       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, 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.
+
+
+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.
-- 
2.30.0.365.g02bc693789-goog

      parent reply	other threads:[~2021-01-31  3:32 UTC|newest]

Thread overview: 43+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-01-31  3:32 [PATCH v2 00/40] test: Refactor tests to have a single test runner Simon Glass
2021-01-31  3:32 ` [PATCH v2 01/40] doc: Tidy up testing section Simon Glass
2021-01-31  3:32 ` [PATCH v2 02/40] doc: Document make tcheck Simon Glass
2021-01-31  3:32 ` [PATCH v2 03/40] sandbox: Drop the 'starting...' message unless testing Simon Glass
2021-01-31  3:32 ` [PATCH v2 04/40] doc: Explain how to run tests without pytest Simon Glass
2021-01-31  3:32 ` [PATCH v2 05/40] doc: Document how sandbox_spl_tests are run Simon Glass
2021-02-01 15:06   ` Pratyush Yadav
2021-01-31  3:32 ` [PATCH v2 06/40] test: Correct setexpr test prefix Simon Glass
2021-01-31  3:32 ` [PATCH v2 07/40] test: Mark all driver model tests with a flag Simon Glass
2021-01-31  3:32 ` [PATCH v2 08/40] test: Rename test-main.c to test-dm.c Simon Glass
2021-01-31  3:32 ` [PATCH v2 09/40] test: Add an overall test runner Simon Glass
2021-01-31  3:32 ` [PATCH v2 10/40] test: Create pre/post-run functions Simon Glass
2021-01-31  3:32 ` [PATCH v2 11/40] test: Call test_pre/post_run() from driver model tests Simon Glass
2021-01-31  3:32 ` [PATCH v2 12/40] test: Move dm_extended_scan() to test_pre_run() Simon Glass
2021-01-31  3:32 ` [PATCH v2 13/40] test: Move do_autoprobe() " Simon Glass
2021-01-31  3:32 ` [PATCH v2 14/40] test: Move dm_scan_plat() " Simon Glass
2021-01-31  3:32 ` [PATCH v2 15/40] test: Drop mallinfo() work-around Simon Glass
2021-01-31  3:32 ` [PATCH v2 16/40] test: Move console silencing to test_pre_run() Simon Glass
2021-01-31  3:32 ` [PATCH v2 17/40] test: Move delay skipping " Simon Glass
2021-01-31  3:32 ` [PATCH v2 18/40] test: Handle driver model reinit in test_pre_run() Simon Glass
2021-01-31  3:32 ` [PATCH v2 19/40] test: Drop struct dm_test_state Simon Glass
2021-01-31  3:32 ` [PATCH v2 20/40] test: Move dm_test_init() into test-main.c Simon Glass
2021-01-31  3:32 ` [PATCH v2 21/40] test: Move dm_test_destroy() " Simon Glass
2021-01-31  3:32 ` [PATCH v2 22/40] test: Move test running into a separate function Simon Glass
2021-01-31  3:32 ` [PATCH v2 23/40] test: Use ut_run_test() to run driver model tests Simon Glass
2021-01-31  3:32 ` [PATCH v2 24/40] test: Drop dm_do_test() Simon Glass
2021-01-31  3:32 ` [PATCH v2 25/40] test: Add ut_run_test_live_flat() to run tests twice Simon Glass
2021-01-31  3:32 ` [PATCH v2 26/40] test: Use a local variable for test state Simon Glass
2021-02-01 16:47   ` Alex G.
2021-01-31  3:32 ` [PATCH v2 27/40] test: Run driver-model tests using ut_run_list() Simon Glass
2021-01-31  3:32 ` [PATCH v2 28/40] test: Use return values in dm_test_run() Simon Glass
2021-01-31  3:32 ` [PATCH v2 29/40] test: Move the devicetree check into ut_run_list() Simon Glass
2021-01-31  3:32 ` [PATCH v2 30/40] test: Move restoring of driver model state to ut_run_list() Simon Glass
2021-01-31  3:32 ` [PATCH v2 31/40] test: log: Rename log main test file to log_ut.c Simon Glass
2021-01-31  3:32 ` [PATCH v2 32/40] test: Add a macros for finding tests in linker_lists Simon Glass
2021-01-31  3:32 ` [PATCH v2 33/40] test: Rename all linker lists to have a ut_ prefix Simon Glass
2021-01-31  3:32 ` [PATCH v2 34/40] test: Allow SPL to run any available test Simon Glass
2021-01-31  3:32 ` [PATCH v2 35/40] sandbox: Update os_find_u_boot() to find the .img file Simon Glass
2021-01-31  3:32 ` [PATCH v2 36/40] spl: Convert spl_fit to work with sandbox Simon Glass
2021-01-31  3:32 ` [PATCH v2 37/40] doc: Move coccinelle into its own section Simon Glass
2021-01-31  3:32 ` [PATCH v2 38/40] spl: test: Add a test for spl_load_simple_fit() Simon Glass
2021-01-31  3:32 ` [PATCH v2 39/40] test: sandbox: Move sandbox test docs into doc/develop Simon Glass
2021-01-31  3:32 ` Simon Glass [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210131033248.1502385-41-sjg@chromium.org \
    --to=sjg@chromium.org \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.