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

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

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

Powered by Google App Engine
This is Rietveld 408576698