Index: lib/src/runner/browser/server.dart |
diff --git a/lib/src/runner/browser/server.dart b/lib/src/runner/browser/server.dart |
index 9730e3c2a276ca36808cccd00ca5caabc535146f..87b6ef38edb402a82d4b775dc5b36922bf1fa15b 100644 |
--- a/lib/src/runner/browser/server.dart |
+++ b/lib/src/runner/browser/server.dart |
@@ -9,6 +9,7 @@ import 'dart:convert'; |
import 'dart:io'; |
import 'package:path/path.dart' as p; |
+import 'package:pool/pool.dart'; |
import 'package:shelf/shelf.dart' as shelf; |
import 'package:shelf/shelf_io.dart' as shelf_io; |
import 'package:shelf_static/shelf_static.dart'; |
@@ -17,6 +18,8 @@ import 'package:shelf_web_socket/shelf_web_socket.dart'; |
import '../../backend/suite.dart'; |
import '../../util/io.dart'; |
import '../../util/one_off_handler.dart'; |
+import '../../utils.dart'; |
+import '../load_exception.dart'; |
import 'browser_manager.dart'; |
import 'compiler_pool.dart'; |
import 'chrome.dart'; |
@@ -31,9 +34,13 @@ class BrowserServer { |
/// compiling tests to JS. Otherwise, the package root is inferred from the |
/// location of the source file. |
/// |
+ /// If [pubServeUrl] is passed, tests will be loaded from the `pub serve` |
+ /// instance at that URL rather than from the filesystem. |
+ /// |
/// If [color] is true, console colors will be used when compiling Dart. |
- static Future<BrowserServer> start({String packageRoot, bool color: false}) { |
- var server = new BrowserServer._(packageRoot, color); |
+ static Future<BrowserServer> start({String packageRoot, Uri pubServeUrl, |
+ bool color: false}) { |
+ var server = new BrowserServer._(packageRoot, pubServeUrl, color); |
return server._load().then((_) => server); |
} |
@@ -51,6 +58,8 @@ class BrowserServer { |
final _webSocketHandler = new OneOffHandler(); |
/// The [CompilerPool] managing active instances of `dart2js`. |
+ /// |
+ /// This is `null` if tests are loaded from `pub serve`. |
final CompilerPool _compilers; |
/// The temporary directory in which compiled JS is emitted. |
@@ -59,6 +68,20 @@ class BrowserServer { |
/// The package root which is passed to `dart2js`. |
final String _packageRoot; |
+ /// The URL for the `pub serve` instance to use to load tests. |
+ /// |
+ /// This is `null` if tests should be compiled manually. |
+ final Uri _pubServeUrl; |
+ |
+ /// The pool of active `pub serve` compilations. |
+ /// |
+ /// Pub itself ensures that only one compilation runs at a time; we just use |
+ /// this pool to make sure that the output is nice and linear. |
+ final _pubServePool = new Pool(1); |
+ |
+ /// The HTTP client to use when caching JS files in `pub serve`. |
+ final HttpClient _http; |
+ |
/// The browser in which test suites are loaded and run. |
/// |
/// This is `null` until a suite is loaded. |
@@ -76,7 +99,14 @@ class BrowserServer { |
})); |
var webSocketUrl = url.replace(scheme: 'ws', path: '/$path'); |
- _browser = new Chrome(url.replace(queryParameters: { |
+ |
+ var hostUrl = url; |
+ if (_pubServeUrl != null) { |
+ hostUrl = _pubServeUrl.resolve( |
+ '/packages/test/src/runner/browser/static/'); |
+ } |
+ |
+ _browser = new Chrome(hostUrl.replace(queryParameters: { |
'managerUrl': webSocketUrl.toString() |
})); |
@@ -91,18 +121,27 @@ class BrowserServer { |
} |
Completer<BrowserManager> _browserManagerCompleter; |
- BrowserServer._(this._packageRoot, bool color) |
- : _compiledDir = Directory.systemTemp.createTempSync('test_').path, |
+ BrowserServer._(this._packageRoot, Uri pubServeUrl, bool color) |
+ : _pubServeUrl = pubServeUrl, |
+ _compiledDir = pubServeUrl == null |
+ ? Directory.systemTemp.createTempSync('test_').path |
+ : null, |
+ _http = pubServeUrl == null ? null : new HttpClient(), |
_compilers = new CompilerPool(color: color); |
/// Starts the underlying server. |
Future _load() { |
- var staticPath = p.join(libDir(packageRoot: _packageRoot), |
- 'src/runner/browser/static'); |
var cascade = new shelf.Cascade() |
- .add(_webSocketHandler.handler) |
- .add(createStaticHandler(staticPath, defaultDocument: 'index.html')) |
- .add(createStaticHandler(_compiledDir, defaultDocument: 'index.html')); |
+ .add(_webSocketHandler.handler); |
+ |
+ if (_pubServeUrl == null) { |
+ var staticPath = p.join(libDir(packageRoot: _packageRoot), |
+ 'src/runner/browser/static'); |
+ cascade = cascade |
+ .add(createStaticHandler(staticPath, defaultDocument: 'index.html')) |
+ .add(createStaticHandler(_compiledDir, |
+ defaultDocument: 'index.html')); |
+ } |
return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) { |
_server = server; |
@@ -113,19 +152,67 @@ class BrowserServer { |
/// |
/// This will start a browser to load the suite if one isn't already running. |
Future<Suite> loadSuite(String path) { |
- return _compileSuite(path).then((dir) { |
+ return new Future.sync(() { |
+ if (_pubServeUrl != null) { |
+ |
+ var suitePrefix = p.withoutExtension(p.relative(path, from: 'test')) + |
+ '.browser_test'; |
+ var jsUrl = _pubServeUrl.resolve('$suitePrefix.dart.js'); |
+ return _pubServeSuite(path, jsUrl) |
+ .then((_) => _pubServeUrl.resolve('$suitePrefix.html')); |
+ } else { |
+ return _compileSuite(path).then((dir) { |
+ // Add a trailing slash because at least on Chrome, the iframe's |
+ // window.location.href will do so automatically, and if that differs |
+ // from the original URL communication will fail. |
+ return url.resolve( |
+ "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/"); |
+ }); |
+ } |
+ }).then((suiteUrl) { |
// TODO(nweiz): Don't start the browser until all the suites are compiled. |
return _browserManager.then((browserManager) { |
- // Add a trailing slash because at least on Chrome, the iframe's |
- // window.location.href will do so automatically, and if that differs |
- // from the original URL communication will fail. |
- var suiteUrl = url.resolve( |
- "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/"); |
return browserManager.loadSuite(path, suiteUrl); |
}); |
}); |
} |
+ /// Loads a test suite at [path] from the `pub serve` URL [jsUrl]. |
+ /// |
+ /// This ensures that only one suite is loaded at a time, and that any errors |
+ /// are exposed as [LoadException]s. |
+ Future _pubServeSuite(String path, Uri jsUrl) { |
+ return _pubServePool.withResource(() { |
+ var timer = new Timer(new Duration(seconds: 1), () { |
+ print('"pub serve" is compiling $path...'); |
+ }); |
+ |
+ return _http.headUrl(jsUrl) |
+ .then((request) => request.close()) |
+ .whenComplete(timer.cancel) |
+ .catchError((error, stackTrace) { |
+ if (error is! IOException) throw error; |
+ |
+ var message = getErrorMessage(error); |
+ if (error is SocketException) { |
+ message = "${error.osError.message} " |
+ "(errno ${error.osError.errorCode})"; |
+ } |
+ |
+ throw new LoadException(path, |
+ "Error getting $jsUrl: $message\n" |
+ 'Make sure "pub serve" is running.'); |
+ }).then((response) { |
+ if (response.statusCode == 200) return; |
+ |
+ throw new LoadException(path, |
+ "Error getting $jsUrl: ${response.statusCode} " |
+ "${response.reasonPhrase}\n" |
+ 'Make sure "pub serve" is serving the test/ directory.'); |
+ }); |
+ }); |
+ } |
+ |
/// Compile the test suite at [dartPath] to JavaScript. |
/// |
/// Returns a [Future] that completes to the path to the JavaScript. |
@@ -154,7 +241,12 @@ class BrowserServer { |
/// Returns a [Future] that completes once the server is closed and its |
/// resources have been fully released. |
Future close() { |
- new Directory(_compiledDir).deleteSync(recursive: true); |
+ if (_pubServeUrl == null) { |
+ new Directory(_compiledDir).deleteSync(recursive: true); |
+ } else { |
+ _http.close(); |
+ } |
+ |
return _server.close().then((_) { |
if (_browserManagerCompleter == null) return null; |
return _browserManager.then((_) => _browser.close()); |