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

Side by Side Diff: sdk/lib/_internal/pub_generated/test/serve/utils.dart

Issue 896623005: Use native async/await support in pub. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Code review changes Created 5 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS d.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 pub_tests;
6
7 import 'dart:async';
8 import 'dart:convert';
9 import 'dart:io';
10
11 import 'package:http/http.dart' as http;
12 import 'package:scheduled_test/scheduled_process.dart';
13 import 'package:scheduled_test/scheduled_stream.dart';
14 import 'package:scheduled_test/scheduled_test.dart';
15 import 'package:stack_trace/stack_trace.dart';
16
17 import '../../lib/src/utils.dart';
18 import '../descriptor.dart' as d;
19 import '../test_pub.dart';
20
21 /// The pub process running "pub serve".
22 ScheduledProcess _pubServer;
23
24 /// The ephemeral port assign to the running admin server.
25 int _adminPort;
26
27 /// The ephemeral ports assigned to the running servers, associated with the
28 /// directories they're serving.
29 final _ports = new Map<String, int>();
30
31 /// A completer that completes when the server has been started and the served
32 /// ports are known.
33 Completer _portsCompleter;
34
35 /// The web socket connection to the running pub process, or `null` if no
36 /// connection has been made.
37 WebSocket _webSocket;
38 Stream _webSocketBroadcastStream;
39
40 /// The code for a transformer that renames ".txt" files to ".out" and adds a
41 /// ".out" suffix.
42 const REWRITE_TRANSFORMER = """
43 import 'dart:async';
44
45 import 'package:barback/barback.dart';
46
47 class RewriteTransformer extends Transformer {
48 RewriteTransformer.asPlugin();
49
50 String get allowedExtensions => '.txt';
51
52 Future apply(Transform transform) {
53 return transform.primaryInput.readAsString().then((contents) {
54 var id = transform.primaryInput.id.changeExtension(".out");
55 transform.addOutput(new Asset.fromString(id, "\$contents.out"));
56 });
57 }
58 }
59 """;
60
61 /// The code for a lazy version of [REWRITE_TRANSFORMER].
62 const LAZY_TRANSFORMER = """
63 import 'dart:async';
64
65 import 'package:barback/barback.dart';
66
67 class LazyRewriteTransformer extends Transformer implements LazyTransformer {
68 LazyRewriteTransformer.asPlugin();
69
70 String get allowedExtensions => '.txt';
71
72 Future apply(Transform transform) {
73 transform.logger.info('Rewriting \${transform.primaryInput.id}.');
74 return transform.primaryInput.readAsString().then((contents) {
75 var id = transform.primaryInput.id.changeExtension(".out");
76 transform.addOutput(new Asset.fromString(id, "\$contents.out"));
77 });
78 }
79
80 Future declareOutputs(DeclaringTransform transform) {
81 transform.declareOutput(transform.primaryId.changeExtension(".out"));
82 return new Future.value();
83 }
84 }
85 """;
86
87 /// The web socket error code for a directory not being served.
88 const NOT_SERVED = 1;
89
90 /// Returns the source code for a Dart library defining a Transformer that
91 /// rewrites Dart files.
92 ///
93 /// The transformer defines a constant named TOKEN whose value is [id]. When the
94 /// transformer transforms another Dart file, it will look for a "TOKEN"
95 /// constant definition there and modify it to include *this* transformer's
96 /// TOKEN value as well.
97 ///
98 /// If [import] is passed, it should be the name of a package that defines its
99 /// own TOKEN constant. The primary library of that package will be imported
100 /// here and its TOKEN value will be added to this library's.
101 ///
102 /// This transformer takes one configuration field: "addition". This is
103 /// concatenated to its TOKEN value before adding it to the output library.
104 String dartTransformer(String id, {String import}) {
105 if (import != null) {
106 id = '$id imports \${$import.TOKEN}';
107 import = 'import "package:$import/$import.dart" as $import;';
108 } else {
109 import = '';
110 }
111
112 return """
113 import 'dart:async';
114
115 import 'package:barback/barback.dart';
116 $import
117
118 import 'dart:io';
119
120 const TOKEN = "$id";
121
122 final _tokenRegExp = new RegExp(r'^const TOKEN = "(.*?)";\$', multiLine: true);
123
124 class DartTransformer extends Transformer {
125 final BarbackSettings _settings;
126
127 DartTransformer.asPlugin(this._settings);
128
129 String get allowedExtensions => '.dart';
130
131 Future apply(Transform transform) {
132 return transform.primaryInput.readAsString().then((contents) {
133 transform.addOutput(new Asset.fromString(transform.primaryInput.id,
134 contents.replaceAllMapped(_tokenRegExp, (match) {
135 var token = TOKEN;
136 var addition = _settings.configuration["addition"];
137 if (addition != null) token += addition;
138 return 'const TOKEN = "(\${match[1]}, \$token)";';
139 })));
140 });
141 }
142 }
143 """;
144 }
145
146 /// Schedules starting the `pub serve` process.
147 ///
148 /// Unlike [pubServe], this doesn't determine the port number of the server, and
149 /// so may be used to test for errors in the initialization process.
150 ///
151 /// Returns the `pub serve` process.
152 ScheduledProcess startPubServe({Iterable<String> args, bool createWebDir: true})
153 {
154 var pubArgs = ["serve", "--port=0", // Use port 0 to get an ephemeral port.
155 "--force-poll", "--log-admin-url"];
156
157 if (args != null) pubArgs.addAll(args);
158
159 // Dart2js can take a long time to compile dart code, so we increase the
160 // timeout to cope with that.
161 currentSchedule.timeout *= 1.5;
162
163 if (createWebDir) d.dir(appPath, [d.dir("web")]).create();
164 return startPub(args: pubArgs);
165 }
166
167 /// Schedules starting the "pub serve" process and records its port number for
168 /// future requests.
169 ///
170 /// If [shouldGetFirst] is `true`, validates that pub get is run first.
171 ///
172 /// If [createWebDir] is `true`, creates a `web/` directory if one doesn't exist
173 /// so pub doesn't complain about having nothing to serve.
174 ///
175 /// Returns the `pub serve` process.
176 ScheduledProcess pubServe({bool shouldGetFirst: false, bool createWebDir: true,
177 Iterable<String> args}) {
178 _pubServer = startPubServe(args: args, createWebDir: createWebDir);
179 _portsCompleter = new Completer();
180
181 currentSchedule.onComplete.schedule(() {
182 _portsCompleter = null;
183 _ports.clear();
184
185 if (_webSocket != null) {
186 _webSocket.close();
187 _webSocket = null;
188 _webSocketBroadcastStream = null;
189 }
190 });
191
192 if (shouldGetFirst) {
193 _pubServer.stdout.expect(
194 consumeThrough(
195 anyOf(["Got dependencies!", matches(new RegExp(r"^Changed \d+ depend enc"))])));
196 }
197
198 _pubServer.stdout.expect(startsWith("Loading source assets..."));
199 _pubServer.stdout.expect(consumeWhile(matches("Loading .* transformers...")));
200
201 _pubServer.stdout.expect(predicate(_parseAdminPort));
202
203 // The server should emit one or more ports.
204 _pubServer.stdout.expect(
205 consumeWhile(predicate(_parsePort, 'emits server url')));
206 schedule(() {
207 expect(_ports, isNot(isEmpty));
208 _portsCompleter.complete();
209 });
210
211 return _pubServer;
212 }
213
214 /// The regular expression for parsing pub's output line describing the URL for
215 /// the server.
216 final _parsePortRegExp = new RegExp(r"([^ ]+) +on http://localhost:(\d+)");
217
218 /// Parses the port number from the "Running admin server on localhost:1234"
219 /// line printed by pub serve.
220 bool _parseAdminPort(String line) {
221 var match = _parsePortRegExp.firstMatch(line);
222 if (match == null) return false;
223 _adminPort = int.parse(match[2]);
224 return true;
225 }
226
227 /// Parses the port number from the "Serving blah on localhost:1234" line
228 /// printed by pub serve.
229 bool _parsePort(String line) {
230 var match = _parsePortRegExp.firstMatch(line);
231 if (match == null) return false;
232 _ports[match[1]] = int.parse(match[2]);
233 return true;
234 }
235
236 void endPubServe() {
237 _pubServer.kill();
238 }
239
240 /// Schedules an HTTP request to the running pub server with [urlPath] and
241 /// invokes [callback] with the response.
242 ///
243 /// [root] indicates which server should be accessed, and defaults to "web".
244 Future<http.Response> scheduleRequest(String urlPath, {String root}) {
245 return schedule(() {
246 return http.get(_getServerUrlSync(root, urlPath));
247 }, "request $urlPath");
248 }
249
250 /// Schedules an HTTP request to the running pub server with [urlPath] and
251 /// verifies that it responds with a body that matches [expectation].
252 ///
253 /// [expectation] may either be a [Matcher] or a string to match an exact body.
254 /// [root] indicates which server should be accessed, and defaults to "web".
255 /// [headers] may be either a [Matcher] or a map to match an exact headers map.
256 void requestShouldSucceed(String urlPath, expectation, {String root, headers}) {
257 scheduleRequest(urlPath, root: root).then((response) {
258 expect(response.statusCode, equals(200));
259 if (expectation != null) expect(response.body, expectation);
260 if (headers != null) expect(response.headers, headers);
261 });
262 }
263
264 /// Schedules an HTTP request to the running pub server with [urlPath] and
265 /// verifies that it responds with a 404.
266 ///
267 /// [root] indicates which server should be accessed, and defaults to "web".
268 void requestShould404(String urlPath, {String root}) {
269 scheduleRequest(urlPath, root: root).then((response) {
270 expect(response.statusCode, equals(404));
271 });
272 }
273
274 /// Schedules an HTTP request to the running pub server with [urlPath] and
275 /// verifies that it responds with a redirect to the given [redirectTarget].
276 ///
277 /// [redirectTarget] may be either a [Matcher] or a string to match an exact
278 /// URL. [root] indicates which server should be accessed, and defaults to
279 /// "web".
280 void requestShouldRedirect(String urlPath, redirectTarget, {String root}) {
281 schedule(() {
282 var request =
283 new http.Request("GET", Uri.parse(_getServerUrlSync(root, urlPath)));
284 request.followRedirects = false;
285 return request.send().then((response) {
286 expect(response.statusCode ~/ 100, equals(3));
287 expect(response.headers, containsPair('location', redirectTarget));
288 });
289 }, "request $urlPath");
290 }
291
292 /// Schedules an HTTP POST to the running pub server with [urlPath] and verifies
293 /// that it responds with a 405.
294 ///
295 /// [root] indicates which server should be accessed, and defaults to "web".
296 void postShould405(String urlPath, {String root}) {
297 schedule(() {
298 return http.post(_getServerUrlSync(root, urlPath)).then((response) {
299 expect(response.statusCode, equals(405));
300 });
301 }, "request $urlPath");
302 }
303
304 /// Schedules an HTTP request to the (theoretically) running pub server with
305 /// [urlPath] and verifies that it cannot be connected to.
306 ///
307 /// [root] indicates which server should be accessed, and defaults to "web".
308 void requestShouldNotConnect(String urlPath, {String root}) {
309 schedule(() {
310 return expect(
311 http.get(_getServerUrlSync(root, urlPath)),
312 throwsA(new isInstanceOf<SocketException>()));
313 }, "request $urlPath");
314 }
315
316 /// Reads lines from pub serve's stdout until it prints the build success
317 /// message.
318 ///
319 /// The schedule will not proceed until the output is found. If not found, it
320 /// will eventually time out.
321 void waitForBuildSuccess() =>
322 _pubServer.stdout.expect(consumeThrough(contains("successfully")));
323
324 /// Schedules opening a web socket connection to the currently running pub
325 /// serve.
326 Future _ensureWebSocket() {
327 // Use the existing one if already connected.
328 if (_webSocket != null) return new Future.value();
329
330 // Server should already be running.
331 expect(_pubServer, isNotNull);
332 expect(_adminPort, isNotNull);
333
334 return WebSocket.connect("ws://localhost:$_adminPort").then((socket) {
335 _webSocket = socket;
336 // TODO(rnystrom): Works around #13913.
337 _webSocketBroadcastStream = _webSocket.map(JSON.decode).asBroadcastStream();
338 });
339 }
340
341 /// Schedules closing the web socket connection to the currently-running pub
342 /// serve.
343 void closeWebSocket() {
344 schedule(() {
345 return _ensureWebSocket().then((_) => _webSocket.close()).then((_) => _webSo cket =
346 null);
347 }, "closing web socket");
348 }
349
350 /// Sends a JSON RPC 2.0 request to the running pub serve's web socket
351 /// connection.
352 ///
353 /// This calls a method named [method] with the given [params] (or no
354 /// parameters, if it's not passed). [params] may contain Futures, in which case
355 /// this will wait until they've completed before sending the request.
356 ///
357 /// This schedules the request, but doesn't block the schedule on the response.
358 /// It returns the response as a [Future].
359 Future<Map> webSocketRequest(String method, [Map params]) {
360 var completer = new Completer();
361 schedule(() {
362 return Future.wait(
363 [_ensureWebSocket(), awaitObject(params),]).then((results) {
364 var resolvedParams = results[1];
365 chainToCompleter(
366 currentSchedule.wrapFuture(_jsonRpcRequest(method, resolvedParams)),
367 completer);
368 });
369 }, "send $method with $params to web socket");
370 return completer.future;
371 }
372
373 /// Sends a JSON RPC 2.0 request to the running pub serve's web socket
374 /// connection, waits for a reply, then verifies the result.
375 ///
376 /// This calls a method named [method] with the given [params]. [params] may
377 /// contain Futures, in which case this will wait until they've completed before
378 /// sending the request.
379 ///
380 /// The result is validated using [result], which may be a [Matcher] or a [Map]
381 /// containing [Matcher]s and [Future]s. This will wait until any futures are
382 /// completed before sending the request.
383 ///
384 /// Returns a [Future] that completes to the call's result.
385 Future<Map> expectWebSocketResult(String method, Map params, result) {
386 return schedule(() {
387 return Future.wait(
388 [webSocketRequest(method, params), awaitObject(result)]).then((results) {
389 var response = results[0];
390 var resolvedResult = results[1];
391 expect(response["result"], resolvedResult);
392 return response["result"];
393 });
394 }, "send $method with $params to web socket and expect $result");
395 }
396
397 /// Sends a JSON RPC 2.0 request to the running pub serve's web socket
398 /// connection, waits for a reply, then verifies the error response.
399 ///
400 /// This calls a method named [method] with the given [params]. [params] may
401 /// contain Futures, in which case this will wait until they've completed before
402 /// sending the request.
403 ///
404 /// The error response is validated using [errorCode] and [errorMessage]. Both
405 /// of these must be provided. The error code is checked against [errorCode] and
406 /// the error message is checked against [errorMessage]. Either of these may be
407 /// matchers.
408 ///
409 /// If [data] is provided, it is a JSON value or matcher used to validate the
410 /// "data" value of the error response.
411 ///
412 /// Returns a [Future] that completes to the error's [data] field.
413 Future expectWebSocketError(String method, Map params, errorCode, errorMessage,
414 {data}) {
415 return schedule(() {
416 return webSocketRequest(method, params).then((response) {
417 expect(response["error"]["code"], errorCode);
418 expect(response["error"]["message"], errorMessage);
419
420 if (data != null) {
421 expect(response["error"]["data"], data);
422 }
423
424 return response["error"]["data"];
425 });
426 }, "send $method with $params to web socket and expect error $errorCode");
427 }
428
429 /// Validates that [root] was not bound to a port when pub serve started.
430 Future expectNotServed(String root) {
431 return schedule(() {
432 expect(_ports.containsKey(root), isFalse);
433 });
434 }
435
436 /// The next id to use for a JSON-RPC 2.0 request.
437 var _rpcId = 0;
438
439 /// Sends a JSON-RPC 2.0 request calling [method] with [params].
440 ///
441 /// Returns the response object.
442 Future<Map> _jsonRpcRequest(String method, [Map params]) {
443 var id = _rpcId++;
444 var message = {
445 "jsonrpc": "2.0",
446 "method": method,
447 "id": id
448 };
449 if (params != null) message["params"] = params;
450 _webSocket.add(JSON.encode(message));
451
452 return _webSocketBroadcastStream.firstWhere(
453 (response) => response["id"] == id).then((value) {
454 currentSchedule.addDebugInfo(
455 "Web Socket request $method with params $params\n" "Result: $value");
456
457 expect(value["id"], equals(id));
458 return value;
459 });
460 }
461
462 /// Returns a [Future] that completes to a URL string for the server serving
463 /// [path] from [root].
464 ///
465 /// If [root] is omitted, defaults to "web". If [path] is omitted, no path is
466 /// included. The Future will complete once the server is up and running and
467 /// the bound ports are known.
468 Future<String> getServerUrl([String root, String path]) =>
469 _portsCompleter.future.then((_) => _getServerUrlSync(root, path));
470
471 /// Records that [root] has been bound to [port].
472 ///
473 /// Used for testing the Web Socket API for binding new root directories to
474 /// ports after pub serve has been started.
475 registerServerPort(String root, int port) {
476 _ports[root] = port;
477 }
478
479 /// Returns a URL string for the server serving [path] from [root].
480 ///
481 /// If [root] is omitted, defaults to "web". If [path] is omitted, no path is
482 /// included. Unlike [getServerUrl], this should only be called after the ports
483 /// are known.
484 String _getServerUrlSync([String root, String path]) {
485 if (root == null) root = 'web';
486 expect(_ports, contains(root));
487 var url = "http://localhost:${_ports[root]}";
488 if (path != null) url = "$url/$path";
489 return url;
490 }
491
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698