跳到内容

测试

深入了解 WebKit 的测试。

WebKit 中的正确性测试

WebKit 非常重视测试驱动开发,我们有许多类型的测试。

  • JavaScript 测试 - 位于顶层 JSTests 目录。这是测试 JavaScriptCore 的主要方法。使用 Tools/Scripts/run-javascriptcore-tests 来运行这些测试。
  • 布局测试 - 位于顶层 LayoutTests 目录。这是测试 WebCore 的主要方法。如果您正在修改 WebCore 代码,通常会运行这些测试。使用 Tools/Scripts/run-webkit-tests 来运行它们。传递 -1 以使用 WebKitLegacy (又名 WebKit1) 运行测试。WebKitTestRunner 用于运行 WebKit2 的这些测试,而 DumpRenderTree 用于运行 WebKit1 的这些测试。布局测试有几种样式,但它们都有一个测试文件和预期结果(以 -expected.txt 结尾),如果测试文件的输出与预期结果匹配,则测试通过。
  • API 测试 - 位于 Tools/TestWebKitAPI,这些是 GTests,用于测试 JavaScriptCore、WebKitLegacy 和 WebKit 层公开的 API,以及针对选定 WTF 类的单元测试。WebKit 不使用 XCTests。使用 Tools/Scripts/run-api-tests 来运行这些测试。由于这些 API 测试是顺序执行的,因此在可能的情况下最好编写布局测试。
  • 绑定测试 - 位于 Source/WebCore/bindings/scripts/test,这些是 WebCore 绑定代码生成器的测试。使用 Tools/Scripts/run-bindings-tests 来运行这些测试。
  • webkitpy 测试 - Tools/Scripts/webkitpy 中 WebKit 各种 Python 脚本的测试。使用 Tools/Scripts/test-webkitpy 来运行这些测试。
  • webkitperl 测试 - Tools/Scripts/webkitperl 中 WebKit 各种 Perl 脚本的测试。使用 Tools/Scripts/test-webkitperl 来运行这些测试。

WebKit 中的性能测试

WebKit 项目有一项“无性能退步”政策。我们维护以下基准测试的性能,这些基准测试位于 PerformanceTests 目录下。如果您的补丁导致其中一项基准测试的性能出现哪怕是轻微的(小于 1%)退步,它将被回滚。

  • JetStream2 - 衡量 JavaScript 和 WASM 性能。
  • MotionMark - 衡量图形性能。
  • Speedometer 3 - 衡量 WebKit 在复杂 Web 应用中的性能。

以下是 Apple WebKit 团队维护的基准测试,但由于 Apple 没有重新分发内容的权利,因此不向其他开源贡献者提供。如果您的 WebKit 补丁导致这些测试中的一项性能退步,您的补丁仍然可能被回滚。

  • RAMification - Apple 的内部 JavaScript 内存基准测试。
  • ScrollPerf - Apple 的内部滚动性能测试。
  • PLT - Apple 的内部页面加载时间测试。
  • Membuster / PLUM - Apple 的内部内存测试。Membuster 用于 macOS,PLUM 用于 iOS 和 iPadOS。

布局测试:为 Web 而生的 Web 测试

布局测试是使用 HTML、CSS 和 JavaScript 等 Web 技术编写的 WebKit 测试,它是测试 WebCore 的主要机制。在修改 WebCore 代码和将补丁上传到 bugs.webkit.org 之前,应运行相关的布局测试。虽然 bugs.webkit.org 的预警系统将在一组配置上构建并运行测试,但最终补丁作者需对他们的补丁导致的任何测试失败负责。

测试文件和预期文件

目录结构

LayoutTests 目录按测试类别组织。例如,LayoutTests/accessibility 包含与可访问性相关的测试,而 LayoutTests/fast/dom/HTMLAnchorElement 包含针对 HTML 锚点元素的测试。

任何以 .html.htm.shtml.xhtml.mht.xht.xml.svg.php 结尾的文件都被视为测试文件,除非其名称前缀为 -ref-notref-expected-expected-mismatch(这些用于参考测试;稍后解释)。它会附带一个同名但以 -expected.txt-expected.png 结尾的文件。这些被称为 预期结果,构成了给定测试的基准输出。当布局测试运行时,测试运行器会生成一个纯文本文件和/或 PNG 图像形式的输出,并将其与这些预期结果进行比较。

