Ladybird 测试体系实战指南:从 test-web 四类测试到 Sanitizer 与 WPT 全流程
发布时间:2026/9/6 20:43:38 作者:尧图编辑部 阅读量:1,286

Ladybird 测试体系实战指南从 test-web 四类测试到 Sanitizer 与 WPT 全流程【免费下载链接】ladybirdTruly independent web browser项目地址: https://gitcode.com/GitHub_Trending/la/ladybird本篇指南基于 Ladybird 仓库的官方测试文档 Documentation/Testing.md 展开并结合 Tests/LibWeb/test-web 测试运行器、Meta/ladybird.py 与 CMakePresets.json 的源码实现进行纵深讲解。读完后你将能够独立跑通 Ladybird 的全部单测与 LibWeb 网页测试复现 CI 的 Sanitizer 构建与失败场景编写 Text / Layout / Ref / Screenshot 四类 LibWeb 测试并正确完成 rebaseline以及用 Meta/WPT.sh 运行、对比与导入 Web Platform Tests。测试体系总览Tests/ 目录与每库一目录Ladybird 的测试统一放在 Tests/ 目录下每个被测试的库对应一个子目录例如 Tests/AK 测试 AK 基础库Tests/LibCore 测试 LibCoreTests/LibJS 测试 JavaScript 引擎含 1400 多个 JS 测试文件Tests/LibWeb 则是网页引擎测试的核心阵地。文档明确要求为 LibWeb 新增的每个特性或 bug 修复都应在Tests/LibWeb中配套一个测试并按特性选择 Text、Layout、Ref 或 Screenshot 四种类型之一而针对内部 C 代码的测试则以独立的TestFoo.cpp文件形式放在Tests/LibWeb下。这一点可以从源码结构直接得到印证——Tests/LibWeb 目录中既有Text/、Layout/、Ref/、Screenshot/四个按类型划分的测试根目录也有一批 C 单元测试文件如 TestHTMLTokenizer.cpp、TestCSSPixels.cpp、TestContentBlocker.cpp、TestStructuredSerializeCorpus.cpp 等。运行测试的三种方式提示若要复现 CI 上的失败应参考后文的 Sanitizer 构建运行 一节。文档给出的第一种也是最简单的方式是使用仓库自带的 Meta/ladybird.py 脚本。它的test子命令先执行 configure 与 build再运行测试。从源码看Meta/ladybird.py 的test_main函数该脚本实际做的事情就是调用 ctesttest_args [ ctest, --preset, preset, --output-on-failure, --test-dir, str(build_dir), ] if pattern: test_args.extend([-R, pattern])即Meta/ladybird.py test运行所有测试Meta/ladybird.py test LibWeb通过 ctest 的-R正则过滤只跑名称匹配LibWeb的测试。preset 默认取环境变量BUILD_PRESET否则为Release构建目录随之映射到Build/release见 Meta/ladybird.py 的known_presets表Debug →Build/debug、Sanitizer →Build/sanitizers等。LibWeb 网页测试是如何注册为 CMake 测试的在 Tests/LibWeb/test-web/CMakeLists.txt 中可以看到test-web可执行文件被注册为名为LibWeb的 ctest 测试并固定附带--python-executable与--per-test-timeout 120 -v -v参数if (BUILD_TESTING) add_test( NAME LibWeb COMMAND $TARGET_FILE:test-web --python-executable ${Python3_EXECUTABLE} --per-test-timeout 120 -v -v ) ... set_tests_properties(LibWeb PROPERTIES ENVIRONMENT LADYBIRD_SOURCE_DIR${LADYBIRD_SOURCE_DIR} TIMEOUT_SIGNAL_NAME SIGTERM)第二方式是直接调用test-web测试运行器绕过 CTest 一层Meta/ladybird.py run test-web从 Tests/LibWeb/test-web/Application.cpp 的参数定义中可以确认test-web支持的完整命令行参数比文档描述更加丰富参数短选项作用--test-path path指定测试根目录默认由LADYBIRD_SOURCE_DIR推导为Tests/LibWeb--results-dir path-R测试结果的输出目录--test-concurrency jobs-j并发测试数默认取 CPU 硬件并发数--filter glob-f只跑匹配 glob 的测试如-f Text/input/your-test.html--rebaseline重新生成被执行的 Layout/Text 测试的期望文件--per-test-timeout sec-t单测试超时秒数默认 30--fail-fast首个失败/超时/崩溃即中止超时时可挂接调试器--dry-run仅列出将要运行的测试--repeat n将匹配的测试重复运行 N 次--shuffle-s打乱测试执行顺序--verbose-v可叠加使用提升日志详细度--dump-gc-graph-G输出 GC 图会自动强制串行执行第三方式是直接调用ctest最简单的是复用 CMakePresets.json 中的Release预设cmake --preset Release cmake --build --preset Release ctest --preset ReleaseLADYBIRD_SOURCE_DIR 环境变量部分测试要求LADYBIRD_SOURCE_DIR指向 Ladybird 源码树根目录。手动构建时需要自行设置# /path/to/ladybird repository export LADYBIRD_SOURCE_DIR${PWD}这里有两处源码佐证可以说明该变量的重要性Meta/ladybird.py 的ensure_ladybird_source_dir会在未设置时通过git rev-parse --show-toplevel自动推导并写入该环境变量Tests/LibWeb/test-web/Application.cpp 中test-web启动时若检测到LADYBIRD_SOURCE_DIR会将测试根路径设为该变量/Tests/LibWebCMakePresets.json 的root_base测试预设也通过LADYBIRD_SOURCE_DIR: ${fileDir}自动注入该变量因此使用ctest --preset时无需手动 export。使用 ninja 与失败输出构建完成后也可以直接用 ninja 驱动测试cd Build/release ninja ninja test查看失败测试的 stdout/stderr 时推荐设置CTEST_OUTPUT_ON_FAILURE环境变量为 1CTEST_OUTPUT_ON_FAILURE1 ninja test # 或者直接使用 ctest... ctest --output-on-failure结果产物如何读懂一次 test-web 运行从 Tests/LibWeb/test-web/main.cpp 的实现看一次运行结束后会在结果目录--results-dir指定中生成一份可浏览的 HTML 报告results.js 由源码树 Tests/LibWeb/test-web/results-index.html 模板复制出的index.html其中记录了 total/fail/timeout/crashed/skipped 汇总与每个未通过测试的模式Text/Layout/Ref/Screenshot/Crash、是否存在日志、Ref 与 Screenshot 测试的pixelErrors与maxChannelDiff像素差异统计。每个失败测试还会落盘.expected.txt/.actual.txt/.diff.txt/.diff.html文本类差异或.actual.png/.expected.png/.diff.png像素类差异差异像素标红。运行期间则持续写出harness-status.txt供排查挂起的 harness。这些细节对定位为什么挂非常有用。另一个值得一提的配置是 Tests/LibWeb/TestConfig.ini。test-web会解析其中的两个分组见 main.cpp 中 load_test_config[LoadFromHttpServer]列出必须经由内置 HTTP echo server 加载的测试原因包括 cookie 行为、跨源 Worker fetch、pushState需要 HTTP(s) scheme、Service Worker Cache API 仅对 HTTP(S) URL 生效等[Skipped]当前被禁用的测试清单每条通常附带原因注释flaky、CI 超时、尚未实现的功能等是理解当前已知问题的活文档。使用 Sanitizer 运行测试复现 CI 失败CI 以 Address SanitizerASan与 Undefined Behavior SanitizerUBSan插桩运行 host 测试。这两类工具能够捕获大量常见 C 错误包括内存泄漏、堆栈越界访问、有符号整数溢出等。Sanitizer 构建会显著变慢并且会使ccache之类的缓存失效。最简单的启用方式是使用Sanitizer预设cmake --preset Sanitizer cmake --build --preset Sanitizer ctest --preset Sanitizer若不想走预设而手动开启则使用-DENABLE_FOO_SANITIZER系列开关。为保证行为与 CI 一致需要按文档设置ASAN_OPTIONS与UBSAN_OPTIONSSanitizer测试预设已在 CMakePresets.json 中内置了同样的值export ASAN_OPTIONSstrict_string_checks1:check_initialization_order1:strict_init_order1:detect_stack_use_after_return1:allocator_may_return_null1 export UBSAN_OPTIONSprint_stacktrace1:print_summary1:halt_on_error1 cmake -GNinja -B Build/lagom -DENABLE_ADDRESS_SANITIZERON -DENABLE_UNDEFINED_SANITIZERON cd Build/lagom ninja CTEST_OUTPUT_ON_FAILURE1 LADYBIRD_SOURCE_DIR${PWD}/../.. ninja test这些选项的含义值得留意ASan 的strict_string_checks开启字符串函数边界检查check_initialization_order/strict_init_order用于揪出静态初始化顺序问题detect_stack_use_after_return检测栈上 use-after-returnUBSan 的print_stacktrace1在报错时打印调用栈、halt_on_error1让首个未定义行为即终止进程避免错误级联掩盖根因。另外从 Meta/ladybird.py 可以看到ladybird.py run在--preset Sanitizer时也会自动注入同一组ASAN_OPTIONS/UBSAN_OPTIONS默认值——这保证了日常本地运行与 CI 的插桩口径一致。运行与导入 Web Platform TestsWeb Platform TestsWPT是衡量浏览器规范符合度的行业标尺Ladybird 通过 Meta/WPT.sh 驱动 wpt 工具链运行该脚本还支持对比两次运行的结果。基本用法run 与 compare# 先跑一遍 WPT 并落日志随后切到你的 CSS 改动分支再跑一次并与基线对比 ./Meta/WPT.sh run --log expectations.log css git checkout my-css-change ./Meta/WPT.sh compare --log results.log expectations.log css# 从上游 WPT 仓库拉取最新测试 ./Meta/WPT.sh update # 运行全部 WPT 测试结果写入 results.log ./Meta/WPT.sh run --log results.log从脚本源码Meta/WPT.sh可以看到它支持的完整子命令update更新 WPT 仓库、run运行、compare与 LOG_FILE 中的期望对比、import把指定 wpt.live 路径的测试抓下来生成 in-tree 测试与期望文件、list-tests、clean、bisect BAD_COMMIT GOOD_COMMIT二分定位首次产生意外结果的提交。脚本默认构建test-web二进制Meta/WPT.sh 中调用./Meta/ladybird.py build test-web通过 WebDriver 驱动浏览器覆盖testharness、reftest、wdspec、crashtest、test262五类测试类型。导入 WPT 测试到本地仓库当你改动的代码让 Ladybird 新通过了某个尚未被导入的 WPT 测试时应把它导入仓库把通过状态固化下来。文档给出的方式./Meta/WPT.sh import html/dom/aria-attribute-reflection.html即把http://wpt.live/URL 的路径部分交给import子命令。脚本会同时下载该测试及其引用的 JavaScript 脚本拷贝到Tests/LibWeb/test-type/input/wpt-import目录运行该测试然后在Tests/LibWeb/test-type/expected/wpt-import目录中生成期望结果文件。这一点在 Tests/LibWeb/TestConfig.ini 中随处可见wpt-import/前缀的条目正是导入机制的产物。编写新测试用脚本生成测试骨架仓库提供了 Python 脚手架 Tests/LibWeb/add_libweb_test.py./Tests/LibWeb/add_libweb_test.py your-new-test-name test_type文档说明接受的test_type取值为 Text、Layout、Ref 与 Screenshot从脚本源码看choices实际还支持 Crash 一类用于验证特定输入不会导致崩溃且提供--async开关为 Text 测试生成异步骨架。脚本会在Tests/LibWeb/test_type/input下创建your-new-test-name.html输入文件在Tests/LibWeb/test_type/expected下创建对应的期望文件——Text/Layout 生成.txtScreenshot 生成.png留空待生成Ref 生成test_name-ref.html参考页Text 测试骨架引用include.js并预填test(() { println(Expected println() output); })--async时预填asyncTest(async (done) { ... done(); })Ref 骨架则自动写入link relmatch href../expected/name-ref.html /标签。生成后把模板替换为你的真实测试内容并重新生成期望文件# Text / Layoutrebaseline 重新落盘期望文件 ./Meta/ladybird.py run test-web --rebaseline -f Text/input/your-new-test-name.htmlRef 与 Screenshot 测试需要手工提供等效渲染的参考内容不过 Screenshot 测试的参考图可以用无头模式浏览器直接生成./Meta/ladybird.py run ladybird --headless --test-mode Tests/LibWeb/Screenshot/input/your-new-test-name.html # 日志会输出类似Saved screenshot to: ~/Downloads/screenshot-2025-06-07-08-37-45.png mv ~/Downloads/screenshot-2025-06-07-08-37-45.png Tests/LibWeb/Screenshot/images/your-new-test-name.png--rebaseline的实际行为可在源码中印证main.cpp 的 handle_completed_test 在rebaseline模式下会把实际输出直接写回期望文件并返回 PassScreenshot 的 rebaseline 分支 则会把实际截图写为 PNG并尝试调用系统的optipng -strip all压缩图片调用失败仅告警不影响流程。四类测试逐一解析Text 测试验证无视觉表现的 Web APIText 测试面向没有视觉表现的 Web API用 JavaScript 编写并在无头浏览器中运行。每个测试在 script 标签中有一个测试函数来驱动 API并用println输出期望结果所有println调用被累积成输出文本再由测试运行器与期望输出文件逐字节忽略尾部换行比较。Text 测试可以是同步或异步的。异步测试应使用done回调信号完成——异步并不意味着一定运行在 async 上下文中只是要求测试函数在结束时主动汇报若测试 API 本身需要异步上下文传给test的 lambda 可以直接写成 async。从实现层面main.cpp 中 Text 模式的处理可以补充两条细节test-web通过view.request_internal_page_info(WebView::PageInfoType::Text)收集页面内累积的输出文本测试完成由页面端调用internals.signalTestIsDone(PASS)上报该注入脚本位于 main.cpp页面加载完成与测试完成两个信号都到位后测试才算结束单测试超时会由--per-test-timeout驱动的定时器触发Timeout结果。Layout 测试比对布局树Layout 测试将页面的布局树与期望布局树做文本比对最适合测试布局代码也常用于验证其他对布局有可观测影响的功能。它不需要任何 JavaScript——页面加载完成后运行器会自动 dump 布局树。源码中还有一个容易忽略的巧妙设计run_dump_test 中 Layout 分支dump 布局树前会先对页面截屏注释说明这是为了强制触发SVG as image文档的惰性布局同时让更多代码路径跑起来以暴露 bug。布局树的期望文件由--rebaseline生成脚手架生成的 Layout 期望文件内容本身就是一句run ./Meta/ladybird.py run test-web --rebaseline ...的提示。Ref 测试与参考页截图像素级对比参考ref测试把测试页的截图与参考页的截图对比两者完全一致才算通过。它们适合测试背景图、阴影这类视觉效果如果难以构造等效参考页例如 SVG 或 canvas 场景文档建议改用 Screenshot 测试。每个 Ref 测试都包含一个特殊的标签来指定参考页link relmatch href../expected/my-test-ref.html /测试运行器据此定位参考页从而允许多个测试共享同一参考页。实现上run_ref_test页面加载后test-web注入一段等待脚本监听reftest-waitclass 移除并等待document.fonts.ready与两帧requestAnimationFrame完成参照 WPT reftest 的等待规范确认渲染稳定后先截测试页、再通过internals.loadReferenceTestMetadata()读取 match/mismatch 引用与 fuzzy 配置逐条加载参考页截图比对失败时输出 actual/expected/diff 三张 PNG。Screenshot 测试与参考 PNG 对比Screenshot 测试可视为 Ref 测试的子类型其参考页是一个指向期望输出截图的img标签。文档建议能用普通 Ref 测试就避免使用 Screenshot 测试因为它们对细微的渲染差异敏感且无法在所有平台上工作。与 Ref 一样它需要link relmatch href...标签在 Screenshot 模式下该标签指向承载img的参考 HTML。结合 main.cpp 中 Screenshot 分支期望 PNG 以Gfx::ImageDecoder解码后与实际截图做 fuzzy 匹配支持页面内声明的 fuzzy 配置允许局部区域的像素容差失败时同样落盘 diff 图并统计pixel_error_count与最大通道差这些数据会汇总进 HTML 报告供人工判断差异性质。相关文档与延伸阅读构建与环境准备Documentation/BuildInstructionsLadybird.mdCMake ≥ 3.30依赖经 vcpkg 管理见 Meta/ladybird.py 的版本校验样式引擎测试Documentation/Style/StyleEngineTesting.md不稳定测试检测工具Meta/check-test-flakiness.py基于test-web --dry-run统计重复运行的结果波动WPT 进度观察脚本Meta/watch_wpt_progress.sh小结Ladybird 的测试体系可以归纳为三层Tests/库/下按库组织的单元测试CTest 直接驱动、test-web驱动的四类网页级测试Text/Layout/Ref/Screenshot Crash以及基于 wpt 工具链的 WPT 规范符合度测试。三条运行入口——Meta/ladybird.py test [pattern]、ctest --preset Release|Debug|Sanitizer、ninja test——殊途同归到 ctest 注册用例Sanitizer 预设通过内置的ASAN_OPTIONS/UBSAN_OPTIONS保证本地与 CI 口径一致新测试则用add_libweb_test.py生成骨架、--rebaseline固化期望、WPT.sh import把新通过的 WPT 测试收编进仓库。掌握这条链路后无论是修 bug 还是加特性都能把改动 → 测试 → 期望固化变成确定性的闭环。【免费下载链接】ladybirdTruly independent web browser项目地址: https://gitcode.com/GitHub_Trending/la/ladybird创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考