KERN_TESTFRWK(9)

✨ NAME ✨

kern_testfrwk β€” a kernel testing framework πŸ’–

okay so real quick, this is the KERN_TESTFRWK(9) page from the Kernel Developer's Manual, and it's literally the blueprint for testing bits of the FreeBSD kernel. buckle up bestie, we're going in πŸ¦‹


🌸 SYNOPSIS 🌸

kld_load kern_testfrwk

πŸŽ€ DESCRIPTION πŸŽ€

so like… what even is that sys/tests directory in the kernel, bestie? πŸ€”

have you ever wanted to test some part of the FreeBSD kernel and there was just no real way from user-land to make the thing you wanted to happen actually happen? like, say, an error path, or a locking situation that only occurs once in a blue moon? 😭

if that's you, then the kernel test framework is exactly what you've been looking for. it's designed to help you create the situation you want. main character energy fr fr πŸ‘‘

there are two components to the whole system: the test framework and your test. this doc describes both of them, and it uses the test that got submitted with the initial commit of this code to talk through things β€” that's callout_test(4). πŸ’…

all of the tests become kernel loadable modules. the test you write should have a dependency on the test framework, so it gets loaded automatically along with your test. for example, you can see how to do this at the bottom of callout_test.c in sys/tests/callout_test/callout_test.c. ✨

the framework itself lives in sys/tests/framework/kern_testfrwk.c. its job is to manage the tests that are loaded. (more than one can be loaded, no cap). the idea is pretty simple: you load the test framework, and then you load your test. 🫢

when your test loads, you register your tests with the kernel test framework. you do that through a call to kern_testframework_register(). usually this happens at the module load event, like this:

                     switch (type) {
                     case MOD_LOAD:
                             err = kern_testframework_register("callout_test",
                                 run_callout_test);

here the test is "callout_test" and it's registered to run the function run_callout_test(), passing it a struct kern_test *ptr. the kern_test structure is defined in kern_testfrwk.h. πŸ’œ

      struct kern_test {
              char name[TEST_NAME_LEN];
              int num_threads;  /* Fill in how many threads you want */
              int tot_threads_running;       /* Private to framework */
              uint8_t test_options[TEST_OPTION_SPACE];
      };

the user sends this structure down via a sysctl to start your test. they put the same name you registered ("callout_test" in our example) in the name field. the user can also set the number of threads to run with num_threads. 🌈

the framework will start the requested number of kernel threads, all running your test at the same time. the user does not specify anything in tot_threads_running β€” that one is private to the framework. as the framework calls each of your tests, it sets tot_threads_running to the index of the thread that your call is being made from. for example, if the user sets num_threads to 2, then run_callout_test() will be called once with tot_threads_running set to 0, and a second time with tot_threads_running set to 1. πŸ”₯

the test_options field is a test-specific set of information that's an opaque blob. it gets passed in from user space and has a maximum size of 256 bytes. you can pass arbitrary test input in that space. in the case of callout_test we reshape it into:

      struct callout_test {
              int number_of_callouts;
              int test_number;
      };

so the first lines of run_callout_test() do the following to get at the user-specific data:

              struct callout_test *u;
              size_t sz;
              int i;
              struct callout_run *rn;
              int index = test->tot_threads_running;

              u = (struct callout_test *)test->test_options;

that way it can access u->test_number (there are two types of tests provided with this test) and u->number_of_callouts (how many simultaneous callouts to run). πŸ“

your test can do anything with these bytes. so the callout_test in question wants to create a situation where multiple callouts are all run β€” that's the number_of_callouts β€” and it tries to cancel the callout with the new callout_async_drain(). the threads do this by acquiring the lock in question, and then starting each of the callouts. it waits for the callouts to all go off (the executor spins and waits). this forces the situation where the callouts have expired and are all waiting on the lock that the executor holds. after the callouts are all blocked, the executor calls callout_async_drain() on each callout and releases the lock. 😀

after all the callouts are done, a total status is printed showing the results via printf(9). the human tester can run dmesg(8) to see the results. in this case it's expected that if you're running test 0, all the callouts expire on the same CPU, so only one callout_drain function would have been called. the number of zero_returns should match the number of callout_drains that were called, i.e., 1. the one_returns should be the remainder of the callouts. if the test number was 1, the callouts were spread across all CPUs. the number of zero_returns will again match the number of drain calls made, which matches the number of CPUs that were put in use. πŸ’―

more than one thread can be used with this test, though in the example case it's probably not necessary. πŸ™ˆ

you should not need to change the framework. just add tests and register them after loading. periodt. ✨


πŸ‘‘ AUTHORS πŸ‘‘

the kernel test framework was written by Randall Stewart <rrs@FreeBSD.org> with help from John Mark Gurney <jmg@FreeBSD.org>. absolute icons, no notes πŸ’–


and that's the whole page bestie!! πŸ¦‹ this one came from the Debian build of the manual, dated November 12, 2015 β€” go forth and write unhinged kernel tests, ily, mwah πŸ’…πŸŽ€

πŸ“¦ where this page comes from