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

Side by Side Diff: sdk/lib/_internal/pub_generated/test/test_pub.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 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 /// Test infrastructure for testing pub.
6 ///
7 /// Unlike typical unit tests, most pub tests are integration tests that stage
8 /// some stuff on the file system, run pub, and then validate the results. This
9 /// library provides an API to build tests like that.
10 library test_pub;
11
12 import 'dart:async';
13 import 'dart:convert';
14 import 'dart:io';
15 import 'dart:math';
16
17 import 'package:http/testing.dart';
18 import 'package:path/path.dart' as p;
19 import 'package:pub_semver/pub_semver.dart';
20 import 'package:scheduled_test/scheduled_process.dart';
21 import 'package:scheduled_test/scheduled_server.dart';
22 import 'package:scheduled_test/scheduled_stream.dart';
23 import 'package:scheduled_test/scheduled_test.dart' hide fail;
24 import 'package:shelf/shelf.dart' as shelf;
25 import 'package:shelf/shelf_io.dart' as shelf_io;
26 import 'package:unittest/compact_vm_config.dart';
27 import 'package:yaml/yaml.dart';
28
29 import '../lib/src/entrypoint.dart';
30 import '../lib/src/exit_codes.dart' as exit_codes;
31 // TODO(rnystrom): Using "gitlib" as the prefix here is ugly, but "git" collides
32 // with the git descriptor method. Maybe we should try to clean up the top level
33 // scope a bit?
34 import '../lib/src/git.dart' as gitlib;
35 import '../lib/src/http.dart';
36 import '../lib/src/io.dart';
37 import '../lib/src/lock_file.dart';
38 import '../lib/src/log.dart' as log;
39 import '../lib/src/package.dart';
40 import '../lib/src/pubspec.dart';
41 import '../lib/src/source/hosted.dart';
42 import '../lib/src/source/path.dart';
43 import '../lib/src/source_registry.dart';
44 import '../lib/src/system_cache.dart';
45 import '../lib/src/utils.dart';
46 import '../lib/src/validator.dart';
47 import 'descriptor.dart' as d;
48 import 'serve_packages.dart';
49
50 export 'serve_packages.dart';
51
52 /// This should be called at the top of a test file to set up an appropriate
53 /// test configuration for the machine running the tests.
54 initConfig() {
55 useCompactVMConfiguration();
56 filterStacks = true;
57 unittestConfiguration.timeout = null;
58 }
59
60 /// The current [HttpServer] created using [serve].
61 var _server;
62
63 /// The list of paths that have been requested from the server since the last
64 /// call to [getRequestedPaths].
65 final _requestedPaths = <String>[];
66
67 /// The cached value for [_portCompleter].
68 Completer<int> _portCompleterCache;
69
70 /// A [Matcher] that matches JavaScript generated by dart2js with minification
71 /// enabled.
72 Matcher isMinifiedDart2JSOutput =
73 isNot(contains("// The code supports the following hooks"));
74
75 /// A [Matcher] that matches JavaScript generated by dart2js with minification
76 /// disabled.
77 Matcher isUnminifiedDart2JSOutput =
78 contains("// The code supports the following hooks");
79
80 /// A map from package names to paths from which those packages should be loaded
81 /// for [createLockFile].
82 ///
83 /// This allows older versions of dependencies than those that exist in the repo
84 /// to be used when testing pub.
85 Map<String, String> _packageOverrides;
86
87 /// A map from barback versions to the paths of directories in the repo
88 /// containing them.
89 ///
90 /// This includes the latest version of barback from pkg as well as all old
91 /// versions of barback in third_party.
92 final _barbackVersions = _findBarbackVersions();
93
94 /// Some older barback versions require older versions of barback's dependencies
95 /// than those that are in the repo.
96 ///
97 /// This is a map from barback version ranges to the dependencies for those
98 /// barback versions. Each dependency version listed here should be included in
99 /// third_party/pkg.
100 final _barbackDeps = {
101 new VersionConstraint.parse("<0.15.0"): {
102 "source_maps": "0.9.4"
103 }
104 };
105
106 /// Populates [_barbackVersions].
107 Map<Version, String> _findBarbackVersions() {
108 var versions = {};
109 var currentBarback = p.join(repoRoot, 'third_party', 'pkg', 'barback');
110 versions[new Pubspec.load(currentBarback, new SourceRegistry()).version] =
111 currentBarback;
112
113 for (var dir in listDir(p.join(repoRoot, 'third_party', 'pkg'))) {
114 var basename = p.basename(dir);
115 if (!basename.startsWith('barback-')) continue;
116 versions[new Version.parse(split1(basename, '-').last)] = dir;
117 }
118
119 return versions;
120 }
121
122 /// Runs the tests in [callback] against all versions of barback in the repo
123 /// that match [versionConstraint].
124 ///
125 /// This is used to test that pub doesn't accidentally break older versions of
126 /// barback that it's committed to supporting. Only versions `0.13.0` and later
127 /// will be tested.
128 void withBarbackVersions(String versionConstraint, void callback()) {
129 var constraint = new VersionConstraint.parse(versionConstraint);
130
131 var validVersions = _barbackVersions.keys.where(constraint.allows);
132 if (validVersions.isEmpty) {
133 throw new ArgumentError(
134 'No available barback version matches "$versionConstraint".');
135 }
136
137 for (var version in validVersions) {
138 group("with barback $version", () {
139 setUp(() {
140 _packageOverrides = {};
141 _packageOverrides['barback'] = _barbackVersions[version];
142 _barbackDeps.forEach((constraint, deps) {
143 if (!constraint.allows(version)) return;
144 deps.forEach((packageName, version) {
145 _packageOverrides[packageName] =
146 p.join(repoRoot, 'third_party', 'pkg', '$packageName-$version');
147 });
148 });
149
150 currentSchedule.onComplete.schedule(() {
151 _packageOverrides = null;
152 });
153 });
154
155 callback();
156 });
157 }
158 }
159
160 /// The completer for [port].
161 Completer<int> get _portCompleter {
162 if (_portCompleterCache != null) return _portCompleterCache;
163 _portCompleterCache = new Completer<int>();
164 currentSchedule.onComplete.schedule(() {
165 _portCompleterCache = null;
166 }, 'clearing the port completer');
167 return _portCompleterCache;
168 }
169
170 /// A future that will complete to the port used for the current server.
171 Future<int> get port => _portCompleter.future;
172
173 /// Gets the list of paths that have been requested from the server since the
174 /// last time this was called (or since the server was first spun up).
175 Future<List<String>> getRequestedPaths() {
176 return schedule(() {
177 var paths = _requestedPaths.toList();
178 _requestedPaths.clear();
179 return paths;
180 }, "get previous network requests");
181 }
182
183 /// Creates an HTTP server to serve [contents] as static files.
184 ///
185 /// This server will exist only for the duration of the pub run. Subsequent
186 /// calls to [serve] replace the previous server.
187 void serve([List<d.Descriptor> contents]) {
188 var baseDir = d.dir("serve-dir", contents);
189
190 _hasServer = true;
191
192 schedule(() {
193 return _closeServer().then((_) {
194 return shelf_io.serve((request) {
195 currentSchedule.heartbeat();
196 var path = p.posix.fromUri(request.url.path.replaceFirst("/", ""));
197 _requestedPaths.add(path);
198
199 return validateStream(
200 baseDir.load(
201 path)).then((stream) => new shelf.Response.ok(stream)).catchErro r((error) {
202 return new shelf.Response.notFound('File "$path" not found.');
203 });
204 }, 'localhost', 0).then((server) {
205 _server = server;
206 _portCompleter.complete(_server.port);
207 currentSchedule.onComplete.schedule(_closeServer);
208 });
209 });
210 }, 'starting a server serving:\n${baseDir.describe()}');
211 }
212
213 /// Closes [_server].
214 ///
215 /// Returns a [Future] that completes after the [_server] is closed.
216 Future _closeServer() {
217 if (_server == null) return new Future.value();
218 var future = _server.close();
219 _server = null;
220 _hasServer = false;
221 _portCompleterCache = null;
222 return future;
223 }
224
225 /// `true` if the current test spins up an HTTP server.
226 bool _hasServer = false;
227
228 /// Converts [value] into a YAML string.
229 String yaml(value) => JSON.encode(value);
230
231 /// The full path to the created sandbox directory for an integration test.
232 String get sandboxDir => _sandboxDir;
233 String _sandboxDir;
234
235 /// The path of the package cache directory used for tests, relative to the
236 /// sandbox directory.
237 final String cachePath = "cache";
238
239 /// The path of the mock app directory used for tests, relative to the sandbox
240 /// directory.
241 final String appPath = "myapp";
242
243 /// The path of the packages directory in the mock app used for tests, relative
244 /// to the sandbox directory.
245 final String packagesPath = "$appPath/packages";
246
247 /// Set to true when the current batch of scheduled events should be aborted.
248 bool _abortScheduled = false;
249
250 /// Enum identifying a pub command that can be run with a well-defined success
251 /// output.
252 class RunCommand {
253 static final get = new RunCommand(
254 'get',
255 new RegExp(r'Got dependencies!|Changed \d+ dependenc(y|ies)!'));
256 static final upgrade = new RunCommand(
257 'upgrade',
258 new RegExp(r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$')) ;
259 static final downgrade = new RunCommand(
260 'downgrade',
261 new RegExp(r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$')) ;
262
263 final String name;
264 final RegExp success;
265 RunCommand(this.name, this.success);
266 }
267
268 /// Runs the tests defined within [callback] using both pub get and pub upgrade.
269 ///
270 /// Many tests validate behavior that is the same between pub get and
271 /// upgrade have the same behavior. Instead of duplicating those tests, this
272 /// takes a callback that defines get/upgrade agnostic tests and runs them
273 /// with both commands.
274 void forBothPubGetAndUpgrade(void callback(RunCommand command)) {
275 group(RunCommand.get.name, () => callback(RunCommand.get));
276 group(RunCommand.upgrade.name, () => callback(RunCommand.upgrade));
277 }
278
279 /// Schedules an invocation of pub [command] and validates that it completes
280 /// in an expected way.
281 ///
282 /// By default, this validates that the command completes successfully and
283 /// understands the normal output of a successful pub command. If [warning] is
284 /// given, it expects the command to complete successfully *and* print
285 /// [warning] to stderr. If [error] is given, it expects the command to *only*
286 /// print [error] to stderr. [output], [error], and [warning] may be strings,
287 /// [RegExp]s, or [Matcher]s.
288 ///
289 /// If [exitCode] is given, expects the command to exit with that code.
290 // TODO(rnystrom): Clean up other tests to call this when possible.
291 void pubCommand(RunCommand command, {Iterable<String> args, output, error,
292 warning, int exitCode}) {
293 if (error != null && warning != null) {
294 throw new ArgumentError("Cannot pass both 'error' and 'warning'.");
295 }
296
297 var allArgs = [command.name];
298 if (args != null) allArgs.addAll(args);
299
300 if (output == null) output = command.success;
301
302 if (error != null && exitCode == null) exitCode = 1;
303
304 // No success output on an error.
305 if (error != null) output = null;
306 if (warning != null) error = warning;
307
308 schedulePub(args: allArgs, output: output, error: error, exitCode: exitCode);
309 }
310
311 void pubGet({Iterable<String> args, output, error, warning, int exitCode}) {
312 pubCommand(
313 RunCommand.get,
314 args: args,
315 output: output,
316 error: error,
317 warning: warning,
318 exitCode: exitCode);
319 }
320
321 void pubUpgrade({Iterable<String> args, output, error, warning, int exitCode}) {
322 pubCommand(
323 RunCommand.upgrade,
324 args: args,
325 output: output,
326 error: error,
327 warning: warning,
328 exitCode: exitCode);
329 }
330
331 void pubDowngrade({Iterable<String> args, output, error, warning, int exitCode})
332 {
333 pubCommand(
334 RunCommand.downgrade,
335 args: args,
336 output: output,
337 error: error,
338 warning: warning,
339 exitCode: exitCode);
340 }
341
342 /// Schedules starting the "pub [global] run" process and validates the
343 /// expected startup output.
344 ///
345 /// If [global] is `true`, this invokes "pub global run", otherwise it does
346 /// "pub run".
347 ///
348 /// Returns the `pub run` process.
349 ScheduledProcess pubRun({bool global: false, Iterable<String> args}) {
350 var pubArgs = global ? ["global", "run"] : ["run"];
351 pubArgs.addAll(args);
352 var pub = startPub(args: pubArgs);
353
354 // Loading sources and transformers isn't normally printed, but the pub test
355 // infrastructure runs pub in verbose mode, which enables this.
356 pub.stdout.expect(consumeWhile(startsWith("Loading")));
357
358 return pub;
359 }
360
361 /// Defines an integration test.
362 ///
363 /// The [body] should schedule a series of operations which will be run
364 /// asynchronously.
365 void integration(String description, void body()) =>
366 _integration(description, body, test);
367
368 /// Like [integration], but causes only this test to run.
369 void solo_integration(String description, void body()) =>
370 _integration(description, body, solo_test);
371
372 void _integration(String description, void body(), [Function testFn]) {
373 testFn(description, () {
374 // TODO(nweiz): remove this when issue 15362 is fixed.
375 currentSchedule.timeout *= 2;
376
377 // The windows bots are very slow, so we increase the default timeout.
378 if (Platform.operatingSystem == "windows") {
379 currentSchedule.timeout *= 2;
380 }
381
382 _sandboxDir = createSystemTempDir();
383 d.defaultRoot = sandboxDir;
384 currentSchedule.onComplete.schedule(
385 () => deleteEntry(_sandboxDir),
386 'deleting the sandbox directory');
387
388 // Schedule the test.
389 body();
390 });
391 }
392
393 /// Get the path to the root "pub/test" directory containing the pub
394 /// tests.
395 String get testDirectory => p.absolute(p.dirname(libraryPath('test_pub')));
396
397 /// Schedules renaming (moving) the directory at [from] to [to], both of which
398 /// are assumed to be relative to [sandboxDir].
399 void scheduleRename(String from, String to) {
400 schedule(
401 () => renameDir(p.join(sandboxDir, from), p.join(sandboxDir, to)),
402 'renaming $from to $to');
403 }
404
405 /// Schedules creating a symlink at path [symlink] that points to [target],
406 /// both of which are assumed to be relative to [sandboxDir].
407 void scheduleSymlink(String target, String symlink) {
408 schedule(
409 () => createSymlink(p.join(sandboxDir, target), p.join(sandboxDir, symlink )),
410 'symlinking $target to $symlink');
411 }
412
413 /// Schedules a call to the Pub command-line utility.
414 ///
415 /// Runs Pub with [args] and validates that its results match [output] (or
416 /// [outputJson]), [error], and [exitCode].
417 ///
418 /// [output] and [error] can be [String]s, [RegExp]s, or [Matcher]s.
419 ///
420 /// If [outputJson] is given, validates that pub outputs stringified JSON
421 /// matching that object, which can be a literal JSON object or any other
422 /// [Matcher].
423 ///
424 /// If [environment] is given, any keys in it will override the environment
425 /// variables passed to the spawned process.
426 void schedulePub({List args, output, error, outputJson, int exitCode:
427 exit_codes.SUCCESS, Map<String, String> environment}) {
428 // Cannot pass both output and outputJson.
429 assert(output == null || outputJson == null);
430
431 var pub = startPub(args: args, environment: environment);
432 pub.shouldExit(exitCode);
433
434 var failures = [];
435 var stderr;
436
437 expect(
438 Future.wait(
439 [pub.stdoutStream().toList(), pub.stderrStream().toList()]).then((resu lts) {
440 var stdout = results[0].join("\n");
441 stderr = results[1].join("\n");
442
443 if (outputJson == null) {
444 _validateOutput(failures, 'stdout', output, stdout);
445 return null;
446 }
447
448 // Allow the expected JSON to contain futures.
449 return awaitObject(outputJson).then((resolved) {
450 _validateOutputJson(failures, 'stdout', resolved, stdout);
451 });
452 }).then((_) {
453 _validateOutput(failures, 'stderr', error, stderr);
454
455 if (!failures.isEmpty) throw new TestFailure(failures.join('\n'));
456 }), completes);
457 }
458
459 /// Like [startPub], but runs `pub lish` in particular with [server] used both
460 /// as the OAuth2 server (with "/token" as the token endpoint) and as the
461 /// package server.
462 ///
463 /// Any futures in [args] will be resolved before the process is started.
464 ScheduledProcess startPublish(ScheduledServer server, {List args}) {
465 var tokenEndpoint =
466 server.url.then((url) => url.resolve('/token').toString());
467 if (args == null) args = [];
468 args = flatten(['lish', '--server', tokenEndpoint, args]);
469 return startPub(args: args, tokenEndpoint: tokenEndpoint);
470 }
471
472 /// Handles the beginning confirmation process for uploading a packages.
473 ///
474 /// Ensures that the right output is shown and then enters "y" to confirm the
475 /// upload.
476 void confirmPublish(ScheduledProcess pub) {
477 // TODO(rnystrom): This is overly specific and inflexible regarding different
478 // test packages. Should validate this a little more loosely.
479 pub.stdout.expect(startsWith('Publishing test_pkg 1.0.0 to '));
480 pub.stdout.expect(
481 emitsLines(
482 "|-- LICENSE\n" "|-- lib\n" "| '-- test_pkg.dart\n" "'-- pubspec.yam l\n" "\n"
483 "Looks great! Are you ready to upload your package (y/n)?"));
484 pub.writeLine("y");
485 }
486
487 /// Gets the absolute path to [relPath], which is a relative path in the test
488 /// sandbox.
489 String _pathInSandbox(String relPath) {
490 return p.join(p.absolute(sandboxDir), relPath);
491 }
492
493 /// Gets the environment variables used to run pub in a test context.
494 Future<Map> getPubTestEnvironment([String tokenEndpoint]) {
495 final completer0 = new Completer();
496 scheduleMicrotask(() {
497 try {
498 var environment = {};
499 environment['_PUB_TESTING'] = 'true';
500 environment['PUB_CACHE'] = _pathInSandbox(cachePath);
501 environment['_PUB_TEST_SDK_VERSION'] = "0.1.2+3";
502 join0() {
503 join1() {
504 completer0.complete(environment);
505 }
506 if (_hasServer) {
507 completer0.complete(port.then(((p) {
508 environment['PUB_HOSTED_URL'] = "http://localhost:$p";
509 return environment;
510 })));
511 } else {
512 join1();
513 }
514 }
515 if (tokenEndpoint != null) {
516 environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint.toString();
517 join0();
518 } else {
519 join0();
520 }
521 } catch (e, s) {
522 completer0.completeError(e, s);
523 }
524 });
525 return completer0.future;
526 }
527
528 /// Starts a Pub process and returns a [ScheduledProcess] that supports
529 /// interaction with that process.
530 ///
531 /// Any futures in [args] will be resolved before the process is started.
532 ///
533 /// If [environment] is given, any keys in it will override the environment
534 /// variables passed to the spawned process.
535 ScheduledProcess startPub({List args, Future<String> tokenEndpoint, Map<String,
536 String> environment}) {
537 ensureDir(_pathInSandbox(appPath));
538
539 // Find a Dart executable we can use to spawn. Use the same one that was
540 // used to run this script itself.
541 var dartBin = Platform.executable;
542
543 // If the executable looks like a path, get its full path. That way we
544 // can still find it when we spawn it with a different working directory.
545 if (dartBin.contains(Platform.pathSeparator)) {
546 dartBin = p.absolute(dartBin);
547 }
548
549 // Always run pub from a snapshot. Since we require the SDK to be built, the
550 // snapshot should be there. Note that this *does* mean that the snapshot has
551 // to be manually updated when changing code before running the tests.
552 // Otherwise, you will test against stale data.
553 //
554 // Using the snapshot makes running the tests much faster, which is why we
555 // make this trade-off.
556 var pubPath = p.join(p.dirname(dartBin), 'snapshots/pub.dart.snapshot');
557 var dartArgs = [pubPath, '--verbose'];
558 dartArgs.addAll(args);
559
560 if (tokenEndpoint == null) tokenEndpoint = new Future.value();
561 var environmentFuture = tokenEndpoint.then(
562 (tokenEndpoint) => getPubTestEnvironment(tokenEndpoint)).then((pubEnvironm ent) {
563 if (environment != null) pubEnvironment.addAll(environment);
564 return pubEnvironment;
565 });
566
567 return new PubProcess.start(
568 dartBin,
569 dartArgs,
570 environment: environmentFuture,
571 workingDirectory: _pathInSandbox(appPath),
572 description: args.isEmpty ? 'pub' : 'pub ${args.first}');
573 }
574
575 /// A subclass of [ScheduledProcess] that parses pub's verbose logging output
576 /// and makes [stdout] and [stderr] work as though pub weren't running in
577 /// verbose mode.
578 class PubProcess extends ScheduledProcess {
579 Stream<Pair<log.Level, String>> _log;
580 Stream<String> _stdout;
581 Stream<String> _stderr;
582
583 PubProcess.start(executable, arguments, {workingDirectory, environment,
584 String description, Encoding encoding: UTF8})
585 : super.start(
586 executable,
587 arguments,
588 workingDirectory: workingDirectory,
589 environment: environment,
590 description: description,
591 encoding: encoding);
592
593 Stream<Pair<log.Level, String>> _logStream() {
594 if (_log == null) {
595 _log = mergeStreams(
596 _outputToLog(super.stdoutStream(), log.Level.MESSAGE),
597 _outputToLog(super.stderrStream(), log.Level.ERROR));
598 }
599
600 var pair = tee(_log);
601 _log = pair.first;
602 return pair.last;
603 }
604
605 final _logLineRegExp = new RegExp(r"^([A-Z ]{4})[:|] (.*)$");
606 final _logLevels = [
607 log.Level.ERROR,
608 log.Level.WARNING,
609 log.Level.MESSAGE,
610 log.Level.IO,
611 log.Level.SOLVER,
612 log.Level.FINE].fold(<String, log.Level>{}, (levels, level) {
613 levels[level.name] = level;
614 return levels;
615 });
616
617 Stream<Pair<log.Level, String>> _outputToLog(Stream<String> stream,
618 log.Level defaultLevel) {
619 var lastLevel;
620 return stream.map((line) {
621 var match = _logLineRegExp.firstMatch(line);
622 if (match == null) return new Pair<log.Level, String>(defaultLevel, line);
623
624 var level = _logLevels[match[1]];
625 if (level == null) level = lastLevel;
626 lastLevel = level;
627 return new Pair<log.Level, String>(level, match[2]);
628 });
629 }
630
631 Stream<String> stdoutStream() {
632 if (_stdout == null) {
633 _stdout = _logStream().expand((entry) {
634 if (entry.first != log.Level.MESSAGE) return [];
635 return [entry.last];
636 });
637 }
638
639 var pair = tee(_stdout);
640 _stdout = pair.first;
641 return pair.last;
642 }
643
644 Stream<String> stderrStream() {
645 if (_stderr == null) {
646 _stderr = _logStream().expand((entry) {
647 if (entry.first != log.Level.ERROR &&
648 entry.first != log.Level.WARNING) {
649 return [];
650 }
651 return [entry.last];
652 });
653 }
654
655 var pair = tee(_stderr);
656 _stderr = pair.first;
657 return pair.last;
658 }
659 }
660
661 /// The path to the `packages` directory from which pub loads its dependencies.
662 String get _packageRoot => p.absolute(Platform.packageRoot);
663
664 /// Fails the current test if Git is not installed.
665 ///
666 /// We require machines running these tests to have git installed. This
667 /// validation gives an easier-to-understand error when that requirement isn't
668 /// met than just failing in the middle of a test when pub invokes git.
669 ///
670 /// This also increases the [Schedule] timeout to 30 seconds on Windows,
671 /// where Git runs really slowly.
672 void ensureGit() {
673 if (Platform.operatingSystem == "windows") {
674 currentSchedule.timeout = new Duration(seconds: 30);
675 }
676
677 if (!gitlib.isInstalled) {
678 throw new Exception("Git must be installed to run this test.");
679 }
680 }
681
682 /// Schedules activating a global package [package] without running
683 /// "pub global activate".
684 ///
685 /// This is useful because global packages must be hosted, but the test hosted
686 /// server doesn't serve barback. The other parameters here follow
687 /// [createLockFile].
688 void makeGlobalPackage(String package, String version,
689 Iterable<d.Descriptor> contents, {Iterable<String> pkg, Map<String,
690 String> hosted}) {
691 // Start the server so we know what port to use in the cache directory name.
692 serveNoPackages();
693
694 // Create the package in the hosted cache.
695 d.hostedCache([d.dir("$package-$version", contents)]).create();
696
697 var lockFile = _createLockFile(pkg: pkg, hosted: hosted);
698
699 // Add the root package to the lockfile.
700 var id =
701 new PackageId(package, "hosted", new Version.parse(version), package);
702 lockFile.packages[package] = id;
703
704 // Write the lockfile to the global cache.
705 var sources = new SourceRegistry();
706 sources.register(new HostedSource());
707 sources.register(new PathSource());
708
709 d.dir(
710 cachePath,
711 [
712 d.dir(
713 "global_packages",
714 [d.file("$package.lock", lockFile.serialize(null, sources))])]).cr eate();
715 }
716
717 /// Creates a lock file for [package] without running `pub get`.
718 ///
719 /// [sandbox] is a list of path dependencies to be found in the sandbox
720 /// directory. [pkg] is a list of packages in the Dart repo's "pkg" directory;
721 /// each package listed here and all its dependencies will be linked to the
722 /// version in the Dart repo.
723 ///
724 /// [hosted] is a list of package names to version strings for dependencies on
725 /// hosted packages.
726 void createLockFile(String package, {Iterable<String> sandbox,
727 Iterable<String> pkg, Map<String, String> hosted}) {
728 var lockFile = _createLockFile(sandbox: sandbox, pkg: pkg, hosted: hosted);
729
730 var sources = new SourceRegistry();
731 sources.register(new HostedSource());
732 sources.register(new PathSource());
733
734 d.file(
735 p.join(package, 'pubspec.lock'),
736 lockFile.serialize(null, sources)).create();
737 }
738
739 /// Creates a lock file for [package] without running `pub get`.
740 ///
741 /// [sandbox] is a list of path dependencies to be found in the sandbox
742 /// directory. [pkg] is a list of packages in the Dart repo's "pkg" directory;
743 /// each package listed here and all its dependencies will be linked to the
744 /// version in the Dart repo.
745 ///
746 /// [hosted] is a list of package names to version strings for dependencies on
747 /// hosted packages.
748 LockFile _createLockFile({Iterable<String> sandbox, Iterable<String> pkg,
749 Map<String, String> hosted}) {
750 var dependencies = {};
751
752 if (sandbox != null) {
753 for (var package in sandbox) {
754 dependencies[package] = '../$package';
755 }
756 }
757
758 if (pkg != null) {
759 _addPackage(String package) {
760 if (dependencies.containsKey(package)) return;
761
762 var path;
763 if (package == 'barback' && _packageOverrides == null) {
764 throw new StateError(
765 "createLockFile() can only create a lock file "
766 "with a barback dependency within a withBarbackVersions() " "blo ck.");
767 }
768
769 if (_packageOverrides.containsKey(package)) {
770 path = _packageOverrides[package];
771 } else {
772 path = packagePath(package);
773 }
774
775 dependencies[package] = path;
776 var pubspec = loadYaml(readTextFile(p.join(path, 'pubspec.yaml')));
777 var packageDeps = pubspec['dependencies'];
778 if (packageDeps == null) return;
779 packageDeps.keys.forEach(_addPackage);
780 }
781
782 pkg.forEach(_addPackage);
783 }
784
785 var lockFile = new LockFile.empty();
786 dependencies.forEach((name, dependencyPath) {
787 var id = new PackageId(name, 'path', new Version(0, 0, 0), {
788 'path': dependencyPath,
789 'relative': p.isRelative(dependencyPath)
790 });
791 lockFile.packages[name] = id;
792 });
793
794 if (hosted != null) {
795 hosted.forEach((name, version) {
796 var id = new PackageId(name, 'hosted', new Version.parse(version), name);
797 lockFile.packages[name] = id;
798 });
799 }
800
801 return lockFile;
802 }
803
804 /// Returns the path to [package] within the repo.
805 String packagePath(String package) =>
806 dirExists(p.join(repoRoot, 'pkg', package)) ?
807 p.join(repoRoot, 'pkg', package) :
808 p.join(repoRoot, 'third_party', 'pkg', package);
809
810 /// Uses [client] as the mock HTTP client for this test.
811 ///
812 /// Note that this will only affect HTTP requests made via http.dart in the
813 /// parent process.
814 void useMockClient(MockClient client) {
815 var oldInnerClient = innerHttpClient;
816 innerHttpClient = client;
817 currentSchedule.onComplete.schedule(() {
818 innerHttpClient = oldInnerClient;
819 }, 'de-activating the mock client');
820 }
821
822 /// Describes a map representing a library package with the given [name],
823 /// [version], and [dependencies].
824 Map packageMap(String name, String version, [Map dependencies]) {
825 var package = {
826 "name": name,
827 "version": version,
828 "author": "Natalie Weizenbaum <nweiz@google.com>",
829 "homepage": "http://pub.dartlang.org",
830 "description": "A package, I guess."
831 };
832
833 if (dependencies != null) package["dependencies"] = dependencies;
834
835 return package;
836 }
837
838 /// Resolves [target] relative to the path to pub's `test/asset` directory.
839 String testAssetPath(String target) {
840 var libPath = libraryPath('test_pub');
841
842 // We are running from the generated directory, but non-dart assets are only
843 // in the canonical directory.
844 // TODO(rnystrom): Remove this when #104 is fixed.
845 libPath = libPath.replaceAll('pub_generated', 'pub');
846
847 return p.join(p.dirname(libPath), 'asset', target);
848 }
849
850 /// Returns a Map in the format used by the pub.dartlang.org API to represent a
851 /// package version.
852 ///
853 /// [pubspec] is the parsed pubspec of the package version. If [full] is true,
854 /// this returns the complete map, including metadata that's only included when
855 /// requesting the package version directly.
856 Map packageVersionApiMap(Map pubspec, {bool full: false}) {
857 var name = pubspec['name'];
858 var version = pubspec['version'];
859 var map = {
860 'pubspec': pubspec,
861 'version': version,
862 'url': '/api/packages/$name/versions/$version',
863 'archive_url': '/packages/$name/versions/$version.tar.gz',
864 'new_dartdoc_url': '/api/packages/$name/versions/$version' '/new_dartdoc',
865 'package_url': '/api/packages/$name'
866 };
867
868 if (full) {
869 map.addAll({
870 'downloads': 0,
871 'created': '2012-09-25T18:38:28.685260',
872 'libraries': ['$name.dart'],
873 'uploader': ['nweiz@google.com']
874 });
875 }
876
877 return map;
878 }
879
880 /// Returns the name of the shell script for a binstub named [name].
881 ///
882 /// Adds a ".bat" extension on Windows.
883 String binStubName(String name) => Platform.isWindows ? '$name.bat' : name;
884
885 /// Compares the [actual] output from running pub with [expected].
886 ///
887 /// If [expected] is a [String], ignores leading and trailing whitespace
888 /// differences and tries to report the offending difference in a nice way.
889 ///
890 /// If it's a [RegExp] or [Matcher], just reports whether the output matches.
891 void _validateOutput(List<String> failures, String pipe, expected,
892 String actual) {
893 if (expected == null) return;
894
895 if (expected is String) {
896 _validateOutputString(failures, pipe, expected, actual);
897 } else {
898 if (expected is RegExp) expected = matches(expected);
899 expect(actual, expected);
900 }
901 }
902
903 void _validateOutputString(List<String> failures, String pipe, String expected,
904 String actual) {
905 var actualLines = actual.split("\n");
906 var expectedLines = expected.split("\n");
907
908 // Strip off the last line. This lets us have expected multiline strings
909 // where the closing ''' is on its own line. It also fixes '' expected output
910 // to expect zero lines of output, not a single empty line.
911 if (expectedLines.last.trim() == '') {
912 expectedLines.removeLast();
913 }
914
915 var results = [];
916 var failed = false;
917
918 // Compare them line by line to see which ones match.
919 var length = max(expectedLines.length, actualLines.length);
920 for (var i = 0; i < length; i++) {
921 if (i >= actualLines.length) {
922 // Missing output.
923 failed = true;
924 results.add('? ${expectedLines[i]}');
925 } else if (i >= expectedLines.length) {
926 // Unexpected extra output.
927 failed = true;
928 results.add('X ${actualLines[i]}');
929 } else {
930 var expectedLine = expectedLines[i].trim();
931 var actualLine = actualLines[i].trim();
932
933 if (expectedLine != actualLine) {
934 // Mismatched lines.
935 failed = true;
936 results.add('X ${actualLines[i]}');
937 } else {
938 // Output is OK, but include it in case other lines are wrong.
939 results.add('| ${actualLines[i]}');
940 }
941 }
942 }
943
944 // If any lines mismatched, show the expected and actual.
945 if (failed) {
946 failures.add('Expected $pipe:');
947 failures.addAll(expectedLines.map((line) => '| $line'));
948 failures.add('Got:');
949 failures.addAll(results);
950 }
951 }
952
953 /// Validates that [actualText] is a string of JSON that matches [expected],
954 /// which may be a literal JSON object, or any other [Matcher].
955 void _validateOutputJson(List<String> failures, String pipe, expected,
956 String actualText) {
957 var actual;
958 try {
959 actual = JSON.decode(actualText);
960 } on FormatException catch (error) {
961 failures.add('Expected $pipe JSON:');
962 failures.add(expected);
963 failures.add('Got invalid JSON:');
964 failures.add(actualText);
965 }
966
967 // Match against the expectation.
968 expect(actual, expected);
969 }
970
971 /// A function that creates a [Validator] subclass.
972 typedef Validator ValidatorCreator(Entrypoint entrypoint);
973
974 /// Schedules a single [Validator] to run on the [appPath].
975 ///
976 /// Returns a scheduled Future that contains the errors and warnings produced
977 /// by that validator.
978 Future<Pair<List<String>, List<String>>>
979 schedulePackageValidation(ValidatorCreator fn) {
980 return schedule(() {
981 var cache =
982 new SystemCache.withSources(rootDir: p.join(sandboxDir, cachePath));
983
984 return new Future.sync(() {
985 var validator = fn(new Entrypoint(p.join(sandboxDir, appPath), cache));
986 return validator.validate().then((_) {
987 return new Pair(validator.errors, validator.warnings);
988 });
989 });
990 }, "validating package");
991 }
992
993 /// A matcher that matches a Pair.
994 Matcher pairOf(Matcher firstMatcher, Matcher lastMatcher) =>
995 new _PairMatcher(firstMatcher, lastMatcher);
996
997 class _PairMatcher extends Matcher {
998 final Matcher _firstMatcher;
999 final Matcher _lastMatcher;
1000
1001 _PairMatcher(this._firstMatcher, this._lastMatcher);
1002
1003 bool matches(item, Map matchState) {
1004 if (item is! Pair) return false;
1005 return _firstMatcher.matches(item.first, matchState) &&
1006 _lastMatcher.matches(item.last, matchState);
1007 }
1008
1009 Description describe(Description description) {
1010 return description.addAll("(", ", ", ")", [_firstMatcher, _lastMatcher]);
1011 }
1012 }
1013
1014 /// A [StreamMatcher] that matches multiple lines of output.
1015 StreamMatcher emitsLines(String output) => inOrder(output.split("\n"));
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698