编写测试

测试用例

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)都提供了测试套件的概念。测试套件是一个代码单元的测试用例集合,带有可选的 setup 和 teardown 函数,这些函数在整个套件和/或每个测试用例之前/之后运行。

注意

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

例如

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_exit 将在所有其他操作之后被调用。kunit_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),
        {}
};

分配内存

您可以改为使用 kunit_kzalloc,而不是使用 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 测试中承担与“cleanup”函数相同的角色,在支持它们的语言中承担“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 完全取消它。

测试静态函数

如果您想测试静态函数,而不将这些函数暴露在测试之外,一种选择是有条件地导出符号。启用 KUnit 后,该符号将被公开,但否则仍保持静态。要使用此方法,请按照以下模板进行操作。

/* In the file containing functions to test "my_file.c" */

#include <kunit/visibility.h>
#include <my_file.h>
...
VISIBLE_IF_KUNIT int do_interesting_thing()
{
...
}
EXPORT_SYMBOL_IF_KUNIT(do_interesting_thing);

/* In the header file "my_file.h" */

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

/* In the KUnit test file "my_file_test.c" */

#include <kunit/visibility.h>
#include <my_file.h>
...
MODULE_IMPORT_NS(EXPORTED_FOR_KUNIT_TESTING);
...
// Use do_interesting_thing() in tests

有关完整示例,请参阅此 补丁,其中修改了测试,以使用上面的宏有条件地公开静态函数以进行测试。

作为上述方法的替代方法,您可以有条件地在 .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。这会编译为 no-op 或静态密钥检查,因此在未运行任何测试时,性能影响可以忽略不计。

下面的示例使用它来实现函数 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,或者当前任务中未运行任何测试,它将不执行任何操作。这会编译为 no-op 或静态密钥检查,因此在未运行任何测试时,性能影响可以忽略不计。

管理伪造设备和驱动程序

在测试驱动程序或与驱动程序交互的代码时,许多函数将需要 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.
}