Let's_start_KUnit! Agenda
What_is_KUnit Es scheint eine neue Funktion zu sein, die in Version 5.5.0 hinzugefügt wurde. Ein leichtes Framework, das Unit-Tests des Kernels ermöglicht, ohne dass eine VM erforderlich ist.
Offiziell
Getting_started
Ich mache es mit einem Python-Wrapper.
Wenn Sie neugierig sind, lesen Sie tools / testing / kunit.py
, um dies herauszufinden.
Wechseln Sie vorerst in das Verzeichnis, in dem sich die Kernelquelle befindet
./tools/testing/kunit/kunit.py run --defconfig
Dann werden verschiedene Dinge erwachsen. früh
Generating .config ...
[00:00:39] Building KUnit Kernel ...
[00:00:50] Starting KUnit Kernel ...
[00:00:50] ============================================================
[00:00:50] ======== [PASSED] kunit-try-catch-test ========
[00:00:50] [PASSED] kunit_test_try_catch_successful_try_no_catch
[00:00:50] [PASSED] kunit_test_try_catch_unsuccessful_try_does_catch
[00:00:50] ============================================================
[00:00:50] ======== [PASSED] kunit-resource-test ========
[00:00:50] [PASSED] kunit_resource_test_init_resources
[00:00:50] [PASSED] kunit_resource_test_alloc_resource
[00:00:50] [PASSED] kunit_resource_test_destroy_resource
[00:00:50] [PASSED] kunit_resource_test_cleanup_resources
[00:00:50] [PASSED] kunit_resource_test_proper_free_ordering
[00:00:50] ============================================================
[00:00:50] ======== [PASSED] string-stream-test ========
[00:00:50] [PASSED] string_stream_test_empty_on_creation
[00:00:50] [PASSED] string_stream_test_not_empty_after_add
[00:00:50] [PASSED] string_stream_test_get_string
[00:00:50] ============================================================
[00:00:50] ======== [PASSED] example ========
[00:00:50] [PASSED] example_simple_test
[00:00:50] ============================================================
[00:00:50] Testing complete. 11 tests run. 0 failed. 0 crashed.
[00:00:50] Elapsed time: 74.073s total, 2.343s configuring, 71.565s building, 0.166s running
Wenn Sie dies tun, wächst .kunitconfig
unter cwd. Inhalt
CONFIG_KUNIT=y
CONFIG_KUNIT_TEST=y
CONFIG_KUNIT_EXAMPLE_TEST=y
Using_KUnit
Es ist gut, dies basierend auf der Standardeinstellung zu tun, die Sie zuvor vorgenommen haben.
Vorerst, da .kunitconfig
wächst,
./tools/testing/kunit/kunit.py run
kann durchgeführt werden
Gegenwärtig ist das Ergebnis natürlich die gleiche Ausgabe wie zuvor.
Zum Verständnis werde ich ein einfaches Beispiel machen.
Holen Sie sich task_struct
von der PID und bereiten Sie eine Funktion vor, um die PID dieser Task zurückzugeben
Der Inhalt jeder Datei
In drivers / misc / task_pid.h
#include <linux/sched.h>
#include <linux/fs_struct.h>
#include <linux/dcache.h>
int dump_task_by_pid(int nr, struct task_struct *taskbuf);
In drivers / misc / task_pid.c
#include <linux/pid.h>
#include "task_pid.h"
int dump_task_by_pid(int nr, struct task_struct *taskbuf)
{
struct pid *pid = find_get_pid(nr);
if(!pid)
return -1;
struct task_struct *tmp = pid_task(pid, PIDTYPE_PID);
if(!tmp)
return -1;
else
*taskbuf = *tmp;
return taskbuf->pid;
}
Schreiben.
In drivers / misc / Kconfig
...
config TASK_FROM_PID
bool "Get task from pid and return task's pid"
config TASK_FROM_PID_TEST
bool "Test get task from pid"
depends on TASK_FROM_PID && KUNIT
...
,
In drivers / misc / Makefile
...
obj-$(CONFIG_TASK_FROM_PID) += task_pid.o
obj-$(CONFIG_TASK_FROM_PID_TEST) += task-from-pid-test.o
...
Hinzufügen. Jetzt können Sie den Test schreiben.
drivers/misc/task-from-pid-test.c
#include <kunit/test.h>
#include "task_pid.h"
static void task_from_pid_test(struct kunit *test)
{
struct task_struct taskbuf;
KUNIT_EXPECT_EQ(test, 1, dump_task_by_pid(1, &taskbuf));
KUNIT_EXPECT_EQ(test, 1, taskbuf.pid);
KUNIT_EXPECT_EQ(test, '/', taskbuf.fs->root.dentry->d_name.name[0]);
}
static struct kunit_case task_dump_test_cases[] = {
KUNIT_CASE(task_from_pid_test),
{}
};
static struct kunit_suite task_dump_test_suite = {
.name = "dump task from pid",
.test_cases = task_dump_test_cases,
};
kunit_test_suite(task_dump_test_suite);
Wie Sie sehen können, schreiben Sie in C. Es sieht aus wie das übliche transzendentale Makro, daher lautet die Erklärung später
In drivers / misc / Makefile
obj-$(CONFIG_MISC_EXAMPLE_TEST) += example-test.o
,
In .kunitconfig
CONFIG_TASK_FROM_PID=y
CONFIG_TASK_FROM_PID_TEST=y
Abgeschlossen durch Hinzufügen
./tools/testing/kunit/kunit.py run
Kann mit gemacht werden
Ergebnis (Auszug)
[00:00:38] ============================================================
[00:00:38] ======== [PASSED] dump task from pid ========
[00:00:38] [PASSED] task_from_pid_test
[00:00:38] ============================================================
Macros
Werfen wir vorerst einen Blick auf include / kunit / test.h
.
Es werden verschiedene Testmakros definiert
Zum Beispiel das vorherige KUNIT_ASSERT_EQ
/**
* KUNIT_ASSERT_EQ() - Sets an assertion that @left and @right are equal.
* @test: The test context object.
* @left: an arbitrary expression that evaluates to a primitive C type.
* @right: an arbitrary expression that evaluates to a primitive C type.
*
* Sets an assertion that the values that @left and @right evaluate to are
* equal. This is the same as KUNIT_EXPECT_EQ(), except it causes an assertion
* failure (see KUNIT_ASSERT_TRUE()) when the assertion is not met.
*/
#define KUNIT_ASSERT_EQ(test, left, right) \
KUNIT_BINARY_EQ_ASSERTION(test, KUNIT_ASSERTION, left, right)
#define KUNIT_ASSERT_EQ_MSG(test, left, right, fmt, ...) \
KUNIT_BINARY_EQ_MSG_ASSERTION(test, \
KUNIT_ASSERTION, \
left, \
right, \
fmt, \
##__VA_ARGS__)
Andere nützliche Makros wie "KUNIT_ASSERT_GT" (größer als) und "KUNIT_ASSERT_PTR_EQ" (Zeigervergleich) werden über 1500 Zeilen definiert.
Außerdem wird hier "KUNIT_CASE" definiert, und sein Inhalt ist einfach.
Wenn Sie einen Funktionszeiger übergeben, werden nur die Zeiger- und Namensfelder ohne direkten Kontakt mit den Elementen gefüllt, die Sie in kunit_case
privat halten möchten.
/**
* struct kunit_case - represents an individual test case.
*
* @run_case: the function representing the actual test case.
* @name: the name of the test case.
*
* A test case is a function with the signature,
* ``void (*)(struct kunit *)``
* that makes expectations and assertions (see KUNIT_EXPECT_TRUE() and
* KUNIT_ASSERT_TRUE()) about code under test. Each test case is associated
* with a &struct kunit_suite and will be run after the suite's init
* function and followed by the suite's exit function.
*
* A test case should be static and should only be created with the
* KUNIT_CASE() macro; additionally, every array of test cases should be
* terminated with an empty test case.
*
* Example:
*
* .. code-block:: c
*
* void add_test_basic(struct kunit *test)
* {
* KUNIT_EXPECT_EQ(test, 1, add(1, 0));
* KUNIT_EXPECT_EQ(test, 2, add(1, 1));
* KUNIT_EXPECT_EQ(test, 0, add(-1, 1));
* KUNIT_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX));
* KUNIT_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN));
* }
*
* static struct kunit_case example_test_cases[] = {
* KUNIT_CASE(add_test_basic),
* {}
* };
*
*/
struct kunit_case {
void (*run_case)(struct kunit *test);
const char *name;
/* private: internal use only. */
bool success;
};
/**
* KUNIT_CASE - A helper for creating a &struct kunit_case
*
* @test_name: a reference to a test case function.
*
* Takes a symbol for a function representing a test case and creates a
* &struct kunit_case object from it. See the documentation for
* &struct kunit_case for an example on how to use it.
*/
#define KUNIT_CASE(test_name) { .run_case = test_name, .name = #test_name }
Die zur Registrierung des Tests verwendete kunit_suite
enthält zwei andere Elemente als den Namen und den Testfall. Jedes kann die Funktion festlegen, die vor und nach jedem Testfall ausgeführt werden soll.
/**
* struct kunit_suite - describes a related collection of &struct kunit_case
*
* @name: the name of the test. Purely informational.
* @init: called before every test case.
* @exit: called after every test case.
* @test_cases: a null terminated array of test cases.
*
* A kunit_suite is a collection of related &struct kunit_case s, such that
* @init is called before every test case and @exit is called after every
* test case, similar to the notion of a *test fixture* or a *test class*
* in other unit testing frameworks like JUnit or Googletest.
*
* Every &struct kunit_case must be associated with a kunit_suite for KUnit
* to run it.
*/
struct kunit_suite {
const char name[256];
int (*init)(struct kunit *test);
void (*exit)(struct kunit *test);
struct kunit_case *test_cases;
};
Die überall verwendete Struktur "Kunit" ist wie folgt definiert.
/**
* struct kunit - represents a running instance of a test.
*
* @priv: for user to store arbitrary data. Commonly used to pass data
* created in the init function (see &struct kunit_suite).
*
* Used to store information about the current context under which the test
* is running. Most of this data is private and should only be accessed
* indirectly via public functions; the one exception is @priv which can be
* used by the test writer to store arbitrary data.
*/
struct kunit {
void *priv;
/* private: internal use only. */
const char *name; /* Read only after initialization! */
struct kunit_try_catch try_catch;
/*
* success starts as true, and may only be set to false during a
* test case; thus, it is safe to update this across multiple
* threads using WRITE_ONCE; however, as a consequence, it may only
* be read after the test case finishes once all threads associated
* with the test case have terminated.
*/
bool success; /* Read only after test_case finishes! */
spinlock_t lock; /* Guards all mutable test state. */
/*
* Because resources is a list that may be updated multiple times (with
* new resources) from any thread associated with a test case, we must
* protect it with some type of lock.
*/
struct list_head resources; /* Protected by lock. */
};
Details werden separat zusammengefasst.
https://github.com/torvalds/linux https://kunit.dev/usage/index.html https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/testing/kunit?h=v5.5 https://kunit.dev/third_party/kernel/docs/ https://lwn.net/Articles/780985/
Recommended Posts