如果预期结果可能因平台而异,则每个测试的预期结果存储在 LayoutTests/platform 中。给定测试的预期结果存在于 LayoutTests/platform 的每个子目录中的相应目录中。例如,LayoutTests/svg/W3C-SVG-1.1/animate-elem-46-t.svg 在 macOS Mojave 上的预期结果位于 LayoutTests/platform/mac-mojave/svg/W3C-SVG-1.1/animate-elem-46-t-expected.txt

这些平台目录具有回退顺序。例如,在 macOS Catalina 上为 WebKit2 运行测试将使用以下回退路径(从最具体到最通用)

  • platform/mac-catalina-wk2 - WebKit2 在 macOS Catalina 上的结果。
  • platform/mac-catalina - WebKit2 和 WebKitLegacy 在 macOS Catalina 上的结果。
  • platform/mac-wk2 - WebKit2 在所有 macOS 上的结果。
  • platform/mac - 所有 macOS 上的结果。
  • platform/wk2 - WebKit2 在所有操作系统上的结果。
  • generic - 测试文件旁边。

导入的测试

测试下 LayoutTests/imported 是从其他仓库导入的。除非首先在相应的仓库中进行更改,否则不应通过 WebKit 补丁修改它们

最值得注意的是 Web 平台测试,它们导入到 LayoutTests/imported/w3c/web-platform-tests 下。这些是 W3C 开发的跨浏览器供应商测试。Mozilla、Google 和 Apple 都为这个共享测试仓库贡献了许多测试。

HTTP 测试

要打开 LayoutTests/httpLayoutTests/imported/w3c/web-platform-tests 下的测试,请使用 Tools/Scripts/open-layout-test 并提供测试路径。

您也可以使用 Tools/Scripts/run-webkit-httpd 手动启动 HTTP 服务器。要停止 HTTP 服务器,请退出脚本(例如,在 macOS 上按 Control + C)。

LayoutTests/http 下的测试可通过 http://127.0.0.1:8000 访问,但 LayoutTests/http/wpt 中的测试除外,这些测试可通过 http://localhost:8800/WebKit/ 访问。

导入到 LayoutTests/imported/w3c/web-platform-tests 下的 Web 平台测试可通过 HTTP 在 http://localhost:8800/ 访问,并通过 HTTPS 在 http://localhost:9443/ 访问。

请注意, verbatim 使用上述精确的主机名,例如 127.0.0.1localhost 非常重要,因为某些测试依赖于或测试基于这些主机名的同源或跨源行为。

测试预期

FIXME:解释测试预期如何工作。

运行布局测试

要运行布局测试,请使用 Tools/Scripts/run-webkit-tests。它可选择接受测试文件或目录的文件路径以及如何运行测试的选项。例如,要只运行 LayoutTests/fast/dom/Element/element-traversal.html,请执行:

Tools/Scripts/run-webkit-tests fast/dom/Element/element-traversal.html

由于 WebKit 中有超过 50,000 个测试,因此在开发代码更改时,您通常希望运行与您的代码更改相关的测试子集(例如,如果您正在处理 IndexedDB,则运行 LayoutTests/storage/indexeddb/),并在最后在本地机器上运行所有布局测试,或者依靠 bugs.webkit.org 上的预警系统进行更彻底的测试。

指定 --debug--release 以使用发布或调试版本。要使用 iOS 模拟器运行测试,您可以根据所需的模拟器指定 --ios-simulator--iphone-simulator--ipad-simulator

默认情况下,run-webkit-tests 将按照相对于 LayoutTests 目录的测试路径的字典顺序,运行您指定的所有测试一次,并重试任何失败的测试。如果您知道测试会失败并且不希望重试,请指定 --no-retry-failures

由于测试数量众多,run-webkit-tests 将并行运行不同目录中的测试(即,单个目录中的所有测试将依次顺序运行)。您可以使用 --child-processes 选项控制并行测试运行器的数量。

