Skia C++ 单元测试实战指南:从 DEF_TEST 编写、GN 构建集成到 dm 运行
图形学【免费下载链接】skiaSkia is a complete 2D graphic library for drawing Text, Geometries, and Images. See documentation for contribution instructions.项目地址https://gitcode.com/gh_mirrors/ski/skia点击查看免费下载本文是一份基于 Skia 仓库 agents/skills/skia-gn-workflow/references/testing.md 的单元测试实战指南面向需要在 Skia 上新增 C 单元测试的开发者。文章覆盖测试宏体系DEF_TEST/DEF_GANESH_TEST/DEF_GRAPHITE_TEST、断言宏、gn/tests.gni构建注册以及使用dm工具过滤与运行测试的完整流程读完后你可以独立完成写一个测试 → 挂进构建 → 跑起来的全链路工作。Skia 自定义单元测试框架概览Skia 没有采用 GTest 等第三方框架而是自带一套轻量的 C 单元测试框架核心头文件位于 tests/Test.h框架主体由skiatest命名空间承载。绝大多数单元测试放在仓库根目录的tests/目录下与 gm/ 中的图形测试 GM、bench/ 中的基准测试区分开并使用DEF_TEST系列宏声明测试函数。从 tests/Test.h 可以看到每个测试最终都被包装为一个skiatest::Test结构体它携带测试名称、测试类型kCPU/kCPUSerial/kGanesh/kGraphite、CTS 强制等级以及对应平台的执行函数指针。测试通过sk_tools::RegistryTest全局注册表见 tests/Test.h完成注册因此测试文件只需要被编译进目标测试即可被发现无需额外手工登记名称。Test结构体按测试类型区分了三种执行入口实现在 tests/Test.cppcpu()执行普通 CPU 测试函数签名为void (*)(Reporter*)ganesh()执行需要 Ganesh GPU 上下文的测试签名带const GrContextOptionsgraphite()执行需要 Graphite 上下文的测试签名带const skiatest::graphite::TestOptions。也就是说同一个测试要跑在哪种后端在宏展开阶段就已经决定了运行时由测试运行器如dm负责分发。编写第一个测试在tests/目录新建一个.cpp文件包含tests/Test.h然后用DEF_TEST宏声明测试函数。下面是最小示例与原文档一致可直接复制编译#include tests/Test.h DEF_TEST(MyNewTest, reporter) { REPORTER_ASSERT(reporter, 1 1 2); if (false) { ERRORF(reporter, Something went wrong!); } }各要素说明DEF_TEST(name, reporter)定义并注册一个 CPU 测试。从 tests/Test.h 的宏定义可以看出它实际展开为声明static void test_MyNewTest(skiatest::Reporter*)通过TestRegistry将Test::MakeCPU(#name, test_MyNewTest)注册进全局注册表再给出函数体。#name会把宏参数字符串化因此测试名在运行时就是一个字符串。reporter指向skiatest::Reporter对象的指针用于上报成功、失败与附加消息。Reporter的接口定义在 tests/Test.h核心虚函数是reportFailed(const skiatest::Failure)它还维护了一个上下文栈配合push/pop可以把失败信息加上子测试等上下文。skiatest::Failure结构tests/Test.h记录了出错的文件名、行号、条件表达式字符串与格式化消息toString()在 tests/Test.cpp 中把三者拼成文件:行号 消息: 条件的格式这就是你在测试输出里看到的失败信息格式。编译前提DEF_TEST只声明为 CPU 测试。若测试文件里用到SkCanvas、SkBitmap等绘制 API还需要包含对应的 Skia 头文件若只是纯逻辑断言上面的最小示例已足够。断言宏REPORTER_ASSERT 与 ERRORF框架提供以下核心断言宏定义见 tests/Test.h宏说明REPORTER_ASSERT(reporter, cond)基础断言条件不成立时上报失败失败信息中自动带上条件的源码字符串REPORTER_ASSERT(reporter, cond, fmt, ...)带 printf 风格格式化消息的断言失败时附加自定义说明ERRORF(reporter, msg, ...)无条件上报一条 printf 风格错误例如你检测到某个异常分支时主动调用INFOF(reporter, ...)仅在reporter-verbose()为真时打印调试信息通过SkDebugf输出适合做测试内日志注意REPORTER_ASSERT失败后不会终止测试而是记录失败并继续执行这便于在一次运行中收集尽可能多的失败点。若某步失败后继续执行没有意义请用ERRORF(...)后紧跟return;提前退出这也是 tests/Test.h 官方注释示例的推荐写法。从宏实现看REPORTER_ASSERT最终走REPORT_FAILURE它会调用reporter-reportFailedWithContext(...)见 tests/Test.cpp把Reporter上下文栈中的消息以[subtest, ...]后缀附加到失败信息后输出。控制台输出与失败格式失败信息最终经由skiatest::Failure::toString()格式化形如tests/MyNewTest.cpp:12 Something went wrong: x should be 15其中12是调用断言的文件行号x should be 15是格式化消息Something went wrong前面的条件字符串可能为空。从 CPU 到 GPUGanesh 与 Graphite 测试宏除了DEF_TEST框架为 GPU 相关测试提供了两组宏在 tests/Test.h 中成体系地定义DEF_GANESH_TEST用于需要 Ganesh GPU 上下文OpenGL / Vulkan / Metal / D3D / Dawn 等的测试DEF_GANESH_TEST(MyGaneshTest, reporter, options, CtsEnforcement::kNever) { // options 是 const GrContextOptions可读取 GPU 配置 REPORTER_ASSERT(reporter, options.fUseShaderSwizzler false); }签名DEF_GANESH_TEST(name, reporter, options, ctsEnforcement)其中options是const GrContextOptionsctsEnforcement是 CTS 强制等级见下文。变体宏DEF_GANESH_TEST_FOR_ALL_CONTEXTS跑遍所有编译进的后端、DEF_GANESH_TEST_FOR_GL_CONTEXT/DEF_GANESH_TEST_FOR_VULKAN_CONTEXT/DEF_GANESH_TEST_FOR_METAL_CONTEXT/DEF_GANESH_TEST_FOR_D3D_CONTEXT/DEF_GANESH_TEST_FOR_DAWN_CONTEXT限定特定后端、DEF_GANESH_TEST_FOR_MOCK_CONTEXTmock 上下文、DEF_GANESH_TEST_FOR_RENDERING_CONTEXTS仅渲染型上下文。这些变体通过 tests/Test.h 声明的IsGLContextType、IsVulkanContextType等过滤器把测试分发给匹配的后端执行。DEF_GRAPHITE_TEST用于需要 Graphite 渲染器上下文的测试DEF_GRAPHITE_TEST(MyGraphiteTest, reporter, CtsEnforcement::kNever) { // 执行普通 CPU 测试逻辑但被标记为 Graphite 测试 REPORTER_ASSERT(reporter, true); }签名DEF_GRAPHITE_TEST(name, reporter, ctsEnforcement)。从 tests/Test.h 的实现看它内部仍然调用一个纯Reporter*签名的函数体但注册为TestType::kGraphite运行时只在 Graphite 上下文存在时执行。需要真正访问 Graphite 上下文的测试应使用DEF_GRAPHITE_TEST_FOR_CONTEXTS/DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS/DEF_GRAPHITE_TEST_FOR_VULKAN_CONTEXT/DEF_GRAPHITE_TEST_FOR_METAL_CONTEXT/DEF_GRAPHITE_TEST_FOR_DAWN_CONTEXT等变体这些宏会通过skiatest::graphite::RunWithGraphiteTestContexts把测试分发到各后端。ctsEnforcement 参数是什么DEF_GANESH_TEST与DEF_GRAPHITE_TEST都要求传入一个CtsEnforcement等级定义在 tests/CtsEnforcement.h。它决定测试在 Android CTS/SkQP 中的执行策略CtsEnforcement::kNever与 CTS 无关永远不被强制普通单元测试通常用它CtsEnforcement::kNextRelease新测试如果稳定且准备在下个 Android 版本被强制应使用该占位值CtsEnforcement::kApiLevel_*对应 Android vendor API 级别如kApiLevel_T 33CtsEnforcement::kToBeDetermined不稳定或进行中的测试使用表示还未决定是否强制。eval(apiLevel)会在三种运行模式间切换跳过、带 workaround 运行、严格运行。对于不关心 CTS 的本地测试直接传CtsEnforcement::kNever即可。其他常用变体DEF_TEST_DISABLED(name, reporter)注册但默认不运行condition false适合临时禁用某个测试tests/Test.h。DEF_SERIAL_TEST(name, reporter)注册为kCPUSerial类型只串行执行适合对线程安全有特殊要求或共享全局状态的测试tests/Test.h。UNIX_ONLY_TEST在 Unix 构建下等价于DEF_TEST其他平台等价于DEF_TEST_DISABLEDtests/Test.h。把新测试加入构建编辑 gn/tests.gnitests/下的测试文件并不会被自动编译必须把新文件登记进 gn/tests.gni 的tests_sources列表。该列表从 gn/tests.gni 开始是一长串以$_tests/为前缀的绝对路径例如tests_sources [ $_tests/AAClipTest.cpp, $_tests/AndroidCodecTest.cpp, ... $_tests/MyNewTest.cpp, # 新增这一行 ... ]其中_tests在文件顶部通过get_path_info(../tests, abspath)解析为tests/目录的绝对路径见 gn/tests.gni因此新增行只需写$_tests/MyNewTest.cpp,同类列表还包含graphite_tests_sourcesgn/tests.gniGraphite 相关测试ganesh_tests_sourcesgn/tests.gniGanesh GPU 测试ganesh_gl_tests_sources/ganesh_metal_tests_sourcesgn/tests.gni特定后端的 GPU 测试pathops_tests_sourcesgn/tests.gnipathops 算法测试。如果只用到DEF_TEST加入tests_sources即可使用了DEF_GANESH_TEST/DEF_GRAPHITE_TEST的测试视具体宏可能还需确认对应后端列表GN 构建会自动按SK_GANESH/SK_GRAPHITE宏条件引入。若项目同时使用 Bazel 构建还需要把新文件加入 BUILD.bazel 中对应的skia_test目标源列表。用 dm 运行测试Skia 的单元测试统一由正确性测试工具dm执行其入口在 dm/DM.cpp测试运行逻辑见 dm/DMGpuTestProcs.cpp。先构建出dm可执行文件假设构建输出目录为out/Debug然后运行out/Debug/dm --src tests --match MyNewTest参数含义--src tests只运行 C 单元测试源dm还支持gm、skp、image等其他源类型--match MyNewTest按名字过滤只跑匹配的测试。常用过滤与运行参数dm的--match支持丰富的匹配语法详见 agents/skills/skia-gn-workflow/references/dm_usage.md语法效果substring匹配名字中包含该子串的测试~exclude排除匹配名字的测试^start匹配以该前缀开头的测试$end匹配以该后缀结尾的测试^exact$精确匹配其他实用参数--src tests --match MyNewTest单跑某个测试最常用--src tests --match ^Text跑所有名字以Text开头的测试--src tests --match ~Slow排除名字含Slow的测试--threads n或-j设置工作线程数默认等于 CPU 核数单测调试时可设为1--nogpu跳过所有 GPU 相关测试GPU 测试在无图形环境下会失败或跳过-v/--verbose输出更详细的日志-i dir/--resourcePath dir测试资源目录若测试加载资源失败先检查该路径是否指向正确的resources目录--config names指定要运行的配置如8888、gl、vkGPU 测试可用它限定后端。例如运行所有 Ganesh GPU 测试可以用out/Debug/dm --src tests --match Ganesh --config gl vkdm对不支持的 GPU 后端会自动跳过因此跨平台 CI 上常见做法是不指定--config让 dm 遍历所有编译进的后端。关于构建输出目录上面示例假设已经用 GN 生成了out/Debug构建目录并构建出dm。Skia 的 GN 工作流生成、配置、编译可参考 agents/skills/skia-gn-workflow/SKILL.mddm目标名即为dm构建命令形如bin/gn gen out/Debug ninja -C out/Debug dm完整示例一个带上下文的 GPU 测试把以上要素串起来一个同时覆盖 CPU 与 GPU 的完整示例文件tests/MyNewTest.cpp可以这样写#include tests/Test.h // 纯 CPU 测试验证一个简单不变量 DEF_TEST(MyNewTest, reporter) { REPORTER_ASSERT(reporter, 1 1 2); REPORTER_ASSERT(reporter, 2 * 2 4, multiplication works); } // 需要 Ganesh GPU 上下文的测试在所有编译进的 GPU 后端起效 DEF_GANESH_TEST_FOR_ALL_CONTEXTS(MyNewGaneshTest, reporter, context_info, CtsEnforcement::kNever) { // context_info 是 const sk_gpu_test::ContextInfo可用于获取 GPU 上下文 REPORTER_ASSERT(reporter, context_info.directContext() ! nullptr); } // 需要 Graphite 上下文的测试 DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(MyNewGraphiteTest, reporter, graphite_ctx, CtsEnforcement::kNever) { REPORTER_ASSERT(reporter, graphite_ctx ! nullptr); }对应地把该文件加入 gn/tests.gni 的tests_sources重新生成构建文件并编译# 编辑 gn/tests.gni追加 $_tests/MyNewTest.cpp bin/gn gen out/Debug ninja -C out/Debug dm out/Debug/dm --src tests --match MyNew运行后你会看到MyNewTest、MyNewGaneshTest、MyNewGraphiteTest三个测试的结果如果当前构建没有编译 GPU 后端后两个会被自动跳过。小结单元测试放在 tests/ 目录用 tests/Test.h 中的宏声明CPU 测试用DEF_TESTGPU 测试按后端选DEF_GANESH_TEST/DEF_GRAPHITE_TEST及其_FOR_*_CONTEXT变体。断言用REPORTER_ASSERT/ERRORF失败不会中断测试必要时手动return。新测试文件必须登记进 gn/tests.gni 的tests_sources才会被编译。用out/Debug/dm --src tests --match pattern运行与过滤测试--match支持子串、排除、前缀、后缀、精确匹配五种模式。关心 CTS 强制的测试需要正确选择CtsEnforcement等级参考 tests/CtsEnforcement.h。赞分享图形学【免费下载链接】skiaSkia is a complete 2D graphic library for drawing Text, Geometries, and Images. See documentation for contribution instructions.项目地址https://gitcode.com/gh_mirrors/ski/skia点击查看免费下载相关推荐ParlAI 测试实战指南从 pytest 运行到 TorchGeneratorAgent 单元测试编写ParlAI 测试实战指南从 pytest 运行到 TorchGeneratorAgent 单元测试编写 ParlAI 是面向开放对话数据集训练与评估 AINLP人工智能深度学习Lynx 单元测试实战指南从 C/Android/iOS 测试编写到 RTF 自动化运行Lynx 单元测试实战指南从 C/Android/iOS 测试编写到 RTF 自动化运行 Lynx 是面向跨端场景的高性能渲染引擎其测试体系贯穿 C跨平台移动开发前端桌面应用Skia 测试工具集成 ANGLE从 GN 构建配置到 --config 命令行的完整指南Skia 测试工具集成 ANGLE从 GN 构建配置到 config 命令行的完整指南 本文档对应 Skia 仓库中的 site/docs/user/spec图形学创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

