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

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

Issue 1062523003: Add support for --pub-serve. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: 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:shelf/shelf.dart' as shelf; 13 import 'package:shelf/shelf.dart' as shelf;
13 import 'package:shelf/shelf_io.dart' as shelf_io; 14 import 'package:shelf/shelf_io.dart' as shelf_io;
14 import 'package:shelf_static/shelf_static.dart'; 15 import 'package:shelf_static/shelf_static.dart';
15 import 'package:shelf_web_socket/shelf_web_socket.dart'; 16 import 'package:shelf_web_socket/shelf_web_socket.dart';
16 17
17 import '../../backend/suite.dart'; 18 import '../../backend/suite.dart';
18 import '../../util/io.dart'; 19 import '../../util/io.dart';
19 import '../../util/one_off_handler.dart'; 20 import '../../util/one_off_handler.dart';
21 import '../../utils.dart';
22 import '../load_exception.dart';
20 import 'browser_manager.dart'; 23 import 'browser_manager.dart';
21 import 'compiler_pool.dart'; 24 import 'compiler_pool.dart';
22 import 'chrome.dart'; 25 import 'chrome.dart';
23 26
24 /// A server that serves JS-compiled tests to browsers. 27 /// A server that serves JS-compiled tests to browsers.
25 /// 28 ///
26 /// A test suite may be loaded for a given file using [loadSuite]. 29 /// A test suite may be loaded for a given file using [loadSuite].
27 class BrowserServer { 30 class BrowserServer {
28 /// Starts the server. 31 /// Starts the server.
29 /// 32 ///
30 /// If [packageRoot] is passed, it's used for all package imports when 33 /// If [packageRoot] is passed, it's used for all package imports when
31 /// compiling tests to JS. Otherwise, the package root is inferred from the 34 /// compiling tests to JS. Otherwise, the package root is inferred from the
32 /// location of the source file. 35 /// location of the source file.
33 /// 36 ///
37 /// If [pubServeUrl] is passed, tests will be loaded from the `pub serve`
38 /// instance at that URL rather than from the filesystem.
39 ///
34 /// If [color] is true, console colors will be used when compiling Dart. 40 /// If [color] is true, console colors will be used when compiling Dart.
35 static Future<BrowserServer> start({String packageRoot, bool color: false}) { 41 static Future<BrowserServer> start({String packageRoot, Uri pubServeUrl,
36 var server = new BrowserServer._(packageRoot, color); 42 bool color: false}) {
43 var server = new BrowserServer._(packageRoot, pubServeUrl, color);
37 return server._load().then((_) => server); 44 return server._load().then((_) => server);
38 } 45 }
39 46
40 /// The underlying HTTP server. 47 /// The underlying HTTP server.
41 HttpServer _server; 48 HttpServer _server;
42 49
43 /// The URL for this server. 50 /// The URL for this server.
44 Uri get url => baseUrlForAddress(_server.address, _server.port); 51 Uri get url => baseUrlForAddress(_server.address, _server.port);
45 52
46 /// a [OneOffHandler] for servicing WebSocket connections for 53 /// a [OneOffHandler] for servicing WebSocket connections for
47 /// [BrowserManager]s. 54 /// [BrowserManager]s.
48 /// 55 ///
49 /// This is one-off because each [BrowserManager] can only connect to a single 56 /// This is one-off because each [BrowserManager] can only connect to a single
50 /// WebSocket, 57 /// WebSocket,
51 final _webSocketHandler = new OneOffHandler(); 58 final _webSocketHandler = new OneOffHandler();
52 59
53 /// The [CompilerPool] managing active instances of `dart2js`. 60 /// The [CompilerPool] managing active instances of `dart2js`.
61 ///
62 /// This is `null` if tests are loaded from `pub serve`.
54 final CompilerPool _compilers; 63 final CompilerPool _compilers;
55 64
56 /// The temporary directory in which compiled JS is emitted. 65 /// The temporary directory in which compiled JS is emitted.
57 final String _compiledDir; 66 final String _compiledDir;
58 67
59 /// The package root which is passed to `dart2js`. 68 /// The package root which is passed to `dart2js`.
60 final String _packageRoot; 69 final String _packageRoot;
61 70
71 /// The URL for the `pub serve` instance to use to load tests.
72 ///
73 /// This is `null` if tests should be compiled manually.
74 final Uri _pubServeUrl;
75
76 /// The pool of active `pub serve` compilations.
77 ///
78 /// 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.
80 final _pubServePool = new Pool(1);
81
82 /// The HTTP client to use when caching JS files in `pub serve`.
83 final HttpClient _http;
84
62 /// The browser in which test suites are loaded and run. 85 /// The browser in which test suites are loaded and run.
63 /// 86 ///
64 /// This is `null` until a suite is loaded. 87 /// This is `null` until a suite is loaded.
65 Chrome _browser; 88 Chrome _browser;
66 89
67 /// A future that will complete to the [BrowserManager] for [_browser]. 90 /// A future that will complete to the [BrowserManager] for [_browser].
68 /// 91 ///
69 /// The first time this is called, it will start both the browser and the 92 /// The first time this is called, it will start both the browser and the
70 /// browser manager. Any further calls will return the existing manager. 93 /// browser manager. Any further calls will return the existing manager.
71 Future<BrowserManager> get _browserManager { 94 Future<BrowserManager> get _browserManager {
72 if (_browserManagerCompleter == null) { 95 if (_browserManagerCompleter == null) {
73 _browserManagerCompleter = new Completer(); 96 _browserManagerCompleter = new Completer();
74 var path = _webSocketHandler.create(webSocketHandler((webSocket) { 97 var path = _webSocketHandler.create(webSocketHandler((webSocket) {
75 _browserManagerCompleter.complete(new BrowserManager(webSocket)); 98 _browserManagerCompleter.complete(new BrowserManager(webSocket));
76 })); 99 }));
77 100
78 var webSocketUrl = url.replace(scheme: 'ws', path: '/$path'); 101 var webSocketUrl = url.replace(scheme: 'ws', path: '/$path');
79 _browser = new Chrome(url.replace(queryParameters: { 102
103 var hostUrl = url;
104 if (_pubServeUrl != null) {
105 hostUrl = _pubServeUrl.resolve(
106 '/packages/test/src/runner/browser/static/');
107 }
108
109 _browser = new Chrome(hostUrl.replace(queryParameters: {
80 'managerUrl': webSocketUrl.toString() 110 'managerUrl': webSocketUrl.toString()
81 })); 111 }));
82 112
83 // TODO(nweiz): Gracefully handle the browser being killed before the 113 // TODO(nweiz): Gracefully handle the browser being killed before the
84 // tests complete. 114 // tests complete.
85 _browser.onExit.catchError((error, stackTrace) { 115 _browser.onExit.catchError((error, stackTrace) {
86 if (_browserManagerCompleter.isCompleted) return; 116 if (_browserManagerCompleter.isCompleted) return;
87 _browserManagerCompleter.completeError(error, stackTrace); 117 _browserManagerCompleter.completeError(error, stackTrace);
88 }); 118 });
89 } 119 }
90 return _browserManagerCompleter.future; 120 return _browserManagerCompleter.future;
91 } 121 }
92 Completer<BrowserManager> _browserManagerCompleter; 122 Completer<BrowserManager> _browserManagerCompleter;
93 123
94 BrowserServer._(this._packageRoot, bool color) 124 BrowserServer._(this._packageRoot, Uri pubServeUrl, bool color)
95 : _compiledDir = Directory.systemTemp.createTempSync('test_').path, 125 : _pubServeUrl = pubServeUrl,
126 _compiledDir = pubServeUrl == null
127 ? Directory.systemTemp.createTempSync('test_').path
128 : null,
129 _http = pubServeUrl == null ? null : new HttpClient(),
96 _compilers = new CompilerPool(color: color); 130 _compilers = new CompilerPool(color: color);
97 131
98 /// Starts the underlying server. 132 /// Starts the underlying server.
99 Future _load() { 133 Future _load() {
100 var staticPath = p.join(libDir(packageRoot: _packageRoot),
101 'src/runner/browser/static');
102 var cascade = new shelf.Cascade() 134 var cascade = new shelf.Cascade()
103 .add(_webSocketHandler.handler) 135 .add(_webSocketHandler.handler);
104 .add(createStaticHandler(staticPath, defaultDocument: 'index.html')) 136
105 .add(createStaticHandler(_compiledDir, defaultDocument: 'index.html')); 137 if (_pubServeUrl == null) {
138 var staticPath = p.join(libDir(packageRoot: _packageRoot),
139 'src/runner/browser/static');
140 cascade = cascade
141 .add(createStaticHandler(staticPath, defaultDocument: 'index.html'))
142 .add(createStaticHandler(_compiledDir,
143 defaultDocument: 'index.html'));
144 }
106 145
107 return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) { 146 return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) {
108 _server = server; 147 _server = server;
109 }); 148 });
110 } 149 }
111 150
112 /// Loads the test suite at [path]. 151 /// Loads the test suite at [path].
113 /// 152 ///
114 /// This will start a browser to load the suite if one isn't already running. 153 /// This will start a browser to load the suite if one isn't already running.
115 Future<Suite> loadSuite(String path) { 154 Future<Suite> loadSuite(String path) {
116 return _compileSuite(path).then((dir) { 155 return new Future.sync(() {
156 if (_pubServeUrl != null) {
157
158 var suitePrefix = p.withoutExtension(p.relative(path, from: 'test')) +
159 '.browser_test';
160 var jsUrl = _pubServeUrl.resolve('$suitePrefix.dart.js');
161 return _pubServeSuite(path, jsUrl)
162 .then((_) => _pubServeUrl.resolve('$suitePrefix.html'));
163 } else {
164 return _compileSuite(path).then((dir) {
165 // Add a trailing slash because at least on Chrome, the iframe's
166 // window.location.href will do so automatically, and if that differs
167 // from the original URL communication will fail.
168 return url.resolve(
169 "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/");
170 });
171 }
172 }).then((suiteUrl) {
117 // TODO(nweiz): Don't start the browser until all the suites are compiled. 173 // TODO(nweiz): Don't start the browser until all the suites are compiled.
118 return _browserManager.then((browserManager) { 174 return _browserManager.then((browserManager) {
119 // Add a trailing slash because at least on Chrome, the iframe's
120 // window.location.href will do so automatically, and if that differs
121 // from the original URL communication will fail.
122 var suiteUrl = url.resolve(
123 "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/");
124 return browserManager.loadSuite(path, suiteUrl); 175 return browserManager.loadSuite(path, suiteUrl);
125 }); 176 });
126 }); 177 });
127 } 178 }
128 179
180 /// Loads a test suite at [path] from the `pub serve` URL [jsUrl].
181 ///
182 /// This ensures that only one suite is loaded at a time, and that any errors
183 /// are exposed as [LoadException]s.
184 Future _pubServeSuite(String path, Uri jsUrl) {
185 return _pubServePool.withResource(() {
186 var timer = new Timer(new Duration(seconds: 1), () {
187 print('"pub serve" is compiling $path...');
188 });
189
190 return _http.headUrl(jsUrl)
191 .then((request) => request.close())
192 .whenComplete(timer.cancel)
193 .catchError((error, stackTrace) {
194 if (error is! IOException) throw error;
195
196 var message = getErrorMessage(error);
197 if (error is SocketException) {
198 message = "${error.osError.message} "
199 "(errno ${error.osError.errorCode})";
200 }
201
202 throw new LoadException(path,
203 "Error getting $jsUrl: $message\n"
204 'Make sure "pub serve" is running.');
205 }).then((response) {
206 if (response.statusCode == 200) return;
207
208 throw new LoadException(path,
209 "Error getting $jsUrl: ${response.statusCode} "
210 "${response.reasonPhrase}\n"
211 'Make sure "pub serve" is serving the test/ directory.');
212 });
213 });
214 }
215
129 /// Compile the test suite at [dartPath] to JavaScript. 216 /// Compile the test suite at [dartPath] to JavaScript.
130 /// 217 ///
131 /// Returns a [Future] that completes to the path to the JavaScript. 218 /// Returns a [Future] that completes to the path to the JavaScript.
132 Future<String> _compileSuite(String dartPath) { 219 Future<String> _compileSuite(String dartPath) {
133 var dir = new Directory(_compiledDir).createTempSync('test_').path; 220 var dir = new Directory(_compiledDir).createTempSync('test_').path;
134 var jsPath = p.join(dir, p.basename(dartPath) + ".js"); 221 var jsPath = p.join(dir, p.basename(dartPath) + ".js");
135 return _compilers.compile(dartPath, jsPath, 222 return _compilers.compile(dartPath, jsPath,
136 packageRoot: packageRootFor(dartPath, _packageRoot)) 223 packageRoot: packageRootFor(dartPath, _packageRoot))
137 .then((_) { 224 .then((_) {
138 // TODO(nweiz): support user-authored HTML files. 225 // TODO(nweiz): support user-authored HTML files.
139 new File(p.join(dir, "index.html")).writeAsStringSync(''' 226 new File(p.join(dir, "index.html")).writeAsStringSync('''
140 <!DOCTYPE html> 227 <!DOCTYPE html>
141 <html> 228 <html>
142 <head> 229 <head>
143 <title>${HTML_ESCAPE.convert(dartPath)} Test</title> 230 <title>${HTML_ESCAPE.convert(dartPath)} Test</title>
144 <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script> 231 <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script>
145 </head> 232 </head>
146 </html> 233 </html>
147 '''); 234 ''');
148 return dir; 235 return dir;
149 }); 236 });
150 } 237 }
151 238
152 /// Closes the server and releases all its resources. 239 /// Closes the server and releases all its resources.
153 /// 240 ///
154 /// Returns a [Future] that completes once the server is closed and its 241 /// Returns a [Future] that completes once the server is closed and its
155 /// resources have been fully released. 242 /// resources have been fully released.
156 Future close() { 243 Future close() {
157 new Directory(_compiledDir).deleteSync(recursive: true); 244 if (_pubServeUrl == null) {
245 new Directory(_compiledDir).deleteSync(recursive: true);
246 } else {
247 _http.close();
248 }
249
158 return _server.close().then((_) { 250 return _server.close().then((_) {
159 if (_browserManagerCompleter == null) return null; 251 if (_browserManagerCompleter == null) return null;
160 return _browserManager.then((_) => _browser.close()); 252 return _browserManager.then((_) => _browser.close());
161 }); 253 });
162 } 254 }
163 } 255 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698