OLD | NEW |
(Empty) | |
| 1 /*global self*/ |
| 2 /*jshint latedef: nofunc*/ |
| 3 /* |
| 4 Distributed under both the W3C Test Suite License [1] and the W3C |
| 5 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the |
| 6 policies and contribution forms [3]. |
| 7 |
| 8 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license |
| 9 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license |
| 10 [3] http://www.w3.org/2004/10/27-testcases |
| 11 */ |
| 12 |
| 13 /* Documentation is in docs/api.md */ |
| 14 |
| 15 (function () |
| 16 { |
| 17 var debug = false; |
| 18 // default timeout is 10 seconds, test can override if needed |
| 19 var settings = { |
| 20 output:true, |
| 21 harness_timeout:{ |
| 22 "normal":10000, |
| 23 "long":60000 |
| 24 }, |
| 25 test_timeout:null |
| 26 }; |
| 27 |
| 28 var xhtml_ns = "http://www.w3.org/1999/xhtml"; |
| 29 |
| 30 // script_prefix is used by Output.prototype.show_results() to figure out |
| 31 // where to get testharness.css from. It's enclosed in an extra closure to |
| 32 // not pollute the library's namespace with variables like "src". |
| 33 var script_prefix = null; |
| 34 (function () |
| 35 { |
| 36 var scripts = document.getElementsByTagName("script"); |
| 37 for (var i = 0; i < scripts.length; i++) { |
| 38 var src; |
| 39 if (scripts[i].src) { |
| 40 src = scripts[i].src; |
| 41 } else if (scripts[i].href) { |
| 42 //SVG case |
| 43 src = scripts[i].href.baseVal; |
| 44 } |
| 45 |
| 46 if (src && src.slice(src.length - "testharness.js".length) === "test
harness.js") { |
| 47 script_prefix = src.slice(0, src.length - "testharness.js".lengt
h); |
| 48 break; |
| 49 } |
| 50 } |
| 51 })(); |
| 52 |
| 53 /* |
| 54 * API functions |
| 55 */ |
| 56 |
| 57 var name_counter = 0; |
| 58 function next_default_name() |
| 59 { |
| 60 //Don't use document.title to work around an Opera bug in XHTML document
s |
| 61 var title = document.getElementsByTagName("title")[0]; |
| 62 var prefix = (title && title.firstChild && title.firstChild.data) || "Un
titled"; |
| 63 var suffix = name_counter > 0 ? " " + name_counter : ""; |
| 64 name_counter++; |
| 65 return prefix + suffix; |
| 66 } |
| 67 |
| 68 function test(func, name, properties) |
| 69 { |
| 70 var test_name = name ? name : next_default_name(); |
| 71 properties = properties ? properties : {}; |
| 72 var test_obj = new Test(test_name, properties); |
| 73 test_obj.step(func, test_obj, test_obj); |
| 74 if (test_obj.phase === test_obj.phases.STARTED) { |
| 75 test_obj.done(); |
| 76 } |
| 77 } |
| 78 |
| 79 function async_test(func, name, properties) |
| 80 { |
| 81 if (typeof func !== "function") { |
| 82 properties = name; |
| 83 name = func; |
| 84 func = null; |
| 85 } |
| 86 var test_name = name ? name : next_default_name(); |
| 87 properties = properties ? properties : {}; |
| 88 var test_obj = new Test(test_name, properties); |
| 89 if (func) { |
| 90 test_obj.step(func, test_obj, test_obj); |
| 91 } |
| 92 return test_obj; |
| 93 } |
| 94 |
| 95 function setup(func_or_properties, maybe_properties) |
| 96 { |
| 97 var func = null; |
| 98 var properties = {}; |
| 99 if (arguments.length === 2) { |
| 100 func = func_or_properties; |
| 101 properties = maybe_properties; |
| 102 } else if (func_or_properties instanceof Function) { |
| 103 func = func_or_properties; |
| 104 } else { |
| 105 properties = func_or_properties; |
| 106 } |
| 107 tests.setup(func, properties); |
| 108 output.setup(properties); |
| 109 } |
| 110 |
| 111 function done() { |
| 112 if (tests.tests.length === 0) { |
| 113 tests.set_file_is_test(); |
| 114 } |
| 115 if (tests.file_is_test) { |
| 116 tests.tests[0].done(); |
| 117 } |
| 118 tests.end_wait(); |
| 119 } |
| 120 |
| 121 function generate_tests(func, args, properties) { |
| 122 forEach(args, function(x, i) |
| 123 { |
| 124 var name = x[0]; |
| 125 test(function() |
| 126 { |
| 127 func.apply(this, x.slice(1)); |
| 128 }, |
| 129 name, |
| 130 Array.isArray(properties) ? properties[i] : properties)
; |
| 131 }); |
| 132 } |
| 133 |
| 134 function on_event(object, event, callback) |
| 135 { |
| 136 object.addEventListener(event, callback, false); |
| 137 } |
| 138 |
| 139 expose(test, 'test'); |
| 140 expose(async_test, 'async_test'); |
| 141 expose(generate_tests, 'generate_tests'); |
| 142 expose(setup, 'setup'); |
| 143 expose(done, 'done'); |
| 144 expose(on_event, 'on_event'); |
| 145 |
| 146 /* |
| 147 * Return a string truncated to the given length, with ... added at the end |
| 148 * if it was longer. |
| 149 */ |
| 150 function truncate(s, len) |
| 151 { |
| 152 if (s.length > len) { |
| 153 return s.substring(0, len - 3) + "..."; |
| 154 } |
| 155 return s; |
| 156 } |
| 157 |
| 158 /* |
| 159 * Return true if object is probably a Node object. |
| 160 */ |
| 161 function is_node(object) |
| 162 { |
| 163 // I use duck-typing instead of instanceof, because |
| 164 // instanceof doesn't work if the node is from another window (like an |
| 165 // iframe's contentWindow): |
| 166 // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295 |
| 167 if ("nodeType" in object && |
| 168 "nodeName" in object && |
| 169 "nodeValue" in object && |
| 170 "childNodes" in object) { |
| 171 try { |
| 172 object.nodeType; |
| 173 } catch (e) { |
| 174 // The object is probably Node.prototype or another prototype |
| 175 // object that inherits from it, and not a Node instance. |
| 176 return false; |
| 177 } |
| 178 return true; |
| 179 } |
| 180 return false; |
| 181 } |
| 182 |
| 183 /* |
| 184 * Convert a value to a nice, human-readable string |
| 185 */ |
| 186 function format_value(val, seen) |
| 187 { |
| 188 if (!seen) { |
| 189 seen = []; |
| 190 } |
| 191 if (typeof val === "object" && val !== null) { |
| 192 if (seen.indexOf(val) >= 0) { |
| 193 return "[...]"; |
| 194 } |
| 195 seen.push(val); |
| 196 } |
| 197 if (Array.isArray(val)) { |
| 198 return "[" + val.map(function(x) {return format_value(x, seen);}).jo
in(", ") + "]"; |
| 199 } |
| 200 |
| 201 switch (typeof val) { |
| 202 case "string": |
| 203 val = val.replace("\\", "\\\\"); |
| 204 for (var i = 0; i < 32; i++) { |
| 205 var replace = "\\"; |
| 206 switch (i) { |
| 207 case 0: replace += "0"; break; |
| 208 case 1: replace += "x01"; break; |
| 209 case 2: replace += "x02"; break; |
| 210 case 3: replace += "x03"; break; |
| 211 case 4: replace += "x04"; break; |
| 212 case 5: replace += "x05"; break; |
| 213 case 6: replace += "x06"; break; |
| 214 case 7: replace += "x07"; break; |
| 215 case 8: replace += "b"; break; |
| 216 case 9: replace += "t"; break; |
| 217 case 10: replace += "n"; break; |
| 218 case 11: replace += "v"; break; |
| 219 case 12: replace += "f"; break; |
| 220 case 13: replace += "r"; break; |
| 221 case 14: replace += "x0e"; break; |
| 222 case 15: replace += "x0f"; break; |
| 223 case 16: replace += "x10"; break; |
| 224 case 17: replace += "x11"; break; |
| 225 case 18: replace += "x12"; break; |
| 226 case 19: replace += "x13"; break; |
| 227 case 20: replace += "x14"; break; |
| 228 case 21: replace += "x15"; break; |
| 229 case 22: replace += "x16"; break; |
| 230 case 23: replace += "x17"; break; |
| 231 case 24: replace += "x18"; break; |
| 232 case 25: replace += "x19"; break; |
| 233 case 26: replace += "x1a"; break; |
| 234 case 27: replace += "x1b"; break; |
| 235 case 28: replace += "x1c"; break; |
| 236 case 29: replace += "x1d"; break; |
| 237 case 30: replace += "x1e"; break; |
| 238 case 31: replace += "x1f"; break; |
| 239 } |
| 240 val = val.replace(RegExp(String.fromCharCode(i), "g"), replace); |
| 241 } |
| 242 return '"' + val.replace(/"/g, '\\"') + '"'; |
| 243 case "boolean": |
| 244 case "undefined": |
| 245 return String(val); |
| 246 case "number": |
| 247 // In JavaScript, -0 === 0 and String(-0) == "0", so we have to |
| 248 // special-case. |
| 249 if (val === -0 && 1/val === -Infinity) { |
| 250 return "-0"; |
| 251 } |
| 252 return String(val); |
| 253 case "object": |
| 254 if (val === null) { |
| 255 return "null"; |
| 256 } |
| 257 |
| 258 // Special-case Node objects, since those come up a lot in my tests.
I |
| 259 // ignore namespaces. |
| 260 if (is_node(val)) { |
| 261 switch (val.nodeType) { |
| 262 case Node.ELEMENT_NODE: |
| 263 var ret = "<" + val.localName; |
| 264 for (var i = 0; i < val.attributes.length; i++) { |
| 265 ret += " " + val.attributes[i].name + '="' + val.attribu
tes[i].value + '"'; |
| 266 } |
| 267 ret += ">" + val.innerHTML + "</" + val.localName + ">"; |
| 268 return "Element node " + truncate(ret, 60); |
| 269 case Node.TEXT_NODE: |
| 270 return 'Text node "' + truncate(val.data, 60) + '"'; |
| 271 case Node.PROCESSING_INSTRUCTION_NODE: |
| 272 return "ProcessingInstruction node with target " + format_va
lue(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 6
0)); |
| 273 case Node.COMMENT_NODE: |
| 274 return "Comment node <!--" + truncate(val.data, 60) + "-->"; |
| 275 case Node.DOCUMENT_NODE: |
| 276 return "Document node with " + val.childNodes.length + (val.
childNodes.length == 1 ? " child" : " children"); |
| 277 case Node.DOCUMENT_TYPE_NODE: |
| 278 return "DocumentType node"; |
| 279 case Node.DOCUMENT_FRAGMENT_NODE: |
| 280 return "DocumentFragment node with " + val.childNodes.length
+ (val.childNodes.length == 1 ? " child" : " children"); |
| 281 default: |
| 282 return "Node object of unknown type"; |
| 283 } |
| 284 } |
| 285 |
| 286 /* falls through */ |
| 287 default: |
| 288 return typeof val + ' "' + truncate(String(val), 60) + '"'; |
| 289 } |
| 290 } |
| 291 expose(format_value, "format_value"); |
| 292 |
| 293 /* |
| 294 * Assertions |
| 295 */ |
| 296 |
| 297 function assert_true(actual, description) |
| 298 { |
| 299 assert(actual === true, "assert_true", description, |
| 300 "expected true got ${actual}", {actual:actual}); |
| 301 } |
| 302 expose(assert_true, "assert_true"); |
| 303 |
| 304 function assert_false(actual, description) |
| 305 { |
| 306 assert(actual === false, "assert_false", description, |
| 307 "expected false got ${actual}", {actual:actual}
); |
| 308 } |
| 309 expose(assert_false, "assert_false"); |
| 310 |
| 311 function same_value(x, y) { |
| 312 if (y !== y) { |
| 313 //NaN case |
| 314 return x !== x; |
| 315 } |
| 316 if (x === 0 && y === 0) { |
| 317 //Distinguish +0 and -0 |
| 318 return 1/x === 1/y; |
| 319 } |
| 320 return x === y; |
| 321 } |
| 322 |
| 323 function assert_equals(actual, expected, description) |
| 324 { |
| 325 /* |
| 326 * Test if two primitives are equal or two objects |
| 327 * are the same object |
| 328 */ |
| 329 if (typeof actual != typeof expected) { |
| 330 assert(false, "assert_equals", description, |
| 331 "expected (" + typeof expected + ") ${expected} but go
t (" + typeof actual + ") ${actual}", |
| 332 {expected:expected, actual:actual}); |
| 333 return; |
| 334 } |
| 335 assert(same_value(actual, expected), "assert_equals", description, |
| 336 "expected ${expected} but got ${act
ual}", |
| 337 {expected:expected, actual:actual})
; |
| 338 } |
| 339 expose(assert_equals, "assert_equals"); |
| 340 |
| 341 function assert_not_equals(actual, expected, description) |
| 342 { |
| 343 /* |
| 344 * Test if two primitives are unequal or two objects |
| 345 * are different objects |
| 346 */ |
| 347 assert(!same_value(actual, expected), "assert_not_equals", description, |
| 348 "got disallowed value ${actual}", |
| 349 {actual:actual}); |
| 350 } |
| 351 expose(assert_not_equals, "assert_not_equals"); |
| 352 |
| 353 function assert_in_array(actual, expected, description) |
| 354 { |
| 355 assert(expected.indexOf(actual) != -1, "assert_in_array", description, |
| 356 "value ${actual} not in array ${e
xpected}", |
| 357 {actual:actual, expected:expected
}); |
| 358 } |
| 359 expose(assert_in_array, "assert_in_array"); |
| 360 |
| 361 function assert_object_equals(actual, expected, description) |
| 362 { |
| 363 //This needs to be improved a great deal |
| 364 function check_equal(actual, expected, stack) |
| 365 { |
| 366 stack.push(actual); |
| 367 |
| 368 var p; |
| 369 for (p in actual) { |
| 370 assert(expected.hasOwnProperty(p), "assert_object_equals", desc
ription, |
| 371 "unexpected property ${p}",
{p:p}); |
| 372 |
| 373 if (typeof actual[p] === "object" && actual[p] !== null) { |
| 374 if (stack.indexOf(actual[p]) === -1) { |
| 375 check_equal(actual[p], expected[p], stack); |
| 376 } |
| 377 } else { |
| 378 assert(same_value(actual[p], expected[p]), "assert_object_e
quals", description, |
| 379 "property ${p} expected $
{expected} got ${actual}", |
| 380 {p:p, expected:expected,
actual:actual}); |
| 381 } |
| 382 } |
| 383 for (p in expected) { |
| 384 assert(actual.hasOwnProperty(p), |
| 385 "assert_object_equals", description, |
| 386 "expected property ${p} missing", {p:p}); |
| 387 } |
| 388 stack.pop(); |
| 389 } |
| 390 check_equal(actual, expected, []); |
| 391 } |
| 392 expose(assert_object_equals, "assert_object_equals"); |
| 393 |
| 394 function assert_array_equals(actual, expected, description) |
| 395 { |
| 396 assert(actual.length === expected.length, |
| 397 "assert_array_equals", description, |
| 398 "lengths differ, expected ${expected} got ${actual}", |
| 399 {expected:expected.length, actual:actual.length}); |
| 400 |
| 401 for (var i = 0; i < actual.length; i++) { |
| 402 assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), |
| 403 "assert_array_equals", description, |
| 404 "property ${i}, property expected to be $expected but was $ac
tual", |
| 405 {i:i, expected:expected.hasOwnProperty(i) ? "present" : "miss
ing", |
| 406 actual:actual.hasOwnProperty(i) ? "present" : "missing"}); |
| 407 assert(same_value(expected[i], actual[i]), |
| 408 "assert_array_equals", description, |
| 409 "property ${i}, expected ${expected} but got ${actual}", |
| 410 {i:i, expected:expected[i], actual:actual[i]}); |
| 411 } |
| 412 } |
| 413 expose(assert_array_equals, "assert_array_equals"); |
| 414 |
| 415 function assert_approx_equals(actual, expected, epsilon, description) |
| 416 { |
| 417 /* |
| 418 * Test if two primitive numbers are equal withing +/- epsilon |
| 419 */ |
| 420 assert(typeof actual === "number", |
| 421 "assert_approx_equals", description, |
| 422 "expected a number but got a ${type_actual}", |
| 423 {type_actual:typeof actual}); |
| 424 |
| 425 assert(Math.abs(actual - expected) <= epsilon, |
| 426 "assert_approx_equals", description, |
| 427 "expected ${expected} +/- ${epsilon} but got ${actual}", |
| 428 {expected:expected, actual:actual, epsilon:epsilon}); |
| 429 } |
| 430 expose(assert_approx_equals, "assert_approx_equals"); |
| 431 |
| 432 function assert_less_than(actual, expected, description) |
| 433 { |
| 434 /* |
| 435 * Test if a primitive number is less than another |
| 436 */ |
| 437 assert(typeof actual === "number", |
| 438 "assert_less_than", description, |
| 439 "expected a number but got a ${type_actual}", |
| 440 {type_actual:typeof actual}); |
| 441 |
| 442 assert(actual < expected, |
| 443 "assert_less_than", description, |
| 444 "expected a number less than ${expected} but got ${actual}", |
| 445 {expected:expected, actual:actual}); |
| 446 } |
| 447 expose(assert_less_than, "assert_less_than"); |
| 448 |
| 449 function assert_greater_than(actual, expected, description) |
| 450 { |
| 451 /* |
| 452 * Test if a primitive number is greater than another |
| 453 */ |
| 454 assert(typeof actual === "number", |
| 455 "assert_greater_than", description, |
| 456 "expected a number but got a ${type_actual}", |
| 457 {type_actual:typeof actual}); |
| 458 |
| 459 assert(actual > expected, |
| 460 "assert_greater_than", description, |
| 461 "expected a number greater than ${expected} but got ${actual}", |
| 462 {expected:expected, actual:actual}); |
| 463 } |
| 464 expose(assert_greater_than, "assert_greater_than"); |
| 465 |
| 466 function assert_less_than_equal(actual, expected, description) |
| 467 { |
| 468 /* |
| 469 * Test if a primitive number is less than or equal to another |
| 470 */ |
| 471 assert(typeof actual === "number", |
| 472 "assert_less_than_equal", description, |
| 473 "expected a number but got a ${type_actual}", |
| 474 {type_actual:typeof actual}); |
| 475 |
| 476 assert(actual <= expected, |
| 477 "assert_less_than", description, |
| 478 "expected a number less than or equal to ${expected} but got ${ac
tual}", |
| 479 {expected:expected, actual:actual}); |
| 480 } |
| 481 expose(assert_less_than_equal, "assert_less_than_equal"); |
| 482 |
| 483 function assert_greater_than_equal(actual, expected, description) |
| 484 { |
| 485 /* |
| 486 * Test if a primitive number is greater than or equal to another |
| 487 */ |
| 488 assert(typeof actual === "number", |
| 489 "assert_greater_than_equal", description, |
| 490 "expected a number but got a ${type_actual}", |
| 491 {type_actual:typeof actual}); |
| 492 |
| 493 assert(actual >= expected, |
| 494 "assert_greater_than_equal", description, |
| 495 "expected a number greater than or equal to ${expected} but got $
{actual}", |
| 496 {expected:expected, actual:actual}); |
| 497 } |
| 498 expose(assert_greater_than_equal, "assert_greater_than_equal"); |
| 499 |
| 500 function assert_regexp_match(actual, expected, description) { |
| 501 /* |
| 502 * Test if a string (actual) matches a regexp (expected) |
| 503 */ |
| 504 assert(expected.test(actual), |
| 505 "assert_regexp_match", description, |
| 506 "expected ${expected} but got ${actual}", |
| 507 {expected:expected, actual:actual}); |
| 508 } |
| 509 expose(assert_regexp_match, "assert_regexp_match"); |
| 510 |
| 511 function assert_class_string(object, class_string, description) { |
| 512 assert_equals({}.toString.call(object), "[object " + class_string + "]", |
| 513 description); |
| 514 } |
| 515 expose(assert_class_string, "assert_class_string"); |
| 516 |
| 517 |
| 518 function _assert_own_property(name) { |
| 519 return function(object, property_name, description) |
| 520 { |
| 521 assert(object.hasOwnProperty(property_name), |
| 522 name, description, |
| 523 "expected property ${p} missing", {p:property_name}); |
| 524 }; |
| 525 } |
| 526 expose(_assert_own_property("assert_exists"), "assert_exists"); |
| 527 expose(_assert_own_property("assert_own_property"), "assert_own_property"); |
| 528 |
| 529 function assert_not_exists(object, property_name, description) |
| 530 { |
| 531 assert(!object.hasOwnProperty(property_name), |
| 532 "assert_not_exists", description, |
| 533 "unexpected property ${p} found", {p:property_name}); |
| 534 } |
| 535 expose(assert_not_exists, "assert_not_exists"); |
| 536 |
| 537 function _assert_inherits(name) { |
| 538 return function (object, property_name, description) |
| 539 { |
| 540 assert(typeof object === "object", |
| 541 name, description, |
| 542 "provided value is not an object"); |
| 543 |
| 544 assert("hasOwnProperty" in object, |
| 545 name, description, |
| 546 "provided value is an object but has no hasOwnProperty method
"); |
| 547 |
| 548 assert(!object.hasOwnProperty(property_name), |
| 549 name, description, |
| 550 "property ${p} found on object expected in prototype chain", |
| 551 {p:property_name}); |
| 552 |
| 553 assert(property_name in object, |
| 554 name, description, |
| 555 "property ${p} not found in prototype chain", |
| 556 {p:property_name}); |
| 557 }; |
| 558 } |
| 559 expose(_assert_inherits("assert_inherits"), "assert_inherits"); |
| 560 expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute"); |
| 561 |
| 562 function assert_readonly(object, property_name, description) |
| 563 { |
| 564 var initial_value = object[property_name]; |
| 565 try { |
| 566 //Note that this can have side effects in the case where |
| 567 //the property has PutForwards |
| 568 object[property_name] = initial_value + "a"; //XXX use some other v
alue here? |
| 569 assert(same_value(object[property_name], initial_value), |
| 570 "assert_readonly", description, |
| 571 "changing property ${p} succeeded", |
| 572 {p:property_name}); |
| 573 } finally { |
| 574 object[property_name] = initial_value; |
| 575 } |
| 576 } |
| 577 expose(assert_readonly, "assert_readonly"); |
| 578 |
| 579 function assert_throws(code, func, description) |
| 580 { |
| 581 try { |
| 582 func.call(this); |
| 583 assert(false, "assert_throws", description, |
| 584 "${func} did not throw", {func:func}); |
| 585 } catch (e) { |
| 586 if (e instanceof AssertionError) { |
| 587 throw e; |
| 588 } |
| 589 if (code === null) { |
| 590 return; |
| 591 } |
| 592 if (typeof code === "object") { |
| 593 assert(typeof e == "object" && "name" in e && e.name == code.nam
e, |
| 594 "assert_throws", description, |
| 595 "${func} threw ${actual} (${actual_name}) expected ${expe
cted} (${expected_name})", |
| 596 {func:func, actual:e, actual_name:e.name, |
| 597 expected:code, |
| 598 expected_name:code.name}); |
| 599 return; |
| 600 } |
| 601 |
| 602 var code_name_map = { |
| 603 INDEX_SIZE_ERR: 'IndexSizeError', |
| 604 HIERARCHY_REQUEST_ERR: 'HierarchyRequestError', |
| 605 WRONG_DOCUMENT_ERR: 'WrongDocumentError', |
| 606 INVALID_CHARACTER_ERR: 'InvalidCharacterError', |
| 607 NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError', |
| 608 NOT_FOUND_ERR: 'NotFoundError', |
| 609 NOT_SUPPORTED_ERR: 'NotSupportedError', |
| 610 INVALID_STATE_ERR: 'InvalidStateError', |
| 611 SYNTAX_ERR: 'SyntaxError', |
| 612 INVALID_MODIFICATION_ERR: 'InvalidModificationError', |
| 613 NAMESPACE_ERR: 'NamespaceError', |
| 614 INVALID_ACCESS_ERR: 'InvalidAccessError', |
| 615 TYPE_MISMATCH_ERR: 'TypeMismatchError', |
| 616 SECURITY_ERR: 'SecurityError', |
| 617 NETWORK_ERR: 'NetworkError', |
| 618 ABORT_ERR: 'AbortError', |
| 619 URL_MISMATCH_ERR: 'URLMismatchError', |
| 620 QUOTA_EXCEEDED_ERR: 'QuotaExceededError', |
| 621 TIMEOUT_ERR: 'TimeoutError', |
| 622 INVALID_NODE_TYPE_ERR: 'InvalidNodeTypeError', |
| 623 DATA_CLONE_ERR: 'DataCloneError' |
| 624 }; |
| 625 |
| 626 var name = code in code_name_map ? code_name_map[code] : code; |
| 627 |
| 628 var name_code_map = { |
| 629 IndexSizeError: 1, |
| 630 HierarchyRequestError: 3, |
| 631 WrongDocumentError: 4, |
| 632 InvalidCharacterError: 5, |
| 633 NoModificationAllowedError: 7, |
| 634 NotFoundError: 8, |
| 635 NotSupportedError: 9, |
| 636 InvalidStateError: 11, |
| 637 SyntaxError: 12, |
| 638 InvalidModificationError: 13, |
| 639 NamespaceError: 14, |
| 640 InvalidAccessError: 15, |
| 641 TypeMismatchError: 17, |
| 642 SecurityError: 18, |
| 643 NetworkError: 19, |
| 644 AbortError: 20, |
| 645 URLMismatchError: 21, |
| 646 QuotaExceededError: 22, |
| 647 TimeoutError: 23, |
| 648 InvalidNodeTypeError: 24, |
| 649 DataCloneError: 25, |
| 650 |
| 651 UnknownError: 0, |
| 652 ConstraintError: 0, |
| 653 DataError: 0, |
| 654 TransactionInactiveError: 0, |
| 655 ReadOnlyError: 0, |
| 656 VersionError: 0 |
| 657 }; |
| 658 |
| 659 if (!(name in name_code_map)) { |
| 660 throw new AssertionError('Test bug: unrecognized DOMException co
de "' + code + '" passed to assert_throws()'); |
| 661 } |
| 662 |
| 663 var required_props = { code: name_code_map[name] }; |
| 664 |
| 665 if (required_props.code === 0 || |
| 666 ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DO
MException")) { |
| 667 // New style exception: also test the name property. |
| 668 required_props.name = name; |
| 669 } |
| 670 |
| 671 //We'd like to test that e instanceof the appropriate interface, |
| 672 //but we can't, because we don't know what window it was created |
| 673 //in. It might be an instanceof the appropriate interface on some |
| 674 //unknown other window. TODO: Work around this somehow? |
| 675 |
| 676 assert(typeof e == "object", |
| 677 "assert_throws", description, |
| 678 "${func} threw ${e} with type ${type}, not an object", |
| 679 {func:func, e:e, type:typeof e}); |
| 680 |
| 681 for (var prop in required_props) { |
| 682 assert(typeof e == "object" && prop in e && e[prop] == required_
props[prop], |
| 683 "assert_throws", description, |
| 684 "${func} threw ${e} that is not a DOMException " + code +
": property ${prop} is equal to ${actual}, expected ${expected}", |
| 685 {func:func, e:e, prop:prop, actual:e[prop], expected:requ
ired_props[prop]}); |
| 686 } |
| 687 } |
| 688 } |
| 689 expose(assert_throws, "assert_throws"); |
| 690 |
| 691 function assert_unreached(description) { |
| 692 assert(false, "assert_unreached", description, |
| 693 "Reached unreachable code"); |
| 694 } |
| 695 expose(assert_unreached, "assert_unreached"); |
| 696 |
| 697 function assert_any(assert_func, actual, expected_array) |
| 698 { |
| 699 var args = [].slice.call(arguments, 3); |
| 700 var errors = []; |
| 701 var passed = false; |
| 702 forEach(expected_array, |
| 703 function(expected) |
| 704 { |
| 705 try { |
| 706 assert_func.apply(this, [actual, expected].concat(args))
; |
| 707 passed = true; |
| 708 } catch (e) { |
| 709 errors.push(e.message); |
| 710 } |
| 711 }); |
| 712 if (!passed) { |
| 713 throw new AssertionError(errors.join("\n\n")); |
| 714 } |
| 715 } |
| 716 expose(assert_any, "assert_any"); |
| 717 |
| 718 function Test(name, properties) |
| 719 { |
| 720 if (tests.file_is_test && tests.tests.length) { |
| 721 throw new Error("Tried to create a test with file_is_test"); |
| 722 } |
| 723 this.name = name; |
| 724 |
| 725 this.phases = { |
| 726 INITIAL:0, |
| 727 STARTED:1, |
| 728 HAS_RESULT:2, |
| 729 COMPLETE:3 |
| 730 }; |
| 731 this.phase = this.phases.INITIAL; |
| 732 |
| 733 this.status = this.NOTRUN; |
| 734 this.timeout_id = null; |
| 735 |
| 736 this.properties = properties; |
| 737 var timeout = properties.timeout ? properties.timeout : settings.test_ti
meout; |
| 738 if (timeout != null) { |
| 739 this.timeout_length = timeout * tests.timeout_multiplier; |
| 740 } else { |
| 741 this.timeout_length = null; |
| 742 } |
| 743 |
| 744 this.message = null; |
| 745 |
| 746 this.steps = []; |
| 747 |
| 748 this.cleanup_callbacks = []; |
| 749 |
| 750 tests.push(this); |
| 751 } |
| 752 |
| 753 Test.statuses = { |
| 754 PASS:0, |
| 755 FAIL:1, |
| 756 TIMEOUT:2, |
| 757 NOTRUN:3 |
| 758 }; |
| 759 |
| 760 Test.prototype = merge({}, Test.statuses); |
| 761 |
| 762 Test.prototype.structured_clone = function() |
| 763 { |
| 764 if (!this._structured_clone) { |
| 765 var msg = this.message; |
| 766 msg = msg ? String(msg) : msg; |
| 767 this._structured_clone = merge({ |
| 768 name:String(this.name), |
| 769 status:this.status, |
| 770 message:msg |
| 771 }, Test.statuses); |
| 772 } |
| 773 return this._structured_clone; |
| 774 }; |
| 775 |
| 776 Test.prototype.step = function(func, this_obj) |
| 777 { |
| 778 if (this.phase > this.phases.STARTED) { |
| 779 return; |
| 780 } |
| 781 this.phase = this.phases.STARTED; |
| 782 //If we don't get a result before the harness times out that will be a t
est timout |
| 783 this.set_status(this.TIMEOUT, "Test timed out"); |
| 784 |
| 785 tests.started = true; |
| 786 |
| 787 if (this.timeout_id === null) { |
| 788 this.set_timeout(); |
| 789 } |
| 790 |
| 791 this.steps.push(func); |
| 792 |
| 793 if (arguments.length === 1) { |
| 794 this_obj = this; |
| 795 } |
| 796 |
| 797 try { |
| 798 return func.apply(this_obj, Array.prototype.slice.call(arguments, 2)
); |
| 799 } catch (e) { |
| 800 if (this.phase >= this.phases.HAS_RESULT) { |
| 801 return; |
| 802 } |
| 803 var message = (typeof e === "object" && e !== null) ? e.message : e; |
| 804 if (typeof e.stack != "undefined" && typeof e.message == "string") { |
| 805 //Try to make it more informative for some exceptions, at least |
| 806 //in Gecko and WebKit. This results in a stack dump instead of |
| 807 //just errors like "Cannot read property 'parentNode' of null" |
| 808 //or "root is null". Makes it a lot longer, of course. |
| 809 message += "(stack: " + e.stack + ")"; |
| 810 } |
| 811 this.set_status(this.FAIL, message); |
| 812 this.phase = this.phases.HAS_RESULT; |
| 813 this.done(); |
| 814 } |
| 815 }; |
| 816 |
| 817 Test.prototype.step_func = function(func, this_obj) |
| 818 { |
| 819 var test_this = this; |
| 820 |
| 821 if (arguments.length === 1) { |
| 822 this_obj = test_this; |
| 823 } |
| 824 |
| 825 return function() |
| 826 { |
| 827 return test_this.step.apply(test_this, [func, this_obj].concat( |
| 828 Array.prototype.slice.call(arguments))); |
| 829 }; |
| 830 }; |
| 831 |
| 832 Test.prototype.step_func_done = function(func, this_obj) |
| 833 { |
| 834 var test_this = this; |
| 835 |
| 836 if (arguments.length === 1) { |
| 837 this_obj = test_this; |
| 838 } |
| 839 |
| 840 return function() |
| 841 { |
| 842 if (func) { |
| 843 test_this.step.apply(test_this, [func, this_obj].concat( |
| 844 Array.prototype.slice.call(arguments))); |
| 845 } |
| 846 test_this.done(); |
| 847 }; |
| 848 }; |
| 849 |
| 850 Test.prototype.unreached_func = function(description) |
| 851 { |
| 852 return this.step_func(function() { |
| 853 assert_unreached(description); |
| 854 }); |
| 855 }; |
| 856 |
| 857 Test.prototype.add_cleanup = function(callback) { |
| 858 this.cleanup_callbacks.push(callback); |
| 859 }; |
| 860 |
| 861 Test.prototype.force_timeout = function() { |
| 862 this.set_status(this.TIMEOUT); |
| 863 this.phase = this.phases.HAS_RESULT; |
| 864 } |
| 865 |
| 866 Test.prototype.set_timeout = function() |
| 867 { |
| 868 if (this.timeout_length !== null) { |
| 869 var this_obj = this; |
| 870 this.timeout_id = setTimeout(function() |
| 871 { |
| 872 this_obj.timeout(); |
| 873 }, this.timeout_length); |
| 874 } |
| 875 }; |
| 876 |
| 877 Test.prototype.set_status = function(status, message) |
| 878 { |
| 879 this.status = status; |
| 880 this.message = message; |
| 881 }; |
| 882 |
| 883 Test.prototype.timeout = function() |
| 884 { |
| 885 this.timeout_id = null; |
| 886 this.set_status(this.TIMEOUT, "Test timed out"); |
| 887 this.phase = this.phases.HAS_RESULT; |
| 888 this.done(); |
| 889 }; |
| 890 |
| 891 Test.prototype.done = function() |
| 892 { |
| 893 if (this.phase == this.phases.COMPLETE) { |
| 894 return; |
| 895 } |
| 896 |
| 897 if (this.phase <= this.phases.STARTED) { |
| 898 this.set_status(this.PASS, null); |
| 899 } |
| 900 |
| 901 if (this.status == this.NOTRUN) { |
| 902 alert(this.phase); |
| 903 } |
| 904 |
| 905 this.phase = this.phases.COMPLETE; |
| 906 |
| 907 clearTimeout(this.timeout_id); |
| 908 tests.result(this); |
| 909 this.cleanup(); |
| 910 }; |
| 911 |
| 912 Test.prototype.cleanup = function() { |
| 913 forEach(this.cleanup_callbacks, |
| 914 function(cleanup_callback) { |
| 915 cleanup_callback(); |
| 916 }); |
| 917 }; |
| 918 |
| 919 /* |
| 920 * Harness |
| 921 */ |
| 922 |
| 923 function TestsStatus() |
| 924 { |
| 925 this.status = null; |
| 926 this.message = null; |
| 927 } |
| 928 |
| 929 TestsStatus.statuses = { |
| 930 OK:0, |
| 931 ERROR:1, |
| 932 TIMEOUT:2 |
| 933 }; |
| 934 |
| 935 TestsStatus.prototype = merge({}, TestsStatus.statuses); |
| 936 |
| 937 TestsStatus.prototype.structured_clone = function() |
| 938 { |
| 939 if (!this._structured_clone) { |
| 940 var msg = this.message; |
| 941 msg = msg ? String(msg) : msg; |
| 942 this._structured_clone = merge({ |
| 943 status:this.status, |
| 944 message:msg |
| 945 }, TestsStatus.statuses); |
| 946 } |
| 947 return this._structured_clone; |
| 948 }; |
| 949 |
| 950 function Tests() |
| 951 { |
| 952 this.tests = []; |
| 953 this.num_pending = 0; |
| 954 |
| 955 this.phases = { |
| 956 INITIAL:0, |
| 957 SETUP:1, |
| 958 HAVE_TESTS:2, |
| 959 HAVE_RESULTS:3, |
| 960 COMPLETE:4 |
| 961 }; |
| 962 this.phase = this.phases.INITIAL; |
| 963 |
| 964 this.properties = {}; |
| 965 |
| 966 //All tests can't be done until the load event fires |
| 967 this.all_loaded = false; |
| 968 this.wait_for_finish = false; |
| 969 this.processing_callbacks = false; |
| 970 |
| 971 this.allow_uncaught_exception = false; |
| 972 |
| 973 this.file_is_test = false; |
| 974 |
| 975 this.timeout_multiplier = 1; |
| 976 this.timeout_length = this.get_timeout(); |
| 977 this.timeout_id = null; |
| 978 |
| 979 this.start_callbacks = []; |
| 980 this.test_done_callbacks = []; |
| 981 this.all_done_callbacks = []; |
| 982 |
| 983 this.status = new TestsStatus(); |
| 984 |
| 985 var this_obj = this; |
| 986 |
| 987 on_event(window, "load", |
| 988 function() |
| 989 { |
| 990 this_obj.all_loaded = true; |
| 991 if (this_obj.all_done()) |
| 992 { |
| 993 this_obj.complete(); |
| 994 } |
| 995 }); |
| 996 |
| 997 this.set_timeout(); |
| 998 } |
| 999 |
| 1000 Tests.prototype.setup = function(func, properties) |
| 1001 { |
| 1002 if (this.phase >= this.phases.HAVE_RESULTS) { |
| 1003 return; |
| 1004 } |
| 1005 |
| 1006 if (this.phase < this.phases.SETUP) { |
| 1007 this.phase = this.phases.SETUP; |
| 1008 } |
| 1009 |
| 1010 this.properties = properties; |
| 1011 |
| 1012 for (var p in properties) { |
| 1013 if (properties.hasOwnProperty(p)) { |
| 1014 var value = properties[p]; |
| 1015 if (p == "allow_uncaught_exception") { |
| 1016 this.allow_uncaught_exception = value; |
| 1017 } else if (p == "explicit_done" && value) { |
| 1018 this.wait_for_finish = true; |
| 1019 } else if (p == "explicit_timeout" && value) { |
| 1020 this.timeout_length = null; |
| 1021 if (this.timeout_id) |
| 1022 { |
| 1023 clearTimeout(this.timeout_id); |
| 1024 } |
| 1025 } else if (p == "timeout_multiplier") { |
| 1026 this.timeout_multiplier = value; |
| 1027 } |
| 1028 } |
| 1029 } |
| 1030 |
| 1031 if (func) { |
| 1032 try { |
| 1033 func(); |
| 1034 } catch (e) { |
| 1035 this.status.status = this.status.ERROR; |
| 1036 this.status.message = String(e); |
| 1037 } |
| 1038 } |
| 1039 this.set_timeout(); |
| 1040 }; |
| 1041 |
| 1042 Tests.prototype.set_file_is_test = function() { |
| 1043 if (this.tests.length > 0) { |
| 1044 throw new Error("Tried to set file as test after creating a test"); |
| 1045 } |
| 1046 this.wait_for_finish = true; |
| 1047 this.file_is_test = true; |
| 1048 // Create the test, which will add it to the list of tests |
| 1049 async_test(); |
| 1050 }; |
| 1051 |
| 1052 Tests.prototype.get_timeout = function() { |
| 1053 var metas = document.getElementsByTagName("meta"); |
| 1054 for (var i = 0; i < metas.length; i++) { |
| 1055 if (metas[i].name == "timeout") { |
| 1056 if (metas[i].content == "long") { |
| 1057 return settings.harness_timeout.long; |
| 1058 } |
| 1059 break; |
| 1060 } |
| 1061 } |
| 1062 return settings.harness_timeout.normal; |
| 1063 }; |
| 1064 |
| 1065 Tests.prototype.set_timeout = function() { |
| 1066 var this_obj = this; |
| 1067 clearTimeout(this.timeout_id); |
| 1068 if (this.timeout_length !== null) { |
| 1069 this.timeout_id = setTimeout(function() { |
| 1070 this_obj.timeout(); |
| 1071 }, this.timeout_length); |
| 1072 } |
| 1073 }; |
| 1074 |
| 1075 Tests.prototype.timeout = function() { |
| 1076 if (this.status.status === null) { |
| 1077 this.status.status = this.status.TIMEOUT; |
| 1078 } |
| 1079 this.complete(); |
| 1080 }; |
| 1081 |
| 1082 Tests.prototype.end_wait = function() |
| 1083 { |
| 1084 this.wait_for_finish = false; |
| 1085 if (this.all_done()) { |
| 1086 this.complete(); |
| 1087 } |
| 1088 }; |
| 1089 |
| 1090 Tests.prototype.push = function(test) |
| 1091 { |
| 1092 if (this.phase < this.phases.HAVE_TESTS) { |
| 1093 this.start(); |
| 1094 } |
| 1095 this.num_pending++; |
| 1096 this.tests.push(test); |
| 1097 }; |
| 1098 |
| 1099 Tests.prototype.all_done = function() { |
| 1100 return (this.tests.length > 0 && this.all_loaded && this.num_pending ===
0 && |
| 1101 !this.wait_for_finish && !this.processing_callbacks); |
| 1102 }; |
| 1103 |
| 1104 Tests.prototype.start = function() { |
| 1105 this.phase = this.phases.HAVE_TESTS; |
| 1106 this.notify_start(); |
| 1107 }; |
| 1108 |
| 1109 Tests.prototype.notify_start = function() { |
| 1110 var this_obj = this; |
| 1111 forEach (this.start_callbacks, |
| 1112 function(callback) |
| 1113 { |
| 1114 callback(this_obj.properties); |
| 1115 }); |
| 1116 forEach_windows( |
| 1117 function(w, is_same_origin) |
| 1118 { |
| 1119 if (is_same_origin && w.start_callback) { |
| 1120 try { |
| 1121 w.start_callback(this_obj.properties); |
| 1122 } catch (e) { |
| 1123 if (debug) { |
| 1124 throw e; |
| 1125 } |
| 1126 } |
| 1127 } |
| 1128 if (supports_post_message(w) && w !== self) { |
| 1129 w.postMessage({ |
| 1130 type: "start", |
| 1131 properties: this_obj.properties |
| 1132 }, "*"); |
| 1133 } |
| 1134 }); |
| 1135 }; |
| 1136 |
| 1137 Tests.prototype.result = function(test) |
| 1138 { |
| 1139 if (this.phase > this.phases.HAVE_RESULTS) { |
| 1140 return; |
| 1141 } |
| 1142 this.phase = this.phases.HAVE_RESULTS; |
| 1143 this.num_pending--; |
| 1144 this.notify_result(test); |
| 1145 }; |
| 1146 |
| 1147 Tests.prototype.notify_result = function(test) { |
| 1148 var this_obj = this; |
| 1149 this.processing_callbacks = true; |
| 1150 forEach(this.test_done_callbacks, |
| 1151 function(callback) |
| 1152 { |
| 1153 callback(test, this_obj); |
| 1154 }); |
| 1155 |
| 1156 forEach_windows( |
| 1157 function(w, is_same_origin) |
| 1158 { |
| 1159 if (is_same_origin && w.result_callback) { |
| 1160 try { |
| 1161 w.result_callback(test); |
| 1162 } catch (e) { |
| 1163 if (debug) { |
| 1164 throw e; |
| 1165 } |
| 1166 } |
| 1167 } |
| 1168 if (supports_post_message(w) && w !== self) { |
| 1169 w.postMessage({ |
| 1170 type: "result", |
| 1171 test: test.structured_clone() |
| 1172 }, "*"); |
| 1173 } |
| 1174 }); |
| 1175 this.processing_callbacks = false; |
| 1176 if (this_obj.all_done()) { |
| 1177 this_obj.complete(); |
| 1178 } |
| 1179 }; |
| 1180 |
| 1181 Tests.prototype.complete = function() { |
| 1182 if (this.phase === this.phases.COMPLETE) { |
| 1183 return; |
| 1184 } |
| 1185 this.phase = this.phases.COMPLETE; |
| 1186 var this_obj = this; |
| 1187 this.tests.forEach( |
| 1188 function(x) |
| 1189 { |
| 1190 if (x.status === x.NOTRUN) { |
| 1191 this_obj.notify_result(x); |
| 1192 x.cleanup(); |
| 1193 } |
| 1194 } |
| 1195 ); |
| 1196 this.notify_complete(); |
| 1197 }; |
| 1198 |
| 1199 Tests.prototype.notify_complete = function() |
| 1200 { |
| 1201 clearTimeout(this.timeout_id); |
| 1202 var this_obj = this; |
| 1203 var tests = map(this_obj.tests, |
| 1204 function(test) |
| 1205 { |
| 1206 return test.structured_clone(); |
| 1207 }); |
| 1208 if (this.status.status === null) { |
| 1209 this.status.status = this.status.OK; |
| 1210 } |
| 1211 |
| 1212 forEach (this.all_done_callbacks, |
| 1213 function(callback) |
| 1214 { |
| 1215 callback(this_obj.tests, this_obj.status); |
| 1216 }); |
| 1217 |
| 1218 forEach_windows( |
| 1219 function(w, is_same_origin) |
| 1220 { |
| 1221 if (is_same_origin && w.completion_callback) { |
| 1222 try { |
| 1223 w.completion_callback(this_obj.tests, this_obj.statu
s); |
| 1224 } catch (e) { |
| 1225 if (debug) { |
| 1226 throw e; |
| 1227 } |
| 1228 } |
| 1229 } |
| 1230 if (supports_post_message(w) && w !== self) { |
| 1231 w.postMessage({ |
| 1232 type: "complete", |
| 1233 tests: tests, |
| 1234 status: this_obj.status.structured_clone() |
| 1235 }, "*"); |
| 1236 } |
| 1237 }); |
| 1238 }; |
| 1239 |
| 1240 var tests = new Tests(); |
| 1241 |
| 1242 addEventListener("error", function(e) { |
| 1243 if (tests.file_is_test) { |
| 1244 var test = tests.tests[0]; |
| 1245 if (test.phase >= test.phases.HAS_RESULT) { |
| 1246 return; |
| 1247 } |
| 1248 var message = e.message; |
| 1249 test.set_status(test.FAIL, message); |
| 1250 test.phase = test.phases.HAS_RESULT; |
| 1251 test.done(); |
| 1252 done(); |
| 1253 } else if (!tests.allow_uncaught_exception) { |
| 1254 tests.status.status = tests.status.ERROR; |
| 1255 tests.status.message = e.message; |
| 1256 } |
| 1257 }); |
| 1258 |
| 1259 function timeout() { |
| 1260 if (tests.timeout_length === null) { |
| 1261 tests.timeout(); |
| 1262 } |
| 1263 } |
| 1264 expose(timeout, 'timeout'); |
| 1265 |
| 1266 function add_start_callback(callback) { |
| 1267 tests.start_callbacks.push(callback); |
| 1268 } |
| 1269 |
| 1270 function add_result_callback(callback) |
| 1271 { |
| 1272 tests.test_done_callbacks.push(callback); |
| 1273 } |
| 1274 |
| 1275 function add_completion_callback(callback) |
| 1276 { |
| 1277 tests.all_done_callbacks.push(callback); |
| 1278 } |
| 1279 |
| 1280 expose(add_start_callback, 'add_start_callback'); |
| 1281 expose(add_result_callback, 'add_result_callback'); |
| 1282 expose(add_completion_callback, 'add_completion_callback'); |
| 1283 |
| 1284 /* |
| 1285 * Output listener |
| 1286 */ |
| 1287 |
| 1288 function Output() { |
| 1289 this.output_document = document; |
| 1290 this.output_node = null; |
| 1291 this.done_count = 0; |
| 1292 this.enabled = settings.output; |
| 1293 this.phase = this.INITIAL; |
| 1294 } |
| 1295 |
| 1296 Output.prototype.INITIAL = 0; |
| 1297 Output.prototype.STARTED = 1; |
| 1298 Output.prototype.HAVE_RESULTS = 2; |
| 1299 Output.prototype.COMPLETE = 3; |
| 1300 |
| 1301 Output.prototype.setup = function(properties) { |
| 1302 if (this.phase > this.INITIAL) { |
| 1303 return; |
| 1304 } |
| 1305 |
| 1306 //If output is disabled in testharnessreport.js the test shouldn't be |
| 1307 //able to override that |
| 1308 this.enabled = this.enabled && (properties.hasOwnProperty("output") ? |
| 1309 properties.output : settings.output); |
| 1310 }; |
| 1311 |
| 1312 Output.prototype.init = function(properties) { |
| 1313 if (this.phase >= this.STARTED) { |
| 1314 return; |
| 1315 } |
| 1316 if (properties.output_document) { |
| 1317 this.output_document = properties.output_document; |
| 1318 } else { |
| 1319 this.output_document = document; |
| 1320 } |
| 1321 this.phase = this.STARTED; |
| 1322 }; |
| 1323 |
| 1324 Output.prototype.resolve_log = function() { |
| 1325 var output_document; |
| 1326 if (typeof this.output_document === "function") { |
| 1327 output_document = this.output_document.apply(undefined); |
| 1328 } else { |
| 1329 output_document = this.output_document; |
| 1330 } |
| 1331 if (!output_document) { |
| 1332 return; |
| 1333 } |
| 1334 var node = output_document.getElementById("log"); |
| 1335 if (!node) { |
| 1336 if (!document.body || document.readyState == "loading") { |
| 1337 return; |
| 1338 } |
| 1339 node = output_document.createElement("div"); |
| 1340 node.id = "log"; |
| 1341 output_document.body.appendChild(node); |
| 1342 } |
| 1343 this.output_document = output_document; |
| 1344 this.output_node = node; |
| 1345 }; |
| 1346 |
| 1347 Output.prototype.show_status = function() { |
| 1348 if (this.phase < this.STARTED) { |
| 1349 this.init(); |
| 1350 } |
| 1351 if (!this.enabled) { |
| 1352 return; |
| 1353 } |
| 1354 if (this.phase < this.HAVE_RESULTS) { |
| 1355 this.resolve_log(); |
| 1356 this.phase = this.HAVE_RESULTS; |
| 1357 } |
| 1358 this.done_count++; |
| 1359 if (this.output_node) { |
| 1360 if (this.done_count < 100 || |
| 1361 (this.done_count < 1000 && this.done_count % 100 === 0) || |
| 1362 this.done_count % 1000 === 0) { |
| 1363 this.output_node.textContent = "Running, " + |
| 1364 this.done_count + " complete, " + |
| 1365 tests.num_pending + " remain"; |
| 1366 } |
| 1367 } |
| 1368 }; |
| 1369 |
| 1370 Output.prototype.show_results = function (tests, harness_status) { |
| 1371 if (this.phase >= this.COMPLETE) { |
| 1372 return; |
| 1373 } |
| 1374 if (!this.enabled) { |
| 1375 return; |
| 1376 } |
| 1377 if (!this.output_node) { |
| 1378 this.resolve_log(); |
| 1379 } |
| 1380 this.phase = this.COMPLETE; |
| 1381 |
| 1382 var log = this.output_node; |
| 1383 if (!log) { |
| 1384 return; |
| 1385 } |
| 1386 var output_document = this.output_document; |
| 1387 |
| 1388 while (log.lastChild) { |
| 1389 log.removeChild(log.lastChild); |
| 1390 } |
| 1391 |
| 1392 if (script_prefix != null) { |
| 1393 var stylesheet = output_document.createElementNS(xhtml_ns, "link"); |
| 1394 stylesheet.setAttribute("rel", "stylesheet"); |
| 1395 stylesheet.setAttribute("href", script_prefix + "testharness.css"); |
| 1396 var heads = output_document.getElementsByTagName("head"); |
| 1397 if (heads.length) { |
| 1398 heads[0].appendChild(stylesheet); |
| 1399 } |
| 1400 } |
| 1401 |
| 1402 var status_text_harness = {}; |
| 1403 status_text_harness[harness_status.OK] = "OK"; |
| 1404 status_text_harness[harness_status.ERROR] = "Error"; |
| 1405 status_text_harness[harness_status.TIMEOUT] = "Timeout"; |
| 1406 |
| 1407 var status_text = {}; |
| 1408 status_text[Test.prototype.PASS] = "Pass"; |
| 1409 status_text[Test.prototype.FAIL] = "Fail"; |
| 1410 status_text[Test.prototype.TIMEOUT] = "Timeout"; |
| 1411 status_text[Test.prototype.NOTRUN] = "Not Run"; |
| 1412 |
| 1413 var status_number = {}; |
| 1414 forEach(tests, |
| 1415 function(test) { |
| 1416 var status = status_text[test.status]; |
| 1417 if (status_number.hasOwnProperty(status)) { |
| 1418 status_number[status] += 1; |
| 1419 } else { |
| 1420 status_number[status] = 1; |
| 1421 } |
| 1422 }); |
| 1423 |
| 1424 function status_class(status) |
| 1425 { |
| 1426 return status.replace(/\s/g, '').toLowerCase(); |
| 1427 } |
| 1428 |
| 1429 var summary_template = ["section", {"id":"summary"}, |
| 1430 ["h2", {}, "Summary"], |
| 1431 function() |
| 1432 { |
| 1433 |
| 1434 var status = status_text_harness[harness_sta
tus.status]; |
| 1435 var rv = [["section", {}, |
| 1436 ["p", {}, |
| 1437 "Harness status: ", |
| 1438 ["span", {"class":status_class(s
tatus)}, |
| 1439 status |
| 1440 ], |
| 1441 ] |
| 1442 ]]; |
| 1443 |
| 1444 if (harness_status.status === harness_status
.ERROR) { |
| 1445 rv[0].push(["pre", {}, harness_status.me
ssage]); |
| 1446 } |
| 1447 return rv; |
| 1448 }, |
| 1449 ["p", {}, "Found ${num_tests} tests"], |
| 1450 function() { |
| 1451 var rv = [["div", {}]]; |
| 1452 var i = 0; |
| 1453 while (status_text.hasOwnProperty(i)) { |
| 1454 if (status_number.hasOwnProperty(status_
text[i])) { |
| 1455 var status = status_text[i]; |
| 1456 rv[0].push(["div", {"class":status_c
lass(status)}, |
| 1457 ["label", {}, |
| 1458 ["input", {type:"checkb
ox", checked:"checked"}], |
| 1459 status_number[status] +
" " + status]]); |
| 1460 } |
| 1461 i++; |
| 1462 } |
| 1463 return rv; |
| 1464 }, |
| 1465 ]; |
| 1466 |
| 1467 log.appendChild(render(summary_template, {num_tests:tests.length}, outpu
t_document)); |
| 1468 |
| 1469 forEach(output_document.querySelectorAll("section#summary label"), |
| 1470 function(element) |
| 1471 { |
| 1472 on_event(element, "click", |
| 1473 function(e) |
| 1474 { |
| 1475 if (output_document.getElementById("results") =
== null) { |
| 1476 e.preventDefault(); |
| 1477 return; |
| 1478 } |
| 1479 var result_class = element.parentNode.getAttrib
ute("class"); |
| 1480 var style_element = output_document.querySelect
or("style#hide-" + result_class); |
| 1481 var input_element = element.querySelector("inpu
t"); |
| 1482 if (!style_element && !input_element.checked) { |
| 1483 style_element = output_document.createEleme
ntNS(xhtml_ns, "style"); |
| 1484 style_element.id = "hide-" + result_class; |
| 1485 style_element.textContent = "table#results
> tbody > tr."+result_class+"{display:none}"; |
| 1486 output_document.body.appendChild(style_elem
ent); |
| 1487 } else if (style_element && input_element.check
ed) { |
| 1488 style_element.parentNode.removeChild(style_
element); |
| 1489 } |
| 1490 }); |
| 1491 }); |
| 1492 |
| 1493 // This use of innerHTML plus manual escaping is not recommended in |
| 1494 // general, but is necessary here for performance. Using textContent |
| 1495 // on each individual <td> adds tens of seconds of execution time for |
| 1496 // large test suites (tens of thousands of tests). |
| 1497 function escape_html(s) |
| 1498 { |
| 1499 return s.replace(/\&/g, "&") |
| 1500 .replace(/</g, "<") |
| 1501 .replace(/"/g, """) |
| 1502 .replace(/'/g, "'"); |
| 1503 } |
| 1504 |
| 1505 function has_assertions() |
| 1506 { |
| 1507 for (var i = 0; i < tests.length; i++) { |
| 1508 if (tests[i].properties.hasOwnProperty("assert")) { |
| 1509 return true; |
| 1510 } |
| 1511 } |
| 1512 return false; |
| 1513 } |
| 1514 |
| 1515 function get_assertion(test) |
| 1516 { |
| 1517 if (test.properties.hasOwnProperty("assert")) { |
| 1518 if (Array.isArray(test.properties.assert)) { |
| 1519 return test.properties.assert.join(' '); |
| 1520 } |
| 1521 return test.properties.assert; |
| 1522 } |
| 1523 return ''; |
| 1524 } |
| 1525 |
| 1526 log.appendChild(document.createElementNS(xhtml_ns, "section")); |
| 1527 var assertions = has_assertions(); |
| 1528 var html = "<h2>Details</h2><table id='results' " + (assertions ? "class
='assertions'" : "" ) + ">" + |
| 1529 "<thead><tr><th>Result</th><th>Test Name</th>" + |
| 1530 (assertions ? "<th>Assertion</th>" : "") + |
| 1531 "<th>Message</th></tr></thead>" + |
| 1532 "<tbody>"; |
| 1533 for (var i = 0; i < tests.length; i++) { |
| 1534 html += '<tr class="' + |
| 1535 escape_html(status_class(status_text[tests[i].status])) + |
| 1536 '"><td>' + |
| 1537 escape_html(status_text[tests[i].status]) + |
| 1538 "</td><td>" + |
| 1539 escape_html(tests[i].name) + |
| 1540 "</td><td>" + |
| 1541 (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>"
: "") + |
| 1542 escape_html(tests[i].message ? tests[i].message : " ") + |
| 1543 "</td></tr>"; |
| 1544 } |
| 1545 html += "</tbody></table>"; |
| 1546 try { |
| 1547 log.lastChild.innerHTML = html; |
| 1548 } catch (e) { |
| 1549 log.appendChild(document.createElementNS(xhtml_ns, "p")) |
| 1550 .textContent = "Setting innerHTML for the log threw an exception.
"; |
| 1551 log.appendChild(document.createElementNS(xhtml_ns, "pre")) |
| 1552 .textContent = html; |
| 1553 } |
| 1554 }; |
| 1555 |
| 1556 var output = new Output(); |
| 1557 add_start_callback(function (properties) {output.init(properties);}); |
| 1558 add_result_callback(function () {output.show_status();}); |
| 1559 add_completion_callback(function (tests, harness_status) {output.show_result
s(tests, harness_status);}); |
| 1560 |
| 1561 /* |
| 1562 * Template code |
| 1563 * |
| 1564 * A template is just a javascript structure. An element is represented as: |
| 1565 * |
| 1566 * [tag_name, {attr_name:attr_value}, child1, child2] |
| 1567 * |
| 1568 * the children can either be strings (which act like text nodes), other tem
plates or |
| 1569 * functions (see below) |
| 1570 * |
| 1571 * A text node is represented as |
| 1572 * |
| 1573 * ["{text}", value] |
| 1574 * |
| 1575 * String values have a simple substitution syntax; ${foo} represents a vari
able foo. |
| 1576 * |
| 1577 * It is possible to embed logic in templates by using a function in a place
where a |
| 1578 * node would usually go. The function must either return part of a template
or null. |
| 1579 * |
| 1580 * In cases where a set of nodes are required as output rather than a single
node |
| 1581 * with children it is possible to just use a list |
| 1582 * [node1, node2, node3] |
| 1583 * |
| 1584 * Usage: |
| 1585 * |
| 1586 * render(template, substitutions) - take a template and an object mapping |
| 1587 * variable names to parameters and return either a DOM node or a list of DO
M nodes |
| 1588 * |
| 1589 * substitute(template, substitutions) - take a template and variable mappin
g object, |
| 1590 * make the variable substitutions and return the substituted template |
| 1591 * |
| 1592 */ |
| 1593 |
| 1594 function is_single_node(template) |
| 1595 { |
| 1596 return typeof template[0] === "string"; |
| 1597 } |
| 1598 |
| 1599 function substitute(template, substitutions) |
| 1600 { |
| 1601 if (typeof template === "function") { |
| 1602 var replacement = template(substitutions); |
| 1603 if (!replacement) { |
| 1604 return null; |
| 1605 } |
| 1606 |
| 1607 return substitute(replacement, substitutions); |
| 1608 } |
| 1609 |
| 1610 if (is_single_node(template)) { |
| 1611 return substitute_single(template, substitutions); |
| 1612 } |
| 1613 |
| 1614 return filter(map(template, function(x) { |
| 1615 return substitute(x, substitutions); |
| 1616 }), function(x) {return x !== null;}); |
| 1617 } |
| 1618 |
| 1619 function substitute_single(template, substitutions) |
| 1620 { |
| 1621 var substitution_re = /\$\{([^ }]*)\}/g; |
| 1622 |
| 1623 function do_substitution(input) { |
| 1624 var components = input.split(substitution_re); |
| 1625 var rv = []; |
| 1626 for (var i = 0; i < components.length; i += 2) { |
| 1627 rv.push(components[i]); |
| 1628 if (components[i + 1]) { |
| 1629 rv.push(String(substitutions[components[i + 1]])); |
| 1630 } |
| 1631 } |
| 1632 return rv; |
| 1633 } |
| 1634 |
| 1635 function substitute_attrs(attrs, rv) |
| 1636 { |
| 1637 rv[1] = {}; |
| 1638 for (var name in template[1]) { |
| 1639 if (attrs.hasOwnProperty(name)) { |
| 1640 var new_name = do_substitution(name).join(""); |
| 1641 var new_value = do_substitution(attrs[name]).join(""); |
| 1642 rv[1][new_name] = new_value; |
| 1643 } |
| 1644 } |
| 1645 } |
| 1646 |
| 1647 function substitute_children(children, rv) |
| 1648 { |
| 1649 for (var i = 0; i < children.length; i++) { |
| 1650 if (children[i] instanceof Object) { |
| 1651 var replacement = substitute(children[i], substitutions); |
| 1652 if (replacement !== null) { |
| 1653 if (is_single_node(replacement)) { |
| 1654 rv.push(replacement); |
| 1655 } else { |
| 1656 extend(rv, replacement); |
| 1657 } |
| 1658 } |
| 1659 } else { |
| 1660 extend(rv, do_substitution(String(children[i]))); |
| 1661 } |
| 1662 } |
| 1663 return rv; |
| 1664 } |
| 1665 |
| 1666 var rv = []; |
| 1667 rv.push(do_substitution(String(template[0])).join("")); |
| 1668 |
| 1669 if (template[0] === "{text}") { |
| 1670 substitute_children(template.slice(1), rv); |
| 1671 } else { |
| 1672 substitute_attrs(template[1], rv); |
| 1673 substitute_children(template.slice(2), rv); |
| 1674 } |
| 1675 |
| 1676 return rv; |
| 1677 } |
| 1678 |
| 1679 function make_dom_single(template, doc) |
| 1680 { |
| 1681 var output_document = doc || document; |
| 1682 var element; |
| 1683 if (template[0] === "{text}") { |
| 1684 element = output_document.createTextNode(""); |
| 1685 for (var i = 1; i < template.length; i++) { |
| 1686 element.data += template[i]; |
| 1687 } |
| 1688 } else { |
| 1689 element = output_document.createElementNS(xhtml_ns, template[0]); |
| 1690 for (var name in template[1]) { |
| 1691 if (template[1].hasOwnProperty(name)) { |
| 1692 element.setAttribute(name, template[1][name]); |
| 1693 } |
| 1694 } |
| 1695 for (var i = 2; i < template.length; i++) { |
| 1696 if (template[i] instanceof Object) { |
| 1697 var sub_element = make_dom(template[i]); |
| 1698 element.appendChild(sub_element); |
| 1699 } else { |
| 1700 var text_node = output_document.createTextNode(template[i]); |
| 1701 element.appendChild(text_node); |
| 1702 } |
| 1703 } |
| 1704 } |
| 1705 |
| 1706 return element; |
| 1707 } |
| 1708 |
| 1709 |
| 1710 |
| 1711 function make_dom(template, substitutions, output_document) |
| 1712 { |
| 1713 if (is_single_node(template)) { |
| 1714 return make_dom_single(template, output_document); |
| 1715 } |
| 1716 |
| 1717 return map(template, function(x) { |
| 1718 return make_dom_single(x, output_document); |
| 1719 }); |
| 1720 } |
| 1721 |
| 1722 function render(template, substitutions, output_document) |
| 1723 { |
| 1724 return make_dom(substitute(template, substitutions), output_document); |
| 1725 } |
| 1726 |
| 1727 /* |
| 1728 * Utility funcions |
| 1729 */ |
| 1730 function assert(expected_true, function_name, description, error, substituti
ons) |
| 1731 { |
| 1732 if (tests.tests.length === 0) { |
| 1733 tests.set_file_is_test(); |
| 1734 } |
| 1735 if (expected_true !== true) { |
| 1736 var msg = make_message(function_name, description, |
| 1737 error, substitutions); |
| 1738 throw new AssertionError(msg); |
| 1739 } |
| 1740 } |
| 1741 |
| 1742 function AssertionError(message) |
| 1743 { |
| 1744 this.message = message; |
| 1745 } |
| 1746 |
| 1747 AssertionError.prototype.toString = function() { |
| 1748 return this.message; |
| 1749 }; |
| 1750 |
| 1751 function make_message(function_name, description, error, substitutions) |
| 1752 { |
| 1753 for (var p in substitutions) { |
| 1754 if (substitutions.hasOwnProperty(p)) { |
| 1755 substitutions[p] = format_value(substitutions[p]); |
| 1756 } |
| 1757 } |
| 1758 var node_form = substitute(["{text}", "${function_name}: ${description}"
+ error], |
| 1759 merge({function_name:function_name, |
| 1760 description:(description?description +
" ":"")}, |
| 1761 substitutions)); |
| 1762 return node_form.slice(1).join(""); |
| 1763 } |
| 1764 |
| 1765 function filter(array, callable, thisObj) { |
| 1766 var rv = []; |
| 1767 for (var i = 0; i < array.length; i++) { |
| 1768 if (array.hasOwnProperty(i)) { |
| 1769 var pass = callable.call(thisObj, array[i], i, array); |
| 1770 if (pass) { |
| 1771 rv.push(array[i]); |
| 1772 } |
| 1773 } |
| 1774 } |
| 1775 return rv; |
| 1776 } |
| 1777 |
| 1778 function map(array, callable, thisObj) |
| 1779 { |
| 1780 var rv = []; |
| 1781 rv.length = array.length; |
| 1782 for (var i = 0; i < array.length; i++) { |
| 1783 if (array.hasOwnProperty(i)) { |
| 1784 rv[i] = callable.call(thisObj, array[i], i, array); |
| 1785 } |
| 1786 } |
| 1787 return rv; |
| 1788 } |
| 1789 |
| 1790 function extend(array, items) |
| 1791 { |
| 1792 Array.prototype.push.apply(array, items); |
| 1793 } |
| 1794 |
| 1795 function forEach (array, callback, thisObj) |
| 1796 { |
| 1797 for (var i = 0; i < array.length; i++) { |
| 1798 if (array.hasOwnProperty(i)) { |
| 1799 callback.call(thisObj, array[i], i, array); |
| 1800 } |
| 1801 } |
| 1802 } |
| 1803 |
| 1804 function merge(a,b) |
| 1805 { |
| 1806 var rv = {}; |
| 1807 var p; |
| 1808 for (p in a) { |
| 1809 rv[p] = a[p]; |
| 1810 } |
| 1811 for (p in b) { |
| 1812 rv[p] = b[p]; |
| 1813 } |
| 1814 return rv; |
| 1815 } |
| 1816 |
| 1817 function expose(object, name) |
| 1818 { |
| 1819 var components = name.split("."); |
| 1820 var target = window; |
| 1821 for (var i = 0; i < components.length - 1; i++) { |
| 1822 if (!(components[i] in target)) { |
| 1823 target[components[i]] = {}; |
| 1824 } |
| 1825 target = target[components[i]]; |
| 1826 } |
| 1827 target[components[components.length - 1]] = object; |
| 1828 } |
| 1829 |
| 1830 function forEach_windows(callback) { |
| 1831 // Iterate of the the windows [self ... top, opener]. The callback is pa
ssed |
| 1832 // two objects, the first one is the windows object itself, the second o
ne |
| 1833 // is a boolean indicating whether or not its on the same origin as the |
| 1834 // current window. |
| 1835 var cache = forEach_windows.result_cache; |
| 1836 if (!cache) { |
| 1837 cache = [[self, true]]; |
| 1838 var w = self; |
| 1839 var i = 0; |
| 1840 var so; |
| 1841 var origins = location.ancestorOrigins; |
| 1842 while (w != w.parent) { |
| 1843 w = w.parent; |
| 1844 // In WebKit, calls to parent windows' properties that aren't on
the same |
| 1845 // origin cause an error message to be displayed in the error co
nsole but |
| 1846 // don't throw an exception. This is a deviation from the curren
t HTML5 |
| 1847 // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504 |
| 1848 // The problem with WebKit's behavior is that it pollutes the er
ror console |
| 1849 // with error messages that can't be caught. |
| 1850 // |
| 1851 // This issue can be mitigated by relying on the (for now) propr
ietary |
| 1852 // `location.ancestorOrigins` property which returns an ordered
list of |
| 1853 // the origins of enclosing windows. See: |
| 1854 // http://trac.webkit.org/changeset/113945. |
| 1855 if (origins) { |
| 1856 so = (location.origin == origins[i]); |
| 1857 } else { |
| 1858 so = is_same_origin(w); |
| 1859 } |
| 1860 cache.push([w, so]); |
| 1861 i++; |
| 1862 } |
| 1863 w = window.opener; |
| 1864 if (w) { |
| 1865 // window.opener isn't included in the `location.ancestorOrigins
` prop. |
| 1866 // We'll just have to deal with a simple check and an error msg
on WebKit |
| 1867 // browsers in this case. |
| 1868 cache.push([w, is_same_origin(w)]); |
| 1869 } |
| 1870 forEach_windows.result_cache = cache; |
| 1871 } |
| 1872 |
| 1873 forEach(cache, |
| 1874 function(a) |
| 1875 { |
| 1876 callback.apply(null, a); |
| 1877 }); |
| 1878 } |
| 1879 |
| 1880 function is_same_origin(w) { |
| 1881 try { |
| 1882 'random_prop' in w; |
| 1883 return true; |
| 1884 } catch (e) { |
| 1885 return false; |
| 1886 } |
| 1887 } |
| 1888 |
| 1889 function supports_post_message(w) |
| 1890 { |
| 1891 var supports; |
| 1892 var type; |
| 1893 // Given IE implements postMessage across nested iframes but not across |
| 1894 // windows or tabs, you can't infer cross-origin communication from the
presence |
| 1895 // of postMessage on the current window object only. |
| 1896 // |
| 1897 // Touching the postMessage prop on a window can throw if the window is |
| 1898 // not from the same origin AND post message is not supported in that |
| 1899 // browser. So just doing an existence test here won't do, you also need |
| 1900 // to wrap it in a try..cacth block. |
| 1901 try { |
| 1902 type = typeof w.postMessage; |
| 1903 if (type === "function") { |
| 1904 supports = true; |
| 1905 } |
| 1906 |
| 1907 // IE8 supports postMessage, but implements it as a host object whic
h |
| 1908 // returns "object" as its `typeof`. |
| 1909 else if (type === "object") { |
| 1910 supports = true; |
| 1911 } |
| 1912 |
| 1913 // This is the case where postMessage isn't supported AND accessing
a |
| 1914 // window property across origins does NOT throw (e.g. old Safari br
owser). |
| 1915 else { |
| 1916 supports = false; |
| 1917 } |
| 1918 } catch (e) { |
| 1919 // This is the case where postMessage isn't supported AND accessing
a |
| 1920 // window property across origins throws (e.g. old Firefox browser). |
| 1921 supports = false; |
| 1922 } |
| 1923 return supports; |
| 1924 } |
| 1925 })(); |
| 1926 // vim: set expandtab shiftwidth=4 tabstop=4: |
OLD | NEW |