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

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

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

Powered by Google App Engine
This is Rietveld 408576698