| OLD | NEW |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library test.runner.browser.content_shell; | 5 library test.runner.browser.content_shell; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:convert'; | 8 import 'dart:convert'; |
| 9 import 'dart:io'; | 9 import 'dart:io'; |
| 10 | 10 |
| 11 import 'package:stack_trace/stack_trace.dart'; | |
| 12 | |
| 13 import '../../utils.dart'; | |
| 14 import '../application_exception.dart'; | 11 import '../application_exception.dart'; |
| 15 import 'browser.dart'; | 12 import 'browser.dart'; |
| 16 | 13 |
| 17 /// A converter that transforms a byte stream into a stream of lines. | 14 /// A converter that transforms a byte stream into a stream of lines. |
| 18 final _lines = UTF8.decoder.fuse(const LineSplitter()); | 15 final _lines = UTF8.decoder.fuse(const LineSplitter()); |
| 19 | 16 |
| 20 /// A class for running an instance of the Dartium content shell. | 17 /// A class for running an instance of the Dartium content shell. |
| 21 /// | 18 /// |
| 22 /// Most of the communication with the browser is expected to happen via HTTP, | 19 /// Most of the communication with the browser is expected to happen via HTTP, |
| 23 /// so this exposes a bare-bones API. The browser starts as soon as the class is | 20 /// so this exposes a bare-bones API. The browser starts as soon as the class is |
| 24 /// constructed, and is killed when [close] is called. | 21 /// constructed, and is killed when [close] is called. |
| 25 /// | 22 /// |
| 26 /// Any errors starting or running the process are reported through [onExit]. | 23 /// Any errors starting or running the process are reported through [onExit]. |
| 27 class ContentShell implements Browser { | 24 class ContentShell extends Browser { |
| 28 /// The underlying process. | 25 final name = "Content Shell"; |
| 29 Process _process; | |
| 30 | 26 |
| 31 Future get onExit => _onExitCompleter.future; | 27 ContentShell(url, {String executable}) |
| 32 final _onExitCompleter = new Completer(); | 28 : super(() => _startBrowser(url, executable)); |
| 33 | |
| 34 /// A future that completes when the browser process has started. | |
| 35 /// | |
| 36 /// This is used to ensure that [close] works regardless of when it's called. | |
| 37 Future get _onProcessStarted => _onProcessStartedCompleter.future; | |
| 38 final _onProcessStartedCompleter = new Completer(); | |
| 39 | 29 |
| 40 /// Starts a new instance of content shell open to the given [url], which may | 30 /// Starts a new instance of content shell open to the given [url], which may |
| 41 /// be a [Uri] or a [String]. | 31 /// be a [Uri] or a [String]. |
| 42 /// | 32 /// |
| 43 /// If [executable] is passed, it's used as the content shell executable. | 33 /// If [executable] is passed, it's used as the content shell executable. |
| 44 /// Otherwise the default executable name for the current OS will be used. | 34 /// Otherwise the default executable name for the current OS will be used. |
| 45 ContentShell(url, {String executable}) { | 35 static Future<Process> _startBrowser(url, [String executable]) async { |
| 46 if (executable == null) executable = _defaultExecutable(); | 36 if (executable == null) executable = _defaultExecutable(); |
| 47 | 37 |
| 48 // Don't return a Future here because there's no need for the caller to wait | 38 var process = await Process.start( |
| 49 // for the process to actually start. They should just wait for the HTTP | 39 executable, ["--dump-render-tree", url.toString()], |
| 50 // request instead. | 40 environment: {"DART_FLAGS": "--checked"}); |
| 51 invoke(() async { | |
| 52 // Whether we killed content shell because it used an expired Dart | |
| 53 // version. | |
| 54 var expired = false; | |
| 55 | 41 |
| 56 try { | 42 _lines.bind(process.stderr).listen((line) { |
| 57 var process = await Process.start( | 43 if (line != "[dartToStderr]: Dartium build has expired") return; |
| 58 executable, ["--dump-render-tree", url.toString()], | |
| 59 environment: {"DART_FLAGS": "--checked"}); | |
| 60 | 44 |
| 61 _lines.bind(process.stderr).listen((line) { | 45 // TODO(nweiz): link to dartlang.org once it has download links for |
| 62 if (line != "[dartToStderr]: Dartium build has expired") return; | 46 // content shell |
| 63 expired = true; | 47 // (https://github.com/dart-lang/www.dartlang.org/issues/1164). |
| 64 process.kill(); | 48 throw new ApplicationException( |
| 65 }); | 49 "You're using an expired content_shell. Upgrade to the latest " |
| 50 "version:\n" |
| 51 "http://gsdview.appspot.com/dart-archive/channels/stable/release/" |
| 52 "latest/dartium/"); |
| 53 }); |
| 66 | 54 |
| 67 _process = process; | 55 return process; |
| 68 _onProcessStartedCompleter.complete(); | |
| 69 var exitCode = await _process.exitCode; | |
| 70 | |
| 71 if (expired) { | |
| 72 // TODO(nweiz): link to dartlang.org once it has download links for | |
| 73 // content shell | |
| 74 // (https://github.com/dart-lang/www.dartlang.org/issues/1164). | |
| 75 throw new ApplicationException( | |
| 76 "You're using an expired content_shell. Upgrade to the latest " | |
| 77 "version:\n" | |
| 78 "http://gsdview.appspot.com/dart-archive/channels/stable/release/" | |
| 79 "latest/dartium/"); | |
| 80 } | |
| 81 | |
| 82 if (exitCode != 0) { | |
| 83 var error = await UTF8.decodeStream(_process.stderr); | |
| 84 throw new ApplicationException( | |
| 85 "Content shell failed with exit code $exitCode:\n$error"); | |
| 86 } | |
| 87 | |
| 88 _onExitCompleter.complete(); | |
| 89 } catch (error, stackTrace) { | |
| 90 if (stackTrace == null) stackTrace = new Trace.current(); | |
| 91 _onExitCompleter.completeError( | |
| 92 new ApplicationException( | |
| 93 "Failed to start content shell: ${getErrorMessage(error)}."), | |
| 94 stackTrace); | |
| 95 } | |
| 96 }); | |
| 97 } | |
| 98 | |
| 99 Future close() { | |
| 100 _onProcessStarted.then((_) => _process.kill()); | |
| 101 | |
| 102 // Swallow exceptions. The user should explicitly use [onExit] for these. | |
| 103 return onExit.catchError((_) {}); | |
| 104 } | 56 } |
| 105 | 57 |
| 106 /// Return the default executable for the current operating system. | 58 /// Return the default executable for the current operating system. |
| 107 String _defaultExecutable() => | 59 static String _defaultExecutable() => |
| 108 Platform.isWindows ? "content_shell.exe" : "content_shell"; | 60 Platform.isWindows ? "content_shell.exe" : "content_shell"; |
| 109 } | 61 } |
| OLD | NEW |