NuttX Unit Test Selection (NUTS)

NUTS is a selection of unit test suites that can be compiled and run on NuttX devices to verify correct operation of the OS APIs. NUTS depends on the cmocka testing framework in order to run the unit tests.

Building & Deploying

In order to build and deploy NUTS on your target device, you will need to enable the cmocka library and CONFIG_TESTING_NUTS. Once NUTS is enabled, you will see a list of available test categories in Kconfig. You may enable the test categories that you want to run tests for.

Once a test category is enabled, you will have access to toggles for each test group within the category. For example, if you enable the ‘devices’ category, you will then be able to choose if you want to include the tests for /dev/zero or /dev/null, etc. By default, all test groups within a category are enabled and must be opted-out of by disabling their toggle.

Note

Some individual tests may have specific dependencies which need to be enabled first, so do not assume that the visible list of tests in Kconfig is the complete selection (unless you have enabled showing hidden options in Kconfig). For example, /dev/urandom depends on CONFIG_DEV_URANDOM to be enabled, and will not show in the menu unless the dependency is selected.

Once built and deployed to the target, you can use the following command to see your test results (example of running /dev/zero tests only):

nsh> nuts
[==========] /dev/zero: Running 8 test(s).
[ RUN      ] open_rdonly
[       OK ] open_rdonly
[ RUN      ] open_rdwr
[       OK ] open_rdwr
[ RUN      ] open_wronly
[       OK ] open_wronly
[ RUN      ] readzero
[       OK ] readzero
[ RUN      ] readlarge
[       OK ] readlarge
[ RUN      ] writezero
[       OK ] writezero
[ RUN      ] writelarge
[       OK ] writelarge
[ RUN      ] wrrd
[       OK ] wrrd
[==========] /dev/zero: 8 test(s) run.
[  PASSED  ] 8 test(s).

Included Tests

NUTS tests are split into categories. Within each category there are test groups/suites which contain all of the individual test cases related to a specific API.

Test Cases

The output of NUTS displays the pass/fail/skip results of each test case that has been included in the build. Although the names of the test cases are intended to describe what the test case is verifying, sometimes more information is needed. Each test case in NUTS has a corresponding source-code comment/docstring which includes a description of what the test is verifying. You can also determine more information by looking at the assertion statements within the test itself.

Adding New Tests

In order to add new tests, first take a look at the cmocka API documentation.

Then, you should add a new .c file under the category that you wish to extend. All of the test cases are static functions that take a void **state in order to pass in context from a setup function. Each test case function should have a docstring comment which describes what the particular test case is testing (and the name should be as descriptive as possible). Setup and teardown functions must start with setup_ and teardown_ respectively, where the suffix is a descriptive name about what is being set up/torn down.

An example of a test case taken from the circular buffer test suite is:

static int setup_empty_cbuf(void **state)
{
  *state = &g_cbuf;
  return circbuf_init(&g_cbuf, g_buf, sizeof(g_buf));
}

static int teardown_cbuf(void **state)
{
  circbuf_uninit(*state);
  if (circbuf_is_init(*state)) return -1;
  return 0;
}

/****************************************************************************
 * Name: empty_postinit
 *
 * Description:
 *   Tests that a circular buffer is empty right after being initialized.
 ****************************************************************************/

static void empty_postinit(void **state)
{
  struct circbuf_s *cbuf = *state;

  assert_true(circbuf_is_empty(cbuf));
  assert_false(circbuf_is_full(cbuf));
  assert_uint_equal(0, circbuf_used(cbuf));
  assert_uint_equal(CBUF_SIZE, circbuf_space(cbuf));
}

Each test group must implement a public function for running all of the test cases, following the convention:

int nuts_category_group(void)
{
  static const struct CMUnitTests tests[] =
  {
    cmocka_unit_test(this_is_test_one),
    cmocka_unit_test_setup_teardown(this_is_test_two, setup_func,
                                    teardown_func),
  };

  return cmocka_run_group_tests_name("group name", tests, NULL, NULL);
}

where ‘category’ is the name of the test category, and ‘group’ is the name of the test group (i.e. category of ‘devices’ and group of ‘devnull’).

Inside the tests.h header file for the category, include the following under the public function prototypes:

#ifdef CONFIG_TESTING_NUTS_CATEGORY_GROUP
int nuts_category_group(void);
#else
#define nuts_category_group()
#endif /* CONFIG_TESTING_NUTS_CATEGORY_GROUP */

where CONFIG_TESTING_NUTS_CATEGORY_GROUP is the Kconfig option to toggle on/off the test group. You will have to add this to the NUTS Kconfig file.

In order to reduce the amount of special switches in the Makefile/CMakeLists.txt files, all source files in each category directory are included with a wild card. Thus, to avoid compiling unselected test groups, each test group’s C file should be surrounded in the gaurd:

#ifdef CONFIG_TESTING_NUTS_CATEGORY_GROUP

/* ... your test cases and public test runner function ... */

#endif /* CONFIG_TESTING_NUTS_CATEGORY_GROUP */