| OLD | NEW | 
| (Empty) |  | 
 |    1 // Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file | 
 |    2 // for details. All rights reserved. Use of this source code is governed by a | 
 |    3 // BSD-style license that can be found in the LICENSE file. | 
 |    4  | 
 |    5 library test.runner.browser.browser; | 
 |    6  | 
 |    7 import 'dart:async'; | 
 |    8 import 'dart:io'; | 
 |    9  | 
 |   10 import 'package:stack_trace/stack_trace.dart'; | 
 |   11  | 
 |   12 import '../../utils.dart'; | 
 |   13 import '../application_exception.dart'; | 
 |   14  | 
 |   15 typedef Future<Process> StartBrowserFn(); | 
 |   16  | 
 |   17 /// An interface for running browser instances. | 
 |   18 /// | 
 |   19 /// This is intentionally coarse-grained: browsers are controlled primary from | 
 |   20 /// inside a single tab. Thus this interface only provides support for closing | 
 |   21 /// the browser and seeing if it closes itself. | 
 |   22 /// | 
 |   23 /// Any errors starting or running the browser process are reported through | 
 |   24 /// [onExit]. | 
 |   25 abstract class Browser { | 
 |   26   String get name; | 
 |   27  | 
 |   28   /// The Observatory URL for this browser. | 
 |   29   /// | 
 |   30   /// This will throw an [UnsupportedError] for browsers that aren't running the | 
 |   31   /// Dart VM, and return `null` if the Observatory URL can't be found. | 
 |   32   Future<Uri> get observatoryUrl => | 
 |   33       throw new UnsupportedError("$name doesn't support Observatory."); | 
 |   34  | 
 |   35   /// The remote debugger URL for this browser. | 
 |   36   /// | 
 |   37   /// This will throw an [UnsupportedError] for browsers that don't support | 
 |   38   /// remote debugging, and return `null` if the remote debugging URL can't be | 
 |   39   /// found. | 
 |   40   Future<Uri> get remoteDebuggerUrl => | 
 |   41       throw new UnsupportedError("$name doesn't support remote debugging."); | 
 |   42  | 
 |   43   /// The underlying process. | 
 |   44   /// | 
 |   45   /// This will fire once the process has started successfully. | 
 |   46   Future<Process> get _process => _processCompleter.future; | 
 |   47   final _processCompleter = new Completer<Process>(); | 
 |   48  | 
 |   49   /// Whether [close] has been called. | 
 |   50   var _closed = false; | 
 |   51  | 
 |   52   /// A future that completes when the browser exits. | 
 |   53   /// | 
 |   54   /// If there's a problem starting or running the browser, this will complete | 
 |   55   /// with an error. | 
 |   56   Future get onExit => _onExitCompleter.future; | 
 |   57   final _onExitCompleter = new Completer(); | 
 |   58  | 
 |   59   /// Creates a new browser. | 
 |   60   /// | 
 |   61   /// This is intended to be called by subclasses. They pass in [startBrowser], | 
 |   62   /// which asynchronously returns the browser process. Any errors in | 
 |   63   /// [startBrowser] (even those raised asynchronously after it returns) are | 
 |   64   /// piped to [onExit] and will cause the browser to be killed. | 
 |   65   Browser(Future<Process> startBrowser()) { | 
 |   66     // Don't return a Future here because there's no need for the caller to wait | 
 |   67     // for the process to actually start. They should just wait for the HTTP | 
 |   68     // request instead. | 
 |   69     runZoned(() async { | 
 |   70       var process = await startBrowser(); | 
 |   71       _processCompleter.complete(process); | 
 |   72  | 
 |   73       var exitCode = await process.exitCode; | 
 |   74  | 
 |   75       // This hack dodges an otherwise intractable race condition. When the user | 
 |   76       // presses Control-C, the signal is sent to the browser and the test | 
 |   77       // runner at the same time. It's possible for the browser to exit before | 
 |   78       // the [Browser.close] is called, which would trigger the error below. | 
 |   79       // | 
 |   80       // A negative exit code signals that the process exited due to a signal. | 
 |   81       // However, it's possible that this signal didn't come from the user's | 
 |   82       // Control-C, in which case we do want to throw the error. The only way to | 
 |   83       // resolve the ambiguity is to wait a brief amount of time and see if this | 
 |   84       // browser is actually closed. | 
 |   85       if (!_closed && exitCode < 0) { | 
 |   86         await new Future.delayed(new Duration(milliseconds: 200)); | 
 |   87       } | 
 |   88  | 
 |   89       if (!_closed && exitCode != 0) { | 
 |   90         throw new ApplicationException( | 
 |   91             "$name failed with exit code $exitCode."); | 
 |   92       } | 
 |   93  | 
 |   94       _onExitCompleter.complete(); | 
 |   95     }, onError: (error, stackTrace) { | 
 |   96       // Ignore any errors after the browser has been closed. | 
 |   97       if (_closed) return; | 
 |   98  | 
 |   99       // Make sure the process dies even if the error wasn't fatal. | 
 |  100       _process.then((process) => process.kill()); | 
 |  101  | 
 |  102       if (stackTrace == null) stackTrace = new Trace.current(); | 
 |  103       _onExitCompleter.completeError( | 
 |  104           new ApplicationException( | 
 |  105               "Failed to run $name: ${getErrorMessage(error)}."), | 
 |  106           stackTrace); | 
 |  107     }); | 
 |  108   } | 
 |  109  | 
 |  110   /// Kills the browser process. | 
 |  111   /// | 
 |  112   /// Returns the same [Future] as [onExit], except that it won't emit | 
 |  113   /// exceptions. | 
 |  114   Future close() { | 
 |  115     _closed = true; | 
 |  116     _process.then((process) => process.kill()); | 
 |  117  | 
 |  118     // Swallow exceptions. The user should explicitly use [onExit] for these. | 
 |  119     return onExit.catchError((_) {}); | 
 |  120   } | 
 |  121 } | 
| OLD | NEW |