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

Side by Side Diff: sdk/lib/_internal/pub/test/test_pub.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 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] = p.join(
146 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(baseDir.load(path))
200 .then((stream) => new shelf.Response.ok(stream))
201 .catchError((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('get', new RegExp(
254 r'Got dependencies!|Changed \d+ dependenc(y|ies)!'));
255 static final upgrade = new RunCommand('upgrade', new RegExp(
256 r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
257 static final downgrade = new RunCommand('downgrade', new RegExp(
258 r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$'));
259
260 final String name;
261 final RegExp success;
262 RunCommand(this.name, this.success);
263 }
264
265 /// Runs the tests defined within [callback] using both pub get and pub upgrade.
266 ///
267 /// Many tests validate behavior that is the same between pub get and
268 /// upgrade have the same behavior. Instead of duplicating those tests, this
269 /// takes a callback that defines get/upgrade agnostic tests and runs them
270 /// with both commands.
271 void forBothPubGetAndUpgrade(void callback(RunCommand command)) {
272 group(RunCommand.get.name, () => callback(RunCommand.get));
273 group(RunCommand.upgrade.name, () => callback(RunCommand.upgrade));
274 }
275
276 /// Schedules an invocation of pub [command] and validates that it completes
277 /// in an expected way.
278 ///
279 /// By default, this validates that the command completes successfully and
280 /// understands the normal output of a successful pub command. If [warning] is
281 /// given, it expects the command to complete successfully *and* print
282 /// [warning] to stderr. If [error] is given, it expects the command to *only*
283 /// print [error] to stderr. [output], [error], and [warning] may be strings,
284 /// [RegExp]s, or [Matcher]s.
285 ///
286 /// If [exitCode] is given, expects the command to exit with that code.
287 // TODO(rnystrom): Clean up other tests to call this when possible.
288 void pubCommand(RunCommand command,
289 {Iterable<String> args, output, error, warning, int exitCode}) {
290 if (error != null && warning != null) {
291 throw new ArgumentError("Cannot pass both 'error' and 'warning'.");
292 }
293
294 var allArgs = [command.name];
295 if (args != null) allArgs.addAll(args);
296
297 if (output == null) output = command.success;
298
299 if (error != null && exitCode == null) exitCode = 1;
300
301 // No success output on an error.
302 if (error != null) output = null;
303 if (warning != null) error = warning;
304
305 schedulePub(args: allArgs, output: output, error: error, exitCode: exitCode);
306 }
307
308 void pubGet({Iterable<String> args, output, error, warning, int exitCode}) {
309 pubCommand(RunCommand.get, args: args, output: output, error: error,
310 warning: warning, exitCode: exitCode);
311 }
312
313 void pubUpgrade({Iterable<String> args, output, error, warning, int exitCode}) {
314 pubCommand(RunCommand.upgrade, args: args, output: output, error: error,
315 warning: warning, exitCode: exitCode);
316 }
317
318 void pubDowngrade({Iterable<String> args, output, error, warning,
319 int exitCode}) {
320 pubCommand(RunCommand.downgrade, args: args, output: output, error: error,
321 warning: warning, exitCode: exitCode);
322 }
323
324 /// Schedules starting the "pub [global] run" process and validates the
325 /// expected startup output.
326 ///
327 /// If [global] is `true`, this invokes "pub global run", otherwise it does
328 /// "pub run".
329 ///
330 /// Returns the `pub run` process.
331 ScheduledProcess pubRun({bool global: false, Iterable<String> args}) {
332 var pubArgs = global ? ["global", "run"] : ["run"];
333 pubArgs.addAll(args);
334 var pub = startPub(args: pubArgs);
335
336 // Loading sources and transformers isn't normally printed, but the pub test
337 // infrastructure runs pub in verbose mode, which enables this.
338 pub.stdout.expect(consumeWhile(startsWith("Loading")));
339
340 return pub;
341 }
342
343 /// Defines an integration test.
344 ///
345 /// The [body] should schedule a series of operations which will be run
346 /// asynchronously.
347 void integration(String description, void body()) =>
348 _integration(description, body, test);
349
350 /// Like [integration], but causes only this test to run.
351 void solo_integration(String description, void body()) =>
352 _integration(description, body, solo_test);
353
354 void _integration(String description, void body(), [Function testFn]) {
355 testFn(description, () {
356 // TODO(nweiz): remove this when issue 15362 is fixed.
357 currentSchedule.timeout *= 2;
358
359 // The windows bots are very slow, so we increase the default timeout.
360 if (Platform.operatingSystem == "windows") {
361 currentSchedule.timeout *= 2;
362 }
363
364 _sandboxDir = createSystemTempDir();
365 d.defaultRoot = sandboxDir;
366 currentSchedule.onComplete.schedule(() => deleteEntry(_sandboxDir),
367 'deleting the sandbox directory');
368
369 // Schedule the test.
370 body();
371 });
372 }
373
374 /// Get the path to the root "pub/test" directory containing the pub
375 /// tests.
376 String get testDirectory =>
377 p.absolute(p.dirname(libraryPath('test_pub')));
378
379 /// Schedules renaming (moving) the directory at [from] to [to], both of which
380 /// are assumed to be relative to [sandboxDir].
381 void scheduleRename(String from, String to) {
382 schedule(
383 () => renameDir(
384 p.join(sandboxDir, from),
385 p.join(sandboxDir, to)),
386 'renaming $from to $to');
387 }
388
389 /// Schedules creating a symlink at path [symlink] that points to [target],
390 /// both of which are assumed to be relative to [sandboxDir].
391 void scheduleSymlink(String target, String symlink) {
392 schedule(
393 () => createSymlink(
394 p.join(sandboxDir, target),
395 p.join(sandboxDir, symlink)),
396 'symlinking $target to $symlink');
397 }
398
399 /// Schedules a call to the Pub command-line utility.
400 ///
401 /// Runs Pub with [args] and validates that its results match [output] (or
402 /// [outputJson]), [error], and [exitCode].
403 ///
404 /// [output] and [error] can be [String]s, [RegExp]s, or [Matcher]s.
405 ///
406 /// If [outputJson] is given, validates that pub outputs stringified JSON
407 /// matching that object, which can be a literal JSON object or any other
408 /// [Matcher].
409 ///
410 /// If [environment] is given, any keys in it will override the environment
411 /// variables passed to the spawned process.
412 void schedulePub({List args, output, error, outputJson,
413 int exitCode: exit_codes.SUCCESS, Map<String, String> environment}) {
414 // Cannot pass both output and outputJson.
415 assert(output == null || outputJson == null);
416
417 var pub = startPub(args: args, environment: environment);
418 pub.shouldExit(exitCode);
419
420 var failures = [];
421 var stderr;
422
423 expect(Future.wait([
424 pub.stdoutStream().toList(),
425 pub.stderrStream().toList()
426 ]).then((results) {
427 var stdout = results[0].join("\n");
428 stderr = results[1].join("\n");
429
430 if (outputJson == null) {
431 _validateOutput(failures, 'stdout', output, stdout);
432 return null;
433 }
434
435 // Allow the expected JSON to contain futures.
436 return awaitObject(outputJson).then((resolved) {
437 _validateOutputJson(failures, 'stdout', resolved, stdout);
438 });
439 }).then((_) {
440 _validateOutput(failures, 'stderr', error, stderr);
441
442 if (!failures.isEmpty) throw new TestFailure(failures.join('\n'));
443 }), completes);
444 }
445
446 /// Like [startPub], but runs `pub lish` in particular with [server] used both
447 /// as the OAuth2 server (with "/token" as the token endpoint) and as the
448 /// package server.
449 ///
450 /// Any futures in [args] will be resolved before the process is started.
451 ScheduledProcess startPublish(ScheduledServer server, {List args}) {
452 var tokenEndpoint = server.url.then((url) =>
453 url.resolve('/token').toString());
454 if (args == null) args = [];
455 args = flatten(['lish', '--server', tokenEndpoint, args]);
456 return startPub(args: args, tokenEndpoint: tokenEndpoint);
457 }
458
459 /// Handles the beginning confirmation process for uploading a packages.
460 ///
461 /// Ensures that the right output is shown and then enters "y" to confirm the
462 /// upload.
463 void confirmPublish(ScheduledProcess pub) {
464 // TODO(rnystrom): This is overly specific and inflexible regarding different
465 // test packages. Should validate this a little more loosely.
466 pub.stdout.expect(startsWith('Publishing test_pkg 1.0.0 to '));
467 pub.stdout.expect(consumeThrough(
468 "Looks great! Are you ready to upload your package (y/n)?"));
469 pub.writeLine("y");
470 }
471
472 /// Gets the absolute path to [relPath], which is a relative path in the test
473 /// sandbox.
474 String _pathInSandbox(String relPath) {
475 return p.join(p.absolute(sandboxDir), relPath);
476 }
477
478 /// Gets the environment variables used to run pub in a test context.
479 Future<Map> getPubTestEnvironment([String tokenEndpoint]) async {
480 var environment = {};
481 environment['_PUB_TESTING'] = 'true';
482 environment['PUB_CACHE'] = _pathInSandbox(cachePath);
483
484 // Ensure a known SDK version is set for the tests that rely on that.
485 environment['_PUB_TEST_SDK_VERSION'] = "0.1.2+3";
486
487 if (tokenEndpoint != null) {
488 environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint.toString();
489 }
490
491 if (_hasServer) {
492 return port.then((p) {
493 environment['PUB_HOSTED_URL'] = "http://localhost:$p";
494 return environment;
495 });
496 }
497
498 return environment;
499 }
500
501 /// Starts a Pub process and returns a [ScheduledProcess] that supports
502 /// interaction with that process.
503 ///
504 /// Any futures in [args] will be resolved before the process is started.
505 ///
506 /// If [environment] is given, any keys in it will override the environment
507 /// variables passed to the spawned process.
508 ScheduledProcess startPub({List args, Future<String> tokenEndpoint,
509 Map<String, String> environment}) {
510 ensureDir(_pathInSandbox(appPath));
511
512 // Find a Dart executable we can use to spawn. Use the same one that was
513 // used to run this script itself.
514 var dartBin = Platform.executable;
515
516 // If the executable looks like a path, get its full path. That way we
517 // can still find it when we spawn it with a different working directory.
518 if (dartBin.contains(Platform.pathSeparator)) {
519 dartBin = p.absolute(dartBin);
520 }
521
522 // Always run pub from a snapshot. Since we require the SDK to be built, the
523 // snapshot should be there. Note that this *does* mean that the snapshot has
524 // to be manually updated when changing code before running the tests.
525 // Otherwise, you will test against stale data.
526 //
527 // Using the snapshot makes running the tests much faster, which is why we
528 // make this trade-off.
529 var pubPath = p.join(p.dirname(dartBin), 'snapshots/pub.dart.snapshot');
530 var dartArgs = [pubPath, '--verbose'];
531 dartArgs.addAll(args);
532
533 if (tokenEndpoint == null) tokenEndpoint = new Future.value();
534 var environmentFuture = tokenEndpoint
535 .then((tokenEndpoint) => getPubTestEnvironment(tokenEndpoint))
536 .then((pubEnvironment) {
537 if (environment != null) pubEnvironment.addAll(environment);
538 return pubEnvironment;
539 });
540
541 return new PubProcess.start(dartBin, dartArgs, environment: environmentFuture,
542 workingDirectory: _pathInSandbox(appPath),
543 description: args.isEmpty ? 'pub' : 'pub ${args.first}');
544 }
545
546 /// A subclass of [ScheduledProcess] that parses pub's verbose logging output
547 /// and makes [stdout] and [stderr] work as though pub weren't running in
548 /// verbose mode.
549 class PubProcess extends ScheduledProcess {
550 Stream<Pair<log.Level, String>> _log;
551 Stream<String> _stdout;
552 Stream<String> _stderr;
553
554 PubProcess.start(executable, arguments,
555 {workingDirectory, environment, String description,
556 Encoding encoding: UTF8})
557 : super.start(executable, arguments,
558 workingDirectory: workingDirectory,
559 environment: environment,
560 description: description,
561 encoding: encoding);
562
563 Stream<Pair<log.Level, String>> _logStream() {
564 if (_log == null) {
565 _log = mergeStreams(
566 _outputToLog(super.stdoutStream(), log.Level.MESSAGE),
567 _outputToLog(super.stderrStream(), log.Level.ERROR));
568 }
569
570 var pair = tee(_log);
571 _log = pair.first;
572 return pair.last;
573 }
574
575 final _logLineRegExp = new RegExp(r"^([A-Z ]{4})[:|] (.*)$");
576 final _logLevels = [
577 log.Level.ERROR, log.Level.WARNING, log.Level.MESSAGE, log.Level.IO,
578 log.Level.SOLVER, log.Level.FINE
579 ].fold(<String, log.Level>{}, (levels, level) {
580 levels[level.name] = level;
581 return levels;
582 });
583
584 Stream<Pair<log.Level, String>> _outputToLog(Stream<String> stream,
585 log.Level defaultLevel) {
586 var lastLevel;
587 return stream.map((line) {
588 var match = _logLineRegExp.firstMatch(line);
589 if (match == null) return new Pair<log.Level, String>(defaultLevel, line);
590
591 var level = _logLevels[match[1]];
592 if (level == null) level = lastLevel;
593 lastLevel = level;
594 return new Pair<log.Level, String>(level, match[2]);
595 });
596 }
597
598 Stream<String> stdoutStream() {
599 if (_stdout == null) {
600 _stdout = _logStream().expand((entry) {
601 if (entry.first != log.Level.MESSAGE) return [];
602 return [entry.last];
603 });
604 }
605
606 var pair = tee(_stdout);
607 _stdout = pair.first;
608 return pair.last;
609 }
610
611 Stream<String> stderrStream() {
612 if (_stderr == null) {
613 _stderr = _logStream().expand((entry) {
614 if (entry.first != log.Level.ERROR &&
615 entry.first != log.Level.WARNING) {
616 return [];
617 }
618 return [entry.last];
619 });
620 }
621
622 var pair = tee(_stderr);
623 _stderr = pair.first;
624 return pair.last;
625 }
626 }
627
628 /// The path to the `packages` directory from which pub loads its dependencies.
629 String get _packageRoot => p.absolute(Platform.packageRoot);
630
631 /// Fails the current test if Git is not installed.
632 ///
633 /// We require machines running these tests to have git installed. This
634 /// validation gives an easier-to-understand error when that requirement isn't
635 /// met than just failing in the middle of a test when pub invokes git.
636 ///
637 /// This also increases the [Schedule] timeout to 30 seconds on Windows,
638 /// where Git runs really slowly.
639 void ensureGit() {
640 if (Platform.operatingSystem == "windows") {
641 currentSchedule.timeout = new Duration(seconds: 30);
642 }
643
644 if (!gitlib.isInstalled) {
645 throw new Exception("Git must be installed to run this test.");
646 }
647 }
648
649 /// Schedules activating a global package [package] without running
650 /// "pub global activate".
651 ///
652 /// This is useful because global packages must be hosted, but the test hosted
653 /// server doesn't serve barback. The other parameters here follow
654 /// [createLockFile].
655 void makeGlobalPackage(String package, String version,
656 Iterable<d.Descriptor> contents, {Iterable<String> pkg,
657 Map<String, String> hosted}) {
658 // Start the server so we know what port to use in the cache directory name.
659 serveNoPackages();
660
661 // Create the package in the hosted cache.
662 d.hostedCache([
663 d.dir("$package-$version", contents)
664 ]).create();
665
666 var lockFile = _createLockFile(pkg: pkg, hosted: hosted);
667
668 // Add the root package to the lockfile.
669 var id = new PackageId(package, "hosted", new Version.parse(version),
670 package);
671 lockFile.packages[package] = id;
672
673 // Write the lockfile to the global cache.
674 var sources = new SourceRegistry();
675 sources.register(new HostedSource());
676 sources.register(new PathSource());
677
678 d.dir(cachePath, [
679 d.dir("global_packages", [
680 d.file("$package.lock", lockFile.serialize(null, sources))
681 ])
682 ]).create();
683 }
684
685 /// Creates a lock file for [package] without running `pub get`.
686 ///
687 /// [sandbox] is a list of path dependencies to be found in the sandbox
688 /// directory. [pkg] is a list of packages in the Dart repo's "pkg" directory;
689 /// each package listed here and all its dependencies will be linked to the
690 /// version in the Dart repo.
691 ///
692 /// [hosted] is a list of package names to version strings for dependencies on
693 /// hosted packages.
694 void createLockFile(String package, {Iterable<String> sandbox,
695 Iterable<String> pkg, Map<String, String> hosted}) {
696 var lockFile = _createLockFile(sandbox: sandbox, pkg: pkg, hosted: hosted);
697
698 var sources = new SourceRegistry();
699 sources.register(new HostedSource());
700 sources.register(new PathSource());
701
702 d.file(p.join(package, 'pubspec.lock'),
703 lockFile.serialize(null, sources)).create();
704 }
705
706 /// Creates a lock file for [package] without running `pub get`.
707 ///
708 /// [sandbox] is a list of path dependencies to be found in the sandbox
709 /// directory. [pkg] is a list of packages in the Dart repo's "pkg" directory;
710 /// each package listed here and all its dependencies will be linked to the
711 /// version in the Dart repo.
712 ///
713 /// [hosted] is a list of package names to version strings for dependencies on
714 /// hosted packages.
715 LockFile _createLockFile({Iterable<String> sandbox,
716 Iterable<String> pkg, Map<String, String> hosted}) {
717 var dependencies = {};
718
719 if (sandbox != null) {
720 for (var package in sandbox) {
721 dependencies[package] = '../$package';
722 }
723 }
724
725 if (pkg != null) {
726 _addPackage(String package) {
727 if (dependencies.containsKey(package)) return;
728
729 var path;
730 if (package == 'barback' && _packageOverrides == null) {
731 throw new StateError("createLockFile() can only create a lock file "
732 "with a barback dependency within a withBarbackVersions() "
733 "block.");
734 }
735
736 if (_packageOverrides.containsKey(package)) {
737 path = _packageOverrides[package];
738 } else {
739 path = packagePath(package);
740 }
741
742 dependencies[package] = path;
743 var pubspec = loadYaml(
744 readTextFile(p.join(path, 'pubspec.yaml')));
745 var packageDeps = pubspec['dependencies'];
746 if (packageDeps == null) return;
747 packageDeps.keys.forEach(_addPackage);
748 }
749
750 pkg.forEach(_addPackage);
751 }
752
753 var lockFile = new LockFile.empty();
754 dependencies.forEach((name, dependencyPath) {
755 var id = new PackageId(name, 'path', new Version(0, 0, 0), {
756 'path': dependencyPath,
757 'relative': p.isRelative(dependencyPath)
758 });
759 lockFile.packages[name] = id;
760 });
761
762 if (hosted != null) {
763 hosted.forEach((name, version) {
764 var id = new PackageId(name, 'hosted', new Version.parse(version), name);
765 lockFile.packages[name] = id;
766 });
767 }
768
769 return lockFile;
770 }
771
772 /// Returns the path to [package] within the repo.
773 String packagePath(String package) =>
774 dirExists(p.join(repoRoot, 'pkg', package)) ?
775 p.join(repoRoot, 'pkg', package) :
776 p.join(repoRoot, 'third_party', 'pkg', package);
777
778 /// Uses [client] as the mock HTTP client for this test.
779 ///
780 /// Note that this will only affect HTTP requests made via http.dart in the
781 /// parent process.
782 void useMockClient(MockClient client) {
783 var oldInnerClient = innerHttpClient;
784 innerHttpClient = client;
785 currentSchedule.onComplete.schedule(() {
786 innerHttpClient = oldInnerClient;
787 }, 'de-activating the mock client');
788 }
789
790 /// Describes a map representing a library package with the given [name],
791 /// [version], and [dependencies].
792 Map packageMap(String name, String version, [Map dependencies]) {
793 var package = {
794 "name": name,
795 "version": version,
796 "author": "Natalie Weizenbaum <nweiz@google.com>",
797 "homepage": "http://pub.dartlang.org",
798 "description": "A package, I guess."
799 };
800
801 if (dependencies != null) package["dependencies"] = dependencies;
802
803 return package;
804 }
805
806 /// Resolves [target] relative to the path to pub's `test/asset` directory.
807 String testAssetPath(String target) =>
808 p.join(p.dirname(libraryPath('test_pub')), 'asset', target);
809
810 /// Returns a Map in the format used by the pub.dartlang.org API to represent a
811 /// package version.
812 ///
813 /// [pubspec] is the parsed pubspec of the package version. If [full] is true,
814 /// this returns the complete map, including metadata that's only included when
815 /// requesting the package version directly.
816 Map packageVersionApiMap(Map pubspec, {bool full: false}) {
817 var name = pubspec['name'];
818 var version = pubspec['version'];
819 var map = {
820 'pubspec': pubspec,
821 'version': version,
822 'url': '/api/packages/$name/versions/$version',
823 'archive_url': '/packages/$name/versions/$version.tar.gz',
824 'new_dartdoc_url': '/api/packages/$name/versions/$version'
825 '/new_dartdoc',
826 'package_url': '/api/packages/$name'
827 };
828
829 if (full) {
830 map.addAll({
831 'downloads': 0,
832 'created': '2012-09-25T18:38:28.685260',
833 'libraries': ['$name.dart'],
834 'uploader': ['nweiz@google.com']
835 });
836 }
837
838 return map;
839 }
840
841 /// Returns the name of the shell script for a binstub named [name].
842 ///
843 /// Adds a ".bat" extension on Windows.
844 String binStubName(String name) => Platform.isWindows ? '$name.bat' : name;
845
846 /// Compares the [actual] output from running pub with [expected].
847 ///
848 /// If [expected] is a [String], ignores leading and trailing whitespace
849 /// differences and tries to report the offending difference in a nice way.
850 ///
851 /// If it's a [RegExp] or [Matcher], just reports whether the output matches.
852 void _validateOutput(List<String> failures, String pipe, expected,
853 String actual) {
854 if (expected == null) return;
855
856 if (expected is String) {
857 _validateOutputString(failures, pipe, expected, actual);
858 } else {
859 if (expected is RegExp) expected = matches(expected);
860 expect(actual, expected);
861 }
862 }
863
864 void _validateOutputString(List<String> failures, String pipe,
865 String expected, String actual) {
866 var actualLines = actual.split("\n");
867 var expectedLines = expected.split("\n");
868
869 // Strip off the last line. This lets us have expected multiline strings
870 // where the closing ''' is on its own line. It also fixes '' expected output
871 // to expect zero lines of output, not a single empty line.
872 if (expectedLines.last.trim() == '') {
873 expectedLines.removeLast();
874 }
875
876 var results = [];
877 var failed = false;
878
879 // Compare them line by line to see which ones match.
880 var length = max(expectedLines.length, actualLines.length);
881 for (var i = 0; i < length; i++) {
882 if (i >= actualLines.length) {
883 // Missing output.
884 failed = true;
885 results.add('? ${expectedLines[i]}');
886 } else if (i >= expectedLines.length) {
887 // Unexpected extra output.
888 failed = true;
889 results.add('X ${actualLines[i]}');
890 } else {
891 var expectedLine = expectedLines[i].trim();
892 var actualLine = actualLines[i].trim();
893
894 if (expectedLine != actualLine) {
895 // Mismatched lines.
896 failed = true;
897 results.add('X ${actualLines[i]}');
898 } else {
899 // Output is OK, but include it in case other lines are wrong.
900 results.add('| ${actualLines[i]}');
901 }
902 }
903 }
904
905 // If any lines mismatched, show the expected and actual.
906 if (failed) {
907 failures.add('Expected $pipe:');
908 failures.addAll(expectedLines.map((line) => '| $line'));
909 failures.add('Got:');
910 failures.addAll(results);
911 }
912 }
913
914 /// Validates that [actualText] is a string of JSON that matches [expected],
915 /// which may be a literal JSON object, or any other [Matcher].
916 void _validateOutputJson(List<String> failures, String pipe,
917 expected, String actualText) {
918 var actual;
919 try {
920 actual = JSON.decode(actualText);
921 } on FormatException catch(error) {
922 failures.add('Expected $pipe JSON:');
923 failures.add(expected);
924 failures.add('Got invalid JSON:');
925 failures.add(actualText);
926 }
927
928 // Match against the expectation.
929 expect(actual, expected);
930 }
931
932 /// A function that creates a [Validator] subclass.
933 typedef Validator ValidatorCreator(Entrypoint entrypoint);
934
935 /// Schedules a single [Validator] to run on the [appPath].
936 ///
937 /// Returns a scheduled Future that contains the errors and warnings produced
938 /// by that validator.
939 Future<Pair<List<String>, List<String>>> schedulePackageValidation(
940 ValidatorCreator fn) {
941 return schedule(() {
942 var cache = new SystemCache.withSources(
943 rootDir: p.join(sandboxDir, cachePath));
944
945 return new Future.sync(() {
946 var validator = fn(new Entrypoint(p.join(sandboxDir, appPath), cache));
947 return validator.validate().then((_) {
948 return new Pair(validator.errors, validator.warnings);
949 });
950 });
951 }, "validating package");
952 }
953
954 /// A matcher that matches a Pair.
955 Matcher pairOf(Matcher firstMatcher, Matcher lastMatcher) =>
956 new _PairMatcher(firstMatcher, lastMatcher);
957
958 class _PairMatcher extends Matcher {
959 final Matcher _firstMatcher;
960 final Matcher _lastMatcher;
961
962 _PairMatcher(this._firstMatcher, this._lastMatcher);
963
964 bool matches(item, Map matchState) {
965 if (item is! Pair) return false;
966 return _firstMatcher.matches(item.first, matchState) &&
967 _lastMatcher.matches(item.last, matchState);
968 }
969
970 Description describe(Description description) {
971 return description.addAll("(", ", ", ")", [_firstMatcher, _lastMatcher]);
972 }
973 }
974
975 /// A [StreamMatcher] that matches multiple lines of output.
976 StreamMatcher emitsLines(String output) => inOrder(output.split("\n"));
OLDNEW
« no previous file with comments | « sdk/lib/_internal/pub/test/snapshot/upgrades_snapshot_test.dart ('k') | sdk/lib/_internal/pub/test/transcript_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698