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

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

Issue 1076803003: Change the URL-space exposed by the browser server. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Changelog + pubspec 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
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 '../../backend/test_platform.dart';
20 import '../../util/io.dart'; 20 import '../../util/io.dart';
21 import '../../util/path_handler.dart';
21 import '../../util/one_off_handler.dart'; 22 import '../../util/one_off_handler.dart';
22 import '../../utils.dart'; 23 import '../../utils.dart';
23 import '../load_exception.dart'; 24 import '../load_exception.dart';
24 import 'browser.dart'; 25 import 'browser.dart';
25 import 'browser_manager.dart'; 26 import 'browser_manager.dart';
26 import 'compiler_pool.dart'; 27 import 'compiler_pool.dart';
27 import 'chrome.dart'; 28 import 'chrome.dart';
28 import 'firefox.dart'; 29 import 'firefox.dart';
29 30
30 /// A server that serves JS-compiled tests to browsers. 31 /// A server that serves JS-compiled tests to browsers.
31 /// 32 ///
32 /// A test suite may be loaded for a given file using [loadSuite]. 33 /// A test suite may be loaded for a given file using [loadSuite].
33 class BrowserServer { 34 class BrowserServer {
34 /// Starts the server. 35 /// Starts the server.
35 /// 36 ///
37 /// [root] is the root directory that the server should serve. It defaults to
38 /// the working directory.
39 ///
36 /// If [packageRoot] is passed, it's used for all package imports when 40 /// If [packageRoot] is passed, it's used for all package imports when
37 /// compiling tests to JS. Otherwise, the package root is inferred from the 41 /// compiling tests to JS. Otherwise, the package root is inferred from the
38 /// location of the source file. 42 /// location of the source file.
39 /// 43 ///
40 /// If [pubServeUrl] is passed, tests will be loaded from the `pub serve` 44 /// If [pubServeUrl] is passed, tests will be loaded from the `pub serve`
41 /// instance at that URL rather than from the filesystem. 45 /// instance at that URL rather than from the filesystem.
42 /// 46 ///
43 /// If [color] is true, console colors will be used when compiling Dart. 47 /// If [color] is true, console colors will be used when compiling Dart.
44 static Future<BrowserServer> start({String packageRoot, Uri pubServeUrl, 48 static Future<BrowserServer> start({String root, String packageRoot,
45 bool color: false}) { 49 Uri pubServeUrl, bool color: false}) {
46 var server = new BrowserServer._(packageRoot, pubServeUrl, color); 50 var server = new BrowserServer._(root, packageRoot, pubServeUrl, color);
47 return server._load().then((_) => server); 51 return server._load().then((_) => server);
48 } 52 }
49 53
50 /// The underlying HTTP server. 54 /// The underlying HTTP server.
51 HttpServer _server; 55 HttpServer _server;
52 56
57 /// A randomly-generated secret.
58 ///
59 /// This is used to ensure that other users on the same system can't snoop
60 /// on data being served through this server.
61 final _secret = randomBase64(24, urlSafe: true);
62
53 /// The URL for this server. 63 /// The URL for this server.
54 Uri get url => baseUrlForAddress(_server.address, _server.port); 64 Uri get url => baseUrlForAddress(_server.address, _server.port)
65 .resolve(_secret + "/");
55 66
56 /// a [OneOffHandler] for servicing WebSocket connections for 67 /// A [OneOffHandler] for servicing WebSocket connections for
57 /// [BrowserManager]s. 68 /// [BrowserManager]s.
58 /// 69 ///
59 /// This is one-off because each [BrowserManager] can only connect to a single 70 /// This is one-off because each [BrowserManager] can only connect to a single
60 /// WebSocket, 71 /// WebSocket,
61 final _webSocketHandler = new OneOffHandler(); 72 final _webSocketHandler = new OneOffHandler();
62 73
74 /// A [PathHandler] used to serve compiled JS.
75 final _jsHandler = new PathHandler();
76
63 /// The [CompilerPool] managing active instances of `dart2js`. 77 /// The [CompilerPool] managing active instances of `dart2js`.
64 /// 78 ///
65 /// This is `null` if tests are loaded from `pub serve`. 79 /// This is `null` if tests are loaded from `pub serve`.
66 final CompilerPool _compilers; 80 final CompilerPool _compilers;
67 81
68 /// The temporary directory in which compiled JS is emitted. 82 /// The temporary directory in which compiled JS is emitted.
69 final String _compiledDir; 83 final String _compiledDir;
70 84
85 /// The root directory served statically by this server.
86 final String _root;
87
71 /// The package root which is passed to `dart2js`. 88 /// The package root which is passed to `dart2js`.
72 final String _packageRoot; 89 final String _packageRoot;
73 90
74 /// The URL for the `pub serve` instance to use to load tests. 91 /// The URL for the `pub serve` instance to use to load tests.
75 /// 92 ///
76 /// This is `null` if tests should be compiled manually. 93 /// This is `null` if tests should be compiled manually.
77 final Uri _pubServeUrl; 94 final Uri _pubServeUrl;
78 95
79 /// The pool of active `pub serve` compilations. 96 /// The pool of active `pub serve` compilations.
80 /// 97 ///
(...skipping 21 matching lines...) Expand all
102 /// This should only be accessed through [_browserManagerFor]. 119 /// This should only be accessed through [_browserManagerFor].
103 final _browserManagers = new Map<TestPlatform, Future<BrowserManager>>(); 120 final _browserManagers = new Map<TestPlatform, Future<BrowserManager>>();
104 121
105 /// A map from test suite paths to Futures that will complete once those 122 /// A map from test suite paths to Futures that will complete once those
106 /// suites are finished compiling. 123 /// suites are finished compiling.
107 /// 124 ///
108 /// This is used to make sure that a given test suite is only compiled once 125 /// This is used to make sure that a given test suite is only compiled once
109 /// per run, rather than one per browser per run. 126 /// per run, rather than one per browser per run.
110 final _compileFutures = new Map<String, Future>(); 127 final _compileFutures = new Map<String, Future>();
111 128
112 BrowserServer._(this._packageRoot, Uri pubServeUrl, bool color) 129 BrowserServer._(String root, this._packageRoot, Uri pubServeUrl, bool color)
113 : _pubServeUrl = pubServeUrl, 130 : _root = root == null ? p.current : root,
131 _pubServeUrl = pubServeUrl,
114 _compiledDir = pubServeUrl == null ? createTempDir() : null, 132 _compiledDir = pubServeUrl == null ? createTempDir() : null,
115 _http = pubServeUrl == null ? null : new HttpClient(), 133 _http = pubServeUrl == null ? null : new HttpClient(),
116 _compilers = new CompilerPool(color: color); 134 _compilers = new CompilerPool(color: color);
117 135
118 /// Starts the underlying server. 136 /// Starts the underlying server.
119 Future _load() { 137 Future _load() {
120 var cascade = new shelf.Cascade() 138 var cascade = new shelf.Cascade()
121 .add(_webSocketHandler.handler); 139 .add(_webSocketHandler.handler);
122 140
123 if (_pubServeUrl == null) { 141 if (_pubServeUrl == null) {
124 var staticPath = p.join(libDir(packageRoot: _packageRoot),
125 'src/runner/browser/static');
126 cascade = cascade 142 cascade = cascade
127 .add(createStaticHandler(staticPath, defaultDocument: 'index.html')) 143 .add(_createPackagesHandler())
128 .add(createStaticHandler(_compiledDir, 144 .add(_jsHandler.handler)
129 defaultDocument: 'index.html')); 145 .add(_wrapperHandler)
146 .add(createStaticHandler(_root));
130 } 147 }
131 148
132 return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) { 149 var pipeline = new shelf.Pipeline()
150 .addMiddleware(nestingMiddleware(_secret))
151 .addHandler(cascade.handler);
152
153 return shelf_io.serve(pipeline, 'localhost', 0).then((server) {
133 _server = server; 154 _server = server;
134 }); 155 });
135 } 156 }
136 157
158 /// Returns a handler that serves the contents of the "packages/" directory
159 /// for any URL that contains "packages/".
160 ///
161 /// This is a factory so it can wrap a static handler.
162 shelf.Handler _createPackagesHandler() {
163 var packageRoot = _packageRoot == null
164 ? p.join(_root, 'packages')
165 : _packageRoot;
166 var staticHandler =
167 createStaticHandler(packageRoot, serveFilesOutsidePath: true);
168
169 return (request) {
170 var segments = p.url.split(shelfUrl(request).path);
171
172 for (var i = 0; i < segments.length; i++) {
173 if (segments[i] != "packages") continue;
174 return staticHandler(
175 shelfChange(request, path: p.url.joinAll(segments.take(i + 1))));
176 }
177
178 return new shelf.Response.notFound("Not found.");
179 };
180 }
181
182 /// A handler that serves wrapper HTML to bootstrap tests.
183 shelf.Response _wrapperHandler(shelf.Request request) {
184 var path = p.fromUri(shelfUrl(request));
185 var withoutExtensions = p.withoutExtension(p.withoutExtension(path));
186 var base = p.basename(withoutExtensions);
187
188 if (path.endsWith(".browser_test.html")) {
189 // TODO(nweiz): support user-authored HTML files.
190 return new shelf.Response.ok('''
191 <!DOCTYPE html>
192 <html>
193 <head>
194 <title>${HTML_ESCAPE.convert(base)}.dart Test</title>
195 <script type="application/javascript"
196 src="${HTML_ESCAPE.convert(base)}.browser_test.dart.js">
197 </script>
198 </head>
199 </html>
200 ''', headers: {'Content-Type': 'text/html'});
201 }
202
203 return new shelf.Response.notFound('Not found.');
204 }
205
137 /// Loads the test suite at [path] on the browser [browser]. 206 /// Loads the test suite at [path] on the browser [browser].
138 /// 207 ///
139 /// This will start a browser to load the suite if one isn't already running. 208 /// This will start a browser to load the suite if one isn't already running.
140 /// Throws an [ArgumentError] if [browser] isn't a browser platform. 209 /// Throws an [ArgumentError] if [browser] isn't a browser platform.
141 Future<Suite> loadSuite(String path, TestPlatform browser) { 210 Future<Suite> loadSuite(String path, TestPlatform browser) {
142 if (!browser.isBrowser) { 211 if (!browser.isBrowser) {
143 throw new ArgumentError("$browser is not a browser."); 212 throw new ArgumentError("$browser is not a browser.");
144 } 213 }
145 214
146 return new Future.sync(() { 215 return new Future.sync(() {
147 if (_pubServeUrl != null) { 216 if (_pubServeUrl != null) {
148 var suitePrefix = p.withoutExtension(p.relative(path, from: 'test')) + 217 var suitePrefix = p.relative(path, from: p.join(_root, 'test')) +
149 '.browser_test'; 218 '.browser_test';
150 var jsUrl = _pubServeUrl.resolve('$suitePrefix.dart.js'); 219 var jsUrl = _pubServeUrl.resolve('$suitePrefix.dart.js');
151 return _pubServeSuite(path, jsUrl) 220 return _pubServeSuite(path, jsUrl).then((_) =>
152 .then((_) => _pubServeUrl.resolve('$suitePrefix.html')); 221 _pubServeUrl.resolve('$suitePrefix.html'));
153 } else { 222 }
154 return _compileSuite(path).then((dir) {
155 if (_closed) return null;
156 223
157 // Add a trailing slash because at least on Chrome, the iframe's 224 return _compileSuite(path).then((_) {
158 // window.location.href will do so automatically, and if that differs 225 if (_closed) return null;
159 // from the original URL communication will fail. 226 return url.resolveUri(
160 return url.resolve( 227 p.toUri(p.relative(path, from: _root) + ".browser_test.html"));
161 "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/"); 228 });
162 });
163 }
164 }).then((suiteUrl) { 229 }).then((suiteUrl) {
165 if (_closed) return null; 230 if (_closed) return null;
166 231
167 // TODO(nweiz): Don't start the browser until all the suites are compiled. 232 // TODO(nweiz): Don't start the browser until all the suites are compiled.
168 return _browserManagerFor(browser).then((browserManager) { 233 return _browserManagerFor(browser).then((browserManager) {
169 if (_closed) return null; 234 if (_closed) return null;
170 return browserManager.loadSuite(path, suiteUrl); 235 return browserManager.loadSuite(path, suiteUrl);
171 }); 236 });
172 }); 237 });
173 } 238 }
(...skipping 29 matching lines...) Expand all
203 throw new LoadException(path, 268 throw new LoadException(path,
204 "Error getting $jsUrl: ${response.statusCode} " 269 "Error getting $jsUrl: ${response.statusCode} "
205 "${response.reasonPhrase}\n" 270 "${response.reasonPhrase}\n"
206 'Make sure "pub serve" is serving the test/ directory.'); 271 'Make sure "pub serve" is serving the test/ directory.');
207 }); 272 });
208 }); 273 });
209 } 274 }
210 275
211 /// Compile the test suite at [dartPath] to JavaScript. 276 /// Compile the test suite at [dartPath] to JavaScript.
212 /// 277 ///
213 /// Returns a [Future] that completes to the path to the JavaScript. 278 /// Once the suite has been compiled, it's added to [_jsHandler] so it can be
214 Future<String> _compileSuite(String dartPath) { 279 /// served.
280 Future _compileSuite(String dartPath) {
215 return _compileFutures.putIfAbsent(dartPath, () { 281 return _compileFutures.putIfAbsent(dartPath, () {
216 var dir = new Directory(_compiledDir).createTempSync('test_').path; 282 var dir = new Directory(_compiledDir).createTempSync('test_').path;
217 var jsPath = p.join(dir, p.basename(dartPath) + ".js"); 283 var jsPath = p.join(dir, p.basename(dartPath) + ".js");
284
218 return _compilers.compile(dartPath, jsPath, 285 return _compilers.compile(dartPath, jsPath,
219 packageRoot: packageRootFor(dartPath, _packageRoot)) 286 packageRoot: packageRootFor(dartPath, _packageRoot))
220 .then((_) { 287 .then((_) {
221 if (_closed) return null; 288 if (_closed) return;
222 289
223 // TODO(nweiz): support user-authored HTML files. 290 _jsHandler.add(
224 new File(p.join(dir, "index.html")).writeAsStringSync(''' 291 p.relative(dartPath, from: _root) + '.browser_test.dart.js',
225 <!DOCTYPE html> 292 (request) {
226 <html> 293 return new shelf.Response.ok(new File(jsPath).readAsStringSync(),
227 <head> 294 headers: {'Content-Type': 'application/javascript'});
228 <title>${HTML_ESCAPE.convert(dartPath)} Test</title> 295 });
229 <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script>
230 </head>
231 </html>
232 ''');
233 return dir;
234 }); 296 });
235 }); 297 });
236 } 298 }
237 299
238 /// Returns the [BrowserManager] for [platform], which should be a browser. 300 /// Returns the [BrowserManager] for [platform], which should be a browser.
239 /// 301 ///
240 /// If no browser manager is running yet, starts one. 302 /// If no browser manager is running yet, starts one.
241 Future<BrowserManager> _browserManagerFor(TestPlatform platform) { 303 Future<BrowserManager> _browserManagerFor(TestPlatform platform) {
242 var manager = _browserManagers[platform]; 304 var manager = _browserManagers[platform];
243 if (manager != null) return manager; 305 if (manager != null) return manager;
244 306
245 var completer = new Completer(); 307 var completer = new Completer();
246 _browserManagers[platform] = completer.future; 308 _browserManagers[platform] = completer.future;
247 var path = _webSocketHandler.create(webSocketHandler((webSocket) { 309 var path = _webSocketHandler.create(webSocketHandler((webSocket) {
248 completer.complete(new BrowserManager(webSocket)); 310 completer.complete(new BrowserManager(webSocket));
249 })); 311 }));
250 312
251 var webSocketUrl = url.replace(scheme: 'ws', path: '/$path'); 313 var webSocketUrl = url.replace(scheme: 'ws').resolve(path);
252 314
253 var hostUrl = url; 315 var hostUrl = (_pubServeUrl == null ? url : _pubServeUrl)
254 if (_pubServeUrl != null) { 316 .resolve('packages/test/src/runner/browser/static/index.html');
255 hostUrl = _pubServeUrl.resolve(
256 '/packages/test/src/runner/browser/static/');
257 }
258 317
259 var browser = _newBrowser(hostUrl.replace(queryParameters: { 318 var browser = _newBrowser(hostUrl.replace(queryParameters: {
260 'managerUrl': webSocketUrl.toString() 319 'managerUrl': webSocketUrl.toString()
261 }), platform); 320 }), platform);
262 _browsers[platform] = browser; 321 _browsers[platform] = browser;
263 322
264 // TODO(nweiz): Gracefully handle the browser being killed before the 323 // TODO(nweiz): Gracefully handle the browser being killed before the
265 // tests complete. 324 // tests complete.
266 browser.onExit.catchError((error, stackTrace) { 325 browser.onExit.catchError((error, stackTrace) {
267 if (completer.isCompleted) return; 326 if (completer.isCompleted) return;
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
302 if (_pubServeUrl == null) { 361 if (_pubServeUrl == null) {
303 new Directory(_compiledDir).deleteSync(recursive: true); 362 new Directory(_compiledDir).deleteSync(recursive: true);
304 } else { 363 } else {
305 _http.close(); 364 _http.close();
306 } 365 }
307 366
308 _closeCompleter.complete(); 367 _closeCompleter.complete();
309 }).catchError(_closeCompleter.completeError); 368 }).catchError(_closeCompleter.completeError);
310 } 369 }
311 } 370 }
OLDNEW
« no previous file with comments | « lib/pub_serve.dart ('k') | lib/src/runner/loader.dart » ('j') | test/runner/loader_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698