OLD | NEW |
1 /*global self*/ | 1 /*global self*/ |
2 /*jshint latedef: nofunc*/ | 2 /*jshint latedef: nofunc*/ |
3 /* | 3 /* |
4 Distributed under both the W3C Test Suite License [1] and the W3C | 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 | 5 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the |
6 policies and contribution forms [3]. | 6 policies and contribution forms [3]. |
7 | 7 |
8 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license | 8 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license |
9 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license | 9 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license |
10 [3] http://www.w3.org/2004/10/27-testcases | 10 [3] http://www.w3.org/2004/10/27-testcases |
11 */ | 11 */ |
12 | 12 |
13 /* | 13 /* Documentation is in docs/api.md */ |
14 * == Introduction == | |
15 * | |
16 * This file provides a framework for writing testcases. It is intended to | |
17 * provide a convenient API for making common assertions, and to work both | |
18 * for testing synchronous and asynchronous DOM features in a way that | |
19 * promotes clear, robust, tests. | |
20 * | |
21 * == Basic Usage == | |
22 * | |
23 * To use this file, import the script and the testharnessreport script into | |
24 * the test document: | |
25 | |
26 <!doctype html> | |
27 <title></title> | |
28 <script src="/resources/testharness.js"></script> | |
29 <script src="/resources/testharnessreport.js"></script> | |
30 | |
31 * Within each file one may define one or more tests. Each test is atomic | |
32 * in the sense that a single test has a single result (pass/fail/timeout). | |
33 * Within each test one may have a number of asserts. The test fails at the | |
34 * first failing assert, and the remainder of the test is (typically) not run. | |
35 * | |
36 * If the file containing the tests is a HTML file, a table containing the test | |
37 * results will be added to the document after all tests have run. By default th
is | |
38 * will be added to a div element with id=log if it exists, or a new div element | |
39 * appended to document.body if it does not. | |
40 * | |
41 * NOTE: By default tests must be created before the load event fires. For ways | |
42 * to create tests after the load event, see "Determining when all tests | |
43 * are complete", below | |
44 * | |
45 * == Synchronous Tests == | |
46 * | |
47 * To create a synchronous test use the test() function: | |
48 * | |
49 * test(test_function, name, properties) | |
50 * | |
51 * test_function is a function that contains the code to test. For example a | |
52 * trivial passing test would be: | |
53 * | |
54 * test(function() {assert_true(true)}, "assert_true with true") | |
55 * | |
56 * The function passed in is run in the test() call. | |
57 * | |
58 * properties is an object that overrides default test properties. The | |
59 * recognised properties are: | |
60 * timeout - the test timeout in ms | |
61 * | |
62 * e.g. | |
63 * test(test_function, "Sample test", {timeout:1000}) | |
64 * | |
65 * would run test_function with a timeout of 1s. | |
66 * | |
67 * Additionally, test-specific metadata can be passed in the properties. These | |
68 * are used when the individual test has different metadata from that stored | |
69 * in the <head>. | |
70 * The recognized metadata properties are: | |
71 * | |
72 * help - The url or an array of urls of the part(s) of the specification(s) | |
73 * being tested | |
74 * | |
75 * assert - A human readable description of what the test is attempting | |
76 * to prove | |
77 * | |
78 * author - Name and contact information for the author of the test in the | |
79 * format: "Name <email_addr>" or "Name http://contact/url" | |
80 * | |
81 * == Asynchronous Tests == | |
82 * | |
83 * Testing asynchronous features is somewhat more complex since the result of | |
84 * a test may depend on one or more events or other callbacks. The API provided | |
85 * for testing these features is indended to be rather low-level but hopefully | |
86 * applicable to many situations. | |
87 * | |
88 * To create a test, one starts by getting a Test object using async_test: | |
89 * | |
90 * async_test(name, properties) | |
91 * | |
92 * e.g. | |
93 * var t = async_test("Simple async test") | |
94 * | |
95 * Assertions can be added to the test by calling the step method of the test | |
96 * object with a function containing the test assertions: | |
97 * | |
98 * t.step(function() {assert_true(true)}); | |
99 * | |
100 * When all the steps are complete, the done() method must be called: | |
101 * | |
102 * t.done(); | |
103 * | |
104 * As a convenience, async_test can also takes a function as first argument. | |
105 * This function is called with the test object as both its `this` object and | |
106 * first argument. The above example can be rewritten as: | |
107 * | |
108 * async_test(function(t) { | |
109 * object.some_event = function() { | |
110 * t.step(function (){assert_true(true); t.done();}); | |
111 * }; | |
112 * }, "Simple async test"); | |
113 * | |
114 * which avoids cluttering the global scope with references to async | |
115 * tests instances. | |
116 * | |
117 * The properties argument is identical to that for test(). | |
118 * | |
119 * In many cases it is convenient to run a step in response to an event or a | |
120 * callback. A convenient method of doing this is through the step_func method | |
121 * which returns a function that, when called runs a test step. For example | |
122 * | |
123 * object.some_event = t.step_func(function(e) {assert_true(e.a)}); | |
124 * | |
125 * For asynchronous callbacks that should never execute, unreached_func can | |
126 * be used. For example: | |
127 * | |
128 * object.some_event = t.unreached_func("some_event should not fire"); | |
129 * | |
130 * == Single Page Tests == | |
131 * | |
132 * Sometimes, particularly when dealing with asynchronous behaviour, | |
133 * having exactly one test per page is desirable, and the overhead of | |
134 * wrapping everything in functions for isolation becomes | |
135 * burdensome. For these cases testharness.js support "single page | |
136 * tests". | |
137 * | |
138 * In order for a test to be interpreted as a "single page" test, the | |
139 * it must simply not call test() or async_test() anywhere on the page, and | |
140 * must call the done() function to indicate that the test is complete. All | |
141 * the assert_* functions are avaliable as normal, but are called without | |
142 * the normal step function wrapper. For example: | |
143 * | |
144 * <!doctype html> | |
145 * <title>Example single-page test</title> | |
146 * <script src="/resources/testharness.js"></script> | |
147 * <script src="/resources/testharnessreport.js"></script> | |
148 * <body> | |
149 * <script> | |
150 * assert_equals(document.body, document.getElementsByTagName("body")[0]) | |
151 * done() | |
152 * </script> | |
153 * | |
154 * The test title for sinple page tests is always taken from document.title. | |
155 * | |
156 * == Making assertions == | |
157 * | |
158 * Functions for making assertions start assert_ | |
159 * The best way to get a list is to look in this file for functions names | |
160 * matching that pattern. The general signature is | |
161 * | |
162 * assert_something(actual, expected, description) | |
163 * | |
164 * although not all assertions precisely match this pattern e.g. assert_true | |
165 * only takes actual and description as arguments. | |
166 * | |
167 * The description parameter is used to present more useful error messages when | |
168 * a test fails | |
169 * | |
170 * NOTE: All asserts must be located in a test() or a step of an async_test(). | |
171 * asserts outside these places won't be detected correctly by the harness | |
172 * and may cause a file to stop testing. | |
173 * | |
174 * == Cleanup == | |
175 * | |
176 * Occasionally tests may create state that will persist beyond the test itself. | |
177 * In order to ensure that tests are independent, such state should be cleaned | |
178 * up once the test has a result. This can be achieved by adding cleanup | |
179 * callbacks to the test. Such callbacks are registered using the add_cleanup | |
180 * function on the test object. All registered callbacks will be run as soon as | |
181 * the test result is known. For example | |
182 * test(function() { | |
183 * window.some_global = "example"; | |
184 * this.add_cleanup(function() {delete window.some_global}); | |
185 * assert_true(false); | |
186 * }); | |
187 * | |
188 * == Harness Timeout == | |
189 * | |
190 * The overall harness admits two timeout values "normal" (the | |
191 * default) and "long", used for tests which have an unusually long | |
192 * runtime. After the timeout is reached, the harness will stop | |
193 * waiting for further async tests to complete. By default the | |
194 * timeouts are set to 10s and 60s, respectively, but may be changed | |
195 * when the test is run on hardware with different performance | |
196 * characteristics to a common desktop computer. In order to opt-in | |
197 * to the longer test timeout, the test must specify a meta element: | |
198 * <meta name="timeout" content="long"> | |
199 * | |
200 * Occasionally tests may have a race between the harness timing out and | |
201 * a particular test failing; typically when the test waits for some event | |
202 * that never occurs. In this case it is possible to use test.force_timeout() | |
203 * in place of assert_unreached(), to immediately fail the test but with a | |
204 * status of "timeout". This should only be used as a last resort when it is | |
205 * not possible to make the test reliable in some other way. | |
206 * | |
207 * == Setup == | |
208 * | |
209 * Sometimes tests require non-trivial setup that may fail. For this purpose | |
210 * there is a setup() function, that may be called with one or two arguments. | |
211 * The two argument version is: | |
212 * | |
213 * setup(func, properties) | |
214 * | |
215 * The one argument versions may omit either argument. | |
216 * func is a function to be run synchronously. setup() becomes a no-op once | |
217 * any tests have returned results. Properties are global properties of the test | |
218 * harness. Currently recognised properties are: | |
219 * | |
220 * | |
221 * explicit_done - Wait for an explicit call to done() before declaring all | |
222 * tests complete (see below; implicitly true for single page | |
223 * tests) | |
224 * | |
225 * output_document - The document to which results should be logged. By default | |
226 * this is the current document but could be an ancestor | |
227 * document in some cases e.g. a SVG test loaded in an HTML | |
228 * wrapper | |
229 * | |
230 * explicit_timeout - disable file timeout; only stop waiting for results | |
231 * when the timeout() function is called (typically for | |
232 * use when integrating with some existing test framework | |
233 * that has its own timeout mechanism). | |
234 * | |
235 * allow_uncaught_exception - don't treat an uncaught exception as an error; | |
236 * needed when e.g. testing the window.onerror | |
237 * handler. | |
238 * | |
239 * timeout_multiplier - Multiplier to apply to per-test timeouts. | |
240 * | |
241 * == Determining when all tests are complete == | |
242 * | |
243 * By default the test harness will assume there are no more results to come | |
244 * when: | |
245 * 1) There are no Test objects that have been created but not completed | |
246 * 2) The load event on the document has fired | |
247 * | |
248 * This behaviour can be overridden by setting the explicit_done property to | |
249 * true in a call to setup(). If explicit_done is true, the test harness will | |
250 * not assume it is done until the global done() function is called. Once done() | |
251 * is called, the two conditions above apply like normal. | |
252 * | |
253 * == Generating tests == | |
254 * | |
255 * NOTE: this functionality may be removed | |
256 * | |
257 * There are scenarios in which is is desirable to create a large number of | |
258 * (synchronous) tests that are internally similar but vary in the parameters | |
259 * used. To make this easier, the generate_tests function allows a single | |
260 * function to be called with each set of parameters in a list: | |
261 * | |
262 * generate_tests(test_function, parameter_lists, properties) | |
263 * | |
264 * For example: | |
265 * | |
266 * generate_tests(assert_equals, [ | |
267 * ["Sum one and one", 1+1, 2], | |
268 * ["Sum one and zero", 1+0, 1] | |
269 * ]) | |
270 * | |
271 * Is equivalent to: | |
272 * | |
273 * test(function() {assert_equals(1+1, 2)}, "Sum one and one") | |
274 * test(function() {assert_equals(1+0, 1)}, "Sum one and zero") | |
275 * | |
276 * Note that the first item in each parameter list corresponds to the name of | |
277 * the test. | |
278 * | |
279 * The properties argument is identical to that for test(). This may be a | |
280 * single object (used for all generated tests) or an array. | |
281 * | |
282 * == Callback API == | |
283 * | |
284 * The framework provides callbacks corresponding to 3 events: | |
285 * | |
286 * start - happens when the first Test is created | |
287 * result - happens when a test result is recieved | |
288 * complete - happens when all results are recieved | |
289 * | |
290 * The page defining the tests may add callbacks for these events by calling | |
291 * the following methods: | |
292 * | |
293 * add_start_callback(callback) - callback called with no arguments | |
294 * add_result_callback(callback) - callback called with a test argument | |
295 * add_completion_callback(callback) - callback called with an array of tests | |
296 * and an status object | |
297 * | |
298 * tests have the following properties: | |
299 * status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and | |
300 * NOTRUN properties on the test object | |
301 * message: A message indicating the reason for failure. In the future this | |
302 * will always be a string | |
303 * | |
304 * The status object gives the overall status of the harness. It has the | |
305 * following properties: | |
306 * status: Can be compared to the OK, ERROR and TIMEOUT properties | |
307 * message: An error message set when the status is ERROR | |
308 * | |
309 * == External API == | |
310 * | |
311 * In order to collect the results of multiple pages containing tests, the test | |
312 * harness will, when loaded in a nested browsing context, attempt to call | |
313 * certain functions in each ancestor and opener browsing context: | |
314 * | |
315 * start - start_callback | |
316 * result - result_callback | |
317 * complete - completion_callback | |
318 * | |
319 * These are given the same arguments as the corresponding internal callbacks | |
320 * described above. | |
321 * | |
322 * == External API through cross-document messaging == | |
323 * | |
324 * Where supported, the test harness will also send messages using | |
325 * cross-document messaging to each ancestor and opener browsing context. Since | |
326 * it uses the wildcard keyword (*), cross-origin communication is enabled and | |
327 * script on different origins can collect the results. | |
328 * | |
329 * This API follows similar conventions as those described above only slightly | |
330 * modified to accommodate message event API. Each message is sent by the harnes
s | |
331 * is passed a single vanilla object, available as the `data` property of the | |
332 * event object. These objects are structures as follows: | |
333 * | |
334 * start - { type: "start" } | |
335 * result - { type: "result", test: Test } | |
336 * complete - { type: "complete", tests: [Test, ...], status: TestsStatus } | |
337 * | |
338 * == List of assertions == | |
339 * | |
340 * assert_true(actual, description) | |
341 * asserts that /actual/ is strictly true | |
342 * | |
343 * assert_false(actual, description) | |
344 * asserts that /actual/ is strictly false | |
345 * | |
346 * assert_equals(actual, expected, description) | |
347 * asserts that /actual/ is the same value as /expected/ | |
348 * | |
349 * assert_not_equals(actual, expected, description) | |
350 * asserts that /actual/ is a different value to /expected/. Yes, this means | |
351 * that "expected" is a misnomer | |
352 * | |
353 * assert_in_array(actual, expected, description) | |
354 * asserts that /expected/ is an Array, and /actual/ is equal to one of the | |
355 * members -- expected.indexOf(actual) != -1 | |
356 * | |
357 * assert_array_equals(actual, expected, description) | |
358 * asserts that /actual/ and /expected/ have the same length and the value of | |
359 * each indexed property in /actual/ is the strictly equal to the correspondin
g | |
360 * property value in /expected/ | |
361 * | |
362 * assert_approx_equals(actual, expected, epsilon, description) | |
363 * asserts that /actual/ is a number within +/- /epsilon/ of /expected/ | |
364 * | |
365 * assert_less_than(actual, expected, description) | |
366 * asserts that /actual/ is a number less than /expected/ | |
367 * | |
368 * assert_greater_than(actual, expected, description) | |
369 * asserts that /actual/ is a number greater than /expected/ | |
370 * | |
371 * assert_less_than_equal(actual, expected, description) | |
372 * asserts that /actual/ is a number less than or equal to /expected/ | |
373 * | |
374 * assert_greater_than_equal(actual, expected, description) | |
375 * asserts that /actual/ is a number greater than or equal to /expected/ | |
376 * | |
377 * assert_regexp_match(actual, expected, description) | |
378 * asserts that /actual/ matches the regexp /expected/ | |
379 * | |
380 * assert_class_string(object, class_name, description) | |
381 * asserts that the class string of /object/ as returned in | |
382 * Object.prototype.toString is equal to /class_name/. | |
383 * | |
384 * assert_own_property(object, property_name, description) | |
385 * assert that object has own property property_name | |
386 * | |
387 * assert_inherits(object, property_name, description) | |
388 * assert that object does not have an own property named property_name | |
389 * but that property_name is present in the prototype chain for object | |
390 * | |
391 * assert_idl_attribute(object, attribute_name, description) | |
392 * assert that an object that is an instance of some interface has the | |
393 * attribute attribute_name following the conditions specified by WebIDL | |
394 * | |
395 * assert_readonly(object, property_name, description) | |
396 * assert that property property_name on object is readonly | |
397 * | |
398 * assert_throws(code, func, description) | |
399 * code - the expected exception: | |
400 * o string: the thrown exception must be a DOMException with the given | |
401 * name, e.g., "TimeoutError" (for compatibility with existing | |
402 * tests, a constant is also supported, e.g., "TIMEOUT_ERR") | |
403 * o object: the thrown exception must have a property called "name" that | |
404 * matches code.name | |
405 * o null: allow any exception (in general, one of the options above | |
406 * should be used) | |
407 * func - a function that should throw | |
408 * | |
409 * assert_unreached(description) | |
410 * asserts if called. Used to ensure that some codepath is *not* taken e.g. | |
411 * an event does not fire. | |
412 * | |
413 * assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N) | |
414 * asserts that one assert_func(actual, expected_array_N, extra_arg1, ..., ext
ra_arg_N) | |
415 * is true for some expected_array_N in expected_array. This only works for as
sert_func | |
416 * with signature assert_func(actual, expected, args_1, ..., args_N). Note tha
t tests | |
417 * with multiple allowed pass conditions are bad practice unless the spec spec
ifically | |
418 * allows multiple behaviours. Test authors should not use this method simply
to hide | |
419 * UA bugs. | |
420 * | |
421 * assert_exists(object, property_name, description) | |
422 * *** deprecated *** | |
423 * asserts that object has an own property property_name | |
424 * | |
425 * assert_not_exists(object, property_name, description) | |
426 * *** deprecated *** | |
427 * assert that object does not have own property property_name | |
428 */ | |
429 | 14 |
430 (function () | 15 (function () |
431 { | 16 { |
432 var debug = false; | 17 var debug = false; |
433 // default timeout is 5 minutes, to let LayoutTests timeout first | 18 // default timeout is 10 seconds, test can override if needed |
434 var settings = { | 19 var settings = { |
435 output:true, | 20 output:true, |
436 harness_timeout:{ | 21 harness_timeout:{ |
437 "normal":300000, | 22 "normal":10000, |
438 "long":300000 | 23 "long":60000 |
439 }, | 24 }, |
440 test_timeout:null | 25 test_timeout:null |
441 }; | 26 }; |
442 | 27 |
443 var xhtml_ns = "http://www.w3.org/1999/xhtml"; | 28 var xhtml_ns = "http://www.w3.org/1999/xhtml"; |
444 | 29 |
445 // script_prefix is used by Output.prototype.show_results() to figure out | 30 /* |
446 // where to get testharness.css from. It's enclosed in an extra closure to | 31 * TestEnvironment is an abstraction for the environment in which the test |
447 // not pollute the library's namespace with variables like "src". | 32 * harness is used. Each implementation of a test environment has to provide |
448 var script_prefix = null; | 33 * the following interface: |
449 (function () | 34 * |
450 { | 35 * interface TestEnvironment { |
451 var scripts = document.getElementsByTagName("script"); | 36 * // Invoked after the global 'tests' object has been created and it's |
452 for (var i = 0; i < scripts.length; i++) { | 37 * // safe to call add_*_callback() to register event handlers. |
453 var src; | 38 * void on_tests_ready(); |
454 if (scripts[i].src) { | 39 * |
455 src = scripts[i].src; | 40 * // Invoked after setup() has been called to notify the test environment |
456 } else if (scripts[i].href) { | 41 * // of changes to the test harness properties. |
457 //SVG case | 42 * void on_new_harness_properties(object properties); |
458 src = scripts[i].href.baseVal; | 43 * |
| 44 * // Should return a new unique default test name. |
| 45 * DOMString next_default_test_name(); |
| 46 * |
| 47 * // Should return the test harness timeout duration in milliseconds. |
| 48 * float test_timeout(); |
| 49 * |
| 50 * // Should return the global scope object. |
| 51 * object global_scope(); |
| 52 * }; |
| 53 */ |
| 54 |
| 55 /* |
| 56 * A test environment with a DOM. The global object is 'window'. By default |
| 57 * test results are displayed in a table. Any parent windows receive |
| 58 * callbacks or messages via postMessage() when test events occur. See |
| 59 * apisample11.html and apisample12.html. |
| 60 */ |
| 61 function WindowTestEnvironment() { |
| 62 this.name_counter = 0; |
| 63 this.window_cache = null; |
| 64 this.output_handler = null; |
| 65 this.all_loaded = false; |
| 66 var this_obj = this; |
| 67 on_event(window, 'load', function() { |
| 68 this_obj.all_loaded = true; |
| 69 }); |
| 70 } |
| 71 |
| 72 WindowTestEnvironment.prototype._dispatch = function(selector, callback_args
, message_arg) { |
| 73 this._forEach_windows( |
| 74 function(w, same_origin) { |
| 75 if (same_origin) { |
| 76 try { |
| 77 var has_selector = selector in w; |
| 78 } catch(e) { |
| 79 // If document.domain was set at some point same_ori
gin can be |
| 80 // wrong and the above will fail. |
| 81 has_selector = false; |
| 82 } |
| 83 if (has_selector) { |
| 84 try { |
| 85 w[selector].apply(undefined, callback_args); |
| 86 } catch (e) { |
| 87 if (debug) { |
| 88 throw e; |
| 89 } |
| 90 } |
| 91 } |
| 92 } |
| 93 if (supports_post_message(w) && w !== self) { |
| 94 w.postMessage(message_arg, "*"); |
| 95 } |
| 96 }); |
| 97 }; |
| 98 |
| 99 WindowTestEnvironment.prototype._forEach_windows = function(callback) { |
| 100 // Iterate of the the windows [self ... top, opener]. The callback is pa
ssed |
| 101 // two objects, the first one is the windows object itself, the second o
ne |
| 102 // is a boolean indicating whether or not its on the same origin as the |
| 103 // current window. |
| 104 var cache = this.window_cache; |
| 105 if (!cache) { |
| 106 cache = [[self, true]]; |
| 107 var w = self; |
| 108 var i = 0; |
| 109 var so; |
| 110 var origins = location.ancestorOrigins; |
| 111 while (w != w.parent) { |
| 112 w = w.parent; |
| 113 // In WebKit, calls to parent windows' properties that aren't on
the same |
| 114 // origin cause an error message to be displayed in the error co
nsole but |
| 115 // don't throw an exception. This is a deviation from the curren
t HTML5 |
| 116 // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504 |
| 117 // The problem with WebKit's behavior is that it pollutes the er
ror console |
| 118 // with error messages that can't be caught. |
| 119 // |
| 120 // This issue can be mitigated by relying on the (for now) propr
ietary |
| 121 // `location.ancestorOrigins` property which returns an ordered
list of |
| 122 // the origins of enclosing windows. See: |
| 123 // http://trac.webkit.org/changeset/113945. |
| 124 if (origins) { |
| 125 so = (location.origin == origins[i]); |
| 126 } else { |
| 127 so = is_same_origin(w); |
| 128 } |
| 129 cache.push([w, so]); |
| 130 i++; |
459 } | 131 } |
460 | 132 w = window.opener; |
461 if (src && src.slice(src.length - "testharness.js".length) === "test
harness.js") { | 133 if (w) { |
462 script_prefix = src.slice(0, src.length - "testharness.js".lengt
h); | 134 // window.opener isn't included in the `location.ancestorOrigins
` prop. |
463 break; | 135 // We'll just have to deal with a simple check and an error msg
on WebKit |
| 136 // browsers in this case. |
| 137 cache.push([w, is_same_origin(w)]); |
464 } | 138 } |
465 } | 139 this.window_cache = cache; |
466 })(); | 140 } |
467 | 141 |
468 /* | 142 forEach(cache, |
469 * API functions | 143 function(a) { |
470 */ | 144 callback.apply(null, a); |
471 | 145 }); |
472 var name_counter = 0; | 146 }; |
473 function next_default_name() | 147 |
474 { | 148 WindowTestEnvironment.prototype.on_tests_ready = function() { |
| 149 var output = new Output(); |
| 150 this.output_handler = output; |
| 151 |
| 152 var this_obj = this; |
| 153 add_start_callback(function (properties) { |
| 154 this_obj.output_handler.init(properties); |
| 155 this_obj._dispatch("start_callback", [properties], |
| 156 { type: "start", properties: properties }); |
| 157 }); |
| 158 add_test_state_callback(function(test) { |
| 159 this_obj.output_handler.show_status(); |
| 160 this_obj._dispatch("test_state_callback", [test], |
| 161 { type: "test_state", test: test.structured_clone
() }); |
| 162 }); |
| 163 add_result_callback(function (test) { |
| 164 this_obj.output_handler.show_status(); |
| 165 this_obj._dispatch("result_callback", [test], |
| 166 { type: "result", test: test.structured_clone() }
); |
| 167 }); |
| 168 add_completion_callback(function (tests, harness_status) { |
| 169 this_obj.output_handler.show_results(tests, harness_status); |
| 170 var cloned_tests = map(tests, function(test) { return test.structure
d_clone(); }); |
| 171 this_obj._dispatch("completion_callback", [tests, harness_status], |
| 172 { type: "complete", tests: cloned_tests, |
| 173 status: harness_status.structured_clone() }); |
| 174 }); |
| 175 }; |
| 176 |
| 177 WindowTestEnvironment.prototype.next_default_test_name = function() { |
475 //Don't use document.title to work around an Opera bug in XHTML document
s | 178 //Don't use document.title to work around an Opera bug in XHTML document
s |
476 var title = document.getElementsByTagName("title")[0]; | 179 var title = document.getElementsByTagName("title")[0]; |
477 var prefix = (title && title.firstChild && title.firstChild.data) || "Un
titled"; | 180 var prefix = (title && title.firstChild && title.firstChild.data) || "Un
titled"; |
478 var suffix = name_counter > 0 ? " " + name_counter : ""; | 181 var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; |
479 name_counter++; | 182 this.name_counter++; |
480 return prefix + suffix; | 183 return prefix + suffix; |
481 } | 184 }; |
| 185 |
| 186 WindowTestEnvironment.prototype.on_new_harness_properties = function(propert
ies) { |
| 187 this.output_handler.setup(properties); |
| 188 }; |
| 189 |
| 190 WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback)
{ |
| 191 on_event(window, 'load', callback); |
| 192 }; |
| 193 |
| 194 WindowTestEnvironment.prototype.test_timeout = function() { |
| 195 var metas = document.getElementsByTagName("meta"); |
| 196 for (var i = 0; i < metas.length; i++) { |
| 197 if (metas[i].name == "timeout") { |
| 198 if (metas[i].content == "long") { |
| 199 return settings.harness_timeout.long; |
| 200 } |
| 201 break; |
| 202 } |
| 203 } |
| 204 return settings.harness_timeout.normal; |
| 205 }; |
| 206 |
| 207 WindowTestEnvironment.prototype.global_scope = function() { |
| 208 return window; |
| 209 }; |
| 210 |
| 211 /* |
| 212 * Base TestEnvironment implementation for a generic web worker. |
| 213 * |
| 214 * Workers accumulate test results. One or more clients can connect and |
| 215 * retrieve results from a worker at any time. |
| 216 * |
| 217 * WorkerTestEnvironment supports communicating with a client via a |
| 218 * MessagePort. The mechanism for determining the appropriate MessagePort |
| 219 * for communicating with a client depends on the type of worker and is |
| 220 * implemented by the various specializations of WorkerTestEnvironment |
| 221 * below. |
| 222 * |
| 223 * A client document using testharness can use fetch_tests_from_worker() to |
| 224 * retrieve results from a worker. See apisample16.html. |
| 225 */ |
| 226 function WorkerTestEnvironment() { |
| 227 this.name_counter = 0; |
| 228 this.all_loaded = true; |
| 229 this.message_list = []; |
| 230 this.message_ports = []; |
| 231 } |
| 232 |
| 233 WorkerTestEnvironment.prototype._dispatch = function(message) { |
| 234 this.message_list.push(message); |
| 235 for (var i = 0; i < this.message_ports.length; ++i) |
| 236 { |
| 237 this.message_ports[i].postMessage(message); |
| 238 } |
| 239 }; |
| 240 |
| 241 // The only requirement is that port has a postMessage() method. It doesn't |
| 242 // have to be an instance of a MessagePort, and often isn't. |
| 243 WorkerTestEnvironment.prototype._add_message_port = function(port) { |
| 244 this.message_ports.push(port); |
| 245 for (var i = 0; i < this.message_list.length; ++i) |
| 246 { |
| 247 port.postMessage(this.message_list[i]); |
| 248 } |
| 249 }; |
| 250 |
| 251 WorkerTestEnvironment.prototype.next_default_test_name = function() { |
| 252 var suffix = this.name_counter > 0 ? " " + this.name_counter : ""; |
| 253 this.name_counter++; |
| 254 return "Untitled" + suffix; |
| 255 }; |
| 256 |
| 257 WorkerTestEnvironment.prototype.on_new_harness_properties = function() {}; |
| 258 |
| 259 WorkerTestEnvironment.prototype.on_tests_ready = function() { |
| 260 var this_obj = this; |
| 261 add_start_callback( |
| 262 function(properties) { |
| 263 this_obj._dispatch({ |
| 264 type: "start", |
| 265 properties: properties, |
| 266 }); |
| 267 }); |
| 268 add_test_state_callback( |
| 269 function(test) { |
| 270 this_obj._dispatch({ |
| 271 type: "test_state", |
| 272 test: test.structured_clone() |
| 273 }); |
| 274 }); |
| 275 add_result_callback( |
| 276 function(test) { |
| 277 this_obj._dispatch({ |
| 278 type: "result", |
| 279 test: test.structured_clone() |
| 280 }); |
| 281 }); |
| 282 add_completion_callback( |
| 283 function(tests, harness_status) { |
| 284 this_obj._dispatch({ |
| 285 type: "complete", |
| 286 tests: map(tests, |
| 287 function(test) { |
| 288 return test.structured_clone(); |
| 289 }), |
| 290 status: harness_status.structured_clone() |
| 291 }); |
| 292 }); |
| 293 }; |
| 294 |
| 295 WorkerTestEnvironment.prototype.add_on_loaded_callback = function() {}; |
| 296 |
| 297 WorkerTestEnvironment.prototype.test_timeout = function() { |
| 298 // Tests running in a worker don't have a default timeout. I.e. all |
| 299 // worker tests behave as if settings.explicit_timeout is true. |
| 300 return null; |
| 301 }; |
| 302 |
| 303 WorkerTestEnvironment.prototype.global_scope = function() { |
| 304 return self; |
| 305 }; |
| 306 |
| 307 /* |
| 308 * Dedicated web workers. |
| 309 * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobal
scope |
| 310 * |
| 311 * This class is used as the test_environment when testharness is running |
| 312 * inside a dedicated worker. |
| 313 */ |
| 314 function DedicatedWorkerTestEnvironment() { |
| 315 WorkerTestEnvironment.call(this); |
| 316 // self is an instance of DedicatedWorkerGlobalScope which exposes |
| 317 // a postMessage() method for communicating via the message channel |
| 318 // established when the worker is created. |
| 319 this._add_message_port(self); |
| 320 } |
| 321 DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironme
nt.prototype); |
| 322 |
| 323 DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() { |
| 324 WorkerTestEnvironment.prototype.on_tests_ready.call(this); |
| 325 // In the absence of an onload notification, we a require dedicated |
| 326 // workers to explicitly signal when the tests are done. |
| 327 tests.wait_for_finish = true; |
| 328 }; |
| 329 |
| 330 /* |
| 331 * Shared web workers. |
| 332 * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalsco
pe |
| 333 * |
| 334 * This class is used as the test_environment when testharness is running |
| 335 * inside a shared web worker. |
| 336 */ |
| 337 function SharedWorkerTestEnvironment() { |
| 338 WorkerTestEnvironment.call(this); |
| 339 var this_obj = this; |
| 340 // Shared workers receive message ports via the 'onconnect' event for |
| 341 // each connection. |
| 342 self.addEventListener("connect", |
| 343 function(message_event) { |
| 344 this_obj._add_message_port(message_event.source); |
| 345 }); |
| 346 } |
| 347 SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.
prototype); |
| 348 |
| 349 SharedWorkerTestEnvironment.prototype.on_tests_ready = function() { |
| 350 WorkerTestEnvironment.prototype.on_tests_ready.call(this); |
| 351 // In the absence of an onload notification, we a require shared |
| 352 // workers to explicitly signal when the tests are done. |
| 353 tests.wait_for_finish = true; |
| 354 }; |
| 355 |
| 356 /* |
| 357 * Service workers. |
| 358 * http://www.w3.org/TR/service-workers/ |
| 359 * |
| 360 * This class is used as the test_environment when testharness is running |
| 361 * inside a service worker. |
| 362 */ |
| 363 function ServiceWorkerTestEnvironment() { |
| 364 WorkerTestEnvironment.call(this); |
| 365 this.all_loaded = false; |
| 366 this.on_loaded_callback = null; |
| 367 var this_obj = this; |
| 368 self.addEventListener("message", |
| 369 function(event) { |
| 370 if (event.data.type && event.data.type === "connect") { |
| 371 this_obj._add_message_port(event.ports[0]); |
| 372 event.ports[0].start(); |
| 373 } |
| 374 }); |
| 375 |
| 376 // The oninstall event is received after the service worker script and |
| 377 // all imported scripts have been fetched and executed. It's the |
| 378 // equivalent of an onload event for a document. All tests should have |
| 379 // been added by the time this event is received, thus it's not |
| 380 // necessary to wait until the onactivate event. |
| 381 on_event(self, "install", |
| 382 function(event) { |
| 383 this_obj.all_loaded = true; |
| 384 if (this_obj.on_loaded_callback) { |
| 385 this_obj.on_loaded_callback(); |
| 386 } |
| 387 }); |
| 388 } |
| 389 ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment
.prototype); |
| 390 |
| 391 ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(cal
lback) { |
| 392 if (this.all_loaded) { |
| 393 callback(); |
| 394 } else { |
| 395 this.on_loaded_callback = callback; |
| 396 } |
| 397 }; |
| 398 |
| 399 function create_test_environment() { |
| 400 if ('document' in self) { |
| 401 return new WindowTestEnvironment(); |
| 402 } |
| 403 if ('DedicatedWorkerGlobalScope' in self && |
| 404 self instanceof DedicatedWorkerGlobalScope) { |
| 405 return new DedicatedWorkerTestEnvironment(); |
| 406 } |
| 407 if ('SharedWorkerGlobalScope' in self && |
| 408 self instanceof SharedWorkerGlobalScope) { |
| 409 return new SharedWorkerTestEnvironment(); |
| 410 } |
| 411 if ('ServiceWorkerGlobalScope' in self && |
| 412 self instanceof ServiceWorkerGlobalScope) { |
| 413 return new ServiceWorkerTestEnvironment(); |
| 414 } |
| 415 throw new Error("Unsupported test environment"); |
| 416 } |
| 417 |
| 418 var test_environment = create_test_environment(); |
| 419 |
| 420 function is_shared_worker(worker) { |
| 421 return 'SharedWorker' in self && worker instanceof SharedWorker; |
| 422 } |
| 423 |
| 424 function is_service_worker(worker) { |
| 425 return 'ServiceWorker' in self && worker instanceof ServiceWorker; |
| 426 } |
| 427 |
| 428 /* |
| 429 * API functions |
| 430 */ |
482 | 431 |
483 function test(func, name, properties) | 432 function test(func, name, properties) |
484 { | 433 { |
485 var test_name = name ? name : next_default_name(); | 434 var test_name = name ? name : test_environment.next_default_test_name(); |
486 properties = properties ? properties : {}; | 435 properties = properties ? properties : {}; |
487 var test_obj = new Test(test_name, properties); | 436 var test_obj = new Test(test_name, properties); |
488 test_obj.step(func, test_obj, test_obj); | 437 test_obj.step(func, test_obj, test_obj); |
489 if (test_obj.phase === test_obj.phases.STARTED) { | 438 if (test_obj.phase === test_obj.phases.STARTED) { |
490 test_obj.done(); | 439 test_obj.done(); |
491 } | 440 } |
492 } | 441 } |
493 | 442 |
494 function async_test(func, name, properties) | 443 function async_test(func, name, properties) |
495 { | 444 { |
496 if (typeof func !== "function") { | 445 if (typeof func !== "function") { |
497 properties = name; | 446 properties = name; |
498 name = func; | 447 name = func; |
499 func = null; | 448 func = null; |
500 } | 449 } |
501 var test_name = name ? name : next_default_name(); | 450 var test_name = name ? name : test_environment.next_default_test_name(); |
502 properties = properties ? properties : {}; | 451 properties = properties ? properties : {}; |
503 var test_obj = new Test(test_name, properties); | 452 var test_obj = new Test(test_name, properties); |
504 if (func) { | 453 if (func) { |
505 test_obj.step(func, test_obj, test_obj); | 454 test_obj.step(func, test_obj, test_obj); |
506 } | 455 } |
507 return test_obj; | 456 return test_obj; |
508 } | 457 } |
509 | 458 |
| 459 function promise_test(func, name, properties) { |
| 460 var test = async_test(name, properties); |
| 461 Promise.resolve(test.step(func, test, test)) |
| 462 .then( |
| 463 function() { |
| 464 test.done(); |
| 465 }) |
| 466 .catch(test.step_func( |
| 467 function(value) { |
| 468 if (value instanceof AssertionError) { |
| 469 throw value; |
| 470 } |
| 471 assert(false, "promise_test", null, |
| 472 "Unhandled rejection with value: ${value}", {value:va
lue}); |
| 473 })); |
| 474 } |
| 475 |
| 476 function promise_rejects(test, expected, promise) { |
| 477 return promise.then(test.unreached_func("Should have rejected.")).catch(
function(e) { |
| 478 assert_throws(expected, function() { throw e }); |
| 479 }); |
| 480 } |
| 481 |
| 482 /** |
| 483 * This constructor helper allows DOM events to be handled using Promises, |
| 484 * which can make it a lot easier to test a very specific series of events, |
| 485 * including ensuring that unexpected events are not fired at any point. |
| 486 */ |
| 487 function EventWatcher(test, watchedNode, eventTypes) |
| 488 { |
| 489 if (typeof eventTypes == 'string') { |
| 490 eventTypes = [eventTypes]; |
| 491 } |
| 492 |
| 493 var waitingFor = null; |
| 494 |
| 495 var eventHandler = test.step_func(function(evt) { |
| 496 assert_true(!!waitingFor, |
| 497 'Not expecting event, but got ' + evt.type + ' event'); |
| 498 assert_equals(evt.type, waitingFor.types[0], |
| 499 'Expected ' + waitingFor.types[0] + ' event, but got '
+ |
| 500 evt.type + ' event instead'); |
| 501 if (waitingFor.types.length > 1) { |
| 502 // Pop first event from array |
| 503 waitingFor.types.shift(); |
| 504 return; |
| 505 } |
| 506 // We need to null out waitingFor before calling the resolve functio
n |
| 507 // since the Promise's resolve handlers may call wait_for() which wi
ll |
| 508 // need to set waitingFor. |
| 509 var resolveFunc = waitingFor.resolve; |
| 510 waitingFor = null; |
| 511 resolveFunc(evt); |
| 512 }); |
| 513 |
| 514 for (var i = 0; i < eventTypes.length; i++) { |
| 515 watchedNode.addEventListener(eventTypes[i], eventHandler); |
| 516 } |
| 517 |
| 518 /** |
| 519 * Returns a Promise that will resolve after the specified event or |
| 520 * series of events has occured. |
| 521 */ |
| 522 this.wait_for = function(types) { |
| 523 if (waitingFor) { |
| 524 return Promise.reject('Already waiting for an event or events'); |
| 525 } |
| 526 if (typeof types == 'string') { |
| 527 types = [types]; |
| 528 } |
| 529 return new Promise(function(resolve, reject) { |
| 530 waitingFor = { |
| 531 types: types, |
| 532 resolve: resolve, |
| 533 reject: reject |
| 534 }; |
| 535 }); |
| 536 }; |
| 537 |
| 538 function stop_watching() { |
| 539 for (var i = 0; i < eventTypes.length; i++) { |
| 540 watchedNode.removeEventListener(eventTypes[i], eventHandler); |
| 541 } |
| 542 }; |
| 543 |
| 544 test.add_cleanup(stop_watching); |
| 545 |
| 546 return this; |
| 547 } |
| 548 expose(EventWatcher, 'EventWatcher'); |
| 549 |
510 function setup(func_or_properties, maybe_properties) | 550 function setup(func_or_properties, maybe_properties) |
511 { | 551 { |
512 var func = null; | 552 var func = null; |
513 var properties = {}; | 553 var properties = {}; |
514 if (arguments.length === 2) { | 554 if (arguments.length === 2) { |
515 func = func_or_properties; | 555 func = func_or_properties; |
516 properties = maybe_properties; | 556 properties = maybe_properties; |
517 } else if (func_or_properties instanceof Function) { | 557 } else if (func_or_properties instanceof Function) { |
518 func = func_or_properties; | 558 func = func_or_properties; |
519 } else { | 559 } else { |
520 properties = func_or_properties; | 560 properties = func_or_properties; |
521 } | 561 } |
522 tests.setup(func, properties); | 562 tests.setup(func, properties); |
523 output.setup(properties); | 563 test_environment.on_new_harness_properties(properties); |
524 } | 564 } |
525 | 565 |
526 function done() { | 566 function done() { |
527 if (tests.tests.length === 0) { | 567 if (tests.tests.length === 0) { |
528 tests.set_file_is_test(); | 568 tests.set_file_is_test(); |
529 } | 569 } |
530 if (tests.file_is_test) { | 570 if (tests.file_is_test) { |
531 tests.tests[0].done(); | 571 tests.tests[0].done(); |
532 } | 572 } |
533 tests.end_wait(); | 573 tests.end_wait(); |
(...skipping 12 matching lines...) Expand all Loading... |
546 }); | 586 }); |
547 } | 587 } |
548 | 588 |
549 function on_event(object, event, callback) | 589 function on_event(object, event, callback) |
550 { | 590 { |
551 object.addEventListener(event, callback, false); | 591 object.addEventListener(event, callback, false); |
552 } | 592 } |
553 | 593 |
554 expose(test, 'test'); | 594 expose(test, 'test'); |
555 expose(async_test, 'async_test'); | 595 expose(async_test, 'async_test'); |
| 596 expose(promise_test, 'promise_test'); |
| 597 expose(promise_rejects, 'promise_rejects'); |
556 expose(generate_tests, 'generate_tests'); | 598 expose(generate_tests, 'generate_tests'); |
557 expose(setup, 'setup'); | 599 expose(setup, 'setup'); |
558 expose(done, 'done'); | 600 expose(done, 'done'); |
559 expose(on_event, 'on_event'); | 601 expose(on_event, 'on_event'); |
560 | 602 |
561 /* | 603 /* |
562 * Return a string truncated to the given length, with ... added at the end | 604 * Return a string truncated to the given length, with ... added at the end |
563 * if it was longer. | 605 * if it was longer. |
564 */ | 606 */ |
565 function truncate(s, len) | 607 function truncate(s, len) |
(...skipping 243 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
809 function assert_array_equals(actual, expected, description) | 851 function assert_array_equals(actual, expected, description) |
810 { | 852 { |
811 assert(actual.length === expected.length, | 853 assert(actual.length === expected.length, |
812 "assert_array_equals", description, | 854 "assert_array_equals", description, |
813 "lengths differ, expected ${expected} got ${actual}", | 855 "lengths differ, expected ${expected} got ${actual}", |
814 {expected:expected.length, actual:actual.length}); | 856 {expected:expected.length, actual:actual.length}); |
815 | 857 |
816 for (var i = 0; i < actual.length; i++) { | 858 for (var i = 0; i < actual.length; i++) { |
817 assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), | 859 assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i), |
818 "assert_array_equals", description, | 860 "assert_array_equals", description, |
819 "property ${i}, property expected to be $expected but was $ac
tual", | 861 "property ${i}, property expected to be ${expected} but was $
{actual}", |
820 {i:i, expected:expected.hasOwnProperty(i) ? "present" : "miss
ing", | 862 {i:i, expected:expected.hasOwnProperty(i) ? "present" : "miss
ing", |
821 actual:actual.hasOwnProperty(i) ? "present" : "missing"}); | 863 actual:actual.hasOwnProperty(i) ? "present" : "missing"}); |
822 assert(same_value(expected[i], actual[i]), | 864 assert(same_value(expected[i], actual[i]), |
823 "assert_array_equals", description, | 865 "assert_array_equals", description, |
824 "property ${i}, expected ${expected} but got ${actual}", | 866 "property ${i}, expected ${expected} but got ${actual}", |
825 {i:i, expected:expected[i], actual:actual[i]}); | 867 {i:i, expected:expected[i], actual:actual[i]}); |
826 } | 868 } |
827 } | 869 } |
828 expose(assert_array_equals, "assert_array_equals"); | 870 expose(assert_array_equals, "assert_array_equals"); |
829 | 871 |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
871 "expected a number but got a ${type_actual}", | 913 "expected a number but got a ${type_actual}", |
872 {type_actual:typeof actual}); | 914 {type_actual:typeof actual}); |
873 | 915 |
874 assert(actual > expected, | 916 assert(actual > expected, |
875 "assert_greater_than", description, | 917 "assert_greater_than", description, |
876 "expected a number greater than ${expected} but got ${actual}", | 918 "expected a number greater than ${expected} but got ${actual}", |
877 {expected:expected, actual:actual}); | 919 {expected:expected, actual:actual}); |
878 } | 920 } |
879 expose(assert_greater_than, "assert_greater_than"); | 921 expose(assert_greater_than, "assert_greater_than"); |
880 | 922 |
| 923 function assert_between_exclusive(actual, lower, upper, description) |
| 924 { |
| 925 /* |
| 926 * Test if a primitive number is between two others |
| 927 */ |
| 928 assert(typeof actual === "number", |
| 929 "assert_between_exclusive", description, |
| 930 "expected a number but got a ${type_actual}", |
| 931 {type_actual:typeof actual}); |
| 932 |
| 933 assert(actual > lower && actual < upper, |
| 934 "assert_between_exclusive", description, |
| 935 "expected a number greater than ${lower} " + |
| 936 "and less than ${upper} but got ${actual}", |
| 937 {lower:lower, upper:upper, actual:actual}); |
| 938 } |
| 939 expose(assert_between_exclusive, "assert_between_exclusive"); |
| 940 |
881 function assert_less_than_equal(actual, expected, description) | 941 function assert_less_than_equal(actual, expected, description) |
882 { | 942 { |
883 /* | 943 /* |
884 * Test if a primitive number is less than or equal to another | 944 * Test if a primitive number is less than or equal to another |
885 */ | 945 */ |
886 assert(typeof actual === "number", | 946 assert(typeof actual === "number", |
887 "assert_less_than_equal", description, | 947 "assert_less_than_equal", description, |
888 "expected a number but got a ${type_actual}", | 948 "expected a number but got a ${type_actual}", |
889 {type_actual:typeof actual}); | 949 {type_actual:typeof actual}); |
890 | 950 |
891 assert(actual <= expected, | 951 assert(actual <= expected, |
892 "assert_less_than", description, | 952 "assert_less_than_equal", description, |
893 "expected a number less than or equal to ${expected} but got ${ac
tual}", | 953 "expected a number less than or equal to ${expected} but got ${ac
tual}", |
894 {expected:expected, actual:actual}); | 954 {expected:expected, actual:actual}); |
895 } | 955 } |
896 expose(assert_less_than_equal, "assert_less_than_equal"); | 956 expose(assert_less_than_equal, "assert_less_than_equal"); |
897 | 957 |
898 function assert_greater_than_equal(actual, expected, description) | 958 function assert_greater_than_equal(actual, expected, description) |
899 { | 959 { |
900 /* | 960 /* |
901 * Test if a primitive number is greater than or equal to another | 961 * Test if a primitive number is greater than or equal to another |
902 */ | 962 */ |
903 assert(typeof actual === "number", | 963 assert(typeof actual === "number", |
904 "assert_greater_than_equal", description, | 964 "assert_greater_than_equal", description, |
905 "expected a number but got a ${type_actual}", | 965 "expected a number but got a ${type_actual}", |
906 {type_actual:typeof actual}); | 966 {type_actual:typeof actual}); |
907 | 967 |
908 assert(actual >= expected, | 968 assert(actual >= expected, |
909 "assert_greater_than_equal", description, | 969 "assert_greater_than_equal", description, |
910 "expected a number greater than or equal to ${expected} but got $
{actual}", | 970 "expected a number greater than or equal to ${expected} but got $
{actual}", |
911 {expected:expected, actual:actual}); | 971 {expected:expected, actual:actual}); |
912 } | 972 } |
913 expose(assert_greater_than_equal, "assert_greater_than_equal"); | 973 expose(assert_greater_than_equal, "assert_greater_than_equal"); |
914 | 974 |
| 975 function assert_between_inclusive(actual, lower, upper, description) |
| 976 { |
| 977 /* |
| 978 * Test if a primitive number is between to two others or equal to eithe
r of them |
| 979 */ |
| 980 assert(typeof actual === "number", |
| 981 "assert_between_inclusive", description, |
| 982 "expected a number but got a ${type_actual}", |
| 983 {type_actual:typeof actual}); |
| 984 |
| 985 assert(actual >= lower && actual <= upper, |
| 986 "assert_between_inclusive", description, |
| 987 "expected a number greater than or equal to ${lower} " + |
| 988 "and less than or equal to ${upper} but got ${actual}", |
| 989 {lower:lower, upper:upper, actual:actual}); |
| 990 } |
| 991 expose(assert_between_inclusive, "assert_between_inclusive"); |
| 992 |
915 function assert_regexp_match(actual, expected, description) { | 993 function assert_regexp_match(actual, expected, description) { |
916 /* | 994 /* |
917 * Test if a string (actual) matches a regexp (expected) | 995 * Test if a string (actual) matches a regexp (expected) |
918 */ | 996 */ |
919 assert(expected.test(actual), | 997 assert(expected.test(actual), |
920 "assert_regexp_match", description, | 998 "assert_regexp_match", description, |
921 "expected ${expected} but got ${actual}", | 999 "expected ${expected} but got ${actual}", |
922 {expected:expected, actual:actual}); | 1000 {expected:expected, actual:actual}); |
923 } | 1001 } |
924 expose(assert_regexp_match, "assert_regexp_match"); | 1002 expose(assert_regexp_match, "assert_regexp_match"); |
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1056 TypeMismatchError: 17, | 1134 TypeMismatchError: 17, |
1057 SecurityError: 18, | 1135 SecurityError: 18, |
1058 NetworkError: 19, | 1136 NetworkError: 19, |
1059 AbortError: 20, | 1137 AbortError: 20, |
1060 URLMismatchError: 21, | 1138 URLMismatchError: 21, |
1061 QuotaExceededError: 22, | 1139 QuotaExceededError: 22, |
1062 TimeoutError: 23, | 1140 TimeoutError: 23, |
1063 InvalidNodeTypeError: 24, | 1141 InvalidNodeTypeError: 24, |
1064 DataCloneError: 25, | 1142 DataCloneError: 25, |
1065 | 1143 |
| 1144 EncodingError: 0, |
| 1145 NotReadableError: 0, |
1066 UnknownError: 0, | 1146 UnknownError: 0, |
1067 ConstraintError: 0, | 1147 ConstraintError: 0, |
1068 DataError: 0, | 1148 DataError: 0, |
1069 TransactionInactiveError: 0, | 1149 TransactionInactiveError: 0, |
1070 ReadOnlyError: 0, | 1150 ReadOnlyError: 0, |
1071 VersionError: 0 | 1151 VersionError: 0, |
| 1152 OperationError: 0, |
1072 }; | 1153 }; |
1073 | 1154 |
1074 if (!(name in name_code_map)) { | 1155 if (!(name in name_code_map)) { |
1075 throw new AssertionError('Test bug: unrecognized DOMException co
de "' + code + '" passed to assert_throws()'); | 1156 throw new AssertionError('Test bug: unrecognized DOMException co
de "' + code + '" passed to assert_throws()'); |
1076 } | 1157 } |
1077 | 1158 |
1078 var required_props = { code: name_code_map[name] }; | 1159 var required_props = { code: name_code_map[name] }; |
1079 | 1160 |
1080 if (required_props.code === 0 || | 1161 if (required_props.code === 0 || |
1081 ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DO
MException")) { | 1162 (typeof e == "object" && |
| 1163 "name" in e && |
| 1164 e.name !== e.name.toUpperCase() && |
| 1165 e.name !== "DOMException")) { |
1082 // New style exception: also test the name property. | 1166 // New style exception: also test the name property. |
1083 required_props.name = name; | 1167 required_props.name = name; |
1084 } | 1168 } |
1085 | 1169 |
1086 //We'd like to test that e instanceof the appropriate interface, | 1170 //We'd like to test that e instanceof the appropriate interface, |
1087 //but we can't, because we don't know what window it was created | 1171 //but we can't, because we don't know what window it was created |
1088 //in. It might be an instanceof the appropriate interface on some | 1172 //in. It might be an instanceof the appropriate interface on some |
1089 //unknown other window. TODO: Work around this somehow? | 1173 //unknown other window. TODO: Work around this somehow? |
1090 | 1174 |
1091 assert(typeof e == "object", | 1175 assert(typeof e == "object", |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1130 } | 1214 } |
1131 expose(assert_any, "assert_any"); | 1215 expose(assert_any, "assert_any"); |
1132 | 1216 |
1133 function Test(name, properties) | 1217 function Test(name, properties) |
1134 { | 1218 { |
1135 if (tests.file_is_test && tests.tests.length) { | 1219 if (tests.file_is_test && tests.tests.length) { |
1136 throw new Error("Tried to create a test with file_is_test"); | 1220 throw new Error("Tried to create a test with file_is_test"); |
1137 } | 1221 } |
1138 this.name = name; | 1222 this.name = name; |
1139 | 1223 |
1140 this.phases = { | |
1141 INITIAL:0, | |
1142 STARTED:1, | |
1143 HAS_RESULT:2, | |
1144 COMPLETE:3 | |
1145 }; | |
1146 this.phase = this.phases.INITIAL; | 1224 this.phase = this.phases.INITIAL; |
1147 | 1225 |
1148 this.status = this.NOTRUN; | 1226 this.status = this.NOTRUN; |
1149 this.timeout_id = null; | 1227 this.timeout_id = null; |
| 1228 this.index = null; |
1150 | 1229 |
1151 this.properties = properties; | 1230 this.properties = properties; |
1152 var timeout = properties.timeout ? properties.timeout : settings.test_ti
meout; | 1231 var timeout = properties.timeout ? properties.timeout : settings.test_ti
meout; |
1153 if (timeout != null) { | 1232 if (timeout !== null) { |
1154 this.timeout_length = timeout * tests.timeout_multiplier; | 1233 this.timeout_length = timeout * tests.timeout_multiplier; |
1155 } else { | 1234 } else { |
1156 this.timeout_length = null; | 1235 this.timeout_length = null; |
1157 } | 1236 } |
1158 | 1237 |
1159 this.message = null; | 1238 this.message = null; |
| 1239 this.stack = null; |
1160 | 1240 |
1161 this.steps = []; | 1241 this.steps = []; |
1162 | 1242 |
1163 this.cleanup_callbacks = []; | 1243 this.cleanup_callbacks = []; |
1164 | 1244 |
1165 tests.push(this); | 1245 tests.push(this); |
1166 } | 1246 } |
1167 | 1247 |
1168 Test.statuses = { | 1248 Test.statuses = { |
1169 PASS:0, | 1249 PASS:0, |
1170 FAIL:1, | 1250 FAIL:1, |
1171 TIMEOUT:2, | 1251 TIMEOUT:2, |
1172 NOTRUN:3 | 1252 NOTRUN:3 |
1173 }; | 1253 }; |
1174 | 1254 |
1175 Test.prototype = merge({}, Test.statuses); | 1255 Test.prototype = merge({}, Test.statuses); |
1176 | 1256 |
| 1257 Test.prototype.phases = { |
| 1258 INITIAL:0, |
| 1259 STARTED:1, |
| 1260 HAS_RESULT:2, |
| 1261 COMPLETE:3 |
| 1262 }; |
| 1263 |
1177 Test.prototype.structured_clone = function() | 1264 Test.prototype.structured_clone = function() |
1178 { | 1265 { |
1179 if (!this._structured_clone) { | 1266 if (!this._structured_clone) { |
1180 var msg = this.message; | 1267 var msg = this.message; |
1181 msg = msg ? String(msg) : msg; | 1268 msg = msg ? String(msg) : msg; |
1182 this._structured_clone = merge({ | 1269 this._structured_clone = merge({ |
1183 name:String(this.name), | 1270 name:String(this.name), |
1184 status:this.status, | 1271 properties:merge({}, this.properties), |
1185 message:msg | |
1186 }, Test.statuses); | 1272 }, Test.statuses); |
1187 } | 1273 } |
| 1274 this._structured_clone.status = this.status; |
| 1275 this._structured_clone.message = this.message; |
| 1276 this._structured_clone.stack = this.stack; |
| 1277 this._structured_clone.index = this.index; |
1188 return this._structured_clone; | 1278 return this._structured_clone; |
1189 }; | 1279 }; |
1190 | 1280 |
1191 Test.prototype.step = function(func, this_obj) | 1281 Test.prototype.step = function(func, this_obj) |
1192 { | 1282 { |
1193 if (this.phase > this.phases.STARTED) { | 1283 if (this.phase > this.phases.STARTED) { |
1194 return; | 1284 return; |
1195 } | 1285 } |
1196 this.phase = this.phases.STARTED; | 1286 this.phase = this.phases.STARTED; |
1197 //If we don't get a result before the harness times out that will be a t
est timout | 1287 //If we don't get a result before the harness times out that will be a t
est timout |
1198 this.set_status(this.TIMEOUT, "Test timed out"); | 1288 this.set_status(this.TIMEOUT, "Test timed out"); |
1199 | 1289 |
1200 tests.started = true; | 1290 tests.started = true; |
| 1291 tests.notify_test_state(this); |
1201 | 1292 |
1202 if (this.timeout_id === null) { | 1293 if (this.timeout_id === null) { |
1203 this.set_timeout(); | 1294 this.set_timeout(); |
1204 } | 1295 } |
1205 | 1296 |
1206 this.steps.push(func); | 1297 this.steps.push(func); |
1207 | 1298 |
1208 if (arguments.length === 1) { | 1299 if (arguments.length === 1) { |
1209 this_obj = this; | 1300 this_obj = this; |
1210 } | 1301 } |
1211 | 1302 |
1212 try { | 1303 try { |
1213 return func.apply(this_obj, Array.prototype.slice.call(arguments, 2)
); | 1304 return func.apply(this_obj, Array.prototype.slice.call(arguments, 2)
); |
1214 } catch (e) { | 1305 } catch (e) { |
1215 if (this.phase >= this.phases.HAS_RESULT) { | 1306 if (this.phase >= this.phases.HAS_RESULT) { |
1216 return; | 1307 return; |
1217 } | 1308 } |
1218 var message = (typeof e === "object" && e !== null) ? e.message : e; | 1309 var message = String((typeof e === "object" && e !== null) ? e.messa
ge : e); |
1219 if (typeof e.stack != "undefined" && typeof e.message == "string") { | 1310 var stack = e.stack ? e.stack : null; |
1220 //Try to make it more informative for some exceptions, at least | 1311 |
1221 //in Gecko and WebKit. This results in a stack dump instead of | 1312 this.set_status(this.FAIL, message, stack); |
1222 //just errors like "Cannot read property 'parentNode' of null" | |
1223 //or "root is null". Makes it a lot longer, of course. | |
1224 message += "(stack: " + e.stack + ")"; | |
1225 } | |
1226 this.set_status(this.FAIL, message); | |
1227 this.phase = this.phases.HAS_RESULT; | 1313 this.phase = this.phases.HAS_RESULT; |
1228 this.done(); | 1314 this.done(); |
1229 } | 1315 } |
1230 }; | 1316 }; |
1231 | 1317 |
1232 Test.prototype.step_func = function(func, this_obj) | 1318 Test.prototype.step_func = function(func, this_obj) |
1233 { | 1319 { |
1234 var test_this = this; | 1320 var test_this = this; |
1235 | 1321 |
1236 if (arguments.length === 1) { | 1322 if (arguments.length === 1) { |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1269 }); | 1355 }); |
1270 }; | 1356 }; |
1271 | 1357 |
1272 Test.prototype.add_cleanup = function(callback) { | 1358 Test.prototype.add_cleanup = function(callback) { |
1273 this.cleanup_callbacks.push(callback); | 1359 this.cleanup_callbacks.push(callback); |
1274 }; | 1360 }; |
1275 | 1361 |
1276 Test.prototype.force_timeout = function() { | 1362 Test.prototype.force_timeout = function() { |
1277 this.set_status(this.TIMEOUT); | 1363 this.set_status(this.TIMEOUT); |
1278 this.phase = this.phases.HAS_RESULT; | 1364 this.phase = this.phases.HAS_RESULT; |
1279 } | 1365 }; |
1280 | 1366 |
1281 Test.prototype.set_timeout = function() | 1367 Test.prototype.set_timeout = function() |
1282 { | 1368 { |
1283 if (this.timeout_length !== null) { | 1369 if (this.timeout_length !== null) { |
1284 var this_obj = this; | 1370 var this_obj = this; |
1285 this.timeout_id = setTimeout(function() | 1371 this.timeout_id = setTimeout(function() |
1286 { | 1372 { |
1287 this_obj.timeout(); | 1373 this_obj.timeout(); |
1288 }, this.timeout_length); | 1374 }, this.timeout_length); |
1289 } | 1375 } |
1290 }; | 1376 }; |
1291 | 1377 |
1292 Test.prototype.set_status = function(status, message) | 1378 Test.prototype.set_status = function(status, message, stack) |
1293 { | 1379 { |
1294 this.status = status; | 1380 this.status = status; |
1295 this.message = message; | 1381 this.message = message; |
| 1382 this.stack = stack ? stack : null; |
1296 }; | 1383 }; |
1297 | 1384 |
1298 Test.prototype.timeout = function() | 1385 Test.prototype.timeout = function() |
1299 { | 1386 { |
1300 this.timeout_id = null; | 1387 this.timeout_id = null; |
1301 this.set_status(this.TIMEOUT, "Test timed out"); | 1388 this.set_status(this.TIMEOUT, "Test timed out"); |
1302 this.phase = this.phases.HAS_RESULT; | 1389 this.phase = this.phases.HAS_RESULT; |
1303 this.done(); | 1390 this.done(); |
1304 }; | 1391 }; |
1305 | 1392 |
1306 Test.prototype.done = function() | 1393 Test.prototype.done = function() |
1307 { | 1394 { |
1308 if (this.phase == this.phases.COMPLETE) { | 1395 if (this.phase == this.phases.COMPLETE) { |
1309 return; | 1396 return; |
1310 } | 1397 } |
1311 | 1398 |
1312 if (this.phase <= this.phases.STARTED) { | 1399 if (this.phase <= this.phases.STARTED) { |
1313 this.set_status(this.PASS, null); | 1400 this.set_status(this.PASS, null); |
1314 } | 1401 } |
1315 | 1402 |
1316 if (this.status == this.NOTRUN) { | |
1317 alert(this.phase); | |
1318 } | |
1319 | |
1320 this.phase = this.phases.COMPLETE; | 1403 this.phase = this.phases.COMPLETE; |
1321 | 1404 |
1322 clearTimeout(this.timeout_id); | 1405 clearTimeout(this.timeout_id); |
1323 tests.result(this); | 1406 tests.result(this); |
1324 this.cleanup(); | 1407 this.cleanup(); |
1325 }; | 1408 }; |
1326 | 1409 |
1327 Test.prototype.cleanup = function() { | 1410 Test.prototype.cleanup = function() { |
1328 forEach(this.cleanup_callbacks, | 1411 forEach(this.cleanup_callbacks, |
1329 function(cleanup_callback) { | 1412 function(cleanup_callback) { |
1330 cleanup_callback(); | 1413 cleanup_callback(); |
1331 }); | 1414 }); |
1332 }; | 1415 }; |
1333 | 1416 |
1334 /* | 1417 /* |
| 1418 * A RemoteTest object mirrors a Test object on a remote worker. The |
| 1419 * associated RemoteWorker updates the RemoteTest object in response to |
| 1420 * received events. In turn, the RemoteTest object replicates these events |
| 1421 * on the local document. This allows listeners (test result reporting |
| 1422 * etc..) to transparently handle local and remote events. |
| 1423 */ |
| 1424 function RemoteTest(clone) { |
| 1425 var this_obj = this; |
| 1426 Object.keys(clone).forEach( |
| 1427 function(key) { |
| 1428 this_obj[key] = clone[key]; |
| 1429 }); |
| 1430 this.index = null; |
| 1431 this.phase = this.phases.INITIAL; |
| 1432 this.update_state_from(clone); |
| 1433 tests.push(this); |
| 1434 } |
| 1435 |
| 1436 RemoteTest.prototype.structured_clone = function() { |
| 1437 var clone = {}; |
| 1438 Object.keys(this).forEach( |
| 1439 function(key) { |
| 1440 if (typeof(this[key]) === "object") { |
| 1441 clone[key] = merge({}, this[key]); |
| 1442 } else { |
| 1443 clone[key] = this[key]; |
| 1444 } |
| 1445 }); |
| 1446 clone.phases = merge({}, this.phases); |
| 1447 return clone; |
| 1448 }; |
| 1449 |
| 1450 RemoteTest.prototype.cleanup = function() {}; |
| 1451 RemoteTest.prototype.phases = Test.prototype.phases; |
| 1452 RemoteTest.prototype.update_state_from = function(clone) { |
| 1453 this.status = clone.status; |
| 1454 this.message = clone.message; |
| 1455 this.stack = clone.stack; |
| 1456 if (this.phase === this.phases.INITIAL) { |
| 1457 this.phase = this.phases.STARTED; |
| 1458 } |
| 1459 }; |
| 1460 RemoteTest.prototype.done = function() { |
| 1461 this.phase = this.phases.COMPLETE; |
| 1462 } |
| 1463 |
| 1464 /* |
| 1465 * A RemoteWorker listens for test events from a worker. These events are |
| 1466 * then used to construct and maintain RemoteTest objects that mirror the |
| 1467 * tests running on the remote worker. |
| 1468 */ |
| 1469 function RemoteWorker(worker) { |
| 1470 this.running = true; |
| 1471 this.tests = new Array(); |
| 1472 |
| 1473 var this_obj = this; |
| 1474 worker.onerror = function(error) { this_obj.worker_error(error); }; |
| 1475 |
| 1476 var message_port; |
| 1477 |
| 1478 if (is_service_worker(worker)) { |
| 1479 // The ServiceWorker's implicit MessagePort is currently not |
| 1480 // reliably accessible from the ServiceWorkerGlobalScope due to |
| 1481 // Blink setting MessageEvent.source to null for messages sent via |
| 1482 // ServiceWorker.postMessage(). Until that's resolved, create an |
| 1483 // explicit MessageChannel and pass one end to the worker. |
| 1484 var message_channel = new MessageChannel(); |
| 1485 message_port = message_channel.port1; |
| 1486 message_port.start(); |
| 1487 worker.postMessage({type: "connect"}, [message_channel.port2]); |
| 1488 } else if (is_shared_worker(worker)) { |
| 1489 message_port = worker.port; |
| 1490 } else { |
| 1491 message_port = worker; |
| 1492 } |
| 1493 |
| 1494 // Keeping a reference to the worker until worker_done() is seen |
| 1495 // prevents the Worker object and its MessageChannel from going away |
| 1496 // before all the messages are dispatched. |
| 1497 this.worker = worker; |
| 1498 |
| 1499 message_port.onmessage = |
| 1500 function(message) { |
| 1501 if (this_obj.running && (message.data.type in this_obj.message_h
andlers)) { |
| 1502 this_obj.message_handlers[message.data.type].call(this_obj,
message.data); |
| 1503 } |
| 1504 }; |
| 1505 } |
| 1506 |
| 1507 RemoteWorker.prototype.worker_error = function(error) { |
| 1508 var message = error.message || String(error); |
| 1509 var filename = (error.filename ? " " + error.filename: ""); |
| 1510 // FIXME: Display worker error states separately from main document |
| 1511 // error state. |
| 1512 this.worker_done({ |
| 1513 status: { |
| 1514 status: tests.status.ERROR, |
| 1515 message: "Error in worker" + filename + ": " + message, |
| 1516 stack: error.stack |
| 1517 } |
| 1518 }); |
| 1519 error.preventDefault(); |
| 1520 }; |
| 1521 |
| 1522 RemoteWorker.prototype.test_state = function(data) { |
| 1523 var remote_test = this.tests[data.test.index]; |
| 1524 if (!remote_test) { |
| 1525 remote_test = new RemoteTest(data.test); |
| 1526 this.tests[data.test.index] = remote_test; |
| 1527 } |
| 1528 remote_test.update_state_from(data.test); |
| 1529 tests.notify_test_state(remote_test); |
| 1530 }; |
| 1531 |
| 1532 RemoteWorker.prototype.test_done = function(data) { |
| 1533 var remote_test = this.tests[data.test.index]; |
| 1534 remote_test.update_state_from(data.test); |
| 1535 remote_test.done(); |
| 1536 tests.result(remote_test); |
| 1537 }; |
| 1538 |
| 1539 RemoteWorker.prototype.worker_done = function(data) { |
| 1540 if (tests.status.status === null && |
| 1541 data.status.status !== data.status.OK) { |
| 1542 tests.status.status = data.status.status; |
| 1543 tests.status.message = data.status.message; |
| 1544 tests.status.stack = data.status.stack; |
| 1545 } |
| 1546 this.running = false; |
| 1547 this.worker = null; |
| 1548 if (tests.all_done()) { |
| 1549 tests.complete(); |
| 1550 } |
| 1551 }; |
| 1552 |
| 1553 RemoteWorker.prototype.message_handlers = { |
| 1554 test_state: RemoteWorker.prototype.test_state, |
| 1555 result: RemoteWorker.prototype.test_done, |
| 1556 complete: RemoteWorker.prototype.worker_done |
| 1557 }; |
| 1558 |
| 1559 /* |
1335 * Harness | 1560 * Harness |
1336 */ | 1561 */ |
1337 | 1562 |
1338 function TestsStatus() | 1563 function TestsStatus() |
1339 { | 1564 { |
1340 this.status = null; | 1565 this.status = null; |
1341 this.message = null; | 1566 this.message = null; |
| 1567 this.stack = null; |
1342 } | 1568 } |
1343 | 1569 |
1344 TestsStatus.statuses = { | 1570 TestsStatus.statuses = { |
1345 OK:0, | 1571 OK:0, |
1346 ERROR:1, | 1572 ERROR:1, |
1347 TIMEOUT:2 | 1573 TIMEOUT:2 |
1348 }; | 1574 }; |
1349 | 1575 |
1350 TestsStatus.prototype = merge({}, TestsStatus.statuses); | 1576 TestsStatus.prototype = merge({}, TestsStatus.statuses); |
1351 | 1577 |
1352 TestsStatus.prototype.structured_clone = function() | 1578 TestsStatus.prototype.structured_clone = function() |
1353 { | 1579 { |
1354 if (!this._structured_clone) { | 1580 if (!this._structured_clone) { |
1355 var msg = this.message; | 1581 var msg = this.message; |
1356 msg = msg ? String(msg) : msg; | 1582 msg = msg ? String(msg) : msg; |
1357 this._structured_clone = merge({ | 1583 this._structured_clone = merge({ |
1358 status:this.status, | 1584 status:this.status, |
1359 message:msg | 1585 message:msg, |
| 1586 stack:this.stack |
1360 }, TestsStatus.statuses); | 1587 }, TestsStatus.statuses); |
1361 } | 1588 } |
1362 return this._structured_clone; | 1589 return this._structured_clone; |
1363 }; | 1590 }; |
1364 | 1591 |
1365 function Tests() | 1592 function Tests() |
1366 { | 1593 { |
1367 this.tests = []; | 1594 this.tests = []; |
1368 this.num_pending = 0; | 1595 this.num_pending = 0; |
1369 | 1596 |
1370 this.phases = { | 1597 this.phases = { |
1371 INITIAL:0, | 1598 INITIAL:0, |
1372 SETUP:1, | 1599 SETUP:1, |
1373 HAVE_TESTS:2, | 1600 HAVE_TESTS:2, |
1374 HAVE_RESULTS:3, | 1601 HAVE_RESULTS:3, |
1375 COMPLETE:4 | 1602 COMPLETE:4 |
1376 }; | 1603 }; |
1377 this.phase = this.phases.INITIAL; | 1604 this.phase = this.phases.INITIAL; |
1378 | 1605 |
1379 this.properties = {}; | 1606 this.properties = {}; |
1380 | 1607 |
1381 //All tests can't be done until the load event fires | |
1382 this.all_loaded = false; | |
1383 this.wait_for_finish = false; | 1608 this.wait_for_finish = false; |
1384 this.processing_callbacks = false; | 1609 this.processing_callbacks = false; |
1385 | 1610 |
1386 this.allow_uncaught_exception = false; | 1611 this.allow_uncaught_exception = false; |
1387 | 1612 |
1388 this.file_is_test = false; | 1613 this.file_is_test = false; |
1389 | 1614 |
1390 this.timeout_multiplier = 1; | 1615 this.timeout_multiplier = 1; |
1391 this.timeout_length = this.get_timeout(); | 1616 this.timeout_length = test_environment.test_timeout(); |
1392 this.timeout_id = null; | 1617 this.timeout_id = null; |
1393 | 1618 |
1394 this.start_callbacks = []; | 1619 this.start_callbacks = []; |
| 1620 this.test_state_callbacks = []; |
1395 this.test_done_callbacks = []; | 1621 this.test_done_callbacks = []; |
1396 this.all_done_callbacks = []; | 1622 this.all_done_callbacks = []; |
1397 | 1623 |
| 1624 this.pending_workers = []; |
| 1625 |
1398 this.status = new TestsStatus(); | 1626 this.status = new TestsStatus(); |
1399 | 1627 |
1400 var this_obj = this; | 1628 var this_obj = this; |
1401 | 1629 |
1402 on_event(window, "load", | 1630 test_environment.add_on_loaded_callback(function() { |
1403 function() | 1631 if (this_obj.all_done()) { |
1404 { | 1632 this_obj.complete(); |
1405 this_obj.all_loaded = true; | 1633 } |
1406 if (this_obj.all_done()) | 1634 }); |
1407 { | |
1408 this_obj.complete(); | |
1409 } | |
1410 }); | |
1411 | 1635 |
1412 this.set_timeout(); | 1636 this.set_timeout(); |
1413 } | 1637 } |
1414 | 1638 |
1415 Tests.prototype.setup = function(func, properties) | 1639 Tests.prototype.setup = function(func, properties) |
1416 { | 1640 { |
1417 if (this.phase >= this.phases.HAVE_RESULTS) { | 1641 if (this.phase >= this.phases.HAVE_RESULTS) { |
1418 return; | 1642 return; |
1419 } | 1643 } |
1420 | 1644 |
(...skipping 21 matching lines...) Expand all Loading... |
1442 } | 1666 } |
1443 } | 1667 } |
1444 } | 1668 } |
1445 | 1669 |
1446 if (func) { | 1670 if (func) { |
1447 try { | 1671 try { |
1448 func(); | 1672 func(); |
1449 } catch (e) { | 1673 } catch (e) { |
1450 this.status.status = this.status.ERROR; | 1674 this.status.status = this.status.ERROR; |
1451 this.status.message = String(e); | 1675 this.status.message = String(e); |
| 1676 this.status.stack = e.stack ? e.stack : null; |
1452 } | 1677 } |
1453 } | 1678 } |
1454 this.set_timeout(); | 1679 this.set_timeout(); |
1455 }; | 1680 }; |
1456 | 1681 |
1457 Tests.prototype.set_file_is_test = function() { | 1682 Tests.prototype.set_file_is_test = function() { |
1458 if (this.tests.length > 0) { | 1683 if (this.tests.length > 0) { |
1459 throw new Error("Tried to set file as test after creating a test"); | 1684 throw new Error("Tried to set file as test after creating a test"); |
1460 } | 1685 } |
1461 this.wait_for_finish = true; | 1686 this.wait_for_finish = true; |
1462 this.file_is_test = true; | 1687 this.file_is_test = true; |
1463 // Create the test, which will add it to the list of tests | 1688 // Create the test, which will add it to the list of tests |
1464 async_test(); | 1689 async_test(); |
1465 }; | 1690 }; |
1466 | 1691 |
1467 Tests.prototype.get_timeout = function() { | |
1468 var metas = document.getElementsByTagName("meta"); | |
1469 for (var i = 0; i < metas.length; i++) { | |
1470 if (metas[i].name == "timeout") { | |
1471 if (metas[i].content == "long") { | |
1472 return settings.harness_timeout.long; | |
1473 } | |
1474 break; | |
1475 } | |
1476 } | |
1477 return settings.harness_timeout.normal; | |
1478 }; | |
1479 | |
1480 Tests.prototype.set_timeout = function() { | 1692 Tests.prototype.set_timeout = function() { |
1481 var this_obj = this; | 1693 var this_obj = this; |
1482 clearTimeout(this.timeout_id); | 1694 clearTimeout(this.timeout_id); |
1483 if (this.timeout_length !== null) { | 1695 if (this.timeout_length !== null) { |
1484 this.timeout_id = setTimeout(function() { | 1696 this.timeout_id = setTimeout(function() { |
1485 this_obj.timeout(); | 1697 this_obj.timeout(); |
1486 }, this.timeout_length); | 1698 }, this.timeout_length); |
1487 } | 1699 } |
1488 }; | 1700 }; |
1489 | 1701 |
1490 Tests.prototype.timeout = function() { | 1702 Tests.prototype.timeout = function() { |
1491 if (this.status.status == this.status.OK) { | 1703 if (this.status.status === null) { |
1492 this.status.status = this.status.TIMEOUT; | 1704 this.status.status = this.status.TIMEOUT; |
1493 } | 1705 } |
1494 this.complete(); | 1706 this.complete(); |
1495 }; | 1707 }; |
1496 | 1708 |
1497 Tests.prototype.end_wait = function() | 1709 Tests.prototype.end_wait = function() |
1498 { | 1710 { |
1499 this.wait_for_finish = false; | 1711 this.wait_for_finish = false; |
1500 if (this.all_done()) { | 1712 if (this.all_done()) { |
1501 this.complete(); | 1713 this.complete(); |
1502 } | 1714 } |
1503 }; | 1715 }; |
1504 | 1716 |
1505 Tests.prototype.push = function(test) | 1717 Tests.prototype.push = function(test) |
1506 { | 1718 { |
1507 if (this.phase < this.phases.HAVE_TESTS) { | 1719 if (this.phase < this.phases.HAVE_TESTS) { |
1508 this.start(); | 1720 this.start(); |
1509 } | 1721 } |
1510 this.num_pending++; | 1722 this.num_pending++; |
1511 this.tests.push(test); | 1723 test.index = this.tests.push(test); |
| 1724 this.notify_test_state(test); |
| 1725 }; |
| 1726 |
| 1727 Tests.prototype.notify_test_state = function(test) { |
| 1728 var this_obj = this; |
| 1729 forEach(this.test_state_callbacks, |
| 1730 function(callback) { |
| 1731 callback(test, this_obj); |
| 1732 }); |
1512 }; | 1733 }; |
1513 | 1734 |
1514 Tests.prototype.all_done = function() { | 1735 Tests.prototype.all_done = function() { |
1515 return (this.tests.length > 0 && this.all_loaded && this.num_pending ===
0 && | 1736 return (this.tests.length > 0 && test_environment.all_loaded && |
1516 !this.wait_for_finish && !this.processing_callbacks); | 1737 this.num_pending === 0 && !this.wait_for_finish && |
| 1738 !this.processing_callbacks && |
| 1739 !this.pending_workers.some(function(w) { return w.running; })); |
1517 }; | 1740 }; |
1518 | 1741 |
1519 Tests.prototype.start = function() { | 1742 Tests.prototype.start = function() { |
1520 this.phase = this.phases.HAVE_TESTS; | 1743 this.phase = this.phases.HAVE_TESTS; |
1521 this.notify_start(); | 1744 this.notify_start(); |
1522 }; | 1745 }; |
1523 | 1746 |
1524 Tests.prototype.notify_start = function() { | 1747 Tests.prototype.notify_start = function() { |
1525 var this_obj = this; | 1748 var this_obj = this; |
1526 forEach (this.start_callbacks, | 1749 forEach (this.start_callbacks, |
1527 function(callback) | 1750 function(callback) |
1528 { | 1751 { |
1529 callback(this_obj.properties); | 1752 callback(this_obj.properties); |
1530 }); | 1753 }); |
1531 forEach_windows( | |
1532 function(w, is_same_origin) | |
1533 { | |
1534 if (is_same_origin && w.start_callback) { | |
1535 try { | |
1536 w.start_callback(this_obj.properties); | |
1537 } catch (e) { | |
1538 if (debug) { | |
1539 throw e; | |
1540 } | |
1541 } | |
1542 } | |
1543 if (supports_post_message(w) && w !== self) { | |
1544 w.postMessage({ | |
1545 type: "start", | |
1546 properties: this_obj.properties | |
1547 }, "*"); | |
1548 } | |
1549 }); | |
1550 }; | 1754 }; |
1551 | 1755 |
1552 Tests.prototype.result = function(test) | 1756 Tests.prototype.result = function(test) |
1553 { | 1757 { |
1554 if (this.phase > this.phases.HAVE_RESULTS) { | 1758 if (this.phase > this.phases.HAVE_RESULTS) { |
1555 return; | 1759 return; |
1556 } | 1760 } |
1557 this.phase = this.phases.HAVE_RESULTS; | 1761 this.phase = this.phases.HAVE_RESULTS; |
1558 this.num_pending--; | 1762 this.num_pending--; |
1559 this.notify_result(test); | 1763 this.notify_result(test); |
1560 }; | 1764 }; |
1561 | 1765 |
1562 Tests.prototype.notify_result = function(test) { | 1766 Tests.prototype.notify_result = function(test) { |
1563 var this_obj = this; | 1767 var this_obj = this; |
1564 this.processing_callbacks = true; | 1768 this.processing_callbacks = true; |
1565 forEach(this.test_done_callbacks, | 1769 forEach(this.test_done_callbacks, |
1566 function(callback) | 1770 function(callback) |
1567 { | 1771 { |
1568 callback(test, this_obj); | 1772 callback(test, this_obj); |
1569 }); | 1773 }); |
1570 | |
1571 forEach_windows( | |
1572 function(w, is_same_origin) | |
1573 { | |
1574 if (is_same_origin && w.result_callback) { | |
1575 try { | |
1576 w.result_callback(test); | |
1577 } catch (e) { | |
1578 if (debug) { | |
1579 throw e; | |
1580 } | |
1581 } | |
1582 } | |
1583 if (supports_post_message(w) && w !== self) { | |
1584 w.postMessage({ | |
1585 type: "result", | |
1586 test: test.structured_clone() | |
1587 }, "*"); | |
1588 } | |
1589 }); | |
1590 this.processing_callbacks = false; | 1774 this.processing_callbacks = false; |
1591 if (this_obj.all_done()) { | 1775 if (this_obj.all_done()) { |
1592 this_obj.complete(); | 1776 this_obj.complete(); |
1593 } | 1777 } |
1594 }; | 1778 }; |
1595 | 1779 |
1596 Tests.prototype.complete = function() { | 1780 Tests.prototype.complete = function() { |
1597 if (this.phase === this.phases.COMPLETE) { | 1781 if (this.phase === this.phases.COMPLETE) { |
1598 return; | 1782 return; |
1599 } | 1783 } |
1600 this.phase = this.phases.COMPLETE; | 1784 this.phase = this.phases.COMPLETE; |
1601 var this_obj = this; | 1785 var this_obj = this; |
1602 this.tests.forEach( | 1786 this.tests.forEach( |
1603 function(x) | 1787 function(x) |
1604 { | 1788 { |
1605 if (x.status === x.NOTRUN) { | 1789 if (x.phase < x.phases.COMPLETE) { |
1606 this_obj.notify_result(x); | 1790 this_obj.notify_result(x); |
1607 x.cleanup(); | 1791 x.cleanup(); |
| 1792 x.phase = x.phases.COMPLETE; |
1608 } | 1793 } |
1609 } | 1794 } |
1610 ); | 1795 ); |
1611 this.notify_complete(); | 1796 this.notify_complete(); |
1612 }; | 1797 }; |
1613 | 1798 |
1614 Tests.prototype.notify_complete = function() | 1799 Tests.prototype.notify_complete = function() { |
1615 { | |
1616 clearTimeout(this.timeout_id); | |
1617 var this_obj = this; | 1800 var this_obj = this; |
1618 var tests = map(this_obj.tests, | |
1619 function(test) | |
1620 { | |
1621 return test.structured_clone(); | |
1622 }); | |
1623 if (this.status.status === null) { | 1801 if (this.status.status === null) { |
1624 this.status.status = this.status.OK; | 1802 this.status.status = this.status.OK; |
1625 } | 1803 } |
1626 | 1804 |
1627 forEach (this.all_done_callbacks, | 1805 forEach (this.all_done_callbacks, |
1628 function(callback) | 1806 function(callback) |
1629 { | 1807 { |
1630 callback(this_obj.tests, this_obj.status); | 1808 callback(this_obj.tests, this_obj.status); |
1631 }); | 1809 }); |
1632 | |
1633 forEach_windows( | |
1634 function(w, is_same_origin) | |
1635 { | |
1636 if (is_same_origin && w.completion_callback) { | |
1637 try { | |
1638 w.completion_callback(this_obj.tests, this_obj.statu
s); | |
1639 } catch (e) { | |
1640 if (debug) { | |
1641 throw e; | |
1642 } | |
1643 } | |
1644 } | |
1645 if (supports_post_message(w) && w !== self) { | |
1646 w.postMessage({ | |
1647 type: "complete", | |
1648 tests: tests, | |
1649 status: this_obj.status.structured_clone() | |
1650 }, "*"); | |
1651 } | |
1652 }); | |
1653 }; | 1810 }; |
1654 | 1811 |
1655 var tests = new Tests(); | 1812 Tests.prototype.fetch_tests_from_worker = function(worker) { |
| 1813 if (this.phase >= this.phases.COMPLETE) { |
| 1814 return; |
| 1815 } |
1656 | 1816 |
1657 addEventListener("error", function(e) { | 1817 this.pending_workers.push(new RemoteWorker(worker)); |
1658 if (tests.file_is_test) { | 1818 }; |
1659 var test = tests.tests[0]; | 1819 |
1660 if (test.phase >= test.phases.HAS_RESULT) { | 1820 function fetch_tests_from_worker(port) { |
1661 return; | 1821 tests.fetch_tests_from_worker(port); |
1662 } | 1822 } |
1663 var message = e.message; | 1823 expose(fetch_tests_from_worker, 'fetch_tests_from_worker'); |
1664 test.set_status(test.FAIL, message); | |
1665 test.phase = test.phases.HAS_RESULT; | |
1666 test.done(); | |
1667 done(); | |
1668 } else if (!tests.allow_uncaught_exception) { | |
1669 tests.status.status = tests.status.ERROR; | |
1670 tests.status.message = e.message; | |
1671 } | |
1672 }); | |
1673 | 1824 |
1674 function timeout() { | 1825 function timeout() { |
1675 if (tests.timeout_length === null) { | 1826 if (tests.timeout_length === null) { |
1676 tests.timeout(); | 1827 tests.timeout(); |
1677 } | 1828 } |
1678 } | 1829 } |
1679 expose(timeout, 'timeout'); | 1830 expose(timeout, 'timeout'); |
1680 | 1831 |
1681 function add_start_callback(callback) { | 1832 function add_start_callback(callback) { |
1682 tests.start_callbacks.push(callback); | 1833 tests.start_callbacks.push(callback); |
1683 } | 1834 } |
1684 | 1835 |
| 1836 function add_test_state_callback(callback) { |
| 1837 tests.test_state_callbacks.push(callback); |
| 1838 } |
| 1839 |
1685 function add_result_callback(callback) | 1840 function add_result_callback(callback) |
1686 { | 1841 { |
1687 tests.test_done_callbacks.push(callback); | 1842 tests.test_done_callbacks.push(callback); |
1688 } | 1843 } |
1689 | 1844 |
1690 function add_completion_callback(callback) | 1845 function add_completion_callback(callback) |
1691 { | 1846 { |
1692 tests.all_done_callbacks.push(callback); | 1847 tests.all_done_callbacks.push(callback); |
1693 } | 1848 } |
1694 | 1849 |
1695 expose(add_start_callback, 'add_start_callback'); | 1850 expose(add_start_callback, 'add_start_callback'); |
| 1851 expose(add_test_state_callback, 'add_test_state_callback'); |
1696 expose(add_result_callback, 'add_result_callback'); | 1852 expose(add_result_callback, 'add_result_callback'); |
1697 expose(add_completion_callback, 'add_completion_callback'); | 1853 expose(add_completion_callback, 'add_completion_callback'); |
1698 | 1854 |
1699 /* | 1855 /* |
1700 * Output listener | 1856 * Output listener |
1701 */ | 1857 */ |
1702 | 1858 |
1703 function Output() { | 1859 function Output() { |
1704 this.output_document = document; | 1860 this.output_document = document; |
1705 this.output_node = null; | 1861 this.output_node = null; |
1706 this.done_count = 0; | |
1707 this.enabled = settings.output; | 1862 this.enabled = settings.output; |
1708 this.phase = this.INITIAL; | 1863 this.phase = this.INITIAL; |
1709 } | 1864 } |
1710 | 1865 |
1711 Output.prototype.INITIAL = 0; | 1866 Output.prototype.INITIAL = 0; |
1712 Output.prototype.STARTED = 1; | 1867 Output.prototype.STARTED = 1; |
1713 Output.prototype.HAVE_RESULTS = 2; | 1868 Output.prototype.HAVE_RESULTS = 2; |
1714 Output.prototype.COMPLETE = 3; | 1869 Output.prototype.COMPLETE = 3; |
1715 | 1870 |
1716 Output.prototype.setup = function(properties) { | 1871 Output.prototype.setup = function(properties) { |
(...skipping 24 matching lines...) Expand all Loading... |
1741 if (typeof this.output_document === "function") { | 1896 if (typeof this.output_document === "function") { |
1742 output_document = this.output_document.apply(undefined); | 1897 output_document = this.output_document.apply(undefined); |
1743 } else { | 1898 } else { |
1744 output_document = this.output_document; | 1899 output_document = this.output_document; |
1745 } | 1900 } |
1746 if (!output_document) { | 1901 if (!output_document) { |
1747 return; | 1902 return; |
1748 } | 1903 } |
1749 var node = output_document.getElementById("log"); | 1904 var node = output_document.getElementById("log"); |
1750 if (!node) { | 1905 if (!node) { |
1751 if (!document.body) { | 1906 if (!document.body || document.readyState == "loading") { |
1752 return; | 1907 return; |
1753 } | 1908 } |
1754 node = output_document.createElement("div"); | 1909 node = output_document.createElement("div"); |
| 1910 node.id = "log"; |
1755 output_document.body.appendChild(node); | 1911 output_document.body.appendChild(node); |
1756 } | 1912 } |
1757 this.output_document = output_document; | 1913 this.output_document = output_document; |
1758 this.output_node = node; | 1914 this.output_node = node; |
1759 }; | 1915 }; |
1760 | 1916 |
1761 Output.prototype.show_status = function() { | 1917 Output.prototype.show_status = function() { |
1762 if (this.phase < this.STARTED) { | 1918 if (this.phase < this.STARTED) { |
1763 this.init(); | 1919 this.init(); |
1764 } | 1920 } |
1765 if (!this.enabled) { | 1921 if (!this.enabled) { |
1766 return; | 1922 return; |
1767 } | 1923 } |
1768 if (this.phase < this.HAVE_RESULTS) { | 1924 if (this.phase < this.HAVE_RESULTS) { |
1769 this.resolve_log(); | 1925 this.resolve_log(); |
1770 this.phase = this.HAVE_RESULTS; | 1926 this.phase = this.HAVE_RESULTS; |
1771 } | 1927 } |
1772 this.done_count++; | 1928 var done_count = tests.tests.length - tests.num_pending; |
1773 if (this.output_node) { | 1929 if (this.output_node) { |
1774 if (this.done_count < 100 || | 1930 if (done_count < 100 || |
1775 (this.done_count < 1000 && this.done_count % 100 === 0) || | 1931 (done_count < 1000 && done_count % 100 === 0) || |
1776 this.done_count % 1000 === 0) { | 1932 done_count % 1000 === 0) { |
1777 this.output_node.textContent = "Running, " + | 1933 this.output_node.textContent = "Running, " + |
1778 this.done_count + " complete, " + | 1934 done_count + " complete, " + |
1779 tests.num_pending + " remain"; | 1935 tests.num_pending + " remain"; |
1780 } | 1936 } |
1781 } | 1937 } |
1782 }; | 1938 }; |
1783 | 1939 |
1784 Output.prototype.show_results = function (tests, harness_status) { | 1940 Output.prototype.show_results = function (tests, harness_status) { |
1785 if (this.phase >= this.COMPLETE) { | 1941 if (this.phase >= this.COMPLETE) { |
1786 return; | 1942 return; |
1787 } | 1943 } |
1788 if (!this.enabled) { | 1944 if (!this.enabled) { |
1789 return; | 1945 return; |
1790 } | 1946 } |
1791 if (!this.output_node) { | 1947 if (!this.output_node) { |
1792 this.resolve_log(); | 1948 this.resolve_log(); |
1793 } | 1949 } |
1794 this.phase = this.COMPLETE; | 1950 this.phase = this.COMPLETE; |
1795 | 1951 |
1796 var log = this.output_node; | 1952 var log = this.output_node; |
1797 if (!log) { | 1953 if (!log) { |
1798 return; | 1954 return; |
1799 } | 1955 } |
1800 var output_document = this.output_document; | 1956 var output_document = this.output_document; |
1801 | 1957 |
1802 while (log.lastChild) { | 1958 while (log.lastChild) { |
1803 log.removeChild(log.lastChild); | 1959 log.removeChild(log.lastChild); |
1804 } | 1960 } |
1805 | 1961 |
1806 if (script_prefix != null) { | 1962 var script_prefix = null; |
| 1963 var scripts = document.getElementsByTagName("script"); |
| 1964 for (var i = 0; i < scripts.length; i++) { |
| 1965 var src; |
| 1966 if (scripts[i].src) { |
| 1967 src = scripts[i].src; |
| 1968 } else if (scripts[i].href) { |
| 1969 //SVG case |
| 1970 src = scripts[i].href.baseVal; |
| 1971 } |
| 1972 |
| 1973 var matches = src && src.match(/^(.*\/|)testharness\.js$/); |
| 1974 if (matches) { |
| 1975 script_prefix = matches[1]; |
| 1976 break; |
| 1977 } |
| 1978 } |
| 1979 |
| 1980 if (script_prefix !== null) { |
1807 var stylesheet = output_document.createElementNS(xhtml_ns, "link"); | 1981 var stylesheet = output_document.createElementNS(xhtml_ns, "link"); |
1808 stylesheet.setAttribute("rel", "stylesheet"); | 1982 stylesheet.setAttribute("rel", "stylesheet"); |
1809 stylesheet.setAttribute("href", script_prefix + "testharness.css"); | 1983 stylesheet.setAttribute("href", script_prefix + "testharness.css"); |
1810 var heads = output_document.getElementsByTagName("head"); | 1984 var heads = output_document.getElementsByTagName("head"); |
1811 if (heads.length) { | 1985 if (heads.length) { |
1812 heads[0].appendChild(stylesheet); | 1986 heads[0].appendChild(stylesheet); |
1813 } | 1987 } |
1814 } | 1988 } |
1815 | 1989 |
1816 var status_text_harness = {}; | 1990 var status_text_harness = {}; |
(...skipping 20 matching lines...) Expand all Loading... |
1837 | 2011 |
1838 function status_class(status) | 2012 function status_class(status) |
1839 { | 2013 { |
1840 return status.replace(/\s/g, '').toLowerCase(); | 2014 return status.replace(/\s/g, '').toLowerCase(); |
1841 } | 2015 } |
1842 | 2016 |
1843 var summary_template = ["section", {"id":"summary"}, | 2017 var summary_template = ["section", {"id":"summary"}, |
1844 ["h2", {}, "Summary"], | 2018 ["h2", {}, "Summary"], |
1845 function() | 2019 function() |
1846 { | 2020 { |
1847 if (harness_status.status === harness_status
.OK) { | |
1848 return null; | |
1849 } | |
1850 | 2021 |
1851 var status = status_text_harness[harness_sta
tus.status]; | 2022 var status = status_text_harness[harness_sta
tus.status]; |
1852 var rv = [["p", {"class":status_class(status
)}]]; | 2023 var rv = [["section", {}, |
| 2024 ["p", {}, |
| 2025 "Harness status: ", |
| 2026 ["span", {"class":status_class(s
tatus)}, |
| 2027 status |
| 2028 ], |
| 2029 ] |
| 2030 ]]; |
1853 | 2031 |
1854 if (harness_status.status === harness_status
.ERROR) { | 2032 if (harness_status.status === harness_status
.ERROR) { |
1855 rv[0].push("Harness encountered an error
:"); | 2033 rv[0].push(["pre", {}, harness_status.me
ssage]); |
1856 rv.push(["pre", {}, harness_status.messa
ge]); | 2034 if (harness_status.stack) { |
1857 } else if (harness_status.status === harness
_status.TIMEOUT) { | 2035 rv[0].push(["pre", {}, harness_statu
s.stack]); |
1858 rv[0].push("Harness timed out."); | 2036 } |
1859 } else { | |
1860 rv[0].push("Harness got an unexpected st
atus."); | |
1861 } | 2037 } |
1862 | |
1863 return rv; | 2038 return rv; |
1864 }, | 2039 }, |
1865 ["p", {}, "Found ${num_tests} tests"], | 2040 ["p", {}, "Found ${num_tests} tests"], |
1866 function() { | 2041 function() { |
1867 var rv = [["div", {}]]; | 2042 var rv = [["div", {}]]; |
1868 var i = 0; | 2043 var i = 0; |
1869 while (status_text.hasOwnProperty(i)) { | 2044 while (status_text.hasOwnProperty(i)) { |
1870 if (status_number.hasOwnProperty(status_
text[i])) { | 2045 if (status_number.hasOwnProperty(status_
text[i])) { |
1871 var status = status_text[i]; | 2046 var status = status_text[i]; |
1872 rv[0].push(["div", {"class":status_c
lass(status)}, | 2047 rv[0].push(["div", {"class":status_c
lass(status)}, |
1873 ["label", {}, | 2048 ["label", {}, |
1874 ["input", {type:"checkb
ox", checked:"checked"}], | 2049 ["input", {type:"checkb
ox", checked:"checked"}], |
1875 status_number[status] +
" " + status]]); | 2050 status_number[status] +
" " + status]]); |
1876 } | 2051 } |
1877 i++; | 2052 i++; |
1878 } | 2053 } |
1879 return rv; | 2054 return rv; |
1880 }]; | 2055 }, |
| 2056 ]; |
1881 | 2057 |
1882 log.appendChild(render(summary_template, {num_tests:tests.length}, outpu
t_document)); | 2058 log.appendChild(render(summary_template, {num_tests:tests.length}, outpu
t_document)); |
1883 | 2059 |
1884 forEach(output_document.querySelectorAll("section#summary label"), | 2060 forEach(output_document.querySelectorAll("section#summary label"), |
1885 function(element) | 2061 function(element) |
1886 { | 2062 { |
1887 on_event(element, "click", | 2063 on_event(element, "click", |
1888 function(e) | 2064 function(e) |
1889 { | 2065 { |
1890 if (output_document.getElementById("results") =
== null) { | 2066 if (output_document.getElementById("results") =
== null) { |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1948 for (var i = 0; i < tests.length; i++) { | 2124 for (var i = 0; i < tests.length; i++) { |
1949 html += '<tr class="' + | 2125 html += '<tr class="' + |
1950 escape_html(status_class(status_text[tests[i].status])) + | 2126 escape_html(status_class(status_text[tests[i].status])) + |
1951 '"><td>' + | 2127 '"><td>' + |
1952 escape_html(status_text[tests[i].status]) + | 2128 escape_html(status_text[tests[i].status]) + |
1953 "</td><td>" + | 2129 "</td><td>" + |
1954 escape_html(tests[i].name) + | 2130 escape_html(tests[i].name) + |
1955 "</td><td>" + | 2131 "</td><td>" + |
1956 (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>"
: "") + | 2132 (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>"
: "") + |
1957 escape_html(tests[i].message ? tests[i].message : " ") + | 2133 escape_html(tests[i].message ? tests[i].message : " ") + |
| 2134 (tests[i].stack ? "<pre>" + |
| 2135 escape_html(tests[i].stack) + |
| 2136 "</pre>": "") + |
1958 "</td></tr>"; | 2137 "</td></tr>"; |
1959 } | 2138 } |
1960 html += "</tbody></table>"; | 2139 html += "</tbody></table>"; |
1961 try { | 2140 try { |
1962 log.lastChild.innerHTML = html; | 2141 log.lastChild.innerHTML = html; |
1963 } catch (e) { | 2142 } catch (e) { |
1964 log.appendChild(document.createElementNS(xhtml_ns, "p")) | 2143 log.appendChild(document.createElementNS(xhtml_ns, "p")) |
1965 .textContent = "Setting innerHTML for the log threw an exception.
"; | 2144 .textContent = "Setting innerHTML for the log threw an exception.
"; |
1966 log.appendChild(document.createElementNS(xhtml_ns, "pre")) | 2145 log.appendChild(document.createElementNS(xhtml_ns, "pre")) |
1967 .textContent = html; | 2146 .textContent = html; |
1968 } | 2147 } |
1969 }; | 2148 }; |
1970 | 2149 |
1971 var output = new Output(); | |
1972 add_start_callback(function (properties) {output.init(properties);}); | |
1973 add_result_callback(function () {output.show_status();}); | |
1974 add_completion_callback(function (tests, harness_status) {output.show_result
s(tests, harness_status);}); | |
1975 | |
1976 /* | 2150 /* |
1977 * Template code | 2151 * Template code |
1978 * | 2152 * |
1979 * A template is just a javascript structure. An element is represented as: | 2153 * A template is just a javascript structure. An element is represented as: |
1980 * | 2154 * |
1981 * [tag_name, {attr_name:attr_value}, child1, child2] | 2155 * [tag_name, {attr_name:attr_value}, child1, child2] |
1982 * | 2156 * |
1983 * the children can either be strings (which act like text nodes), other tem
plates or | 2157 * the children can either be strings (which act like text nodes), other tem
plates or |
1984 * functions (see below) | 2158 * functions (see below) |
1985 * | 2159 * |
(...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2114 } else { | 2288 } else { |
2115 var text_node = output_document.createTextNode(template[i]); | 2289 var text_node = output_document.createTextNode(template[i]); |
2116 element.appendChild(text_node); | 2290 element.appendChild(text_node); |
2117 } | 2291 } |
2118 } | 2292 } |
2119 } | 2293 } |
2120 | 2294 |
2121 return element; | 2295 return element; |
2122 } | 2296 } |
2123 | 2297 |
2124 | |
2125 | |
2126 function make_dom(template, substitutions, output_document) | 2298 function make_dom(template, substitutions, output_document) |
2127 { | 2299 { |
2128 if (is_single_node(template)) { | 2300 if (is_single_node(template)) { |
2129 return make_dom_single(template, output_document); | 2301 return make_dom_single(template, output_document); |
2130 } | 2302 } |
2131 | 2303 |
2132 return map(template, function(x) { | 2304 return map(template, function(x) { |
2133 return make_dom_single(x, output_document); | 2305 return make_dom_single(x, output_document); |
2134 }); | 2306 }); |
2135 } | 2307 } |
(...skipping 14 matching lines...) Expand all Loading... |
2150 if (expected_true !== true) { | 2322 if (expected_true !== true) { |
2151 var msg = make_message(function_name, description, | 2323 var msg = make_message(function_name, description, |
2152 error, substitutions); | 2324 error, substitutions); |
2153 throw new AssertionError(msg); | 2325 throw new AssertionError(msg); |
2154 } | 2326 } |
2155 } | 2327 } |
2156 | 2328 |
2157 function AssertionError(message) | 2329 function AssertionError(message) |
2158 { | 2330 { |
2159 this.message = message; | 2331 this.message = message; |
| 2332 this.stack = this.get_stack(); |
2160 } | 2333 } |
2161 | 2334 |
2162 AssertionError.prototype.toString = function() { | 2335 AssertionError.prototype = Object.create(Error.prototype); |
2163 return this.message; | 2336 |
2164 }; | 2337 AssertionError.prototype.get_stack = function() { |
| 2338 var lines = new Error().stack.split("\n"); |
| 2339 var rv = []; |
| 2340 var re = /\/resources\/testharness\.js/; |
| 2341 var i = 0; |
| 2342 // Fire remove any preamble that doesn't match the regexp |
| 2343 while (!re.test(lines[i])) { |
| 2344 i++ |
| 2345 } |
| 2346 // Then remove top frames in testharness.js itself |
| 2347 while (re.test(lines[i])) { |
| 2348 i++ |
| 2349 } |
| 2350 return lines.slice(i).join("\n"); |
| 2351 } |
2165 | 2352 |
2166 function make_message(function_name, description, error, substitutions) | 2353 function make_message(function_name, description, error, substitutions) |
2167 { | 2354 { |
2168 for (var p in substitutions) { | 2355 for (var p in substitutions) { |
2169 if (substitutions.hasOwnProperty(p)) { | 2356 if (substitutions.hasOwnProperty(p)) { |
2170 substitutions[p] = format_value(substitutions[p]); | 2357 substitutions[p] = format_value(substitutions[p]); |
2171 } | 2358 } |
2172 } | 2359 } |
2173 var node_form = substitute(["{text}", "${function_name}: ${description}"
+ error], | 2360 var node_form = substitute(["{text}", "${function_name}: ${description}"
+ error], |
2174 merge({function_name:function_name, | 2361 merge({function_name:function_name, |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2225 } | 2412 } |
2226 for (p in b) { | 2413 for (p in b) { |
2227 rv[p] = b[p]; | 2414 rv[p] = b[p]; |
2228 } | 2415 } |
2229 return rv; | 2416 return rv; |
2230 } | 2417 } |
2231 | 2418 |
2232 function expose(object, name) | 2419 function expose(object, name) |
2233 { | 2420 { |
2234 var components = name.split("."); | 2421 var components = name.split("."); |
2235 var target = window; | 2422 var target = test_environment.global_scope(); |
2236 for (var i = 0; i < components.length - 1; i++) { | 2423 for (var i = 0; i < components.length - 1; i++) { |
2237 if (!(components[i] in target)) { | 2424 if (!(components[i] in target)) { |
2238 target[components[i]] = {}; | 2425 target[components[i]] = {}; |
2239 } | 2426 } |
2240 target = target[components[i]]; | 2427 target = target[components[i]]; |
2241 } | 2428 } |
2242 target[components[components.length - 1]] = object; | 2429 target[components[components.length - 1]] = object; |
2243 } | 2430 } |
2244 | 2431 |
2245 function forEach_windows(callback) { | |
2246 // Iterate of the the windows [self ... top, opener]. The callback is pa
ssed | |
2247 // two objects, the first one is the windows object itself, the second o
ne | |
2248 // is a boolean indicating whether or not its on the same origin as the | |
2249 // current window. | |
2250 var cache = forEach_windows.result_cache; | |
2251 if (!cache) { | |
2252 cache = [[self, true]]; | |
2253 var w = self; | |
2254 var i = 0; | |
2255 var so; | |
2256 var origins = location.ancestorOrigins; | |
2257 while (w != w.parent) { | |
2258 w = w.parent; | |
2259 // In WebKit, calls to parent windows' properties that aren't on
the same | |
2260 // origin cause an error message to be displayed in the error co
nsole but | |
2261 // don't throw an exception. This is a deviation from the curren
t HTML5 | |
2262 // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504 | |
2263 // The problem with WebKit's behavior is that it pollutes the er
ror console | |
2264 // with error messages that can't be caught. | |
2265 // | |
2266 // This issue can be mitigated by relying on the (for now) propr
ietary | |
2267 // `location.ancestorOrigins` property which returns an ordered
list of | |
2268 // the origins of enclosing windows. See: | |
2269 // http://trac.webkit.org/changeset/113945. | |
2270 if (origins) { | |
2271 so = (location.origin == origins[i]); | |
2272 } else { | |
2273 so = is_same_origin(w); | |
2274 } | |
2275 cache.push([w, so]); | |
2276 i++; | |
2277 } | |
2278 w = window.opener; | |
2279 if (w) { | |
2280 // window.opener isn't included in the `location.ancestorOrigins
` prop. | |
2281 // We'll just have to deal with a simple check and an error msg
on WebKit | |
2282 // browsers in this case. | |
2283 cache.push([w, is_same_origin(w)]); | |
2284 } | |
2285 forEach_windows.result_cache = cache; | |
2286 } | |
2287 | |
2288 forEach(cache, | |
2289 function(a) | |
2290 { | |
2291 callback.apply(null, a); | |
2292 }); | |
2293 } | |
2294 | |
2295 function is_same_origin(w) { | 2432 function is_same_origin(w) { |
2296 try { | 2433 try { |
2297 'random_prop' in w; | 2434 'random_prop' in w; |
2298 return true; | 2435 return true; |
2299 } catch (e) { | 2436 } catch (e) { |
2300 return false; | 2437 return false; |
2301 } | 2438 } |
2302 } | 2439 } |
2303 | 2440 |
2304 function supports_post_message(w) | 2441 function supports_post_message(w) |
(...skipping 25 matching lines...) Expand all Loading... |
2330 else { | 2467 else { |
2331 supports = false; | 2468 supports = false; |
2332 } | 2469 } |
2333 } catch (e) { | 2470 } catch (e) { |
2334 // This is the case where postMessage isn't supported AND accessing
a | 2471 // This is the case where postMessage isn't supported AND accessing
a |
2335 // window property across origins throws (e.g. old Firefox browser). | 2472 // window property across origins throws (e.g. old Firefox browser). |
2336 supports = false; | 2473 supports = false; |
2337 } | 2474 } |
2338 return supports; | 2475 return supports; |
2339 } | 2476 } |
| 2477 |
| 2478 /** |
| 2479 * Setup globals |
| 2480 */ |
| 2481 |
| 2482 var tests = new Tests(); |
| 2483 |
| 2484 addEventListener("error", function(e) { |
| 2485 if (tests.file_is_test) { |
| 2486 var test = tests.tests[0]; |
| 2487 if (test.phase >= test.phases.HAS_RESULT) { |
| 2488 return; |
| 2489 } |
| 2490 test.set_status(test.FAIL, e.message, e.stack); |
| 2491 test.phase = test.phases.HAS_RESULT; |
| 2492 test.done(); |
| 2493 done(); |
| 2494 } else if (!tests.allow_uncaught_exception) { |
| 2495 tests.status.status = tests.status.ERROR; |
| 2496 tests.status.message = e.message; |
| 2497 tests.status.stack = e.stack; |
| 2498 } |
| 2499 }); |
| 2500 |
| 2501 test_environment.on_tests_ready(); |
| 2502 |
2340 })(); | 2503 })(); |
2341 // vim: set expandtab shiftwidth=4 tabstop=4: | 2504 // vim: set expandtab shiftwidth=4 tabstop=4: |
OLD | NEW |