STM32H743+LAN8720以太网移植:LWIP配置与Ping不通排查指南

STM32H743+LAN8720以太网移植:LWIP配置与Ping不通排查指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 2:42:06 阅读更多 →
工业以太网IO模块Modbus TCP对接实战:从协议原理到EAP系统集成

工业以太网IO模块Modbus TCP对接实战:从协议原理到EAP系统集成

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 2:41:05 阅读更多 →
自适应啸叫抑制算法原理与MATLAB实现:从反馈抵消到陷波器组

自适应啸叫抑制算法原理与MATLAB实现:从反馈抵消到陷波器组

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 2:41:05 阅读更多 →

最新新闻

OpenCodex 独立 Images 数据面:Codex 图像生成/编辑代理通道的修复与验证

OpenCodex 独立 Images 数据面:Codex 图像生成/编辑代理通道的修复与验证

【免费下载链接】opencodex Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code 项目地址: https://gitcode.com/gh_mirrors/ope/opencodex 点击…

2026/9/24 3:22:31 阅读更多 →
Palantir本体存储架构:从选型到落地的完整指南

Palantir本体存储架构:从选型到落地的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 3:22:31 阅读更多 →
【MySQL】类型合法的脏数据谁来拦?表约束上篇:非空、默认值、zerofill 与主键

