Files
sdk/pkg/test_runner/lib/src/test_controller.js
T
Robert Nystrom 99e8a9fba5 Move the test runner (i.e. "test.dart"/"test.py") to pkg/.
This makes it an actual Pub package like most other code inside the SDK
repo. The main goal is to make it easier to write tests for the test
runner itself.

This change:

- Moves all of the code from tools/testing/dart/ over to
  pkg/test_runner. Most of it ends up under test_runner/lib/src.

- Move tools/testing/dart/main.dart to
  pkg/test_runner/bin/test_runner.dart.

- Move standalone_2/io/test_runner_test.dart to
  pkg/test_runner/test/test_runner_test.dart. I don't think it currently
  works, but it wasn't being run in its old location either.

- Add test_runner to the analysis-server bot. This ensures the
  test_runner package is static error clean.

- Remove standalone_2/io/test_runner_analyze_test.dart which used to
  attempt to do the above and is no longer needed.

- Update test.py to look for the test runner at its new location.

- Add test_runner to the repo .packages file and remove the weird
  test_dart pseudo-package. (I think this fixes #35279.)

- Remove status file entries for the removed standalone_2 tests.

There are no code changes to the test runner itself aside from fixing
up import paths.

Change-Id: I3d05d50d222b291848fa5a30de2846e803bc81e6
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/105821
Commit-Queue: Bob Nystrom <rnystrom@google.com>
Reviewed-by: Jonas Termansen <sortie@google.com>
2019-06-14 23:35:10 +00:00

318 lines
9.8 KiB
JavaScript

// 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: pkg/test_runner/lib/src/browser.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');
}