Bug 1104433 part 2 - Update dom/imptests/testharness.js and friends; rs=Ms2ger

This commit is contained in:
Brian Birtles 2014-12-01 08:36:45 +09:00
parent 8609ff398c
commit 97ac3e753d
4 changed files with 1485 additions and 1425 deletions

View File

@ -31,7 +31,7 @@
return tokens; return tokens;
}; };
var parse = function (tokens) { var parse = function (tokens, opt) {
var line = 1; var line = 1;
tokens = tokens.slice(); tokens = tokens.slice();
@ -82,14 +82,41 @@
} }
}; };
var all_ws = function () { var all_ws = function (store, pea) { // pea == post extended attribute, tpea = same for types
var t = { type: "whitespace", value: "" }; var t = { type: "whitespace", value: "" };
while (true) { while (true) {
var w = ws(); var w = ws();
if (!w) break; if (!w) break;
t.value += w.value; t.value += w.value;
} }
if (t.value.length > 0) return t; if (t.value.length > 0) {
if (store) {
var w = t.value
, re = {
"ws": /^([\t\n\r ]+)/
, "line-comment": /^\/\/(.*)\n?/m
, "multiline-comment": /^\/\*((?:.|\n|\r)*?)\*\//
}
, wsTypes = []
;
for (var k in re) wsTypes.push(k);
while (w.length) {
var matched = false;
for (var i = 0, n = wsTypes.length; i < n; i++) {
var type = wsTypes[i];
w = w.replace(re[type], function (tok, m1) {
store.push({ type: type + (pea ? ("-" + pea) : ""), value: m1 });
matched = true;
return "";
});
if (matched) break;
}
if (matched) continue;
throw new Error("Surprising white space construct."); // this shouldn't happen
}
}
return t;
}
}; };
var integer_type = function () { var integer_type = function () {
@ -151,8 +178,15 @@
else if (consume(OTHER, "[")) { else if (consume(OTHER, "[")) {
all_ws(); all_ws();
consume(OTHER, "]") || error("Unterminated array type"); consume(OTHER, "]") || error("Unterminated array type");
if (!obj.array) obj.array = 1; if (!obj.array) {
else obj.array++; obj.array = 1;
obj.nullableArray = [obj.nullable];
}
else {
obj.array++;
obj.nullableArray.push(obj.nullable);
}
obj.nullable = false;
} }
else return; else return;
} }
@ -160,40 +194,46 @@
var single_type = function () { var single_type = function () {
var prim = primitive_type() var prim = primitive_type()
, ret = { sequence: false, nullable: false, array: false, union: false } , ret = { sequence: false, generic: null, nullable: false, array: false, union: false }
, name
, value
; ;
if (prim) { if (prim) {
ret.idlType = prim; ret.idlType = prim;
} }
else if (consume(ID, "sequence")) { else if (name = consume(ID)) {
value = name.value;
all_ws(); all_ws();
if (!consume(OTHER, "<")) { // Generic types
ret.idlType = "sequence"; if (consume(OTHER, "<")) {
} // backwards compat
else { if (value === "sequence") {
ret.sequence = true; ret.sequence = true;
ret.idlType = type() || error("Error parsing sequence type"); }
ret.generic = value;
ret.idlType = type() || error("Error parsing generic type " + value);
all_ws(); all_ws();
if (!consume(OTHER, ">")) error("Unterminated sequence"); if (!consume(OTHER, ">")) error("Unterminated generic type " + value);
all_ws(); all_ws();
if (consume(OTHER, "?")) ret.nullable = true; if (consume(OTHER, "?")) ret.nullable = true;
return ret; return ret;
} }
else {
ret.idlType = value;
}
} }
else { else {
var name = consume(ID); return;
if (!name) return;
ret.idlType = name.value;
} }
type_suffix(ret); type_suffix(ret);
if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable"); if (ret.nullable && !ret.array && ret.idlType === "any") error("Type any cannot be made nullable");
return ret; return ret;
}; };
var union_type = function () { var union_type = function () {
all_ws(); all_ws();
if (!consume(OTHER, "(")) return; if (!consume(OTHER, "(")) return;
var ret = { sequence: false, nullable: false, array: false, union: true, idlType: [] }; var ret = { sequence: false, generic: null, nullable: false, array: false, union: true, idlType: [] };
var fst = type() || error("Union type with no content"); var fst = type() || error("Union type with no content");
ret.idlType.push(fst); ret.idlType.push(fst);
while (true) { while (true) {
@ -211,16 +251,21 @@
return single_type() || union_type(); return single_type() || union_type();
}; };
var argument = function () { var argument = function (store) {
var ret = { optional: false, variadic: false }; var ret = { optional: false, variadic: false };
ret.extAttrs = extended_attrs(); ret.extAttrs = extended_attrs(store);
all_ws(); all_ws(store, "pea");
if (consume(ID, "optional")) { var opt_token = consume(ID, "optional");
if (opt_token) {
ret.optional = true; ret.optional = true;
all_ws(); all_ws();
} }
ret.idlType = type(); ret.idlType = type();
if (!ret.idlType) return; if (!ret.idlType) {
if (opt_token) tokens.unshift(opt_token);
return;
}
var type_token = last_token;
if (!ret.optional) { if (!ret.optional) {
all_ws(); all_ws();
if (tokens.length >= 3 && if (tokens.length >= 3 &&
@ -235,7 +280,12 @@
} }
} }
all_ws(); all_ws();
var name = consume(ID) || error("No name in argument"); var name = consume(ID);
if (!name) {
if (opt_token) tokens.unshift(opt_token);
tokens.unshift(type_token);
return;
}
ret.name = name.value; ret.name = name.value;
if (ret.optional) { if (ret.optional) {
all_ws(); all_ws();
@ -244,20 +294,33 @@
return ret; return ret;
}; };
var argument_list = function () { var argument_list = function (store) {
var arg = argument(), ret = []; var ret = []
if (!arg) return ret; , arg = argument(store ? ret : null)
;
if (!arg) return;
ret.push(arg); ret.push(arg);
while (true) { while (true) {
all_ws(); all_ws(store ? ret : null);
if (!consume(OTHER, ",")) return ret; if (!consume(OTHER, ",")) return ret;
all_ws(); var nxt = argument(store ? ret : null) || error("Trailing comma in arguments list");
var nxt = argument() || error("Trailing comma in arguments list");
ret.push(nxt); ret.push(nxt);
} }
}; };
var simple_extended_attr = function () { var type_pair = function () {
all_ws();
var k = type();
if (!k) return;
all_ws()
if (!consume(OTHER, ",")) return;
all_ws();
var v = type();
if (!v) return;
return [k, v];
};
var simple_extended_attr = function (store) {
all_ws(); all_ws();
var name = consume(ID); var name = consume(ID);
if (!name) return; if (!name) return;
@ -274,24 +337,35 @@
} }
all_ws(); all_ws();
if (consume(OTHER, "(")) { if (consume(OTHER, "(")) {
ret["arguments"] = argument_list(); var args, pair;
// [Constructor(DOMString str)]
if (args = argument_list(store)) {
ret["arguments"] = args;
}
// [MapClass(DOMString, DOMString)]
else if (pair = type_pair()) {
ret.typePair = pair;
}
// [Constructor()]
else {
ret["arguments"] = [];
}
all_ws(); all_ws();
consume(OTHER, ")") || error("Unclosed argument in extended attribute"); consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair");
} }
return ret; return ret;
}; };
// Note: we parse something simpler than the official syntax. It's all that ever // Note: we parse something simpler than the official syntax. It's all that ever
// seems to be used // seems to be used
var extended_attrs = function () { var extended_attrs = function (store) {
var eas = []; var eas = [];
all_ws(); all_ws(store);
if (!consume(OTHER, "[")) return eas; if (!consume(OTHER, "[")) return eas;
eas[0] = simple_extended_attr() || error("Extended attribute with not content"); eas[0] = simple_extended_attr(store) || error("Extended attribute with not content");
all_ws(); all_ws();
while (consume(OTHER, ",")) { while (consume(OTHER, ",")) {
all_ws(); eas.push(simple_extended_attr(store) || error("Trailing comma in extended attribute"));
eas.push(simple_extended_attr() || error("Trailing comma in extended attribute"));
all_ws(); all_ws();
} }
consume(OTHER, "]") || error("No end of extended attribute"); consume(OTHER, "]") || error("No end of extended attribute");
@ -314,8 +388,8 @@
} }
}; };
var const_ = function () { var const_ = function (store) {
all_ws(); all_ws(store, "pea");
if (!consume(ID, "const")) return; if (!consume(ID, "const")) return;
var ret = { type: "const", nullable: false }; var ret = { type: "const", nullable: false };
all_ws(); all_ws();
@ -352,14 +426,14 @@
} }
}; };
var operation_rest = function (ret) { var operation_rest = function (ret, store) {
all_ws(); all_ws();
if (!ret) ret = {}; if (!ret) ret = {};
var name = consume(ID); var name = consume(ID);
ret.name = name ? name.value : null; ret.name = name ? name.value : null;
all_ws(); all_ws();
consume(OTHER, "(") || error("Invalid operation"); consume(OTHER, "(") || error("Invalid operation");
ret["arguments"] = argument_list(); ret["arguments"] = argument_list(store) || [];
all_ws(); all_ws();
consume(OTHER, ")") || error("Unterminated operation"); consume(OTHER, ")") || error("Unterminated operation");
all_ws(); all_ws();
@ -367,8 +441,8 @@
return ret; return ret;
}; };
var callback = function () { var callback = function (store) {
all_ws(); all_ws(store, "pea");
var ret; var ret;
if (!consume(ID, "callback")) return; if (!consume(ID, "callback")) return;
all_ws(); all_ws();
@ -387,7 +461,7 @@
ret.idlType = return_type(); ret.idlType = return_type();
all_ws(); all_ws();
consume(OTHER, "(") || error("No arguments in callback"); consume(OTHER, "(") || error("No arguments in callback");
ret["arguments"] = argument_list(); ret["arguments"] = argument_list(store) || [];
all_ws(); all_ws();
consume(OTHER, ")") || error("Unterminated callback"); consume(OTHER, ")") || error("Unterminated callback");
all_ws(); all_ws();
@ -395,8 +469,8 @@
return ret; return ret;
}; };
var attribute = function () { var attribute = function (store) {
all_ws(); all_ws(store, "pea");
var grabbed = [] var grabbed = []
, ret = { , ret = {
type: "attribute" type: "attribute"
@ -454,8 +528,8 @@
return typ; return typ;
}; };
var operation = function () { var operation = function (store) {
all_ws(); all_ws(store, "pea");
var ret = { var ret = {
type: "operation" type: "operation"
, getter: false , getter: false
@ -478,13 +552,13 @@
if (ret.getter || ret.setter || ret.creator || ret.deleter || ret.legacycaller) { if (ret.getter || ret.setter || ret.creator || ret.deleter || ret.legacycaller) {
all_ws(); all_ws();
ret.idlType = return_type(); ret.idlType = return_type();
operation_rest(ret); operation_rest(ret, store);
return ret; return ret;
} }
if (consume(ID, "static")) { if (consume(ID, "static")) {
ret["static"] = true; ret["static"] = true;
ret.idlType = return_type(); ret.idlType = return_type();
operation_rest(ret); operation_rest(ret, store);
return ret; return ret;
} }
else if (consume(ID, "stringifier")) { else if (consume(ID, "stringifier")) {
@ -492,7 +566,7 @@
all_ws(); all_ws();
if (consume(OTHER, ";")) return ret; if (consume(OTHER, ";")) return ret;
ret.idlType = return_type(); ret.idlType = return_type();
operation_rest(ret); operation_rest(ret, store);
return ret; return ret;
} }
ret.idlType = return_type(); ret.idlType = return_type();
@ -513,7 +587,7 @@
return ret; return ret;
} }
else { else {
operation_rest(ret); operation_rest(ret, store);
return ret; return ret;
} }
}; };
@ -530,8 +604,8 @@
} }
}; };
var serialiser = function () { var serialiser = function (store) {
all_ws(); all_ws(store, "pea");
if (!consume(ID, "serializer")) return; if (!consume(ID, "serializer")) return;
var ret = { type: "serializer" }; var ret = { type: "serializer" };
all_ws(); all_ws();
@ -589,77 +663,84 @@
else { else {
ret.idlType = return_type(); ret.idlType = return_type();
all_ws(); all_ws();
ret.operation = operation_rest(); ret.operation = operation_rest(null, store);
} }
return ret; return ret;
}; };
var interface_ = function (isPartial) { var interface_ = function (isPartial, store) {
all_ws(); all_ws(isPartial ? null : store, "pea");
if (!consume(ID, "interface")) return; if (!consume(ID, "interface")) return;
all_ws(); all_ws();
var name = consume(ID) || error("No name for interface"); var name = consume(ID) || error("No name for interface");
var ret = { var mems = []
, ret = {
type: "interface" type: "interface"
, name: name.value , name: name.value
, partial: false , partial: false
, members: [] , members: mems
}; };
if (!isPartial) ret.inheritance = inheritance() || null; if (!isPartial) ret.inheritance = inheritance() || null;
all_ws(); all_ws();
consume(OTHER, "{") || error("Bodyless interface"); consume(OTHER, "{") || error("Bodyless interface");
while (true) { while (true) {
all_ws(); all_ws(store ? mems : null);
if (consume(OTHER, "}")) { if (consume(OTHER, "}")) {
all_ws(); all_ws();
consume(OTHER, ";") || error("Missing semicolon after interface"); consume(OTHER, ";") || error("Missing semicolon after interface");
return ret; return ret;
} }
var ea = extended_attrs(); var ea = extended_attrs(store ? mems : null);
all_ws(); all_ws();
var cnt = const_(); var cnt = const_(store ? mems : null);
if (cnt) { if (cnt) {
cnt.extAttrs = ea; cnt.extAttrs = ea;
ret.members.push(cnt); ret.members.push(cnt);
continue; continue;
} }
var mem = serialiser() || attribute() || operation() || error("Unknown member"); var mem = serialiser(store ? mems : null) ||
attribute(store ? mems : null) ||
operation(store ? mems : null) ||
error("Unknown member");
mem.extAttrs = ea; mem.extAttrs = ea;
ret.members.push(mem); ret.members.push(mem);
} }
}; };
var partial = function () { var partial = function (store) {
all_ws(); all_ws(store, "pea");
if (!consume(ID, "partial")) return; if (!consume(ID, "partial")) return;
var thing = dictionary(true) || interface_(true) || error("Partial doesn't apply to anything"); var thing = dictionary(true, store) ||
interface_(true, store) ||
error("Partial doesn't apply to anything");
thing.partial = true; thing.partial = true;
return thing; return thing;
}; };
var dictionary = function (isPartial) { var dictionary = function (isPartial, store) {
all_ws(); all_ws(isPartial ? null : store, "pea");
if (!consume(ID, "dictionary")) return; if (!consume(ID, "dictionary")) return;
all_ws(); all_ws();
var name = consume(ID) || error("No name for dictionary"); var name = consume(ID) || error("No name for dictionary");
var ret = { var mems = []
, ret = {
type: "dictionary" type: "dictionary"
, name: name.value , name: name.value
, partial: false , partial: false
, members: [] , members: mems
}; };
if (!isPartial) ret.inheritance = inheritance() || null; if (!isPartial) ret.inheritance = inheritance() || null;
all_ws(); all_ws();
consume(OTHER, "{") || error("Bodyless dictionary"); consume(OTHER, "{") || error("Bodyless dictionary");
while (true) { while (true) {
all_ws(); all_ws(store ? mems : null);
if (consume(OTHER, "}")) { if (consume(OTHER, "}")) {
all_ws(); all_ws();
consume(OTHER, ";") || error("Missing semicolon after dictionary"); consume(OTHER, ";") || error("Missing semicolon after dictionary");
return ret; return ret;
} }
var ea = extended_attrs(); var ea = extended_attrs(store ? mems : null);
all_ws(); all_ws(store ? mems : null, "pea");
var typ = type() || error("No type for dictionary member"); var typ = type() || error("No type for dictionary member");
all_ws(); all_ws();
var name = consume(ID) || error("No name for dictionary member"); var name = consume(ID) || error("No name for dictionary member");
@ -675,28 +756,29 @@
} }
}; };
var exception = function () { var exception = function (store) {
all_ws(); all_ws(store, "pea");
if (!consume(ID, "exception")) return; if (!consume(ID, "exception")) return;
all_ws(); all_ws();
var name = consume(ID) || error("No name for exception"); var name = consume(ID) || error("No name for exception");
var ret = { var mems = []
, ret = {
type: "exception" type: "exception"
, name: name.value , name: name.value
, members: [] , members: mems
}; };
ret.inheritance = inheritance() || null; ret.inheritance = inheritance() || null;
all_ws(); all_ws();
consume(OTHER, "{") || error("Bodyless exception"); consume(OTHER, "{") || error("Bodyless exception");
while (true) { while (true) {
all_ws(); all_ws(store ? mems : null);
if (consume(OTHER, "}")) { if (consume(OTHER, "}")) {
all_ws(); all_ws();
consume(OTHER, ";") || error("Missing semicolon after exception"); consume(OTHER, ";") || error("Missing semicolon after exception");
return ret; return ret;
} }
var ea = extended_attrs(); var ea = extended_attrs(store ? mems : null);
all_ws(); all_ws(store ? mems : null, "pea");
var cnt = const_(); var cnt = const_();
if (cnt) { if (cnt) {
cnt.extAttrs = ea; cnt.extAttrs = ea;
@ -718,21 +800,22 @@
} }
}; };
var enum_ = function () { var enum_ = function (store) {
all_ws(); all_ws(store, "pea");
if (!consume(ID, "enum")) return; if (!consume(ID, "enum")) return;
all_ws(); all_ws();
var name = consume(ID) || error("No name for enum"); var name = consume(ID) || error("No name for enum");
var ret = { var vals = []
, ret = {
type: "enum" type: "enum"
, name: name.value , name: name.value
, values: [] , values: vals
}; };
all_ws(); all_ws();
consume(OTHER, "{") || error("No curly for enum"); consume(OTHER, "{") || error("No curly for enum");
var saw_comma = false; var saw_comma = false;
while (true) { while (true) {
all_ws(); all_ws(store ? vals : null);
if (consume(OTHER, "}")) { if (consume(OTHER, "}")) {
all_ws(); all_ws();
if (saw_comma) error("Trailing comma in enum"); if (saw_comma) error("Trailing comma in enum");
@ -741,9 +824,10 @@
} }
var val = consume(STR) || error("Unexpected value in enum"); var val = consume(STR) || error("Unexpected value in enum");
ret.values.push(val.value.replace(/"/g, "")); ret.values.push(val.value.replace(/"/g, ""));
all_ws(); all_ws(store ? vals : null);
if (consume(OTHER, ",")) { if (consume(OTHER, ",")) {
all_ws(); if (store) vals.push({ type: "," });
all_ws(store ? vals : null);
saw_comma = true; saw_comma = true;
} }
else { else {
@ -752,15 +836,15 @@
} }
}; };
var typedef = function () { var typedef = function (store) {
all_ws(); all_ws(store, "pea");
if (!consume(ID, "typedef")) return; if (!consume(ID, "typedef")) return;
var ret = { var ret = {
type: "typedef" type: "typedef"
}; };
all_ws(); all_ws();
ret.typeExtAttrs = extended_attrs(); ret.typeExtAttrs = extended_attrs();
all_ws(); all_ws(store, "tpea");
ret.idlType = type() || error("No type in typedef"); ret.idlType = type() || error("No type in typedef");
all_ws(); all_ws();
var name = consume(ID) || error("No name in typedef"); var name = consume(ID) || error("No name in typedef");
@ -770,8 +854,8 @@
return ret; return ret;
}; };
var implements_ = function () { var implements_ = function (store) {
all_ws(); all_ws(store, "pea");
var target = consume(ID); var target = consume(ID);
if (!target) return; if (!target) return;
var w = all_ws(); var w = all_ws();
@ -794,24 +878,24 @@
} }
}; };
var definition = function () { var definition = function (store) {
return callback() || return callback(store) ||
interface_() || interface_(false, store) ||
partial() || partial(store) ||
dictionary() || dictionary(false, store) ||
exception() || exception(store) ||
enum_() || enum_(store) ||
typedef() || typedef(store) ||
implements_() implements_(store)
; ;
}; };
var definitions = function () { var definitions = function (store) {
if (!tokens.length) return []; if (!tokens.length) return [];
var defs = []; var defs = [];
while (true) { while (true) {
var ea = extended_attrs() var ea = extended_attrs(store ? defs : null)
, def = definition(); , def = definition(store ? defs : null);
if (!def) { if (!def) {
if (ea.length) error("Stray extended attributes"); if (ea.length) error("Stray extended attributes");
break; break;
@ -821,21 +905,20 @@
} }
return defs; return defs;
}; };
var res = definitions(); var res = definitions(opt.ws);
if (tokens.length) error("Unrecognised tokens"); if (tokens.length) error("Unrecognised tokens");
return res; return res;
}; };
var obj = { var inNode = typeof module !== "undefined" && module.exports
parse: function (str) { , obj = {
var tokens = tokenise(str); parse: function (str, opt) {
return parse(tokens); if (!opt) opt = {};
} var tokens = tokenise(str);
return parse(tokens, opt);
}
}; };
if (typeof module !== "undefined" && module.exports) {
module.exports = obj; if (inNode) module.exports = obj;
} else window.WebIDL2 = obj;
else {
window.WebIDL2 = obj;
}
}()); }());

