
Linux 内核 KTAP 格式深度解析KUnit 与 kselftest 测试结果的通用输出协议【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linuxLinux 内核的测试框架KUnit、kselftest普遍以 TAPTest Anything Protocol风格的文本报告测试结果但由于内核测试存在嵌套测试、未知行容忍等特殊需求内核在 TAP 之上定义了自己的扩展格式——KTAPKernel Test Anything Protocol。本文基于内核文档 ktap.rst 完整讲解 KTAP 的四种行类型、指令directive语义、嵌套测试规则及其与标准 TAP 的差异并结合 kunit_parser.py、ktap_helpers.sh、kselftest.h 等仓库源码说明这一格式在内核测试工具链中是如何被产出和消费的。读完本文你将能够读懂任何一份内核测试的 TAP/KTAP 输出并为自己编写的内核测试或解析器正确实现该协议。一、KTAP 的定位为什么内核需要扩展 TAPTAPTest Anything Protocol是一种被大量项目采用的测试结果文本格式其规范位于 testanything.org文档中给出的外部链接此处不展开。Linux 内核长期以来以 TAP 输出作为测试结果载体但内核测试框架有一些原始 TAP 规范覆盖不到的需求因此内核社区定义了 Kernel TAPKTAP格式对 TAP 进行扩展和修改。规范文档明确KTAP 测试描述一系列测试测试可以嵌套test 内可以有 subtest每个测试既可包含非结构化的诊断数据如日志行供人工调试也有一个最终结果测试结构与结果对机器可读诊断数据则不保证结构。KTAP 输出由四类行构成Version 行版本行Plan 行测试计划行Test case result 行测试结果行Diagnostic 行诊断行一般而言合法的 KTAP 输出应当同时是合法的 TAP 输出但部分信息尤其是嵌套测试结果在按纯 TAP 解读时会丢失。文档同时提醒TAP 存在一个停滞的 14 版草案TAP14KTAP 在个别地方与其分道扬镳最典型的是子测试的 Subtest 头。二、Version 行识别输出格式与子测试边界所有 KTAP 格式的结果都以一个版本行开头声明其遵从的 (K)TAP 版本。文档列出的合法形式包括KTAP version 1TAP version 13TAP version 14关键点在于在 KTAP 中子测试同样以版本行开头以此标志嵌套测试结果区段的开始。这与 TAP14 使用独立的Subtest: name行不同。虽然规范建议新实现的测试统一输出KTAP version 1但解析器通常应兼容其余版本以对接既有测试与框架。仓库中的 KUnit 解析器正是这样实现的kunit_parser.py 中定义了两个正则KTAP_START与TAP_START分别匹配KTAP version N和TAP version N而 kunit_parser.py 明确声明了各自接受的版本集合KTAP_VERSIONS [1]、TAP_VERSIONS [13, 14]check_version()会对过高或过低的版本号记录错误。内核侧的输出源头也可以验证这一点KUnit 执行器在 executor.c 中通过pr_info(KTAP version 1\n)打印顶层版本行子测试则在 test.c 中以KUNIT_SUBTEST_INDENT KTAP version 1\n打印即带缩进的版本行开启嵌套段debugfs.c 中的测试报告接口也输出同样的版本行。三、Plan 行声明测试数量Plan 行给出 KTAP 输出中测试或子测试的总数格式必须是1..N其中 N 为测试或子测试数量在嵌套结构中Plan 行跟随版本行出现用于指示该嵌套层的测试数量。文档指出有些情况下测试数量事先未知此时可以省略 Plan 行但强烈建议在可能时提供。从源码结构看解析器对缺失 Plan 的行是容忍的kunit_parser.py 中TEST_PLAN正则匹配1..Nparse_test_plan()匹配失败时会将expected_count置为None未知数量解析循环 kunit_parser.py 随即改为按名字匹配结果行时停止的策略而非按固定数量截断。kselftest 的 shell 工具库 ktap_helpers.sh 则给出了最常见的用法ktap_set_plan()打印1..Nktap_skip_all()打印1..0 # SKIP 原因后者用于整个套件因依赖缺失而整体跳过的场景。四、Test case result 行结果、指令与诊断数据Test case result 行表示一个测试的最终状态是必选行格式为result number [description][ # [directive] [diagnostic data]]各字段语义resultok表示通过not ok表示失败number测试编号同一嵌套层内第一个测试编号必须为 1之后逐个 1description可选但推荐的测试描述通常是测试名可以是除#和换行之外的任意字符串directive 与 diagnostic data均可选若存在必须跟在#之后。4.1 指令directive指令是表示非单纯通过/失败结果的关键词。解析器遇到不认识的指令时应回退到按ok/not ok处理。规范当前接受五个指令指令含义与 result 字段的约束SKIP测试被跳过result 可以是ok或not okTODO继承自 TAP测试当前预期不通过如被测功能已知损坏内核中不鼓励使用无强制约束XFAIL测试预期失败与 TODO 类似被部分 kselftest 使用无强制约束TIMEOUT测试超时应使用not okERROR测试执行因诊断数据中记录的特定错误而失败应使用not ok诊断数据diagnostic data是纯文本字段用于补充说明产生该结果的原因通常是 ERROR 或失败测试的错误信息、SKIP 结果缺失的依赖说明等若既无指令也无诊断数据结果行可以不带#分隔符。文档给出的示例结果行及语义ok 1 test_case_name测试 test_case_name 通过。not ok 1 test_case_name测试 test_case_name 失败。ok 1 test # SKIP necessary dependency unavailable测试 test 被跳过诊断信息为 necessary dependency unavailable。not ok 1 test # TIMEOUT 30 seconds测试 test 超时诊断数据为 30 秒。ok 5 check return code # rcode0测试 check return code 通过附带诊断数据 rcode0。解析器对指令的实际处理值得注意KUnit 解析器 kunit_parser.py 用TEST_RESULT正则匹配通用结果行、用TEST_RESULT_SKIP专门匹配# SKIP行并在parse_test_result()kunit_parser.py的注释中说明SKIP 指令是唯一会改变状态解析的指令——# SKIP使状态置为TestStatus.SKIPPED并记录跳过原因其余情况直接按ok/not ok映射为 SUCCESS/FAILURE。这与规范解析器遇到不认识的指令应回退到 ok/not ok 结果的要求一致。kselftest 侧的 C 语言辅助函数族则展示了产出端kselftest.h 提供ksft_test_result_pass/fail/skip/xfail/xpass/error等函数统一打印上述格式的结果行shell 库 ktap_helpers.sh 的__ktap_test()拼接result number description # directivektap_test_skip()与ktap_test_xfail()分别固定ok结果加上SKIP/XFAIL指令。五、Diagnostic 行与 Unknown 行5.1 Diagnostic 行测试需要输出额外信息时应使用诊断行可选、自由文本常用来比结果行诊断数据更详细地描述正在测试什么以及中间结果。格式为# diagnostic_description描述可以是任意字符串诊断行可出现在输出的任何位置惯例是与某测试相关的诊断行直接放在该测试的结果行之前。5.2 Unknown 行KTAP 与 TAP 的关键差异KTAP 输出中允许出现不符合上述四种格式的行它们不影响任何测试的状态。文档特别强调这是与 TAP 的重要区别内核测试可能向系统控制台或日志文件打印消息这些目的地可能混有无关的内核或用户态活动产生的消息也可能有测试所调用的内核代码打印的消息——被测试代码往往不知道自己正处于测试进程中因此无法把消息格式化为诊断行。同时文档提醒多数工具会把未知行当作诊断行处理即使不以#开头以捕获任何有助于调试的内核输出但测试自身仍应尽量用#前缀输出诊断信息。KUnit 解析器的parse_diagnostic()kunit_parser.py即把不属于结果行、Subtest 头、版本行、Plan 行的其他行收集进测试日志用于在失败/崩溃时回显。六、嵌套测试KTAP 最核心的扩展6.1 规则KTAP 允许测试嵌套一个测试的输出中内嵌一整组 KTAP 格式的结果用于对关联测试分组分类或将同一测试的不同结果拆分开。具体规则父测试的结果应汇总其全部子测试的结果结构为另一条 KTAP 版本行 测试计划 子测试 最终结果若某个子测试失败父测试也应失败结果向上冒泡子测试的所有行都应缩进一级缩进为两个空格 缩进从版本行开始到父测试结果行之前结束Unknown 行不计入子测试行缩进与否均可。KUnit 内核侧的实现正好印证了这两条test.c 打印子测试版本行时带KUNIT_SUBTEST_INDENT前缀而 executor.c 顶层版本行无缩进。6.2 两个级子测试的示例KTAP version 1 1..1 KTAP version 1 1..2 ok 1 test_1 not ok 2 test_2 # example failed not ok 1 example外层 plan 为1..1唯一的测试 example 包含两个缩进两空的子测试test_2 失败导致父测试 example 记为not ok并附带诊断行# example failed。多级嵌套示例KTAP version 1 1..2 KTAP version 1 1..2 KTAP version 1 1..2 not ok 1 test_1 ok 2 test_2 not ok 1 test_3 ok 2 test_4 # SKIP not ok 1 example_test_1 ok 2 example_test_2可见缩进随嵌套层级逐层加深SKIP指令出现在子测试 test_4 上而 test_4 的 result 仍写作ok——与规范SKIP 允许 ok 或 not ok的约束吻合。6.3 完整示例及其层级语义文档给出的完整 KTAP 输出示例KTAP version 1 1..1 KTAP version 1 1..3 KTAP version 1 1..1 # test_1: initializing test_1 ok 1 test_1 ok 1 example_test_1 KTAP version 1 1..2 ok 1 test_1 # SKIP test_1 skipped ok 2 test_2 ok 2 example_test_2 KTAP version 1 1..3 ok 1 test_1 # test_2: FAIL not ok 2 test_2 ok 3 test_3 # SKIP test_3 skipped not ok 3 example_test_3 not ok 1 main_test它定义的层级为顶层单一测试 main_test失败含三个子测试example_test_1通过含子测试 test_1通过输出诊断信息 test_1: initializing test_1example_test_2通过含子测试 test_1跳过原因 test_1 skipped与 test_2通过example_test_3失败含子测试 test_1通过、test_2输出诊断行 test_2: FAIL 后失败、test_3跳过原因 test_3 skipped。注意同名子测试在不同父测试下并不冲突。该示例还展示了结果向上冒泡的合理规则任一子测试失败则父测试失败被跳过的子测试不影响父测试结果但若某父测试的全部子测试都被跳过将该父测试标记为跳过通常更合理。KUnit 解析器中的bubble_up_test_results()kunit_parser.py实现了同一套统计冒泡逻辑把子测试的 TestCounts 累加到父测试崩溃状态优先测试数据 test_data 等文件则提供了供 kunit_tool_test.py 单元测试使用的 KTAP 样本日志。七、TAP 与 KTAP 的主要差异对照特性TAPKTAP诊断消息中允许 YAML/JSON允许不推荐TODO 指令识别不识别内核中不鼓励允许任意深度的测试嵌套不允许允许未知行归类于 Anything else是否未知行属错误/不正确允许补充一点TAP14 规范虽然也允许嵌套测试但它使用Subtest: name行name 为父测试名代替嵌套版本行来标志子测试——这正是 KTAP 与 TAP14 的著名分歧点。KUnit 解析器对两种风格都做了兼容TEST_HEADER正则kunit_parser.py匹配# Subtest: name而parse_test()的文档字符串kunit_parser.py明确列出接受的三种测试格式——主 KTAP/TAP 头、带版本行和/或# Subtest头的子测试头、纯结果行——并注明仅 KTAP 版本行的形式即为符合 KTAP v1 规范的形式。八、在内核中产出 KTAP 输出的两条路径8.1 kselftest用户态kselftest 的 C 辅助头文件 kselftest.h 中ksft_print_header()打印TAP version 13随后一系列ksft_test_result_*函数输出对应结果行shell 测试则 source ktap_helpers.sh 使用ktap_print_header()同样输出TAP version 13、ktap_set_plan()、ktap_test_pass/skip/xfail/fail()测试结束时ktap_finished()依据 pass/skip/xfail 计数与 plan 总数比较决定退出码ktap_print_totals()打印# Totals: pass:... fail:... xfail:... skip:... error:...汇总诊断行。注意kselftest 目前选择输出TAP version 13头这在 KTAP 解析器中属于兼容版本。8.2 KUnit内核态KUnit 测试在内核内运行KTAP 结果直接打印到内核日志dmesg。顶层版本行见 executor.c嵌套子测试版本行带缩进前缀见 test.c 与 debugfs.c。用户侧的 kunit_tool 通过 kunit_parser.py 的extract_tap_lines()从整段内核输出中切出 KTAP 片段从第一个KTAP/TAP version行开始剥离 dmesg 前缀遇到KTAP_END正则定义的边界如List of all partitions:、Kernel panic - not syncing: VFS:、reboot: System halted为止——这正体现了未知行/内核杂讯被容忍且需被过滤的设计动机。解析结果再由parse_run_tests()生成可读报告与 Testing complete. Passed: N, ... 汇总行。九、给测试编写者与解析器实现者的实践要点综合规范与仓库实现实现或消费 KTAP 时建议遵守新内核测试输出KTAP version 1头嵌套层同样以版本行开头并保持两级缩进为两空格尽量提供1..Nplan 行kselftest shell 库的ktap_set_plan()/ C 的ksft_print_plan()语义数量确实不可预知时才省略使用# SKIP 原因表达跳过TIMEOUT、ERROR指令必须配not ok避免使用内核中不鼓励的TODO指令kselftest 生态用XFAIL/XPASS表达预期失败见 ktap_helpers.sh诊断信息一律加#前缀并放在对应测试的结果行之前解析器必须容忍未知行与 dmesg 前缀参考 kunit_parser.py 的前缀剥离与起止边界处理对不认识的指令回退到 ok/not ok 语义实现嵌套时保证任一子测试失败则父测试失败、全跳过则建议标记跳过的冒泡规则并在不同父测试下允许同名子测试。十、延伸阅读kselftest 文档Documentation/dev-tools/kselftest.rstKUnit 文档Documentation/dev-tools/kunit/index.rstKTAP 规范原文Documentation/dev-tools/ktap.rstKUnit 输出解析器及其测试数据tools/testing/kunit/kunit_parser.py、tools/testing/kunit/kunit_tool_test.py、tools/testing/kunit/test_data/test_parse_ktap_output.log【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考