| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 2 个月前 | ||
| 4 个月前 | ||
| 3 个月前 | ||
| 5 个月前 | ||
| 2 个月前 | ||
| 1 个月前 | ||
| 1 个月前 | ||
| 22 天前 | ||
| 3 个月前 | ||
| 4 个月前 | ||
| 1 个月前 | ||
| 4 个月前 | ||
| 5 个月前 |
Devmon 测试模块总结
概述
tests/ 目录包含了 Devmon 设备管理系统的完整测试套件,覆盖了设备发现、设备管理、拓扑管理和工具函数等核心功能。测试使用 Google Test 框架编写,通过 meson 构建系统运行。
测试模块结构
测试套件按功能模块组织,分为以下几个子目录:
1. discovery/ - 设备发现测试模块
测试设备发现和 CSR(配置状态寄存器)解析相关的核心功能,包括应用模式(App Schema)、CSR 解析器、CSR 工具函数等。 详细说明请参考:discovery/README.md
2. device/ - 设备管理测试模块
测试设备管理相关的核心功能,包括设备管理器、CSR 处理和设备驱动等。 详细说明请参考:device/README.md
3. topology/ - 拓扑管理测试模块
测试设备拓扑管理相关的核心功能,包括拓扑对象的创建、设备关系的建立、Host Bus 继承等。 详细说明请参考:topology/README.md
4. plugins/ - 应用层 Lua 插件测试(独立目标)
C++ 宿主加载生产 hwproxy.plugins.* 快照(power_mgmt / general_hardware / smbus / pcie_device / sml)。五个独立 executable,不并入下面的 646 个 devmon_test。运行时需要 /opt/bmc/luaclib/mc.so、/opt/bmc/lualib/mc/init.lua 与 /opt/bmc/mcskynet(lua_engine 硬编码)。解释器与 mc.so 都用 Conan LuaJIT(与主 builddir / 板上同 ABI);smart_build.sh / Fixture 会把 builddir 产物拷到 /opt/bmc,并把断链的 lualib 改指 /usr/local/opt/bmc/lualib。
hwproxy.plugins.* 是 Lua require 名,不是 C++ libs/hwproxy。这些用例测的是 Chip so 里的 lua_engine + init.lua,不迁入 libs/hwproxy/tests/(那边测 hwproxy_service 启停/dump;且 if unidev 才编译,并会链 LuaJIT)。理由见 plugins/README.md「为何不迁入 libs/hwproxy/tests」。
总览:plugins/README.md。分插件:power_mgmt、general_hardware、mctpd/smbus、pcie_device、storage/sml。
5. libs/device_runtime/tests/ - 硬件访问调度器
独立目标 devmon_device_runtime_test(suite hwproxy_plugin,37 用例),测 access_domain_scheduler 的域准入、限流、合并读与 logical_executor 分离。不并入 devmon_test。
详细说明请参考:device_runtime 测试
6. 根目录测试文件
根目录包含以下测试文件:
test_devmon.cpp - Devmon 应用测试
测试 Devmon 应用的核心功能,包括服务初始化、清理机制、根对象管理、信息导出等。
测试用例数量:15 个
服务初始化测试:
init_with_app_paths: 测试服务初始化(带应用路径配置)init_populates_app_schema: 测试初始化时加载应用模式(Schema)
清理机制测试:
cleanup: 测试清理方法(基本功能)cleanup_after_start: 测试服务启动后调用清理方法cleanup_multiple_calls: 测试多次调用清理方法(幂等性)
根对象管理测试:
root_objects_exist_after_start: 验证服务启动后根对象已注册(Devmon、DevTopology、ObjectGroupRoot)root_object_duplicate_creation_throws: 测试重复创建根对象抛出异常object_group_root_duplicate_creation_throws: 测试重复创建对象组根对象抛出异常
服务生命周期测试:
stop_without_topology_instance_returns_true: 测试未启动拓扑发现时停止服务stop_handles_topology_cleanup_exception: 测试停止服务时的拓扑清理异常处理
命令行选项测试:
register_options: 测试命令行选项注册(boost::program_options 集成)
信息导出测试(Dump 功能):
dump_scanners_info: 测试导出扫描器信息到 CSV 文件(snapshot.csv)dump_drivers_load_info: 测试导出驱动加载信息到 CSV 文件(drivers_load_info.csv)dump_chips_access_info: 测试导出芯片访问统计信息到 CSV 文件(chips_access_statistic.csv)dump_accessors_info: 测试导出访问器信息到 CSV 文件(snapshot.csv,包含访问器标题行)
主要功能:
- 服务初始化和配置(应用路径、Schema 加载)
- 清理机制和资源管理(幂等性、异常安全)
- 根对象生命周期管理(创建、验证、重复创建检测)
- 服务启动和停止流程(拓扑发现集成、异常处理)
- 命令行选项注册和配置
- 信息导出功能(扫描器、驱动、芯片访问、访问器信息的 CSV 导出)
test_utils.cpp - 工具函数测试
测试工具函数的正确性,包括时间获取、连接器位置解析、Unit 信息提取等。
测试用例数量:49 个
get_sys_ms 测试(2 个用例):
test_get_sys_ms_basic: 基本功能测试(时间获取、时间递增验证)test_get_sys_ms_multiple_calls: 多次调用测试(时间单调递增)
get_connector_position 测试(23 个用例):
- 正常情况:
- 整数类型(int8_t、int16_t、int32_t、int64_t、uint8_t、uint16_t)
- 字符串类型(正常字符串、小写字符串)
- 零值处理
- 大值处理(0xFF、0x1234、0xABCD、0xEF01、0x3DEF)
- 三位十六进制数补零(0xABC → "0ABC")
- 边界情况:
- 空字符串
- 字段缺失
- 其他类型(布尔值、null、double、字典)
- 奇数长度补齐(0x0A、0x01、0x05、0x0B、0x0F)
get_connector_unit 测试(24 个用例):
- 正常情况:
- 成功获取 Unit 信息(基本功能)
- 多个兼容驱动
- 空兼容列表
- 兼容列表包含数字(自动转换为字符串)
- 包含额外字段(应被忽略)
- 异常情况:
- Unit 字段缺失
- Unit 字段为空字典
- Unit 字段类型错误(字符串、整数、数组、布尔值、null、double、嵌套字典)
- 空类型(m_type 为空)
- 缺少 Name 字段
- 缺少 Compatible 字段
- 类型转换错误:
- Type 字段类型不匹配(数组、整数、字典)
- Name 字段类型不匹配(字典)
- Compatible 字段类型不匹配(字符串、字典)
- 复杂嵌套结构处理
主要功能:
- 系统时间获取(单调递增、多次调用)
- 连接器位置解析(多种整数类型、字符串、边界情况、类型容错)
- Unit 信息提取(反射解析、类型验证、异常处理、字段容错)
动态驱动加载资源回归
本节跟踪 PR !416 / 任务 #144 的动态加载问题。生产代码位于 device_driver.cpp,回归用例位于 test_driver.cpp,真实插件夹具为 memory_throwing_driver.cpp 和 memory_nonstandard_throwing_driver.cpp。范围限于注册失败清理和重复加载保护,不代表全库内存安全验收。
问题与清理顺序
原 device_driver::load_file() 在 dlopen 成功、注册失败后仍正常返回,工厂随后可能缓存失败驱动;再次加载还可能覆盖旧句柄,丢失对应的 dlclose 入口。当前实现先拒绝已有句柄的重复加载,并在注册失败时清理后向调用者报告失败。
以下是同一函数的关键片段;省略日志和耗时统计,不是完整函数。
// 修复前:捕获注册异常后继续正常返回。
try {
register_driver();
info.success = true;
info.device_count = m_devices.size();
} catch (const std::exception& e) {
info.error_msg = e.what();
}
// 当前:先复制插件错误,离开 catch 后再卸载动态库。
bool registration_failed = false;
std::string registration_error;
try {
register_driver();
info.success = true;
info.device_count = m_devices.size();
} catch (const std::exception& e) {
registration_error = e.what();
registration_failed = true;
} catch (...) {
registration_error = "unknown driver registration error";
registration_failed = true;
}
try 内的函数抛出异常后,控制流进入匹配的 catch,不会继续执行成功赋值。const std::exception& e 是对异常对象的只读引用,可类比 C 中借用对象的指针;它不会复制异常对象。e.what() 返回的错误文本也不应跨越异常对象的寿命使用,赋给 std::string 才得到独立拥有的文本副本,类似 C 中另行分配并复制错误字符串。catch (...) 还接住 throw 42 这类不继承 std::exception 的异常。
当前失败路径的执行顺序为:复制错误文本 → 离开 catch 并销毁本路径的插件异常对象 → unload() 清空设备项并 dlclose → 记录失败信息 → 抛出 mc::bad_function_call_exception。C++ 异常对象销毁时会执行其析构函数;若异常类型由插件定义,这段代码可能位于 .so,所以必须先结束异常处理再卸载库。复制后的错误文本仍保留到日志记录和框架异常构造完成。mc::bad_function_call_exception 是项目的错误类型,并非 C++ 标准库提供的资源清理机制。
device_driver.h 明确约定:同一实例的加载、卸载和设备访问由调用者串行化。工厂为每次候选加载创建独立实例,在缓存锁内发布成功结果;竞争失败的独立实例会卸载自身句柄。unload() 的普通指针赋值与 C 的 void *handle = obj->handle; obj->handle = NULL; 一样,并非原子交换,也没有互斥作用。get_device() / get_devices() 返回借用引用,设备中的函数指针仍指向插件;调用方必须在卸载前结束使用,不能把 std::shared_ptr 持有的设备结构体误认为动态库也被自动保活。
回归用例与运行入口
以下用例均属于 DeviceDriverTest:
| 用例 | 场景及关键断言 |
|---|---|
LoadLibraryWithoutRegistrationRejectsAndClosesHandle |
库缺注册入口:抛出错误、句柄置空、设备集合为空,并记录失败 |
RepeatedLoadKeepsOriginalHandle |
已有句柄时再次加载:拒绝操作,保留原句柄 |
NonstandardRegistrationExceptionClosesHandle |
真实插件抛出非标准异常:清理后报错,RTLD_NOLOAD 检查插件不再驻留 |
PluginDefinedRegistrationExceptionClosesHandle |
真实插件抛出自定义 std::exception:异常对象先于库卸载销毁,检查句柄及驻留状态 |
从仓库根目录使用正式测试入口:
bingo test -ut -jit
bingo test -ut -jit -as
这是 CI 环境的完整复现入口。本轮本机 Bingo 缺少 test 子命令,因此本地使用复用依赖的原生 Meson 构建,不能将其表述为 Bingo 全流程通过。当前 Lua 包装入口不会将 Bingo 的 -f 参数转交给 GTest,不能用 -f 'DeviceDriverTest.*' 证明这些 C++ 用例已运行;应检查日志中的实际 RUN / OK / FAILED 记录。执行前核对实际构建源码、测试程序和依赖版本,同时保留实际可执行文件路径。新增用例依赖 libdl.so.2、/proc/self/exe 和 RTLD_NOLOAD,目前验证范围为 Linux/glibc。
插件构建、安装与用例隔离
common 测试构建配置 将 test_driver.cpp 同时编入 common_test 可执行文件及 common_test_lib 静态库;devmon 测试构建配置 链接该静态库。test_csr.cpp 引用同一对象文件中的 test_driver::create(),因此插件回归用例也会进入 devmon_test。这与 C 链接静态库时为满足一个函数引用而取出整个 .o 的情形相同。
原配置仅构建插件,未安装;测试又只在自身可执行文件目录找库,导致 CI 的 temp/bin/devmon_test 缺少夹具。现将两个插件安装到与测试程序相同的 bindir,并在所有引用这些用例的 Meson 测试入口声明插件构建依赖:
| 入口 | 插件定位方式 |
|---|---|
Meson common_test / devmon_test / unidev 的 hwdiscovery_test |
Meson 设置 DEVMON_TEST_DRIVER_DIR,指向构建目录的 common 测试夹具目录 |
安装或移动后的测试程序(包括 CI temp/bin/devmon_test) |
未设置该变量时,在 /proc/self/exe 所在目录找插件 |
scripts/run_tests_loop.sh 从 Meson 测试定义读取并传递插件目录;缺少定义时使用构建目录的 libs/common/tests。直接运行构建目录内的 devmon_test 时也应设置 DEVMON_TEST_DRIVER_DIR,或使用 Meson 测试入口。部署验收必须取消该变量,防止意外从构建树借到插件而掩盖漏打包。测试先检查夹具存在,再断言注册失败和插件卸载;不能把缺库导致的 mc::system_exception 当作注册失败的 mc::bad_function_call_exception。
两个插件分别固定抛出整数异常和插件定义的 std::exception,不再通过 DEVMON_TEST_STD_EXCEPTION 切换行为。这里抛整数用于覆盖 catch (...);另一个插件的异常对象包含定义在 .so 中的虚函数实现,测试确保捕获结束后才卸载库。路径变量只配置测试资产所在目录,不改变插件行为。夹具只在启用测试的构建中安装,不进入关闭测试的产品构建。
路径修复与 C++ 写法
原测试直接使用可执行文件目录,无法覆盖插件位于 common 目录、程序位于 devmon 目录的构建布局:
// 修改前,来自原测试。
auto library_path = std::filesystem::read_symlink("/proc/self/exe").parent_path() / "libmemory_throwing_driver.so";
现在两个用例各自选固定插件,并提前诊断缺失文件。以下摘自整数异常用例:
const auto library_path = throwing_driver_path("libmemory_nonstandard_throwing_driver.so");
ASSERT_TRUE(std::filesystem::is_regular_file(library_path)) << "Missing test driver: " << library_path;
auto 让编译器从右侧推导类型,此处得到 std::filesystem::path;const 表示这个局部路径不再修改。path 自己保存路径内容,可类比一个管理字符串的 C 结构体;路径之间的 / 是 C++ 重载的拼接操作,不是整数除法。throwing_driver_path() 先通过与 C 相同的 getenv() 借用目录字符串,再构造自己的路径对象;未设置目录时回到可执行文件目录。ASSERT_TRUE 是 GTest 的断言宏,失败即结束当前用例;后面的 << 为诊断输出追加实际路径。它先排除“测试资产缺失”,后面的异常类型、错误文本、加载记录和 RTLD_NOLOAD 断言才检验“插件已执行但注册失败并卸载”。
flowchart LR
A[原安装配置:只安装测试程序] --> B[temp/bin 缺少插件]
B --> C[dlopen 失败,未执行注册函数]
D[修复:两个固定插件随测试程序安装] --> E[同目录找到插件]
E --> F[调用注册函数并捕获异常]
F --> G[复制错误文本,异常对象销毁]
G --> H[清空设备并 dlclose]
H --> I[断言错误文本及插件不再驻留]
验证记录与限制
- 本轮合入主线
9eea32c后,以 GCC 9、原生 Meson、本机既有依赖完成构建;三个已配置测试入口全部通过:调度测试 59 项、common_test415 项、devmon_test1024 项通过,后者另有 14 项 CSR 引用对象用例按现有条件跳过。不是完整 Bingo/产品依赖构建结果。 - 构建目录中两个主要入口的
DeviceDriverTest.*各 17 项通过。执行实际meson install后,取消插件目录变量,以安装目录中的库运行两个测试程序,各 17 项随机顺序重复 10 轮通过;临时移走两个已安装夹具时,目标两用例明确因缺文件失败,恢复夹具后通过。 unidev=true单独完成 Meson 配置,核对common_test、devmon_test、hwdiscovery_test均具有插件目录和两个插件依赖;本轮没有编译或运行完整 unidev 套件。循环脚本的实际 Meson 参数解析、空库路径字段及解析失败回退已验证。- 历史本地
57e786db的 ASAN 驱动测试 17/17、common_test408/408 属于旧提交与旧构建,不能计入本轮验证。本轮未重新执行 ASAN 或目标板测试。 - 用户提供的 2026-10-13 CI 日志对应源提交
5aa8a6a的合成提交42a06e874afcb094d0e5cb2dfce139ffa0b7d6b7:编译了插件,包内.so列表却不含插件,实际运行temp/bin/devmon_test,最终列出两条插件异常用例失败。附件仅包含失败摘要,没有原始断言全文。 - tests/main.cpp 使用
std::quick_exit;仅设置detect_leaks=1不能证明 LSAN 正常退出扫描已完成。远端 CI 必须按推送后的实际提交单独核对,不能复用上述历史失败或本地结果替代。
测试统计
以下总数、覆盖率和连续运行统计为既有文档记录,未在本轮重新测量;不能替代上节按提交和入口区分的验证结果。
总体统计
该测试套件共包含 646 个测试用例:
| 模块 | 测试文件 | 测试用例数 | 说明 |
|---|---|---|---|
| discovery/ | 4 个文件 | 133 个 | 设备发现和 CSR 解析 |
| device/ | 3 个文件 | 175 个 | 设备管理 |
| topology/ | 2 个文件 | 274 个 | 拓扑管理 |
| 根目录 | 2 个文件 | 64 个 | 应用和工具函数测试(test_devmon.cpp: 15 个,test_utils.cpp: 49 个) |
| plugins/ | 5 个文件 | 48 个 | 真实 Lua 插件(独立目标,不计入上行 646) |
| 总计 | 11 个文件 | 646 个 | devmon_test / common_test 主套件 |
代码覆盖率
当前测试覆盖情况如下:
总体覆盖率
| 指标 | 覆盖率 | 覆盖数量 | 总数 |
|---|---|---|---|
| 代码行(Lines) | 93.1% | 2,071 行 | 2,225 行 |
| 函数(Functions) | 72.0% | 376 个 | 522 个 |
| 分支(Branches) | 47.6% | 3,812 个 | 8,016 个 |
覆盖率分析
代码行覆盖率(93.1%)
- 覆盖情况良好:超过 93% 的代码行已被测试覆盖
- 未覆盖部分:主要是错误处理路径、边界情况和一些特殊场景
- 改进方向:可以增加边界情况和错误处理的测试用例
函数覆盖率(72.0%)
- 覆盖情况良好:超过 72% 的函数已被测试覆盖
- 未覆盖部分:主要集中在反射相关函数、Lambda 函数、错误处理函数和辅助函数
- 改进方向:可以增加对辅助函数和工具函数的测试覆盖
分支覆盖率(47.6%)
- 覆盖情况仍偏低:这是需要重点改进的方面
- 未覆盖部分:条件分支、错误处理分支和特殊逻辑分支仍然是薄弱环节
- 改进方向:需要增加更多的边界情况测试和条件分支测试
覆盖率改进建议
- 增加边界情况测试:覆盖更多的边界条件和错误处理路径
- 增加条件分支测试:提高分支覆盖率,特别是复杂条件判断的分支
- 增加错误处理测试:测试异常情况和错误恢复机制
- 增加并发测试:测试多线程场景和并发安全性
- 增加集成测试:测试模块间的交互和集成场景
- 间接测试反射函数:通过实际使用场景间接测试反射相关函数
- 触发 Lambda 执行:通过触发相应条件来覆盖 Lambda 中的代码路径
测试框架和基础设施
测试框架
- Google Test:所有测试用例使用 Google Test 框架编写
- meson 构建系统:通过 meson 构建和运行测试
- TestBase 继承:所有测试类继承自
mc::test::TestWithEngine,提供统一的测试环境
测试基础设施
测试驱动和模拟
- test_driver:提供模拟设备驱动,支持创建 PCIeNicCard、NetworkPort、OpticalModule 等设备类型
- mock_topology_drivers:提供模拟的拓扑设备驱动(I2c、Chip、Pca9545)
D-Bus 集成
- 测试中集成了 D-Bus 通信,验证设备在系统中的注册和属性访问
- 支持重试机制(最多 5 次重试,每次间隔 100-200ms)
测试隔离机制
- 对象表清理:每个测试用例后自动清理对象表中的对象(保留根对象)
- 多次清理:多次清理确保对象被完全删除(最多 3 次重试,处理依赖关系)
- 智能删除顺序:按路径长度降序删除,先删除子对象(路径更长的)
- 缓存清理:清空 func_collection 缓存,避免测试用例间相互影响
- 驱动清理:移除测试驱动,确保驱动状态干净
- 异常安全:支持异常安全的清理操作
测试最佳实践
1. 测试隔离
- 每个测试用例应该是独立的,不依赖其他测试用例的执行顺序
- 使用
SetUp()和TearDown()确保测试环境的一致性和清理
2. 测试清理
- 确保测试后清理所有创建的资源(对象、驱动、缓存等)
- 使用多次清理机制处理依赖关系
- 按正确顺序删除对象(先删除子对象)
3. 错误处理
- 测试异常情况时使用
EXPECT_THROW或ASSERT_THROW - 测试正常情况时使用
EXPECT_NO_THROW - 验证错误消息和异常类型
4. D-Bus 测试
- 使用重试机制处理 D-Bus 调用的异步性
- 验证 D-Bus 消息的有效性和类型
- 处理路径冲突等边界情况
5. 覆盖率考虑
- 编写测试时考虑覆盖率目标
- 优先覆盖关键路径和核心功能
- 逐步增加边界情况和错误处理的测试
持续改进
当前状态
- ✅ 测试用例数量充足(646 个)
- ✅ 代码行覆盖率良好(93.1%)
- ✅ 函数覆盖率良好(72.0%)
- ⚠️ 分支覆盖率较低(47.6%,仍需改进)
- 历史稳定性记录为 100 次连续运行通过;动态加载回归仍有上文列出的 CI 失败,不能据此判断当前完整测试通过。
改进方向
- 提高分支覆盖率:增加条件分支和边界情况的测试
- 完善函数覆盖:增加对辅助函数和工具函数的测试
- 增加集成测试:测试模块间的交互和复杂场景
- 增加并发测试:测试多线程场景和并发安全性
- 增加性能测试:测试大规模数据和复杂场景下的性能