🎯 学习目标

完成本章后,你将能够:

  • ✅ 从零开始创建一个新的测试文件
  • ✅ 编写包含多个子测试的完整测试
  • ✅ 将测试添加到构建系统
  • ✅ 编译并运行自己的测试
  • ✅ 理解测试的最佳实践

📝 准备工作

前置知识

确保你已经:

  • ✅ 完成环境搭建(第 3 章)
  • ✅ 理解测试框架基础(第 4 章)
  • ✅ 知道如何运行测试(第 5 章)

开发环境

# 确认 IGT 已编译
cd /path/to/igt-gpu-tools
ls -la build/

# 确认有写权限
ls -la tests/

🚀 第一步:创建测试文件

选择测试位置

根据测试类型选择目录:

tests/
├── core_*.c           # DRM 核心测试
├── kms_*.c            # KMS 显示测试
├── drm_*.c            # 通用 DRM 测试
├── prime_*.c          # PRIME 共享测试
├── i915/              # Intel 特定测试
├── amdgpu/            # AMD 特定测试
└── my_test.c          # 你的测试 ✨

创建测试文件

让我们创建一个名为 my_first_test.c 的测试:

cd tests/
touch my_first_test.c

✍️ 第二步:编写基本测试结构

最小测试模板

/*
 * Copyright © 2026 Your Name
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "igt.h"

IGT_TEST_DESCRIPTION("我的第一个 IGT 测试");

igt_simple_main
{
    igt_info("Hello, IGT!\n");
    igt_assert(true);
}

测试说明

关键元素

  1. 版权声明:必须包含(MIT 许可证)
  2. 头文件#include "igt.h" - 包含所有 IGT 功能
  3. 测试描述IGT_TEST_DESCRIPTION() - 描述测试目的
  4. 主函数igt_simple_main - 最简单的测试入口

🧪 第三步:添加子测试

使用 igt_main 和子测试

#include "igt.h"

IGT_TEST_DESCRIPTION("演示子测试的使用");

igt_main
{
    int fd;
    
    igt_fixture {
        fd = drm_open_driver(DRIVER_ANY);
        igt_require(fd >= 0);
    }
    
    igt_subtest("basic") {
        igt_info("运行基本测试\n");
        igt_assert(fd >= 0);
    }
    
    igt_subtest("query-version") {
        drmVersionPtr version;
        
        version = drmGetVersion(fd);
        igt_assert(version != NULL);
        
        igt_info("驱动: %s\n", version->name);
        igt_info("版本: %d.%d.%d\n",
                 version->version_major,
                 version->version_minor,
                 version->version_patchlevel);
        
        drmFreeVersion(version);
    }
    
    igt_fixture {
        close(fd);
    }
}

结构说明

igt_main {
    igt_fixture {
        // 所有测试前的准备
    }
    
    igt_subtest("test-1") {
        // 子测试 1
    }
    
    igt_subtest("test-2") {
        // 子测试 2
    }
    
    igt_fixture {
        // 所有测试后的清理
    }
}

📦 完整示例:DRM 设备信息测试

让我们编写一个完整的实用测试:

my_first_test.c

/*
 * Copyright © 2026 Your Name
 * SPDX-License-Identifier: MIT
 */

#include "igt.h"
#include <fcntl.h>
#include <string.h>

IGT_TEST_DESCRIPTION("测试 DRM 设备信息查询功能");

/**
 * SUBTEST: device-info
 * Description: 查询并显示 DRM 设备基本信息
 * Category: basic
 * Run type: FULL
 */

/**
 * SUBTEST: version-info
 * Description: 查询并验证驱动版本信息
 * Category: basic
 * Run type: FULL
 */

/**
 * SUBTEST: multiple-open
 * Description: 测试多次打开关闭设备
 * Category: stress
 * Run type: FULL
 */

static void print_device_info(int fd)
{
    drmVersionPtr version;
    char *busid;
    
    version = drmGetVersion(fd);
    igt_assert(version);
    
    igt_info("=== 设备信息 ===\n");
    igt_info("驱动名称: %s\n", version->name);
    igt_info("驱动描述: %s\n", version->desc);
    igt_info("驱动日期: %s\n", version->date);
    igt_info("驱动版本: %d.%d.%d\n",
             version->version_major,
             version->version_minor,
             version->version_patchlevel);
    
    busid = drmGetBusid(fd);
    if (busid) {
        igt_info("Bus ID: %s\n", busid);
        drmFreeBusid(busid);
    }
    
    drmFreeVersion(version);
}

