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

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

Issue 1070313002: Add firefox support. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Don't double-compile test suites Created 5 years, 8 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/firefox.dart ('k') | lib/src/runner/loader.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.server; 5 library test.runner.browser.server;
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:path/path.dart' as p; 11 import 'package:path/path.dart' as p;
12 import 'package:pool/pool.dart'; 12 import 'package:pool/pool.dart';
13 import 'package:shelf/shelf.dart' as shelf; 13 import 'package:shelf/shelf.dart' as shelf;
14 import 'package:shelf/shelf_io.dart' as shelf_io; 14 import 'package:shelf/shelf_io.dart' as shelf_io;
15 import 'package:shelf_static/shelf_static.dart'; 15 import 'package:shelf_static/shelf_static.dart';
16 import 'package:shelf_web_socket/shelf_web_socket.dart'; 16 import 'package:shelf_web_socket/shelf_web_socket.dart';
17 17
18 import '../../backend/suite.dart'; 18 import '../../backend/suite.dart';
19 import '../../backend/test_platform.dart';
19 import '../../util/io.dart'; 20 import '../../util/io.dart';
20 import '../../util/one_off_handler.dart'; 21 import '../../util/one_off_handler.dart';
21 import '../../utils.dart'; 22 import '../../utils.dart';
22 import '../load_exception.dart'; 23 import '../load_exception.dart';
24 import 'browser.dart';
23 import 'browser_manager.dart'; 25 import 'browser_manager.dart';
24 import 'compiler_pool.dart'; 26 import 'compiler_pool.dart';
25 import 'chrome.dart'; 27 import 'chrome.dart';
28 import 'firefox.dart';
26 29
27 /// A server that serves JS-compiled tests to browsers. 30 /// A server that serves JS-compiled tests to browsers.
28 /// 31 ///
29 /// A test suite may be loaded for a given file using [loadSuite]. 32 /// A test suite may be loaded for a given file using [loadSuite].
30 class BrowserServer { 33 class BrowserServer {
31 /// Starts the server. 34 /// Starts the server.
32 /// 35 ///
33 /// If [packageRoot] is passed, it's used for all package imports when 36 /// If [packageRoot] is passed, it's used for all package imports when
34 /// compiling tests to JS. Otherwise, the package root is inferred from the 37 /// compiling tests to JS. Otherwise, the package root is inferred from the
35 /// location of the source file. 38 /// location of the source file.
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
75 78
76 /// The pool of active `pub serve` compilations. 79 /// The pool of active `pub serve` compilations.
77 /// 80 ///
78 /// Pub itself ensures that only one compilation runs at a time; we just use 81 /// Pub itself ensures that only one compilation runs at a time; we just use
79 /// this pool to make sure that the output is nice and linear. 82 /// this pool to make sure that the output is nice and linear.
80 final _pubServePool = new Pool(1); 83 final _pubServePool = new Pool(1);
81 84
82 /// The HTTP client to use when caching JS files in `pub serve`. 85 /// The HTTP client to use when caching JS files in `pub serve`.
83 final HttpClient _http; 86 final HttpClient _http;
84 87
85 /// The browser in which test suites are loaded and run.
86 ///
87 /// This is `null` until a suite is loaded.
88 Chrome _browser;
89
90 /// Whether [close] has been called. 88 /// Whether [close] has been called.
91 bool get _closed => _closeCompleter != null; 89 bool get _closed => _closeCompleter != null;
92 90
93 /// The completer for the [Future] returned by [close]. 91 /// The completer for the [Future] returned by [close].
94 Completer _closeCompleter; 92 Completer _closeCompleter;
95 93
96 /// A future that will complete to the [BrowserManager] for [_browser]. 94 /// All currently-running browsers.
97 /// 95 ///
98 /// The first time this is called, it will start both the browser and the 96 /// These are controlled by [_browserManager]s.
99 /// browser manager. Any further calls will return the existing manager. 97 final _browsers = new Map<TestPlatform, Browser>();
100 Future<BrowserManager> get _browserManager {
101 if (_browserManagerCompleter == null) {
102 _browserManagerCompleter = new Completer();
103 var path = _webSocketHandler.create(webSocketHandler((webSocket) {
104 _browserManagerCompleter.complete(new BrowserManager(webSocket));
105 }));
106 98
107 var webSocketUrl = url.replace(scheme: 'ws', path: '/$path'); 99 /// A map from browser identifiers to futures that will complete to the
100 /// [BrowserManager]s for those browsers.
101 ///
102 /// This should only be accessed through [_browserManagerFor].
103 final _browserManagers = new Map<TestPlatform, Future<BrowserManager>>();
108 104
109 var hostUrl = url; 105 /// A map from test suite paths to Futures that will complete once those
110 if (_pubServeUrl != null) { 106 /// suites are finished compiling.
111 hostUrl = _pubServeUrl.resolve( 107 ///
112 '/packages/test/src/runner/browser/static/'); 108 /// This is used to make sure that a given test suite is only compiled once
113 } 109 /// per run, rather than one per browser per run.
114 110 final _compileFutures = new Map<String, Future>();
115 _browser = new Chrome(hostUrl.replace(queryParameters: {
116 'managerUrl': webSocketUrl.toString()
117 }));
118
119 // TODO(nweiz): Gracefully handle the browser being killed before the
120 // tests complete.
121 _browser.onExit.catchError((error, stackTrace) {
122 if (_browserManagerCompleter.isCompleted) return;
123 _browserManagerCompleter.completeError(error, stackTrace);
124 });
125 }
126 return _browserManagerCompleter.future;
127 }
128 Completer<BrowserManager> _browserManagerCompleter;
129 111
130 BrowserServer._(this._packageRoot, Uri pubServeUrl, bool color) 112 BrowserServer._(this._packageRoot, Uri pubServeUrl, bool color)
131 : _pubServeUrl = pubServeUrl, 113 : _pubServeUrl = pubServeUrl,
132 _compiledDir = pubServeUrl == null ? createTempDir() : null, 114 _compiledDir = pubServeUrl == null ? createTempDir() : null,
133 _http = pubServeUrl == null ? null : new HttpClient(), 115 _http = pubServeUrl == null ? null : new HttpClient(),
134 _compilers = new CompilerPool(color: color); 116 _compilers = new CompilerPool(color: color);
135 117
136 /// Starts the underlying server. 118 /// Starts the underlying server.
137 Future _load() { 119 Future _load() {
138 var cascade = new shelf.Cascade() 120 var cascade = new shelf.Cascade()
139 .add(_webSocketHandler.handler); 121 .add(_webSocketHandler.handler);
140 122
141 if (_pubServeUrl == null) { 123 if (_pubServeUrl == null) {
142 var staticPath = p.join(libDir(packageRoot: _packageRoot), 124 var staticPath = p.join(libDir(packageRoot: _packageRoot),
143 'src/runner/browser/static'); 125 'src/runner/browser/static');
144 cascade = cascade 126 cascade = cascade
145 .add(createStaticHandler(staticPath, defaultDocument: 'index.html')) 127 .add(createStaticHandler(staticPath, defaultDocument: 'index.html'))
146 .add(createStaticHandler(_compiledDir, 128 .add(createStaticHandler(_compiledDir,
147 defaultDocument: 'index.html')); 129 defaultDocument: 'index.html'));
148 } 130 }
149 131
150 return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) { 132 return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) {
151 _server = server; 133 _server = server;
152 }); 134 });
153 } 135 }
154 136
155 /// Loads the test suite at [path]. 137 /// Loads the test suite at [path] on the browser [browser].
156 /// 138 ///
157 /// This will start a browser to load the suite if one isn't already running. 139 /// This will start a browser to load the suite if one isn't already running.
158 Future<Suite> loadSuite(String path) { 140 /// Throws an [ArgumentError] if [browser] isn't a browser platform.
141 Future<Suite> loadSuite(String path, TestPlatform browser) {
142 if (!browser.isBrowser) {
143 throw new ArgumentError("$browser is not a browser.");
144 }
145
159 return new Future.sync(() { 146 return new Future.sync(() {
160 if (_pubServeUrl != null) { 147 if (_pubServeUrl != null) {
161 var suitePrefix = p.withoutExtension(p.relative(path, from: 'test')) + 148 var suitePrefix = p.withoutExtension(p.relative(path, from: 'test')) +
162 '.browser_test'; 149 '.browser_test';
163 var jsUrl = _pubServeUrl.resolve('$suitePrefix.dart.js'); 150 var jsUrl = _pubServeUrl.resolve('$suitePrefix.dart.js');
164 return _pubServeSuite(path, jsUrl) 151 return _pubServeSuite(path, jsUrl)
165 .then((_) => _pubServeUrl.resolve('$suitePrefix.html')); 152 .then((_) => _pubServeUrl.resolve('$suitePrefix.html'));
166 } else { 153 } else {
167 return _compileSuite(path).then((dir) { 154 return _compileSuite(path).then((dir) {
168 if (_closed) return null; 155 if (_closed) return null;
169 156
170 // Add a trailing slash because at least on Chrome, the iframe's 157 // Add a trailing slash because at least on Chrome, the iframe's
171 // window.location.href will do so automatically, and if that differs 158 // window.location.href will do so automatically, and if that differs
172 // from the original URL communication will fail. 159 // from the original URL communication will fail.
173 return url.resolve( 160 return url.resolve(
174 "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/"); 161 "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/");
175 }); 162 });
176 } 163 }
177 }).then((suiteUrl) { 164 }).then((suiteUrl) {
178 if (_closed) return null; 165 if (_closed) return null;
179 166
180 // TODO(nweiz): Don't start the browser until all the suites are compiled. 167 // TODO(nweiz): Don't start the browser until all the suites are compiled.
181 return _browserManager.then((browserManager) { 168 return _browserManagerFor(browser).then((browserManager) {
182 if (_closed) return null; 169 if (_closed) return null;
183 return browserManager.loadSuite(path, suiteUrl); 170 return browserManager.loadSuite(path, suiteUrl);
184 }); 171 });
185 }); 172 });
186 } 173 }
187 174
188 /// Loads a test suite at [path] from the `pub serve` URL [jsUrl]. 175 /// Loads a test suite at [path] from the `pub serve` URL [jsUrl].
189 /// 176 ///
190 /// This ensures that only one suite is loaded at a time, and that any errors 177 /// This ensures that only one suite is loaded at a time, and that any errors
191 /// are exposed as [LoadException]s. 178 /// are exposed as [LoadException]s.
(...skipping 26 matching lines...) Expand all
218 "${response.reasonPhrase}\n" 205 "${response.reasonPhrase}\n"
219 'Make sure "pub serve" is serving the test/ directory.'); 206 'Make sure "pub serve" is serving the test/ directory.');
220 }); 207 });
221 }); 208 });
222 } 209 }
223 210
224 /// Compile the test suite at [dartPath] to JavaScript. 211 /// Compile the test suite at [dartPath] to JavaScript.
225 /// 212 ///
226 /// Returns a [Future] that completes to the path to the JavaScript. 213 /// Returns a [Future] that completes to the path to the JavaScript.
227 Future<String> _compileSuite(String dartPath) { 214 Future<String> _compileSuite(String dartPath) {
228 var dir = new Directory(_compiledDir).createTempSync('test_').path; 215 return _compileFutures.putIfAbsent(dartPath, () {
229 var jsPath = p.join(dir, p.basename(dartPath) + ".js"); 216 var dir = new Directory(_compiledDir).createTempSync('test_').path;
230 return _compilers.compile(dartPath, jsPath, 217 var jsPath = p.join(dir, p.basename(dartPath) + ".js");
231 packageRoot: packageRootFor(dartPath, _packageRoot)) 218 return _compilers.compile(dartPath, jsPath,
232 .then((_) { 219 packageRoot: packageRootFor(dartPath, _packageRoot))
233 if (_closed) return null; 220 .then((_) {
221 if (_closed) return null;
234 222
235 // TODO(nweiz): support user-authored HTML files. 223 // TODO(nweiz): support user-authored HTML files.
236 new File(p.join(dir, "index.html")).writeAsStringSync(''' 224 new File(p.join(dir, "index.html")).writeAsStringSync('''
237 <!DOCTYPE html> 225 <!DOCTYPE html>
238 <html> 226 <html>
239 <head> 227 <head>
240 <title>${HTML_ESCAPE.convert(dartPath)} Test</title> 228 <title>${HTML_ESCAPE.convert(dartPath)} Test</title>
241 <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script> 229 <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script>
242 </head> 230 </head>
243 </html> 231 </html>
244 '''); 232 ''');
245 return dir; 233 return dir;
234 });
246 }); 235 });
247 } 236 }
248 237
238 /// Returns the [BrowserManager] for [platform], which should be a browser.
239 ///
240 /// If no browser manager is running yet, starts one.
241 Future<BrowserManager> _browserManagerFor(TestPlatform platform) {
242 var manager = _browserManagers[platform];
243 if (manager != null) return manager;
244
245 var completer = new Completer();
246 _browserManagers[platform] = completer.future;
247 var path = _webSocketHandler.create(webSocketHandler((webSocket) {
248 completer.complete(new BrowserManager(webSocket));
249 }));
250
251 var webSocketUrl = url.replace(scheme: 'ws', path: '/$path');
252
253 var hostUrl = url;
254 if (_pubServeUrl != null) {
255 hostUrl = _pubServeUrl.resolve(
256 '/packages/test/src/runner/browser/static/');
257 }
258
259 var browser = _newBrowser(hostUrl.replace(queryParameters: {
260 'managerUrl': webSocketUrl.toString()
261 }), platform);
262 _browsers[platform] = browser;
263
264 // TODO(nweiz): Gracefully handle the browser being killed before the
265 // tests complete.
266 browser.onExit.catchError((error, stackTrace) {
267 if (completer.isCompleted) return;
268 completer.completeError(error, stackTrace);
269 });
270
271 return completer.future;
272 }
273
274 /// Starts the browser identified by [browser] and has it load [url].
275 Browser _newBrowser(Uri url, TestPlatform browser) {
276 switch (browser) {
277 case TestPlatform.chrome: return new Chrome(url);
278 case TestPlatform.firefox: return new Firefox(url);
279 default:
280 throw new ArgumentError("$browser is not a browser.");
281 }
282 }
283
249 /// Closes the server and releases all its resources. 284 /// Closes the server and releases all its resources.
250 /// 285 ///
251 /// Returns a [Future] that completes once the server is closed and its 286 /// Returns a [Future] that completes once the server is closed and its
252 /// resources have been fully released. 287 /// resources have been fully released.
253 Future close() { 288 Future close() {
254 if (_closeCompleter != null) return _closeCompleter.future; 289 if (_closeCompleter != null) return _closeCompleter.future;
255 _closeCompleter = new Completer(); 290 _closeCompleter = new Completer();
256 291
257 return Future.wait([ 292 return Future.wait([
258 _server.close(), 293 _server.close(),
259 _compilers.close() 294 _compilers.close()
260 ]).then((_) { 295 ]).then((_) {
261 if (_browserManagerCompleter == null) return null; 296 if (_browserManagers.isEmpty) return null;
262 return _browserManager.then((_) => _browser.close()); 297 return Future.wait(_browserManagers.keys.map((platform) {
298 return _browserManagers[platform]
299 .then((_) => _browsers[platform].close());
300 }));
263 }).then((_) { 301 }).then((_) {
264 if (_pubServeUrl == null) { 302 if (_pubServeUrl == null) {
265 new Directory(_compiledDir).deleteSync(recursive: true); 303 new Directory(_compiledDir).deleteSync(recursive: true);
266 } else { 304 } else {
267 _http.close(); 305 _http.close();
268 } 306 }
269 307
270 _closeCompleter.complete(); 308 _closeCompleter.complete();
271 }).catchError(_closeCompleter.completeError); 309 }).catchError(_closeCompleter.completeError);
272 } 310 }
273 } 311 }
OLDNEW
« no previous file with comments | « lib/src/runner/browser/firefox.dart ('k') | lib/src/runner/loader.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698