编写测试¶
测试用例¶
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_EXPECT
和 KUNIT_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_foo
、example_test_bar
和 example_test_baz
。每个测试用例都将在其运行之前立即调用 example_test_init
,并在其运行之后立即调用 example_test_exit
。最后,在所有其他操作之后将调用 example_suite_exit
。kunit_test_suite(example_test_suite)
将测试套件注册到 KUnit 测试框架。
注意
即使 init
或 suite_init
失败,exit
和 suite_exit
函数也会运行。确保它们可以处理由于 init
或 suite_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 kunit
的 priv
成员作为一种将数据从 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 device
或 struct 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.
}