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 cascade = new shelf.Cascade() | |
98 .add(_webSocketHandler.handler) | |
99 .add(createStaticHandler(p.join(libDir, 'src/runner/browser/static'), | |
kevmoo
2015/03/04 20:30:37
Call the method - libDir()
| |
100 defaultDocument: 'index.html')) | |
101 .add(createStaticHandler(_compiledDir, defaultDocument: 'index.html')); | |
102 | |
103 return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) { | |
104 _server = server; | |
105 }); | |
106 } | |
107 | |
108 /// Loads the test suite at [path]. | |
109 /// | |
110 /// This will start a browser to load the suite if one isn't already running. | |
111 Future<Suite> loadSuite(String path) { | |
112 return _compileSuite(path).then((dir) { | |
113 // TODO(nweiz): Don't start the browser until all the suites are compiled. | |
114 return _browserManager.then((browserManager) { | |
115 // Add a trailing slash because at least on Chrome, the iframe's | |
116 // window.location.href will do so automatically, and if that differs | |
117 // from the original URL communication will fail. | |
118 var suiteUrl = url.resolve( | |
119 "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/"); | |
120 return browserManager.loadSuite(path, suiteUrl); | |
121 }); | |
122 }); | |
123 } | |
124 | |
125 /// Compile the test suite at [dartPath] to JavaScript. | |
126 /// | |
127 /// Returns a [Future] that completes to the path to the JavaScript. | |
128 Future<String> _compileSuite(String dartPath) { | |
129 var dir = new Directory(_compiledDir).createTempSync('test_').path; | |
130 var jsPath = p.join(dir, p.basename(dartPath) + ".js"); | |
131 return _compilers.compile(dartPath, jsPath, | |
132 packageRoot: packageRootFor(dartPath, _packageRoot)) | |
133 .then((_) { | |
134 // TODO(nweiz): support user-authored HTML files. | |
135 new File(p.join(dir, "index.html")).writeAsStringSync(''' | |
136 <!DOCTYPE html> | |
137 <html> | |
138 <head> | |
139 <title>${HTML_ESCAPE.convert(dartPath)} Test</title> | |
140 <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script> | |
141 </head> | |
142 </html> | |
143 '''); | |
144 return dir; | |
145 }); | |
146 } | |
147 | |
148 /// Closes the server and releases all its resources. | |
149 /// | |
150 /// Returns a [Future] that completes once the server is closed and its | |
151 /// resources have been fully released. | |
152 Future close() { | |
153 new Directory(_compiledDir).deleteSync(recursive: true); | |
154 return _server.close().then((_) { | |
155 if (_browserManagerCompleter == null) return null; | |
156 return _browserManager.then((_) => _browser.close()); | |
157 }); | |
158 } | |
159 } | |
OLD | NEW |