Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(501)

Unified Diff: mojo/public/dart/third_party/test/lib/src/runner/browser/browser_manager.dart

Issue 1346773002: Stop running pub get at gclient sync time and fix build bugs (Closed) Base URL: git@github.com:domokit/mojo.git@master
Patch Set: Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: mojo/public/dart/third_party/test/lib/src/runner/browser/browser_manager.dart
diff --git a/mojo/public/dart/third_party/test/lib/src/runner/browser/browser_manager.dart b/mojo/public/dart/third_party/test/lib/src/runner/browser/browser_manager.dart
new file mode 100644
index 0000000000000000000000000000000000000000..7498efad5b352f9564f2abeb180637fcce176820
--- /dev/null
+++ b/mojo/public/dart/third_party/test/lib/src/runner/browser/browser_manager.dart
@@ -0,0 +1,300 @@
+// Copyright (c) 2015, 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 test.runner.browser.browser_manager;
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:async/async.dart';
+import 'package:http_parser/http_parser.dart';
+import 'package:pool/pool.dart';
+
+import '../../backend/metadata.dart';
+import '../../backend/test_platform.dart';
+import '../../util/cancelable_future.dart';
+import '../../util/multi_channel.dart';
+import '../../util/remote_exception.dart';
+import '../../util/stack_trace_mapper.dart';
+import '../../utils.dart';
+import '../application_exception.dart';
+import '../environment.dart';
+import '../load_exception.dart';
+import '../runner_suite.dart';
+import 'browser.dart';
+import 'chrome.dart';
+import 'content_shell.dart';
+import 'dartium.dart';
+import 'firefox.dart';
+import 'iframe_test.dart';
+import 'internet_explorer.dart';
+import 'phantom_js.dart';
+import 'safari.dart';
+
+/// A class that manages the connection to a single running browser.
+///
+/// This is in charge of telling the browser which test suites to load and
+/// converting its responses into [Suite] objects.
+class BrowserManager {
+ /// The browser instance that this is connected to via [_channel].
+ final Browser _browser;
+
+ // TODO(nweiz): Consider removing the duplication between this and
+ // [_browser.name].
+ /// The [TestPlatform] for [_browser].
+ final TestPlatform _platform;
+
+ /// The channel used to communicate with the browser.
+ ///
+ /// This is connected to a page running `static/host.dart`.
+ final MultiChannel _channel;
+
+ /// A pool that ensures that limits the number of initial connections the
+ /// manager will wait for at once.
+ ///
+ /// This isn't the *total* number of connections; any number of iframes may be
+ /// loaded in the same browser. However, the browser can only load so many at
+ /// once, and we want a timeout in case they fail so we only wait for so many
+ /// at once.
+ final _pool = new Pool(8);
+
+ /// The ID of the next suite to be loaded.
+ ///
+ /// This is used to ensure that the suites can be referred to consistently
+ /// across the client and server.
+ int _suiteId = 0;
+
+ /// Whether the channel to the browser has closed.
+ bool _closed = false;
+
+ /// The completer for [_BrowserEnvironment.displayPause].
+ ///
+ /// This will be `null` as long as the browser isn't displaying a pause
+ /// screen.
+ CancelableCompleter _pauseCompleter;
+
+ /// The environment to attach to each suite.
+ Future<_BrowserEnvironment> _environment;
+
+ /// Starts the browser identified by [platform] and has it connect to [url].
+ ///
+ /// [url] should serve a page that establishes a WebSocket connection with
+ /// this process. That connection, once established, should be emitted via
+ /// [future]. If [debug] is true, starts the browser in debug mode, with its
+ /// debugger interfaces on and detected.
+ ///
+ /// Returns the browser manager, or throws an [ApplicationException] if a
+ /// connection fails to be established.
+ static Future<BrowserManager> start(TestPlatform platform, Uri url,
+ Future<CompatibleWebSocket> future, {bool debug: false}) {
+ var browser = _newBrowser(url, platform, debug: debug);
+
+ var completer = new Completer();
+
+ // TODO(nweiz): Gracefully handle the browser being killed before the
+ // tests complete.
+ browser.onExit.then((_) {
+ throw new ApplicationException(
+ "${platform.name} exited before connecting.");
+ }).catchError((error, stackTrace) {
+ if (completer.isCompleted) return;
+ completer.completeError(error, stackTrace);
+ });
+
+ future.then((webSocket) {
+ if (completer.isCompleted) return;
+ completer.complete(new BrowserManager._(browser, platform, webSocket));
+ }).catchError((error, stackTrace) {
+ browser.close();
+ if (completer.isCompleted) return;
+ completer.completeError(error, stackTrace);
+ });
+
+ return completer.future.timeout(new Duration(seconds: 30), onTimeout: () {
+ browser.close();
+ throw new ApplicationException(
+ "Timed out waiting for ${platform.name} to connect.");
+ });
+ }
+
+ /// Starts the browser identified by [browser] and has it load [url].
+ ///
+ /// If [debug] is true, starts the browser in debug mode.
+ static Browser _newBrowser(Uri url, TestPlatform browser,
+ {bool debug: false}) {
+ switch (browser) {
+ case TestPlatform.dartium: return new Dartium(url, debug: debug);
+ case TestPlatform.contentShell:
+ return new ContentShell(url, debug: debug);
+ case TestPlatform.chrome: return new Chrome(url);
+ case TestPlatform.phantomJS: return new PhantomJS(url, debug: debug);
+ case TestPlatform.firefox: return new Firefox(url);
+ case TestPlatform.safari: return new Safari(url);
+ case TestPlatform.internetExplorer: return new InternetExplorer(url);
+ default:
+ throw new ArgumentError("$browser is not a browser.");
+ }
+ }
+
+ /// Creates a new BrowserManager that communicates with [browser] over
+ /// [webSocket].
+ BrowserManager._(this._browser, this._platform, CompatibleWebSocket webSocket)
+ : _channel = new MultiChannel(
+ webSocket.map(JSON.decode),
+ mapSink(webSocket, JSON.encode)) {
+ _environment = _loadBrowserEnvironment();
+ _channel.stream.listen(_onMessage, onDone: close);
+ }
+
+ /// Loads [_BrowserEnvironment].
+ Future<_BrowserEnvironment> _loadBrowserEnvironment() async {
+ var observatoryUrl;
+ if (_platform.isDartVM) observatoryUrl = await _browser.observatoryUrl;
+
+ var remoteDebuggerUrl;
+ if (_platform.isHeadless) {
+ remoteDebuggerUrl = await _browser.remoteDebuggerUrl;
+ }
+
+ return new _BrowserEnvironment(this, observatoryUrl, remoteDebuggerUrl);
+ }
+
+ /// Tells the browser the load a test suite from the URL [url].
+ ///
+ /// [url] should be an HTML page with a reference to the JS-compiled test
+ /// suite. [path] is the path of the original test suite file, which is used
+ /// for reporting. [metadata] is the parsed metadata for the test suite.
+ ///
+ /// If [mapper] is passed, it's used to map stack traces for errors coming
+ /// from this test suite.
+ Future<RunnerSuite> loadSuite(String path, Uri url, Metadata metadata,
+ {StackTraceMapper mapper}) async {
+ url = url.replace(fragment: Uri.encodeFull(JSON.encode({
+ "metadata": metadata.serialize(),
+ "browser": _platform.identifier
+ })));
+
+ // The stream may close before emitting a value if the browser is killed
+ // prematurely (e.g. via Control-C).
+ var suiteVirtualChannel = _channel.virtualChannel();
+ var suiteId = _suiteId++;
+ var suiteChannel;
+
+ closeIframe() {
+ if (_closed) return;
+ suiteChannel.sink.close();
+ _channel.sink.add({
+ "command": "closeSuite",
+ "id": suiteId
+ });
+ }
+
+ var response = await _pool.withResource(() {
+ _channel.sink.add({
+ "command": "loadSuite",
+ "url": url.toString(),
+ "id": suiteId,
+ "channel": suiteVirtualChannel.id
+ });
+
+ // Create a nested MultiChannel because the iframe will be using a channel
+ // wrapped within the host's channel.
+ suiteChannel = new MultiChannel(
+ suiteVirtualChannel.stream, suiteVirtualChannel.sink);
+
+ var completer = new Completer();
+ suiteChannel.stream.listen((response) {
+ if (response["type"] == "print") {
+ print(response["line"]);
+ } else {
+ completer.complete(response);
+ }
+ }, onDone: () {
+ if (!completer.isCompleted) completer.complete();
+ });
+
+ return completer.future.timeout(new Duration(minutes: 1), onTimeout: () {
+ throw new LoadException(
+ path,
+ "Timed out waiting for the test suite to connect on "
+ "${_platform.name}.");
+ });
+ });
+
+ if (response == null) {
+ closeIframe();
+ throw new LoadException(
+ path, "Connection closed before test suite loaded.");
+ }
+
+ if (response["type"] == "loadException") {
+ closeIframe();
+ throw new LoadException(path, response["message"]);
+ }
+
+ if (response["type"] == "error") {
+ closeIframe();
+ var asyncError = RemoteException.deserialize(response["error"]);
+ await new Future.error(
+ new LoadException(path, asyncError.error),
+ asyncError.stackTrace);
+ }
+
+ return new RunnerSuite(await _environment, response["tests"].map((test) {
+ var testMetadata = new Metadata.deserialize(test['metadata']);
+ var testChannel = suiteChannel.virtualChannel(test['channel']);
+ return new IframeTest(test['name'], testMetadata, testChannel,
+ mapper: mapper);
+ }), platform: _platform, metadata: metadata, path: path,
+ onClose: () => closeIframe());
+ }
+
+ /// An implementation of [Environment.displayPause].
+ CancelableFuture _displayPause() {
+ if (_pauseCompleter != null) return _pauseCompleter.future;
+
+ _pauseCompleter = new CancelableCompleter(() {
+ _channel.sink.add({"command": "resume"});
+ _pauseCompleter = null;
+ });
+
+ _channel.sink.add({"command": "displayPause"});
+ return _pauseCompleter.future.whenComplete(() {
+ _pauseCompleter = null;
+ });
+ }
+
+ /// The callback for handling messages received from the host page.
+ void _onMessage(Map message) {
+ assert(message["command"] == "resume");
+ if (_pauseCompleter == null) return;
+ _pauseCompleter.complete();
+ }
+
+ /// Closes the manager and releases any resources it owns, including closing
+ /// the browser.
+ Future close() => _closeMemoizer.runOnce(() {
+ _closed = true;
+ if (_pauseCompleter != null) _pauseCompleter.complete();
+ _pauseCompleter = null;
+ return _browser.close();
+ });
+ final _closeMemoizer = new AsyncMemoizer();
+}
+
+/// An implementation of [Environment] for the browser.
+///
+/// All methods forward directly to [BrowserManager].
+class _BrowserEnvironment implements Environment {
+ final BrowserManager _manager;
+
+ final Uri observatoryUrl;
+
+ final Uri remoteDebuggerUrl;
+
+ _BrowserEnvironment(this._manager, this.observatoryUrl,
+ this.remoteDebuggerUrl);
+
+ CancelableFuture displayPause() => _manager._displayPause();
+}

Powered by Google App Engine
This is Rietveld 408576698