OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /* | |
6 * The communication protocol between test_controller.js and the driving | |
7 * page are JSON encoded messages of the following form: | |
8 * message = { | |
9 * is_first_message: true/false, | |
10 * is_status_update: true/false, | |
11 * is_done: true/false, | |
12 * message: message_content, | |
13 * } | |
14 * | |
15 * The first message should have [is_first_message] set, the last message | |
16 * should have [is_done] set. Status updates should have [is_status_update] set. | |
17 * | |
18 * The [message_content] can be be any content. In our case it will a list of | |
19 * events encoded in JSON. See the next comment further down about what an event | |
20 * is. | |
21 */ | |
22 | |
23 /* | |
24 * We will collect testing driver specific events here instead of printing | |
25 * them to the DOM. | |
26 * Every entry will look like this: | |
27 * { | |
28 * 'type' : 'sync_exception' / 'window_onerror' / 'script_onerror' / 'print' | |
29 * 'window_compilationerror' / 'message_received' / 'dom' / 'debug' | |
30 * 'value' : 'some content', | |
31 * 'timestamp' : TimestampInMs, | |
32 * } | |
33 */ | |
34 var recordedEventList = []; | |
35 var timestampOfFirstEvent = null; | |
36 | |
37 var STATUS_UPDATE_INTERVALL = 10000; | |
38 | |
39 function getCurrentTimestamp() { | |
40 if (timestampOfFirstEvent == null) { | |
41 timestampOfFirstEvent = new Date().getTime(); | |
42 } | |
43 return (new Date().getTime() - timestampOfFirstEvent) / 1000.0; | |
44 } | |
45 | |
46 function stringifyEvent(event) { | |
47 return JSON.stringify(event, null, 2); | |
48 } | |
49 | |
50 function recordEvent(type, value) { | |
51 var event = { | |
52 type: type, | |
53 value: value, | |
54 timestamp: getCurrentTimestamp() | |
55 }; | |
56 recordedEventList.push(event); | |
57 printToConsole(stringifyEvent(event)); | |
58 } | |
59 | |
60 function clearConsole() { | |
61 // Clear the console before every test run - this is Firebug specific code. | |
62 if (typeof console == 'object' && typeof console.clear == 'function') { | |
63 console.clear(); | |
64 } | |
65 } | |
66 | |
67 function printToDOM(message) { | |
68 var pre = document.createElement('pre'); | |
69 pre.appendChild(document.createTextNode(String(message))); | |
70 document.body.appendChild(pre); | |
71 document.body.appendChild(document.createTextNode('\n')); | |
72 } | |
73 | |
74 function printToConsole(message) { | |
75 var consoleAvailable = typeof console === 'object'; | |
76 | |
77 if (consoleAvailable) { | |
78 console.log(message); | |
79 } | |
80 } | |
81 | |
82 clearConsole(); | |
83 | |
84 // Some tests may expect and have no way to suppress global errors. | |
85 var testExpectsGlobalError = false; | |
86 var testSuppressedGlobalErrors = []; | |
87 | |
88 // Set window onerror to make sure that we catch test harness errors across all | |
89 // browsers. | |
90 window.onerror = function (message, url, lineNumber) { | |
91 if (url) { | |
92 message = ('window.onerror called: \n\n' + | |
93 url + ':' + lineNumber + ':\n' + message + '\n\n'); | |
94 } | |
95 if (testExpectsGlobalError) { | |
96 testSuppressedGlobalErrors.push({ | |
97 message: message | |
98 }); | |
99 return; | |
100 } | |
101 recordEvent('window_onerror', message); | |
102 notifyDone('FAIL'); | |
103 }; | |
104 | |
105 // testRunner is provided by content shell. | |
106 // It is not available in browser tests. | |
107 var testRunner = window.testRunner || window.layoutTestController; | |
108 var isContentShell = testRunner; | |
109 | |
110 var waitForDone = false; | |
111 | |
112 var driverWindowCached = false; | |
113 var driverWindow; | |
114 var reportingDriverWindowError = false; | |
115 | |
116 // Returns the driving window object if available | |
117 // This function occasionally returns null instead of the | |
118 // parent on Android content shell, so we cache the value | |
119 // to get a consistent answer. | |
120 function getDriverWindow() { | |
121 if (window != window.parent) { | |
122 // We're running in an iframe. | |
123 result = window.parent; | |
124 } else if (window.opener) { | |
125 // We were opened by another window. | |
126 result = window.opener; | |
127 } else { | |
128 result = null; | |
129 } | |
130 if (driverWindowCached) { | |
131 if (result != driverWindow) { | |
132 recordEvent('debug', 'Driver windows changed: was null == ' + | |
133 (driverWindow == null) + ', is null == ' + (result == null)); | |
134 // notifyDone calls back into this function multiple times. Avoid loop. | |
135 if (!reportingDriverWindowError) { | |
136 reportingDriverWindowError = true; | |
137 notifyDone('FAIL'); | |
138 } | |
139 } | |
140 } else { | |
141 driverWindowCached = true; | |
142 driverWindow = result; | |
143 } | |
144 return driverWindow; | |
145 } | |
146 | |
147 function usingBrowserController() { | |
148 return getDriverWindow() != null; | |
149 } | |
150 | |
151 function buildDomEvent() { | |
152 return { | |
153 type: 'dom', | |
154 value: '' + window.document.documentElement.innerHTML, | |
155 timestamp: getCurrentTimestamp() | |
156 }; | |
157 } | |
158 | |
159 function notifyUpdate(testOutcome, isFirstMessage, isStatusUpdate, isDone) { | |
160 // If we are not using the browser controller (e.g. in the none-drt | |
161 // configuration), we need to print 'testOutcome' as it is. | |
162 if (isDone && !usingBrowserController()) { | |
163 if (isContentShell) { | |
164 // We need this, since test.dart is looking for 'FAIL\n', 'PASS\n' in the | |
165 // DOM output of content shell. | |
166 printToDOM(testOutcome); | |
167 } else { | |
168 printToConsole('Test outcome: ' + testOutcome); | |
169 } | |
170 } else if (usingBrowserController()) { | |
171 // To support in browser launching of tests we post back start and result | |
172 // messages to the window.opener. | |
173 var driver = getDriverWindow(); | |
174 | |
175 recordEvent('debug', 'Sending events to driver page (isFirstMessage = ' + | |
176 isFirstMessage + ', isStatusUpdate = ' + | |
177 isStatusUpdate + ', isDone = ' + isDone + ')'); | |
178 // Post the DOM and all events that happened. | |
179 var events = recordedEventList.slice(0); | |
180 events.push(buildDomEvent()); | |
181 | |
182 var message = JSON.stringify(events); | |
183 driver.postMessage( | |
184 JSON.stringify({ | |
185 message: message, | |
186 is_first_message: isFirstMessage, | |
187 is_status_update: isStatusUpdate, | |
188 is_done: isDone | |
189 }), '*'); | |
190 } | |
191 if (isDone) { | |
192 if (testRunner) testRunner.notifyDone(); | |
193 } | |
194 } | |
195 | |
196 function notifyDone(testOutcome) { | |
197 notifyUpdate(testOutcome, false, false, true); | |
198 } | |
199 | |
200 // Repeatedly send back the current status of this test. | |
201 function sendStatusUpdate(isFirstMessage) { | |
202 notifyUpdate('', isFirstMessage, true, false); | |
203 setTimeout(function() {sendStatusUpdate(false)}, STATUS_UPDATE_INTERVALL); | |
204 } | |
205 | |
206 // We call notifyStart here to notify the encapsulating browser. | |
207 recordEvent('debug', 'test_controller.js started'); | |
208 sendStatusUpdate(true); | |
209 | |
210 function processMessage(msg) { | |
211 // Filter out ShadowDOM polyfill messages which are random floats. | |
212 if (msg != parseFloat(msg)) { | |
213 recordEvent('message_received', '' + msg); | |
214 } | |
215 if (typeof msg != 'string') return; | |
216 if (msg == 'unittest-suite-wait-for-done') { | |
217 waitForDone = true; | |
218 if (testRunner) { | |
219 testRunner.startedDartTest = true; | |
220 } | |
221 } else if (msg == 'dart-calling-main') { | |
222 if (testRunner) { | |
223 testRunner.startedDartTest = true; | |
224 } | |
225 } else if (msg == 'dart-main-done') { | |
226 if (!waitForDone) { | |
227 notifyDone('PASS'); | |
228 } | |
229 } else if (msg == 'unittest-suite-success' || | |
230 msg == 'unittest-suite-done') { | |
231 notifyDone('PASS'); | |
232 } else if (msg == 'unittest-suite-fail') { | |
233 notifyDone('FAIL'); | |
234 } | |
235 } | |
236 | |
237 function onReceive(e) { | |
238 processMessage(e.data); | |
239 } | |
240 | |
241 if (testRunner) { | |
242 testRunner.dumpAsText(); | |
243 testRunner.waitUntilDone(); | |
244 } | |
245 window.addEventListener('message', onReceive, false); | |
246 | |
247 function onLoad(e) { | |
248 // needed for dartium compilation errors. | |
249 if (window.compilationError) { | |
250 recordEvent('window_compilationerror', | |
251 'DOMContentLoaded event: window.compilationError = ' + | |
252 calledwindow.compilationError); | |
253 notifyDone('FAIL'); | |
254 } | |
255 } | |
256 | |
257 window.addEventListener('DOMContentLoaded', onLoad, false); | |
258 | |
259 // Note: before renaming this function, note that it is also included in an | |
260 // inlined error handler in generated HTML files, and manually in tests that | |
261 // include an HTML file. | |
262 // See: tools/testing/dart/browser_test.dart | |
263 function scriptTagOnErrorCallback(e) { | |
264 var message = e && e.message; | |
265 recordEvent('script_onerror', 'script.onError called: ' + message); | |
266 notifyDone('FAIL'); | |
267 } | |
268 | |
269 // dart2js will generate code to call this function to handle the Dart | |
270 // [print] method. | |
271 // | |
272 // dartium will invoke this method for [print] calls if the environment variable | |
273 // "DART_FORWARDING_PRINT" was set when launching dartium. | |
274 // | |
275 // Our tests will be wrapped, so we can detect when [main] is called and when | |
276 // it has ended. | |
277 // The wrapping happens either via "dartMainRunner" (for dart2js) or wrapped | |
278 // tests for dartium. | |
279 // | |
280 // The following messages are handled specially: | |
281 // dart-calling-main: signals that the dart [main] function will be invoked | |
282 // dart-main-done: signals that the dart [main] function has finished | |
283 // unittest-suite-wait-for-done: signals the start of an asynchronous test | |
284 // unittest-suite-success: signals the end of an asynchrounous test | |
285 // unittest-suite-fail: signals that the asynchronous test failed | |
286 // unittest-suite-done: signals the end of an asynchronous test, the outcome | |
287 // is unknown | |
288 // | |
289 // These messages are used to communicate with the test and will be posted so | |
290 // [processMessage] above can see it. | |
291 function dartPrint(message) { | |
292 recordEvent('print', message); | |
293 if ((message === 'unittest-suite-wait-for-done') || | |
294 (message === 'unittest-suite-success') || | |
295 (message === 'unittest-suite-fail') || | |
296 (message === 'unittest-suite-done') || | |
297 (message === 'dart-calling-main') || | |
298 (message === 'dart-main-done')) { | |
299 // We have to do this asynchronously, in case error messages are | |
300 // already in the message queue. | |
301 window.postMessage(message, '*'); | |
302 return; | |
303 } | |
304 } | |
305 | |
306 // dart2js will generate code to call this function instead of calling | |
307 // Dart [main] directly. The argument is a closure that invokes main. | |
308 function dartMainRunner(main) { | |
309 dartPrint('dart-calling-main'); | |
310 try { | |
311 main(); | |
312 } catch (e) { | |
313 recordEvent('sync_exception', 'Exception: ' + e + '\nStack: ' + e.stack); | |
314 notifyDone('FAIL'); | |
315 return; | |
316 } | |
317 dartPrint('dart-main-done'); | |
318 } | |
OLD | NEW |