Convert regions parsing test to use testharness.js
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 20 Mar 2012 17:56:15 +0000 (17:56 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 20 Mar 2012 17:56:15 +0000 (17:56 +0000)
https://bugs.webkit.org/show_bug.cgi?id=80709

Patch by Jacob Goldstein <jacobg@adobe.com> on 2012-03-20
Reviewed by Ryosuke Niwa.

* fast/regions/script-tests/webkit-flow-parsing.js:
(testParse):
(testComputedStyle):
(testNotInherited):
(test):
* fast/regions/webkit-flow-parsing-expected.txt:
* fast/regions/webkit-flow-parsing.html:
* resources/testharness.js: Added.
(.):
* resources/testharnessreport.js: Added.
(convertResult):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@111411 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/regions/script-tests/webkit-flow-parsing.js
LayoutTests/fast/regions/webkit-flow-parsing-expected.txt
LayoutTests/fast/regions/webkit-flow-parsing.html
LayoutTests/resources/testharness.js [new file with mode: 0644]
LayoutTests/resources/testharnessreport.js [new file with mode: 0644]

index f5deeb13cae3a07842fdb1edae03e8ddee90ee30..1dd0d7db3411d8e29dd323c6b8921b97f68845ad 100644 (file)
@@ -1,3 +1,22 @@
+2012-03-20  Jacob Goldstein  <jacobg@adobe.com>
+
+        Convert regions parsing test to use testharness.js
+        https://bugs.webkit.org/show_bug.cgi?id=80709
+
+        Reviewed by Ryosuke Niwa.
+
+        * fast/regions/script-tests/webkit-flow-parsing.js:
+        (testParse):
+        (testComputedStyle):
+        (testNotInherited):
+        (test):
+        * fast/regions/webkit-flow-parsing-expected.txt:
+        * fast/regions/webkit-flow-parsing.html:
+        * resources/testharness.js: Added.
+        (.):
+        * resources/testharnessreport.js: Added.
+        (convertResult):
+
 2012-03-20  Vsevolod Vlasov  <vsevik@chromium.org>
 
         Unreviewed test fix and unskip.
index 11aa3b452af9e3833d2c95af14e5d8718c65520e..106647e3409c1fd3232a8fa1f71869e1b1883e12 100644 (file)
@@ -1,6 +1,4 @@
-description('Test parsing of the CSS webkit-flow-into property.');
-
-function test(declaration) {
+function testParse(declaration) {
     var div = document.createElement("div");
     div.setAttribute("style", declaration);
     return div.style.webkitFlowInto;
@@ -32,22 +30,22 @@ function testNotInherited(parentValue, childValue) {
     return childWebKitFlowComputedValue;
 }
 
-shouldBeEqualToString('test("-webkit-flow-into: none")', "none");
-shouldBeEqualToString('test("-webkit-flow-into: first-flow")', "first-flow");
-shouldBeEqualToString('test("-webkit-flow-into: \'first flow\'")', "");
-shouldBeEqualToString('test("-webkit-flow-into: ;")', "");
-shouldBeEqualToString('test("-webkit-flow-into: 1")', "");
-shouldBeEqualToString('test("-webkit-flow-into: 1.2")', "");
-shouldBeEqualToString('test("-webkit-flow-into: -1")', "");
-shouldBeEqualToString('test("-webkit-flow-into: 12px")', "");
-
-shouldBeEqualToString('testComputedStyle("none")', "none");
-shouldBeEqualToString('testComputedStyle("")', "none");
-shouldBeEqualToString('testComputedStyle("\'first-flow\'")', "none");
-shouldBeEqualToString('testComputedStyle("first-flow")', "first-flow");
-shouldBeEqualToString('testComputedStyle("12px")', "none");
-
-shouldBeEqualToString('testNotInherited("none", "none")', "none");
-shouldBeEqualToString('testNotInherited("none", "child-flow")', "child-flow");
-shouldBeEqualToString('testNotInherited("parent-flow", "none")', "none");
-shouldBeEqualToString('testNotInherited("parent-flow", "child-flow")', "child-flow");
+test(function() {assert_equals(testParse("-webkit-flow-into: none"), "none")}, "Test Parse none"); 
+test(function() {assert_equals(testParse("-webkit-flow-into: first-flow"), "first-flow")}, "Test Parse first-flow");
+test(function() {assert_equals(testParse("-webkit-flow-into: \'first flow\'"), "")}, "Test Parse 'first-flow'");
+test(function() {assert_equals(testParse("-webkit-flow-into: ;"), "")}, "Test Parse ;");
+test(function() {assert_equals(testParse("-webkit-flow-into: 1"), "")}, "Test Parse 1");
+test(function() {assert_equals(testParse("-webkit-flow-into: 1.2"), "")}, "Test Parse 1.2");
+test(function() {assert_equals(testParse("-webkit-flow-into: -1"), "")}, "Test Parse -1");
+test(function() {assert_equals(testParse("-webkit-flow-into: 12px"), "")}, "Test Parse 12px");
+
+test(function() {assert_equals(testComputedStyle("none"), "none")}, "Test Computed Style none");
+test(function() {assert_equals(testComputedStyle(""), "none")}, "Test Computed Style ''");
+test(function() {assert_equals(testComputedStyle("\'first-flow\'"), "none")}, "Test Computed Style 'first-flow'");
+test(function() {assert_equals(testComputedStyle("first-flow"), "first-flow")}, "Test Computed Style first-flow");
+test(function() {assert_equals(testComputedStyle("12px"), "none")}, "Test Computed Style 12px ");
+
+test(function() {assert_equals(testNotInherited("none", "none"), "none")}, "Test Non-Inherited none, none");
+test(function() {assert_equals(testNotInherited("none", "child-flow"), "child-flow")}, "Test Non-Inherited none, child-flow");
+test(function() {assert_equals(testNotInherited("parent-flow", "none"), "none")}, "Test Non-Inherited parent-flow, none");
+test(function() {assert_equals(testNotInherited("parent-flow", "child-flow"), "child-flow")}, "Test Non-Inherited parent-flow, child-flow");
index 6ebdbb31568653f9eed88e1100dcf6523a1bb7a8..4303ce599e88012673f3f785aca739afa6644f97 100644 (file)
@@ -1,26 +1,20 @@
-Test parsing of the CSS webkit-flow-into property.
+This test verifies that the webkit-flow-into property is correctly parsed
 
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-
-PASS test("-webkit-flow-into: none") is "none"
-PASS test("-webkit-flow-into: first-flow") is "first-flow"
-PASS test("-webkit-flow-into: 'first flow'") is ""
-PASS test("-webkit-flow-into: ;") is ""
-PASS test("-webkit-flow-into: 1") is ""
-PASS test("-webkit-flow-into: 1.2") is ""
-PASS test("-webkit-flow-into: -1") is ""
-PASS test("-webkit-flow-into: 12px") is ""
-PASS testComputedStyle("none") is "none"
-PASS testComputedStyle("") is "none"
-PASS testComputedStyle("'first-flow'") is "none"
-PASS testComputedStyle("first-flow") is "first-flow"
-PASS testComputedStyle("12px") is "none"
-PASS testNotInherited("none", "none") is "none"
-PASS testNotInherited("none", "child-flow") is "child-flow"
-PASS testNotInherited("parent-flow", "none") is "none"
-PASS testNotInherited("parent-flow", "child-flow") is "child-flow"
-PASS successfullyParsed is true
-
-TEST COMPLETE
+PASS Test Parse none 
+PASS Test Parse first-flow 
+PASS Test Parse 'first-flow' 
+PASS Test Parse ; 
+PASS Test Parse 1 
+PASS Test Parse 1.2 
+PASS Test Parse -1 
+PASS Test Parse 12px 
+PASS Test Computed Style none 
+PASS Test Computed Style '' 
+PASS Test Computed Style 'first-flow' 
+PASS Test Computed Style first-flow 
+PASS Test Computed Style 12px  
+PASS Test Non-Inherited none, none 
+PASS Test Non-Inherited none, child-flow 
+PASS Test Non-Inherited parent-flow, none 
+PASS Test Non-Inherited parent-flow, child-flow 
 
index 12112d47c3be7fb68cbb2cf67ddf62fce14fb1d6..f71ebefc8261179bcecb4bbe53056e5702bb421d 100644 (file)
@@ -1,10 +1,17 @@
-<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<!DOCTYPE html>
 <html>
-<head>
-<script src="../../fast/js/resources/js-test-pre.js"></script>
-</head>
-<body>
-<script src="script-tests/webkit-flow-parsing.js"></script>
-<script src="../../fast/js/resources/js-test-post.js"></script>
-</body>
+    <head>
+        <title>CSS Regions Parsing Test: Parse webkit-flow-into property</title>
+        <link rel="author" title="Jacob Goldstein" href="mailto:jacobg@adobe.com"/>
+        <link rel="help" href="http://www.w3.org/TR/css3-regions/#properties-and-rules"/>
+        <meta name="flags" content=""/>
+        <meta name="assert" content="Value specified for the webkit-flow-into property should be parsed correctly."/>
+        <script src="../../resources/testharness.js"></script> 
+        <script src="../../resources/testharnessreport.js"></script>       
+    </head>
+    <body>
+        <span>This test verifies that the webkit-flow-into property is correctly parsed</span>
+        <div id="log"></div>
+        <script src="script-tests/webkit-flow-parsing.js"></script>
+    </body>
 </html>
diff --git a/LayoutTests/resources/testharness.js b/LayoutTests/resources/testharness.js
new file mode 100644 (file)
index 0000000..5f78a5d
--- /dev/null
@@ -0,0 +1,1761 @@
+/*\r
+Distributed under both the W3C Test Suite License [1] and the W3C\r
+3-clause BSD License [2]. To contribute to a W3C Test Suite, see the\r
+policies and contribution forms [3].\r
+\r
+[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license\r
+[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license\r
+[3] http://www.w3.org/2004/10/27-testcases\r
+*/\r
+\r
+/*\r
+ * == Introduction ==\r
+ *\r
+ * This file provides a framework for writing testcases. It is intended to\r
+ * provide a convenient API for making common assertions, and to work both\r
+ * for testing synchronous and asynchronous DOM features in a way that\r
+ * promotes clear, robust, tests.\r
+ *\r
+ * == Basic Usage ==\r
+ *\r
+ * To use this file, import the script and the testharnessreport script into\r
+ * the test document:\r
+ * <script src="/resources/testharness.js"></script>\r
+ * <script src="/resources/testharnessreport.js"></script>\r
+ *\r
+ * Within each file one may define one or more tests. Each test is atomic\r
+ * in the sense that a single test has a single result (pass/fail/timeout).\r
+ * Within each test one may have a number of asserts. The test fails at the\r
+ * first failing assert, and the remainder of the test is (typically) not run.\r
+ *\r
+ * If the file containing the tests is a HTML file with an element of id "log"\r
+ * this will be populated with a table containing the test results after all\r
+ * the tests have run.\r
+ *\r
+ * NOTE: By default tests must be created before the load event fires. For ways\r
+ *       to create tests after the load event, see "Determining when all tests are\r
+ *       complete", below\r
+ *\r
+ * == Synchronous Tests ==\r
+ *\r
+ * To create a synchronous test use the test() function:\r
+ *\r
+ * test(test_function, name, properties)\r
+ *\r
+ * test_function is a function that contains the code to test. For example a\r
+ * trivial passing test would be:\r
+ *\r
+ * test(function() {assert_true(true)}, "assert_true with true")\r
+ *\r
+ * The function passed in is run in the test() call.\r
+ *\r
+ * properties is an object that overrides default test properties. The recognised properties\r
+ * are:\r
+ *    timeout - the test timeout in ms\r
+ *\r
+ * e.g.\r
+ * test(test_function, "Sample test", {timeout:1000})\r
+ *\r
+ * would run test_function with a timeout of 1s.\r
+ *\r
+ * == Asynchronous Tests ==\r
+ *\r
+ * Testing asynchronous features is somewhat more complex since the result of\r
+ * a test may depend on one or more events or other callbacks. The API provided\r
+ * for testing these features is indended to be rather low-level but hopefully\r
+ * applicable to many situations.\r
+ *\r
+ * To create a test, one starts by getting a Test object using async_test:\r
+ *\r
+ * async_test(name, properties)\r
+ *\r
+ * e.g.\r
+ * var t = async_test("Simple async test")\r
+ *\r
+ * Assertions can be added to the test by calling the step method of the test\r
+ * object with a function containing the test assertions:\r
+ *\r
+ * t.step(function() {assert_true(true)});\r
+ *\r
+ * When all the steps are complete, the done() method must be called:\r
+ *\r
+ * t.done();\r
+ *\r
+ * The properties argument is identical to that for test().\r
+ *\r
+ * In many cases it is convenient to run a step in response to an event or a\r
+ * callback. A convenient method of doing this is through the step_func method\r
+ * which returns a function that, when called runs a test step. For example\r
+ *\r
+ * object.some_event = t.step_func(function(e) {assert_true(e.a)});\r
+ *\r
+ * == Making assertions ==\r
+ *\r
+ * Functions for making assertions start assert_\r
+ * The best way to get a list is to look in this file for functions names\r
+ * matching that pattern. The general signature is\r
+ *\r
+ * assert_something(actual, expected, description)\r
+ *\r
+ * although not all assertions precisely match this pattern e.g. assert_true\r
+ * only takes actual and description as arguments.\r
+ *\r
+ * The description parameter is used to present more useful error messages when\r
+ * a test fails\r
+ *\r
+ * NOTE: All asserts must be located in a test() or a step of an async_test().\r
+ *       asserts outside these places won't be detected correctly by the harness\r
+ *       and may cause a file to stop testing.\r
+ *\r
+ * == Setup ==\r
+ *\r
+ * Sometimes tests require non-trivial setup that may fail. For this purpose\r
+ * there is a setup() function, that may be called with one or two arguments.\r
+ * The two argument version is:\r
+ *\r
+ * setup(func, properties)\r
+ *\r
+ * The one argument versions may omit either argument.\r
+ * func is a function to be run synchronously. setup() becomes a no-op once\r
+ * any tests have returned results. Properties are global properties of the test\r
+ * harness. Currently recognised properties are:\r
+ *\r
+ * timeout - The time in ms after which the harness should stop waiting for\r
+ *           tests to complete (this is different to the per-test timeout\r
+ *           because async tests do not start their timer until .step is called)\r
+ *\r
+ * explicit_done - Wait for an explicit call to done() before declaring all tests\r
+ *                 complete (see below)\r
+ *\r
+ * output_document - The document to which results should be logged. By default this is\r
+ *                   the current document but could be an ancestor document in some cases\r
+ *                   e.g. a SVG test loaded in an HTML wrapper\r
+ *\r
+ * == Determining when all tests are complete ==\r
+ *\r
+ * By default the test harness will assume there are no more results to come\r
+ * when:\r
+ * 1) There are no Test objects that have been created but not completed\r
+ * 2) The load event on the document has fired\r
+ *\r
+ * This behaviour can be overridden by setting the explicit_done property to true\r
+ * in a call to setup(). If explicit_done is true, the test harness will not assume\r
+ * it is done until the global done() function is called. Once done() is called, the\r
+ * two conditions above apply like normal.\r
+ *\r
+ * == Generating tests ==\r
+ *\r
+ * NOTE: this functionality may be removed\r
+ *\r
+ * There are scenarios in which is is desirable to create a large number of\r
+ * (synchronous) tests that are internally similar but vary in the parameters\r
+ * used. To make this easier, the generate_tests function allows a single\r
+ * function to be called with each set of parameters in a list:\r
+ *\r
+ * generate_tests(test_function, parameter_lists)\r
+ *\r
+ * For example:\r
+ *\r
+ * generate_tests(assert_equals, [\r
+ *     ["Sum one and one", 1+1, 2],\r
+ *     ["Sum one and zero", 1+0, 1]\r
+ *     ])\r
+ *\r
+ * Is equivalent to:\r
+ *\r
+ * test(function() {assert_equals(1+1, 2)}, "Sum one and one")\r
+ * test(function() {assert_equals(1+0, 1)}, "Sum one and zero")\r
+ *\r
+ * Note that the first item in each parameter list corresponds to the name of\r
+ * the test.\r
+ *\r
+ * == Callback API ==\r
+ *\r
+ * The framework provides callbacks corresponding to 3 events:\r
+ *\r
+ * start - happens when the first Test is created\r
+ * result - happens when a test result is recieved\r
+ * complete - happens when all results are recieved\r
+ *\r
+ * The page defining the tests may add callbacks for these events by calling\r
+ * the following methods:\r
+ *\r
+ *   add_start_callback(callback) - callback called with no arguments\r
+ *   add_result_callback(callback) - callback called with a test argument\r
+ *   add_completion_callback(callback) - callback called with an array of tests\r
+ *                                       and an status object\r
+ *\r
+ * tests have the following properties:\r
+ *   status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and\r
+ *           NOTRUN properties on the test object\r
+ *   message: A message indicating the reason for failure. In the future this\r
+ *            will always be a string\r
+ *\r
+ *  The status object gives the overall status of the harness. It has the\r
+ *  following properties:\r
+ *    status: Can be compared to the OK, ERROR and TIMEOUT properties\r
+ *    message: An error message set when the status is ERROR\r
+ *\r
+ * == External API ==\r
+ *\r
+ * In order to collect the results of multiple pages containing tests, the test\r
+ * harness will, when loaded in a nested browsing context, attempt to call\r
+ * certain functions in each ancestor browsing context:\r
+ *\r
+ * start - start_callback\r
+ * result - result_callback\r
+ * complete - completion_callback\r
+ *\r
+ * These are given the same arguments as the corresponding internal callbacks\r
+ * described above.\r
+ *\r
+ * == List of assertions ==\r
+ *\r
+ * assert_true(actual, description)\r
+ *   asserts that /actual/ is strictly true\r
+ *\r
+ * assert_false(actual, description)\r
+ *   asserts that /actual/ is strictly false\r
+ *\r
+ * assert_equals(actual, expected, description)\r
+ *   asserts that /actual/ is the same value as /expected/\r
+ *\r
+ * assert_not_equals(actual, expected, description)\r
+ *   asserts that /actual/ is a different value to /expected/. Yes, this means\r
+ *   that "expected" is a misnomer\r
+ *\r
+ * assert_in_array(actual, expected, description)\r
+ *   asserts that /expected/ is an Array, and /actual/ is equal to one of the\r
+ *   members -- expected.indexOf(actual) != -1\r
+ *\r
+ * assert_array_equals(actual, expected, description)\r
+ *   asserts that /actual/ and /expected/ have the same length and the value of\r
+ *   each indexed property in /actual/ is the strictly equal to the corresponding\r
+ *   property value in /expected/\r
+ *\r
+ * assert_approx_equals(actual, expected, epsilon, description)\r
+ *   asserts that /actual/ is a number within +/- /epsilon/ of /expected/\r
+ *\r
+ * assert_regexp_match(actual, expected, description)\r
+ *   asserts that /actual/ matches the regexp /expected/\r
+ *\r
+ * assert_own_property(object, property_name, description)\r
+ *   assert that object has own property property_name\r
+ *\r
+ * assert_inherits(object, property_name, description)\r
+ *   assert that object does not have an own property named property_name\r
+ *   but that property_name is present in the prototype chain for object\r
+ *\r
+ * assert_idl_attribute(object, attribute_name, description)\r
+ *   assert that an object that is an instance of some interface has the\r
+ *   attribute attribute_name following the conditions specified by WebIDL\r
+ *\r
+ * assert_readonly(object, property_name, description)\r
+ *   assert that property property_name on object is readonly\r
+ *\r
+ * assert_throws(code, func, description)\r
+ *   code - a DOMException/RangeException code as a string, e.g. "HIERARCHY_REQUEST_ERR"\r
+ *   func - a function that should throw\r
+ *\r
+ *   assert that func throws a DOMException or RangeException (as appropriate)\r
+ *   with the given code.  If an object is passed for code instead of a string,\r
+ *   checks that the thrown exception has a property called "name" that matches\r
+ *   the property of code called "name".  Note, this function will probably be\r
+ *   rewritten sometime to make more sense.\r
+ *\r
+ * assert_unreached(description)\r
+ *   asserts if called. Used to ensure that some codepath is *not* taken e.g.\r
+ *   an event does not fire.\r
+ *\r
+ * assert_exists(object, property_name, description)\r
+ *   *** deprecated ***\r
+ *   asserts that object has an own property property_name\r
+ *\r
+ * assert_not_exists(object, property_name, description)\r
+ *   *** deprecated ***\r
+ *   assert that object does not have own property property_name\r
+ */\r
+\r
+(function ()\r
+{\r
+    var debug = false;\r
+    // default timeout is 5 seconds, test can override if needed\r
+    var settings = {\r
+      output:true,\r
+      timeout:5000,\r
+      test_timeout:2000\r
+    };\r
+\r
+    var xhtml_ns = "http://www.w3.org/1999/xhtml";\r
+\r
+    // script_prefix is used by Output.prototype.show_results() to figure out\r
+    // where to get testharness.css from.  It's enclosed in an extra closure to\r
+    // not pollute the library's namespace with variables like "src".\r
+    var script_prefix = null;\r
+    (function ()\r
+    {\r
+        var scripts = document.getElementsByTagName("script");\r
+        for (var i = 0; i < scripts.length; i++)\r
+        {\r
+            if (scripts[i].src)\r
+            {\r
+                var src = scripts[i].src;\r
+            }\r
+            else if (scripts[i].href)\r
+            {\r
+                //SVG case\r
+                var src = scripts[i].href.baseVal;\r
+            }\r
+            if (src && src.slice(src.length - "testharness.js".length) === "testharness.js")\r
+            {\r
+                script_prefix = src.slice(0, src.length - "testharness.js".length);\r
+                break;\r
+            }\r
+        }\r
+    })();\r
+\r
+    /*\r
+     * API functions\r
+     */\r
+\r
+    var name_counter = 0;\r
+    function next_default_name()\r
+    {\r
+        //Don't use document.title to work around an Opera bug in XHTML documents\r
+        var prefix = document.getElementsByTagName("title").length > 0 ?\r
+                         document.getElementsByTagName("title")[0].firstChild.data :\r
+                         "Untitled";\r
+        var suffix = name_counter > 0 ? " " + name_counter : "";\r
+        name_counter++;\r
+        return prefix + suffix;\r
+    }\r
+\r
+    function test(func, name, properties)\r
+    {\r
+        var test_name = name ? name : next_default_name();\r
+        properties = properties ? properties : {};\r
+        var test_obj = new Test(test_name, properties);\r
+        test_obj.step(func);\r
+        if (test_obj.status === test_obj.NOTRUN) {\r
+            test_obj.done();\r
+        }\r
+    }\r
+\r
+    function async_test(name, properties)\r
+    {\r
+        var test_name = name ? name : next_default_name();\r
+        properties = properties ? properties : {};\r
+        var test_obj = new Test(test_name, properties);\r
+        return test_obj;\r
+    }\r
+\r
+    function setup(func_or_properties, maybe_properties)\r
+    {\r
+        var func = null;\r
+        var properties = {};\r
+        if (arguments.length === 2) {\r
+            func = func_or_properties;\r
+            properties = maybe_properties;\r
+        } else if (func_or_properties instanceof Function){\r
+            func = func_or_properties;\r
+        } else {\r
+            properties = func_or_properties;\r
+        }\r
+        tests.setup(func, properties);\r
+        output.setup(properties);\r
+    }\r
+\r
+    function done() {\r
+        tests.end_wait();\r
+    }\r
+\r
+    function generate_tests(func, args) {\r
+        forEach(args, function(x)\r
+                {\r
+                    var name = x[0];\r
+                    test(function()\r
+                         {\r
+                             func.apply(this, x.slice(1));\r
+                         }, name);\r
+                });\r
+    }\r
+\r
+    function on_event(object, event, callback)\r
+    {\r
+      object.addEventListener(event, callback, false);\r
+    }\r
+\r
+    expose(test, 'test');\r
+    expose(async_test, 'async_test');\r
+    expose(generate_tests, 'generate_tests');\r
+    expose(setup, 'setup');\r
+    expose(done, 'done');\r
+    expose(on_event, 'on_event');\r
+\r
+    /*\r
+     * Return a string truncated to the given length, with ... added at the end\r
+     * if it was longer.\r
+     */\r
+    function truncate(s, len)\r
+    {\r
+        if (s.length > len) {\r
+            return s.substring(0, len - 3) + "...";\r
+        }\r
+        return s;\r
+    }\r
+\r
+    function format_string(str) {\r
+        for (var i = 0; i < 32; i++) {\r
+            var replace = "\\";\r
+            switch (i) {\r
+            case 0: replace += "0"; break;\r
+            case 1: replace += "x01"; break;\r
+            case 2: replace += "x02"; break;\r
+            case 3: replace += "x03"; break;\r
+            case 4: replace += "x04"; break;\r
+            case 5: replace += "x05"; break;\r
+            case 6: replace += "x06"; break;\r
+            case 7: replace += "x07"; break;\r
+            case 8: replace += "b"; break;\r
+            case 9: replace += "t"; break;\r
+            case 10: replace += "n"; break;\r
+            case 11: replace += "v"; break;\r
+            case 12: replace += "f"; break;\r
+            case 13: replace += "r"; break;\r
+            case 14: replace += "x0e"; break;\r
+            case 15: replace += "x0f"; break;\r
+            case 16: replace += "x10"; break;\r
+            case 17: replace += "x11"; break;\r
+            case 18: replace += "x12"; break;\r
+            case 19: replace += "x13"; break;\r
+            case 20: replace += "x14"; break;\r
+            case 21: replace += "x15"; break;\r
+            case 22: replace += "x16"; break;\r
+            case 23: replace += "x17"; break;\r
+            case 24: replace += "x18"; break;\r
+            case 25: replace += "x19"; break;\r
+            case 26: replace += "x1a"; break;\r
+            case 27: replace += "x1b"; break;\r
+            case 28: replace += "x1c"; break;\r
+            case 29: replace += "x1d"; break;\r
+            case 30: replace += "x1e"; break;\r
+            case 31: replace += "x1f"; break;\r
+            }\r
+            str = str.replace(RegExp(String.fromCharCode(i), "g"), replace);\r
+        }\r
+        return str.replace(/"/g, '\\"')\r
+    }\r
+\r
+    /*\r
+     * Convert a value to a nice, human-readable string\r
+     */\r
+    function format_value(val)\r
+    {\r
+        if (Array.isArray(val))\r
+        {\r
+            return "[" + val.map(format_value).join(", ") + "]";\r
+        }\r
+\r
+        switch (typeof val)\r
+        {\r
+        case "string":\r
+            return '"' + format_string(val) + '"';\r
+        case "boolean":\r
+        case "undefined":\r
+            return String(val);\r
+        case "number":\r
+            // In JavaScript, -0 === 0 and String(-0) == "0", so we have to\r
+            // special-case.\r
+            if (val === -0 && 1/val === -Infinity)\r
+            {\r
+                return "-0";\r
+            }\r
+            return String(val);\r
+        case "object":\r
+            if (val === null)\r
+            {\r
+                return "null";\r
+            }\r
+\r
+            // Special-case Node objects, since those come up a lot in my tests.  I\r
+            // ignore namespaces.  I use duck-typing instead of instanceof, because\r
+            // instanceof doesn't work if the node is from another window (like an\r
+            // iframe's contentWindow):\r
+            // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295\r
+            if ("nodeType" in val\r
+            && "nodeName" in val\r
+            && "nodeValue" in val\r
+            && "childNodes" in val)\r
+            {\r
+                switch (val.nodeType)\r
+                {\r
+                case Node.ELEMENT_NODE:\r
+                    var ret = "<" + val.tagName.toLowerCase();\r
+                    for (var i = 0; i < val.attributes.length; i++)\r
+                    {\r
+                        ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';\r
+                    }\r
+                    ret += ">" + val.innerHTML + "</" + val.tagName.toLowerCase() + ">";\r
+                    return "Element node " + truncate(ret, 60);\r
+                case Node.TEXT_NODE:\r
+                    return 'Text node "' + truncate(val.data, 60) + '"';\r
+                case Node.PROCESSING_INSTRUCTION_NODE:\r
+                    return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));\r
+                case Node.COMMENT_NODE:\r
+                    return "Comment node <!--" + truncate(val.data, 60) + "-->";\r
+                case Node.DOCUMENT_NODE:\r
+                    return "Document node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");\r
+                case Node.DOCUMENT_TYPE_NODE:\r
+                    return "DocumentType node";\r
+                case Node.DOCUMENT_FRAGMENT_NODE:\r
+                    return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");\r
+                default:\r
+                    return "Node object of unknown type";\r
+                }\r
+            }\r
+\r
+            // Fall through to default\r
+        default:\r
+            return typeof val + ' "' + truncate(String(val), 60) + '"';\r
+        }\r
+    }\r
+    expose(format_value, "format_value");\r
+\r
+    /*\r
+     * Assertions\r
+     */\r
+\r
+    function assert_true(actual, description)\r
+    {\r
+        assert(actual === true, "assert_true", description,\r
+                                "expected true got ${actual}", {actual:actual});\r
+    };\r
+    expose(assert_true, "assert_true");\r
+\r
+    function assert_false(actual, description)\r
+    {\r
+        assert(actual === false, "assert_false", description,\r
+                                 "expected false got ${actual}", {actual:actual});\r
+    };\r
+    expose(assert_false, "assert_false");\r
+\r
+    function same_value(x, y) {\r
+        if (y !== y)\r
+        {\r
+            //NaN case\r
+            return x !== x;\r
+        }\r
+        else if (x === 0 && y === 0) {\r
+            //Distinguish +0 and -0\r
+            return 1/x === 1/y;\r
+        }\r
+        else\r
+        {\r
+            //typical case\r
+            return x === y;\r
+        }\r
+    }\r
+\r
+    function assert_equals(actual, expected, description)\r
+    {\r
+         /*\r
+          * Test if two primitives are equal or two objects\r
+          * are the same object\r
+          */\r
+        assert(same_value(actual, expected), "assert_equals", description,\r
+                                             "expected ${expected} but got ${actual}",\r
+                                             {expected:expected, actual:actual});\r
+    };\r
+    expose(assert_equals, "assert_equals");\r
+\r
+    function assert_not_equals(actual, expected, description)\r
+    {\r
+         /*\r
+          * Test if two primitives are unequal or two objects\r
+          * are different objects\r
+          */\r
+        assert(!same_value(actual, expected), "assert_not_equals", description,\r
+                                              "got disallowed value ${actual}",\r
+                                              {actual:actual});\r
+    };\r
+    expose(assert_not_equals, "assert_not_equals");\r
+\r
+    function assert_in_array(actual, expected, description)\r
+    {\r
+        assert(expected.indexOf(actual) != -1, "assert_in_array", description,\r
+                                               "value ${actual} not in array ${expected}",\r
+                                               {actual:actual, expected:expected});\r
+    }\r
+    expose(assert_in_array, "assert_in_array");\r
+\r
+    function assert_object_equals(actual, expected, description)\r
+    {\r
+         //This needs to be improved a great deal\r
+         function check_equal(expected, actual, stack)\r
+         {\r
+             stack.push(actual);\r
+\r
+             var p;\r
+             for (p in actual)\r
+             {\r
+                 assert(expected.hasOwnProperty(p), "assert_object_equals", description,\r
+                                                    "unexpected property ${p}", {p:p});\r
+\r
+                 if (typeof actual[p] === "object" && actual[p] !== null)\r
+                 {\r
+                     if (stack.indexOf(actual[p]) === -1)\r
+                     {\r
+                         check_equal(actual[p], expected[p], stack);\r
+                     }\r
+                 }\r
+                 else\r
+                 {\r
+                     assert(actual[p] === expected[p], "assert_object_equals", description,\r
+                                                       "property ${p} expected ${expected} got ${actual}",\r
+                                                       {p:p, expected:expected, actual:actual});\r
+                 }\r
+             }\r
+             for (p in expected)\r
+             {\r
+                 assert(actual.hasOwnProperty(p),\r
+                        "assert_object_equals", description,\r
+                        "expected property ${p} missing", {p:p});\r
+             }\r
+             stack.pop();\r
+         }\r
+         check_equal(actual, expected, []);\r
+    };\r
+    expose(assert_object_equals, "assert_object_equals");\r
+\r
+    function assert_array_equals(actual, expected, description)\r
+    {\r
+        assert(actual.length === expected.length,\r
+               "assert_array_equals", description,\r
+               "lengths differ, expected ${expected} got ${actual}",\r
+               {expected:expected.length, actual:actual.length});\r
+\r
+        for (var i=0; i < actual.length; i++)\r
+        {\r
+            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),\r
+                   "assert_array_equals", description,\r
+                   "property ${i}, property expected to be $expected but was $actual",\r
+                   {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",\r
+                   actual:actual.hasOwnProperty(i) ? "present" : "missing"});\r
+            assert(expected[i] === actual[i],\r
+                   "assert_array_equals", description,\r
+                   "property ${i}, expected ${expected} but got ${actual}",\r
+                   {i:i, expected:expected[i], actual:actual[i]});\r
+        }\r
+    }\r
+    expose(assert_array_equals, "assert_array_equals");\r
+\r
+    function assert_approx_equals(actual, expected, epsilon, description)\r
+    {\r
+        /*\r
+         * Test if two primitive numbers are equal withing +/- epsilon\r
+         */\r
+        assert(typeof actual === "number",\r
+               "assert_approx_equals", description,\r
+               "expected a number but got a ${type_actual}",\r
+               {type_actual:typeof actual});\r
+\r
+        assert(Math.abs(actual - expected) <= epsilon,\r
+               "assert_approx_equals", description,\r
+               "expected ${expected} +/- ${epsilon} but got ${actual}",\r
+               {expected:expected, actual:actual, epsilon:epsilon});\r
+    };\r
+    expose(assert_approx_equals, "assert_approx_equals");\r
+\r
+    function assert_regexp_match(actual, expected, description) {\r
+        /*\r
+         * Test if a string (actual) matches a regexp (expected)\r
+         */\r
+        assert(expected.test(actual),\r
+               "assert_regexp_match", description,\r
+               "expected ${expected} but got ${actual}",\r
+               {expected:expected, actual:actual});\r
+    }\r
+    expose(assert_regexp_match, "assert_regexp_match");\r
+\r
+\r
+    function _assert_own_property(name) {\r
+        return function(object, property_name, description)\r
+        {\r
+            assert(object.hasOwnProperty(property_name),\r
+                   name, description,\r
+                   "expected property ${p} missing", {p:property_name});\r
+        };\r
+    }\r
+    expose(_assert_own_property("assert_exists"), "assert_exists");\r
+    expose(_assert_own_property("assert_own_property"), "assert_own_property");\r
+\r
+    function assert_not_exists(object, property_name, description)\r
+    {\r
+        assert(!object.hasOwnProperty(property_name),\r
+               "assert_not_exists", description,\r
+               "unexpected property ${p} found", {p:property_name});\r
+    };\r
+    expose(assert_not_exists, "assert_not_exists");\r
+\r
+    function _assert_inherits(name) {\r
+        return function (object, property_name, description)\r
+        {\r
+            assert(typeof object === "object",\r
+                   name, description,\r
+                   "provided value is not an object");\r
+\r
+            assert("hasOwnProperty" in object,\r
+                   name, description,\r
+                   "provided value is an object but has no hasOwnProperty method");\r
+\r
+            assert(!object.hasOwnProperty(property_name),\r
+                   name, description,\r
+                   "property ${p} found on object expected in prototype chain",\r
+                   {p:property_name});\r
+\r
+            assert(property_name in object,\r
+                   name, description,\r
+                   "property ${p} not found in prototype chain",\r
+                   {p:property_name});\r
+        };\r
+    }\r
+    expose(_assert_inherits("assert_inherits"), "assert_inherits");\r
+    expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");\r
+\r
+    function assert_readonly(object, property_name, description)\r
+    {\r
+         var initial_value = object[property_name];\r
+         try {\r
+             //Note that this can have side effects in the case where\r
+             //the property has PutForwards\r
+             object[property_name] = initial_value + "a"; //XXX use some other value here?\r
+             assert(object[property_name] === initial_value,\r
+                    "assert_readonly", description,\r
+                    "changing property ${p} succeeded",\r
+                    {p:property_name});\r
+         }\r
+         finally\r
+         {\r
+             object[property_name] = initial_value;\r
+         }\r
+    };\r
+    expose(assert_readonly, "assert_readonly");\r
+\r
+    function assert_throws(code, func, description)\r
+    {\r
+        try\r
+        {\r
+            func.call(this);\r
+            assert(false, "assert_throws", description,\r
+                   "${func} did not throw", {func:func});\r
+        }\r
+        catch(e)\r
+        {\r
+            if (e instanceof AssertionError) {\r
+                throw(e);\r
+            }\r
+            if (typeof code === "object")\r
+            {\r
+                assert(typeof e == "object" && "name" in e && e.name == code.name,\r
+                       "assert_throws", description,\r
+                       "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",\r
+                                    {func:func, actual:e, actual_name:e.name,\r
+                                     expected:code,\r
+                                     expected_name:code.name});\r
+                return;\r
+            }\r
+            var required_props = {};\r
+            required_props.code = {\r
+                INDEX_SIZE_ERR: 1,\r
+                HIERARCHY_REQUEST_ERR: 3,\r
+                WRONG_DOCUMENT_ERR: 4,\r
+                INVALID_CHARACTER_ERR: 5,\r
+                NO_MODIFICATION_ALLOWED_ERR: 7,\r
+                NOT_FOUND_ERR: 8,\r
+                NOT_SUPPORTED_ERR: 9,\r
+                INVALID_STATE_ERR: 11,\r
+                SYNTAX_ERR: 12,\r
+                INVALID_MODIFICATION_ERR: 13,\r
+                NAMESPACE_ERR: 14,\r
+                INVALID_ACCESS_ERR: 15,\r
+                TYPE_MISMATCH_ERR: 17,\r
+                SECURITY_ERR: 18,\r
+                NETWORK_ERR: 19,\r
+                ABORT_ERR: 20,\r
+                URL_MISMATCH_ERR: 21,\r
+                QUOTA_EXCEEDED_ERR: 22,\r
+                TIMEOUT_ERR: 23,\r
+                INVALID_NODE_TYPE_ERR: 24,\r
+                DATA_CLONE_ERR: 25,\r
+            }[code];\r
+            if (required_props.code === undefined)\r
+            {\r
+                throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');\r
+            }\r
+            required_props[code] = required_props.code;\r
+            //Uncomment this when the latest version of every browser\r
+            //actually implements the spec; otherwise it just creates\r
+            //zillions of failures.  Also do required_props.type.\r
+            //required_props.name = code;\r
+            //\r
+            //We'd like to test that e instanceof the appropriate interface,\r
+            //but we can't, because we don't know what window it was created\r
+            //in.  It might be an instanceof the appropriate interface on some\r
+            //unknown other window.  TODO: Work around this somehow?\r
+\r
+            assert(typeof e == "object",\r
+                   "assert_throws", description,\r
+                   "${func} threw ${e} with type ${type}, not an object",\r
+                   {func:func, e:e, type:typeof e});\r
+\r
+            for (var prop in required_props)\r
+            {\r
+                assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],\r
+                       "assert_throws", description,\r
+                       "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",\r
+                       {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});\r
+            }\r
+        }\r
+    }\r
+    expose(assert_throws, "assert_throws");\r
+\r
+    function assert_unreached(description) {\r
+         assert(false, "assert_unreached", description,\r
+                "Reached unreachable code");\r
+    }\r
+    expose(assert_unreached, "assert_unreached");\r
+\r
+    function Test(name, properties)\r
+    {\r
+        this.name = name;\r
+        this.status = this.NOTRUN;\r
+        this.timeout_id = null;\r
+        this.is_done = false;\r
+\r
+        this.timeout_length = properties.timeout ? properties.timeout : settings.test_timeout;\r
+\r
+        this.message = null;\r
+\r
+        var this_obj = this;\r
+        this.steps = [];\r
+\r
+        tests.push(this);\r
+    }\r
+\r
+    Test.prototype = {\r
+        PASS:0,\r
+        FAIL:1,\r
+        TIMEOUT:2,\r
+        NOTRUN:3\r
+    };\r
+\r
+\r
+    Test.prototype.step = function(func, this_obj)\r
+    {\r
+        //In case the test has already failed\r
+        if (this.status !== this.NOTRUN)\r
+        {\r
+          return;\r
+        }\r
+\r
+        tests.started = true;\r
+\r
+        if (this.timeout_id === null) {\r
+            this.set_timeout();\r
+        }\r
+\r
+        this.steps.push(func);\r
+\r
+        if (arguments.length === 1)\r
+        {\r
+            this_obj = this;\r
+        }\r
+\r
+        try\r
+        {\r
+            func.apply(this_obj, Array.prototype.slice.call(arguments, 2));\r
+        }\r
+        catch(e)\r
+        {\r
+            //This can happen if something called synchronously invoked another\r
+            //step\r
+            if (this.status !== this.NOTRUN)\r
+            {\r
+                return;\r
+            }\r
+            this.status = this.FAIL;\r
+            this.message = e.message;\r
+            if (typeof e.stack != "undefined" && typeof e.message == "string") {\r
+                //Try to make it more informative for some exceptions, at least\r
+                //in Gecko and WebKit.  This results in a stack dump instead of\r
+                //just errors like "Cannot read property 'parentNode' of null"\r
+                //or "root is null".  Makes it a lot longer, of course.\r
+                this.message += "(stack: " + e.stack + ")";\r
+            }\r
+            this.done();\r
+            if (debug && e.constructor !== AssertionError) {\r
+                throw e;\r
+            }\r
+        }\r
+    };\r
+\r
+    Test.prototype.step_func = function(func, this_obj)\r
+    {\r
+        var test_this = this;\r
+\r
+        if (arguments.length === 1)\r
+        {\r
+            this_obj = test_this;\r
+        }\r
+\r
+        return function()\r
+        {\r
+            test_this.step.apply(test_this, [func, this_obj].concat(\r
+                Array.prototype.slice.call(arguments)));\r
+        };\r
+    };\r
+\r
+    Test.prototype.set_timeout = function()\r
+    {\r
+        var this_obj = this;\r
+        this.timeout_id = setTimeout(function()\r
+                                     {\r
+                                         this_obj.timeout();\r
+                                     }, this.timeout_length);\r
+    };\r
+\r
+    Test.prototype.timeout = function()\r
+    {\r
+        this.status = this.TIMEOUT;\r
+        this.timeout_id = null;\r
+        this.message = "Test timed out";\r
+        this.done();\r
+    };\r
+\r
+    Test.prototype.done = function()\r
+    {\r
+        if (this.is_done) {\r
+            return;\r
+        }\r
+        clearTimeout(this.timeout_id);\r
+        if (this.status === this.NOTRUN)\r
+        {\r
+            this.status = this.PASS;\r
+        }\r
+        this.is_done = true;\r
+        tests.result(this);\r
+    };\r
+\r
+\r
+    /*\r
+     * Harness\r
+     */\r
+\r
+    function TestsStatus()\r
+    {\r
+        this.status = null;\r
+        this.message = null;\r
+    }\r
+    TestsStatus.prototype = {\r
+        OK:0,\r
+        ERROR:1,\r
+        TIMEOUT:2\r
+    };\r
+\r
+    function Tests()\r
+    {\r
+        this.tests = [];\r
+        this.num_pending = 0;\r
+\r
+        this.phases = {\r
+            INITIAL:0,\r
+            SETUP:1,\r
+            HAVE_TESTS:2,\r
+            HAVE_RESULTS:3,\r
+            COMPLETE:4\r
+        };\r
+        this.phase = this.phases.INITIAL;\r
+\r
+        //All tests can't be done until the load event fires\r
+        this.all_loaded = false;\r
+        this.wait_for_finish = false;\r
+        this.processing_callbacks = false;\r
+\r
+        this.timeout_length = settings.timeout;\r
+        this.timeout_id = null;\r
+        this.set_timeout();\r
+\r
+        this.start_callbacks = [];\r
+        this.test_done_callbacks = [];\r
+        this.all_done_callbacks = [];\r
+\r
+        this.status = new TestsStatus();\r
+\r
+        var this_obj = this;\r
+\r
+        on_event(window, "load",\r
+                 function()\r
+                 {\r
+                     this_obj.all_loaded = true;\r
+                     if (this_obj.all_done())\r
+                     {\r
+                         this_obj.complete();\r
+                     }\r
+                 });\r
+        this.properties = {};\r
+    }\r
+\r
+    Tests.prototype.setup = function(func, properties)\r
+    {\r
+        if (this.phase >= this.phases.HAVE_RESULTS)\r
+        {\r
+            return;\r
+        }\r
+        if (this.phase < this.phases.SETUP)\r
+        {\r
+            this.phase = this.phases.SETUP;\r
+        }\r
+\r
+        for (var p in properties)\r
+        {\r
+            if (properties.hasOwnProperty(p))\r
+            {\r
+                this.properties[p] = properties[p];\r
+            }\r
+        }\r
+\r
+        if (properties.timeout)\r
+        {\r
+            this.timeout_length = properties.timeout;\r
+            this.set_timeout();\r
+        }\r
+        if (properties.explicit_done)\r
+        {\r
+            this.wait_for_finish = true;\r
+        }\r
+\r
+        if (func)\r
+        {\r
+            try\r
+            {\r
+                func();\r
+            } catch(e)\r
+            {\r
+                this.status.status = this.status.ERROR;\r
+                this.status.message = e;\r
+            };\r
+        }\r
+    };\r
+\r
+    Tests.prototype.set_timeout = function()\r
+    {\r
+        var this_obj = this;\r
+        clearTimeout(this.timeout_id);\r
+        this.timeout_id = setTimeout(function() {\r
+                                         this_obj.timeout();\r
+                                     }, this.timeout_length);\r
+    };\r
+\r
+    Tests.prototype.timeout = function() {\r
+        this.status.status = this.status.TIMEOUT;\r
+        this.complete();\r
+    };\r
+\r
+    Tests.prototype.end_wait = function()\r
+    {\r
+        this.wait_for_finish = false;\r
+        if (this.all_done()) {\r
+            this.complete();\r
+        }\r
+    };\r
+\r
+    Tests.prototype.push = function(test)\r
+    {\r
+        if (this.phase < this.phases.HAVE_TESTS) {\r
+            this.notify_start();\r
+        }\r
+        this.num_pending++;\r
+        this.tests.push(test);\r
+    };\r
+\r
+    Tests.prototype.all_done = function() {\r
+        return (this.all_loaded && this.num_pending === 0 &&\r
+                !this.wait_for_finish && !this.processing_callbacks);\r
+    };\r
+\r
+    Tests.prototype.start = function() {\r
+        this.phase = this.phases.HAVE_TESTS;\r
+        this.notify_start();\r
+    };\r
+\r
+    Tests.prototype.notify_start = function() {\r
+        var this_obj = this;\r
+        forEach (this.start_callbacks,\r
+                 function(callback)\r
+                 {\r
+                     callback(this_obj.properties);\r
+                 });\r
+        forEach(ancestor_windows(),\r
+                function(w)\r
+                {\r
+                    if(w.start_callback)\r
+                    {\r
+                        try\r
+                        {\r
+                            w.start_callback(this_obj.properties);\r
+                        }\r
+                        catch(e)\r
+                        {\r
+                            if (debug)\r
+                            {\r
+                                throw(e);\r
+                            }\r
+                        }\r
+                    }\r
+                });\r
+    };\r
+\r
+    Tests.prototype.result = function(test)\r
+    {\r
+        if (this.phase > this.phases.HAVE_RESULTS)\r
+        {\r
+            return;\r
+        }\r
+        this.phase = this.phases.HAVE_RESULTS;\r
+        this.num_pending--;\r
+        this.notify_result(test);\r
+    };\r
+\r
+    Tests.prototype.notify_result = function(test) {\r
+        var this_obj = this;\r
+        this.processing_callbacks = true;\r
+        forEach(this.test_done_callbacks,\r
+                function(callback)\r
+                {\r
+                    callback(test, this_obj);\r
+                });\r
+\r
+        forEach(ancestor_windows(),\r
+                function(w)\r
+                {\r
+                    if(w.result_callback)\r
+                    {\r
+                        try\r
+                        {\r
+                            w.result_callback(test);\r
+                        }\r
+                        catch(e)\r
+                        {\r
+                            if(debug) {\r
+                                throw e;\r
+                            }\r
+                        }\r
+                    }\r
+                });\r
+        this.processing_callbacks = false;\r
+        if (this_obj.all_done())\r
+        {\r
+            this_obj.complete();\r
+        }\r
+    };\r
+\r
+    Tests.prototype.complete = function() {\r
+        if (this.phase === this.phases.COMPLETE) {\r
+            return;\r
+        }\r
+        this.phase = this.phases.COMPLETE;\r
+        this.notify_complete();\r
+    };\r
+\r
+    Tests.prototype.notify_complete = function()\r
+    {\r
+        clearTimeout(this.timeout_id);\r
+        var this_obj = this;\r
+        if (this.status.status === null)\r
+        {\r
+            this.status.status = this.status.OK;\r
+        }\r
+\r
+        forEach (this.all_done_callbacks,\r
+                 function(callback)\r
+                 {\r
+                     callback(this_obj.tests, this_obj.status);\r
+                 });\r
+\r
+        forEach(ancestor_windows(),\r
+                function(w)\r
+                {\r
+                    if(w.completion_callback)\r
+                    {\r
+                        try\r
+                        {\r
+                            w.completion_callback(this_obj.tests, this_obj.status);\r
+                        }\r
+                        catch(e)\r
+                        {\r
+                            if (debug)\r
+                            {\r
+                                throw e;\r
+                            }\r
+                        }\r
+                    }\r
+                });\r
+    };\r
+\r
+    var tests = new Tests();\r
+\r
+    function add_start_callback(callback) {\r
+        tests.start_callbacks.push(callback);\r
+    }\r
+\r
+    function add_result_callback(callback)\r
+    {\r
+        tests.test_done_callbacks.push(callback);\r
+    }\r
+\r
+    function add_completion_callback(callback)\r
+    {\r
+       tests.all_done_callbacks.push(callback);\r
+    }\r
+\r
+    expose(add_start_callback, 'add_start_callback');\r
+    expose(add_result_callback, 'add_result_callback');\r
+    expose(add_completion_callback, 'add_completion_callback');\r
+\r
+    /*\r
+     * Output listener\r
+    */\r
+\r
+    function Output() {\r
+      this.output_document = null;\r
+      this.output_node = null;\r
+      this.done_count = 0;\r
+      this.enabled = settings.output;\r
+      this.phase = this.INITIAL;\r
+    }\r
+\r
+    Output.prototype.INITIAL = 0;\r
+    Output.prototype.STARTED = 1;\r
+    Output.prototype.HAVE_RESULTS = 2;\r
+    Output.prototype.COMPLETE = 3;\r
+\r
+    Output.prototype.setup = function(properties) {\r
+        if (this.phase > this.INITIAL) {\r
+            return;\r
+        }\r
+\r
+        //If output is disabled in testharnessreport.js the test shouldn't be\r
+        //able to override that\r
+        this.enabled = this.enabled && (properties.hasOwnProperty("output") ?\r
+                                        properties.output : settings.output);\r
+    };\r
+\r
+    Output.prototype.init = function(properties)\r
+    {\r
+        if (this.phase >= this.STARTED) {\r
+            return;\r
+        }\r
+        if (properties.output_document) {\r
+            this.output_document = properties.output_document;\r
+        } else {\r
+            this.output_document = document;\r
+        }\r
+        this.phase = this.STARTED;\r
+    };\r
+\r
+    Output.prototype.resolve_log = function()\r
+    {\r
+        if (!this.output_document) {\r
+            return;\r
+        }\r
+        var node = this.output_document.getElementById("log");\r
+        if (node) {\r
+            this.output_node = node;\r
+        }\r
+    };\r
+\r
+    Output.prototype.show_status = function(test)\r
+    {\r
+        if (this.phase < this.STARTED)\r
+        {\r
+            this.init();\r
+        }\r
+        if (!this.enabled)\r
+        {\r
+            return;\r
+        }\r
+        if (this.phase < this.HAVE_RESULTS)\r
+        {\r
+            this.resolve_log();\r
+            this.phase = this.HAVE_RESULTS;\r
+        }\r
+        this.done_count++;\r
+        if (this.output_node)\r
+        {\r
+            if (this.done_count < 100\r
+            || (this.done_count < 1000 && this.done_count % 100 == 0)\r
+            || this.done_count % 1000 == 0) {\r
+                this.output_node.textContent = "Running, "\r
+                    + this.done_count + " complete, "\r
+                    + tests.num_pending + " remain";\r
+            }\r
+        }\r
+    };\r
+\r
+    Output.prototype.show_results = function (tests, harness_status)\r
+    {\r
+        if (this.phase >= this.COMPLETE) {\r
+            return;\r
+        }\r
+        if (!this.enabled)\r
+        {\r
+            return;\r
+        }\r
+        if (!this.output_node) {\r
+            this.resolve_log();\r
+        }\r
+        this.phase = this.COMPLETE;\r
+\r
+        var log = this.output_node;\r
+        if (!log)\r
+        {\r
+            return;\r
+        }\r
+        var output_document = this.output_document;\r
+\r
+        while (log.lastChild)\r
+        {\r
+            log.removeChild(log.lastChild);\r
+        }\r
+\r
+        if (script_prefix != null) {\r
+            var stylesheet = output_document.createElementNS(xhtml_ns, "link");\r
+            stylesheet.setAttribute("rel", "stylesheet");\r
+            stylesheet.setAttribute("href", script_prefix + "testharness.css");\r
+            var heads = output_document.getElementsByTagName("head");\r
+            if (heads.length) {\r
+                heads[0].appendChild(stylesheet);\r
+            }\r
+        }\r
+\r
+        var status_text = {};\r
+        status_text[Test.prototype.PASS] = "Pass";\r
+        status_text[Test.prototype.FAIL] = "Fail";\r
+        status_text[Test.prototype.TIMEOUT] = "Timeout";\r
+        status_text[Test.prototype.NOTRUN] = "Not Run";\r
+\r
+        var status_number = {};\r
+        forEach(tests, function(test) {\r
+                    var status = status_text[test.status];\r
+                    if (status_number.hasOwnProperty(status))\r
+                    {\r
+                        status_number[status] += 1;\r
+                    } else {\r
+                        status_number[status] = 1;\r
+                    }\r
+                });\r
+\r
+        function status_class(status)\r
+        {\r
+            return status.replace(/\s/g, '').toLowerCase();\r
+        }\r
+\r
+        var summary_template = ["section", {"id":"summary"},\r
+                                ["h2", {}, "Summary"],\r
+                                ["p", {}, "Found ${num_tests} tests"],\r
+                                function(vars) {\r
+                                    var rv = [["div", {}]];\r
+                                    var i=0;\r
+                                    while (status_text.hasOwnProperty(i)) {\r
+                                        if (status_number.hasOwnProperty(status_text[i])) {\r
+                                            var status = status_text[i];\r
+                                            rv[0].push(["div", {"class":status_class(status)},\r
+                                                        ["label", {},\r
+                                                         ["input", {type:"checkbox", checked:"checked"}],\r
+                                                         status_number[status] + " " + status]]);\r
+                                        }\r
+                                        i++;\r
+                                    }\r
+                                    return rv;\r
+                                }];\r
+\r
+        log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));\r
+\r
+        forEach(output_document.querySelectorAll("section#summary label"),\r
+                function(element)\r
+                {\r
+                    on_event(element, "click",\r
+                             function(e)\r
+                             {\r
+                                 if (output_document.getElementById("results") === null)\r
+                                 {\r
+                                     e.preventDefault();\r
+                                     return;\r
+                                 }\r
+                                 var result_class = element.parentNode.getAttribute("class");\r
+                                 var style_element = output_document.querySelector("style#hide-" + result_class);\r
+                                 var input_element = element.querySelector("input");\r
+                                 if (!style_element && !input_element.checked) {\r
+                                     style_element = output_document.createElementNS(xhtml_ns, "style");\r
+                                     style_element.id = "hide-" + result_class;\r
+                                     style_element.innerHTML = "table#results > tbody > tr."+result_class+"{display:none}";\r
+                                     output_document.body.appendChild(style_element);\r
+                                 } else if (style_element && input_element.checked) {\r
+                                     style_element.parentNode.removeChild(style_element);\r
+                                 }\r
+                             });\r
+                });\r
+\r
+        // This use of innerHTML plus manual escaping is not recommended in\r
+        // general, but is necessary here for performance.  Using textContent\r
+        // on each individual <td> adds tens of seconds of execution time for\r
+        // large test suites (tens of thousands of tests).\r
+        function escape_html(s)\r
+        {\r
+            return s.replace(/\&/g, "&amp;")\r
+                .replace(/</g, "&lt;")\r
+                .replace(/"/g, "&quot;")\r
+                .replace(/'/g, "&#39;");\r
+        }\r
+\r
+        log.appendChild(document.createElement("section"));\r
+        var html = "<h2>Details</h2><table id='results'>"\r
+            + "<thead><tr><th>Result</th><th>Test Name</th><th>Message</th></tr></thead>"\r
+            + "<tbody>";\r
+        for (var i = 0; i < tests.length; i++) {\r
+            html += '<tr class="'\r
+                + escape_html(status_class(status_text[tests[i].status]))\r
+                + '"><td>'\r
+                + escape_html(status_text[tests[i].status])\r
+                + "</td><td>"\r
+                + escape_html(format_string(tests[i].name))\r
+                + "</td><td>"\r
+                + escape_html(tests[i].message ? format_string(tests[i].message) : " ")\r
+                + "</td></tr>";\r
+        }\r
+        log.lastChild.innerHTML = html + "</tbody></table>";\r
+    };\r
+\r
+    var output = new Output();\r
+    add_start_callback(function (properties) {output.init(properties);});\r
+    add_result_callback(function (test) {output.show_status(tests);});\r
+    add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);});\r
+\r
+    /*\r
+     * Template code\r
+     *\r
+     * A template is just a javascript structure. An element is represented as:\r
+     *\r
+     * [tag_name, {attr_name:attr_value}, child1, child2]\r
+     *\r
+     * the children can either be strings (which act like text nodes), other templates or\r
+     * functions (see below)\r
+     *\r
+     * A text node is represented as\r
+     *\r
+     * ["{text}", value]\r
+     *\r
+     * String values have a simple substitution syntax; ${foo} represents a variable foo.\r
+     *\r
+     * It is possible to embed logic in templates by using a function in a place where a\r
+     * node would usually go. The function must either return part of a template or null.\r
+     *\r
+     * In cases where a set of nodes are required as output rather than a single node\r
+     * with children it is possible to just use a list\r
+     * [node1, node2, node3]\r
+     *\r
+     * Usage:\r
+     *\r
+     * render(template, substitutions) - take a template and an object mapping\r
+     * variable names to parameters and return either a DOM node or a list of DOM nodes\r
+     *\r
+     * substitute(template, substitutions) - take a template and variable mapping object,\r
+     * make the variable substitutions and return the substituted template\r
+     *\r
+     */\r
+\r
+    function is_single_node(template)\r
+    {\r
+        return typeof template[0] === "string";\r
+    }\r
+\r
+    function substitute(template, substitutions)\r
+    {\r
+        if (typeof template === "function") {\r
+            var replacement = template(substitutions);\r
+            if (replacement)\r
+            {\r
+                var rv = substitute(replacement, substitutions);\r
+                return rv;\r
+            }\r
+            else\r
+            {\r
+                return null;\r
+            }\r
+        }\r
+        else if (is_single_node(template))\r
+        {\r
+            return substitute_single(template, substitutions);\r
+        }\r
+        else\r
+        {\r
+            return filter(map(template, function(x) {\r
+                                  return substitute(x, substitutions);\r
+                              }), function(x) {return x !== null;});\r
+        }\r
+    }\r
+\r
+    function substitute_single(template, substitutions)\r
+    {\r
+        var substitution_re = /\${([^ }]*)}/g;\r
+\r
+        function do_substitution(input) {\r
+            var components = input.split(substitution_re);\r
+            var rv = [];\r
+            for (var i=0; i<components.length; i+=2)\r
+            {\r
+                rv.push(components[i]);\r
+                if (components[i+1])\r
+                {\r
+                    rv.push(String(substitutions[components[i+1]]));\r
+                }\r
+            }\r
+            return rv;\r
+        }\r
+\r
+        var rv = [];\r
+        rv.push(do_substitution(String(template[0])).join(""));\r
+\r
+        if (template[0] === "{text}") {\r
+            substitute_children(template.slice(1), rv);\r
+        } else {\r
+            substitute_attrs(template[1], rv);\r
+            substitute_children(template.slice(2), rv);\r
+        }\r
+\r
+        function substitute_attrs(attrs, rv)\r
+        {\r
+            rv[1] = {};\r
+            for (var name in template[1])\r
+            {\r
+                if (attrs.hasOwnProperty(name))\r
+                {\r
+                    var new_name = do_substitution(name).join("");\r
+                    var new_value = do_substitution(attrs[name]).join("");\r
+                    rv[1][new_name] = new_value;\r
+                };\r
+            }\r
+        }\r
+\r
+        function substitute_children(children, rv)\r
+        {\r
+            for (var i=0; i<children.length; i++)\r
+            {\r
+                if (children[i] instanceof Object) {\r
+                    var replacement = substitute(children[i], substitutions);\r
+                    if (replacement !== null)\r
+                    {\r
+                        if (is_single_node(replacement))\r
+                        {\r
+                            rv.push(replacement);\r
+                        }\r
+                        else\r
+                        {\r
+                            extend(rv, replacement);\r
+                        }\r
+                    }\r
+                }\r
+                else\r
+                {\r
+                    extend(rv, do_substitution(String(children[i])));\r
+                }\r
+            }\r
+            return rv;\r
+        }\r
+\r
+        return rv;\r
+    }\r
+\r
+ function make_dom_single(template, doc)\r
+ {\r
+     var output_document = doc || document;\r
+     if (template[0] === "{text}")\r
+     {\r
+         var element = output_document.createTextNode("");\r
+         for (var i=1; i<template.length; i++)\r
+         {\r
+             element.data += template[i];\r
+         }\r
+     }\r
+     else\r
+     {\r
+         var element = output_document.createElementNS(xhtml_ns, template[0]);\r
+         for (var name in template[1]) {\r
+             if (template[1].hasOwnProperty(name))\r
+             {\r
+                 element.setAttribute(name, template[1][name]);\r
+             }\r
+         }\r
+         for (var i=2; i<template.length; i++)\r
+         {\r
+             if (template[i] instanceof Object)\r
+             {\r
+                 var sub_element = make_dom(template[i]);\r
+                 element.appendChild(sub_element);\r
+             }\r
+             else\r
+             {\r
+                 var text_node = output_document.createTextNode(template[i]);\r
+                 element.appendChild(text_node);\r
+             }\r
+         }\r
+     }\r
+\r
+     return element;\r
+ }\r
+\r
+\r
+\r
+ function make_dom(template, substitutions, output_document)\r
+    {\r
+        if (is_single_node(template))\r
+        {\r
+            return make_dom_single(template, output_document);\r
+        }\r
+        else\r
+        {\r
+            return map(template, function(x) {\r
+                           return make_dom_single(x, output_document);\r
+                       });\r
+        }\r
+    }\r
+\r
+ function render(template, substitutions, output_document)\r
+    {\r
+        return make_dom(substitute(template, substitutions), output_document);\r
+    }\r
+\r
+    /*\r
+     * Utility funcions\r
+     */\r
+    function assert(expected_true, function_name, description, error, substitutions)\r
+    {\r
+        if (expected_true !== true)\r
+        {\r
+            throw new AssertionError(make_message(function_name, description,\r
+                                                  error, substitutions));\r
+        }\r
+    }\r
+\r
+    function AssertionError(message)\r
+    {\r
+        this.message = message;\r
+    }\r
+\r
+    function make_message(function_name, description, error, substitutions)\r
+    {\r
+        for (var p in substitutions) {\r
+            if (substitutions.hasOwnProperty(p)) {\r
+                substitutions[p] = format_value(substitutions[p]);\r
+            }\r
+        }\r
+        var node_form = substitute(["{text}", "${function_name}: ${description}" + error],\r
+                                   merge({function_name:function_name,\r
+                                          description:(description?description + " ":"")},\r
+                                          substitutions));\r
+        return node_form.slice(1).join("");\r
+    }\r
+\r
+    function filter(array, callable, thisObj) {\r
+        var rv = [];\r
+        for (var i=0; i<array.length; i++)\r
+        {\r
+            if (array.hasOwnProperty(i))\r
+            {\r
+                var pass = callable.call(thisObj, array[i], i, array);\r
+                if (pass) {\r
+                    rv.push(array[i]);\r
+                }\r
+            }\r
+        }\r
+        return rv;\r
+    }\r
+\r
+    function map(array, callable, thisObj)\r
+    {\r
+        var rv = [];\r
+        rv.length = array.length;\r
+        for (var i=0; i<array.length; i++)\r
+        {\r
+            if (array.hasOwnProperty(i))\r
+            {\r
+                rv[i] = callable.call(thisObj, array[i], i, array);\r
+            }\r
+        }\r
+        return rv;\r
+    }\r
+\r
+    function extend(array, items)\r
+    {\r
+        Array.prototype.push.apply(array, items);\r
+    }\r
+\r
+    function forEach (array, callback, thisObj)\r
+    {\r
+        for (var i=0; i<array.length; i++)\r
+        {\r
+            if (array.hasOwnProperty(i))\r
+            {\r
+                callback.call(thisObj, array[i], i, array);\r
+            }\r
+        }\r
+    }\r
+\r
+    function merge(a,b)\r
+    {\r
+        var rv = {};\r
+        var p;\r
+        for (p in a)\r
+        {\r
+            rv[p] = a[p];\r
+        }\r
+        for (p in b) {\r
+            rv[p] = b[p];\r
+        }\r
+        return rv;\r
+    }\r
+\r
+    function expose(object, name)\r
+    {\r
+        var components = name.split(".");\r
+        var target = window;\r
+        for (var i=0; i<components.length - 1; i++)\r
+        {\r
+            if (!(components[i] in target))\r
+            {\r
+                target[components[i]] = {};\r
+            }\r
+            target = target[components[i]];\r
+        }\r
+        target[components[components.length - 1]] = object;\r
+    }\r
+\r
+ function ancestor_windows() {\r
+     //Get the windows [self ... top] as an array\r
+     if ("result_cache" in ancestor_windows)\r
+     {\r
+         return ancestor_windows.result_cache;\r
+     }\r
+     var rv = [self];\r
+     var w = self;\r
+     while (w != w.parent)\r
+     {\r
+         w = w.parent;\r
+         rv.push(w);\r
+     }\r
+     ancestor_windows.result_cache = rv;\r
+     return rv;\r
+ }\r
+\r
+})();\r
+// vim: set expandtab shiftwidth=4 tabstop=4:
\ No newline at end of file
diff --git a/LayoutTests/resources/testharnessreport.js b/LayoutTests/resources/testharnessreport.js
new file mode 100644 (file)
index 0000000..5166889
--- /dev/null
@@ -0,0 +1,74 @@
+/*\r
+ * THIS FILE INTENTIONALLY LEFT BLANK\r
+ *\r
+ * More specifically, this file is intended for vendors to implement\r
+ * code needed to integrate testharness.js tests with their own test systems.\r
+ *\r
+ * Typically such integration will attach callbacks when each test is\r
+ * has run, using add_result_callback(callback(test)), or when the whole test file has\r
+ * completed, using add_completion_callback(callback(tests, harness_status)).\r
+ *\r
+ * For more documentation about the callback functions and the\r
+ * parameters they are called with see testharness.js\r
+ */\r
+\r
+// Setup for WebKit JavaScript tests\r
+if (self.layoutTestController)\r
+    layoutTestController.dumpAsText();\r
+\r
+// Function used to convert the test status code into\r
+// the corresponding string\r
+function convertResult(resultStatus){\r
+       if(resultStatus == 0)\r
+               return("PASS");\r
+       else if(resultStatus == 1)\r
+               return("FAIL");\r
+       else if(resultStatus == 2)\r
+               return("TIMEOUT");\r
+       else\r
+               return("NOTRUN");\r
+}\r
+\r
+/* Disable the default output of testharness.js.  The default output formats\r
+*  test results into an HTML table.  When that table is dumped as text, no\r
+*  spacing between cells is preserved, and it is therefore not readable. By\r
+*  setting output to false, the HTML table will not be created\r
+*/\r
+setup({"output":false});\r
+\r
+/*  Using a callback function, test results will be added to the page in a \r
+*   manner that allows dumpAsText to produce readable test results\r
+*/\r
+add_completion_callback(function (tests, harness_status){\r
+       \r
+       // Create element to hold results\r
+       var results = document.createElement("pre");\r
+       \r
+       // Declare result string\r
+       var resultStr = "\n";\r
+       \r
+       // Check harness_status.  If it is not 0, tests did not\r
+       // execute correctly, output the error code and message\r
+       if(harness_status.status != 0){\r
+               resultStr += "Harness Error. harness_status.status = " + \r
+                                        harness_status.status +\r
+                                        " , harness_status.message = " +\r
+                                        harness_status.message;\r
+       }\r
+       else {\r
+               // Iterate through tests array and build string that contains\r
+               // results for all tests\r
+               for(var i=0; i<tests.length; i++){                               \r
+                       resultStr += convertResult(tests[i].status) + " " + \r
+                                               ( (tests[i].name!=null) ? tests[i].name : "" ) + " " +\r
+                                               ( (tests[i].message!=null) ? tests[i].message : "" ) + \r
+                                               "\n";\r
+               }                       \r
+       }\r
+\r
+       // Set results element's innerHTML to the results string\r
+       results.innerHTML = resultStr;\r
+\r
+       // Add results element to document\r
+       document.body.appendChild(results);\r
+});
\ No newline at end of file