编写测试

测试用例

KUnit 中的基本单元是测试用例。测试用例是一个具有 void (*)(struct kunit *test) 签名的函数。它调用被测函数,然后为应该发生的事情设置期望。例如

void example_test_success(struct kunit *test)
{
}

void example_test_failure(struct kunit *test)
{
        KUNIT_FAIL(test, "This test never passes.");
}

在上面的示例中,example_test_success 总是通过,因为它什么也不做;没有设置任何期望,因此所有期望都通过了。另一方面,example_test_failure 总是失败,因为它调用了 KUNIT_FAIL,这是一个特殊的期望,它会记录一条消息并导致测试用例失败。

期望

期望指定我们期望一段代码在测试中做某事。期望像函数一样被调用。测试是通过设置关于被测代码行为的期望来完成的。当一个或多个期望失败时,测试用例失败,并记录关于失败的信息。例如

void add_test_basic(struct kunit *test)
{
        KUNIT_EXPECT_EQ(test, 1, add(1, 0));
        KUNIT_EXPECT_EQ(test, 2, add(1, 1));
}

在上面的示例中,add_test_basic 对名为 add 的函数的行为进行了一些断言。第一个参数始终是 struct kunit * 类型,其中包含关于当前测试上下文的信息。在这种情况下,第二个参数是期望的值。最后一个值是实际值。如果 add 通过所有这些期望,则测试用例 add_test_basic 将通过;如果其中任何一个期望失败,则测试用例将失败。

当任何期望被违反时,测试用例失败;但是,测试将继续运行,并尝试其他期望,直到测试用例结束或以其他方式终止。这与稍后讨论的断言相反。

要了解更多关于 KUnit 期望的信息,请参阅 测试 API

注意

单个测试用例应该简短、易于理解,并且专注于单个行为。

例如,如果我们想严格测试上面的 add 函数,请创建额外的测试用例,这些用例将测试 add 函数应该具有的每个属性,如下所示

void add_test_basic(struct kunit *test)
{
        KUNIT_EXPECT_EQ(test, 1, add(1, 0));
        KUNIT_EXPECT_EQ(test, 2, add(1, 1));
}

void add_test_negative(struct kunit *test)
{
        KUNIT_EXPECT_EQ(test, 0, add(-1, 1));
}

void add_test_max(struct kunit *test)
{
        KUNIT_EXPECT_EQ(test, INT_MAX, add(0, INT_MAX));
        KUNIT_EXPECT_EQ(test, -1, add(INT_MAX, INT_MIN));
}

void add_test_overflow(struct kunit *test)
{
        KUNIT_EXPECT_EQ(test, INT_MIN, add(INT_MAX, 1));
}

断言

断言类似于期望,不同之处在于,如果条件不满足,则断言会立即终止测试用例。例如

static void test_sort(struct kunit *test)
{
        int *a, i, r = 1;
        a = kunit_kmalloc_array(test, TEST_LEN, sizeof(*a), GFP_KERNEL);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, a);
        for (i = 0; i < TEST_LEN; i++) {
                r = (r * 725861) % 6599;
                a[i] = r;
        }
        sort(a, TEST_LEN, sizeof(*a), cmpint, NULL);
        for (i = 0; i < TEST_LEN-1; i++)
                KUNIT_EXPECT_LE(test, a[i], a[i + 1]);
}

在这个例子中,我们需要能够分配一个数组来测试 sort() 函数。因此,如果存在分配错误,我们使用 KUNIT_ASSERT_NOT_ERR_OR_NULL() 来中止测试。

注意

在其他测试框架中,ASSERT 宏通常通过调用 return 来实现,因此它们只能从测试函数中使用。在 KUnit 中,我们会在失败时停止当前的 kthread,因此您可以从任何地方调用它们。

注意

警告:上述规则有一个例外。您不应该在套件的 exit() 函数或资源的 free 函数中使用断言。这些会在测试关闭时运行,此处的断言会阻止进一步的清理代码运行,从而可能导致内存泄漏。

自定义错误消息

每个 KUNIT_EXPECTKUNIT_ASSERT 宏都有一个 _MSG 变体。这些变体采用格式字符串和参数,为自动生成的错误消息提供额外的上下文。

char some_str[41];
generate_sha1_hex_string(some_str);

/* Before. Not easy to tell why the test failed. */
KUNIT_EXPECT_EQ(test, strlen(some_str), 40);