static void verify_version(int fd)
{
    drmVersionPtr version;
    
    version = drmGetVersion(fd);
    igt_require(version);
    
    /* 验证基本字段 */
    igt_assert(version->name);
    igt_assert(strlen(version->name) > 0);
    igt_assert(version->name_len > 0);
    
    /* 验证描述 */
    igt_assert(version->desc);
    igt_assert(strlen(version->desc) > 0);
    
    /* 验证日期 */
    igt_assert(version->date);
    igt_assert(strlen(version->date) > 0);
    
    /* 版本号应该合理 */
    igt_assert(version->version_major >= 0);
    igt_assert(version->version_minor >= 0);
    igt_assert(version->version_patchlevel >= 0);
    
    drmFreeVersion(version);
}

igt_main
{
    int fd = -1;
    
    igt_fixture {
        fd = drm_open_driver(DRIVER_ANY);
        igt_require(fd >= 0);
        igt_info("成功打开 DRM 设备: fd=%d\n", fd);
    }
    
    igt_describe("查询并显示设备基本信息");
    igt_subtest("device-info") {
        print_device_info(fd);
    }
    
    igt_describe("验证驱动版本信息的完整性");
    igt_subtest("version-info") {
        verify_version(fd);
    }
    
    igt_describe("测试设备可以被多次打开和关闭");
    igt_subtest("multiple-open") {
        int test_fd;
        
        for (int i = 0; i < 10; i++) {
            test_fd = drm_open_driver(DRIVER_ANY);
            igt_assert(test_fd >= 0);
            
            /* 验证每次打开都能获取版本信息 */
            verify_version(test_fd);
            
            close(test_fd);
        }
        
        igt_info("成功完成 10 次打开/关闭循环\n");
    }
    
    igt_subtest_group {
        igt_fixture {
            igt_info("进入子测试组\n");
        }
        
        igt_describe("测试设备名称长度");
        igt_subtest("name-length") {
            drmVersionPtr version = drmGetVersion(fd);
            igt_assert(version);
            
            igt_assert(version->name_len > 0);
            igt_assert(version->name_len < 256);
            
            drmFreeVersion(version);
        }
        
        igt_describe("测试驱动描述长度");
        igt_subtest("desc-length") {
            drmVersionPtr version = drmGetVersion(fd);
            igt_assert(version);
            
            igt_assert(version->desc_len > 0);
            igt_assert(version->desc_len < 1024);
            
            drmFreeVersion(version);
        }
    }
    
    igt_fixture {
        close(fd);
        igt_info("测试完成,已关闭设备\n");
    }
}

代码亮点

1. 文档注释

/**
 * SUBTEST: device-info
 * Description: 查询并显示 DRM 设备基本信息
 * Category: basic
 * Run type: FULL
 */

这些注释会被自动提取生成文档。

2. 辅助函数

static void print_device_info(int fd)
static void verify_version(int fd)

将复杂逻辑封装成函数,提高可读性。

3. 子测试组

igt_subtest_group {
    igt_fixture { /* 组准备 */ }
    igt_subtest("test-1") { }
    igt_subtest("test-2") { }
}

组织相关的测试。


🔧 第四步:添加到构建系统

编辑 meson.build

tests/meson.build 中添加你的测试:

cd tests/
vim meson.build

找到测试列表部分,添加:

# 在适当的位置添加
test_progs = [
    'core_auth',
    'core_getclient',
    # ... 其他测试 ...
    'my_first_test',  # ← 添加你的测试
]

完整的 meson.build 示例

如果你想更精确地控制:

# 在 tests/meson.build 中
my_first_test = executable(
    'my_first_test',
    'my_first_test.c',
    dependencies: test_deps,
    install: true,
    install_dir: libexecdir / 'igt-gpu-tools',
)

test(
    'my_first_test',
    my_first_test,
    is_parallel: false,
    timeout: 60,
)

🏗️ 第五步:编译测试

重新配置构建

cd /path/to/igt-gpu-tools

# 重新配置(如果需要)
meson setup --reconfigure build/

# 或者直接编译
meson compile -C build/

只编译你的测试

# 使用 ninja 只编译特定目标
ninja -C build/ my_first_test

# 验证编译成功
ls -lh build/tests/my_first_test

编译输出

[1/1] Linking target tests/my_first_test
NOTICE: You are running this build from a shallow git checkout which may
        make some operations unavailable. To fix this, please run
        'git fetch --unshallow' and re-configure the build.
Build targets in project: 386

▶️ 第六步:运行测试

列出子测试

# 查看所有子测试
./build/tests/my_first_test --list-subtests