run-webkit-tests 有许多选项。使用 --help 列出所有支持的选项。

重复运行布局测试

当您调查不稳定的测试或崩溃时,可能需要调整此项。--iterations X 选项将指定测试列表运行的次数。例如,如果我们正在运行测试 A、B、C 并且指定了 --iterations 3run-webkit-tests 将运行:A、B、C、A、B、C、A、B、C。类似地,--repeat-each 选项将指定每个测试在进入下一个测试之前重复的次数。例如,如果我们正在运行测试 A、B、C 并且指定了 --repeat-each 3run-webkit-tests 将运行:A、A、A、B、B、B、C、C、C。--exit-after-n-failures 选项将指定在 run-webkit-tests 停止之前测试失败的总数。特别是,当调查不稳定失败时,--exit-after-n-failures=1 非常有用,这样当失败首次实际发生时,run-webkit-tests 就会停止。

测试结果

每当测试失败时,run-webkit-tests 会将结果存储在 WebKitBuild/Debug/layout-test-results 中,镜像 LayoutTests 的目录结构。例如,如果 LayoutTests/editing/inserting/typing-001.html 失败,其实际输出将出现在 WebKitBuild/Debug/layout-test-results/editing/inserting/typing-001-actual.txt 中。run-webkit-tests 还会在 WebKitBuild/Debug/layout-test-results/results.html 中生成一个包含结果摘要的网页,并自动尝试使用本地构建的 WebKit 在 Safari 中打开它。

如果 Safari 无法启动,请指定 --no-show-results 并手动打开 results.html 文件。

更新预期结果

如果您更新了测试内容或测试输出因您的代码更改而发生变化(例如,更多测试用例通过),则您可能需要更新测试附带的 -expected.txt 文件。为此,首先运行测试一次,以确保 diff 和新输出在 results.html 中有意义,然后再次使用 --reset-results 运行测试。这将更新匹配的 -expected.txt 文件。

您可能需要手动将新结果复制到 LayoutTests 下其他平台和配置的 -expected.txt 文件中。执行此操作时,请查找其他 -expected.txt 文件。

添加新测试时,run-webkit-tests 将自动为您的测试生成新的 -expected.txt 文件。您可以通过指定 --no-new-test-results 来禁用此功能,例如在测试仍在开发中时。

不同风格的布局测试

WebKit 中有多种风格的布局测试。

渲染树转储

这是最古老的布局测试风格,也是布局测试的默认模式。它是 WebKit 渲染树的文本序列化,其输出看起来像 这样

layer at (0,0) size 800x600
  RenderView at (0,0) size 800x600
