测试¶
深入了解 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/http 或 LayoutTests/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.1
和 localhost
非常重要,因为某些测试依赖于或测试基于这些主机名的同源或跨源行为。
测试预期¶
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 3
,run-webkit-tests
将运行:A、B、C、A、B、C、A、B、C。类似地,--repeat-each
选项将指定每个测试在进入下一个测试之前重复的次数。例如,如果我们正在运行测试 A、B、C 并且指定了 --repeat-each 3
,run-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.js 和 LayoutTests/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.js 和 js-test-pre.js 提供了各种其他断言和辅助函数。以下是一些示例:
debug(msg)
- 在输出中插入调试/日志字符串。evalAndLog(code)
- 类似于debug()
,但将代码作为 JavaScript 求值。shouldNotBe(a, b)
- 如果a
和b
的求值结果不同,则生成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 层代码不受您正在覆盖的设置的影响。
- 公开各种 WebCore 设置并允许测试覆盖它们。请注意,WebKit 层代码可能依赖于 UI 进程中的偏好设置,并且可能需要使用前面提到的
在测试运行器中启用或禁用某个功能¶
FIXME:提及 test-runner-options
测试工具脚本¶
FIXME:编写关于 dump-as-markup.js 和 ui-helper.js 的内容
调查机器人上观察到的测试失败¶
有多种工具可以调查我们的持续集成系统(build.webkit.org)上发生的测试失败。最值得注意的是不稳定性仪表板:results.webkit.org
FIXME:编写如何调查测试失败。
深入探究 API 测试¶
FIXME:谈谈如何调试 API 测试。