Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(373)

Side by Side Diff: lib/src/runner/browser/browser_manager.dart

Issue 1258363003: Expose the Observatory URL when debugging. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/src/runner/browser/browser.dart ('k') | lib/src/runner/browser/content_shell.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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.browser_manager; 5 library test.runner.browser.browser_manager;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:convert'; 8 import 'dart:convert';
9 9
10 import 'package:async/async.dart';
10 import 'package:http_parser/http_parser.dart'; 11 import 'package:http_parser/http_parser.dart';
11 import 'package:pool/pool.dart'; 12 import 'package:pool/pool.dart';
12 13
13 import '../../backend/metadata.dart'; 14 import '../../backend/metadata.dart';
14 import '../../backend/test_platform.dart'; 15 import '../../backend/test_platform.dart';
15 import '../../util/cancelable_future.dart'; 16 import '../../util/cancelable_future.dart';
16 import '../../util/multi_channel.dart'; 17 import '../../util/multi_channel.dart';
17 import '../../util/remote_exception.dart'; 18 import '../../util/remote_exception.dart';
18 import '../../util/stack_trace_mapper.dart'; 19 import '../../util/stack_trace_mapper.dart';
19 import '../../utils.dart'; 20 import '../../utils.dart';
21 import '../application_exception.dart';
20 import '../environment.dart'; 22 import '../environment.dart';
21 import '../load_exception.dart'; 23 import '../load_exception.dart';
22 import '../runner_suite.dart'; 24 import '../runner_suite.dart';
25 import 'browser.dart';
26 import 'chrome.dart';
27 import 'content_shell.dart';
28 import 'dartium.dart';
29 import 'firefox.dart';
23 import 'iframe_test.dart'; 30 import 'iframe_test.dart';
31 import 'internet_explorer.dart';
32 import 'phantom_js.dart';
33 import 'safari.dart';
24 34
25 /// A class that manages the connection to a single running browser. 35 /// A class that manages the connection to a single running browser.
26 /// 36 ///
27 /// This is in charge of telling the browser which test suites to load and 37 /// This is in charge of telling the browser which test suites to load and
28 /// converting its responses into [Suite] objects. 38 /// converting its responses into [Suite] objects.
29 class BrowserManager { 39 class BrowserManager {
30 /// The browser that this is managing. 40 /// The browser instance that this is connected to via [_channel].
31 final TestPlatform browser; 41 final Browser _browser;
42
43 // TODO(nweiz): Consider removing the duplication between this and
44 // [_browser.name].
45 /// The [TestPlatform] for [_browser].
46 final TestPlatform _platform;
32 47
33 /// The channel used to communicate with the browser. 48 /// The channel used to communicate with the browser.
34 /// 49 ///
35 /// This is connected to a page running `static/host.dart`. 50 /// This is connected to a page running `static/host.dart`.
36 final MultiChannel _channel; 51 final MultiChannel _channel;
37 52
38 /// A pool that ensures that limits the number of initial connections the 53 /// A pool that ensures that limits the number of initial connections the
39 /// manager will wait for at once. 54 /// manager will wait for at once.
40 /// 55 ///
41 /// This isn't the *total* number of connections; any number of iframes may be 56 /// This isn't the *total* number of connections; any number of iframes may be
(...skipping 11 matching lines...) Expand all
53 /// Whether the channel to the browser has closed. 68 /// Whether the channel to the browser has closed.
54 bool _closed = false; 69 bool _closed = false;
55 70
56 /// The completer for [_BrowserEnvironment.displayPause]. 71 /// The completer for [_BrowserEnvironment.displayPause].
57 /// 72 ///
58 /// This will be `null` as long as the browser isn't displaying a pause 73 /// This will be `null` as long as the browser isn't displaying a pause
59 /// screen. 74 /// screen.
60 CancelableCompleter _pauseCompleter; 75 CancelableCompleter _pauseCompleter;
61 76
62 /// The environment to attach to each suite. 77 /// The environment to attach to each suite.
63 _BrowserEnvironment _environment; 78 Future<_BrowserEnvironment> _environment;
79
80 /// Starts the browser identified by [platform] and has it connect to [url].
81 ///
82 /// [url] should serve a page that establishes a WebSocket connection with
83 /// this process. That connection, once established, should be emitted via
84 /// [future].
85 ///
86 /// Returns the browser manager, or throws an [ApplicationException] if a
87 /// connection fails to be established.
88 static Future<BrowserManager> start(TestPlatform platform, Uri url,
89 Future<CompatibleWebSocket> future) {
90 var browser = _newBrowser(url, platform);
91
92 var completer = new Completer();
93
94 // TODO(nweiz): Gracefully handle the browser being killed before the
95 // tests complete.
96 browser.onExit.then((_) {
97 throw new ApplicationException(
98 "${platform.name} exited before connecting.");
99 }).catchError((error, stackTrace) {
100 if (completer.isCompleted) return;
101 completer.completeError(error, stackTrace);
102 });
103
104 future.then((webSocket) {
105 if (completer.isCompleted) return;
106 completer.complete(new BrowserManager._(browser, platform, webSocket));
107 }).catchError((error, stackTrace) {
108 browser.close();
109 if (completer.isCompleted) return;
110 completer.completeError(error, stackTrace);
111 });
112
113 return completer.future.timeout(new Duration(seconds: 30), onTimeout: () {
114 browser.close();
115 throw new ApplicationException(
116 "Timed out waiting for ${platform.name} to connect.");
117 });
118 }
119
120 /// Starts the browser identified by [browser] and has it load [url].
121 static Browser _newBrowser(Uri url, TestPlatform browser) {
122 switch (browser) {
123 case TestPlatform.dartium: return new Dartium(url);
124 case TestPlatform.contentShell: return new ContentShell(url);
125 case TestPlatform.chrome: return new Chrome(url);
126 case TestPlatform.phantomJS: return new PhantomJS(url);
127 case TestPlatform.firefox: return new Firefox(url);
128 case TestPlatform.safari: return new Safari(url);
129 case TestPlatform.internetExplorer: return new InternetExplorer(url);
130 default:
131 throw new ArgumentError("$browser is not a browser.");
132 }
133 }
64 134
65 /// Creates a new BrowserManager that communicates with [browser] over 135 /// Creates a new BrowserManager that communicates with [browser] over
66 /// [webSocket]. 136 /// [webSocket].
67 BrowserManager(this.browser, CompatibleWebSocket webSocket) 137 BrowserManager._(this._browser, this._platform, CompatibleWebSocket webSocket)
68 : _channel = new MultiChannel( 138 : _channel = new MultiChannel(
69 webSocket.map(JSON.decode), 139 webSocket.map(JSON.decode),
70 mapSink(webSocket, JSON.encode)) { 140 mapSink(webSocket, JSON.encode)) {
71 _environment = new _BrowserEnvironment(this); 141 _environment = _loadBrowserEnvironment();
72 _channel.stream.listen(_onMessage, onDone: _onDone); 142 _channel.stream.listen(_onMessage, onDone: close);
143 }
144
145 /// Loads [_BrowserEnvironment].
146 Future<_BrowserEnvironment> _loadBrowserEnvironment() async {
147 var observatoryUrl;
148 if (_platform.isDartVM) observatoryUrl = await _browser.observatoryUrl;
149 return new _BrowserEnvironment(this, observatoryUrl);
73 } 150 }
74 151
75 /// Tells the browser the load a test suite from the URL [url]. 152 /// Tells the browser the load a test suite from the URL [url].
76 /// 153 ///
77 /// [url] should be an HTML page with a reference to the JS-compiled test 154 /// [url] should be an HTML page with a reference to the JS-compiled test
78 /// suite. [path] is the path of the original test suite file, which is used 155 /// suite. [path] is the path of the original test suite file, which is used
79 /// for reporting. [metadata] is the parsed metadata for the test suite. 156 /// for reporting. [metadata] is the parsed metadata for the test suite.
80 /// 157 ///
81 /// If [mapper] is passed, it's used to map stack traces for errors coming 158 /// If [mapper] is passed, it's used to map stack traces for errors coming
82 /// from this test suite. 159 /// from this test suite.
83 Future<RunnerSuite> loadSuite(String path, Uri url, Metadata metadata, 160 Future<RunnerSuite> loadSuite(String path, Uri url, Metadata metadata,
84 {StackTraceMapper mapper}) async { 161 {StackTraceMapper mapper}) async {
85 url = url.replace(fragment: Uri.encodeFull(JSON.encode({ 162 url = url.replace(fragment: Uri.encodeFull(JSON.encode({
86 "metadata": metadata.serialize(), 163 "metadata": metadata.serialize(),
87 "browser": browser.identifier 164 "browser": _platform.identifier
88 }))); 165 })));
89 166
90 // The stream may close before emitting a value if the browser is killed 167 // The stream may close before emitting a value if the browser is killed
91 // prematurely (e.g. via Control-C). 168 // prematurely (e.g. via Control-C).
92 var suiteVirtualChannel = _channel.virtualChannel(); 169 var suiteVirtualChannel = _channel.virtualChannel();
93 var suiteId = _suiteId++; 170 var suiteId = _suiteId++;
94 var suiteChannel; 171 var suiteChannel;
95 172
96 closeIframe() { 173 closeIframe() {
97 if (_closed) return; 174 if (_closed) return;
(...skipping 25 matching lines...) Expand all
123 completer.complete(response); 200 completer.complete(response);
124 } 201 }
125 }, onDone: () { 202 }, onDone: () {
126 if (!completer.isCompleted) completer.complete(); 203 if (!completer.isCompleted) completer.complete();
127 }); 204 });
128 205
129 return completer.future.timeout(new Duration(minutes: 1), onTimeout: () { 206 return completer.future.timeout(new Duration(minutes: 1), onTimeout: () {
130 throw new LoadException( 207 throw new LoadException(
131 path, 208 path,
132 "Timed out waiting for the test suite to connect on " 209 "Timed out waiting for the test suite to connect on "
133 "${browser.name}."); 210 "${_platform.name}.");
134 }); 211 });
135 }); 212 });
136 213
137 if (response == null) { 214 if (response == null) {
138 closeIframe(); 215 closeIframe();
139 return null; 216 throw new LoadException(
217 path, "Connection closed before test suite loaded.");
140 } 218 }
141 219
142 if (response["type"] == "loadException") { 220 if (response["type"] == "loadException") {
143 closeIframe(); 221 closeIframe();
144 throw new LoadException(path, response["message"]); 222 throw new LoadException(path, response["message"]);
145 } 223 }
146 224
147 if (response["type"] == "error") { 225 if (response["type"] == "error") {
148 closeIframe(); 226 closeIframe();
149 var asyncError = RemoteException.deserialize(response["error"]); 227 var asyncError = RemoteException.deserialize(response["error"]);
150 await new Future.error( 228 await new Future.error(
151 new LoadException(path, asyncError.error), 229 new LoadException(path, asyncError.error),
152 asyncError.stackTrace); 230 asyncError.stackTrace);
153 } 231 }
154 232
155 return new RunnerSuite(_environment, response["tests"].map((test) { 233 return new RunnerSuite(await _environment, response["tests"].map((test) {
156 var testMetadata = new Metadata.deserialize(test['metadata']); 234 var testMetadata = new Metadata.deserialize(test['metadata']);
157 var testChannel = suiteChannel.virtualChannel(test['channel']); 235 var testChannel = suiteChannel.virtualChannel(test['channel']);
158 return new IframeTest(test['name'], testMetadata, testChannel, 236 return new IframeTest(test['name'], testMetadata, testChannel,
159 mapper: mapper); 237 mapper: mapper);
160 }), platform: browser, metadata: metadata, path: path, 238 }), platform: _platform, metadata: metadata, path: path,
161 onClose: () => closeIframe()); 239 onClose: () => closeIframe());
162 } 240 }
163 241
164 /// An implementation of [Environment.displayPause]. 242 /// An implementation of [Environment.displayPause].
165 CancelableFuture _displayPause() { 243 CancelableFuture _displayPause() {
166 if (_pauseCompleter != null) return _pauseCompleter.future; 244 if (_pauseCompleter != null) return _pauseCompleter.future;
167 245
168 _pauseCompleter = new CancelableCompleter(() { 246 _pauseCompleter = new CancelableCompleter(() {
169 _channel.sink.add({"command": "resume"}); 247 _channel.sink.add({"command": "resume"});
170 _pauseCompleter = null; 248 _pauseCompleter = null;
171 }); 249 });
172 250
173 _channel.sink.add({"command": "displayPause"}); 251 _channel.sink.add({"command": "displayPause"});
174 return _pauseCompleter.future.whenComplete(() { 252 return _pauseCompleter.future.whenComplete(() {
175 _pauseCompleter = null; 253 _pauseCompleter = null;
176 }); 254 });
177 } 255 }
178 256
179 /// The callback for handling messages received from the host page. 257 /// The callback for handling messages received from the host page.
180 void _onMessage(Map message) { 258 void _onMessage(Map message) {
181 assert(message["command"] == "resume"); 259 assert(message["command"] == "resume");
182 if (_pauseCompleter == null) return; 260 if (_pauseCompleter == null) return;
183 _pauseCompleter.complete(); 261 _pauseCompleter.complete();
184 } 262 }
185 263
186 /// The callback called when the WebSocket is closed. 264 /// Closes the manager and releases any resources it owns, including closing
187 void _onDone() { 265 /// the browser.
266 Future close() => _closeMemoizer.runOnce(() {
188 _closed = true; 267 _closed = true;
189 if (_pauseCompleter != null) _pauseCompleter.complete(); 268 if (_pauseCompleter != null) _pauseCompleter.complete();
190 _pauseCompleter = null; 269 _pauseCompleter = null;
191 } 270 return _browser.close();
271 });
272 final _closeMemoizer = new AsyncMemoizer();
192 } 273 }
193 274
194 /// An implementation of [Environment] for the browser. 275 /// An implementation of [Environment] for the browser.
195 /// 276 ///
196 /// All methods forward directly to [BrowserManager]. 277 /// All methods forward directly to [BrowserManager].
197 class _BrowserEnvironment implements Environment { 278 class _BrowserEnvironment implements Environment {
198 final BrowserManager _manager; 279 final BrowserManager _manager;
199 280
200 _BrowserEnvironment(this._manager); 281 final Uri observatoryUrl;
282
283 _BrowserEnvironment(this._manager, this.observatoryUrl);
201 284
202 CancelableFuture displayPause() => _manager._displayPause(); 285 CancelableFuture displayPause() => _manager._displayPause();
203 } 286 }
OLDNEW
« no previous file with comments | « lib/src/runner/browser/browser.dart ('k') | lib/src/runner/browser/content_shell.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698