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

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

Issue 1165473002: Start pulling pub from its own repo. (Closed) Base URL: git@github.com:dart-lang/sdk.git@master
Patch Set: Code review changes Created 5 years, 6 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
(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,
153 bool createWebDir: true}) {
154 var pubArgs = [
155 "serve",
156 "--port=0", // Use port 0 to get an ephemeral port.
157 "--force-poll",
158 "--admin-port=0", // Use port 0 to get an ephemeral port.
159 "--log-admin-url"
160 ];
161
162 if (args != null) pubArgs.addAll(args);
163
164 // Dart2js can take a long time to compile dart code, so we increase the
165 // timeout to cope with that.
166 currentSchedule.timeout *= 1.5;
167
168 if (createWebDir) d.dir(appPath, [d.dir("web")]).create();
169 return startPub(args: pubArgs);
170 }
171
172 /// Schedules starting the "pub serve" process and records its port number for
173 /// future requests.
174 ///
175 /// If [shouldGetFirst] is `true`, validates that pub get is run first.
176 ///
177 /// If [createWebDir] is `true`, creates a `web/` directory if one doesn't exist
178 /// so pub doesn't complain about having nothing to serve.
179 ///
180 /// Returns the `pub serve` process.
181 ScheduledProcess pubServe({bool shouldGetFirst: false, bool createWebDir: true,
182 Iterable<String> args}) {
183 _pubServer = startPubServe(args: args, createWebDir: createWebDir);
184 _portsCompleter = new Completer();
185
186 currentSchedule.onComplete.schedule(() {
187 _portsCompleter = null;
188 _ports.clear();
189
190 if (_webSocket != null) {
191 _webSocket.close();
192 _webSocket = null;
193 _webSocketBroadcastStream = null;
194 }
195 });
196
197 if (shouldGetFirst) {
198 _pubServer.stdout.expect(consumeThrough(anyOf([
199 "Got dependencies!",
200 matches(new RegExp(r"^Changed \d+ dependenc"))
201 ])));
202 }
203
204 _pubServer.stdout.expect(startsWith("Loading source assets..."));
205 _pubServer.stdout.expect(consumeWhile(matches("Loading .* transformers...")));
206
207 _pubServer.stdout.expect(predicate(_parseAdminPort));
208
209 // The server should emit one or more ports.
210 _pubServer.stdout.expect(
211 consumeWhile(predicate(_parsePort, 'emits server url')));
212 schedule(() {
213 expect(_ports, isNot(isEmpty));
214 _portsCompleter.complete();
215 });
216
217 return _pubServer;
218 }
219
220 /// The regular expression for parsing pub's output line describing the URL for
221 /// the server.
222 final _parsePortRegExp = new RegExp(r"([^ ]+) +on http://localhost:(\d+)");
223
224 /// Parses the port number from the "Running admin server on localhost:1234"
225 /// line printed by pub serve.
226 bool _parseAdminPort(String line) {
227 expect(line, startsWith('Running admin server on'));
228 var match = _parsePortRegExp.firstMatch(line);
229 if (match == null) return false;
230 _adminPort = int.parse(match[2]);
231 return true;
232 }
233
234 /// Parses the port number from the "Serving blah on localhost:1234" line
235 /// printed by pub serve.
236 bool _parsePort(String line) {
237 var match = _parsePortRegExp.firstMatch(line);
238 if (match == null) return false;
239 _ports[match[1]] = int.parse(match[2]);
240 return true;
241 }
242
243 void endPubServe() {
244 _pubServer.kill();
245 }
246
247 /// Schedules an HTTP request to the running pub server with [urlPath] and
248 /// invokes [callback] with the response.
249 ///
250 /// [root] indicates which server should be accessed, and defaults to "web".
251 Future<http.Response> scheduleRequest(String urlPath, {String root}) {
252 return schedule(() {
253 return http.get(_getServerUrlSync(root, urlPath));
254 }, "request $urlPath");
255 }
256
257 /// Schedules an HTTP request to the running pub server with [urlPath] and
258 /// verifies that it responds with a body that matches [expectation].
259 ///
260 /// [expectation] may either be a [Matcher] or a string to match an exact body.
261 /// [root] indicates which server should be accessed, and defaults to "web".
262 /// [headers] may be either a [Matcher] or a map to match an exact headers map.
263 void requestShouldSucceed(String urlPath, expectation, {String root, headers}) {
264 scheduleRequest(urlPath, root: root).then((response) {
265 expect(response.statusCode, equals(200));
266 if (expectation != null) expect(response.body, expectation);
267 if (headers != null) expect(response.headers, headers);
268 });
269 }
270
271 /// Schedules an HTTP request to the running pub server with [urlPath] and
272 /// verifies that it responds with a 404.
273 ///
274 /// [root] indicates which server should be accessed, and defaults to "web".
275 void requestShould404(String urlPath, {String root}) {
276 scheduleRequest(urlPath, root: root).then((response) {
277 expect(response.statusCode, equals(404));
278 });
279 }
280
281 /// Schedules an HTTP request to the running pub server with [urlPath] and
282 /// verifies that it responds with a redirect to the given [redirectTarget].
283 ///
284 /// [redirectTarget] may be either a [Matcher] or a string to match an exact
285 /// URL. [root] indicates which server should be accessed, and defaults to
286 /// "web".
287 void requestShouldRedirect(String urlPath, redirectTarget, {String root}) {
288 schedule(() {
289 var request = new http.Request("GET",
290 Uri.parse(_getServerUrlSync(root, urlPath)));
291 request.followRedirects = false;
292 return request.send().then((response) {
293 expect(response.statusCode ~/ 100, equals(3));
294 expect(response.headers, containsPair('location', redirectTarget));
295 });
296 }, "request $urlPath");
297 }
298
299 /// Schedules an HTTP POST to the running pub server with [urlPath] and verifies
300 /// that it responds with a 405.
301 ///
302 /// [root] indicates which server should be accessed, and defaults to "web".
303 void postShould405(String urlPath, {String root}) {
304 schedule(() {
305 return http.post(_getServerUrlSync(root, urlPath)).then((response) {
306 expect(response.statusCode, equals(405));
307 });
308 }, "request $urlPath");
309 }
310
311 /// Schedules an HTTP request to the (theoretically) running pub server with
312 /// [urlPath] and verifies that it cannot be connected to.
313 ///
314 /// [root] indicates which server should be accessed, and defaults to "web".
315 void requestShouldNotConnect(String urlPath, {String root}) {
316 schedule(() {
317 return expect(http.get(_getServerUrlSync(root, urlPath)),
318 throwsA(new isInstanceOf<SocketException>()));
319 }, "request $urlPath");
320 }
321
322 /// Reads lines from pub serve's stdout until it prints the build success
323 /// message.
324 ///
325 /// The schedule will not proceed until the output is found. If not found, it
326 /// will eventually time out.
327 void waitForBuildSuccess() =>
328 _pubServer.stdout.expect(consumeThrough(contains("successfully")));
329
330 /// Schedules opening a web socket connection to the currently running pub
331 /// serve.
332 Future _ensureWebSocket() {
333 // Use the existing one if already connected.
334 if (_webSocket != null) return new Future.value();
335
336 // Server should already be running.
337 expect(_pubServer, isNotNull);
338 expect(_adminPort, isNotNull);
339
340 return WebSocket.connect("ws://localhost:$_adminPort").then((socket) {
341 _webSocket = socket;
342 // TODO(rnystrom): Works around #13913.
343 _webSocketBroadcastStream = _webSocket.map(JSON.decode).asBroadcastStream();
344 });
345 }
346
347 /// Schedules closing the web socket connection to the currently-running pub
348 /// serve.
349 void closeWebSocket() {
350 schedule(() {
351 return _ensureWebSocket().then((_) => _webSocket.close())
352 .then((_) => _webSocket = null);
353 }, "closing web socket");
354 }
355
356 /// Sends a JSON RPC 2.0 request to the running pub serve's web socket
357 /// connection.
358 ///
359 /// This calls a method named [method] with the given [params] (or no
360 /// parameters, if it's not passed). [params] may contain Futures, in which case
361 /// this will wait until they've completed before sending the request.
362 ///
363 /// This schedules the request, but doesn't block the schedule on the response.
364 /// It returns the response as a [Future].
365 Future<Map> webSocketRequest(String method, [Map params]) {
366 var completer = new Completer();
367 schedule(() {
368 return Future.wait([
369 _ensureWebSocket(),
370 awaitObject(params),
371 ]).then((results) {
372 var resolvedParams = results[1];
373 chainToCompleter(
374 currentSchedule.wrapFuture(_jsonRpcRequest(method, resolvedParams)),
375 completer);
376 });
377 }, "send $method with $params to web socket");
378 return completer.future;
379 }
380
381 /// Sends a JSON RPC 2.0 request to the running pub serve's web socket
382 /// connection, waits for a reply, then verifies the result.
383 ///
384 /// This calls a method named [method] with the given [params]. [params] may
385 /// contain Futures, in which case this will wait until they've completed before
386 /// sending the request.
387 ///
388 /// The result is validated using [result], which may be a [Matcher] or a [Map]
389 /// containing [Matcher]s and [Future]s. This will wait until any futures are
390 /// completed before sending the request.
391 ///
392 /// Returns a [Future] that completes to the call's result.
393 Future<Map> expectWebSocketResult(String method, Map params, result) {
394 return schedule(() {
395 return Future.wait([
396 webSocketRequest(method, params),
397 awaitObject(result)
398 ]).then((results) {
399 var response = results[0];
400 var resolvedResult = results[1];
401 expect(response["result"], resolvedResult);
402 return response["result"];
403 });
404 }, "send $method with $params to web socket and expect $result");
405 }
406
407 /// Sends a JSON RPC 2.0 request to the running pub serve's web socket
408 /// connection, waits for a reply, then verifies the error response.
409 ///
410 /// This calls a method named [method] with the given [params]. [params] may
411 /// contain Futures, in which case this will wait until they've completed before
412 /// sending the request.
413 ///
414 /// The error response is validated using [errorCode] and [errorMessage]. Both
415 /// of these must be provided. The error code is checked against [errorCode] and
416 /// the error message is checked against [errorMessage]. Either of these may be
417 /// matchers.
418 ///
419 /// If [data] is provided, it is a JSON value or matcher used to validate the
420 /// "data" value of the error response.
421 ///
422 /// Returns a [Future] that completes to the error's [data] field.
423 Future expectWebSocketError(String method, Map params, errorCode,
424 errorMessage, {data}) {
425 return schedule(() {
426 return webSocketRequest(method, params).then((response) {
427 expect(response["error"]["code"], errorCode);
428 expect(response["error"]["message"], errorMessage);
429
430 if (data != null) {
431 expect(response["error"]["data"], data);
432 }
433
434 return response["error"]["data"];
435 });
436 }, "send $method with $params to web socket and expect error $errorCode");
437 }
438
439 /// Validates that [root] was not bound to a port when pub serve started.
440 Future expectNotServed(String root) {
441 return schedule(() {
442 expect(_ports.containsKey(root), isFalse);
443 });
444 }
445
446 /// The next id to use for a JSON-RPC 2.0 request.
447 var _rpcId = 0;
448
449 /// Sends a JSON-RPC 2.0 request calling [method] with [params].
450 ///
451 /// Returns the response object.
452 Future<Map> _jsonRpcRequest(String method, [Map params]) {
453 var id = _rpcId++;
454 var message = {
455 "jsonrpc": "2.0",
456 "method": method,
457 "id": id
458 };
459 if (params != null) message["params"] = params;
460 _webSocket.add(JSON.encode(message));
461
462 return _webSocketBroadcastStream
463 .firstWhere((response) => response["id"] == id).then((value) {
464 currentSchedule.addDebugInfo(
465 "Web Socket request $method with params $params\n"
466 "Result: $value");
467
468 expect(value["id"], equals(id));
469 return value;
470 });
471 }
472
473 /// Returns a [Future] that completes to a URL string for the server serving
474 /// [path] from [root].
475 ///
476 /// If [root] is omitted, defaults to "web". If [path] is omitted, no path is
477 /// included. The Future will complete once the server is up and running and
478 /// the bound ports are known.
479 Future<String> getServerUrl([String root, String path]) =>
480 _portsCompleter.future.then((_) => _getServerUrlSync(root, path));
481
482 /// Records that [root] has been bound to [port].
483 ///
484 /// Used for testing the Web Socket API for binding new root directories to
485 /// ports after pub serve has been started.
486 registerServerPort(String root, int port) {
487 _ports[root] = port;
488 }
489
490 /// Returns a URL string for the server serving [path] from [root].
491 ///
492 /// If [root] is omitted, defaults to "web". If [path] is omitted, no path is
493 /// included. Unlike [getServerUrl], this should only be called after the ports
494 /// are known.
495 String _getServerUrlSync([String root, String path]) {
496 if (root == null) root = 'web';
497 expect(_ports, contains(root));
498 var url = "http://localhost:${_ports[root]}";
499 if (path != null) url = "$url/$path";
500 return url;
501 }
502
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698