Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(226)

Side by Side Diff: lib/src/runner/browser/platform.dart

Issue 1704773002: Load web tests using the plugin infrastructure. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 import 'dart:async'; 5 import 'dart:async';
6 import 'dart:convert'; 6 import 'dart:convert';
7 import 'dart:io'; 7 import 'dart:io';
8 8
9 import 'package:async/async.dart'; 9 import 'package:async/async.dart';
10 import 'package:http_multi_server/http_multi_server.dart'; 10 import 'package:http_multi_server/http_multi_server.dart';
11 import 'package:path/path.dart' as p; 11 import 'package:path/path.dart' as p;
12 import 'package:pool/pool.dart'; 12 import 'package:pool/pool.dart';
13 import 'package:shelf/shelf.dart' as shelf; 13 import 'package:shelf/shelf.dart' as shelf;
14 import 'package:shelf/shelf_io.dart' as shelf_io; 14 import 'package:shelf/shelf_io.dart' as shelf_io;
15 import 'package:shelf_static/shelf_static.dart'; 15 import 'package:shelf_static/shelf_static.dart';
16 import 'package:shelf_web_socket/shelf_web_socket.dart'; 16 import 'package:shelf_web_socket/shelf_web_socket.dart';
17 import 'package:stream_channel/stream_channel.dart';
17 18
18 import '../../backend/metadata.dart'; 19 import '../../backend/metadata.dart';
19 import '../../backend/suite.dart';
20 import '../../backend/test_platform.dart'; 20 import '../../backend/test_platform.dart';
21 import '../../util/io.dart'; 21 import '../../util/io.dart';
22 import '../../util/one_off_handler.dart'; 22 import '../../util/one_off_handler.dart';
23 import '../../util/path_handler.dart'; 23 import '../../util/path_handler.dart';
24 import '../../util/stack_trace_mapper.dart'; 24 import '../../util/stack_trace_mapper.dart';
25 import '../../utils.dart'; 25 import '../../utils.dart';
26 import '../configuration.dart'; 26 import '../configuration.dart';
27 import '../load_exception.dart'; 27 import '../load_exception.dart';
28 import '../plugin/platform.dart';
29 import '../runner_suite.dart';
28 import 'browser_manager.dart'; 30 import 'browser_manager.dart';
29 import 'compiler_pool.dart'; 31 import 'compiler_pool.dart';
30 import 'polymer.dart'; 32 import 'polymer.dart';
31 33
32 /// A server that serves JS-compiled tests to browsers. 34 class BrowserPlatform extends PlatformPlugin {
33 ///
34 /// A test suite may be loaded for a given file using [loadSuite].
35 class BrowserServer {
36 /// Starts the server. 35 /// Starts the server.
37 /// 36 ///
38 /// [root] is the root directory that the server should serve. It defaults to 37 /// [root] is the root directory that the server should serve. It defaults to
39 /// the working directory. 38 /// the working directory.
40 static Future<BrowserServer> start(Configuration config, {String root}) 39 static Future<BrowserPlatform> start(Configuration config, {String root})
41 async { 40 async {
42 var server = new shelf_io.IOServer(await HttpMultiServer.loopback(0)); 41 var server = new shelf_io.IOServer(await HttpMultiServer.loopback(0));
43 return new BrowserServer(server, config, root: root); 42 return new BrowserPlatform._(server, config, root: root);
44 } 43 }
45 44
46 /// The underlying server. 45 /// The underlying server.
47 final shelf.Server _server; 46 final shelf.Server _server;
48 47
49 /// A randomly-generated secret. 48 /// A randomly-generated secret.
50 /// 49 ///
51 /// This is used to ensure that other users on the same system can't snoop 50 /// This is used to ensure that other users on the same system can't snoop
52 /// on data being served through this server. 51 /// on data being served through this server.
53 final _secret = randomBase64(24, urlSafe: true); 52 final _secret = randomBase64(24, urlSafe: true);
(...skipping 30 matching lines...) Expand all
84 /// Pub itself ensures that only one compilation runs at a time; we just use 83 /// Pub itself ensures that only one compilation runs at a time; we just use
85 /// this pool to make sure that the output is nice and linear. 84 /// this pool to make sure that the output is nice and linear.
86 final _pubServePool = new Pool(1); 85 final _pubServePool = new Pool(1);
87 86
88 /// The HTTP client to use when caching JS files in `pub serve`. 87 /// The HTTP client to use when caching JS files in `pub serve`.
89 final HttpClient _http; 88 final HttpClient _http;
90 89
91 /// Whether [close] has been called. 90 /// Whether [close] has been called.
92 bool get _closed => _closeMemo.hasRun; 91 bool get _closed => _closeMemo.hasRun;
93 92
94 /// The memoizer for running [close] exactly once.
95 final _closeMemo = new AsyncMemoizer();
96
97 /// A map from browser identifiers to futures that will complete to the 93 /// A map from browser identifiers to futures that will complete to the
98 /// [BrowserManager]s for those browsers, or the errors that occurred when 94 /// [BrowserManager]s for those browsers, or the errors that occurred when
99 /// trying to load those managers. 95 /// trying to load those managers.
100 /// 96 ///
101 /// This should only be accessed through [_browserManagerFor]. 97 /// This should only be accessed through [_browserManagerFor].
102 final _browserManagers = 98 final _browserManagers =
103 new Map<TestPlatform, Future<Result<BrowserManager>>>(); 99 new Map<TestPlatform, Future<Result<BrowserManager>>>();
104 100
105 /// A map from test suite paths to Futures that will complete once those 101 /// A map from test suite paths to Futures that will complete once those
106 /// suites are finished compiling. 102 /// suites are finished compiling.
107 /// 103 ///
108 /// This is used to make sure that a given test suite is only compiled once 104 /// This is used to make sure that a given test suite is only compiled once
109 /// per run, rather than once per browser per run. 105 /// per run, rather than once per browser per run.
110 final _compileFutures = new Map<String, Future>(); 106 final _compileFutures = new Map<String, Future>();
111 107
108 /// Mappers for Dartifying stack traces, indexed by test path.
112 final _mappers = new Map<String, StackTraceMapper>(); 109 final _mappers = new Map<String, StackTraceMapper>();
113 110
114 BrowserServer(this._server, Configuration config, {String root}) 111 BrowserPlatform._(this._server, Configuration config, {String root})
115 : _root = root == null ? p.current : root, 112 : _root = root == null ? p.current : root,
116 _config = config, 113 _config = config,
117 _compiledDir = config.pubServeUrl == null ? createTempDir() : null, 114 _compiledDir = config.pubServeUrl == null ? createTempDir() : null,
118 _http = config.pubServeUrl == null ? null : new HttpClient(), 115 _http = config.pubServeUrl == null ? null : new HttpClient(),
119 _compilers = new CompilerPool(color: config.color) { 116 _compilers = new CompilerPool(color: config.color) {
120 var cascade = new shelf.Cascade() 117 var cascade = new shelf.Cascade()
121 .add(_webSocketHandler.handler); 118 .add(_webSocketHandler.handler);
122 119
123 if (_config.pubServeUrl == null) { 120 if (_config.pubServeUrl == null) {
124 cascade = cascade 121 cascade = cascade
(...skipping 29 matching lines...) Expand all
154 151
155 return new shelf.Response.notFound("Not found."); 152 return new shelf.Response.notFound("Not found.");
156 }; 153 };
157 } 154 }
158 155
159 /// A handler that serves wrapper files used to bootstrap tests. 156 /// A handler that serves wrapper files used to bootstrap tests.
160 shelf.Response _wrapperHandler(shelf.Request request) { 157 shelf.Response _wrapperHandler(shelf.Request request) {
161 var path = p.fromUri(request.url); 158 var path = p.fromUri(request.url);
162 159
163 if (path.endsWith(".browser_test.dart")) { 160 if (path.endsWith(".browser_test.dart")) {
161 var testPath = p.basename(p.withoutExtension(p.withoutExtension(path)));
164 return new shelf.Response.ok(''' 162 return new shelf.Response.ok('''
165 import "package:test/src/runner/browser/iframe_listener.dart"; 163 import "package:stream_channel/stream_channel.dart";
166 164
167 import "${p.basename(p.withoutExtension(p.withoutExtension(path)))}" as test; 165 import "package:test/src/runner/plugin/remote_platform_helpers.dart";
166 import "package:test/src/runner/browser/post_message_channel.dart";
168 167
169 void main() { 168 import "$testPath" as test;
170 IframeListener.start(() => test.main); 169
171 } 170 void main() {
172 ''', headers: {'Content-Type': 'application/dart'}); 171 var channel = serializeSuite(() => test.main, hidePrints: false);
172 postMessageChannel().pipe(channel);
173 }
174 ''', headers: {'Content-Type': 'application/dart'});
173 } 175 }
174 176
175 if (path.endsWith(".html")) { 177 if (path.endsWith(".html")) {
176 var test = p.withoutExtension(path) + ".dart"; 178 var test = p.withoutExtension(path) + ".dart";
177 179
178 // Link to the Dart wrapper on Dartium and the compiled JS version 180 // Link to the Dart wrapper on Dartium and the compiled JS version
179 // elsewhere. 181 // elsewhere.
180 var scriptBase = 182 var scriptBase =
181 "${HTML_ESCAPE.convert(p.basename(test))}.browser_test.dart"; 183 "${HTML_ESCAPE.convert(p.basename(test))}.browser_test.dart";
182 var script = request.headers['user-agent'].contains('(Dart)') 184 var script = request.headers['user-agent'].contains('(Dart)')
183 ? 'type="application/dart" src="$scriptBase"' 185 ? 'type="application/dart" src="$scriptBase"'
184 : 'src="$scriptBase.js"'; 186 : 'src="$scriptBase.js"';
185 187
186 return new shelf.Response.ok(''' 188 return new shelf.Response.ok('''
187 <!DOCTYPE html> 189 <!DOCTYPE html>
188 <html> 190 <html>
189 <head> 191 <head>
190 <title>${HTML_ESCAPE.convert(test)} Test</title> 192 <title>${HTML_ESCAPE.convert(test)} Test</title>
191 <script $script></script> 193 <script $script></script>
192 </head> 194 </head>
193 </html> 195 </html>
194 ''', headers: {'Content-Type': 'text/html'}); 196 ''', headers: {'Content-Type': 'text/html'});
195 } 197 }
196 198
197 return new shelf.Response.notFound('Not found.'); 199 return new shelf.Response.notFound('Not found.');
198 } 200 }
199 201
200 /// Loads the test suite at [path] on the browser [browser]. 202 /// Loads the test suite at [path] on the browser [browser].
201 /// 203 ///
202 /// This will start a browser to load the suite if one isn't already running. 204 /// This will start a browser to load the suite if one isn't already running.
203 /// Throws an [ArgumentError] if [browser] isn't a browser platform. 205 /// Throws an [ArgumentError] if [browser] isn't a browser platform.
204 Future<Suite> loadSuite(String path, TestPlatform browser, 206 Future<RunnerSuite> load(String path, TestPlatform browser,
205 Metadata metadata) async { 207 Metadata metadata) async {
206 if (!browser.isBrowser) { 208 if (!browser.isBrowser) {
207 throw new ArgumentError("$browser is not a browser."); 209 throw new ArgumentError("$browser is not a browser.");
208 } 210 }
209 211
210 var htmlPath = p.withoutExtension(path) + '.html'; 212 var htmlPath = p.withoutExtension(path) + '.html';
211 if (new File(htmlPath).existsSync() && 213 if (new File(htmlPath).existsSync() &&
212 !new File(htmlPath).readAsStringSync() 214 !new File(htmlPath).readAsStringSync()
213 .contains('packages/test/dart.js')) { 215 .contains('packages/test/dart.js')) {
214 throw new LoadException( 216 throw new LoadException(
(...skipping 29 matching lines...) Expand all
244 suiteUrl = url.resolveUri(p.toUri( 246 suiteUrl = url.resolveUri(p.toUri(
245 p.withoutExtension(p.relative(path, from: _root)) + ".html")); 247 p.withoutExtension(p.relative(path, from: _root)) + ".html"));
246 } 248 }
247 249
248 if (_closed) return null; 250 if (_closed) return null;
249 251
250 // TODO(nweiz): Don't start the browser until all the suites are compiled. 252 // TODO(nweiz): Don't start the browser until all the suites are compiled.
251 var browserManager = await _browserManagerFor(browser); 253 var browserManager = await _browserManagerFor(browser);
252 if (_closed) return null; 254 if (_closed) return null;
253 255
254 var suite = await browserManager.loadSuite(path, suiteUrl, metadata, 256 var suite = await browserManager.load(path, suiteUrl, metadata,
255 mapper: browser.isJS ? _mappers[path] : null); 257 mapper: browser.isJS ? _mappers[path] : null);
256 if (_closed) return null; 258 if (_closed) return null;
257 return suite; 259 return suite;
258 } 260 }
259 261
262 StreamChannel loadChannel(String path, TestPlatform platform) =>
263 throw new UnimplementedError();
264
260 /// Loads a test suite at [path] from the `pub serve` URL [dartUrl]. 265 /// Loads a test suite at [path] from the `pub serve` URL [dartUrl].
261 /// 266 ///
262 /// This ensures that only one suite is loaded at a time, and that any errors 267 /// This ensures that only one suite is loaded at a time, and that any errors
263 /// are exposed as [LoadException]s. 268 /// are exposed as [LoadException]s.
264 Future _pubServeSuite(String path, Uri dartUrl, TestPlatform browser) { 269 Future _pubServeSuite(String path, Uri dartUrl, TestPlatform browser) {
265 return _pubServePool.withResource(() async { 270 return _pubServePool.withResource(() async {
266 var timer = new Timer(new Duration(seconds: 1), () { 271 var timer = new Timer(new Duration(seconds: 1), () {
267 print('"pub serve" is compiling $path...'); 272 print('"pub serve" is compiling $path...');
268 }); 273 });
269 274
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
382 // an explicit [Result] fixes that. 387 // an explicit [Result] fixes that.
383 _browserManagers[platform] = Result.capture(future); 388 _browserManagers[platform] = Result.capture(future);
384 389
385 return future; 390 return future;
386 } 391 }
387 392
388 /// Close all the browsers that the server currently has open. 393 /// Close all the browsers that the server currently has open.
389 /// 394 ///
390 /// Note that this doesn't close the server itself. Browser tests can still be 395 /// Note that this doesn't close the server itself. Browser tests can still be
391 /// loaded, they'll just spawn new browsers. 396 /// loaded, they'll just spawn new browsers.
392 Future closeBrowsers() { 397 Future closeEphemeral() {
393 var managers = _browserManagers.values.toList(); 398 var managers = _browserManagers.values.toList();
394 _browserManagers.clear(); 399 _browserManagers.clear();
395 return Future.wait(managers.map((manager) async { 400 return Future.wait(managers.map((manager) async {
396 var result = await manager; 401 var result = await manager;
397 if (result.isError) return; 402 if (result.isError) return;
398 await result.asValue.value.close(); 403 await result.asValue.value.close();
399 })); 404 }));
400 } 405 }
401 406
402 /// Closes the server and releases all its resources. 407 /// Closes the server and releases all its resources.
403 /// 408 ///
404 /// Returns a [Future] that completes once the server is closed and its 409 /// Returns a [Future] that completes once the server is closed and its
405 /// resources have been fully released. 410 /// resources have been fully released.
406 Future close() { 411 Future close() => _closeMemo.runOnce(() async {
407 return _closeMemo.runOnce(() async { 412 var futures = _browserManagers.values.map((future) async {
408 var futures = _browserManagers.values.map((future) async { 413 var result = await future;
409 var result = await future; 414 if (result.isError) return;
410 if (result.isError) return;
411 415
412 await result.asValue.value.close(); 416 await result.asValue.value.close();
413 }).toList(); 417 }).toList();
414 418
415 futures.add(_server.close()); 419 futures.add(_server.close());
416 futures.add(_compilers.close()); 420 futures.add(_compilers.close());
417 421
418 await Future.wait(futures); 422 await Future.wait(futures);
419 423
420 if (_config.pubServeUrl == null) { 424 if (_config.pubServeUrl == null) {
421 new Directory(_compiledDir).deleteSync(recursive: true); 425 new Directory(_compiledDir).deleteSync(recursive: true);
422 } else { 426 } else {
423 _http.close(); 427 _http.close();
424 } 428 }
425 }); 429 });
426 } 430 final _closeMemo = new AsyncMemoizer();
427 } 431 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698