Index: lib/src/runner/browser/server.dart |
diff --git a/lib/src/runner/browser/server.dart b/lib/src/runner/browser/server.dart |
index a98bcb08dc55c96864391ad7faba476f6b07299e..1c2ee38ead6e8f418b1373894b3b58dde7b9e145 100644 |
--- a/lib/src/runner/browser/server.dart |
+++ b/lib/src/runner/browser/server.dart |
@@ -18,6 +18,7 @@ import 'package:shelf_web_socket/shelf_web_socket.dart'; |
import '../../backend/suite.dart'; |
import '../../backend/test_platform.dart'; |
import '../../util/io.dart'; |
+import '../../util/path_handler.dart'; |
import '../../util/one_off_handler.dart'; |
import '../../utils.dart'; |
import '../load_exception.dart'; |
@@ -33,6 +34,9 @@ import 'firefox.dart'; |
class BrowserServer { |
/// Starts the server. |
/// |
+ /// [root] is the root directory that the server should serve. It defaults to |
+ /// the working directory. |
+ /// |
/// If [packageRoot] is passed, it's used for all package imports when |
/// compiling tests to JS. Otherwise, the package root is inferred from the |
/// location of the source file. |
@@ -41,25 +45,35 @@ class BrowserServer { |
/// 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, Uri pubServeUrl, |
- bool color: false}) { |
- var server = new BrowserServer._(packageRoot, pubServeUrl, color); |
+ static Future<BrowserServer> start({String root, String packageRoot, |
+ Uri pubServeUrl, bool color: false}) { |
+ var server = new BrowserServer._(root, packageRoot, pubServeUrl, color); |
return server._load().then((_) => server); |
} |
/// The underlying HTTP server. |
HttpServer _server; |
+ /// A randomly-generated secret. |
+ /// |
+ /// This is used to ensure that other users on the same system can't snoop |
+ /// on data being served through this server. |
+ final _secret = randomBase64(24, urlSafe: true); |
+ |
/// The URL for this server. |
- Uri get url => baseUrlForAddress(_server.address, _server.port); |
+ Uri get url => baseUrlForAddress(_server.address, _server.port) |
+ .resolve(_secret + "/"); |
- /// a [OneOffHandler] for servicing WebSocket connections for |
+ /// A [OneOffHandler] for servicing WebSocket connections for |
/// [BrowserManager]s. |
/// |
/// This is one-off because each [BrowserManager] can only connect to a single |
/// WebSocket, |
final _webSocketHandler = new OneOffHandler(); |
+ /// A [PathHandler] used to serve compiled JS. |
+ final _jsHandler = new PathHandler(); |
+ |
/// The [CompilerPool] managing active instances of `dart2js`. |
/// |
/// This is `null` if tests are loaded from `pub serve`. |
@@ -68,6 +82,9 @@ class BrowserServer { |
/// The temporary directory in which compiled JS is emitted. |
final String _compiledDir; |
+ /// The root directory served statically by this server. |
+ final String _root; |
+ |
/// The package root which is passed to `dart2js`. |
final String _packageRoot; |
@@ -109,8 +126,9 @@ class BrowserServer { |
/// per run, rather than one per browser per run. |
final _compileFutures = new Map<String, Future>(); |
- BrowserServer._(this._packageRoot, Uri pubServeUrl, bool color) |
- : _pubServeUrl = pubServeUrl, |
+ BrowserServer._(String root, this._packageRoot, Uri pubServeUrl, bool color) |
+ : _root = root == null ? p.current : root, |
+ _pubServeUrl = pubServeUrl, |
_compiledDir = pubServeUrl == null ? createTempDir() : null, |
_http = pubServeUrl == null ? null : new HttpClient(), |
_compilers = new CompilerPool(color: color); |
@@ -121,19 +139,70 @@ class BrowserServer { |
.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')); |
+ .add(_createPackagesHandler()) |
+ .add(_jsHandler.handler) |
+ .add(_wrapperHandler) |
+ .add(createStaticHandler(_root)); |
} |
- return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) { |
+ var pipeline = new shelf.Pipeline() |
+ .addMiddleware(nestingMiddleware(_secret)) |
+ .addHandler(cascade.handler); |
+ |
+ return shelf_io.serve(pipeline, 'localhost', 0).then((server) { |
_server = server; |
}); |
} |
+ /// Returns a handler that serves the contents of the "packages/" directory |
+ /// for any URL that contains "packages/". |
+ /// |
+ /// This is a factory so it can wrap a static handler. |
+ shelf.Handler _createPackagesHandler() { |
+ var packageRoot = _packageRoot == null |
+ ? p.join(_root, 'packages') |
+ : _packageRoot; |
+ var staticHandler = |
+ createStaticHandler(packageRoot, serveFilesOutsidePath: true); |
+ |
+ return (request) { |
+ var segments = p.url.split(shelfUrl(request).path); |
+ |
+ for (var i = 0; i < segments.length; i++) { |
+ if (segments[i] != "packages") continue; |
+ return staticHandler( |
+ shelfChange(request, path: p.url.joinAll(segments.take(i + 1)))); |
+ } |
+ |
+ return new shelf.Response.notFound("Not found."); |
+ }; |
+ } |
+ |
+ /// A handler that serves wrapper HTML to bootstrap tests. |
+ shelf.Response _wrapperHandler(shelf.Request request) { |
+ var path = p.fromUri(shelfUrl(request)); |
+ var withoutExtensions = p.withoutExtension(p.withoutExtension(path)); |
+ var base = p.basename(withoutExtensions); |
+ |
+ if (path.endsWith(".browser_test.html")) { |
+ // TODO(nweiz): support user-authored HTML files. |
+ return new shelf.Response.ok(''' |
+<!DOCTYPE html> |
+<html> |
+<head> |
+ <title>${HTML_ESCAPE.convert(base)}.dart Test</title> |
+ <script type="application/javascript" |
+ src="${HTML_ESCAPE.convert(base)}.browser_test.dart.js"> |
+ </script> |
+</head> |
+</html> |
+''', headers: {'Content-Type': 'text/html'}); |
+ } |
+ |
+ return new shelf.Response.notFound('Not found.'); |
+ } |
+ |
/// Loads the test suite at [path] on the browser [browser]. |
/// |
/// This will start a browser to load the suite if one isn't already running. |
@@ -145,22 +214,18 @@ class BrowserServer { |
return new Future.sync(() { |
if (_pubServeUrl != null) { |
- var suitePrefix = p.withoutExtension(p.relative(path, from: 'test')) + |
+ var suitePrefix = p.relative(path, from: p.join(_root, '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) { |
- if (_closed) return null; |
- |
- // 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 + "/"); |
- }); |
+ return _pubServeSuite(path, jsUrl).then((_) => |
+ _pubServeUrl.resolve('$suitePrefix.html')); |
} |
+ |
+ return _compileSuite(path).then((_) { |
+ if (_closed) return null; |
+ return url.resolveUri( |
+ p.toUri(p.relative(path, from: _root) + ".browser_test.html")); |
+ }); |
}).then((suiteUrl) { |
if (_closed) return null; |
@@ -210,27 +275,24 @@ class BrowserServer { |
/// Compile the test suite at [dartPath] to JavaScript. |
/// |
- /// Returns a [Future] that completes to the path to the JavaScript. |
- Future<String> _compileSuite(String dartPath) { |
+ /// Once the suite has been compiled, it's added to [_jsHandler] so it can be |
+ /// served. |
+ Future _compileSuite(String dartPath) { |
return _compileFutures.putIfAbsent(dartPath, () { |
var dir = new Directory(_compiledDir).createTempSync('test_').path; |
var jsPath = p.join(dir, p.basename(dartPath) + ".js"); |
+ |
return _compilers.compile(dartPath, jsPath, |
packageRoot: packageRootFor(dartPath, _packageRoot)) |
.then((_) { |
- if (_closed) return null; |
+ if (_closed) return; |
- // TODO(nweiz): support user-authored HTML files. |
- new File(p.join(dir, "index.html")).writeAsStringSync(''' |
-<!DOCTYPE html> |
-<html> |
-<head> |
- <title>${HTML_ESCAPE.convert(dartPath)} Test</title> |
- <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script> |
-</head> |
-</html> |
-'''); |
- return dir; |
+ _jsHandler.add( |
+ p.relative(dartPath, from: _root) + '.browser_test.dart.js', |
+ (request) { |
+ return new shelf.Response.ok(new File(jsPath).readAsStringSync(), |
+ headers: {'Content-Type': 'application/javascript'}); |
+ }); |
}); |
}); |
} |
@@ -248,13 +310,10 @@ class BrowserServer { |
completer.complete(new BrowserManager(webSocket)); |
})); |
- var webSocketUrl = url.replace(scheme: 'ws', path: '/$path'); |
+ var webSocketUrl = url.replace(scheme: 'ws').resolve(path); |
- var hostUrl = url; |
- if (_pubServeUrl != null) { |
- hostUrl = _pubServeUrl.resolve( |
- '/packages/test/src/runner/browser/static/'); |
- } |
+ var hostUrl = (_pubServeUrl == null ? url : _pubServeUrl) |
+ .resolve('packages/test/src/runner/browser/static/index.html'); |
var browser = _newBrowser(hostUrl.replace(queryParameters: { |
'managerUrl': webSocketUrl.toString() |