From 6719207be7861beb42b781bfc8011aa4a2fd115a Mon Sep 17 00:00:00 2001
From: Natalie Weizenbaum <nweiz@google.com>
Date: Thu, 6 Oct 2016 14:33:16 -0700
Subject: [PATCH] Add a JS API to restart the current test.

---
 lib/src/runner/browser/browser_manager.dart | 28 ++++++++++++++++-----
 lib/src/runner/browser/static/host.dart     |  8 +++++-
 lib/src/runner/browser/static/host.dart.js  |  9 ++++++-
 lib/src/runner/debugger.dart                |  9 +++++++
 lib/src/runner/environment.dart             |  8 ++++++
 lib/src/runner/plugin/environment.dart      |  3 +++
 6 files changed, 57 insertions(+), 8 deletions(-)

diff --git a/lib/src/runner/browser/browser_manager.dart b/lib/src/runner/browser/browser_manager.dart
index e8172bfa..66acacc9 100644
--- a/lib/src/runner/browser/browser_manager.dart
+++ b/lib/src/runner/browser/browser_manager.dart
@@ -68,6 +68,9 @@ class BrowserManager {
   /// screen.
   CancelableCompleter _pauseCompleter;
 
+  /// The controller for [_BrowserEnvironment.onRestart].
+  final _onRestartController = new StreamController();
+
   /// The environment to attach to each suite.
   Future<_BrowserEnvironment> _environment;
 
@@ -179,7 +182,7 @@ class BrowserManager {
   /// Loads [_BrowserEnvironment].
   Future<_BrowserEnvironment> _loadBrowserEnvironment() async {
     return new _BrowserEnvironment(this, await _browser.observatoryUrl,
-        await _browser.remoteDebuggerUrl);
+        await _browser.remoteDebuggerUrl, _onRestartController.stream);
   }
 
   /// Tells the browser the load a test suite from the URL [url].
@@ -259,11 +262,22 @@ class BrowserManager {
 
   /// The callback for handling messages received from the host page.
   void _onMessage(Map message) {
-    if (message["command"] == "ping") return;
+    switch (message["command"]) {
+      case "ping": break;
+
+      case "restart":
+        _onRestartController.add(null);
+        break;
+
+      case "resume":
+        if (_pauseCompleter != null) _pauseCompleter.complete();
+        break;
 
-    assert(message["command"] == "resume");
-    if (_pauseCompleter == null) return;
-    _pauseCompleter.complete();
+      default:
+        // Unreachable.
+        assert(false);
+        break;
+    }
   }
 
   /// Closes the manager and releases any resources it owns, including closing
@@ -291,8 +305,10 @@ class _BrowserEnvironment implements Environment {
 
   final Uri remoteDebuggerUrl;
 
+  final Stream onRestart;
+
   _BrowserEnvironment(this._manager, this.observatoryUrl,
-      this.remoteDebuggerUrl);
+      this.remoteDebuggerUrl, this.onRestart);
 
   CancelableOperation displayPause() => _manager._displayPause();
 }
diff --git a/lib/src/runner/browser/static/host.dart b/lib/src/runner/browser/static/host.dart
index c802b04a..675c70dd 100644
--- a/lib/src/runner/browser/static/host.dart
+++ b/lib/src/runner/browser/static/host.dart
@@ -31,7 +31,11 @@ class _JSApi {
   /// the "play" button.
   external Function get resume;
 
-  external factory _JSApi({void resume()});
+  /// Causes the test runner to restart the current test once it finishes
+  /// running.
+  external Function get restartCurrent;
+
+  external factory _JSApi({void resume(), void restartCurrent()});
 }
 
 /// Sets the top-level `dartTest` object so that it's visible to JS.
@@ -135,6 +139,8 @@ void main() {
     _jsApi = new _JSApi(resume: allowInterop(() {
       if (!document.body.classes.remove('paused')) return;
       serverChannel.sink.add({"command": "resume"});
+    }), restartCurrent: allowInterop(() {
+      serverChannel.sink.add({"command": "restart"});
     }));
   }, onError: (error, stackTrace) {
     print("$error\n${new Trace.from(stackTrace).terse}");
diff --git a/lib/src/runner/browser/static/host.dart.js b/lib/src/runner/browser/static/host.dart.js
index ab52b3ca..7aafafa3 100644
--- a/lib/src/runner/browser/static/host.dart.js
+++ b/lib/src/runner/browser/static/host.dart.js
@@ -15448,7 +15448,8 @@
         P.Timer_Timer$periodic(P.Duration$(0, 0, 0, 0, 0, 1), new M.main__closure0(serverChannel));
         t1 = J.get$onClick$x(document.querySelector("#play"));
         new W._EventStreamSubscription(0, t1._html$_target, t1._eventType, W._wrapZone(new M.main__closure1(serverChannel)), false, [H.getTypeArgumentByIndex(t1, 0)])._tryResume$0();
-        t1 = {resume: P.allowInterop(new M.main__closure2(serverChannel))};
+        t1 = P.allowInterop(new M.main__closure2(serverChannel));
+        t1 = {restartCurrent: P.allowInterop(new M.main__closure3(serverChannel)), resume: t1};
         self.dartTest = t1;
       }, null, null, 0, 0, null, "call"]
     },
