| // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /* |
| * The communication protocol between test_controller.js and the driving |
| * page are JSON encoded messages of the following form: |
| * message = { |
| * is_first_message: true/false, |
| * is_status_update: true/false, |
| * is_done: true/false, |
| * message: message_content, |
| * } |
| * |
| * The first message should have [is_first_message] set, the last message |
| * should have [is_done] set. Status updates should have [is_status_update] set. |
| * |
| * The [message_content] can be be any content. In our case it will a list of |
| * events encoded in JSON. See the next comment further down about what an event |
| * is. |
| */ |
| |
| /* |
| * We will collect testing driver specific events here instead of printing |
| * them to the DOM. |
| * Every entry will look like this: |
| * { |
| * 'type' : 'sync_exception' / 'window_onerror' / 'script_onerror' / 'print' |
| * 'window_compilationerror' / 'message_received' / 'dom' / 'debug' |
| * 'value' : 'some content', |
| * 'timestamp' : TimestampInMs, |
| * } |
| * |
| * If the type is 'sync_exception', it will have an additional 'stack_trace' |
| * field whose value is a string. |
| */ |
| var recordedEventList = []; |
| var timestampOfFirstEvent = null; |
| |
| var STATUS_UPDATE_INTERVAL = 10000; |
| |
| function getCurrentTimestamp() { |
| if (timestampOfFirstEvent == null) { |
| timestampOfFirstEvent = new Date().getTime(); |
| } |
| return (new Date().getTime() - timestampOfFirstEvent) / 1000.0; |
| } |
| |
| function stringifyEvent(event) { |
| return JSON.stringify(event, null, 2); |
| } |
| |
| function recordEvent(type, value, stackTrace) { |
| var event = { |
| type: type, |
| value: value, |
| timestamp: getCurrentTimestamp() |
| }; |
| |
| if (stackTrace !== undefined) { |
| event['stack_trace'] = stackTrace; |
| } |
| |
| recordedEventList.push(event); |
| printToConsole(stringifyEvent(event)); |
| } |
| |
| function clearConsole() { |
| // Clear the console before every test run - this is Firebug specific code. |
| if (typeof console == 'object' && typeof console.clear == 'function') { |
| console.clear(); |
| } |
| } |
| |
| function printToConsole(message) { |
| var consoleAvailable = typeof console === 'object'; |
| |
| if (consoleAvailable) { |
| console.log(message); |
| } |
| } |
| |
| clearConsole(); |
| |
| // Some tests may expect and have no way to suppress global errors. |
| var testExpectsGlobalError = false; |
| var testSuppressedGlobalErrors = []; |
| |
| // Set window onerror to make sure that we catch test harness errors across all |
| // browsers. |
| window.onerror = function (message, url, lineNumber) { |
| if (url) { |
| message = ('window.onerror called: \n\n' + |
| url + ':' + lineNumber + ':\n' + message + '\n\n'); |
| } |
| if (testExpectsGlobalError) { |
| testSuppressedGlobalErrors.push({ |
| message: message |
| }); |
| return; |
| } |
| recordEvent('window_onerror', message); |
| notifyDone('FAIL'); |
| }; |
| |
| window.onunhandledrejection = function (e) { |
| var reason = e.reason != null ? e.reason.stack : null; |
| var message = ('window.onunhandledrejection called: \n\n' + reason + '\n\n'); |
| if (testExpectsGlobalError) { |
| testSuppressedGlobalErrors.push({message: message}); |
| return; |
| } |
| recordEvent('window_onerror', message); |
| notifyDone('FAIL'); |
| }; |
| |
| var waitForDone = false; |
| |
| var driverWindowCached = false; |
| var driverWindow; |
| var reportingDriverWindowError = false; |
| |
| // This can be set to a function that takes an error and produces a stringified |
| // stack trace for the error. DDC sets this to clean up its stack traces before |
| // reporting them. |
| var testErrorToStackTrace = null; |
| |
| // Returns the driving window object if available |
| // This function occasionally returns null instead of the |
| // parent on Android content shell, so we cache the value |
| // to get a consistent answer. |
| function getDriverWindow() { |
| if (window != window.parent) { |
| // We're running in an iframe. |
| result = window.parent; |
| } else if (window.opener) { |
| // We were opened by another window. |
| result = window.opener; |
| } else { |
| result = null; |
| } |
| if (driverWindowCached) { |
| if (result != driverWindow) { |
| recordEvent('debug', 'Driver windows changed: was null == ' + |
| (driverWindow == null) + ', is null == ' + (result == null)); |
| // notifyDone calls back into this function multiple times. Avoid loop. |
| if (!reportingDriverWindowError) { |
| reportingDriverWindowError = true; |
| notifyDone('FAIL'); |
| } |
| } |
| } else { |
| driverWindowCached = true; |
| driverWindow = result; |
| } |
| return driverWindow; |
| } |
| |
| function usingBrowserController() { |
| return getDriverWindow() != null; |
| } |
| |
| function buildDomEvent() { |
| return { |
| type: 'dom', |
| value: '' + window.document.documentElement.innerHTML, |
| timestamp: getCurrentTimestamp() |
| }; |
| } |
| |
| function notifyUpdate(testOutcome, isFirstMessage, isStatusUpdate, isDone) { |
| // If we are not using the browser controller (e.g. in the none-drt |
| // configuration), we need to print 'testOutcome' as it is. |
| if (isDone && !usingBrowserController()) { |
| printToConsole('Test outcome: ' + testOutcome); |
| } else if (usingBrowserController()) { |
| // To support in browser launching of tests we post back start and result |
| // messages to the window.opener. |
| var driver = getDriverWindow(); |
| |
| recordEvent('debug', 'Sending events to driver page (isFirstMessage = ' + |
| isFirstMessage + ', isStatusUpdate = ' + |
| isStatusUpdate + ', isDone = ' + isDone + ')'); |
| // Post the DOM and all events that happened. |
| var events = recordedEventList.slice(0); |
| events.push(buildDomEvent()); |
| |
| var message = JSON.stringify(events); |
| driver.postMessage( |
| JSON.stringify({ |
| message: message, |
| is_first_message: isFirstMessage, |
| is_status_update: isStatusUpdate, |
| is_done: isDone |
| }), '*'); |
| } |
| } |
| |
| function notifyDone(testOutcome) { |
| notifyUpdate(testOutcome, false, false, true); |
| } |
| |
| // Repeatedly send back the current status of this test. |
| function sendStatusUpdate(isFirstMessage) { |
| notifyUpdate('', isFirstMessage, true, false); |
| setTimeout(function() {sendStatusUpdate(false)}, STATUS_UPDATE_INTERVAL); |
| } |
| |
| // We call notifyStart here to notify the encapsulating browser. |
| recordEvent('debug', 'test_controller.js started'); |
| sendStatusUpdate(true); |
| |
| function processMessage(msg) { |
| // Filter out ShadowDOM polyfill messages which are random floats. |
| if (msg != parseFloat(msg)) { |
| recordEvent('message_received', '' + msg); |
| } |
| if (typeof msg != 'string') return; |
| if (msg == 'unittest-suite-wait-for-done') { |
| waitForDone = true; |
| } else if (msg == 'dart-main-done') { |
| if (!waitForDone) { |
| notifyDone('PASS'); |
| } |
| } else if (msg == 'unittest-suite-success' || |
| msg == 'unittest-suite-done') { |
| notifyDone('PASS'); |
| } else if (msg == 'unittest-suite-fail') { |
| notifyDone('FAIL'); |
| } |
| } |
| |
| function onReceive(e) { |
| processMessage(e.data); |
| } |
| |
| window.addEventListener('message', onReceive, false); |
| |
| function onLoad(e) { |
| // needed for dartium compilation errors. |
| if (window.compilationError) { |
| recordEvent('window_compilationerror', |
| 'DOMContentLoaded event: window.compilationError = ' + |
| calledwindow.compilationError); |
| notifyDone('FAIL'); |
| } |
| } |
| |
| window.addEventListener('DOMContentLoaded', onLoad, false); |
| |
| // Note: before renaming this function, note that it is also included in an |
| // inlined error handler in generated HTML files, and manually in tests that |
| // include an HTML file. |
| // See: tools/testing/dart/browser_test.dart |
| function scriptTagOnErrorCallback(e) { |
| var message = e && e.message; |
| recordEvent('script_onerror', 'script.onError called: ' + message); |
| notifyDone('FAIL'); |
| } |
| |
| // dart2js will generate code to call this function to handle the Dart |
| // [print] method. |
| // |
| // dartium will invoke this method for [print] calls if the environment variable |
| // "DART_FORWARDING_PRINT" was set when launching dartium. |
| // |
| // Our tests will be wrapped, so we can detect when [main] is called and when |
| // it has ended. |
| // The wrapping happens either via "dartMainRunner" (for dart2js) or wrapped |
| // tests for dartium. |
| // |
| // The following messages are handled specially: |
| // dart-calling-main: signals that the dart [main] function will be invoked |
| // dart-main-done: signals that the dart [main] function has finished |
| // unittest-suite-wait-for-done: signals the start of an asynchronous test |
| // unittest-suite-success: signals the end of an asynchronous test |
| // unittest-suite-fail: signals that the asynchronous test failed |
| // unittest-suite-done: signals the end of an asynchronous test, the outcome |
| // is unknown |
| // |
| // These messages are used to communicate with the test and will be posted so |
| // [processMessage] above can see it. |
| function dartPrint(message) { |
| recordEvent('print', message); |
| if ((message === 'unittest-suite-wait-for-done') || |
| (message === 'unittest-suite-success') || |
| (message === 'unittest-suite-fail') || |
| (message === 'unittest-suite-done') || |
| (message === 'dart-calling-main') || |
| (message === 'dart-main-done')) { |
| // We have to do this asynchronously, in case error messages are |
| // already in the message queue. |
| window.postMessage(message, '*'); |
| return; |
| } |
| } |
| |
| // dart2js will generate code to call this function instead of calling |
| // Dart [main] directly. The argument is a closure that invokes main. |
| function dartMainRunner(main) { |
| dartPrint('dart-calling-main'); |
| try { |
| main(); |
| } catch (error) { |
| var stack; |
| // TODO(rnystrom): Use this for script tag and window onerror calls. |
| if (testErrorToStackTrace) { |
| stack = testErrorToStackTrace(error); |
| } else { |
| stack = error.stack.toString(); |
| } |
| recordEvent('sync_exception', error.toString(), stack); |
| notifyDone('FAIL'); |
| return; |
| } |
| dartPrint('dart-main-done'); |
| } |