wpt: first draft for tests

This commit is contained in:
Pierre Tachoire
2023-10-02 21:18:29 +02:00
parent b782a591f9
commit d171b0ff01
7 changed files with 5531 additions and 0 deletions

View File

@@ -72,6 +72,19 @@ pub fn build(b: *std.build.Builder) !void {
// step
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_tests.step);
// wpt tests
// -----
// compile
const wpt = b.addTest(.{ .root_source_file = .{ .path = "src/run_wpt.zig" } });
try common(wpt, options);
wpt.single_threaded = true;
const run_wpt = b.addRunArtifact(wpt);
// step
const wpt_step = b.step("wpt", "Run Web Platform Tests");
wpt_step.dependOn(&run_wpt.step);
}
fn common(

143
src/run_wpt.zig Normal file
View File

@@ -0,0 +1,143 @@
const std = @import("std");
const parser = @import("netsurf.zig");
const jsruntime = @import("jsruntime");
const public = @import("jsruntime");
const API = public.API;
const TPL = public.TPL;
const Env = public.Env;
const Loop = public.Loop;
const DOM = @import("dom.zig");
const HTMLElem = @import("html/elements.zig");
fn readFile(allocator: std.mem.Allocator, filename: []const u8) ![]const u8 {
var file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const file_size = try file.getEndPos();
return file.readToEndAlloc(allocator, file_size);
}
// generate APIs
const apis = jsruntime.compile(DOM.Interfaces);
// TODO For now the WPT tests run is specific to WPT.
// It manually load js framwork libs, and run the first script w/ js content in
// the HTML page.
// Once browsercore will have the html loader, it would be useful to refacto
// this test to use it.
test {
std.debug.print("Running WPT test suite\n", .{});
var bench_alloc = jsruntime.bench_allocator(std.testing.allocator);
const alloc = bench_alloc.allocator();
// initialize VM JS lib.
const vm = jsruntime.VM.init();
defer vm.deinit();
// prepare libraries to load on each test case.
var libs: [2][]const u8 = undefined;
// read testharness.js content
libs[0] = try readFile(alloc, "tests/wpt/resources/testharness.js");
defer alloc.free(libs[0]);
// read testharnessreport.js content
libs[1] = try readFile(alloc, "tests/wpt/resources/testharnessreport.js");
defer alloc.free(libs[1]);
// TODO browse the dir to get the tests dynamically.
const testcases = [_][]const u8{
"tests/wpt/dom/nodes/ChildNode-after.html",
"tests/wpt/dom/nodes/Document-createElement.html",
"tests/wpt/dom/nodes/append-on-Document.html",
};
var failures: usize = 0;
for (testcases) |tc| {
// create an arena and deinit it for each test case.
var arena = std.heap.ArenaAllocator.init(alloc);
defer arena.deinit();
// TODO I don't use testing.expect here b/c I want to execute all the
// tests. And testing.expect stops running test in the first failure.
std.debug.print("{s}\t\t", .{tc});
const res = runWPT(&arena, tc, libs[0..]) catch |err| {
std.debug.print("[ ERR ]\n\t> {any}\n", .{err});
failures += 1;
continue;
};
if (!res.success) {
std.debug.print("[ ERR ]\n\t> {s}\n", .{res.result});
failures += 1;
continue;
}
std.debug.print("[ OK ]\n", .{});
}
if (failures > 0) {
std.debug.print("{d}/{d} tests failures\n", .{ failures, testcases.len });
}
try std.testing.expect(failures == 0);
}
// runWPT parses the given HTML file, starts a js env and run the first script
// tags containing javascript sources.
// It loads first the js libs files.
fn runWPT(arena: *std.heap.ArenaAllocator, f: []const u8, libs: [][]const u8) !jsruntime.JSResult {
const alloc = arena.allocator();
// document
var ff: []u8 = @constCast(f);
const htmldoc = parser.documentHTMLParse(ff);
const doc = parser.documentHTMLToDocument(htmldoc);
// create JS env
var loop = try Loop.init(alloc);
defer loop.deinit();
var js_env = try Env.init(arena, &loop);
defer js_env.deinit();
// load APIs in JS env
var tpls: [apis.len]TPL = undefined;
try js_env.load(apis, &tpls);
// start JS env
js_env.start(apis);
defer js_env.stop();
// add document object
try js_env.addObject(apis, doc, "document");
var res = jsruntime.JSResult{};
var cbk_res = jsruntime.JSResult{
.success = true,
// assume that the return value of the successfull callback is "undefined"
.result = "undefined",
};
// execute libs
for (libs) |lib| {
try js_env.run(alloc, lib, "", &res, &cbk_res);
}
// loop hover the scripts.
const scripts = parser.documentGetElementsByTagName(doc, "script");
const slen = parser.nodeListLength(scripts);
for (0..slen) |i| {
const s = parser.nodeListItem(scripts, @intCast(i)).?;
// search only script tag containing text a child.
const text = parser.nodeFirstChild(s) orelse continue;
const src = parser.nodeTextContent(text).?;
try js_env.run(alloc, src, "", &res, &cbk_res);
return res;
}
return error.EmptyTest;
}

View File

@@ -0,0 +1,166 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>ChildNode.after</title>
<link rel=help href="https://dom.spec.whatwg.org/#dom-childnode-after">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
function test_after(child, nodeName, innerHTML) {
test(function() {
var parent = document.createElement('div');
parent.appendChild(child);
child.after();
assert_equals(parent.innerHTML, innerHTML);
}, nodeName + '.after() without any argument.');
test(function() {
var parent = document.createElement('div');
parent.appendChild(child);
child.after(null);
var expected = innerHTML + 'null';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with null as an argument.');
test(function() {
var parent = document.createElement('div');
parent.appendChild(child);
child.after(undefined);
var expected = innerHTML + 'undefined';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with undefined as an argument.');
test(function() {
var parent = document.createElement('div');
parent.appendChild(child);
child.after('');
assert_equals(parent.lastChild.data, '');
}, nodeName + '.after() with the empty string as an argument.');
test(function() {
var parent = document.createElement('div');
parent.appendChild(child);
child.after('text');
var expected = innerHTML + 'text';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with only text as an argument.');
test(function() {
var parent = document.createElement('div');
var x = document.createElement('x');
parent.appendChild(child);
child.after(x);
var expected = innerHTML + '<x></x>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with only one element as an argument.');
test(function() {
var parent = document.createElement('div');
var x = document.createElement('x');
parent.appendChild(child);
child.after(x, 'text');
var expected = innerHTML + '<x></x>text';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with one element and text as arguments.');
test(function() {
var parent = document.createElement('div');
parent.appendChild(child);
child.after('text', child);
var expected = 'text' + innerHTML;
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with context object itself as the argument.');
test(function() {
var parent = document.createElement('div')
var x = document.createElement('x');
parent.appendChild(x);
parent.appendChild(child);
child.after(child, x);
var expected = innerHTML + '<x></x>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with context object itself and node as the arguments, switching positions.');
test(function() {
var parent = document.createElement('div');
var x = document.createElement('x');
var y = document.createElement('y');
var z = document.createElement('z');
parent.appendChild(y);
parent.appendChild(child);
parent.appendChild(x);
child.after(x, y, z);
var expected = innerHTML + '<x></x><y></y><z></z>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with all siblings of child as arguments.');
test(function() {
var parent = document.createElement('div')
var x = document.createElement('x');
var y = document.createElement('y');
var z = document.createElement('z');
parent.appendChild(child);
parent.appendChild(x);
parent.appendChild(y);
parent.appendChild(z);
child.after(x, y);
var expected = innerHTML + '<x></x><y></y><z></z>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.before() with some siblings of child as arguments; no changes in tree; viable sibling is first child.');
test(function() {
var parent = document.createElement('div')
var v = document.createElement('v');
var x = document.createElement('x');
var y = document.createElement('y');
var z = document.createElement('z');
parent.appendChild(child);
parent.appendChild(v);
parent.appendChild(x);
parent.appendChild(y);
parent.appendChild(z);
child.after(v, x);
var expected = innerHTML + '<v></v><x></x><y></y><z></z>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with some siblings of child as arguments; no changes in tree.');
test(function() {
var parent = document.createElement('div');
var x = document.createElement('x');
var y = document.createElement('y');
parent.appendChild(child);
parent.appendChild(x);
parent.appendChild(y);
child.after(y, x);
var expected = innerHTML + '<y></y><x></x>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() when pre-insert behaves like append.');
test(function() {
var parent = document.createElement('div');
var x = document.createElement('x');
var y = document.createElement('y');
parent.appendChild(child);
parent.appendChild(x);
parent.appendChild(document.createTextNode('1'));
parent.appendChild(y);
child.after(x, '2');
var expected = innerHTML + '<x></x>21<y></y>';
assert_equals(parent.innerHTML, expected);
}, nodeName + '.after() with one sibling of child and text as arguments.');
test(function() {
var x = document.createElement('x');
var y = document.createElement('y');
x.after(y);
assert_equals(x.nextSibling, null);
}, nodeName + '.after() on a child without any parent.');
}
test_after(document.createComment('test'), 'Comment', '<!--test-->');
test_after(document.createElement('test'), 'Element', '<test></test>');
test_after(document.createTextNode('test'), 'Text', 'test');
</script>
</html>

View File

@@ -0,0 +1,158 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>Document.createElement</title>
<link rel=help href="https://dom.spec.whatwg.org/#dom-document-createelement">
<link rel=help href="https://dom.spec.whatwg.org/#dom-element-localname">
<link rel=help href="https://dom.spec.whatwg.org/#dom-element-tagname">
<link rel=help href="https://dom.spec.whatwg.org/#dom-element-prefix">
<link rel=help href="https://dom.spec.whatwg.org/#dom-element-namespaceuri">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<div id="log"></div>
<iframe src="/common/dummy.xml"></iframe>
<iframe src="/common/dummy.xhtml"></iframe>
<script>
function toASCIIUppercase(str) {
var diff = "a".charCodeAt(0) - "A".charCodeAt(0);
var res = "";
for (var i = 0; i < str.length; ++i) {
if ("a" <= str[i] && str[i] <= "z") {
res += String.fromCharCode(str.charCodeAt(i) - diff);
} else {
res += str[i];
}
}
return res;
}
function toASCIILowercase(str) {
var diff = "a".charCodeAt(0) - "A".charCodeAt(0);
var res = "";
for (var i = 0; i < str.length; ++i) {
if ("A" <= str[i] && str[i] <= "Z") {
res += String.fromCharCode(str.charCodeAt(i) + diff);
} else {
res += str[i];
}
}
return res;
}
var HTMLNS = "http://www.w3.org/1999/xhtml",
valid = [
undefined,
null,
"foo",
"f1oo",
"foo1",
"f\u0BC6",
"foo\u0BC6",
":",
":foo",
"f:oo",
"foo:",
"f:o:o",
"f::oo",
"f::oo:",
"foo:0",
"foo:_",
// combining char after :, invalid QName but valid Name
"foo:\u0BC6",
"foo:foo\u0BC6",
"foo\u0BC6:foo",
"xml",
"xmlns",
"xmlfoo",
"xml:foo",
"xmlns:foo",
"xmlfoo:bar",
"svg",
"math",
"FOO",
// Test that non-ASCII chars don't get uppercased/lowercased
"mar\u212a",
"\u0130nput",
"\u0131nput",
],
invalid = [
"",
"1foo",
"1:foo",
"fo o",
"\u0300foo",
"}foo",
"f}oo",
"foo}",
"\ufffffoo",
"f\uffffoo",
"foo\uffff",
"<foo",
"foo>",
"<foo>",
"f<oo",
"-foo",
".foo",
"\u0300",
]
var xmlIframe = document.querySelector('[src="/common/dummy.xml"]');
var xhtmlIframe = document.querySelector('[src="/common/dummy.xhtml"]');
function getWin(desc) {
if (desc == "HTML document") {
return window;
}
if (desc == "XML document") {
assert_equals(xmlIframe.contentDocument.documentElement.textContent,
"Dummy XML document", "XML document didn't load");
return xmlIframe.contentWindow;
}
if (desc == "XHTML document") {
assert_equals(xhtmlIframe.contentDocument.documentElement.textContent,
"Dummy XHTML document", "XHTML document didn't load");
return xhtmlIframe.contentWindow;
}
}
valid.forEach(function(t) {
["HTML document", "XML document", "XHTML document"].forEach(function(desc) {
async_test(function(testObj) {
window.addEventListener("load", function() {
testObj.step(function() {
var win = getWin(desc);
var doc = win.document;
var elt = doc.createElement(t)
assert_true(elt instanceof win.Element, "instanceof Element")
assert_true(elt instanceof win.Node, "instanceof Node")
assert_equals(elt.localName,
desc == "HTML document" ? toASCIILowercase(String(t))
: String(t),
"localName")
assert_equals(elt.tagName,
desc == "HTML document" ? toASCIIUppercase(String(t))
: String(t),
"tagName")
assert_equals(elt.prefix, null, "prefix")
assert_equals(elt.namespaceURI,
desc == "XML document" ? null : HTMLNS, "namespaceURI")
});
testObj.done();
});
}, "createElement(" + format_value(t) + ") in " + desc);
});
});
invalid.forEach(function(arg) {
["HTML document", "XML document", "XHTML document"].forEach(function(desc) {
async_test(function(testObj) {
window.addEventListener("load", function() {
testObj.step(function() {
let win = getWin(desc);
let doc = win.document;
assert_throws_dom("InvalidCharacterError", win.DOMException,
function() { doc.createElement(arg) })
});
testObj.done();
});
}, "createElement(" + format_value(arg) + ") in " + desc);
});
});
</script>

View File

@@ -0,0 +1,53 @@
<!DOCTYPE html>
<meta charset=utf-8>
<title>DocumentType.append</title>
<link rel=help href="https://dom.spec.whatwg.org/#dom-parentnode-append">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
function test_append_on_Document() {
var node = document.implementation.createDocument(null, null);
test(function() {
var parent = node.cloneNode();
parent.append();
assert_array_equals(parent.childNodes, []);
}, 'Document.append() without any argument, on a Document having no child.');
test(function() {
var parent = node.cloneNode();
var x = document.createElement('x');
parent.append(x);
assert_array_equals(parent.childNodes, [x]);
}, 'Document.append() with only one element as an argument, on a Document having no child.');
test(function() {
var parent = node.cloneNode();
var x = document.createElement('x');
var y = document.createElement('y');
parent.appendChild(x);
assert_throws_dom('HierarchyRequestError', function() { parent.append(y); });
assert_array_equals(parent.childNodes, [x]);
}, 'Document.append() with only one element as an argument, on a Document having one child.');
test(function() {
var parent = node.cloneNode();
assert_throws_dom('HierarchyRequestError', function() { parent.append('text'); });
assert_array_equals(parent.childNodes, []);
}, 'Document.append() with text as an argument, on a Document having no child.');
test(function() {
var parent = node.cloneNode();
var x = document.createElement('x');
var y = document.createElement('y');
assert_throws_dom('HierarchyRequestError', function() { parent.append(x, y); });
assert_array_equals(parent.childNodes, []);
}, 'Document.append() with two elements as the argument, on a Document having no child.');
}
test_append_on_Document();
</script>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
/* global add_completion_callback */
/* global setup */
/*
* This file is intended for vendors to implement code needed to integrate
* testharness.js tests with their own test systems.
*
* Typically test system integration will attach callbacks when each test has
* run, using add_result_callback(callback(test)), or when the whole test file
* has completed, using
* add_completion_callback(callback(tests, harness_status)).
*
* For more documentation about the callback functions and the
* parameters they are called with see testharness.js
*/
function dump_test_results(tests, status) {
var results_element = document.createElement("script");
results_element.type = "text/json";
results_element.id = "__testharness__results__";
var test_results = tests.map(function(x) {
return {name:x.name, status:x.status, message:x.message, stack:x.stack}
});
var data = {test:window.location.href,
tests:test_results,
status: status.status,
message: status.message,
stack: status.stack};
results_element.textContent = JSON.stringify(data);
// To avoid a HierarchyRequestError with XML documents, ensure that 'results_element'
// is inserted at a location that results in a valid document.
var parent = document.body
? document.body // <body> is required in XHTML documents
: document.documentElement; // fallback for optional <body> in HTML5, SVG, etc.
parent.appendChild(results_element);
}
add_completion_callback(dump_test_results);
/* If the parent window has a testharness_properties object,
* we use this to provide the test settings. This is used by the
* default in-browser runner to configure the timeout and the
* rendering of results
*/
try {
if (window.opener && "testharness_properties" in window.opener) {
/* If we pass the testharness_properties object as-is here without
* JSON stringifying and reparsing it, IE fails & emits the message
* "Could not complete the operation due to error 80700019".
*/
setup(JSON.parse(JSON.stringify(window.opener.testharness_properties)));
}
} catch (e) {
}
// vim: set expandtab shiftwidth=4 tabstop=4: