Skip to content

Linux驱动测试框架

随着Linux内核的不断发展,驱动程序的复杂性也在不断增加。为了确保驱动程序的质量和稳定性,Linux内核社区引入了多种测试框架。本章将详细介绍Linux内核中的各种测试方法和框架。

测试框架概述

测试的重要性

驱动程序直接与硬件交互,其稳定性和正确性对整个系统至关重要。通过建立完善的测试体系,可以:

  1. 提高代码质量,减少bug
  2. 加快开发周期,及早发现问题
  3. 增强代码可维护性
  4. 提供文档化的行为说明
  5. 支持回归测试,防止引入新问题

Linux内核测试框架

Linux内核提供了多种测试框架,每种框架都有其特定的用途:

  1. KUnit - 内核单元测试框架
  2. kselftest - 用户空间内核测试框架
  3. KernelCI - 持续集成测试平台
  4. ktest - 内核测试自动化工具
  5. 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.mk

kselftest编写示例

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);

测试最佳实践

测试设计原则

  1. 独立性:每个测试用例应该是独立的,不依赖于其他测试用例的执行结果
  2. 可重复性:测试应该在相同条件下产生相同结果
  3. 自动化:测试应该能够自动运行,无需人工干预
  4. 快速性:测试应该尽可能快速执行
  5. 可读性:测试代码应该清晰易懂

测试覆盖率

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);
}

通过建立完善的测试体系,可以大大提高驱动程序的质量和可靠性。在实际开发中,应该根据驱动的特点选择合适的测试框架和方法,并持续完善测试用例以覆盖更多的场景。