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 |