Initial commit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+353
@@ -0,0 +1,353 @@
|
||||
'use strict';
|
||||
var fs = require('fs');
|
||||
var Path = require('path');
|
||||
var domino = require('../lib');
|
||||
var Window = require('../lib/Window');
|
||||
|
||||
var BLOCKLIST_PATH = Path.resolve(__dirname, 'web-platform-blocklist.json');
|
||||
// Set to true and delete the existing blocklist file to regenerate the
|
||||
// blocklist from currently-failing tests.
|
||||
var WRITE_BLOCKLIST = false;
|
||||
|
||||
// These are the tests we currently fail.
|
||||
// Some of these failures are bugs we ought to fix.
|
||||
var blocklist = {};
|
||||
try {
|
||||
blocklist = require(BLOCKLIST_PATH);
|
||||
} catch(e) {
|
||||
// We expect that you deleted the old blocklist before using WRITE_BLOCKLIST
|
||||
if (!WRITE_BLOCKLIST) { throw e; }
|
||||
}
|
||||
|
||||
var escapeRegExp = function(s) {
|
||||
// Note that JSON is not a subset of JavaScript: it allows \u2028 and \u2029
|
||||
// to be embedded as literals. Escape them in the regexp to prevent this
|
||||
// from causing a syntax error when we eval() this regexp.
|
||||
return s.replace(/[\^\\$*+?.()|{}\[\]\/]/g, '\\$&')
|
||||
.replace(/[\u2028\u2029]/, function(c) {
|
||||
var cp = c.codePointAt(0).toString(16);
|
||||
while (cp.length < 4) { cp = '0' + cp; }
|
||||
return '\\u' + cp;
|
||||
});
|
||||
};
|
||||
|
||||
var onblocklist = function(shortFile) {
|
||||
if (WRITE_BLOCKLIST) { return null; }
|
||||
if (!Array.isArray(blocklist[shortFile])) { return null; }
|
||||
// convert strings to huge regexp
|
||||
return new RegExp('^(' + blocklist[shortFile].map(escapeRegExp).join('|') + ')$');
|
||||
};
|
||||
|
||||
// Test suite requires Array.includes(); polyfill from
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes
|
||||
/* jshint bitwise: false */
|
||||
if (!Array.prototype.includes) {
|
||||
Object.defineProperty(Array.prototype, 'includes', {
|
||||
value: function(searchElement, fromIndex) {
|
||||
if (this === null || this === undefined) {
|
||||
throw new TypeError('"this" is null or not defined');
|
||||
}
|
||||
var o = Object(this);
|
||||
var len = o.length >>> 0;
|
||||
if (len === 0) {
|
||||
return false;
|
||||
}
|
||||
var n = fromIndex | 0;
|
||||
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
|
||||
|
||||
function sameValueZero(x, y) {
|
||||
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
|
||||
}
|
||||
|
||||
while (k < len) {
|
||||
if (sameValueZero(o[k], searchElement)) {
|
||||
return true;
|
||||
}
|
||||
k++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
// Test suite requires Array.values() as well
|
||||
if (global.Symbol && global.Symbol.iterator && !Array.prototype.values) {
|
||||
Object.defineProperty(
|
||||
Array.prototype, 'values',
|
||||
Object.getOwnPropertyDescriptor(Array.prototype, global.Symbol.iterator)
|
||||
);
|
||||
}
|
||||
|
||||
function read(file) {
|
||||
return fs.readFileSync(Path.resolve(__dirname, '..', file), 'utf8');
|
||||
}
|
||||
|
||||
var testharness = require(__dirname + '/web-platform-tests/resources/testharness.js');
|
||||
|
||||
function list(base, dir, fn) {
|
||||
var result = {};
|
||||
var fulldir = Path.resolve(__dirname, '..', base, dir);
|
||||
fs.readdirSync(fulldir).forEach(function(file) {
|
||||
var path = Path.join(dir, file);
|
||||
var stat = fs.statSync(Path.join(fulldir, file));
|
||||
if (stat.isDirectory()) {
|
||||
result[file] = list(base, path, fn);
|
||||
}
|
||||
else if (file.match(/\.x?html$/)) {
|
||||
var test = fn(path, Path.join(fulldir, file));
|
||||
if (test) result[file] = test;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
var badTestFiles = new RegExp('(' + [
|
||||
'/html/dom/interfaces.https.html', // Uses ES6, missing .../WebIDLParser.js
|
||||
'/dom/nodes/Document-characterSet-normalization.html',
|
||||
'/dom/nodes/Document-contentType/contentType/contenttype_datauri_02.html',
|
||||
'/dom/nodes/Element-getElementsByTagName-change-document-HTMLNess.html',
|
||||
'/dom/nodes/ParentNode-querySelector-All.html',
|
||||
'/dom/nodes/query-target-in-load-event.html',
|
||||
].map(escapeRegExp).join('|') + ')$');
|
||||
|
||||
var forceSyncTestFiles = new RegExp('(' + [
|
||||
'/dom/nodes/Node-parentNode.html',
|
||||
'/html/dom/documents/dom-tree-accessors/Document.currentScript.html',
|
||||
'/html/dom/self-origin.sub.html',
|
||||
'/dom/nodes/Node-isEqualNode-xhtml.xhtml',
|
||||
].map(escapeRegExp).join('|') + ')$');
|
||||
|
||||
var harness = function() {
|
||||
var paths = Array.from(arguments);
|
||||
var harnessResult = {
|
||||
// Could add 'before' or 'after' hooks here.
|
||||
};
|
||||
paths.forEach(function(path) {
|
||||
var shortName = path.replace(/^.*?\/web-platform-tests\//, '');
|
||||
harnessResult[shortName] = list(path, '', function(name, file) {
|
||||
var shortFile = file.replace(/^.*?\/web-platform-tests\//, '');
|
||||
if (/\/html\/dom\/reflection-original.html$/.test(file)) {
|
||||
// This is a compilation file & not a test suite.
|
||||
return; // skip
|
||||
}
|
||||
if (badTestFiles.test(file)) {
|
||||
// Misc problems w/ test file, usually resulting in async done() method
|
||||
// never getting invoked and a time-wasting timeout.
|
||||
return; // skip
|
||||
}
|
||||
var html = read(file);
|
||||
|
||||
var document = domino.createDocument(html);
|
||||
document.address = 'http://example.com/';
|
||||
var window = new Window(document);
|
||||
|
||||
Array.from(window.document.getElementsByTagName('iframe')).forEach(function(iframe) {
|
||||
if (iframe.src === 'http://example.com/common/dummy.xml') {
|
||||
var dummyXmlDoc = domino.createDOMImplementation().createDocument(
|
||||
'http://www.w3.org/1999/xhtml', 'html', null
|
||||
);
|
||||
dummyXmlDoc._contentType = 'application/xml';
|
||||
iframe._contentWindow = new Window(dummyXmlDoc);
|
||||
var foo = dummyXmlDoc.createElement('foo');
|
||||
foo.textContent = 'Dummy XML document';
|
||||
dummyXmlDoc.documentElement.appendChild(foo);
|
||||
}
|
||||
if (iframe.src === 'http://example.com/common/dummy.xhtml') {
|
||||
var dummyXhtml = read('test/web-platform-tests/common/dummy.xhtml');
|
||||
var dummyXhtmlAsHtml = domino.createDocument(dummyXhtml);
|
||||
// Tweak this a tiny bit, since we actually used an HTML parser not
|
||||
// an XML parser.
|
||||
dummyXhtmlAsHtml.body.textContent = '';
|
||||
// Create a proper XML document, and copy the HTML contents into it
|
||||
var dummyXhtmlDoc = domino.createDOMImplementation().createDocument(
|
||||
'http://www.w3.org/1999/xhtml', 'html', null
|
||||
);
|
||||
dummyXhtmlDoc._contentType = 'application/xhtml+xml';
|
||||
dummyXhtmlDoc.insertBefore(
|
||||
dummyXhtmlDoc.adoptNode(dummyXhtmlAsHtml.doctype),
|
||||
dummyXhtmlDoc.documentElement
|
||||
);
|
||||
dummyXhtmlDoc.documentElement.appendChild(
|
||||
dummyXhtmlDoc.adoptNode(dummyXhtmlAsHtml.head)
|
||||
);
|
||||
dummyXhtmlDoc.documentElement.appendChild(
|
||||
dummyXhtmlDoc.adoptNode(dummyXhtmlAsHtml.body)
|
||||
);
|
||||
iframe._contentWindow = new Window(dummyXhtmlDoc);
|
||||
}
|
||||
});
|
||||
testharness(window);
|
||||
window.setup({explicit_timeout:true});
|
||||
|
||||
if (forceSyncTestFiles.test(file)) {
|
||||
window.async_test = function(){
|
||||
return {
|
||||
step: function() {},
|
||||
step_func_done: function() {},
|
||||
done: function() {},
|
||||
}
|
||||
};
|
||||
}
|
||||
var scripts = window.document.getElementsByTagName('script');
|
||||
scripts = Array.from(scripts);
|
||||
var sawTestHarness = false;
|
||||
var concatenatedScripts = scripts.map(function(script) {
|
||||
if (/\/resources\/testharness(report)?\.js$/.test(script.getAttribute('src')||'')) {
|
||||
// We've already included the test harnesses.
|
||||
sawTestHarness = true;
|
||||
return '';
|
||||
}
|
||||
if (/\/webrtc\/RTCPeerConnection-helper.js$/.test(script.getAttribute('src')||'')) {
|
||||
// Bad script: we don't support webrtc and furthermore it contains
|
||||
// ES6 syntax which causes older versions of node to choke.
|
||||
return '';
|
||||
}
|
||||
if (/^text\/plain$/.test(script.getAttribute('type')||'')) {
|
||||
return '';
|
||||
}
|
||||
if (/^(\w+|..|\/)/.test(script.getAttribute('src')||'')) {
|
||||
var f = script.getAttribute('src');
|
||||
if (/^\//.test(f)) {
|
||||
f = Path.resolve(__dirname, 'web-platform-tests' + f);
|
||||
} else {
|
||||
//console.log('??', {path:path, name:name, file:file});
|
||||
f = Path.resolve(Path.dirname(file), f);
|
||||
}
|
||||
if (fs.existsSync(f)) {
|
||||
return read(f);
|
||||
} else {
|
||||
// console.warn('SKIPPING SCRIPT', script.outerHTML);
|
||||
}
|
||||
}
|
||||
var textContent = script.textContent;
|
||||
if (/\.xhtml$/.test(file)) {
|
||||
// hacky way to expand entities
|
||||
var txt = window.document.createElementNS('http://www.w3.org/1999/xhtml', 'textarea');
|
||||
txt.innerHTML = textContent;
|
||||
textContent = txt.textContent;
|
||||
}
|
||||
return textContent + '\n';
|
||||
}).join("\n");
|
||||
concatenatedScripts =
|
||||
concatenatedScripts.replace(/\.attributes\[(\w+)\]/g,
|
||||
'.attributes.item($1)');
|
||||
// Some tests use [...foo] syntax for `Array.from(foo)`
|
||||
concatenatedScripts =
|
||||
concatenatedScripts.replace(/\[\.\.\.(\w+)\]/g,
|
||||
'Array.from($1)');
|
||||
// usvstring-reflection.html uses () => { ... } syntax (unnecessarily)
|
||||
concatenatedScripts =
|
||||
concatenatedScripts.replace(/(\b\w+|\(\w*\))\s*=>\s*\{/g, function(_,a){
|
||||
if (a.slice(0,1)!=='(') { a = '(' + a + ')'; }
|
||||
return 'function '+a+' {';
|
||||
});
|
||||
// It also contains `([channel1, channel2]) => {` in a test case which is
|
||||
// bound to fail anyway (it exercises WebRTC)
|
||||
concatenatedScripts =
|
||||
concatenatedScripts.replace(
|
||||
/\(\[channel1, channel2\]\) => \{/,
|
||||
'function(channel1, channel2){' // not right, but not a syntax error
|
||||
);
|
||||
// Workaround for https://github.com/w3c/web-platform-tests/pull/3984
|
||||
concatenatedScripts =
|
||||
'"use strict";\n' +
|
||||
'var ReflectionTests, x, attr;\n' +
|
||||
// Hack in globals on window object
|
||||
'"String|Boolean|Number".split("|").forEach(function(x){' +
|
||||
'window[x] = global[x];})\n' +
|
||||
// Hack in frames on window object
|
||||
'Array.from(document.getElementsByTagName("iframe")).forEach(' +
|
||||
'function(f,i){window[i]=f.contentWindow;});\n' +
|
||||
concatenatedScripts;
|
||||
// Fire load events
|
||||
var closeDocument =
|
||||
'document.close();\n' +
|
||||
'Array.from(document.getElementsByTagName("iframe")).forEach(' +
|
||||
'function(f){f.dispatchEvent(new Event("load"));});';
|
||||
|
||||
var expectedFailures = onblocklist(shortFile);
|
||||
|
||||
return function(done) {
|
||||
var haveTests = false, isComplete = false, sawError = [];
|
||||
var calledOnce = false;
|
||||
var withResults = function(results) {
|
||||
if (calledOnce) {
|
||||
console.warn('DONE CALLED TWICE', name, (new Error()).stack);
|
||||
return;
|
||||
}
|
||||
else { calledOnce = true; }
|
||||
if (!WRITE_BLOCKLIST) {
|
||||
var str = results.map(function(item) {
|
||||
var s = item.name;
|
||||
if (item.message) s += ': ' + item.message;
|
||||
if (item.status) s += ' ['+item.status+']';
|
||||
return s;
|
||||
});
|
||||
sawError.forEach(function(err) {
|
||||
if (!(expectedFailures && expectedFailures.test('Uncaught: '+err.message))) {
|
||||
str.push('Uncaught: '+err.message);
|
||||
}
|
||||
});
|
||||
if (sawError.length === 1 && str.length === 1) {
|
||||
done(sawError[0]); // for nicer stack trace
|
||||
} else {
|
||||
done(str.length ? new Error(str.join('\n\n')) : null);
|
||||
}
|
||||
} else {
|
||||
var bl = {};
|
||||
try {
|
||||
bl = JSON.parse(fs.readFileSync(blocklist_PATH, 'utf-8'));
|
||||
} catch (e) { /* ignore */ }
|
||||
bl[shortFile] = results.map(function(item) { return item.name; });
|
||||
sawError.forEach(function(err) {
|
||||
bl[shortFile].push('Uncaught: ' + err.message);
|
||||
});
|
||||
if (!bl[shortFile].length) { bl[shortFile] = undefined; }
|
||||
fs.writeFileSync(
|
||||
blocklist_PATH, JSON.stringify(bl, null, 2), 'utf-8'
|
||||
);
|
||||
done();
|
||||
}
|
||||
};
|
||||
window.add_start_callback(function() {
|
||||
haveTests = true;
|
||||
});
|
||||
window.add_completion_callback(function(tests, status) {
|
||||
isComplete = true;
|
||||
var failed = tests.filter(function(t) {
|
||||
if (t.status === t.TIMEOUT) { return true; /* never ok */ }
|
||||
var expectFail =
|
||||
expectedFailures ? expectedFailures.test(t.name) : false;
|
||||
var actualFail = (t.status === t.FAIL);
|
||||
return expectFail !== actualFail;
|
||||
});
|
||||
var report = failed.map(function(t) {
|
||||
var item = { name: t.name, message: t.message };
|
||||
if (t.status===t.TIMEOUT) { item.status = 'TIMEOUT'; }
|
||||
else if (t.status!==t.FAIL) { item.status = 'EXPECT FAIL'; }
|
||||
return item;
|
||||
});
|
||||
withResults(report);
|
||||
});
|
||||
// TODO(jessicajaniuk): window._run was removed to enable ESM support
|
||||
// try {
|
||||
// window._run(concatenatedScripts);
|
||||
// } catch (e) {
|
||||
// sawError.push(e);
|
||||
// }
|
||||
// try {
|
||||
// window._run(closeDocument);
|
||||
// } catch (e) {
|
||||
// sawError.push(e);
|
||||
// }
|
||||
if (!sawTestHarness) { withResults([]); }
|
||||
else if (sawError.length && !haveTests) { withResults([]); }
|
||||
};
|
||||
});
|
||||
});
|
||||
return harnessResult;
|
||||
};
|
||||
|
||||
// module.exports = harness(__dirname + '/web-platform-tests/html/dom',
|
||||
// __dirname + '/web-platform-tests/dom/nodes',
|
||||
// __dirname + '/web-platform-tests/dom/traversal',
|
||||
// __dirname + '/web-platform-tests/domparsing');
|
||||
Reference in New Issue
Block a user