# 输出:
# device-info
# version-info
# multiple-open
# name-length
# desc-length

运行所有子测试

sudo ./build/tests/my_first_test

# 或指定子测试
sudo ./build/tests/my_first_test --run-subtest device-info

期望输出

IGT-Version: 1.28-gxxxx (x86_64) (Linux: 6.x.x)
Starting subtest: device-info
成功打开 DRM 设备: fd=3
=== 设备信息 ===
驱动名称: i915
驱动描述: Intel Graphics
驱动日期: 20230101
驱动版本: 1.6.0
Bus ID: pci:0000:00:02.0
Subtest device-info: SUCCESS (0.001s)

Starting subtest: version-info
Subtest version-info: SUCCESS (0.000s)

Starting subtest: multiple-open
成功完成 10 次打开/关闭循环
Subtest multiple-open: SUCCESS (0.002s)

Starting subtest: name-length
Subtest name-length: SUCCESS (0.000s)

Starting subtest: desc-length
Subtest desc-length: SUCCESS (0.000s)

测试完成,已关闭设备

📊 第七步:测试验证

验证测试正确性

1. 检查所有子测试都能运行

for test in $(./build/tests/my_first_test --list-subtests); do
    echo "测试: $test"
    sudo ./build/tests/my_first_test --run-subtest $test
done

2. 测试失败场景

添加一个故意失败的测试:

igt_subtest("intentional-failure") {
    igt_info("这个测试应该失败\n");
    igt_assert_eq(1, 2);  // 故意失败
}

运行并观察输出:

sudo ./build/tests/my_first_test --run-subtest intentional-failure

# 输出:
# Starting subtest: intentional-failure
# 这个测试应该失败
# (my_first_test:12345) CRITICAL: Test assertion failure function main, file my_first_test.c:123:
# (my_first_test:12345) CRITICAL: Failed assertion: 1 == 2
# Subtest intentional-failure: FAIL (0.000s)

3. 测试跳过场景

igt_subtest("skip-test") {
    igt_require(false);  // 条件不满足,跳过
    igt_info("不会执行到这里\n");
}

🎨 实践练习

练习 1:Hello World 测试

创建 tests/hello_igt.c

#include "igt.h"

IGT_TEST_DESCRIPTION("Hello IGT 测试");

igt_simple_main
{
    igt_info("Hello, IGT World!\n");
    igt_info("当前进程 PID: %d\n", getpid());
    igt_info("运行用户 UID: %d\n", getuid());
}

任务

  1. 添加到 meson.build
  2. 编译并运行
  3. 观察输出

练习 2:文件操作测试

创建一个测试读写临时文件:

#include "igt.h"
#include <fcntl.h>
#include <unistd.h>

IGT_TEST_DESCRIPTION("文件操作测试");

igt_main
{
    char tmpfile[64];
    int fd;
    
    igt_subtest("create-file") {
        snprintf(tmpfile, sizeof(tmpfile), "/tmp/igt-test-%d", getpid());
        
        fd = open(tmpfile, O_CREAT | O_RDWR, 0644);
        igt_assert(fd >= 0);
        
        igt_info("创建文件: %s\n", tmpfile);
        close(fd);
    }
    
    igt_subtest("write-file") {
        const char *data = "IGT Test Data\n";
        ssize_t ret;
        
        fd = open(tmpfile, O_WRONLY);
        igt_assert(fd >= 0);
        
        ret = write(fd, data, strlen(data));
        igt_assert_eq(ret, strlen(data));
        
        close(fd);
    }
    
    igt_subtest("read-file") {
        char buffer[64] = {0};
        ssize_t ret;
        
        fd = open(tmpfile, O_RDONLY);
        igt_assert(fd >= 0);
        
        ret = read(fd, buffer, sizeof(buffer));
        igt_assert(ret > 0);
        
        igt_info("读取内容: %s", buffer);
        close(fd);
    }
    
    igt_fixture {
        unlink(tmpfile);
    }
}

练习 3:KMS 测试

创建一个简单的 KMS 测试:

#include "igt.h"
#include "igt_kms.h"

IGT_TEST_DESCRIPTION("简单的 KMS 测试");

igt_main
{
    igt_display_t display;
    igt_output_t *output;
    int fd;
    
    igt_fixture {
        fd = drm_open_driver_master(DRIVER_ANY);
        igt_display_require(&display, fd);
    }
    
    igt_subtest("list-outputs") {
        int count = 0;
        
        for_each_connected_output(&display, output) {
            drmModeModeInfo *mode;
            
            mode = igt_output_get_mode(output);
            igt_info("输出 %d: %s\n", count, 
                     igt_output_name(output));
            igt_info("  模式: %dx%d@%d\n",
                     mode->hdisplay,
                     mode->vdisplay,
                     mode->vrefresh);
            count++;
        }
        
        igt_info("总共 %d 个已连接的输出\n", count);
    }
    
    igt_fixture {
        igt_display_fini(&display);
        close(fd);
    }
}

✅ 最佳实践

1. 命名约定

测试文件名

core_*       - DRM 核心测试
kms_*        - KMS 显示测试
gem_*        - GEM 内存管理(Intel)
amd_*        - AMD 特定测试

子测试名

basic               - 基础功能
invalid-*           - 无效参数测试
stress-*            - 压力测试
suspend-*           - 电源管理测试

2. 代码风格

遵循 Linux 内核风格:

// ✅ 好的风格
static void helper_function(int fd)
{
    if (condition) {
        do_something();
    }
}

// ❌ 不好的风格
static void helperFunction(int fd){
    if(condition){
        do_something();
    }
}

3. 错误处理

// ✅ 正确的错误处理
int fd = drm_open_driver(DRIVER_ANY);
igt_require(fd >= 0);

// ✅ 使用断言验证
drmVersionPtr ver = drmGetVersion(fd);
igt_assert(ver != NULL);

// ✅ 清理资源
drmFreeVersion(ver);
close(fd);

4. 日志输出

// 使用合适的日志级别
igt_debug("调试信息:细节较多\n");
igt_info("一般信息:正常输出\n");
igt_warn("警告信息:可能有问题\n");
igt_critical("严重错误:必须注意\n");

5. 测试隔离

igt_main
{
    // 每个 fixture 独立
    igt_subtest("test-1") {
        int fd = drm_open_driver(DRIVER_ANY);
        // 测试逻辑
        close(fd);
    }
    
    igt_subtest("test-2") {
        // test-2 不依赖 test-1 的状态
        int fd = drm_open_driver(DRIVER_ANY);
        // 测试逻辑
        close(fd);
    }
}

🐛 常见问题

问题 1:编译失败

错误

undefined reference to `drm_open_driver'

解决

# 确保 meson.build 包含正确的依赖
executable('my_test',
    'my_test.c',
    dependencies: test_deps,  # ← 必须有这个
    install: true,
)

问题 2:找不到测试

错误

bash: ./build/tests/my_test: No such file or directory

解决

# 检查是否添加到 test_progs
grep "my_test" tests/meson.build

# 重新编译
ninja -C build/ my_test

问题 3:权限问题

错误

Could not open DRM device: Permission denied

解决

# 使用 sudo 运行
sudo ./build/tests/my_test

# 或添加到 video 组
sudo usermod -a -G video $USER
# 注销后重新登录

问题 4:设备不支持

错误

Test requirement not met in function main

解决

// 添加更宽松的要求
igt_fixture {
    fd = drm_open_driver(DRIVER_ANY);  // 接受任意驱动
    if (fd < 0) {
        igt_skip("No DRM device available\n");
    }
}

📚 下一步

恭喜!你已经学会了编写 IGT 测试。接下来:

👉 继续阅读12-调试技巧 - 学习如何调试测试

或者

  • 研究现有测试:tests/core_*.c
  • 尝试更复杂的测试:KMS、GEM 等
  • 阅读 API 文档:lib/igt_*.h

📎 参考资料

示例测试

  • tests/core_auth.c - 简单的核心测试
  • tests/kms_addfb_basic.c - 基础 KMS 测试
  • tests/template.c - 测试模板(如果存在)

构建系统

  • tests/meson.build - 测试构建配置
  • meson_options.txt - 构建选项

代码风格

API 文档

  • lib/igt_core.h - 核心测试框架
  • lib/drmtest.h - DRM 测试辅助
  • lib/igt_kms.h - KMS 测试框架

💡 提示

快速开始模板

#include "igt.h"

IGT_TEST_DESCRIPTION("测试描述");

igt_main
{
    int fd;
    
    igt_fixture {
        fd = drm_open_driver(DRIVER_ANY);
    }
    
    igt_subtest("my-test") {
        // 你的测试代码
    }
    
    igt_fixture {
        close(fd);
    }
}

调试技巧

# 启用调试输出
IGT_LOG_LEVEL=debug sudo ./build/tests/my_test

# 使用 gdb 调试
sudo gdb --args ./build/tests/my_test --run-subtest my-test

# 查看详细输出
sudo ./build/tests/my_test --debug

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