/* After. Now we see the offending string. */
KUNIT_EXPECT_EQ_MSG(test, strlen(some_str), 40, "some_str='%s'", some_str);

或者,可以使用 KUNIT_FAIL() 完全控制错误消息,例如

/* Before */
KUNIT_EXPECT_EQ(test, some_setup_function(), 0);

/* After: full control over the failure message. */
if (some_setup_function())
        KUNIT_FAIL(test, "Failed to setup thing for testing");

测试套件

我们需要许多测试用例来覆盖所有单元的行为。拥有许多类似的测试是很常见的。为了减少这些密切相关测试中的重复,大多数单元测试框架(包括 KUnit)都提供了测试套件的概念。测试套件是一个代码单元的测试用例集合,其中包含可选的设置和拆卸函数,这些函数在整个套件和/或每个测试用例之前/之后运行。

注意

测试用例只有在与测试套件关联时才会运行。

例如

static struct kunit_case example_test_cases[] = {
        KUNIT_CASE(example_test_foo),
        KUNIT_CASE(example_test_bar),
        KUNIT_CASE(example_test_baz),
        {}
};

static struct kunit_suite example_test_suite = {
        .name = "example",
        .init = example_test_init,
        .exit = example_test_exit,
        .suite_init = example_suite_init,
        .suite_exit = example_suite_exit,
        .test_cases = example_test_cases,
};
kunit_test_suite(example_test_suite);

在上面的示例中,测试套件 example_test_suite 将首先运行 example_suite_init,然后运行测试用例 example_test_fooexample_test_barexample_test_baz。每个测试用例都将在其运行之前立即调用 example_test_init,并在其运行之后立即调用 example_test_exit。最后,在所有其他操作之后将调用 example_suite_exitkunit_test_suite(example_test_suite) 将测试套件注册到 KUnit 测试框架。

注意

即使 initsuite_init 失败,exitsuite_exit 函数也会运行。确保它们可以处理由于 initsuite_init 遇到错误或提前退出而可能导致的任何不一致状态。

kunit_test_suite(...) 是一个宏,它告诉链接器将指定的测试套件放在一个特殊的链接器段中,以便 KUnit 可以在 late_init 之后或者在加载测试模块时运行(如果测试是作为模块构建的)。

有关更多信息,请参阅 测试 API

为其他架构编写测试

最好编写在 UML 上运行的测试,而不是仅在特定架构下运行的测试。最好编写在 QEMU 或其他易于获取(且免费)的软件环境中运行的测试,而不是在特定的硬件上运行的测试。

尽管如此,仍然有充分的理由编写特定于架构或硬件的测试。例如,我们可能想测试真正属于 arch/some-arch/* 中的代码。即便如此,也要尽量编写不依赖于物理硬件的测试。我们的一些测试用例可能不需要硬件,只有少数测试实际上需要硬件才能进行测试。当硬件不可用时,我们可以跳过测试,而不是禁用测试。

既然我们已经缩小了硬件特定的确切范围,那么编写和运行测试的实际过程与编写普通的 KUnit 测试相同。

重要

我们可能需要重置硬件状态。如果不可能,我们可能每次调用只能运行一个测试用例。

常见模式

隔离行为

单元测试将受测代码量限制为单个单元。它控制当被测单元调用函数时运行的代码。如果一个函数作为 API 的一部分公开,以便可以在不影响其余代码库的情况下更改该函数的定义。在内核中,这来自两个构造:类,它们是包含由实现者提供的函数指针的结构,以及特定于架构的函数,这些函数在编译时选择定义。

类并不是 C 编程语言内置的结构,但它是一个很容易推导的概念。因此,在大多数情况下,每个不使用标准化面向对象库(如 GNOME 的 GObject)的项目都有自己略有不同的面向对象编程方式;Linux 内核也不例外。

内核面向对象编程的核心概念是类。在内核中,是一个包含函数指针的结构体。这在实现者用户之间创建了一个契约,因为它强制他们使用相同的函数签名,而无需直接调用该函数。要成为一个类,函数指针必须指定指向该类的指针(称为类句柄)作为参数之一。因此,成员函数(也称为方法)可以访问成员变量(也称为字段),从而允许同一个实现拥有多个实例

类可以通过在子类中嵌入父类来被子类覆盖。然后,当调用子类方法时,子类实现知道传递给它的指针是包含在子类中的父类的指针。因此,子类可以计算指向自身的指针,因为指向父类的指针始终是相对于指向子类的指针的固定偏移量。此偏移量是包含在子类结构体中的父类的偏移量。例如

struct shape {
        int (*area)(struct shape *this);
};

struct rectangle {
        struct shape parent;
        int length;
        int width;
};

int rectangle_area(struct shape *this)
{
        struct rectangle *self = container_of(this, struct rectangle, parent);

        return self->length * self->width;
};

void rectangle_new(struct rectangle *self, int length, int width)
{
        self->parent.area = rectangle_area;
        self->length = length;
        self->width = width;
}

在此示例中,通过 container_of 从指向父类的指针计算指向子类的指针。

伪造类

为了对调用类中方法的代码片段进行单元测试,必须能够控制该方法的行为,否则该测试将不再是单元测试,而成为集成测试。

一个伪造类实现了一段与生产实例中运行的代码不同的代码,但从调用者的角度来看行为相同。这样做是为了替换难以处理或速度较慢的依赖项。例如,实现一个伪造的 EEPROM,将“内容”存储在内部缓冲区中。假设我们有一个代表 EEPROM 的类

struct eeprom {
        ssize_t (*read)(struct eeprom *this, size_t offset, char *buffer, size_t count);
        ssize_t (*write)(struct eeprom *this, size_t offset, const char *buffer, size_t count);
};

并且我们想测试缓冲写入 EEPROM 的代码

struct eeprom_buffer {
        ssize_t (*write)(struct eeprom_buffer *this, const char *buffer, size_t count);
        int flush(struct eeprom_buffer *this);
        size_t flush_count; /* Flushes when buffer exceeds flush_count. */
};

