OLD | NEW |
(Empty) | |
| 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 |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 library unittest.runner.browser.server; |
| 6 |
| 7 import 'dart:async'; |
| 8 import 'dart:convert'; |
| 9 import 'dart:io'; |
| 10 |
| 11 import 'package:path/path.dart' as p; |
| 12 import 'package:shelf/shelf.dart' as shelf; |
| 13 import 'package:shelf/shelf_io.dart' as shelf_io; |
| 14 import 'package:shelf_static/shelf_static.dart'; |
| 15 import 'package:shelf_web_socket/shelf_web_socket.dart'; |
| 16 |
| 17 import '../../backend/suite.dart'; |
| 18 import '../../util/io.dart'; |
| 19 import '../../util/one_off_handler.dart'; |
| 20 import 'browser_manager.dart'; |
| 21 import 'compiler_pool.dart'; |
| 22 import 'chrome.dart'; |
| 23 |
| 24 /// A server that serves JS-compiled tests to browsers. |
| 25 /// |
| 26 /// A test suite may be loaded for a given file using [loadSuite]. |
| 27 class BrowserServer { |
| 28 /// Starts the server. |
| 29 /// |
| 30 /// 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 |
| 32 /// location of the source file. |
| 33 static Future<BrowserServer> start({String packageRoot}) { |
| 34 var server = new BrowserServer._(packageRoot); |
| 35 return server._load().then((_) => server); |
| 36 } |
| 37 |
| 38 /// The underlying HTTP server. |
| 39 HttpServer _server; |
| 40 |
| 41 /// The URL for this server. |
| 42 Uri get url => baseUrlForAddress(_server.address, _server.port); |
| 43 |
| 44 /// a [OneOffHandler] for servicing WebSocket connections for |
| 45 /// [BrowserManager]s. |
| 46 /// |
| 47 /// This is one-off because each [BrowserManager] can only connect to a single |
| 48 /// WebSocket, |
| 49 final _webSocketHandler = new OneOffHandler(); |
| 50 |
| 51 /// The [CompilerPool] managing active instances of `dart2js`. |
| 52 final _compilers = new CompilerPool(); |
| 53 |
| 54 /// The temporary directory in which compiled JS is emitted. |
| 55 final String _compiledDir; |
| 56 |
| 57 /// The package root which is passed to `dart2js`. |
| 58 final String _packageRoot; |
| 59 |
| 60 /// The browser in which test suites are loaded and run. |
| 61 /// |
| 62 /// This is `null` until a suite is loaded. |
| 63 Chrome _browser; |
| 64 |
| 65 /// A future that will complete to the [BrowserManager] for [_browser]. |
| 66 /// |
| 67 /// The first time this is called, it will start both the browser and the |
| 68 /// browser manager. Any further calls will return the existing manager. |
| 69 Future<BrowserManager> get _browserManager { |
| 70 if (_browserManagerCompleter == null) { |
| 71 _browserManagerCompleter = new Completer(); |
| 72 var path = _webSocketHandler.create(webSocketHandler((webSocket) { |
| 73 _browserManagerCompleter.complete(new BrowserManager(webSocket)); |
| 74 })); |
| 75 |
| 76 var webSocketUrl = url.replace(scheme: 'ws', path: '/$path'); |
| 77 _browser = new Chrome(url.replace(queryParameters: { |
| 78 'managerUrl': webSocketUrl.toString() |
| 79 })); |
| 80 |
| 81 // TODO(nweiz): Gracefully handle the browser being killed before the |
| 82 // tests complete. |
| 83 _browser.onExit.catchError((error, stackTrace) { |
| 84 if (_browserManagerCompleter.isCompleted) return; |
| 85 _browserManagerCompleter.completeError(error, stackTrace); |
| 86 }); |
| 87 } |
| 88 return _browserManagerCompleter.future; |
| 89 } |
| 90 Completer<BrowserManager> _browserManagerCompleter; |
| 91 |
| 92 BrowserServer._(this._packageRoot) |
| 93 : _compiledDir = Directory.systemTemp.createTempSync('unittest_').path; |
| 94 |
| 95 /// Starts the underlying server. |
| 96 Future _load() { |
| 97 var staticPath = p.join(libDir(packageRoot: _packageRoot), |
| 98 'src/runner/browser/static'); |
| 99 var cascade = new shelf.Cascade() |
| 100 .add(_webSocketHandler.handler) |
| 101 .add(createStaticHandler(staticPath, defaultDocument: 'index.html')) |
| 102 .add(createStaticHandler(_compiledDir, defaultDocument: 'index.html')); |
| 103 |
| 104 return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) { |
| 105 _server = server; |
| 106 }); |
| 107 } |
| 108 |
| 109 /// Loads the test suite at [path]. |
| 110 /// |
| 111 /// This will start a browser to load the suite if one isn't already running. |
| 112 Future<Suite> loadSuite(String path) { |
| 113 return _compileSuite(path).then((dir) { |
| 114 // TODO(nweiz): Don't start the browser until all the suites are compiled. |
| 115 return _browserManager.then((browserManager) { |
| 116 // Add a trailing slash because at least on Chrome, the iframe's |
| 117 // window.location.href will do so automatically, and if that differs |
| 118 // from the original URL communication will fail. |
| 119 var suiteUrl = url.resolve( |
| 120 "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/"); |
| 121 return browserManager.loadSuite(path, suiteUrl); |
| 122 }); |
| 123 }); |
| 124 } |
| 125 |
| 126 /// Compile the test suite at [dartPath] to JavaScript. |
| 127 /// |
| 128 /// Returns a [Future] that completes to the path to the JavaScript. |
| 129 Future<String> _compileSuite(String dartPath) { |
| 130 var dir = new Directory(_compiledDir).createTempSync('test_').path; |
| 131 var jsPath = p.join(dir, p.basename(dartPath) + ".js"); |
| 132 return _compilers.compile(dartPath, jsPath, |
| 133 packageRoot: packageRootFor(dartPath, _packageRoot)) |
| 134 .then((_) { |
| 135 // TODO(nweiz): support user-authored HTML files. |
| 136 new File(p.join(dir, "index.html")).writeAsStringSync(''' |
| 137 <!DOCTYPE html> |
| 138 <html> |
| 139 <head> |
| 140 <title>${HTML_ESCAPE.convert(dartPath)} Test</title> |
| 141 <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script> |
| 142 </head> |
| 143 </html> |
| 144 '''); |
| 145 return dir; |
| 146 }); |
| 147 } |
| 148 |
| 149 /// Closes the server and releases all its resources. |
| 150 /// |
| 151 /// Returns a [Future] that completes once the server is closed and its |
| 152 /// resources have been fully released. |
| 153 Future close() { |
| 154 new Directory(_compiledDir).deleteSync(recursive: true); |
| 155 return _server.close().then((_) { |
| 156 if (_browserManagerCompleter == null) return null; |
| 157 return _browserManager.then((_) => _browser.close()); |
| 158 }); |
| 159 } |
| 160 } |
OLD | NEW |