| 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());
|
|
|