layer at (0,0) size 800x600
  RenderBlock {HTML} at (0,0) size 800x600
    RenderBody {BODY} at (8,8) size 784x584
      RenderInline {A} at (0,0) size 238x18 [color=#0000EE]
        RenderInline {B} at (0,0) size 238x18
          RenderText {#text} at (0,0) size 238x18
            text run at (0,0) width 238: "the second copy should not be bold"
      RenderText {#text} at (237,0) size 5x18
        text run at (237,0) width 5: " "
      RenderText {#text} at (241,0) size 227x18
        text run at (241,0) width 227: "the second copy should not be bold"

这种风格的布局测试现在不被鼓励使用,因为其输出高度依赖于每个平台,最终导致每个平台都需要特定的预期结果。但它们在测试新的渲染和布局功能或其缺陷时仍然有用。

这些测试还附带 -expected.png 文件,但 run-webkit-tests 默认不检查 PNG 输出是否与预期结果匹配。要执行此检查,请传递 --pixel。不幸的是,许多 像素测试 将失败,因为我们在过去十年中大部分时间都没有更新预期 PNG 结果。但是,这些像素结果在诊断新的测试失败时可能很有用。因此,run-webkit-tests 在重试测试时会自动生成 PNG 结果,有效地为重试启用 --pixel 选项。

dumpAsText 测试

这些测试使用测试页面的纯文本序列化作为输出(就好像将整个页面的内容复制为纯文本一样)。所有这些测试都调用 testRunner.dumpAsText 来触发此行为。输出通常包含页面中生成的文本日志或其他信息性输出脚本。例如,LayoutTests/fast/dom/anchor-toString.html 如下所示:

<a href="http://localhost/sometestfile.html" id="anchor">
A link!
</a>
<br>
<br>
<script>
    {
        if (window.testRunner)
            testRunner.dumpAsText();

        var anchor = document.getElementById("anchor");
        document.write("Writing just the anchor object - " + anchor);

        var anchorString = String(anchor);
        document.write("<br><br>Writing the result of the String(anchor) - " + anchorString);

        var anchorToString = anchor.toString();
        document.write("<br><br>Writing the result of the anchor's toString() method - " + anchorToString);
    }
</script>

并生成以下 输出

A link! 

Writing just the anchor object - http://localhost/sometestfile.html

Writing the result of the String(anchor) - http://localhost/sometestfile.html

Writing the result of the anchor's toString() method - http://localhost/sometestfile.html

js-test.js 和 js-test-pre.js 测试

这些是 dumpAsText 测试的变体,使用 WebKit 的断言库:LayoutTests/resources/js-test.jsLayoutTests/resources/js-test-pre.js。它由 shouldX 函数调用组成,这些函数接受两个 JavaScript 代码片段,然后执行并比较它们的输出。js-test.js 只是 js-test-pre.js 的一个新变体,它不需要在末尾包含 LayoutTests/resources/js-test-post.js在新测试中使用 js-test.js,而不是 js-test-pre.js。

例如,LayoutTests/fast/dom/Comment/remove.html 用于测试 remove() 方法在 Comment 节点上的行为,其编写方式如下:

<!DOCTYPE html>
<script src="../../../resources/js-test-pre.js"></script>
<div id="test"></div>
<script>

description('This tests the DOM 4 remove method on a Comment.');

var testDiv = document.getElementById('test');
var comment = document.createComment('Comment');
testDiv.appendChild(comment);
shouldBe('testDiv.childNodes.length', '1');
comment.remove();
shouldBe('testDiv.childNodes.length', '0');
comment.remove();
shouldBe('testDiv.childNodes.length', '0');

</script>
<script src="../../../resources/js-test-post.js"></script>

附带以下 预期结果(输出):

This tests the DOM 4 remove method on a Comment.

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS testDiv.childNodes.length is 1
PASS testDiv.childNodes.length is 0
PASS testDiv.childNodes.length is 0
PASS successfullyParsed is true

TEST COMPLETE

description 函数指定此测试的描述,随后的 shouldBe 调用接受两个字符串,这两个字符串都作为 JavaScript 求值然后进行比较。

一些旧的 js-test-pre.js 测试可能会将其测试代码放在单独的 JS 文件中,但我们现在不再这样做,以便将所有测试代码集中在一处。

js-test.jsjs-test-pre.js 提供了各种其他断言和辅助函数。以下是一些示例:

  • debug(msg) - 在输出中插入调试/日志字符串。
  • evalAndLog(code) - 类似于 debug(),但将代码作为 JavaScript 求值。
  • shouldNotBe(a, b) - 如果 ab 的求值结果不同,则生成 PASS
  • shouldBeTrue(code) - shouldBe(code, 'true') 的简写。
  • shouldBeFalse(code) - shouldBe(code, 'false') 的简写。
  • shouldBeNaN(code) - shouldBe(code, 'NaN') 的简写。
  • shouldBeNull(code) - shouldBe(code, 'null') 的简写。
  • shouldBeZero(code) - shouldBe(code, '0') 的简写。
  • shouldBeEqualToString(code, string) - 类似于 shouldBe,但第二个参数不作为字符串求值。
  • finishJSTest() - 当 js-test.js 风格的测试需要执行异步工作时,定义名为 jsTestIsAsync 的全局变量并将其设置为 true。当测试完成时,调用此函数通知测试运行器(不要直接调用后面提到的 testRunner.notifyDone)。请参阅 示例

值得注意的是,这些 shouldX 函数只添加表示 PASS 或 FAIL 的输出字符串。如果预期结果也包含相同的 FAIL 字符串,则 run-webkit-tests 将认为整个测试文件已通过。

另一种理解方式是,-expected.txt 文件是基准输出,而基准输出可以包含已知故障。

有一个辅助脚本可以为新的 js-test.js 测试创建模板。以下命令将在 LayoutTests/fast/dom 中创建名为 new-test.html 的新测试。

Tools/Scripts/make-new-script-test fast/dom/new-test.html

dump-as-markup.js 测试

dump-as-markup.js 测试是 dumpAsText 测试的另一种变体,它使用 LayoutTests/resources/dump-as-markup.js。这种测试风格用于比较 DOM 树在某些操作之前和之后的状态。例如,LayoutTests/editing 下的许多测试都使用这种测试风格来测试复杂的 DOM 变异操作,例如从用户剪贴板粘贴 HTML。dump-as-markup.js 在全局对象上添加了 Markup 并公开了一些辅助函数。像 js-test.js 测试一样,可以通过 Markup.description 指定测试描述。测试随后涉及 Markup.dump(node, description) 将 DOM 树的状态序列化为纯文本,其中 element 是应序列化状态的 DOM 节点或其 id

例如,LayoutTests/editing/inserting/insert-list-in-table-cell-01.html 如下所示:

<!DOCTYPE html>
<div id="container" contenteditable="true"><table border="1"><tr><td id="element">fsdf</td><td>fsdf</td></tr><tr><td>gghfg</td><td>fsfg</td></tr></table></div>
<script src="../editing.js"></script>
<script src="../../resources/dump-as-markup.js"></script>
<script>
    Markup.description('Insert list items in a single table cell:');

    var e = document.getElementById("element");
    setSelectionCommand(e, 0, e, 1);
    Markup.dump('container', 'Before');

    document.execCommand("insertOrderedList");
    Markup.dump('container', 'After');
</script>

附带以下 预期结果

Insert list items in a single table cell:

Before:
| <table>
|   border="1"
|   <tbody>
|     <tr>
|       <td>
|         id="element"
|         "<#selection-anchor>fsdf<#selection-focus>"
|       <td>
|         "fsdf"
|     <tr>
|       <td>
|         "gghfg"
|       <td>
|         "fsfg"

After:
| <table>
|   border="1"
|   <tbody>
|     <tr>
|       <td>
|         id="element"
|         <ol>
|           <li>
|             "<#selection-anchor>fsdf<#selection-focus>"
|             <br>
|       <td>
|         "fsdf"
|     <tr>
|       <td>
|         "gghfg"
|       <td>
|         "fsfg"

testharness.js 测试

这是 dumpAsText 测试的又一个变体,它使用 Web 平台测试的测试工具,这是 W3C 官方的 Web 测试。关于此工具的工作原理有详尽的文档

如上所述,除非在 Web 平台测试的主仓库中进行了相同的测试更改,否则不要修改 LayoutTests/imported/w3c/web-platform-tests 中的测试。

参考测试

参考测试的特殊之处在于它们没有附带的 -expected.txt 文件。相反,它们有一个匹配或不匹配的预期结果文件。测试文件和随附的匹配或不匹配预期结果都生成 PNG 输出。如果测试的 PNG 输出与匹配的预期结果相同,则测试通过;否则测试失败。对于具有不匹配预期结果的测试,如果测试的 PNG 输出与不匹配的预期结果不同,则测试通过;如果相同,则测试失败。

匹配的预期结果或不匹配的预期结果可以通过几种方式指定:

  • 文件名与测试名称相同,但以 -expected.*-ref.* 结尾的文件是测试的匹配预期结果。
  • 文件名与测试名称相同,但以 -expected-mismatch.*-notref.* 结尾的文件是不匹配的预期结果。
  • 测试文件中通过 match 关系指定的 HTML 链接元素文件:<link rel=match href=X>,其中 X 是相对文件路径,是匹配的预期结果。
  • 测试文件中通过 mismatch 关系指定的 HTML 链接元素文件:<link rel=mismatch href=X>,其中 X 是相对文件路径,是不匹配的预期结果。

例如,LayoutTests/imported/w3c/web-platform-tests/html/rendering/replaced-elements/images/space.html 在同一目录中将 space-ref.html 指定为匹配的预期结果,如下所示:

<!doctype html>
<meta charset=utf-8>
<title>img hspace/vspace</title>
<link rel=match href=space-ref.html>
<style>
span { background: blue; }
</style>
<div style=width:400px;>
<p><span><img src=/images/green.png></span>
<p><span><img src=/images/green.png hspace=10></span>
<p><span><img src=/images/green.png vspace=10></span>
<p><span><img src=/images/green.png hspace=10%></span>
<p><span><img src=/images/green.png vspace=10%></span>
</div>

测试运行器

大多数布局测试都设计为可在浏览器内部运行,但 run-webkit-tests 使用一个特殊程序来运行它们。我们的持续集成系统以及预警系统都使用 run-webkit-tests 来运行布局测试。在 WebKit2 中,此程序恰当地命名为 WebKitTestRunner。在 WebKit1 或 WebKitLegacy 中,它是 DumpRenderTree,其名称来源于第一种布局测试类型,该类型生成渲染树的文本表示。

测试运行器中可用的额外接口

WebKitTestRunner 和 DumpRenderTree 都向 window(即全局对象)上的 JavaScript 公开了一些额外接口,以模拟用户输入、启用或禁用某个功能,或提高测试的可靠性。

  • GCController
    • GCController.collect() 触发同步的完整垃圾回收。此函数对于测试崩溃或 JS 封装器和内存泄漏的错误过早回收很有用。
  • testRunner
    • TestRunner 接口公开了许多方法来控制 WebKitTestRunner 和 DumpRenderTree 的行为。最常用的一些方法如下:
    • waitUntilDone() / notifyDone() - 这些函数在编写涉及异步任务的测试时很有用,这些任务可能需要测试在加载完成后继续运行。testRunner.waitUntilDone() 使 WebKitTestRunner 和 DumpRenderTree 在布局测试加载完成后不结束测试。测试将继续运行直到调用 testRunner.notifyDone()
    • dumpAsText(boolean dumpPixels) - 使 WebKitTestRunner 和 DumpRenderTree 输出加载页面的纯文本而不是渲染树的状态。
    • overridePreference(DOMString preference, DOMString value) - 覆盖 WebKit 的偏好设置。对于 WebKitLegacy,这些定义在 macOS 的 Source/WebKitLegacy/mac/WebView/WebPreferences.h 和 Windows 的 Source/WebKitLegacy/win/WebPreferences.h 中。
  • eventSender
    • 公开模拟鼠标、键盘和触摸操作的方法。使用 ui-helpers.js 脚本,而不是直接调用此函数上的方法。这将确保测试与我们所有的测试配置具有最佳兼容性。
  • UIScriptController
    • 公开模拟用户输入的方法,主要在 iOS WebKit2 上类似于 eventSender。使用 ui-helpers.js 脚本,而不是直接调用此函数上的方法。这将确保测试与我们所有的测试配置具有最佳兼容性。
  • textInputController

此外,WebCore/testing 公开了一些测试钩子来测试其内部结构。

  • internals
    • 公开 WebCore 的各种钩子,这些钩子不应成为 WebKit 或 WebKitLegacy API 的一部分。
  • internals.settings
    • 公开各种 WebCore 设置并允许测试覆盖它们。请注意,WebKit 层代码可能依赖于 UI 进程中的偏好设置,并且可能需要使用前面提到的 testRunner.overridePreference。实际上,最好通过 testRunner.overridePreference 覆盖等效的偏好设置,除非您确定 WebKit 或 WebKitLegacy 层代码不受您正在覆盖的设置的影响。

在测试运行器中启用或禁用某个功能

FIXME:提及 test-runner-options

测试工具脚本

FIXME:编写关于 dump-as-markup.js 和 ui-helper.js 的内容

调查机器人上观察到的测试失败

有多种工具可以调查我们的持续集成系统(build.webkit.org)上发生的测试失败。最值得注意的是不稳定性仪表板:results.webkit.org

FIXME:编写如何调查测试失败。

深入探究 API 测试

FIXME:谈谈如何调试 API 测试。