From c5e657750aac9cd2e344858c679c23d693cc7b39 Mon Sep 17 00:00:00 2001
From: Natalie Weizenbaum <nweiz@google.com>
Date: Tue, 3 Mar 2015 13:06:57 -0800
Subject: [PATCH] Add BrowserManager and IframeTest for communicating with
 browser tests.

See #5

R=kevmoo@google.com

Review URL: https://codereview.chromium.org//971103002
---
 lib/src/runner/browser/browser_manager.dart | 68 +++++++++++++++++++++
 lib/src/runner/browser/iframe_test.dart     | 52 ++++++++++++++++
 lib/src/utils.dart                          | 10 +++
 3 files changed, 130 insertions(+)
 create mode 100644 lib/src/runner/browser/browser_manager.dart
 create mode 100644 lib/src/runner/browser/iframe_test.dart

diff --git a/lib/src/runner/browser/browser_manager.dart b/lib/src/runner/browser/browser_manager.dart
new file mode 100644
index 00000000..44851f0b
--- /dev/null
+++ b/lib/src/runner/browser/browser_manager.dart
@@ -0,0 +1,68 @@
+// 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 unittest.runner.browser.browser_manager;
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:http_parser/http_parser.dart';
+
+import '../../backend/suite.dart';
+import '../../util/multi_channel.dart';
+import '../../util/remote_exception.dart';
+import '../../utils.dart';
+import '../load_exception.dart';
+import 'iframe_test.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 channel used to communicate with the browser.
+  ///
+  /// This is connected to a page running `static/host.dart`.
+  final MultiChannel _channel;
+
+  /// Creates a new BrowserManager that communicates with a browser over
+  /// [webSocket].
+  BrowserManager(CompatibleWebSocket webSocket)
+      : _channel = new MultiChannel(
+          webSocket.map(JSON.decode),
+          mapSink(webSocket, JSON.encode));
+
+  /// 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.
+  Future<Suite> loadSuite(String path, Uri url) {
+    var suiteChannel = _channel.virtualChannel();
+    _channel.sink.add({
+      "command": "loadSuite",
+      "url": url.toString(),
+      "channel": suiteChannel.id
+    });
+
+    // Create a nested MultiChannel because the iframe will be using a channel
+    // wrapped within the host's channel.
+    suiteChannel = new MultiChannel(suiteChannel.stream, suiteChannel.sink);
+    return suiteChannel.stream.first.then((response) {
+      if (response["type"] == "loadException") {
+        return new Future.error(new LoadException(path, response["message"]));
+      } else if (response["type"] == "error") {
+        var asyncError = RemoteException.deserialize(response["error"]);
+        return new Future.error(
+            new LoadException(path, asyncError.error),
+            asyncError.stackTrace);
+      }
+
+      return new Suite(path, response["tests"].map((test) {
+        var testChannel = suiteChannel.virtualChannel(test['channel']);
+        return new IframeTest(test['name'], testChannel);
+      }));
+    });
+  }
+}
diff --git a/lib/src/runner/browser/iframe_test.dart b/lib/src/runner/browser/iframe_test.dart
new file mode 100644
index 00000000..e4aee528
--- /dev/null
+++ b/lib/src/runner/browser/iframe_test.dart
@@ -0,0 +1,52 @@
+// 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 unittest.runner.browser.iframe_test;
+
+import '../../backend/live_test.dart';
+import '../../backend/live_test_controller.dart';
+import '../../backend/state.dart';
+import '../../backend/suite.dart';
+import '../../backend/test.dart';
+import '../../util/multi_channel.dart';
+import '../../util/remote_exception.dart';
+
+/// A test in a running iframe.
+class IframeTest implements Test {
+  final String name;
+
+  /// The channel used to communicate with the test's [IframeListener].
+  final MultiChannel _channel;
+
+  IframeTest(this.name, this._channel);
+
+  LiveTest load(Suite suite) {
+    var controller;
+    controller = new LiveTestController(suite, this, () {
+      controller.setState(const State(Status.running, Result.success));
+
+      var testChannel = _channel.virtualChannel();
+      _channel.sink.add({
+        'command': 'run',
+        'channel': testChannel.id
+      });
+
+      testChannel.stream.listen((message) {
+        if (message['type'] == 'error') {
+          var asyncError = RemoteException.deserialize(message['error']);
+          controller.addError(asyncError.error, asyncError.stackTrace);
+        } else if (message['type'] == 'state-change') {
+          controller.setState(
+              new State(
+                  new Status.parse(message['status']),
+                  new Result.parse(message['result'])));
+        } else {
+          assert(message['type'] == 'complete');
+          controller.completer.complete();
+        }
+      });
+    });
+    return controller.liveTest;
+  }
+}
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 06bd8dff..6b2b21ef 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -64,6 +64,16 @@ List flatten(Iterable nested) {
   return result;
 }
 
+/// Returns a sink that maps events sent to [original] using [fn].
+StreamSink mapSink(StreamSink original, fn(event)) {
+  var controller = new StreamController(sync: true);
+  controller.stream.listen(
+      (event) => original.add(fn(event)),
+      onError: (error, stackTrace) => original.addError(error, stackTrace),
+      onDone: () => original.close());
+  return controller.sink;
+}
+
 /// Truncates [text] to fit within [maxLength].
 ///
 /// This will try to truncate along word boundaries and preserve words both at
-- 
GitLab