struct eeprom_buffer *new_eeprom_buffer(struct eeprom *eeprom);
void destroy_eeprom_buffer(struct eeprom *eeprom);

我们可以通过伪造底层的 EEPROM 来测试这段代码

struct fake_eeprom {
        struct eeprom parent;
        char contents[FAKE_EEPROM_CONTENTS_SIZE];
};

ssize_t fake_eeprom_read(struct eeprom *parent, size_t offset, char *buffer, size_t count)
{
        struct fake_eeprom *this = container_of(parent, struct fake_eeprom, parent);

        count = min(count, FAKE_EEPROM_CONTENTS_SIZE - offset);
        memcpy(buffer, this->contents + offset, count);

        return count;
}

ssize_t fake_eeprom_write(struct eeprom *parent, size_t offset, const char *buffer, size_t count)
{
        struct fake_eeprom *this = container_of(parent, struct fake_eeprom, parent);

        count = min(count, FAKE_EEPROM_CONTENTS_SIZE - offset);
        memcpy(this->contents + offset, buffer, count);

        return count;
}

void fake_eeprom_init(struct fake_eeprom *this)
{
        this->parent.read = fake_eeprom_read;
        this->parent.write = fake_eeprom_write;
        memset(this->contents, 0, FAKE_EEPROM_CONTENTS_SIZE);
}

我们现在可以使用它来测试 struct eeprom_buffer

struct eeprom_buffer_test {
        struct fake_eeprom *fake_eeprom;
        struct eeprom_buffer *eeprom_buffer;
};

static void eeprom_buffer_test_does_not_write_until_flush(struct kunit *test)
{
        struct eeprom_buffer_test *ctx = test->priv;
        struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer;
        struct fake_eeprom *fake_eeprom = ctx->fake_eeprom;
        char buffer[] = {0xff};

        eeprom_buffer->flush_count = SIZE_MAX;

        eeprom_buffer->write(eeprom_buffer, buffer, 1);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0);

        eeprom_buffer->write(eeprom_buffer, buffer, 1);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0);

        eeprom_buffer->flush(eeprom_buffer);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff);
}

static void eeprom_buffer_test_flushes_after_flush_count_met(struct kunit *test)
{
        struct eeprom_buffer_test *ctx = test->priv;
        struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer;
        struct fake_eeprom *fake_eeprom = ctx->fake_eeprom;
        char buffer[] = {0xff};

        eeprom_buffer->flush_count = 2;

        eeprom_buffer->write(eeprom_buffer, buffer, 1);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0);

        eeprom_buffer->write(eeprom_buffer, buffer, 1);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff);
}