View File

@ -8,131 +8,7 @@ policies and contribution forms [3].
[3] http://www.w3.org/2004/10/27-testcases [3] http://www.w3.org/2004/10/27-testcases
*/ */
/* /* For user documentation see docs/idlharness.md */
* This file automatically generates browser tests for WebIDL interfaces, using
* the testharness.js framework. To use, first include the following:
*
* <script src=/resources/testharness.js></script>
* <script src=/resources/testharnessreport.js></script>
* <script src=/resources/WebIDLParser.js></script>
* <script src=/resources/idlharness.js></script>
*
* Then you'll need some type of IDLs. Here's some script that can be run on a
* spec written in HTML, which will grab all the elements with class="idl",
* concatenate them, and replace the body so you can copy-paste:
*
var s = "";
[].forEach.call(document.getElementsByClassName("idl"), function(idl) {
//https://www.w3.org/Bugs/Public/show_bug.cgi?id=14914
if (!idl.classList.contains("extract"))
{
s += idl.textContent + "\n\n";
}
});
document.body.innerHTML = '<pre></pre>';
document.body.firstChild.textContent = s;
*
* (TODO: write this in Python or something so that it can be done from the
* command line instead.)
*
* Once you have that, put it in your script somehow. The easiest way is to
* embed it literally in an HTML file with <script type=text/plain> or similar,
* so that you don't have to do any escaping. Another possibility is to put it
* in a separate .idl file that's fetched via XHR or similar. Sample usage:
*
* var idl_array = new IdlArray();
* idl_array.add_untested_idls("interface Node { readonly attribute DOMString nodeName; };");
* idl_array.add_idls("interface Document : Node { readonly attribute DOMString URL; };");
* idl_array.add_objects({Document: ["document"]});
* idl_array.test();
*
* This tests that window.Document exists and meets all the requirements of
* WebIDL. It also tests that window.document (the result of evaluating the
* string "document") has URL and nodeName properties that behave as they
* should, and otherwise meets WebIDL's requirements for an object whose
* primary interface is Document. It does not test that window.Node exists,
* which is what you want if the Node interface is already tested in some other
* specification's suite and your specification only extends or refers to it.
* Of course, each IDL string can define many different things, and calls to
* add_objects() can register many different objects for different interfaces:
* this is a very simple example.
*
* TODO: Write assert_writable, assert_enumerable, assert_configurable and
* their inverses, and use those instead of just checking
* getOwnPropertyDescriptor.
*
* == Public methods of IdlArray ==
*
* IdlArray objects can be obtained with new IdlArray(). Anything not
* documented in this section should be considered an implementation detail,
* and outside callers should not use it.
*
* add_idls(idl_string):
* Parses idl_string (throwing on parse error) and adds the results to the
* IdlArray. All the definitions will be tested when you run test(). If
* some of the definitions refer to other definitions, those must be present
* too. For instance, if idl_string says that Document inherits from Node,
* the Node interface must also have been provided in some call to add_idls()
* or add_untested_idls().
*
* add_untested_idls(idl_string):
* Like add_idls(), but the definitions will not be tested. If an untested
* interface is added and then extended with a tested partial interface, the
* members of the partial interface will still be tested. Also, all the
* members will still be tested for objects added with add_objects(), because
* you probably want to test that (for instance) window.document has all the
* properties from Node, not just Document, even if the Node interface itself
* is tested in a different test suite.
*
* add_objects(dict):
* dict should be an object whose keys are the names of interfaces or
* exceptions, and whose values are arrays of strings. When an interface or
* exception is tested, every string registered for it with add_objects()
* will be evaluated, and tests will be run on the result to verify that it
* correctly implements that interface or exception. This is the only way to
* test anything about [NoInterfaceObject] interfaces, and there are many
* tests that can't be run on any interface without an object to fiddle with.
*
* The interface has to be the *primary* interface of all the objects
* provided. For example, don't pass {Node: ["document"]}, but rather
* {Document: ["document"]}. Assuming the Document interface was declared to
* inherit from Node, this will automatically test that document implements
* the Node interface too.
*
* Warning: methods will be called on any provided objects, in a manner that
* WebIDL requires be safe. For instance, if a method has mandatory
* arguments, the test suite will try calling it with too few arguments to
* see if it throws an exception. If an implementation incorrectly runs the
* function instead of throwing, this might have side effects, possibly even
* preventing the test suite from running correctly.
*
* prevent_multiple_testing(name):
* This is a niche method for use in case you're testing many objects that
* implement the same interfaces, and don't want to retest the same
* interfaces every single time. For instance, HTML defines many interfaces
* that all inherit from HTMLElement, so the HTML test suite has something
* like
* .add_objects({
* HTMLHtmlElement: ['document.documentElement'],
* HTMLHeadElement: ['document.head'],
* HTMLBodyElement: ['document.body'],
* ...
* })
* and so on for dozens of element types. This would mean that it would
* retest that each and every one of those elements implements HTMLElement,
* Element, and Node, which would be thousands of basically redundant tests.
* The test suite therefore calls prevent_multiple_testing("HTMLElement").
* This means that once one object has been tested to implement HTMLElement
* and its ancestors, no other object will be. Thus in the example code
* above, the harness would test that document.documentElement correctly
* implements HTMLHtmlElement, HTMLElement, Element, and Node; but
* document.head would only be tested for HTMLHeadElement, and so on for
* further objects.
*
* test():
* Run all tests. This should be called after you've called all other
* methods to add IDLs and objects.
*/
/** /**
* Notes for people who want to edit this file (not just use it as a library): * Notes for people who want to edit this file (not just use it as a library):
@ -550,6 +426,9 @@ IdlArray.prototype.assert_type_is = function(value, type)
return; return;
case "DOMString": case "DOMString":
case "ByteString":
case "USVString":
// TODO: https://github.com/w3c/testharness.js/issues/92
assert_equals(typeof value, "string"); assert_equals(typeof value, "string");
return; return;
@ -1312,6 +1191,249 @@ IdlInterface.prototype.test_self = function()
}.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property'); }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property');
}; };
//@}
IdlInterface.prototype.test_member_const = function(member)
//@{
{
test(function()
{
assert_own_property(window, this.name,
"window does not have own property " + format_value(this.name));
// "For each constant defined on an interface A, there must be
// a corresponding property on the interface object, if it
// exists."
assert_own_property(window[this.name], member.name);
// "The value of the property is that which is obtained by
// converting the constants IDL value to an ECMAScript
// value."
assert_equals(window[this.name][member.name], constValue(member.value),
"property has wrong value");
// "The property has attributes { [[Writable]]: false,
// [[Enumerable]]: true, [[Configurable]]: false }."
var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
assert_false("get" in desc, "property has getter");
assert_false("set" in desc, "property has setter");
assert_false(desc.writable, "property is writable");
assert_true(desc.enumerable, "property is not enumerable");
assert_false(desc.configurable, "property is configurable");
}.bind(this), this.name + " interface: constant " + member.name + " on interface object");
// "In addition, a property with the same characteristics must
// exist on the interface prototype object."
test(function()
{
assert_own_property(window, this.name,
"window does not have own property " + format_value(this.name));
if (this.has_extended_attribute("Callback")) {
assert_false("prototype" in window[this.name],
this.name + ' should not have a "prototype" property');
return;
}
assert_own_property(window[this.name], "prototype",
'interface "' + this.name + '" does not have own property "prototype"');
assert_own_property(window[this.name].prototype, member.name);
assert_equals(window[this.name].prototype[member.name], constValue(member.value),
"property has wrong value");
var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
assert_false("get" in desc, "property has getter");
assert_false("set" in desc, "property has setter");
assert_false(desc.writable, "property is writable");
assert_true(desc.enumerable, "property is not enumerable");
assert_false(desc.configurable, "property is configurable");
}.bind(this), this.name + " interface: constant " + member.name + " on interface prototype object");
};
//@}
IdlInterface.prototype.test_member_attribute = function(member)
//@{
{
test(function()
{
assert_own_property(window, this.name,
"window does not have own property " + format_value(this.name));
assert_own_property(window[this.name], "prototype",
'interface "' + this.name + '" does not have own property "prototype"');
if (member["static"]) {
assert_own_property(window[this.name], member.name,
"The interface object must have a property " +
format_value(member.name));
}
else
{
assert_true(member.name in window[this.name].prototype,
"The prototype object must have a property " +
format_value(member.name));
if (!member.has_extended_attribute("LenientThis")) {
assert_throws(new TypeError(), function() {
window[this.name].prototype[member.name];
}.bind(this), "getting property on prototype object must throw TypeError");
} else {
assert_equals(window[this.name].prototype[member.name], undefined,
"getting property on prototype object must return undefined");
}
do_interface_attribute_asserts(window[this.name].prototype, member);
}
}.bind(this), this.name + " interface: attribute " + member.name);
};
//@}
IdlInterface.prototype.test_member_operation = function(member)
//@{
{
test(function()
{
assert_own_property(window, this.name,
"window does not have own property " + format_value(this.name));
if (this.has_extended_attribute("Callback")) {
assert_false("prototype" in window[this.name],
this.name + ' should not have a "prototype" property');
return;
}
assert_own_property(window[this.name], "prototype",
'interface "' + this.name + '" does not have own property "prototype"');
// "For each unique identifier of an operation defined on the
// interface, there must be a corresponding property on the
// interface prototype object (if it is a regular operation) or
// the interface object (if it is a static operation), unless
// the effective overload set for that identifier and operation
// and with an argument count of 0 (for the ECMAScript language
// binding) has no entries."
//
var prototypeOrInterfaceObject;
if (member["static"]) {
assert_own_property(window[this.name], member.name,
"interface prototype object missing static operation");
prototypeOrInterfaceObject = window[this.name];
}
else
{
assert_own_property(window[this.name].prototype, member.name,
"interface prototype object missing non-static operation");
prototypeOrInterfaceObject = window[this.name].prototype;
}
var desc = Object.getOwnPropertyDescriptor(prototypeOrInterfaceObject, member.name);
// "The property has attributes { [[Writable]]: true,
// [[Enumerable]]: true, [[Configurable]]: true }."
assert_false("get" in desc, "property has getter");
assert_false("set" in desc, "property has setter");
assert_true(desc.writable, "property is not writable");
assert_true(desc.enumerable, "property is not enumerable");
assert_true(desc.configurable, "property is not configurable");
// "The value of the property is a Function object whose
// behavior is as follows . . ."
assert_equals(typeof prototypeOrInterfaceObject[member.name], "function",
"property must be a function");
// "The value of the Function objects “length” property is
// a Number determined as follows:
// ". . .
// "Return the length of the shortest argument list of the
// entries in S."
//
// TODO: Doesn't handle overloading or variadic arguments.
assert_equals(prototypeOrInterfaceObject[member.name].length,
member.arguments.filter(function(arg) {
return !arg.optional;
}).length,
"property has wrong .length");
// Make some suitable arguments
var args = member.arguments.map(function(arg) {
return create_suitable_object(arg.idlType);
});
// "Let O be a value determined as follows:
// ". . .
// "Otherwise, throw a TypeError."
// This should be hit if the operation is not static, there is
// no [ImplicitThis] attribute, and the this value is null.
//
// TODO: We currently ignore the [ImplicitThis] case.
if (!member["static"]) {
assert_throws(new TypeError(), function() {
window[this.name].prototype[member.name].apply(null, args);
}, "calling operation with this = null didn't throw TypeError");
}
// ". . . If O is not null and is also not a platform object
// that implements interface I, throw a TypeError."
//
// TODO: Test a platform object that implements some other
// interface. (Have to be sure to get inheritance right.)
assert_throws(new TypeError(), function() {
window[this.name].prototype[member.name].apply({}, args);
}, "calling operation with this = {} didn't throw TypeError");
}.bind(this), this.name + " interface: operation " + member.name +
"(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
")");
};
//@}
IdlInterface.prototype.test_member_stringifier = function(member)
//@{
{
test(function()
{
assert_own_property(window, this.name,
"window does not have own property " + format_value(this.name));
if (this.has_extended_attribute("Callback")) {
assert_false("prototype" in window[this.name],
this.name + ' should not have a "prototype" property');
return;
}
assert_own_property(window[this.name], "prototype",
'interface "' + this.name + '" does not have own property "prototype"');
// ". . . the property exists on the interface prototype object."
var interfacePrototypeObject = window[this.name].prototype;
assert_own_property(window[this.name].prototype, "toString",
"interface prototype object missing non-static operation");
var desc = Object.getOwnPropertyDescriptor(interfacePrototypeObject, "toString");
// "The property has attributes { [[Writable]]: B,
// [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the
// stringifier is unforgeable on the interface, and true otherwise."
assert_false("get" in desc, "property has getter");
assert_false("set" in desc, "property has setter");
assert_true(desc.writable, "property is not writable");
assert_true(desc.enumerable, "property is not enumerable");
assert_true(desc.configurable, "property is not configurable");
// "The value of the property is a Function object, which behaves as
// follows . . ."
assert_equals(typeof interfacePrototypeObject.toString, "function",
"property must be a function");
// "The value of the Function objects “length” property is the Number
// value 0."
assert_equals(interfacePrototypeObject.toString.length, 0,
"property has wrong .length");
// "Let O be the result of calling ToObject on the this value."
assert_throws(new TypeError(), function() {
window[this.name].prototype.toString.apply(null, []);
}, "calling stringifier with this = null didn't throw TypeError");
// "If O is not an object that implements the interface on which the
// stringifier was declared, then throw a TypeError."
//
// TODO: Test a platform object that implements some other
// interface. (Have to be sure to get inheritance right.)
assert_throws(new TypeError(), function() {
window[this.name].prototype.toString.apply({}, []);
}, "calling stringifier with this = {} didn't throw TypeError");
}.bind(this), this.name + " interface: stringifier");
};
//@} //@}
IdlInterface.prototype.test_members = function() IdlInterface.prototype.test_members = function()
//@{ //@{
@ -1319,195 +1441,37 @@ IdlInterface.prototype.test_members = function()
for (var i = 0; i < this.members.length; i++) for (var i = 0; i < this.members.length; i++)
{ {
var member = this.members[i]; var member = this.members[i];
if (member.untested) if (member.untested) {
{
continue; continue;
} }
if (member.type == "const")
{
test(function()
{
assert_own_property(window, this.name,
"window does not have own property " + format_value(this.name));
// "For each constant defined on an interface A, there must be switch (member.type) {
// a corresponding property on the interface object, if it case "const":
// exists." this.test_member_const(member);
assert_own_property(window[this.name], member.name); break;
// "The value of the property is that which is obtained by
// converting the constants IDL value to an ECMAScript
// value."
assert_equals(window[this.name][member.name], constValue(member.value),
"property has wrong value");
// "The property has attributes { [[Writable]]: false,
// [[Enumerable]]: true, [[Configurable]]: false }."
var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
assert_false("get" in desc, "property has getter");
assert_false("set" in desc, "property has setter");
assert_false(desc.writable, "property is writable");
assert_true(desc.enumerable, "property is not enumerable");
assert_false(desc.configurable, "property is configurable");
}.bind(this), this.name + " interface: constant " + member.name + " on interface object");
// "In addition, a property with the same characteristics must
// exist on the interface prototype object."
test(function()
{
assert_own_property(window, this.name,
"window does not have own property " + format_value(this.name));
if (this.has_extended_attribute("Callback")) { case "attribute":
assert_false("prototype" in window[this.name], // For unforgeable attributes, we do the checks in
this.name + ' should not have a "prototype" property'); // test_interface_of instead.
return; if (!member.has_extended_attribute("Unforgeable")) {
} this.test_member_attribute(member);
assert_own_property(window[this.name], "prototype",
'interface "' + this.name + '" does not have own property "prototype"');
assert_own_property(window[this.name].prototype, member.name);
assert_equals(window[this.name].prototype[member.name], constValue(member.value),
"property has wrong value");
var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
assert_false("get" in desc, "property has getter");
assert_false("set" in desc, "property has setter");
assert_false(desc.writable, "property is writable");
assert_true(desc.enumerable, "property is not enumerable");
assert_false(desc.configurable, "property is configurable");
}.bind(this), this.name + " interface: constant " + member.name + " on interface prototype object");
}
else if (member.type == "attribute")
{
if (member.has_extended_attribute("Unforgeable"))
{
// We do the checks in test_interface_of instead
continue;
} }
test(function() break;
{
assert_own_property(window, this.name,
"window does not have own property " + format_value(this.name));
assert_own_property(window[this.name], "prototype",
'interface "' + this.name + '" does not have own property "prototype"');
if (member["static"]) { case "operation":
assert_own_property(window[this.name], member.name,
"The interface object must have a property " +
format_value(member.name));
}
else
{
assert_true(member.name in window[this.name].prototype,
"The prototype object must have a property " +
format_value(member.name));
// TODO: Needs to test for LenientThis.
assert_throws(new TypeError(), function() {
window[this.name].prototype[member.name];
}.bind(this), "getting property on prototype object must throw TypeError");
do_interface_attribute_asserts(window[this.name].prototype, member);
}
}.bind(this), this.name + " interface: attribute " + member.name);
}
else if (member.type == "operation")
{
// TODO: Need to correctly handle multiple operations with the same // TODO: Need to correctly handle multiple operations with the same
// identifier. // identifier.
if (!member.name) if (member.name) {
{ this.test_member_operation(member);
// Unnamed getter or such } else if (member.stringifier) {
continue; this.test_member_stringifier(member);
} }
test(function() break;
{
assert_own_property(window, this.name,
"window does not have own property " + format_value(this.name));
if (this.has_extended_attribute("Callback")) { default:
assert_false("prototype" in window[this.name], // TODO: check more member types.
this.name + ' should not have a "prototype" property'); break;
return;
}
assert_own_property(window[this.name], "prototype",
'interface "' + this.name + '" does not have own property "prototype"');
// "For each unique identifier of an operation defined on the
// interface, there must be a corresponding property on the
// interface prototype object (if it is a regular operation) or
// the interface object (if it is a static operation), unless
// the effective overload set for that identifier and operation
// and with an argument count of 0 (for the ECMAScript language
// binding) has no entries."
//
var prototypeOrInterfaceObject;
if (member["static"]) {
assert_own_property(window[this.name], member.name,
"interface prototype object missing static operation");
prototypeOrInterfaceObject = window[this.name];
}
else
{
assert_own_property(window[this.name].prototype, member.name,
"interface prototype object missing non-static operation");
prototypeOrInterfaceObject = window[this.name].prototype;
}
var desc = Object.getOwnPropertyDescriptor(prototypeOrInterfaceObject, member.name);
// "The property has attributes { [[Writable]]: true,
// [[Enumerable]]: true, [[Configurable]]: true }."
assert_false("get" in desc, "property has getter");
assert_false("set" in desc, "property has setter");
assert_true(desc.writable, "property is not writable");
assert_true(desc.enumerable, "property is not enumerable");
assert_true(desc.configurable, "property is not configurable");
// "The value of the property is a Function object whose
// behavior is as follows . . ."
assert_equals(typeof prototypeOrInterfaceObject[member.name], "function",
"property must be a function");
// "The value of the Function objects “length” property is
// a Number determined as follows:
// ". . .
// "Return the length of the shortest argument list of the
// entries in S."
//
// TODO: Doesn't handle overloading or variadic arguments.
assert_equals(prototypeOrInterfaceObject[member.name].length,
member.arguments.filter(function(arg) {
return !arg.optional;
}).length,
"property has wrong .length");
// Make some suitable arguments
var args = member.arguments.map(function(arg) {
return create_suitable_object(arg.idlType);
});
// "Let O be a value determined as follows:
// ". . .
// "Otherwise, throw a TypeError."
// This should be hit if the operation is not static, there is
// no [ImplicitThis] attribute, and the this value is null.
//
// TODO: We currently ignore the [ImplicitThis] case.
if (!member["static"]) {
assert_throws(new TypeError(), function() {
window[this.name].prototype[member.name].apply(null, args);
}, "calling operation with this = null didn't throw TypeError");
}
// ". . . If O is not null and is also not a platform object
// that implements interface I, throw a TypeError."
//
// TODO: Test a platform object that implements some other
// interface. (Have to be sure to get inheritance right.)
assert_throws(new TypeError(), function() {
window[this.name].prototype[member.name].apply({}, args);
}, "calling operation with this = {} didn't throw TypeError");
}.bind(this), this.name + " interface: operation " + member.name +
"(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
")");
} }
// TODO: check more member types, like stringifier
} }
}; };
@ -1748,11 +1712,14 @@ function do_interface_attribute_asserts(obj, member)
// value 0." // value 0."
assert_equals(typeof desc.get, "function", "getter must be Function"); assert_equals(typeof desc.get, "function", "getter must be Function");
assert_equals(desc.get.length, 0, "getter length must be 0"); assert_equals(desc.get.length, 0, "getter length must be 0");
// TODO: Account for LenientThis if (!member.has_extended_attribute("LenientThis")) {
assert_throws(new TypeError(), function() assert_throws(new TypeError(), function() {
{ desc.get.call({});
desc.get.call({}); }.bind(this), "calling getter on wrong object type must throw TypeError");
}.bind(this), "calling getter on wrong object type must throw TypeError"); } else {
assert_equals(desc.get.call({}), undefined,
"calling getter on wrong object type must return undefined");
}
// TODO: Test calling setter on the interface prototype (should throw // TODO: Test calling setter on the interface prototype (should throw
// TypeError in most cases). // TypeError in most cases).
@ -1774,6 +1741,14 @@ function do_interface_attribute_asserts(obj, member)
{ {
assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes"); assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes");
assert_equals(desc.set.length, 1, "setter length must be 1"); assert_equals(desc.set.length, 1, "setter length must be 1");
if (!member.has_extended_attribute("LenientThis")) {
assert_throws(new TypeError(), function() {
desc.set.call({});
}.bind(this), "calling setter on wrong object type must throw TypeError");
} else {
assert_equals(desc.set.call({}), undefined,
"calling setter on wrong object type must return undefined");
}
} }
} }
//@} //@}
@ -1826,6 +1801,8 @@ function create_suitable_object(type)
return 7; return 7;
case "DOMString": case "DOMString":
case "ByteString":
case "USVString":
return "foo"; return "foo";
case "object": case "object":

View File

@ -90,3 +90,18 @@ table#results span.actual {
white-space:pre; white-space:pre;
} }
span.ok {
color:green;
}
tr.error {
color:red;
}
span.timeout {
color:red;
}
span.ok, span.timeout, span.error {
font-variant:small-caps;
}

File diff suppressed because it is too large Load Diff