@@ -15502,6 +15503,12 @@
         this.serverChannel._mainController._foreign._sink.add$1(0, P.LinkedHashMap__makeLiteral(["command", "resume"]));
       }, null, null, 0, 0, null, "call"]
     },
+    main__closure3: {
+      "^": "Closure:1;serverChannel",
+      call$0: [function() {
+        this.serverChannel._mainController._foreign._sink.add$1(0, P.LinkedHashMap__makeLiteral(["command", "restart"]));
+      }, null, null, 0, 0, null, "call"]
+    },
     main_closure0: {
       "^": "Closure:3;",
       call$2: [function(error, stackTrace) {
diff --git a/lib/src/runner/debugger.dart b/lib/src/runner/debugger.dart
index 305ff7fc..1b9fb634 100644
--- a/lib/src/runner/debugger.dart
+++ b/lib/src/runner/debugger.dart
@@ -73,6 +73,9 @@ class _Debugger {
   /// The subscription to [_suite.onDebugging].
   StreamSubscription<bool> _onDebuggingSubscription;
 
+  /// The subscription to [_suite.environment.onRestart].
+  StreamSubscription _onRestartSubscription;
+
   /// Whether [close] has been called.
   bool _closed = false;
 
@@ -91,6 +94,10 @@ class _Debugger {
         _onNotDebugging();
       }
     });
+
+    _onRestartSubscription = _suite.environment.onRestart.listen((_) {
+      _restartTest();
+    });
   }
 
   /// Runs the debugger.
@@ -189,6 +196,7 @@ class _Debugger {
 
   /// Restarts the current test.
   void _restartTest() {
+    if (_engine.active.isEmpty) return;
     var liveTest = _engine.active.single;
     _engine.restartTest(liveTest);
     if (!_json) {
@@ -201,6 +209,7 @@ class _Debugger {
   void close() {
     _closed = true;
     _onDebuggingSubscription.cancel();
+    _onRestartSubscription.cancel();
     _console.stop();
   }
 }
diff --git a/lib/src/runner/environment.dart b/lib/src/runner/environment.dart
index 694f9382..bd8afd15 100644
--- a/lib/src/runner/environment.dart
+++ b/lib/src/runner/environment.dart
@@ -2,6 +2,8 @@
 // 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.
 
+import 'dart:async';
+
 import 'package:async/async.dart';
 
 /// The abstract class of environments in which test suites are
@@ -18,6 +20,12 @@ abstract class Environment {
   /// enabled.
   Uri get remoteDebuggerUrl;
 
+  /// Emits a `null` event whenever the user tells the environment to restart
+  /// the current test once it's finished.
+  ///
+  /// Never emits an error, and never closes.
+  Stream get onRestart;
+
   /// Displays information indicating that the test runner is paused.
   ///
   /// The returned operation will complete when the user takes action within the
diff --git a/lib/src/runner/plugin/environment.dart b/lib/src/runner/plugin/environment.dart
index 90d5b3e8..417f8e5d 100644
--- a/lib/src/runner/plugin/environment.dart
+++ b/lib/src/runner/plugin/environment.dart
@@ -2,6 +2,8 @@
 // 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.
 
+import 'dart:async';
+
 import 'package:async/async.dart';
 
 import '../environment.dart';
@@ -9,6 +11,7 @@ import '../environment.dart';
 /// The default environment for platform plugins.
 class PluginEnvironment implements Environment {
   final supportsDebugging = false;
+  Stream get onRestart => new StreamController().stream;
 
   const PluginEnvironment();
 
-- 
GitLab