static void eeprom_buffer_test_flushes_increments_of_flush_count(struct kunit *test)
{
        struct eeprom_buffer_test *ctx = test->priv;
        struct eeprom_buffer *eeprom_buffer = ctx->eeprom_buffer;
        struct fake_eeprom *fake_eeprom = ctx->fake_eeprom;
        char buffer[] = {0xff, 0xff};

        eeprom_buffer->flush_count = 2;

        eeprom_buffer->write(eeprom_buffer, buffer, 1);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0);

        eeprom_buffer->write(eeprom_buffer, buffer, 2);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[0], 0xff);
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[1], 0xff);
        /* Should have only flushed the first two bytes. */
        KUNIT_EXPECT_EQ(test, fake_eeprom->contents[2], 0);
}

static int eeprom_buffer_test_init(struct kunit *test)
{
        struct eeprom_buffer_test *ctx;

        ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx);

        ctx->fake_eeprom = kunit_kzalloc(test, sizeof(*ctx->fake_eeprom), GFP_KERNEL);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->fake_eeprom);
        fake_eeprom_init(ctx->fake_eeprom);

        ctx->eeprom_buffer = new_eeprom_buffer(&ctx->fake_eeprom->parent);
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ctx->eeprom_buffer);

        test->priv = ctx;

        return 0;
}

static void eeprom_buffer_test_exit(struct kunit *test)
{
        struct eeprom_buffer_test *ctx = test->priv;

        destroy_eeprom_buffer(ctx->eeprom_buffer);
}

针对多个输入进行测试

仅测试少量输入不足以确保代码正常工作,例如:测试哈希函数。

我们可以编写一个辅助宏或函数。该函数针对每个输入调用。例如,要测试 sha1sum(1),我们可以编写

#define TEST_SHA1(in, want) \
        sha1sum(in, out); \
        KUNIT_EXPECT_STREQ_MSG(test, out, want, "sha1sum(%s)", in);

char out[40];
TEST_SHA1("hello world",  "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed");
TEST_SHA1("hello world!", "430ce34d020724ed75a196dfc2ad67c77772d169");

请注意使用 KUNIT_EXPECT_STREQ_MSG 版本来打印更详细的错误,并使辅助宏中的断言更清晰。

当多次调用相同的期望(在循环或辅助函数中)时,_MSG 变体非常有用,因此行号不足以识别失败的原因,如下所示。

在复杂的情况下,我们建议使用表驱动测试,而不是辅助宏变体,例如

int i;
char out[40];

struct sha1_test_case {
        const char *str;
        const char *sha1;
};

struct sha1_test_case cases[] = {
        {
                .str = "hello world",
                .sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
        },
        {
                .str = "hello world!",
                .sha1 = "430ce34d020724ed75a196dfc2ad67c77772d169",
        },
};
for (i = 0; i < ARRAY_SIZE(cases); ++i) {
        sha1sum(cases[i].str, out);
        KUNIT_EXPECT_STREQ_MSG(test, out, cases[i].sha1,
                              "sha1sum(%s)", cases[i].str);
}

涉及更多的样板代码,但当存在多个输入/输出(由于字段名称)时,它可以

  • 更具可读性。

    • 例如,请参阅 fs/ext4/inode-test.c

  • 如果测试用例在多个测试之间共享,则减少重复。

    • 例如:如果我们想测试 sha256sum,我们可以添加一个 sha256 字段并重用 cases

  • 转换为“参数化测试”。

参数化测试

表驱动测试模式非常常见,以至于 KUnit 对其有特殊的支持。

通过重用上面的相同 cases 数组,我们可以使用以下内容将测试编写为“参数化测试”。

// This is copy-pasted from above.
struct sha1_test_case {
        const char *str;
        const char *sha1;
};
const struct sha1_test_case cases[] = {
        {
                .str = "hello world",
                .sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
        },
        {
                .str = "hello world!",
                .sha1 = "430ce34d020724ed75a196dfc2ad67c77772d169",
        },
};

// Creates `sha1_gen_params()` to iterate over `cases` while using
// the struct member `str` for the case description.
KUNIT_ARRAY_PARAM_DESC(sha1, cases, str);

// Looks no different from a normal test.
static void sha1_test(struct kunit *test)
{
        // This function can just contain the body of the for-loop.
        // The former `cases[i]` is accessible under test->param_value.
        char out[40];
        struct sha1_test_case *test_param = (struct sha1_test_case *)(test->param_value);

        sha1sum(test_param->str, out);
        KUNIT_EXPECT_STREQ_MSG(test, out, test_param->sha1,
                              "sha1sum(%s)", test_param->str);
}