【MySQL】类型合法的脏数据谁来拦?表约束上篇:非空、默认值、zerofill 与主键

5.MySQL表的约束(上) 文章目录5.MySQL表的约束(上)一、为什么需要约束约束是什么:把"写数据"从自由变成有规则约束总览二、空属性约束:null 与 not null三、默认值约束:defaultdefaul…

2026/9/24 3:22:31 阅读更多 →
Unity UGUI中的Canvas重建机制

Unity UGUI中的Canvas重建机制

在 Unity UGUI 中,Canvas 是整个 UI 系统的核心。很多 UI 性能问题,例如界面频繁卡顿、批次突然增加、Canvas.BuildBatch 占用 CPU、UI 动画导致大量 CPU 消耗,本质上都可能与 Canvas 的重建机制有关。 很多开发者知道修改 UI 属性会触发 Canvas 重建,但真正的问题是:什么…

2026/9/24 3:22:31 阅读更多 →
【2026年华为杯B题】​ 氢燃料电池低温冷启动建模与控制策略研究(思路、代码、论文,持续更新)

【2026年华为杯B题】​ 氢燃料电池低温冷启动建模与控制策略研究(思路、代码、论文,持续更新)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 &#x1f381…

2026/9/24 3:21:30 阅读更多 →
智慧看守所监管系统解析:AI联动与子系统集成实战

智慧看守所监管系统解析:AI联动与子系统集成实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 3:21:30 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →