From f169f3d64e880472f82e9c2b744bb57329d6122d Mon Sep 17 00:00:00 2001
From: Natalie Weizenbaum <nweiz@google.com>
Date: Wed, 22 Apr 2015 17:50:43 -0700
Subject: [PATCH] Add IE support.

Closes #31

R=kevmoo@google.com

Review URL: https://codereview.chromium.org//1088933006
---
 .status                                       |   9 +-
 CHANGELOG.md                                  |   2 +-
 README.md                                     |   2 +
 lib/src/backend/test_platform.dart            |  17 ++-
 lib/src/executable.dart                       |   1 +
 lib/src/runner/browser/internet_explorer.dart |  98 +++++++++++++++++
 lib/src/runner/browser/server.dart            |   4 +-
 .../browser/internet_explorer_test.dart       | 104 ++++++++++++++++++
 test/runner/runner_test.dart                  |   3 +-
 9 files changed, 232 insertions(+), 8 deletions(-)
 create mode 100644 lib/src/runner/browser/internet_explorer.dart
 create mode 100644 test/runner/browser/internet_explorer_test.dart

diff --git a/.status b/.status
index e4e15514..870ef8a0 100644
--- a/.status
+++ b/.status
@@ -28,6 +28,12 @@ test/runner/browser/phantom_js: Skip
 
 test/runner/browser/loader_test: Pass, Slow
 
+[ $system != windows ]
+test/runner/browser/internet_explorer: SkipByDesign
+
+[ $system != macos ]
+test/runner/browser/safari: SkipByDesign
+
 # The test harness for browser tests doesn't play nicely with the new way of
 # doing browser tests.
 [ $browser ]
@@ -35,6 +41,3 @@ test/*: SkipByDesign
 test/*/*: SkipByDesign
 test/*/*/*: SkipByDesign
 test/*/*/*/*: SkipByDesign
-
-[ $runtime == safari ]
-test/matcher/prints_test: Fail # Issue 4
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0089c858..3601d835 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,7 +20,7 @@
 * When running a test suite via `dart path/to/test.dart`, throw an exception if
   the suite fails so that the exit code is set properly.
 
-* Add support for running on Windows.
+* Add support for running on Windows and Internet Explorer.
 
 ### 0.12.0-beta.10
 
diff --git a/README.md b/README.md
index d1602fd9..731a0324 100644
--- a/README.md
+++ b/README.md
@@ -152,6 +152,8 @@ valid identifiers are:
 
 * `safari`: Whether the test is running on Apple Safari.
 
+* `ie`: Whether the test is running on Microsoft Internet Explorer.
+
 * `dart-vm`: Whether the test is running on the Dart VM in any context,
   including Dartium. It's identical to `!js`.
 
diff --git a/lib/src/backend/test_platform.dart b/lib/src/backend/test_platform.dart
index d311d692..fef5a624 100644
--- a/lib/src/backend/test_platform.dart
+++ b/lib/src/backend/test_platform.dart
@@ -40,9 +40,22 @@ class TestPlatform {
   static const TestPlatform safari = const TestPlatform._("Safari", "safari",
       isBrowser: true, isJS: true);
 
+  /// Microsoft Internet Explorer.
+  static const TestPlatform internetExplorer = const TestPlatform._(
+      "Internet Explorer", "ie",
+      isBrowser: true, isJS: true);
+
   /// A list of all instances of [TestPlatform].
-  static const List<TestPlatform> all =
-      const [vm, dartium, contentShell, chrome, phantomJS, firefox, safari];
+  static const List<TestPlatform> all = const [
+    vm,
+    dartium,
+    contentShell,
+    chrome,
+    phantomJS,
+    firefox,
+    safari,
+    internetExplorer
+  ];
 
   /// Finds a platform by its identifier string.
   ///
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index a9d9ccfc..f2f8c3c1 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -78,6 +78,7 @@ bool get _usesTransformer {
 void main(List<String> args) {
   var allPlatforms = TestPlatform.all.toList();
   if (!Platform.isMacOS) allPlatforms.remove(TestPlatform.safari);
+  if (!Platform.isWindows) allPlatforms.remove(TestPlatform.internetExplorer);
 
   _parser.addFlag("help", abbr: "h", negatable: false,
       help: "Shows this usage information.");
diff --git a/lib/src/runner/browser/internet_explorer.dart b/lib/src/runner/browser/internet_explorer.dart
new file mode 100644
index 00000000..5453252d
--- /dev/null
+++ b/lib/src/runner/browser/internet_explorer.dart
@@ -0,0 +1,98 @@
+// 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.internet_explorer;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+import 'package:stack_trace/stack_trace.dart';
+
+import '../../utils.dart';
+import '../application_exception.dart';
+import 'browser.dart';
+
+/// A class for running an instance of Internet Explorer.
+///
+/// Any errors starting or running the process are reported through [onExit].
+class InternetExplorer implements Browser {
+  /// The underlying process.
+  Process _process;
+
+  Future get onExit => _onExitCompleter.future;
+  final _onExitCompleter = new Completer();
+
+  /// A future that completes when the browser process has started.
+  ///
+  /// This is used to ensure that [close] works regardless of when it's called.
+  Future get _onProcessStarted => _onProcessStartedCompleter.future;
+  final _onProcessStartedCompleter = new Completer();
+
+  /// Starts a new instance of Internet Explorer open to the given [url], which
+  /// may be a [Uri] or a [String].
+  ///
+  /// If [executable] is passed, it's used as the Internet Explorer executable.
+  /// Otherwise the default executable name will be used.
+  InternetExplorer(url, {String executable}) {
+    if (executable == null) executable = _defaultExecutable();
+
+    // Don't return a Future here because there's no need for the caller to wait
+    // for the process to actually start. They should just wait for the HTTP
+    // request instead.
+    Process.start(executable, ['-extoff', url.toString()])
+        .then((process) {
+      _process = process;
+      _onProcessStartedCompleter.complete();
+
+      // TODO(nweiz): the browser's standard output is almost always useless
+      // noise, but we should allow the user to opt in to seeing it.
+      return _process.exitCode;
+    }).then((exitCode) {
+      if (exitCode == 0) return null;
+
+      return UTF8.decodeStream(_process.stderr).then((error) {
+        throw new ApplicationException(
+            "Internet Explorer failed with exit code $exitCode:\n$error");
+      });
+    }).then(_onExitCompleter.complete).catchError((error, stackTrace) {
+      if (stackTrace == null) stackTrace = new Trace.current();
+      _onExitCompleter.completeError(
+          new ApplicationException(
+              "Failed to start Internet Explorer: ${getErrorMessage(error)}."),
+          stackTrace);
+    });
+  }
+
+  Future close() {
+    _onProcessStarted.then((_) => _process.kill());
+
+    // Swallow exceptions. The user should explicitly use [onExit] for these.
+    return onExit.catchError((_) {});
+  }
+
+  /// Return the default executable for the current operating system.
+  String _defaultExecutable() {
+    // Chrome could be installed in several places on Windows. The only way to
+    // find it is to check.
+    var prefixes = [
+      Platform.environment['PROGRAMW6432'],
+      Platform.environment['PROGRAMFILES'],
+      Platform.environment['PROGRAMFILES(X86)']
+    ];
+    var suffix = r'Internet Explorer\iexplore.exe';
+
+    for (var prefix in prefixes) {
+      if (prefix == null) continue;
+
+      var path = p.join(prefix, suffix);
+      if (new File(p.join(prefix, suffix)).existsSync()) return path;
+    }
+
+    // Fall back on looking it up on the path. This probably won't work, but at
+    // least it will fail with a useful error message.
+    return "iexplore.exe";
+  }
+}
diff --git a/lib/src/runner/browser/server.dart b/lib/src/runner/browser/server.dart
index 06c97668..5fbbf8ad 100644
--- a/lib/src/runner/browser/server.dart
+++ b/lib/src/runner/browser/server.dart
@@ -28,9 +28,10 @@ import 'browser.dart';
 import 'browser_manager.dart';
 import 'compiler_pool.dart';
 import 'chrome.dart';
-import 'dartium.dart';
 import 'content_shell.dart';
+import 'dartium.dart';
 import 'firefox.dart';
+import 'internet_explorer.dart';
 import 'phantom_js.dart';
 import 'safari.dart';
 
@@ -389,6 +390,7 @@ void main() {
       case TestPlatform.phantomJS: return new PhantomJS(url);
       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.");
     }
diff --git a/test/runner/browser/internet_explorer_test.dart b/test/runner/browser/internet_explorer_test.dart
new file mode 100644
index 00000000..47c7d09f
--- /dev/null
+++ b/test/runner/browser/internet_explorer_test.dart
@@ -0,0 +1,104 @@
+// 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.
+
+@TestOn("vm && windows")
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:test/src/runner/browser/internet_explorer.dart';
+import 'package:test/src/util/io.dart';
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf_io;
+import 'package:shelf_web_socket/shelf_web_socket.dart';
+
+import '../../io.dart';
+import '../../utils.dart';
+
+void main() {
+  group("running JavaScript", () {
+    // The JavaScript to serve in the server. We use actual JavaScript here to
+    // avoid the pain of compiling to JS in a test
+    var javaScript;
+
+    var servePage = (request) {
+      var path = request.url.path;
+
+      // We support both shelf 0.5.x and 0.6.x. The former has a leading "/"
+      // here, the latter does not.
+      if (path.startsWith("/")) path = path.substring(1);
+
+      if (path.isEmpty) {
+        return new shelf.Response.ok("""
+<!doctype html>
+<html>
+<head>
+  <script src="index.js"></script>
+</head>
+</html>
+""", headers: {'content-type': 'text/html'});
+      } else if (path == "index.js") {
+        return new shelf.Response.ok(javaScript,
+            headers: {'content-type': 'application/javascript'});
+      } else {
+        return new shelf.Response.notFound(null);
+      }
+    };
+
+    var server;
+    var webSockets;
+    setUp(() {
+      var webSocketsController = new StreamController();
+      webSockets = webSocketsController.stream;
+
+      return shelf_io.serve(
+          new shelf.Cascade()
+              .add(webSocketHandler(webSocketsController.add))
+              .add(servePage).handler,
+          'localhost', 0).then((server_) {
+        server = server_;
+      });
+    });
+
+    tearDown(() {
+      if (server != null) server.close();
+
+      javaScript = null;
+      server = null;
+      webSockets = null;
+    });
+
+    test("starts IE with the given URL", () {
+      javaScript = '''
+var webSocket = new WebSocket(window.location.href.replace("http://", "ws://"));
+webSocket.addEventListener("open", function() {
+  webSocket.send("loaded!");
+});
+''';
+      var ie = new InternetExplorer(
+          baseUrlForAddress(server.address, server.port));
+
+      return webSockets.first.then((webSocket) {
+        return webSocket.first.then(
+            (message) => expect(message, equals("loaded!")));
+      }).whenComplete(ie.close);
+    });
+  });
+
+  test("a process can be killed synchronously after it's started", () {
+    return shelf_io.serve(expectAsync((_) {}, count: 0), 'localhost', 0)
+        .then((server) {
+      var ie = new InternetExplorer(
+          baseUrlForAddress(server.address, server.port));
+      return ie.close().whenComplete(server.close);
+    });
+  });
+
+  test("reports an error in onExit", () {
+    var ie = new InternetExplorer("http://dart-lang.org",
+        executable: "_does_not_exist");
+    expect(ie.onExit, throwsA(isApplicationException(startsWith(
+        "Failed to start Internet Explorer: $noSuchFileMessage"))));
+  });
+}
diff --git a/test/runner/runner_test.dart b/test/runner/runner_test.dart
index b60af6fe..634d22ce 100644
--- a/test/runner/runner_test.dart
+++ b/test/runner/runner_test.dart
@@ -40,7 +40,8 @@ final _defaultConcurrency = math.max(1, Platform.numberOfProcessors ~/ 2);
 
 final _browsers =
     "[vm (default), dartium, content-shell, chrome, phantomjs, firefox" +
-        (Platform.isMacOS ? ", safari" : "") + "]";
+        (Platform.isMacOS ? ", safari" : "") +
+        (Platform.isWindows ? ", ie" : "") + "]";
 
 final _usage = """
 Usage: pub run test:test [files or directories...]
-- 
GitLab