// Instead of KUNIT_CASE, we use KUNIT_CASE_PARAM and pass in the
// function declared by KUNIT_ARRAY_PARAM or KUNIT_ARRAY_PARAM_DESC.
static struct kunit_case sha1_test_cases[] = {
        KUNIT_CASE_PARAM(sha1_test, sha1_gen_params),
        {}
};

分配内存

在您可能使用 kzalloc 的地方,您可以改用 kunit_kzalloc,因为 KUnit 将确保在测试完成后释放内存。

这很有用,因为它允许我们使用 KUNIT_ASSERT_EQ 宏从测试中提前退出,而无需担心记住调用 kfree。例如

void example_test_allocation(struct kunit *test)
{
        char *buffer = kunit_kzalloc(test, 16, GFP_KERNEL);
        /* Ensure allocation succeeded. */
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buffer);

        KUNIT_ASSERT_STREQ(test, buffer, "");
}

注册清理操作

如果您需要执行超出简单使用 kunit_kzalloc 的清理操作,您可以注册一个自定义的“延迟操作”,该操作是在测试退出时(无论是干净地退出还是通过失败的断言退出)运行的清理函数。

操作是简单的函数,没有返回值,只有一个 void* 上下文参数,并且与 Python 和 Go 测试中的“清理”函数、“支持它们的语言中的“defer”语句以及 RAII 语言中的析构函数(在某些情况下)的作用相同。

这些对于从全局列表中取消注册、关闭文件或其他资源或释放资源非常有用。

例如

static void cleanup_device(void *ctx)
{
        struct device *dev = (struct device *)ctx;

        device_unregister(dev);
}

void example_device_test(struct kunit *test)
{
        struct my_device dev;

        device_register(&dev);

        kunit_add_action(test, &cleanup_device, &dev);
}

请注意,对于像 device_unregister 这样只接受单个指针大小参数的函数,可以使用 KUNIT_DEFINE_ACTION_WRAPPER() 宏自动生成一个包装器,例如

KUNIT_DEFINE_ACTION_WRAPPER(device_unregister, device_unregister_wrapper, struct device *);
kunit_add_action(test, &device_unregister_wrapper, &dev);

您应该优先这样做,而不是手动强制转换为 kunit_action_t 类型,因为强制转换函数指针会破坏控制流完整性 (CFI)。

例如,如果系统内存不足,kunit_add_action 可能会失败。您可以改用 kunit_add_action_or_reset,如果无法延迟操作,则立即运行该操作。

如果您需要更多地控制何时调用清理函数,可以使用 kunit_release_action 提前触发它,或者使用 kunit_remove_action 完全取消它。

测试静态函数

如果我们不想公开用于测试的函数或变量,一种选择是有条件地导出所使用的符号。例如

/* In my_file.c */

VISIBLE_IF_KUNIT int do_interesting_thing();
EXPORT_SYMBOL_IF_KUNIT(do_interesting_thing);

/* In my_file.h */

#if IS_ENABLED(CONFIG_KUNIT)
        int do_interesting_thing(void);
#endif

或者,您可以在 .c 文件的末尾有条件地 #include 测试文件。例如

/* In my_file.c */

static int do_interesting_thing();

#ifdef CONFIG_MY_KUNIT_TEST
#include "my_kunit_test.c"
#endif

注入仅限测试的代码

与上面所示类似,我们可以添加特定于测试的逻辑。例如

/* In my_file.h */

#ifdef CONFIG_MY_KUNIT_TEST
/* Defined in my_kunit_test.c */
void test_only_hook(void);
#else
void test_only_hook(void) { }
#endif

通过访问下一节中所示的当前 kunit_test,可以使此仅限测试的代码更有用:访问当前测试

访问当前测试

在某些情况下,我们需要从测试文件外部调用仅限测试的代码。例如,当提供函数的伪造实现,或从错误处理程序中使任何当前测试失败时,这会很有帮助。我们可以通过 task_struct 中的 kunit_test 字段来完成此操作,我们可以使用 kunit/test-bug.h 中的 kunit_get_current_test() 函数访问该字段。

即使未启用 KUnit,也可以安全地调用 kunit_get_current_test()。如果未启用 KUnit,或者当前任务中没有正在运行的测试,它将返回 NULL。这会编译为无操作或静态键检查,因此当没有测试运行时,对性能的影响可以忽略不计。

下面的示例使用它来实现函数 foo 的“模拟”实现

#include <kunit/test-bug.h> /* for kunit_get_current_test */

struct test_data {
        int foo_result;
        int want_foo_called_with;
};

static int fake_foo(int arg)
{
        struct kunit *test = kunit_get_current_test();
        struct test_data *test_data = test->priv;

        KUNIT_EXPECT_EQ(test, test_data->want_foo_called_with, arg);
        return test_data->foo_result;
}

static void example_simple_test(struct kunit *test)
{
        /* Assume priv (private, a member used to pass test data from
         * the init function) is allocated in the suite's .init */
        struct test_data *test_data = test->priv;

        test_data->foo_result = 42;
        test_data->want_foo_called_with = 1;

        /* In a real test, we'd probably pass a pointer to fake_foo somewhere
         * like an ops struct, etc. instead of calling it directly. */
        KUNIT_EXPECT_EQ(test, fake_foo(1), 42);
}

在此示例中,我们使用 struct kunitpriv 成员作为一种将数据从 init 函数传递到测试的方式。通常,priv 是可用于任何用户数据的指针。这比静态变量更可取,因为它避免了并发问题。

如果我们想要更灵活的东西,我们可以使用命名的 kunit_resource。每个测试都可以有多个资源,这些资源具有提供与 priv 成员相同灵活性的字符串名称,例如,还允许辅助函数创建资源而彼此不冲突。还可以为每个资源定义清理函数,从而轻松避免资源泄漏。有关更多信息,请参阅 资源 API

使当前测试失败

如果我们想使当前测试失败,可以使用 kunit_fail_current_test(fmt, args...),它在 <kunit/test-bug.h> 中定义,并且不需要引入 <kunit/test.h>。例如,我们可以选择在某些数据结构上启用一些额外的调试检查,如下所示

#include <kunit/test-bug.h>

#ifdef CONFIG_EXTRA_DEBUG_CHECKS
static void validate_my_data(struct data *data)
{
        if (is_valid(data))
                return;

        kunit_fail_current_test("data %p is invalid", data);

        /* Normal, non-KUnit, error reporting code here. */
}
#else
static void my_debug_function(void) { }
#endif

即使未启用 KUnit,也可以安全地调用 kunit_fail_current_test()。如果未启用 KUnit,或者当前任务中没有正在运行的测试,它将不执行任何操作。这会编译为无操作或静态键检查,因此当没有测试运行时,对性能的影响可以忽略不计。

管理伪造设备和驱动程序

在测试驱动程序或与驱动程序交互的代码时,许多函数会需要一个 struct devicestruct device_driver。在许多情况下,测试任何给定函数不需要设置真实的设备,因此可以使用虚拟设备代替。

KUnit 提供了辅助函数来创建和管理这些虚拟设备,这些设备在内部是 struct kunit_device 类型,并附加到一个特殊的 kunit_bus。这些设备支持托管设备资源(devres),如 Devres - 托管设备资源 中所述。

要创建 KUnit 管理的 struct device_driver,请使用 kunit_driver_create(),它将在 kunit_bus 上创建一个具有给定名称的驱动程序。该驱动程序会在相应的测试完成后自动销毁,但也可以使用 driver_unregister() 手动销毁。

要创建虚拟设备,请使用 kunit_device_register(),它将使用 kunit_driver_create() 创建的新 KUnit 管理的驱动程序来创建和注册设备。要提供特定的、非 KUnit 管理的驱动程序,请改用 kunit_device_register_with_driver()。与托管驱动程序一样,KUnit 管理的虚拟设备会在测试完成后自动清理,但可以使用 kunit_device_unregister() 提前手动清理。

KUnit 设备应该优先于 root_device_register() 使用,并且在设备不是平台设备的情况下,应优先于 platform_device_register() 使用。

例如

#include <kunit/device.h>

static void test_my_device(struct kunit *test)
{
        struct device *fake_device;
        const char *dev_managed_string;

        // Create a fake device.
        fake_device = kunit_device_register(test, "my_device");
        KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fake_device)

        // Pass it to functions which need a device.
        dev_managed_string = devm_kstrdup(fake_device, "Hello, World!");

        // Everything is cleaned up automatically when the test ends.
}