| Index: tools/testing/dart/browser_controller.dart
|
| diff --git a/tools/testing/dart/browser_controller.dart b/tools/testing/dart/browser_controller.dart
|
| deleted file mode 100644
|
| index 01c5aec7c6f3ed1c8b097f2f3f063c3b381441b2..0000000000000000000000000000000000000000
|
| --- a/tools/testing/dart/browser_controller.dart
|
| +++ /dev/null
|
| @@ -1,1794 +0,0 @@
|
| -// 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.
|
| -library browser;
|
| -
|
| -import "dart:async";
|
| -import "dart:convert" show LineSplitter, UTF8, JSON;
|
| -import "dart:core";
|
| -import "dart:io";
|
| -
|
| -import 'android.dart';
|
| -import 'http_server.dart';
|
| -import 'path.dart';
|
| -import 'utils.dart';
|
| -
|
| -class BrowserOutput {
|
| - final StringBuffer stdout = new StringBuffer();
|
| - final StringBuffer stderr = new StringBuffer();
|
| - final StringBuffer eventLog = new StringBuffer();
|
| -}
|
| -
|
| -/** Class describing the interface for communicating with browsers. */
|
| -abstract class Browser {
|
| - BrowserOutput _allBrowserOutput = new BrowserOutput();
|
| - BrowserOutput _testBrowserOutput = new BrowserOutput();
|
| -
|
| - // This is called after the process is closed, before the done future
|
| - // is completed.
|
| - // Subclasses can use this to cleanup any browser specific resources
|
| - // (temp directories, profiles, etc). The function is expected to do
|
| - // it's work synchronously.
|
| - Function _cleanup;
|
| -
|
| - /** The version of the browser - normally set when starting a browser */
|
| - String version = "";
|
| -
|
| - // The path to the browser executable.
|
| - String _binary;
|
| -
|
| - /**
|
| - * The underlying process - don't mess directly with this if you don't
|
| - * know what you are doing (this is an interactive process that needs
|
| - * special treatment to not leak).
|
| - */
|
| - Process process;
|
| -
|
| - Function logger;
|
| -
|
| - /**
|
| - * Id of the browser
|
| - */
|
| - String id;
|
| -
|
| - /**
|
| - * Delete the browser specific caches on startup.
|
| - * Browser specific implementations are free to ignore this.
|
| - */
|
| - static bool deleteCache = false;
|
| -
|
| - /** Print everything (stdout, stderr, usageLog) whenever we add to it */
|
| - bool debugPrint = false;
|
| -
|
| - // This future returns when the process exits. It is also the return value
|
| - // of close()
|
| - Future done;
|
| -
|
| - Browser();
|
| -
|
| - factory Browser.byName(String name,
|
| - String executablePath,
|
| - [bool checkedMode = false]) {
|
| - var browser;
|
| - if (name == 'firefox') {
|
| - browser = new Firefox();
|
| - } else if (name == 'chrome') {
|
| - browser = new Chrome();
|
| - } else if (name == 'dartium') {
|
| - browser = new Dartium(checkedMode);
|
| - } else if (name == 'safari') {
|
| - browser = new Safari();
|
| - } else if (name == 'safarimobilesim') {
|
| - browser = new SafariMobileSimulator();
|
| - } else if (name.startsWith('ie')) {
|
| - browser = new IE();
|
| - } else {
|
| - throw "Non supported browser";
|
| - }
|
| - browser._binary = executablePath;
|
| - return browser;
|
| - }
|
| -
|
| - static const List<String> SUPPORTED_BROWSERS =
|
| - const ['safari', 'ff', 'firefox', 'chrome', 'ie9', 'ie10',
|
| - 'ie11', 'dartium'];
|
| -
|
| - static const List<String> BROWSERS_WITH_WINDOW_SUPPORT =
|
| - const ['ie11', 'ie10'];
|
| -
|
| - // TODO(kustermann): add standard support for chrome on android
|
| - static bool supportedBrowser(String name) {
|
| - return SUPPORTED_BROWSERS.contains(name);
|
| - }
|
| -
|
| - void _logEvent(String event) {
|
| - String toLog = "$this ($id) - $event \n";
|
| - if (debugPrint) print("usageLog: $toLog");
|
| - if (logger != null) logger(toLog);
|
| -
|
| - _allBrowserOutput.eventLog.write(toLog);
|
| - _testBrowserOutput.eventLog.write(toLog);
|
| - }
|
| -
|
| - void _addStdout(String output) {
|
| - if (debugPrint) print("stdout: $output");
|
| -
|
| - _allBrowserOutput.stdout.write(output);
|
| - _testBrowserOutput.stdout.write(output);
|
| - }
|
| -
|
| - void _addStderr(String output) {
|
| - if (debugPrint) print("stderr: $output");
|
| -
|
| - _allBrowserOutput.stderr.write(output);
|
| - _testBrowserOutput.stderr.write(output);
|
| - }
|
| -
|
| - Future close() {
|
| - _logEvent("Close called on browser");
|
| - if (process != null) {
|
| - if (process.kill(ProcessSignal.SIGKILL)) {
|
| - _logEvent("Successfully sent kill signal to process.");
|
| - } else {
|
| - _logEvent("Sending kill signal failed.");
|
| - }
|
| - return done;
|
| - } else {
|
| - _logEvent("The process is already dead.");
|
| - return new Future.value(true);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Start the browser using the supplied argument.
|
| - * This sets up the error handling and usage logging.
|
| - */
|
| - Future<bool> startBrowser(String command,
|
| - List<String> arguments,
|
| - {Map<String,String> environment}) {
|
| - return Process.start(command, arguments, environment: environment)
|
| - .then((startedProcess) {
|
| - process = startedProcess;
|
| - // Used to notify when exiting, and as a return value on calls to
|
| - // close().
|
| - var doneCompleter = new Completer();
|
| - done = doneCompleter.future;
|
| -
|
| - Completer stdoutDone = new Completer();
|
| - Completer stderrDone = new Completer();
|
| -
|
| - bool stdoutIsDone = false;
|
| - bool stderrIsDone = false;
|
| - StreamSubscription stdoutSubscription;
|
| - StreamSubscription stderrSubscription;
|
| -
|
| - // This timer is used to close stdio to the subprocess once we got
|
| - // the exitCode. Sometimes descendants of the subprocess keep stdio
|
| - // handles alive even though the direct subprocess is dead.
|
| - Timer watchdogTimer;
|
| -
|
| - void closeStdout([_]){
|
| - if (!stdoutIsDone) {
|
| - stdoutDone.complete();
|
| - stdoutIsDone = true;
|
| -
|
| - if (stderrIsDone && watchdogTimer != null) {
|
| - watchdogTimer.cancel();
|
| - }
|
| - }
|
| - }
|
| -
|
| - void closeStderr([_]) {
|
| - if (!stderrIsDone) {
|
| - stderrDone.complete();
|
| - stderrIsDone = true;
|
| -
|
| - if (stdoutIsDone && watchdogTimer != null) {
|
| - watchdogTimer.cancel();
|
| - }
|
| - }
|
| - }
|
| -
|
| - stdoutSubscription =
|
| - process.stdout.transform(UTF8.decoder).listen((data) {
|
| - _addStdout(data);
|
| - }, onError: (error) {
|
| - // This should _never_ happen, but we really want this in the log
|
| - // if it actually does due to dart:io or vm bug.
|
| - _logEvent("An error occured in the process stdout handling: $error");
|
| - }, onDone: closeStdout);
|
| -
|
| - stderrSubscription =
|
| - process.stderr.transform(UTF8.decoder).listen((data) {
|
| - _addStderr(data);
|
| - }, onError: (error) {
|
| - // This should _never_ happen, but we really want this in the log
|
| - // if it actually does due to dart:io or vm bug.
|
| - _logEvent("An error occured in the process stderr handling: $error");
|
| - }, onDone: closeStderr);
|
| -
|
| - process.exitCode.then((exitCode) {
|
| - _logEvent("Browser closed with exitcode $exitCode");
|
| -
|
| - if (!stdoutIsDone || !stderrIsDone) {
|
| - watchdogTimer = new Timer(MAX_STDIO_DELAY, () {
|
| - DebugLogger.warning(
|
| - "$MAX_STDIO_DELAY_PASSED_MESSAGE (browser: $this)");
|
| - watchdogTimer = null;
|
| - stdoutSubscription.cancel();
|
| - stderrSubscription.cancel();
|
| - closeStdout();
|
| - closeStderr();
|
| - });
|
| - }
|
| -
|
| - Future.wait([stdoutDone.future, stderrDone.future]).then((_) {
|
| - process = null;
|
| - if (_cleanup != null) {
|
| - _cleanup();
|
| - }
|
| - }).catchError((error) {
|
| - _logEvent("Error closing browsers: $error");
|
| - }).whenComplete(() => doneCompleter.complete(true));
|
| - });
|
| - return true;
|
| - }).catchError((error) {
|
| - _logEvent("Running $command $arguments failed with $error");
|
| - return false;
|
| - });
|
| - }
|
| -
|
| - /**
|
| - * Get the output that was written so far to stdout/stderr/eventLog.
|
| - */
|
| - BrowserOutput get allBrowserOutput => _allBrowserOutput;
|
| - BrowserOutput get testBrowserOutput => _testBrowserOutput;
|
| -
|
| - void resetTestBrowserOutput() {
|
| - _testBrowserOutput = new BrowserOutput();
|
| - }
|
| -
|
| - /**
|
| - * Add useful info about the browser to the _testBrowserOutput.stdout,
|
| - * where it will be reported for failing tests. Used to report which
|
| - * android device a failing test is running on.
|
| - */
|
| - void logBrowserInfoToTestBrowserOutput() { }
|
| -
|
| - String toString();
|
| -
|
| - /** Starts the browser loading the given url */
|
| - Future<bool> start(String url);
|
| -}
|
| -
|
| -class Safari extends Browser {
|
| - /**
|
| - * We get the safari version by parsing a version file
|
| - */
|
| - static const String versionFile =
|
| - "/Applications/Safari.app/Contents/version.plist";
|
| -
|
| - /**
|
| - * Directories where safari stores state. We delete these if the deleteCache
|
| - * is set
|
| - */
|
| - static const List<String> CACHE_DIRECTORIES =
|
| - const ["Library/Caches/com.apple.Safari",
|
| - "Library/Safari",
|
| - "Library/Saved Application State/com.apple.Safari.savedState",
|
| - "Library/Caches/Metadata/Safari"];
|
| -
|
| -
|
| - Future<bool> allowPopUps() {
|
| - var command = "defaults";
|
| - var args = ["write", "com.apple.safari",
|
| - "com.apple.Safari.ContentPageGroupIdentifier."
|
| - "WebKit2JavaScriptCanOpenWindowsAutomatically",
|
| - "1"];
|
| - return Process.run(command, args).then((result) {
|
| - if (result.exitCode != 0) {
|
| - _logEvent("Could not disable pop-up blocking for safari");
|
| - return false;
|
| - }
|
| - return true;
|
| - });
|
| - }
|
| -
|
| - Future<bool> deleteIfExists(Iterator<String> paths) {
|
| - if (!paths.moveNext()) return new Future.value(true);
|
| - Directory directory = new Directory(paths.current);
|
| - return directory.exists().then((exists) {
|
| - if (exists) {
|
| - _logEvent("Deleting ${paths.current}");
|
| - return directory.delete(recursive: true)
|
| - .then((_) => deleteIfExists(paths))
|
| - .catchError((error) {
|
| - _logEvent("Failure trying to delete ${paths.current}: $error");
|
| - return false;
|
| - });
|
| - } else {
|
| - _logEvent("${paths.current} is not present");
|
| - return deleteIfExists(paths);
|
| - }
|
| - });
|
| - }
|
| -
|
| - // Clears the cache if the static deleteCache flag is set.
|
| - // Returns false if the command to actually clear the cache did not complete.
|
| - Future<bool> clearCache() {
|
| - if (!Browser.deleteCache) return new Future.value(true);
|
| - var home = Platform.environment['HOME'];
|
| - Iterator iterator = CACHE_DIRECTORIES.map((s) => "$home/$s").iterator;
|
| - return deleteIfExists(iterator);
|
| - }
|
| -
|
| - Future<String> getVersion() {
|
| - /**
|
| - * Example of the file:
|
| - * <?xml version="1.0" encoding="UTF-8"?>
|
| - * <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
| - * <plist version="1.0">
|
| - * <dict>
|
| - * <key>BuildVersion</key>
|
| - * <string>2</string>
|
| - * <key>CFBundleShortVersionString</key>
|
| - * <string>6.0.4</string>
|
| - * <key>CFBundleVersion</key>
|
| - * <string>8536.29.13</string>
|
| - * <key>ProjectName</key>
|
| - * <string>WebBrowser</string>
|
| - * <key>SourceVersion</key>
|
| - * <string>7536029013000000</string>
|
| - * </dict>
|
| - * </plist>
|
| - */
|
| - File f = new File(versionFile);
|
| - return f.readAsLines().then((content) {
|
| - bool versionOnNextLine = false;
|
| - for (var line in content) {
|
| - if (versionOnNextLine) return line;
|
| - if (line.contains("CFBundleShortVersionString")) {
|
| - versionOnNextLine = true;
|
| - }
|
| - }
|
| - return null;
|
| - });
|
| - }
|
| -
|
| - void _createLaunchHTML(var path, var url) {
|
| - var file = new File("${path}/launch.html");
|
| - var randomFile = file.openSync(mode: FileMode.WRITE);
|
| - var content = '<script language="JavaScript">location = "$url"</script>';
|
| - randomFile.writeStringSync(content);
|
| - randomFile.close();
|
| - }
|
| -
|
| - Future<bool> start(String url) {
|
| - _logEvent("Starting Safari browser on: $url");
|
| - return allowPopUps().then((success) {
|
| - if (!success) {
|
| - return false;
|
| - }
|
| - return clearCache().then((cleared) {
|
| - if (!cleared) {
|
| - _logEvent("Could not clear cache");
|
| - return false;
|
| - }
|
| - // Get the version and log that.
|
| - return getVersion().then((version) {
|
| - _logEvent("Got version: $version");
|
| - return Directory.systemTemp.createTemp().then((userDir) {
|
| - _cleanup = () { userDir.deleteSync(recursive: true); };
|
| - _createLaunchHTML(userDir.path, url);
|
| - var args = ["${userDir.path}/launch.html"];
|
| - return startBrowser(_binary, args);
|
| - });
|
| - }).catchError((error) {
|
| - _logEvent("Running $_binary --version failed with $error");
|
| - return false;
|
| - });
|
| - });
|
| - });
|
| - }
|
| -
|
| - String toString() => "Safari";
|
| -}
|
| -
|
| -
|
| -class Chrome extends Browser {
|
| - String _version = "Version not found yet";
|
| -
|
| - Map<String, String> _getEnvironment() => null;
|
| -
|
| - Future<bool> _getVersion() {
|
| - if (Platform.isWindows) {
|
| - // The version flag does not work on windows.
|
| - // See issue:
|
| - // https://code.google.com/p/chromium/issues/detail?id=158372
|
| - // The registry hack does not seem to work.
|
| - _version = "Can't get version on windows";
|
| - // We still validate that the binary exists so that we can give good
|
| - // feedback.
|
| - return new File(_binary).exists().then((exists) {
|
| - if (!exists) {
|
| - _logEvent("Chrome binary not available.");
|
| - _logEvent("Make sure $_binary is a valid program for running chrome");
|
| - }
|
| - return exists;
|
| - });
|
| - }
|
| - return Process.run(_binary, ["--version"]).then((var versionResult) {
|
| - if (versionResult.exitCode != 0) {
|
| - _logEvent("Failed to chrome get version");
|
| - _logEvent("Make sure $_binary is a valid program for running chrome");
|
| - return false;
|
| - }
|
| - _version = versionResult.stdout;
|
| - return true;
|
| - });
|
| - }
|
| -
|
| -
|
| - Future<bool> start(String url) {
|
| - _logEvent("Starting chrome browser on: $url");
|
| - // Get the version and log that.
|
| - return _getVersion().then((success) {
|
| - if (!success) return false;
|
| - _logEvent("Got version: $_version");
|
| -
|
| - return Directory.systemTemp.createTemp().then((userDir) {
|
| - _cleanup = () { userDir.deleteSync(recursive: true); };
|
| - var args = ["--user-data-dir=${userDir.path}", url,
|
| - "--disable-extensions", "--disable-popup-blocking",
|
| - "--bwsi", "--no-first-run"];
|
| - return startBrowser(_binary, args, environment: _getEnvironment());
|
| - });
|
| - }).catchError((e) {
|
| - _logEvent("Running $_binary --version failed with $e");
|
| - return false;
|
| - });
|
| - }
|
| -
|
| - String toString() => "Chrome";
|
| -}
|
| -
|
| -
|
| -class SafariMobileSimulator extends Safari {
|
| - /**
|
| - * Directories where safari simulator stores state. We delete these if the
|
| - * deleteCache is set
|
| - */
|
| - static const List<String> CACHE_DIRECTORIES =
|
| - const ["Library/Application Support/iPhone Simulator/7.1/Applications"];
|
| -
|
| - // Clears the cache if the static deleteCache flag is set.
|
| - // Returns false if the command to actually clear the cache did not complete.
|
| - Future<bool> clearCache() {
|
| - if (!Browser.deleteCache) return new Future.value(true);
|
| - var home = Platform.environment['HOME'];
|
| - Iterator iterator = CACHE_DIRECTORIES.map((s) => "$home/$s").iterator;
|
| - return deleteIfExists(iterator);
|
| - }
|
| -
|
| - Future<bool> start(String url) {
|
| - _logEvent("Starting safari mobile simulator browser on: $url");
|
| - return clearCache().then((success) {
|
| - if (!success) {
|
| - _logEvent("Could not clear cache, exiting");
|
| - return false;
|
| - }
|
| - var args = ["-SimulateApplication",
|
| - "/Applications/Xcode.app/Contents/Developer/Platforms/"
|
| - "iPhoneSimulator.platform/Developer/SDKs/"
|
| - "iPhoneSimulator7.1.sdk/Applications/MobileSafari.app/"
|
| - "MobileSafari",
|
| - "-u", url];
|
| - return startBrowser(_binary, args)
|
| - .catchError((e) {
|
| - _logEvent("Running $_binary --version failed with $e");
|
| - return false;
|
| - });
|
| - });
|
| - }
|
| -
|
| - String toString() => "SafariMobileSimulator";
|
| -}
|
| -
|
| -
|
| -class Dartium extends Chrome {
|
| - final bool checkedMode;
|
| -
|
| - Dartium(this.checkedMode);
|
| -
|
| - Map<String, String> _getEnvironment() {
|
| - var environment = new Map<String,String>.from(Platform.environment);
|
| - // By setting this environment variable, dartium will forward "print()"
|
| - // calls in dart to the top-level javascript function "dartPrint()" if
|
| - // available.
|
| - environment['DART_FORWARDING_PRINT'] = '1';
|
| - if (checkedMode) {
|
| - environment['DART_FLAGS'] = '--checked';
|
| - }
|
| - return environment;
|
| - }
|
| -
|
| - String toString() => "Dartium";
|
| -}
|
| -
|
| -class IE extends Browser {
|
| - Future<String> getVersion() {
|
| - var args = ["query",
|
| - "HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Internet Explorer",
|
| - "/v",
|
| - "svcVersion"];
|
| - return Process.run("reg", args).then((result) {
|
| - if (result.exitCode == 0) {
|
| - // The string we get back looks like this:
|
| - // HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer
|
| - // version REG_SZ 9.0.8112.16421
|
| - var findString = "REG_SZ";
|
| - var index = result.stdout.indexOf(findString);
|
| - if (index > 0) {
|
| - return result.stdout.substring(index + findString.length).trim();
|
| - }
|
| - }
|
| - return "Could not get the version of internet explorer";
|
| - });
|
| - }
|
| -
|
| - // Clears the recovery cache if the static deleteCache flag is set.
|
| - Future<bool> clearCache() {
|
| - if (!Browser.deleteCache) return new Future.value(true);
|
| - var localAppData = Platform.environment['LOCALAPPDATA'];
|
| -
|
| - Directory dir = new Directory("$localAppData\\Microsoft\\"
|
| - "Internet Explorer\\Recovery");
|
| - return dir.delete(recursive: true)
|
| - .then((_) { return true; })
|
| - .catchError((error) {
|
| - _logEvent("Deleting recovery dir failed with $error");
|
| - return false;
|
| - });
|
| - }
|
| -
|
| - Future<bool> start(String url) {
|
| - _logEvent("Starting ie browser on: $url");
|
| - return clearCache().then((_) => getVersion()).then((version) {
|
| - _logEvent("Got version: $version");
|
| - return startBrowser(_binary, [url]);
|
| - });
|
| - }
|
| - String toString() => "IE";
|
| -
|
| -}
|
| -
|
| -
|
| -class AndroidBrowserConfig {
|
| - final String name;
|
| - final String package;
|
| - final String activity;
|
| - final String action;
|
| - AndroidBrowserConfig(this.name, this.package, this.activity, this.action);
|
| -}
|
| -
|
| -
|
| -final contentShellOnAndroidConfig = new AndroidBrowserConfig(
|
| - 'ContentShellOnAndroid',
|
| - 'org.chromium.content_shell_apk',
|
| - '.ContentShellActivity',
|
| - 'android.intent.action.VIEW');
|
| -
|
| -
|
| -final dartiumOnAndroidConfig = new AndroidBrowserConfig(
|
| - 'DartiumOnAndroid',
|
| - 'com.google.android.apps.chrome',
|
| - '.Main',
|
| - 'android.intent.action.VIEW');
|
| -
|
| -
|
| -class AndroidBrowser extends Browser {
|
| - final bool checkedMode;
|
| - AdbDevice _adbDevice;
|
| - AndroidBrowserConfig _config;
|
| -
|
| - AndroidBrowser(this._adbDevice, this._config, this.checkedMode, apkPath) {
|
| - _binary = apkPath;
|
| - }
|
| -
|
| - Future<bool> start(String url) {
|
| - var intent = new Intent(
|
| - _config.action, _config.package, _config.activity, url);
|
| - return _adbDevice.waitForBootCompleted().then((_) {
|
| - return _adbDevice.forceStop(_config.package);
|
| - }).then((_) {
|
| - return _adbDevice.killAll();
|
| - }).then((_) {
|
| - return _adbDevice.adbRoot();
|
| - }).then((_) {
|
| - return _adbDevice.setProp("DART_FORWARDING_PRINT", "1");
|
| - }).then((_) {
|
| - if (checkedMode) {
|
| - return _adbDevice.setProp("DART_FLAGS", "--checked");
|
| - } else {
|
| - return _adbDevice.setProp("DART_FLAGS", "");
|
| - }
|
| - }).then((_) {
|
| - return _adbDevice.installApk(new Path(_binary));
|
| - }).then((_) {
|
| - return _adbDevice.startActivity(intent).then((_) => true);
|
| - });
|
| - }
|
| -
|
| - Future<bool> close() {
|
| - if (_adbDevice != null) {
|
| - return _adbDevice.forceStop(_config.package).then((_) {
|
| - return _adbDevice.killAll().then((_) => true);
|
| - });
|
| - }
|
| - return new Future.value(true);
|
| - }
|
| -
|
| - void logBrowserInfoToTestBrowserOutput() {
|
| - _testBrowserOutput.stdout.write(
|
| - 'Android device id: ${_adbDevice.deviceId}\n');
|
| - }
|
| -
|
| - String toString() => _config.name;
|
| -}
|
| -
|
| -
|
| -class AndroidChrome extends Browser {
|
| - static const String viewAction = 'android.intent.action.VIEW';
|
| - static const String mainAction = 'android.intent.action.MAIN';
|
| - static const String chromePackage = 'com.android.chrome';
|
| - static const String browserPackage = 'com.android.browser';
|
| - static const String firefoxPackage = 'org.mozilla.firefox';
|
| - static const String turnScreenOnPackage = 'com.google.dart.turnscreenon';
|
| -
|
| - AndroidEmulator _emulator;
|
| - AdbDevice _adbDevice;
|
| -
|
| - AndroidChrome(this._adbDevice);
|
| -
|
| - Future<bool> start(String url) {
|
| - var browserIntent = new Intent(
|
| - viewAction, browserPackage, '.BrowserActivity', url);
|
| - var chromeIntent = new Intent(viewAction, chromePackage, '.Main', url);
|
| - var firefoxIntent = new Intent(viewAction, firefoxPackage, '.App', url);
|
| - var turnScreenOnIntent =
|
| - new Intent(mainAction, turnScreenOnPackage, '.Main');
|
| -
|
| - var testing_resources_dir =
|
| - new Path('third_party/android_testing_resources');
|
| - if (!new Directory(testing_resources_dir.toNativePath()).existsSync()) {
|
| - DebugLogger.error("$testing_resources_dir doesn't exist. Exiting now.");
|
| - exit(1);
|
| - }
|
| -
|
| - var chromeAPK = testing_resources_dir.append('com.android.chrome-1.apk');
|
| - var turnScreenOnAPK = testing_resources_dir.append('TurnScreenOn.apk');
|
| - var chromeConfDir = testing_resources_dir.append('chrome_configuration');
|
| - var chromeConfDirRemote = new Path('/data/user/0/com.android.chrome/');
|
| -
|
| - return _adbDevice.waitForBootCompleted().then((_) {
|
| - return _adbDevice.forceStop(chromeIntent.package);
|
| - }).then((_) {
|
| - return _adbDevice.killAll();
|
| - }).then((_) {
|
| - return _adbDevice.adbRoot();
|
| - }).then((_) {
|
| - return _adbDevice.installApk(turnScreenOnAPK);
|
| - }).then((_) {
|
| - return _adbDevice.installApk(chromeAPK);
|
| - }).then((_) {
|
| - return _adbDevice.pushData(chromeConfDir, chromeConfDirRemote);
|
| - }).then((_) {
|
| - return _adbDevice.chmod('777', chromeConfDirRemote);
|
| - }).then((_) {
|
| - return _adbDevice.startActivity(turnScreenOnIntent).then((_) => true);
|
| - }).then((_) {
|
| - return _adbDevice.startActivity(chromeIntent).then((_) => true);
|
| - });
|
| - }
|
| -
|
| - Future<bool> close() {
|
| - if (_adbDevice != null) {
|
| - return _adbDevice.forceStop(chromePackage).then((_) {
|
| - return _adbDevice.killAll().then((_) => true);
|
| - });
|
| - }
|
| - return new Future.value(true);
|
| - }
|
| -
|
| - void logBrowserInfoToTestBrowserOutput() {
|
| - _testBrowserOutput.stdout.write(
|
| - 'Android device id: ${_adbDevice.deviceId}\n');
|
| - }
|
| -
|
| - String toString() => "chromeOnAndroid";
|
| -}
|
| -
|
| -
|
| -class Firefox extends Browser {
|
| - static const String enablePopUp =
|
| - 'user_pref("dom.disable_open_during_load", false);';
|
| - static const String disableDefaultCheck =
|
| - 'user_pref("browser.shell.checkDefaultBrowser", false);';
|
| - static const String disableScriptTimeLimit =
|
| - 'user_pref("dom.max_script_run_time", 0);';
|
| -
|
| - void _createPreferenceFile(var path) {
|
| - var file = new File("${path.toString()}/user.js");
|
| - var randomFile = file.openSync(mode: FileMode.WRITE);
|
| - randomFile.writeStringSync(enablePopUp);
|
| - randomFile.writeStringSync(disableDefaultCheck);
|
| - randomFile.writeStringSync(disableScriptTimeLimit);
|
| - randomFile.close();
|
| - }
|
| -
|
| - Future<bool> start(String url) {
|
| - _logEvent("Starting firefox browser on: $url");
|
| - // Get the version and log that.
|
| - return Process.run(_binary, ["--version"]).then((var versionResult) {
|
| - if (versionResult.exitCode != 0) {
|
| - _logEvent("Failed to firefox get version");
|
| - _logEvent("Make sure $_binary is a valid program for running firefox");
|
| - return new Future.value(false);
|
| - }
|
| - version = versionResult.stdout;
|
| - _logEvent("Got version: $version");
|
| -
|
| - return Directory.systemTemp.createTemp().then((userDir) {
|
| - _createPreferenceFile(userDir.path);
|
| - _cleanup = () { userDir.deleteSync(recursive: true); };
|
| - var args = ["-profile", "${userDir.path}",
|
| - "-no-remote", "-new-instance", url];
|
| - return startBrowser(_binary, args);
|
| -
|
| - });
|
| - }).catchError((e) {
|
| - _logEvent("Running $_binary --version failed with $e");
|
| - return false;
|
| - });
|
| - }
|
| -
|
| - String toString() => "Firefox";
|
| -}
|
| -
|
| -
|
| -/**
|
| - * Describes the current state of a browser used for testing.
|
| - */
|
| -class BrowserTestingStatus {
|
| - Browser browser;
|
| - BrowserTest currentTest;
|
| -
|
| - // This is currently not used for anything except for error reporting.
|
| - // Given the usefulness of this in debugging issues this should not be
|
| - // removed even when we have a really stable system.
|
| - BrowserTest lastTest;
|
| - bool timeout = false;
|
| - Timer nextTestTimeout;
|
| - Stopwatch timeSinceRestart = new Stopwatch();
|
| -
|
| - BrowserTestingStatus(Browser this.browser);
|
| -}
|
| -
|
| -
|
| -/**
|
| - * Describes a single test to be run in the browser.
|
| - */
|
| -class BrowserTest {
|
| - // TODO(ricow): Add timeout callback instead of the string passing hack.
|
| - Function doneCallback;
|
| - String url;
|
| - int timeout;
|
| - String lastKnownMessage = '';
|
| - Stopwatch stopwatch;
|
| -
|
| - // This might be null
|
| - Duration delayUntilTestStarted;
|
| -
|
| - // We store this here for easy access when tests time out (instead of
|
| - // capturing this in a closure)
|
| - Timer timeoutTimer;
|
| -
|
| - // Used for debugging, this is simply a unique identifier assigned to each
|
| - // test.
|
| - int id;
|
| - static int _idCounter = 0;
|
| -
|
| - BrowserTest(this.url, this.doneCallback, this.timeout) {
|
| - id = _idCounter++;
|
| - }
|
| -
|
| - String toJSON() => JSON.encode({'url': url,
|
| - 'id': id,
|
| - 'isHtmlTest': false});
|
| -}
|
| -
|
| -
|
| -/**
|
| - * Describes a test with a custom HTML page to be run in the browser.
|
| - */
|
| -class HtmlTest extends BrowserTest {
|
| - List<String> expectedMessages;
|
| -
|
| - HtmlTest(url, doneCallback, timeout, this.expectedMessages)
|
| - : super(url, doneCallback, timeout) { }
|
| -
|
| - String toJSON() => JSON.encode({'url': url,
|
| - 'id': id,
|
| - 'isHtmlTest': true,
|
| - 'expectedMessages': expectedMessages});
|
| -}
|
| -
|
| -
|
| -/* Describes the output of running the test in a browser */
|
| -class BrowserTestOutput {
|
| - final Duration delayUntilTestStarted;
|
| - final Duration duration;
|
| -
|
| - final String lastKnownMessage;
|
| -
|
| - final BrowserOutput browserOutput;
|
| - final bool didTimeout;
|
| -
|
| - BrowserTestOutput(
|
| - this.delayUntilTestStarted, this.duration, this.lastKnownMessage,
|
| - this.browserOutput, {this.didTimeout: false});
|
| -}
|
| -
|
| -/**
|
| - * Encapsulates all the functionality for running tests in browsers.
|
| - * The interface is rather simple. After starting, the runner tests
|
| - * are simply added to the queue and a the supplied callbacks are called
|
| - * whenever a test completes.
|
| - */
|
| -class BrowserTestRunner {
|
| - static const int MAX_NEXT_TEST_TIMEOUTS = 10;
|
| - static const Duration NEXT_TEST_TIMEOUT = const Duration(seconds: 60);
|
| - static const Duration RESTART_BROWSER_INTERVAL = const Duration(seconds: 60);
|
| -
|
| - final Map configuration;
|
| -
|
| - final String localIp;
|
| - String browserName;
|
| - final int maxNumBrowsers;
|
| - bool checkedMode;
|
| - // Used to send back logs from the browser (start, stop etc)
|
| - Function logger;
|
| - int browserIdCount = 0;
|
| -
|
| - bool underTermination = false;
|
| - int numBrowserGetTestTimeouts = 0;
|
| -
|
| - List<BrowserTest> testQueue = new List<BrowserTest>();
|
| - Map<String, BrowserTestingStatus> browserStatus =
|
| - new Map<String, BrowserTestingStatus>();
|
| -
|
| - var adbDeviceMapping = new Map<String, AdbDevice>();
|
| - // This cache is used to guarantee that we never see double reporting.
|
| - // If we do we need to provide developers with this information.
|
| - // We don't add urls to the cache until we have run it.
|
| - Map<int, String> testCache = new Map<int, String>();
|
| - Map<int, String> doubleReportingOutputs = new Map<int, String>();
|
| -
|
| - BrowserTestingServer testingServer;
|
| -
|
| - /**
|
| - * The TestRunner takes the testingServer in as a constructor parameter in
|
| - * case we wish to have a testing server with different behavior (such as the
|
| - * case for performance testing.
|
| - */
|
| - BrowserTestRunner(this.configuration,
|
| - this.localIp,
|
| - this.browserName,
|
| - this.maxNumBrowsers,
|
| - {BrowserTestingServer this.testingServer}) {
|
| - checkedMode = configuration['checked'];
|
| - }
|
| -
|
| - Future<bool> start() {
|
| - // If [browserName] doesn't support opening new windows, we use new iframes
|
| - // instead.
|
| - bool useIframe =
|
| - !Browser.BROWSERS_WITH_WINDOW_SUPPORT.contains(browserName);
|
| - if (testingServer == null) {
|
| - testingServer = new BrowserTestingServer(
|
| - configuration, localIp, useIframe);
|
| - }
|
| - return testingServer.start().then((_) {
|
| - testingServer.testDoneCallBack = handleResults;
|
| - testingServer.testStatusUpdateCallBack = handleStatusUpdate;
|
| - testingServer.testStartedCallBack = handleStarted;
|
| - testingServer.nextTestCallBack = getNextTest;
|
| - return getBrowsers().then((browsers) {
|
| - var futures = [];
|
| - for (var browser in browsers) {
|
| - var url = testingServer.getDriverUrl(browser.id);
|
| - var future = browser.start(url).then((success) {
|
| - if (success) {
|
| - var status = new BrowserTestingStatus(browser);
|
| - browserStatus[browser.id] = status;
|
| - status.nextTestTimeout = createNextTestTimer(status);
|
| - status.timeSinceRestart.start();
|
| - }
|
| - return success;
|
| - });
|
| - futures.add(future);
|
| - }
|
| - return Future.wait(futures).then((values) {
|
| - return !values.contains(false);
|
| - });
|
| - });
|
| - });
|
| - }
|
| -
|
| - Future<List<Browser>> getBrowsers() {
|
| - // TODO(kustermann): This is a hackisch way to accomplish it and should
|
| - // be encapsulated
|
| - var browsersCompleter = new Completer();
|
| - var androidBrowserCreationMapping = {
|
| - 'chromeOnAndroid' : (AdbDevice device) => new AndroidChrome(device),
|
| - 'ContentShellOnAndroid' : (AdbDevice device) => new AndroidBrowser(
|
| - device,
|
| - contentShellOnAndroidConfig,
|
| - checkedMode,
|
| - configuration['drt']),
|
| - 'DartiumOnAndroid' : (AdbDevice device) => new AndroidBrowser(
|
| - device,
|
| - dartiumOnAndroidConfig,
|
| - checkedMode,
|
| - configuration['dartium']),
|
| - };
|
| - if (androidBrowserCreationMapping.containsKey(browserName)) {
|
| - AdbHelper.listDevices().then((deviceIds) {
|
| - if (deviceIds.length > 0) {
|
| - var browsers = [];
|
| - for (int i = 0; i < deviceIds.length; i++) {
|
| - var id = "BROWSER$i";
|
| - var device = new AdbDevice(deviceIds[i]);
|
| - adbDeviceMapping[id] = device;
|
| - var browser = androidBrowserCreationMapping[browserName](device);
|
| - browsers.add(browser);
|
| - // We store this in case we need to kill the browser.
|
| - browser.id = id;
|
| - }
|
| - browsersCompleter.complete(browsers);
|
| - } else {
|
| - throw new StateError("No android devices found.");
|
| - }
|
| - });
|
| - } else {
|
| - var browsers = [];
|
| - for (int i = 0; i < maxNumBrowsers; i++) {
|
| - var id = "BROWSER$browserIdCount";
|
| - browserIdCount++;
|
| - var browser = getInstance();
|
| - browsers.add(browser);
|
| - // We store this in case we need to kill the browser.
|
| - browser.id = id;
|
| - }
|
| - browsersCompleter.complete(browsers);
|
| - }
|
| - return browsersCompleter.future;
|
| - }
|
| -
|
| - var timedOut = [];
|
| -
|
| - void handleResults(String browserId, String output, int testId) {
|
| - var status = browserStatus[browserId];
|
| - if (testCache.containsKey(testId)) {
|
| - doubleReportingOutputs[testId] = output;
|
| - return;
|
| - }
|
| -
|
| - if (status == null || status.timeout) {
|
| - // We don't do anything, this browser is currently being killed and
|
| - // replaced. The browser here can be null if we decided to kill the
|
| - // browser.
|
| - } else if (status.currentTest != null) {
|
| - status.currentTest.timeoutTimer.cancel();
|
| - status.currentTest.stopwatch.stop();
|
| -
|
| - if (status.currentTest.id != testId) {
|
| - print("Expected test id ${status.currentTest.id} for"
|
| - "${status.currentTest.url}");
|
| - print("Got test id ${testId}");
|
| - print("Last test id was ${status.lastTest.id} for "
|
| - "${status.currentTest.url}");
|
| - throw("This should never happen, wrong test id");
|
| - }
|
| - testCache[testId] = status.currentTest.url;
|
| -
|
| - // Report that the test is finished now
|
| - var browserTestOutput = new BrowserTestOutput(
|
| - status.currentTest.delayUntilTestStarted,
|
| - status.currentTest.stopwatch.elapsed,
|
| - output,
|
| - status.browser.testBrowserOutput);
|
| - status.currentTest.doneCallback(browserTestOutput);
|
| -
|
| - status.lastTest = status.currentTest;
|
| - status.currentTest = null;
|
| - status.nextTestTimeout = createNextTestTimer(status);
|
| - } else {
|
| - print("\nThis is bad, should never happen, handleResult no test");
|
| - print("URL: ${status.lastTest.url}");
|
| - print(output);
|
| - terminate().then((_) {
|
| - exit(1);
|
| - });
|
| - }
|
| - }
|
| -
|
| - void handleStatusUpdate(String browserId, String output, int testId) {
|
| - var status = browserStatus[browserId];
|
| -
|
| - if (status == null || status.timeout) {
|
| - // We don't do anything, this browser is currently being killed and
|
| - // replaced. The browser here can be null if we decided to kill the
|
| - // browser.
|
| - } else if (status.currentTest != null && status.currentTest.id == testId) {
|
| - status.currentTest.lastKnownMessage = output;
|
| - }
|
| - }
|
| -
|
| - void handleStarted(String browserId, String output, int testId) {
|
| - var status = browserStatus[browserId];
|
| -
|
| - if (status != null && !status.timeout && status.currentTest != null) {
|
| - status.currentTest.timeoutTimer.cancel();
|
| - status.currentTest.timeoutTimer =
|
| - createTimeoutTimer(status.currentTest, status);
|
| - status.currentTest.delayUntilTestStarted =
|
| - status.currentTest.stopwatch.elapsed;
|
| - }
|
| - }
|
| -
|
| - void handleTimeout(BrowserTestingStatus status) {
|
| - // We simply kill the browser and starts up a new one!
|
| - // We could be smarter here, but it does not seems like it is worth it.
|
| - if (status.timeout) {
|
| - DebugLogger.error(
|
| - "Got test timeout for an already restarting browser");
|
| - return;
|
| - }
|
| - status.timeout = true;
|
| - timedOut.add(status.currentTest.url);
|
| - var id = status.browser.id;
|
| -
|
| - status.currentTest.stopwatch.stop();
|
| - status.browser.close().then((_) {
|
| - var lastKnownMessage =
|
| - 'Dom could not be fetched, since the test timed out.';
|
| - if (status.currentTest.lastKnownMessage.length > 0) {
|
| - lastKnownMessage = status.currentTest.lastKnownMessage;
|
| - }
|
| - // Wait until the browser is closed before reporting the test as timeout.
|
| - // This will enable us to capture stdout/stderr from the browser
|
| - // (which might provide us with information about what went wrong).
|
| - var browserTestOutput = new BrowserTestOutput(
|
| - status.currentTest.delayUntilTestStarted,
|
| - status.currentTest.stopwatch.elapsed,
|
| - lastKnownMessage,
|
| - status.browser.testBrowserOutput,
|
| - didTimeout: true);
|
| - status.currentTest.doneCallback(browserTestOutput);
|
| - status.lastTest = status.currentTest;
|
| - status.currentTest = null;
|
| -
|
| - // We don't want to start a new browser if we are terminating.
|
| - if (underTermination) return;
|
| - restartBrowser(id);
|
| - });
|
| - }
|
| -
|
| - void restartBrowser(String id) {
|
| - var browser;
|
| - var new_id = id;
|
| - if (browserName == 'chromeOnAndroid') {
|
| - browser = new AndroidChrome(adbDeviceMapping[id]);
|
| - } else if (browserName == 'ContentShellOnAndroid') {
|
| - browser = new AndroidBrowser(adbDeviceMapping[id],
|
| - contentShellOnAndroidConfig,
|
| - checkedMode,
|
| - configuration['drt']);
|
| - } else if (browserName == 'DartiumOnAndroid') {
|
| - browser = new AndroidBrowser(adbDeviceMapping[id],
|
| - dartiumOnAndroidConfig,
|
| - checkedMode,
|
| - configuration['dartium']);
|
| - } else {
|
| - browserStatus.remove(id);
|
| - browser = getInstance();
|
| - new_id = "BROWSER$browserIdCount";
|
| - browserIdCount++;
|
| - }
|
| - browser.id = new_id;
|
| - var status = new BrowserTestingStatus(browser);
|
| - browserStatus[new_id] = status;
|
| - status.nextTestTimeout = createNextTestTimer(status);
|
| - status.timeSinceRestart.start();
|
| - browser.start(testingServer.getDriverUrl(new_id)).then((success) {
|
| - // We may have started terminating in the mean time.
|
| - if (underTermination) {
|
| - if (status.nextTestTimeout != null) {
|
| - status.nextTestTimeout.cancel();
|
| - status.nextTestTimeout = null;
|
| - }
|
| - browser.close().then((success) {
|
| - // We should never hit this, print it out.
|
| - if (!success) {
|
| - print("Could not kill browser ($id) started due to timeout");
|
| - }
|
| - });
|
| - return;
|
| - }
|
| - if (!success) {
|
| - // TODO(ricow): Handle this better.
|
| - print("This is bad, should never happen, could not start browser");
|
| - exit(1);
|
| - }
|
| - });
|
| - }
|
| -
|
| - BrowserTest getNextTest(String browserId) {
|
| - var status = browserStatus[browserId];
|
| - if (status == null) return null;
|
| - if (status.nextTestTimeout != null) {
|
| - status.nextTestTimeout.cancel();
|
| - status.nextTestTimeout = null;
|
| - }
|
| - if (testQueue.isEmpty) return null;
|
| -
|
| - // We are currently terminating this browser, don't start a new test.
|
| - if (status.timeout) return null;
|
| -
|
| - // Restart content_shell and dartium on Android if they have been
|
| - // running for longer than RESTART_BROWSER_INTERVAL. The tests have
|
| - // had flaky timeouts, and this may help.
|
| - if ((browserName == 'ContentShellOnAndroid' ||
|
| - browserName == 'DartiumOnAndroid' ) &&
|
| - status.timeSinceRestart.elapsed > RESTART_BROWSER_INTERVAL) {
|
| - var id = status.browser.id;
|
| - // Reset stopwatch so we don't trigger again before restarting.
|
| - status.timeout = true;
|
| - status.browser.close().then((_) {
|
| - // We don't want to start a new browser if we are terminating.
|
| - if (underTermination) return;
|
| - restartBrowser(id);
|
| - });
|
| - // Don't send a test to the browser we are restarting.
|
| - return null;
|
| - }
|
| -
|
| - BrowserTest test = testQueue.removeLast();
|
| - if (status.currentTest == null) {
|
| - status.currentTest = test;
|
| - status.currentTest.lastKnownMessage = '';
|
| - } else {
|
| - // TODO(ricow): Handle this better.
|
| - print("Browser requested next test before reporting previous result");
|
| - print("This happened for browser $browserId");
|
| - print("Old test was: ${status.currentTest.url}");
|
| - print("The test before that was: ${status.lastTest.url}");
|
| - print("Timed out tests:");
|
| - for (var v in timedOut) {
|
| - print(" $v");
|
| - }
|
| - exit(1);
|
| - }
|
| -
|
| - status.currentTest.timeoutTimer = createTimeoutTimer(test, status);
|
| - status.currentTest.stopwatch = new Stopwatch()..start();
|
| -
|
| - // Reset the test specific output information (stdout, stderr) on the
|
| - // browser, since a new test is being started.
|
| - status.browser.resetTestBrowserOutput();
|
| - status.browser.logBrowserInfoToTestBrowserOutput();
|
| -
|
| - return test;
|
| - }
|
| -
|
| - Timer createTimeoutTimer(BrowserTest test, BrowserTestingStatus status) {
|
| - return new Timer(new Duration(seconds: test.timeout),
|
| - () { handleTimeout(status); });
|
| - }
|
| -
|
| - Timer createNextTestTimer(BrowserTestingStatus status) {
|
| - return new Timer(BrowserTestRunner.NEXT_TEST_TIMEOUT,
|
| - () { handleNextTestTimeout(status); });
|
| - }
|
| -
|
| - void handleNextTestTimeout(status) {
|
| - DebugLogger.warning(
|
| - "Browser timed out before getting next test. Restarting");
|
| - if (status.timeout) return;
|
| - numBrowserGetTestTimeouts++;
|
| - if (numBrowserGetTestTimeouts >= MAX_NEXT_TEST_TIMEOUTS) {
|
| - DebugLogger.error(
|
| - "Too many browser timeouts before getting next test. Terminating");
|
| - terminate().then((_) => exit(1));
|
| - } else {
|
| - status.timeout = true;
|
| - status.browser.close().then((_) => restartBrowser(status.browser.id));
|
| - }
|
| - }
|
| -
|
| - void queueTest(BrowserTest test) {
|
| - testQueue.add(test);
|
| - }
|
| -
|
| - void printDoubleReportingTests() {
|
| - if (doubleReportingOutputs.length == 0) return;
|
| - // TODO(ricow): die on double reporting.
|
| - // Currently we just report this here, we could have a callback to the
|
| - // encapsulating environment.
|
| - print("");
|
| - print("Double reporting tests");
|
| - for (var id in doubleReportingOutputs.keys) {
|
| - print(" ${testCache[id]}");
|
| - }
|
| -
|
| - DebugLogger.warning("Double reporting tests:");
|
| - for (var id in doubleReportingOutputs.keys) {
|
| - DebugLogger.warning("${testCache[id]}, output: ");
|
| - DebugLogger.warning("${doubleReportingOutputs[id]}");
|
| - DebugLogger.warning("");
|
| - DebugLogger.warning("");
|
| - }
|
| - }
|
| -
|
| - Future<bool> terminate() {
|
| - var browsers = [];
|
| - underTermination = true;
|
| - testingServer.underTermination = true;
|
| - for (BrowserTestingStatus status in browserStatus.values) {
|
| - browsers.add(status.browser);
|
| - if (status.nextTestTimeout != null) {
|
| - status.nextTestTimeout.cancel();
|
| - status.nextTestTimeout = null;
|
| - }
|
| - }
|
| - // Success if all the browsers closed successfully.
|
| - bool success = true;
|
| - Future closeBrowser(Browser b) {
|
| - return b.close().then((bool closeSucceeded) {
|
| - if (!closeSucceeded) {
|
| - success = false;
|
| - }
|
| - });
|
| - }
|
| - return Future.forEach(browsers, closeBrowser).then((_) {
|
| - testingServer.errorReportingServer.close();
|
| - printDoubleReportingTests();
|
| - return success;
|
| - });
|
| - }
|
| -
|
| - Browser getInstance() {
|
| - if (browserName == 'ff') browserName = 'firefox';
|
| - var path = Locations.getBrowserLocation(browserName, configuration);
|
| - var browser = new Browser.byName(browserName, path, checkedMode);
|
| - browser.logger = logger;
|
| - return browser;
|
| - }
|
| -}
|
| -
|
| -class BrowserTestingServer {
|
| - final Map configuration;
|
| - /// Interface of the testing server:
|
| - ///
|
| - /// GET /driver/BROWSER_ID -- This will get the driver page to fetch
|
| - /// and run tests ...
|
| - /// GET /next_test/BROWSER_ID -- returns "WAIT" "TERMINATE" or "url#id"
|
| - /// where url is the test to run, and id is the id of the test.
|
| - /// If there are currently no available tests the waitSignal is send
|
| - /// back. If we are in the process of terminating the terminateSignal
|
| - /// is send back and the browser will stop requesting new tasks.
|
| - /// POST /report/BROWSER_ID?id=NUM -- sends back the dom of the executed
|
| - /// test
|
| -
|
| - final String localIp;
|
| -
|
| - static const String driverPath = "/driver";
|
| - static const String nextTestPath = "/next_test";
|
| - static const String reportPath = "/report";
|
| - static const String statusUpdatePath = "/status_update";
|
| - static const String startedPath = "/started";
|
| - static const String waitSignal = "WAIT";
|
| - static const String terminateSignal = "TERMINATE";
|
| -
|
| - var testCount = 0;
|
| - var errorReportingServer;
|
| - bool underTermination = false;
|
| - bool useIframe = false;
|
| -
|
| - Function testDoneCallBack;
|
| - Function testStatusUpdateCallBack;
|
| - Function testStartedCallBack;
|
| - Function nextTestCallBack;
|
| -
|
| - BrowserTestingServer(this.configuration, this.localIp, this.useIframe);
|
| -
|
| - Future start() {
|
| - var test_driver_error_port = configuration['test_driver_error_port'];
|
| - return HttpServer.bind(localIp, test_driver_error_port)
|
| - .then(setupErrorServer)
|
| - .then(setupDispatchingServer);
|
| - }
|
| -
|
| - void setupErrorServer(HttpServer server) {
|
| - errorReportingServer = server;
|
| - void errorReportingHandler(HttpRequest request) {
|
| - StringBuffer buffer = new StringBuffer();
|
| - request.transform(UTF8.decoder).listen((data) {
|
| - buffer.write(data);
|
| - }, onDone: () {
|
| - String back = buffer.toString();
|
| - request.response.headers.set("Access-Control-Allow-Origin", "*");
|
| - request.response.done.catchError((error) {
|
| - DebugLogger.error("Error getting error from browser"
|
| - "on uri ${request.uri.path}: $error");
|
| - });
|
| - request.response.close();
|
| - DebugLogger.error("Error from browser on : "
|
| - "${request.uri.path}, data: $back");
|
| - }, onError: (error) { print(error); });
|
| - }
|
| - void errorHandler(e) {
|
| - if (!underTermination) print("Error occured in httpserver: $e");
|
| - }
|
| - errorReportingServer.listen(errorReportingHandler, onError: errorHandler);
|
| - }
|
| -
|
| - void setupDispatchingServer(_) {
|
| - DispatchingServer server = configuration['_servers_'].server;
|
| - void noCache(request) {
|
| - request.response.headers.set("Cache-Control",
|
| - "no-cache, no-store, must-revalidate");
|
| - }
|
| - int testId(request) =>
|
| - int.parse(request.uri.queryParameters["id"]);
|
| - String browserId(request, prefix) =>
|
| - request.uri.path.substring(prefix.length + 1);
|
| -
|
| -
|
| - server.addHandler(reportPath, (HttpRequest request) {
|
| - noCache(request);
|
| - handleReport(request, browserId(request, reportPath),
|
| - testId(request), isStatusUpdate: false);
|
| - });
|
| - server.addHandler(statusUpdatePath, (HttpRequest request) {
|
| - noCache(request);
|
| - handleReport(request, browserId(request, statusUpdatePath),
|
| - testId(request), isStatusUpdate: true);
|
| - });
|
| - server.addHandler(startedPath, (HttpRequest request) {
|
| - noCache(request);
|
| - handleStarted(request, browserId(request, startedPath),
|
| - testId(request));
|
| - });
|
| -
|
| - makeSendPageHandler(String prefix) => (HttpRequest request) {
|
| - noCache(request);
|
| - var textResponse = "";
|
| - if (prefix == driverPath) {
|
| - textResponse = getDriverPage(browserId(request, prefix));
|
| - request.response.headers.set('Content-Type', 'text/html');
|
| - }
|
| - if (prefix == nextTestPath) {
|
| - textResponse = getNextTest(browserId(request, prefix));
|
| - request.response.headers.set('Content-Type', 'text/plain');
|
| - }
|
| - request.response.write(textResponse);
|
| - request.listen((_) {}, onDone: request.response.close);
|
| - request.response.done.catchError((error) {
|
| - if (!underTermination) {
|
| - print("URI ${request.uri}");
|
| - print("Textresponse $textResponse");
|
| - throw "Error returning content to browser: $error";
|
| - }
|
| - });
|
| - };
|
| - server.addHandler(driverPath, makeSendPageHandler(driverPath));
|
| - server.addHandler(nextTestPath, makeSendPageHandler(nextTestPath));
|
| - }
|
| -
|
| - void handleReport(HttpRequest request, String browserId, var testId,
|
| - {bool isStatusUpdate}) {
|
| - StringBuffer buffer = new StringBuffer();
|
| - request.transform(UTF8.decoder).listen((data) {
|
| - buffer.write(data);
|
| - }, onDone: () {
|
| - String back = buffer.toString();
|
| - request.response.close();
|
| - if (isStatusUpdate) {
|
| - testStatusUpdateCallBack(browserId, back, testId);
|
| - } else {
|
| - testDoneCallBack(browserId, back, testId);
|
| - }
|
| - // TODO(ricow): We should do something smart if we get an error here.
|
| - }, onError: (error) { DebugLogger.error("$error"); });
|
| - }
|
| -
|
| - void handleStarted(HttpRequest request, String browserId, var testId) {
|
| - StringBuffer buffer = new StringBuffer();
|
| - // If an error occurs while receiving the data from the request stream,
|
| - // we don't handle it specially. We can safely ignore it, since the started
|
| - // events are not crucial.
|
| - request.transform(UTF8.decoder).listen((data) {
|
| - buffer.write(data);
|
| - }, onDone: () {
|
| - String back = buffer.toString();
|
| - request.response.close();
|
| - testStartedCallBack(browserId, back, testId);
|
| - }, onError: (error) { DebugLogger.error("$error"); });
|
| - }
|
| -
|
| - String getNextTest(String browserId) {
|
| - var nextTest = nextTestCallBack(browserId);
|
| - if (underTermination) {
|
| - // Browsers will be killed shortly, send them a terminate signal so
|
| - // that they stop pulling.
|
| - return terminateSignal;
|
| - }
|
| - return nextTest == null ? waitSignal : nextTest.toJSON();
|
| - }
|
| -
|
| - String getDriverUrl(String browserId) {
|
| - if (errorReportingServer == null) {
|
| - print("Bad browser testing server, you are not started yet. Can't "
|
| - "produce driver url");
|
| - exit(1);
|
| - // This should never happen - exit immediately;
|
| - }
|
| - var port = configuration['_servers_'].port;
|
| - return "http://$localIp:$port/driver/$browserId";
|
| - }
|
| -
|
| -
|
| - String getDriverPage(String browserId) {
|
| - var errorReportingUrl =
|
| - "http://$localIp:${errorReportingServer.port}/$browserId";
|
| - String driverContent = """
|
| -<!DOCTYPE html><html>
|
| -<head>
|
| - <style>
|
| - body {
|
| - margin: 0;
|
| - }
|
| - .box {
|
| - overflow: hidden;
|
| - overflow-y: auto;
|
| - position: absolute;
|
| - left: 0;
|
| - right: 0;
|
| - }
|
| - .controller.box {
|
| - height: 75px;
|
| - top: 0;
|
| - }
|
| - .test.box {
|
| - top: 75px;
|
| - bottom: 0;
|
| - }
|
| - </style>
|
| - <title>Driving page</title>
|
| - <script type='text/javascript'>
|
| - var STATUS_UPDATE_INTERVAL = 10000;
|
| -
|
| - function startTesting() {
|
| - var number_of_tests = 0;
|
| - var current_id;
|
| - var next_id;
|
| -
|
| - // Has the test in the current iframe reported that it is done?
|
| - var test_completed = true;
|
| - // Has the test in the current iframe reported that it is started?
|
| - var test_started = false;
|
| - var testing_window;
|
| -
|
| - var embedded_iframe_div = document.getElementById('embedded_iframe_div');
|
| - var embedded_iframe = document.getElementById('embedded_iframe');
|
| - var number_div = document.getElementById('number');
|
| - var executing_div = document.getElementById('currently_executing');
|
| - var error_div = document.getElementById('unhandled_error');
|
| - var use_iframe = ${useIframe};
|
| - var start = new Date();
|
| -
|
| - // Object that holds the state of an HTML test
|
| - var html_test;
|
| -
|
| - function newTaskHandler() {
|
| - if (this.readyState == this.DONE) {
|
| - if (this.status == 200) {
|
| - if (this.responseText == '$waitSignal') {
|
| - setTimeout(getNextTask, 500);
|
| - } else if (this.responseText == '$terminateSignal') {
|
| - // Don't do anything, we will be killed shortly.
|
| - } else {
|
| - var elapsed = new Date() - start;
|
| - var nextTask = JSON.parse(this.responseText);
|
| - var url = nextTask.url;
|
| - next_id = nextTask.id;
|
| - if (nextTask.isHtmlTest) {
|
| - html_test = {
|
| - expected_messages: nextTask.expectedMessages,
|
| - found_message_count: 0,
|
| - double_received_messages: [],
|
| - unexpected_messages: [],
|
| - found_messages: {}
|
| - };
|
| - for (var i = 0; i < html_test.expected_messages.length; ++i) {
|
| - html_test.found_messages[html_test.expected_messages[i]] = 0;
|
| - }
|
| - } else {
|
| - html_test = null;
|
| - }
|
| - run(url);
|
| - }
|
| - } else {
|
| - reportError('Could not contact the server and get a new task');
|
| - }
|
| - }
|
| - }
|
| -
|
| - function contactBrowserController(method,
|
| - path,
|
| - callback,
|
| - msg,
|
| - isUrlEncoded) {
|
| - var client = new XMLHttpRequest();
|
| - client.onreadystatechange = callback;
|
| - client.open(method, path);
|
| - if (isUrlEncoded) {
|
| - client.setRequestHeader('Content-type',
|
| - 'application/x-www-form-urlencoded');
|
| - }
|
| - client.send(msg);
|
| - }
|
| -
|
| - function getNextTask() {
|
| - // Until we have the next task we set the current_id to a specific
|
| - // negative value.
|
| - contactBrowserController(
|
| - 'GET', '$nextTestPath/$browserId', newTaskHandler, "", false);
|
| - }
|
| -
|
| - function childError(message, filename, lineno, colno, error) {
|
| - sendStatusUpdate();
|
| - if (error) {
|
| - reportMessage('FAIL:' + filename + ':' + lineno +
|
| - ':' + colno + ':' + message + '\\n' + error.stack, false, false);
|
| - } else if (filename) {
|
| - reportMessage('FAIL:' + filename + ':' + lineno +
|
| - ':' + colno + ':' + message, false, false);
|
| - } else {
|
| - reportMessage('FAIL: ' + message, false, false);
|
| - }
|
| - return true;
|
| - }
|
| -
|
| - function setChildHandlers(e) {
|
| - embedded_iframe.contentWindow.addEventListener('message',
|
| - childMessageHandler,
|
| - false);
|
| - embedded_iframe.contentWindow.onerror = childError;
|
| - reportMessage("First message from html test", true, false);
|
| - html_test.handlers_installed = true;
|
| - sendRepeatingStatusUpdate();
|
| - }
|
| -
|
| - function checkChildHandlersInstalled() {
|
| - if (!html_test.handlers_installed) {
|
| - reportMessage("First message from html test", true, false);
|
| - reportMessage(
|
| - 'FAIL: Html test did not call ' +
|
| - 'window.parent.dispatchEvent(new Event("detect_errors")) ' +
|
| - 'as its first action', false, false);
|
| - }
|
| - }
|
| -
|
| - function run(url) {
|
| - number_of_tests++;
|
| - number_div.innerHTML = number_of_tests;
|
| - executing_div.innerHTML = url;
|
| - if (use_iframe) {
|
| - if (html_test) {
|
| - window.addEventListener('detect_errors', setChildHandlers, false);
|
| - embedded_iframe.onload = checkChildHandlersInstalled;
|
| - } else {
|
| - embedded_iframe.onload = null;
|
| - }
|
| - embedded_iframe_div.removeChild(embedded_iframe);
|
| - embedded_iframe = document.createElement('iframe');
|
| - embedded_iframe.id = "embedded_iframe";
|
| - embedded_iframe.style="width:100%;height:100%";
|
| - embedded_iframe_div.appendChild(embedded_iframe);
|
| - embedded_iframe.src = url;
|
| - } else {
|
| - if (typeof testing_window != 'undefined') {
|
| - testing_window.close();
|
| - }
|
| - testing_window = window.open(url);
|
| - }
|
| - test_started = false;
|
| - test_completed = false;
|
| - }
|
| -
|
| - window.onerror = function (message, url, lineNumber) {
|
| - if (url) {
|
| - reportError(url + ':' + lineNumber + ':' + message);
|
| - } else {
|
| - reportError(message);
|
| - }
|
| - }
|
| -
|
| - function reportError(msg) {
|
| - function handleReady() {
|
| - if (this.readyState == this.DONE && this.status != 200) {
|
| - var error = 'Sending back error did not succeeed: ' + this.status;
|
| - error = error + '. Failed to send msg: ' + msg;
|
| - error_div.innerHTML = error;
|
| - }
|
| - }
|
| - contactBrowserController(
|
| - 'POST', '$errorReportingUrl?test=1', handleReady, msg, true);
|
| - }
|
| -
|
| - function reportMessage(msg, isFirstMessage, isStatusUpdate) {
|
| - if (isFirstMessage) {
|
| - if (test_started) {
|
| - reportMessage(
|
| - "FAIL: test started more than once (test reloads itself) " +
|
| - msg, false, false);
|
| - return;
|
| - }
|
| - current_id = next_id;
|
| - test_started = true;
|
| - contactBrowserController(
|
| - 'POST', '$startedPath/${browserId}?id=' + current_id,
|
| - function () {}, msg, true);
|
| - } else if (isStatusUpdate) {
|
| - contactBrowserController(
|
| - 'POST', '$statusUpdatePath/${browserId}?id=' + current_id,
|
| - function() {}, msg, true);
|
| - } else {
|
| - var is_double_report = test_completed;
|
| - var retry = 0;
|
| - test_completed = true;
|
| -
|
| - function reportDoneMessage() {
|
| - contactBrowserController(
|
| - 'POST', '$reportPath/${browserId}?id=' + current_id,
|
| - handleReady, msg, true);
|
| - }
|
| -
|
| - function handleReady() {
|
| - if (this.readyState == this.DONE) {
|
| - if (this.status == 200) {
|
| - if (!is_double_report) {
|
| - getNextTask();
|
| - }
|
| - } else {
|
| - reportError('Error sending result to server. Status: ' +
|
| - this.status + ' Retry: ' + retry);
|
| - retry++;
|
| - if (retry < 3) {
|
| - setTimeout(reportDoneMessage, 1000);
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - reportDoneMessage();
|
| - }
|
| - }
|
| -
|
| - function parseResult(result) {
|
| - var parsedData = null;
|
| - try {
|
| - parsedData = JSON.parse(result);
|
| - } catch(error) { }
|
| - return parsedData;
|
| - }
|
| -
|
| - // Browser tests send JSON messages to the driver window, handled here.
|
| - function messageHandler(e) {
|
| - var msg = e.data;
|
| - if (typeof msg != 'string') return;
|
| - var expectedSource =
|
| - use_iframe ? embedded_iframe.contentWindow : testing_window;
|
| - if (e.source != expectedSource) {
|
| - reportError("Message received from old test window: " + msg);
|
| - return;
|
| - }
|
| - var parsedData = parseResult(msg);
|
| - if (parsedData) {
|
| - // Only if the JSON message contains all required parameters,
|
| - // will we handle it and post it back to the test controller.
|
| - if ('message' in parsedData &&
|
| - 'is_first_message' in parsedData &&
|
| - 'is_status_update' in parsedData &&
|
| - 'is_done' in parsedData) {
|
| - var message = parsedData['message'];
|
| - var isFirstMessage = parsedData['is_first_message'];
|
| - var isStatusUpdate = parsedData['is_status_update'];
|
| - var isDone = parsedData['is_done'];
|
| - if (!isFirstMessage && !isStatusUpdate) {
|
| - if (!isDone) {
|
| - alert("Bug in test_controller.js: " +
|
| - "isFirstMessage/isStatusUpdate/isDone were all false");
|
| - }
|
| - }
|
| - reportMessage(message, isFirstMessage, isStatusUpdate);
|
| - }
|
| - }
|
| - }
|
| -
|
| - function sendStatusUpdate () {
|
| - var dom =
|
| - embedded_iframe.contentWindow.document.documentElement.innerHTML;
|
| - reportMessage('Status:\\n Messages received multiple times:\\n ' +
|
| - html_test.double_received_messages +
|
| - '\\n Unexpected messages:\\n ' +
|
| - html_test.unexpected_messages +
|
| - '\\n DOM:\\n ' + dom, false, true);
|
| - }
|
| -
|
| - function sendRepeatingStatusUpdate() {
|
| - sendStatusUpdate();
|
| - setTimeout(sendRepeatingStatusUpdate, STATUS_UPDATE_INTERVAL);
|
| - }
|
| -
|
| - // HTML tests post messages to their own window, handled by this handler.
|
| - // This handler is installed on the child window when it sends the
|
| - // 'detect_errors' event. Every HTML test must send 'detect_errors' to
|
| - // its parent window as its first action, so all errors will be caught.
|
| - function childMessageHandler(e) {
|
| - var msg = e.data;
|
| - if (typeof msg != 'string') return;
|
| - if (msg in html_test.found_messages) {
|
| - html_test.found_messages[msg]++;
|
| - if (html_test.found_messages[msg] == 1) {
|
| - html_test.found_message_count++;
|
| - } else {
|
| - html_test.double_received_messages.push(msg);
|
| - sendStatusUpdate();
|
| - }
|
| - } else {
|
| - html_test.unexpected_messages.push(msg);
|
| - sendStatusUpdate();
|
| - }
|
| - if (html_test.found_message_count ==
|
| - html_test.expected_messages.length) {
|
| - reportMessage('Test done: PASS', false, false);
|
| - }
|
| - }
|
| -
|
| - if (!html_test) {
|
| - window.addEventListener('message', messageHandler, false);
|
| - waitForDone = false;
|
| - }
|
| - getNextTask();
|
| - }
|
| - </script>
|
| -</head>
|
| - <body onload="startTesting()">
|
| - <div class="controller box">
|
| - Dart test driver, number of tests: <span id="number"></span><br>
|
| - Currently executing: <span id="currently_executing"></span><br>
|
| - Unhandled error: <span id="unhandled_error"></span>
|
| - </div>
|
| - <div id="embedded_iframe_div" class="test box">
|
| - <iframe style="width:100%;height:100%;" id="embedded_iframe"></iframe>
|
| - </div>
|
| - </body>
|
| -</html>
|
| -""";
|
| - return driverContent;
|
| - }
|
| -}
|
|
|