Appearance
Linux驱动测试框架
随着Linux内核的不断发展,驱动程序的复杂性也在不断增加。为了确保驱动程序的质量和稳定性,Linux内核社区引入了多种测试框架。本章将详细介绍Linux内核中的各种测试方法和框架。
测试框架概述
测试的重要性
驱动程序直接与硬件交互,其稳定性和正确性对整个系统至关重要。通过建立完善的测试体系,可以:
- 提高代码质量,减少bug
- 加快开发周期,及早发现问题
- 增强代码可维护性
- 提供文档化的行为说明
- 支持回归测试,防止引入新问题
Linux内核测试框架
Linux内核提供了多种测试框架,每种框架都有其特定的用途:
- KUnit - 内核单元测试框架
- kselftest - 用户空间内核测试框架
- KernelCI - 持续集成测试平台
- ktest - 内核测试自动化工具
- syzkaller - 系统调用模糊测试工具
KUnit单元测试框架
KUnit是Linux内核官方的单元测试框架,专门用于编写和运行内核模块的单元测试。
KUnit基本概念
测试用例(Test Case)
测试用例是测试的基本单元,用于验证特定功能的正确性:
c
// 简单的测试用例示例
static void example_test_case(struct kunit *test)
{
int a = 1;
int b = 2;
KUNIT_EXPECT_EQ(test, a + b, 3);
}测试套件(Test Suite)
测试套件是一组相关测试用例的集合:
c
// 测试套件定义
static struct kunit_case example_test_cases[] = {
KUNIT_CASE(example_test_case),
KUNIT_CASE(another_test_case),
{}
};
static struct kunit_suite example_test_suite = {
.name = "example",
.test_cases = example_test_cases,
};
kunit_test_suite(example_test_suite);测试模块
测试模块是可加载的内核模块,包含一个或多个测试套件:
c
// 测试模块示例
static int __init example_test_init(void)
{
return 0;
}
static void __exit example_test_exit(void)
{
}
module_init(example_test_init);
module_exit(example_test_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Example KUnit test module");KUnit API详解
断言和期望
KUnit提供了丰富的断言和期望宏来验证测试结果:
c
// 相等性检查
KUNIT_EXPECT_EQ(test, actual, expected);
KUNIT_ASSERT_EQ(test, actual, expected);
// 布尔值检查
KUNIT_EXPECT_TRUE(test, condition);
KUNIT_EXPECT_FALSE(test, condition);
// 指针检查
KUNIT_EXPECT_PTR_EQ(test, ptr1, ptr2);
KUNIT_EXPECT_NULL(test, ptr);
KUNIT_EXPECT_NOT_NULL(test, ptr);
// 字符串检查
KUNIT_EXPECT_STREQ(test, str1, str2);
// 内存检查
KUNIT_EXPECT_MEMEQ(test, addr1, addr2, size);测试夹具(Test Fixtures)
测试夹具用于在测试用例执行前后进行初始化和清理工作:
c
// 测试夹具结构体
struct example_fixture {
struct device *dev;
void *buffer;
size_t buffer_size;
};
// 夹具初始化函数
static int example_test_init(struct kunit *test)
{
struct example_fixture *fixture;
fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture);
fixture->buffer_size = 1024;
fixture->buffer = kunit_kmalloc(test, fixture->buffer_size, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->buffer);
test->priv = fixture;
return 0;
}
// 测试用例使用夹具
static void example_test_with_fixture(struct kunit *test)
{
struct example_fixture *fixture = test->priv;
// 使用fixture中的资源进行测试
memset(fixture->buffer, 0, fixture->buffer_size);
KUNIT_EXPECT_EQ(test, ((char *)fixture->buffer)[0], 0);
}
// 测试套件定义
static struct kunit_case example_test_cases[] = {
KUNIT_CASE(example_test_with_fixture),
{}
};
static struct kunit_suite example_test_suite = {
.name = "example_with_fixture",
.init = example_test_init,
.test_cases = example_test_cases,
};
kunit_test_suite(example_test_suite);KUnit使用示例
字符设备驱动测试
c
#include <kunit/test.h>
#include "my_char_driver.h"
// 测试夹具
struct char_driver_fixture {
struct my_char_device *dev;
struct device *device;
};
static int char_driver_test_init(struct kunit *test)
{
struct char_driver_fixture *fixture;
fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture);
// 初始化设备
fixture->dev = my_char_device_create();
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->dev);
test->priv = fixture;
return 0;
}
static void char_driver_test_init(struct kunit *test)
{
struct char_driver_fixture *fixture = test->priv;
int ret;
ret = my_char_device_init(fixture->dev);
KUNIT_EXPECT_EQ(test, ret, 0);
}
static void char_driver_test_read(struct kunit *test)
{
struct char_driver_fixture *fixture = test->priv;
char buffer[32];
ssize_t ret;
// 写入测试数据
ret = my_char_device_write(fixture->dev, "Hello World", 11);
KUNIT_EXPECT_EQ(test, ret, 11);
// 读取数据
ret = my_char_device_read(fixture->dev, buffer, sizeof(buffer));
KUNIT_EXPECT_EQ(test, ret, 11);
KUNIT_EXPECT_MEMEQ(test, buffer, "Hello World", 11);
}
static void char_driver_test_ioctl(struct kunit *test)
{
struct char_driver_fixture *fixture = test->priv;
int value = 42;
int ret;
// 设置ioctl参数
ret = my_char_device_ioctl(fixture->dev, MY_IOCTL_SET_VALUE, &value);
KUNIT_EXPECT_EQ(test, ret, 0);
// 获取ioctl参数
value = 0;
ret = my_char_device_ioctl(fixture->dev, MY_IOCTL_GET_VALUE, &value);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, value, 42);
}
static struct kunit_case char_driver_test_cases[] = {
KUNIT_CASE(char_driver_test_init),
KUNIT_CASE(char_driver_test_read),
KUNIT_CASE(char_driver_test_ioctl),
{}
};
static struct kunit_suite char_driver_test_suite = {
.name = "my_char_driver",
.init = char_driver_test_init,
.exit = char_driver_test_exit,
.test_cases = char_driver_test_cases,
};
kunit_test_suite(char_driver_test_suite);网络驱动测试
c
#include <kunit/test.h>
#include "my_net_driver.h"
// 网络驱动测试夹具
struct net_driver_fixture {
struct my_net_device *net_dev;
struct sk_buff *test_skb;
};
static int net_driver_test_init(struct kunit *test)
{
struct net_driver_fixture *fixture;
fixture = kunit_kzalloc(test, sizeof(*fixture), GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture);
// 创建网络设备
fixture->net_dev = my_net_device_create();
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->net_dev);
// 创建测试数据包
fixture->test_skb = alloc_skb(128, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fixture->test_skb);
test->priv = fixture;
return 0;
}
static void net_driver_test_tx(struct kunit *test)
{
struct net_driver_fixture *fixture = test->priv;
int ret;
// 填充测试数据
skb_put(fixture->test_skb, 64);
memset(fixture->test_skb->data, 0xAA, 64);
// 发送数据包
ret = my_net_device_start_xmit(fixture->test_skb,
&fixture->net_dev->netdev);
KUNIT_EXPECT_EQ(test, ret, NETDEV_TX_OK);
}
static void net_driver_test_rx(struct kunit *test)
{
struct net_driver_fixture *fixture = test->priv;
struct sk_buff *skb;
// 模拟接收数据包
skb = alloc_skb(128, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, skb);
skb_put(skb, 64);
memset(skb->data, 0x55, 64);
// 处理接收数据包
my_net_device_receive(fixture->net_dev, skb);
// 验证处理结果
KUNIT_EXPECT_EQ(test, fixture->net_dev->stats.rx_packets, 1);
KUNIT_EXPECT_EQ(test, fixture->net_dev->stats.rx_bytes, 64);
}
static struct kunit_case net_driver_test_cases[] = {
KUNIT_CASE(net_driver_test_tx),
KUNIT_CASE(net_driver_test_rx),
{}
};
static struct kunit_suite net_driver_test_suite = {
.name = "my_net_driver",
.init = net_driver_test_init,
.exit = net_driver_test_exit,
.test_cases = net_driver_test_cases,
};
kunit_test_suite(net_driver_test_suite);kselftest框架
kselftest是用户空间的内核测试框架,用于测试系统调用、内核接口和子系统功能。
kselftest基本结构
kselftest测试通常位于tools/testing/selftests/目录下:
makefile
# Makefile示例
CFLAGS += -I../../../../include/uapi
CFLAGS += -I../../../../include
TEST_GEN_PROGS := test_example
TEST_PROGS := run_tests.sh
include ../lib.mkkselftest编写示例
c
// test_example.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include "../kselftest.h"
#define DEVICE_PATH "/dev/my_device"
static int test_device_open(void)
{
int fd;
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
ksft_test_result_fail("Failed to open device: %s\n",
strerror(errno));
return -1;
}
ksft_test_result_pass("Device opened successfully\n");
close(fd);
return 0;
}
static int test_device_read_write(void)
{
int fd;
char buffer[32];
ssize_t ret;
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
ksft_test_result_fail("Failed to open device: %s\n",
strerror(errno));
return -1;
}
// 写入数据
ret = write(fd, "Hello World", 11);
if (ret != 11) {
ksft_test_result_fail("Write failed: %s\n", strerror(errno));
close(fd);
return -1;
}
// 读取数据
ret = read(fd, buffer, sizeof(buffer));
if (ret != 11) {
ksft_test_result_fail("Read failed: %s\n", strerror(errno));
close(fd);
return -1;
}
if (memcmp(buffer, "Hello World", 11) != 0) {
ksft_test_result_fail("Data mismatch\n");
close(fd);
return -1;
}
ksft_test_result_pass("Read/write test passed\n");
close(fd);
return 0;
}
static int test_device_ioctl(void)
{
int fd;
int value = 42;
int ret;
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
ksft_test_result_fail("Failed to open device: %s\n",
strerror(errno));
return -1;
}
// 设置ioctl参数
ret = ioctl(fd, MY_IOCTL_SET_VALUE, &value);
if (ret < 0) {
ksft_test_result_fail("IOCTL set failed: %s\n", strerror(errno));
close(fd);
return -1;
}
// 获取ioctl参数
value = 0;
ret = ioctl(fd, MY_IOCTL_GET_VALUE, &value);
if (ret < 0) {
ksft_test_result_fail("IOCTL get failed: %s\n", strerror(errno));
close(fd);
return -1;
}
if (value != 42) {
ksft_test_result_fail("IOCTL value mismatch: expected 42, got %d\n",
value);
close(fd);
return -1;
}
ksft_test_result_pass("IOCTL test passed\n");
close(fd);
return 0;
}
static struct test_case {
const char *name;
int (*func)(void);
} test_cases[] = {
{ "device_open", test_device_open },
{ "device_read_write", test_device_read_write },
{ "device_ioctl", test_device_ioctl },
{ NULL, NULL }
};
int main(int argc, char **argv)
{
int i, ret;
int passed = 0, failed = 0;
ksft_print_header();
ksft_set_plan(3);
for (i = 0; test_cases[i].name; i++) {
ret = test_cases[i].func();
if (ret == 0)
passed++;
else
failed++;
}
if (failed == 0)
ksft_exit_pass();
else
ksft_exit_fail();
return 0;
}模块测试
模块加载测试
c
// module_test.c
#include <kunit/test.h>
#include <linux/module.h>
// 测试模块是否能正确加载和卸载
static void module_load_test(struct kunit *test)
{
struct module *mod;
// 尝试加载模块
mod = find_module("my_module");
KUNIT_EXPECT_NOT_NULL(test, mod);
if (mod) {
KUNIT_EXPECT_EQ(test, mod->state, MODULE_STATE_LIVE);
}
}
// 测试模块参数
static void module_param_test(struct kunit *test)
{
extern int my_module_param;
// 验证模块参数的默认值
KUNIT_EXPECT_EQ(test, my_module_param, 42);
// 修改参数并验证
my_module_param = 100;
KUNIT_EXPECT_EQ(test, my_module_param, 100);
}
static struct kunit_case module_test_cases[] = {
KUNIT_CASE(module_load_test),
KUNIT_CASE(module_param_test),
{}
};
static struct kunit_suite module_test_suite = {
.name = "module_test",
.test_cases = module_test_cases,
};
kunit_test_suite(module_test_suite);设备节点测试
c
// device_node_test.c
#include <kunit/test.h>
#include <linux/device.h>
#include <linux/fs.h>
static void device_node_creation_test(struct kunit *test)
{
struct device *dev;
struct device_node *np;
// 查找设备节点
dev = device_find_child(&platform_bus, match_function, NULL);
KUNIT_EXPECT_NOT_NULL(test, dev);
if (dev) {
np = dev->of_node;
KUNIT_EXPECT_NOT_NULL(test, np);
if (np) {
// 验证设备树属性
const char *compatible;
int ret;
ret = of_property_read_string(np, "compatible", &compatible);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_STREQ(test, compatible, "myvendor,mydevice");
}
put_device(dev);
}
}
static struct kunit_case device_node_test_cases[] = {
KUNIT_CASE(device_node_creation_test),
{}
};
static struct kunit_suite device_node_test_suite = {
.name = "device_node_test",
.test_cases = device_node_test_cases,
};
kunit_test_suite(device_node_test_suite);集成测试
系统级测试
c
// system_integration_test.c
#include <kunit/test.h>
#include <linux/completion.h>
#include <linux/workqueue.h>
struct integration_test_data {
struct completion work_done;
int result;
struct work_struct work;
};
static void test_work_function(struct work_struct *work)
{
struct integration_test_data *data =
container_of(work, struct integration_test_data, work);
// 模拟一些工作
msleep(100);
data->result = 42;
complete(&data->work_done);
}
static void system_integration_test(struct kunit *test)
{
struct integration_test_data data;
long ret;
init_completion(&data.work_done);
INIT_WORK(&data.work, test_work_function);
// 调度工作
schedule_work(&data.work);
// 等待工作完成
ret = wait_for_completion_timeout(&data.work_done, HZ);
KUNIT_EXPECT_NE(test, ret, 0);
KUNIT_EXPECT_EQ(test, data.result, 42);
// 清理工作队列
flush_scheduled_work();
}
static struct kunit_case integration_test_cases[] = {
KUNIT_CASE(system_integration_test),
{}
};
static struct kunit_suite integration_test_suite = {
.name = "integration_test",
.test_cases = integration_test_cases,
};
kunit_test_suite(integration_test_suite);测试最佳实践
测试设计原则
- 独立性:每个测试用例应该是独立的,不依赖于其他测试用例的执行结果
- 可重复性:测试应该在相同条件下产生相同结果
- 自动化:测试应该能够自动运行,无需人工干预
- 快速性:测试应该尽可能快速执行
- 可读性:测试代码应该清晰易懂
测试覆盖率
c
// coverage_example.c
#include <kunit/test.h>
// 被测试的函数
int divide(int a, int b)
{
if (b == 0)
return -1;
return a / b;
}
// 测试正常情况
static void divide_normal_test(struct kunit *test)
{
int result = divide(10, 2);
KUNIT_EXPECT_EQ(test, result, 5);
}
// 测试边界情况
static void divide_zero_test(struct kunit *test)
{
int result = divide(10, 0);
KUNIT_EXPECT_EQ(test, result, -1);
}
// 测试负数情况
static void divide_negative_test(struct kunit *test)
{
int result = divide(-10, 2);
KUNIT_EXPECT_EQ(test, result, -5);
result = divide(10, -2);
KUNIT_EXPECT_EQ(test, result, -5);
result = divide(-10, -2);
KUNIT_EXPECT_EQ(test, result, 5);
}
static struct kunit_case divide_test_cases[] = {
KUNIT_CASE(divide_normal_test),
KUNIT_CASE(divide_zero_test),
KUNIT_CASE(divide_negative_test),
{}
};
static struct kunit_suite divide_test_suite = {
.name = "divide_test",
.test_cases = divide_test_cases,
};
kunit_test_suite(divide_test_suite);测试调试技巧
c
// debug_test.c
#include <kunit/test.h>
static void debug_example_test(struct kunit *test)
{
int a = 10;
int b = 20;
int result;
KUNIT_EXPECT_EQ(test, a, 10);
KUNIT_EXPECT_EQ(test, b, 20);
result = a + b;
KUNIT_EXPECT_EQ(test, result, 30);
// 使用KUNIT_LOG输出调试信息
kunit_log(KERN_INFO, test, "a=%d, b=%d, result=%d\n", a, b, result);
}通过建立完善的测试体系,可以大大提高驱动程序的质量和可靠性。在实际开发中,应该根据驱动的特点选择合适的测试框架和方法,并持续完善测试用例以覆盖更多的场景。