OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, 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. Unlike typical unit tests, most pub | 5 /// Test infrastructure for testing pub. Unlike typical unit tests, most pub |
6 /// tests are integration tests that stage some stuff on the file system, run | 6 /// tests are integration tests that stage some stuff on the file system, run |
7 /// pub, and then validate the results. This library provides an API to build | 7 /// pub, and then validate the results. This library provides an API to build |
8 /// tests like that. | 8 /// tests like that. |
9 library test_pub; | 9 library test_pub; |
10 | 10 |
(...skipping 13 matching lines...) Expand all Loading... |
24 import '../../lib/file_system.dart' as fs; | 24 import '../../lib/file_system.dart' as fs; |
25 import '../../pub/entrypoint.dart'; | 25 import '../../pub/entrypoint.dart'; |
26 // TODO(rnystrom): Using "gitlib" as the prefix here is ugly, but "git" collides | 26 // TODO(rnystrom): Using "gitlib" as the prefix here is ugly, but "git" collides |
27 // with the git descriptor method. Maybe we should try to clean up the top level | 27 // with the git descriptor method. Maybe we should try to clean up the top level |
28 // scope a bit? | 28 // scope a bit? |
29 import '../../pub/git.dart' as gitlib; | 29 import '../../pub/git.dart' as gitlib; |
30 import '../../pub/git_source.dart'; | 30 import '../../pub/git_source.dart'; |
31 import '../../pub/hosted_source.dart'; | 31 import '../../pub/hosted_source.dart'; |
32 import '../../pub/http.dart'; | 32 import '../../pub/http.dart'; |
33 import '../../pub/io.dart'; | 33 import '../../pub/io.dart'; |
| 34 import '../../pub/path_source.dart'; |
34 import '../../pub/sdk_source.dart'; | 35 import '../../pub/sdk_source.dart'; |
35 import '../../pub/system_cache.dart'; | 36 import '../../pub/system_cache.dart'; |
36 import '../../pub/utils.dart'; | 37 import '../../pub/utils.dart'; |
37 import '../../pub/validator.dart'; | 38 import '../../pub/validator.dart'; |
38 import 'command_line_config.dart'; | 39 import 'command_line_config.dart'; |
39 | 40 |
40 /// This should be called at the top of a test file to set up an appropriate | 41 /// This should be called at the top of a test file to set up an appropriate |
41 /// test configuration for the machine running the tests. | 42 /// test configuration for the machine running the tests. |
42 initConfig() { | 43 initConfig() { |
43 // If we aren't running on the bots, use the human-friendly config. | 44 // If we aren't running on the bots, use the human-friendly config. |
(...skipping 353 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
397 var keys = dependency.keys.where((key) => key != "version"); | 398 var keys = dependency.keys.where((key) => key != "version"); |
398 var sourceName = only(keys); | 399 var sourceName = only(keys); |
399 var source; | 400 var source; |
400 switch (sourceName) { | 401 switch (sourceName) { |
401 case "git": | 402 case "git": |
402 source = new GitSource(); | 403 source = new GitSource(); |
403 break; | 404 break; |
404 case "hosted": | 405 case "hosted": |
405 source = new HostedSource(); | 406 source = new HostedSource(); |
406 break; | 407 break; |
| 408 case "path": |
| 409 source = new PathSource(); |
| 410 break; |
407 case "sdk": | 411 case "sdk": |
408 source = new SdkSource(); | 412 source = new SdkSource(); |
409 break; | 413 break; |
410 default: | 414 default: |
411 throw 'Unknown source "$sourceName"'; | 415 throw 'Unknown source "$sourceName"'; |
412 } | 416 } |
413 | 417 |
414 result[_packageName(sourceName, dependency[sourceName])] = dependency; | 418 result[_packageName(sourceName, dependency[sourceName])] = dependency; |
415 } | 419 } |
416 return result; | 420 return result; |
417 }); | 421 }); |
418 } | 422 } |
419 | 423 |
420 /// Return the name for the package described by [description] and from | 424 /// Return the name for the package described by [description] and from |
421 /// [sourceName]. | 425 /// [sourceName]. |
422 String _packageName(String sourceName, description) { | 426 String _packageName(String sourceName, description) { |
423 switch (sourceName) { | 427 switch (sourceName) { |
424 case "git": | 428 case "git": |
425 var url = description is String ? description : description['url']; | 429 var url = description is String ? description : description['url']; |
426 return basename(url.replaceFirst(new RegExp(r"(\.git)?/?$"), "")); | 430 return basename(url.replaceFirst(new RegExp(r"(\.git)?/?$"), "")); |
427 case "hosted": | 431 case "hosted": |
428 if (description is String) return description; | 432 if (description is String) return description; |
429 return description['name']; | 433 return description['name']; |
| 434 case "path": |
| 435 return basename(description); |
430 case "sdk": | 436 case "sdk": |
431 return description; | 437 return description; |
432 default: | 438 default: |
433 return description; | 439 return description; |
434 } | 440 } |
435 } | 441 } |
436 | 442 |
| 443 /// The full path to the created sandbox directory for an integration test. |
| 444 String get sandboxDir => _sandboxDir.path; |
| 445 Directory _sandboxDir; |
| 446 |
437 /// The path of the package cache directory used for tests. Relative to the | 447 /// The path of the package cache directory used for tests. Relative to the |
438 /// sandbox directory. | 448 /// sandbox directory. |
439 final String cachePath = "cache"; | 449 final String cachePath = "cache"; |
440 | 450 |
441 /// The path of the mock SDK directory used for tests. Relative to the sandbox | 451 /// The path of the mock SDK directory used for tests. Relative to the sandbox |
442 /// directory. | 452 /// directory. |
443 final String sdkPath = "sdk"; | 453 final String sdkPath = "sdk"; |
444 | 454 |
445 /// The path of the mock app directory used for tests. Relative to the sandbox | 455 /// The path of the mock app directory used for tests. Relative to the sandbox |
446 /// directory. | 456 /// directory. |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
481 void solo_integration(String description, void body()) => | 491 void solo_integration(String description, void body()) => |
482 _integration(description, body, solo_test); | 492 _integration(description, body, solo_test); |
483 | 493 |
484 void _integration(String description, void body(), [Function testFn]) { | 494 void _integration(String description, void body(), [Function testFn]) { |
485 testFn(description, () { | 495 testFn(description, () { |
486 // Ensure the SDK version is always available. | 496 // Ensure the SDK version is always available. |
487 dir(sdkPath, [ | 497 dir(sdkPath, [ |
488 file('version', '0.1.2.3') | 498 file('version', '0.1.2.3') |
489 ]).scheduleCreate(); | 499 ]).scheduleCreate(); |
490 | 500 |
| 501 _sandboxDir = createTempDir(); |
| 502 |
491 // Schedule the test. | 503 // Schedule the test. |
492 body(); | 504 body(); |
493 | 505 |
494 // Run all of the scheduled tasks. If an error occurs, it will propagate | 506 // Run all of the scheduled tasks. If an error occurs, it will propagate |
495 // through the futures back up to here where we can hand it off to unittest. | 507 // through the futures back up to here where we can hand it off to unittest. |
496 var asyncDone = expectAsync0(() {}); | 508 var asyncDone = expectAsync0(() {}); |
497 var sandboxDir = createTempDir(); | 509 return timeout(_runScheduled(_scheduled), |
498 return timeout(_runScheduled(sandboxDir, _scheduled), | |
499 _TIMEOUT, 'waiting for a test to complete').catchError((e) { | 510 _TIMEOUT, 'waiting for a test to complete').catchError((e) { |
500 return _runScheduled(sandboxDir, _scheduledOnException).then((_) { | 511 return _runScheduled(_scheduledOnException).then((_) { |
501 // Rethrow the original error so it keeps propagating. | 512 // Rethrow the original error so it keeps propagating. |
502 throw e; | 513 throw e; |
503 }); | 514 }); |
504 }).whenComplete(() { | 515 }).whenComplete(() { |
505 // Clean up after ourselves. Do this first before reporting back to | 516 // Clean up after ourselves. Do this first before reporting back to |
506 // unittest because it will advance to the next test immediately. | 517 // unittest because it will advance to the next test immediately. |
507 return _runScheduled(sandboxDir, _scheduledCleanup).then((_) { | 518 return _runScheduled(_scheduledCleanup).then((_) { |
508 _scheduled = null; | 519 _scheduled = null; |
509 _scheduledCleanup = null; | 520 _scheduledCleanup = null; |
510 _scheduledOnException = null; | 521 _scheduledOnException = null; |
511 if (sandboxDir != null) return deleteDir(sandboxDir); | 522 if (_sandboxDir != null) { |
| 523 var dir = _sandboxDir; |
| 524 _sandboxDir = null; |
| 525 return deleteDir(dir); |
| 526 } |
512 }); | 527 }); |
513 }).then((_) { | 528 }).then((_) { |
514 // If we got here, the test completed successfully so tell unittest so. | 529 // If we got here, the test completed successfully so tell unittest so. |
515 asyncDone(); | 530 asyncDone(); |
516 }).catchError((e) { | 531 }).catchError((e) { |
517 // If we got here, an error occurred. We will register it with unittest | 532 // If we got here, an error occurred. We will register it with unittest |
518 // directly so that the error message isn't wrapped in any matcher stuff. | 533 // directly so that the error message isn't wrapped in any matcher stuff. |
519 // We do this call last because it will cause unittest to *synchronously* | 534 // We do this call last because it will cause unittest to *synchronously* |
520 // advance to the next test and run it. | 535 // advance to the next test and run it. |
521 registerException(e.error, e.stackTrace); | 536 registerException(e.error, e.stackTrace); |
522 }); | 537 }); |
523 }); | 538 }); |
524 } | 539 } |
525 | 540 |
526 /// Get the path to the root "util/test/pub" directory containing the pub | 541 /// Get the path to the root "util/test/pub" directory containing the pub |
527 /// tests. | 542 /// tests. |
528 String get testDirectory { | 543 String get testDirectory { |
529 var dir = new Options().script; | 544 var dir = new Options().script; |
530 while (basename(dir) != 'pub') dir = dirname(dir); | 545 while (basename(dir) != 'pub') dir = dirname(dir); |
531 | 546 |
532 return getFullPath(dir); | 547 return getFullPath(dir); |
533 } | 548 } |
534 | 549 |
| 550 /// Schedules renaming (moving) the directory at [from] to [to], both of which |
| 551 /// are assumed to be relative to [sandboxDir]. |
| 552 void scheduleRename(String from, String to) { |
| 553 _schedule((sandboxDir) { |
| 554 return renameDir(join(sandboxDir, from), join(sandboxDir, to)); |
| 555 }); |
| 556 } |
| 557 |
535 /// Schedules a call to the Pub command-line utility. Runs Pub with [args] and | 558 /// Schedules a call to the Pub command-line utility. Runs Pub with [args] and |
536 /// validates that its results match [output], [error], and [exitCode]. | 559 /// validates that its results match [output], [error], and [exitCode]. |
537 void schedulePub({List args, Pattern output, Pattern error, | 560 void schedulePub({List args, Pattern output, Pattern error, |
538 Future<Uri> tokenEndpoint, int exitCode: 0}) { | 561 Future<Uri> tokenEndpoint, int exitCode: 0}) { |
539 _schedule((sandboxDir) { | 562 _schedule((sandboxDir) { |
540 return _doPub(runProcess, sandboxDir, args, tokenEndpoint).then((result) { | 563 return _doPub(runProcess, sandboxDir, args, tokenEndpoint).then((result) { |
541 var failures = []; | 564 var failures = []; |
542 | 565 |
543 _validateOutput(failures, 'stdout', output, result.stdout); | 566 _validateOutput(failures, 'stdout', output, result.stdout); |
544 _validateOutput(failures, 'stderr', error, result.stderr); | 567 _validateOutput(failures, 'stderr', error, result.stderr); |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
595 expectLater(pub.nextLine(), equals('Publishing "test_pkg" 1.0.0:')); | 618 expectLater(pub.nextLine(), equals('Publishing "test_pkg" 1.0.0:')); |
596 expectLater(pub.nextLine(), equals("|-- LICENSE")); | 619 expectLater(pub.nextLine(), equals("|-- LICENSE")); |
597 expectLater(pub.nextLine(), equals("|-- lib")); | 620 expectLater(pub.nextLine(), equals("|-- lib")); |
598 expectLater(pub.nextLine(), equals("| '-- test_pkg.dart")); | 621 expectLater(pub.nextLine(), equals("| '-- test_pkg.dart")); |
599 expectLater(pub.nextLine(), equals("'-- pubspec.yaml")); | 622 expectLater(pub.nextLine(), equals("'-- pubspec.yaml")); |
600 expectLater(pub.nextLine(), equals("")); | 623 expectLater(pub.nextLine(), equals("")); |
601 | 624 |
602 pub.writeLine("y"); | 625 pub.writeLine("y"); |
603 } | 626 } |
604 | 627 |
605 | |
606 /// Calls [fn] with appropriately modified arguments to run a pub process. [fn] | 628 /// Calls [fn] with appropriately modified arguments to run a pub process. [fn] |
607 /// should have the same signature as [startProcess], except that the returned | 629 /// should have the same signature as [startProcess], except that the returned |
608 /// [Future] may have a type other than [Process]. | 630 /// [Future] may have a type other than [Process]. |
609 Future _doPub(Function fn, sandboxDir, List args, Future<Uri> tokenEndpoint) { | 631 Future _doPub(Function fn, sandboxDir, List args, Future<Uri> tokenEndpoint) { |
610 String pathInSandbox(path) => join(getFullPath(sandboxDir), path); | 632 String pathInSandbox(path) => join(getFullPath(sandboxDir), path); |
611 return defer(() { | 633 return defer(() { |
612 ensureDir(pathInSandbox(appPath)); | 634 ensureDir(pathInSandbox(appPath)); |
613 return Future.wait([ | 635 return Future.wait([ |
614 _awaitObject(args), | 636 _awaitObject(args), |
615 tokenEndpoint == null ? new Future.immediate(null) : tokenEndpoint | 637 tokenEndpoint == null ? new Future.immediate(null) : tokenEndpoint |
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
670 /// Note that this will only affect HTTP requests made via http.dart in the | 692 /// Note that this will only affect HTTP requests made via http.dart in the |
671 /// parent process. | 693 /// parent process. |
672 void useMockClient(MockClient client) { | 694 void useMockClient(MockClient client) { |
673 var oldInnerClient = httpClient.inner; | 695 var oldInnerClient = httpClient.inner; |
674 httpClient.inner = client; | 696 httpClient.inner = client; |
675 _scheduleCleanup((_) { | 697 _scheduleCleanup((_) { |
676 httpClient.inner = oldInnerClient; | 698 httpClient.inner = oldInnerClient; |
677 }); | 699 }); |
678 } | 700 } |
679 | 701 |
680 Future _runScheduled(Directory parentDir, List<_ScheduledEvent> scheduled) { | 702 Future _runScheduled(List<_ScheduledEvent> scheduled) { |
681 if (scheduled == null) return new Future.immediate(null); | 703 if (scheduled == null) return new Future.immediate(null); |
682 var iterator = scheduled.iterator; | 704 var iterator = scheduled.iterator; |
683 | 705 |
684 Future runNextEvent(_) { | 706 Future runNextEvent(_) { |
685 if (_abortScheduled || !iterator.moveNext()) { | 707 if (_abortScheduled || !iterator.moveNext()) { |
686 _abortScheduled = false; | 708 _abortScheduled = false; |
687 scheduled.clear(); | 709 scheduled.clear(); |
688 return new Future.immediate(null); | 710 return new Future.immediate(null); |
689 } | 711 } |
690 | 712 |
691 var future = iterator.current(parentDir); | 713 var future = iterator.current(_sandboxDir); |
692 if (future != null) { | 714 if (future != null) { |
693 return future.then(runNextEvent); | 715 return future.then(runNextEvent); |
694 } else { | 716 } else { |
695 return runNextEvent(null); | 717 return runNextEvent(null); |
696 } | 718 } |
697 } | 719 } |
698 | 720 |
699 return runNextEvent(null); | 721 return runNextEvent(null); |
700 } | 722 } |
701 | 723 |
(...skipping 458 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1160 throw "Can't load ${Strings.join(path, '/')} from within $name: $name " | 1182 throw "Can't load ${Strings.join(path, '/')} from within $name: $name " |
1161 "doesn't exist."; | 1183 "doesn't exist."; |
1162 } | 1184 } |
1163 } | 1185 } |
1164 } | 1186 } |
1165 | 1187 |
1166 /// A function that creates a [Validator] subclass. | 1188 /// A function that creates a [Validator] subclass. |
1167 typedef Validator ValidatorCreator(Entrypoint entrypoint); | 1189 typedef Validator ValidatorCreator(Entrypoint entrypoint); |
1168 | 1190 |
1169 /// Schedules a single [Validator] to run on the [appPath]. Returns a scheduled | 1191 /// Schedules a single [Validator] to run on the [appPath]. Returns a scheduled |
1170 /// Future that contains the erros and warnings produced by that validator. | 1192 /// Future that contains the errors and warnings produced by that validator. |
1171 Future<Pair<List<String>, List<String>>> schedulePackageValidation( | 1193 Future<Pair<List<String>, List<String>>> schedulePackageValidation( |
1172 ValidatorCreator fn) { | 1194 ValidatorCreator fn) { |
1173 return _scheduleValue((sandboxDir) { | 1195 return _scheduleValue((sandboxDir) { |
1174 var cache = new SystemCache.withSources(join(sandboxDir, cachePath)); | 1196 var cache = new SystemCache.withSources(join(sandboxDir, cachePath)); |
1175 | 1197 |
1176 return defer(() { | 1198 return defer(() { |
1177 var validator = fn(new Entrypoint(join(sandboxDir, appPath), cache)); | 1199 var validator = fn(new Entrypoint(join(sandboxDir, appPath), cache)); |
1178 return validator.validate().then((_) { | 1200 return validator.validate().then((_) { |
1179 return new Pair(validator.errors, validator.warnings); | 1201 return new Pair(validator.errors, validator.warnings); |
1180 }); | 1202 }); |
(...skipping 418 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1599 /// calling [completion] is unnecessary. | 1621 /// calling [completion] is unnecessary. |
1600 void expectLater(Future actual, matcher, {String reason, | 1622 void expectLater(Future actual, matcher, {String reason, |
1601 FailureHandler failureHandler, bool verbose: false}) { | 1623 FailureHandler failureHandler, bool verbose: false}) { |
1602 _schedule((_) { | 1624 _schedule((_) { |
1603 return actual.then((value) { | 1625 return actual.then((value) { |
1604 expect(value, matcher, reason: reason, failureHandler: failureHandler, | 1626 expect(value, matcher, reason: reason, failureHandler: failureHandler, |
1605 verbose: false); | 1627 verbose: false); |
1606 }); | 1628 }); |
1607 }); | 1629 }); |
1608 } | 1630 } |
OLD | NEW |