OLD | NEW |
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; |
11 | 11 |
12 import 'dart:async'; | 12 import 'dart:async'; |
13 import 'dart:convert'; | 13 import 'dart:convert'; |
14 import 'dart:io'; | 14 import 'dart:io'; |
15 import 'dart:math'; | 15 import 'dart:math'; |
16 | 16 |
| 17 import 'package:crypto/crypto.dart'; |
17 import 'package:http/testing.dart'; | 18 import 'package:http/testing.dart'; |
18 import 'package:path/path.dart' as p; | 19 import 'package:path/path.dart' as p; |
19 import 'package:pub_semver/pub_semver.dart'; | 20 import 'package:pub_semver/pub_semver.dart'; |
20 import 'package:scheduled_test/scheduled_process.dart'; | 21 import 'package:scheduled_test/scheduled_process.dart'; |
21 import 'package:scheduled_test/scheduled_server.dart'; | 22 import 'package:scheduled_test/scheduled_server.dart'; |
22 import 'package:scheduled_test/scheduled_stream.dart'; | 23 import 'package:scheduled_test/scheduled_stream.dart'; |
23 import 'package:scheduled_test/scheduled_test.dart' hide fail; | 24 import 'package:scheduled_test/scheduled_test.dart' hide fail; |
24 import 'package:shelf/shelf.dart' as shelf; | 25 import 'package:shelf/shelf.dart' as shelf; |
25 import 'package:shelf/shelf_io.dart' as shelf_io; | 26 import 'package:shelf/shelf_io.dart' as shelf_io; |
26 import 'package:unittest/compact_vm_config.dart'; | 27 import 'package:unittest/compact_vm_config.dart'; |
27 import 'package:yaml/yaml.dart'; | 28 import 'package:yaml/yaml.dart'; |
28 | 29 |
29 import '../lib/src/entrypoint.dart'; | 30 import '../lib/src/entrypoint.dart'; |
30 import '../lib/src/exit_codes.dart' as exit_codes; | 31 import '../lib/src/exit_codes.dart' as exit_codes; |
31 // TODO(rnystrom): Using "gitlib" as the prefix here is ugly, but "git" collides | 32 // 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 // with the git descriptor method. Maybe we should try to clean up the top level |
33 // scope a bit? | 34 // scope a bit? |
34 import '../lib/src/git.dart' as gitlib; | 35 import '../lib/src/git.dart' as gitlib; |
35 import '../lib/src/http.dart'; | 36 import '../lib/src/http.dart'; |
36 import '../lib/src/io.dart'; | 37 import '../lib/src/io.dart'; |
37 import '../lib/src/lock_file.dart'; | 38 import '../lib/src/lock_file.dart'; |
38 import '../lib/src/log.dart' as log; | 39 import '../lib/src/log.dart' as log; |
39 import '../lib/src/package.dart'; | 40 import '../lib/src/package.dart'; |
40 import '../lib/src/pubspec.dart'; | 41 import '../lib/src/pubspec.dart'; |
| 42 import '../lib/src/sdk.dart' as sdk; |
41 import '../lib/src/source/hosted.dart'; | 43 import '../lib/src/source/hosted.dart'; |
42 import '../lib/src/source/path.dart'; | 44 import '../lib/src/source/path.dart'; |
43 import '../lib/src/source_registry.dart'; | 45 import '../lib/src/source_registry.dart'; |
44 import '../lib/src/system_cache.dart'; | 46 import '../lib/src/system_cache.dart'; |
45 import '../lib/src/utils.dart'; | 47 import '../lib/src/utils.dart'; |
46 import '../lib/src/validator.dart'; | 48 import '../lib/src/validator.dart'; |
47 import 'descriptor.dart' as d; | 49 import 'descriptor.dart' as d; |
48 import 'serve_packages.dart'; | 50 import 'serve_packages.dart'; |
49 | 51 |
50 export 'serve_packages.dart'; | 52 export 'serve_packages.dart'; |
(...skipping 461 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
512 // Find a Dart executable we can use to spawn. Use the same one that was | 514 // Find a Dart executable we can use to spawn. Use the same one that was |
513 // used to run this script itself. | 515 // used to run this script itself. |
514 var dartBin = Platform.executable; | 516 var dartBin = Platform.executable; |
515 | 517 |
516 // If the executable looks like a path, get its full path. That way we | 518 // If the executable looks like a path, get its full path. That way we |
517 // can still find it when we spawn it with a different working directory. | 519 // can still find it when we spawn it with a different working directory. |
518 if (dartBin.contains(Platform.pathSeparator)) { | 520 if (dartBin.contains(Platform.pathSeparator)) { |
519 dartBin = p.absolute(dartBin); | 521 dartBin = p.absolute(dartBin); |
520 } | 522 } |
521 | 523 |
522 // Always run pub from a snapshot. Since we require the SDK to be built, the | 524 // Always run pub from a snapshot. Using the snapshot makes running the tests |
523 // snapshot should be there. Note that this *does* mean that the snapshot has | 525 // much faster, especially when multiple tests are run at once. |
524 // to be manually updated when changing code before running the tests. | 526 var pubPath = p.absolute(p.join(pubRoot, '.pub/pub.test.snapshot')); |
525 // Otherwise, you will test against stale data. | |
526 // | |
527 // Using the snapshot makes running the tests much faster, which is why we | |
528 // make this trade-off. | |
529 var pubPath = p.join(p.dirname(dartBin), 'snapshots/pub.dart.snapshot'); | |
530 var dartArgs = [pubPath, '--verbose']; | 527 var dartArgs = [pubPath, '--verbose']; |
531 dartArgs.addAll(args); | 528 dartArgs.addAll(args); |
532 | 529 |
533 if (tokenEndpoint == null) tokenEndpoint = new Future.value(); | 530 if (tokenEndpoint == null) tokenEndpoint = new Future.value(); |
534 var environmentFuture = tokenEndpoint | 531 var environmentFuture = tokenEndpoint |
535 .then((tokenEndpoint) => getPubTestEnvironment(tokenEndpoint)) | 532 .then((tokenEndpoint) => getPubTestEnvironment(tokenEndpoint)) |
536 .then((pubEnvironment) { | 533 .then((pubEnvironment) { |
537 if (environment != null) pubEnvironment.addAll(environment); | 534 if (environment != null) pubEnvironment.addAll(environment); |
538 return pubEnvironment; | 535 return pubEnvironment; |
539 }); | 536 }); |
540 | 537 |
| 538 _ensureSnapshot(); |
| 539 |
541 return new PubProcess.start(dartBin, dartArgs, environment: environmentFuture, | 540 return new PubProcess.start(dartBin, dartArgs, environment: environmentFuture, |
542 workingDirectory: _pathInSandbox(appPath), | 541 workingDirectory: _pathInSandbox(appPath), |
543 description: args.isEmpty ? 'pub' : 'pub ${args.first}'); | 542 description: args.isEmpty ? 'pub' : 'pub ${args.first}'); |
544 } | 543 } |
545 | 544 |
| 545 /// Ensure that a snapshot of the current pub source exists at |
| 546 /// ".pub/pub.snapshot". |
| 547 void _ensureSnapshot() { |
| 548 ensureDir(p.join(pubRoot, '.pub')); |
| 549 |
| 550 var version = sdk.version.toString(); |
| 551 var hash = _hashChanges(); |
| 552 |
| 553 var snapshotPath = p.join(pubRoot, '.pub', 'pub.test.snapshot'); |
| 554 var hashPath = p.join(pubRoot, '.pub', 'pub.hash'); |
| 555 var versionPath = p.join(pubRoot, '.pub', 'pub.version'); |
| 556 if (fileExists(hashPath) && fileExists(versionPath)) { |
| 557 var oldHash = readTextFile(hashPath); |
| 558 var oldVersion = readTextFile(versionPath); |
| 559 |
| 560 if (oldHash == hash && oldVersion == version && fileExists(snapshotPath)) { |
| 561 return; |
| 562 } |
| 563 } |
| 564 |
| 565 var dartSnapshot = runProcessSync(Platform.executable, [ |
| 566 '--snapshot=$snapshotPath', |
| 567 p.join(pubRoot, 'bin', 'pub.dart') |
| 568 ]); |
| 569 if (dartSnapshot.exitCode != 0) throw "Failed to run dart --snapshot."; |
| 570 |
| 571 writeTextFile(hashPath, hash); |
| 572 writeTextFile(versionPath, version); |
| 573 } |
| 574 |
| 575 /// Returns a hash that encapsulates the current state of the repo. |
| 576 String _hashChanges() { |
| 577 var hash = new SHA1(); |
| 578 |
| 579 // Include the current Git commit. |
| 580 hash.add(UTF8.encode(gitlib.runSync(['rev-parse', 'HEAD']).first)); |
| 581 |
| 582 // Include the changes in lib and bin relative to the current Git commit. |
| 583 var tracked = gitlib.runSync(['diff-index', '--patch', 'HEAD', 'lib', 'bin']); |
| 584 for (var line in tracked) { |
| 585 hash.add(UTF8.encode("$line\n")); |
| 586 } |
| 587 |
| 588 // Include the full contents of non-ignored files in lib and bin that aren't |
| 589 // tracked by Git. |
| 590 var untracked = gitlib.runSync( |
| 591 ['ls-files', '--others', '--exclude-standard', 'lib', 'bin']); |
| 592 for (var path in untracked) { |
| 593 hash.add(readBinaryFile(path)); |
| 594 } |
| 595 |
| 596 return CryptoUtils.bytesToHex(hash.close()); |
| 597 } |
| 598 |
546 /// A subclass of [ScheduledProcess] that parses pub's verbose logging output | 599 /// A subclass of [ScheduledProcess] that parses pub's verbose logging output |
547 /// and makes [stdout] and [stderr] work as though pub weren't running in | 600 /// and makes [stdout] and [stderr] work as though pub weren't running in |
548 /// verbose mode. | 601 /// verbose mode. |
549 class PubProcess extends ScheduledProcess { | 602 class PubProcess extends ScheduledProcess { |
550 Stream<Pair<log.Level, String>> _log; | 603 Stream<Pair<log.Level, String>> _log; |
551 Stream<String> _stdout; | 604 Stream<String> _stdout; |
552 Stream<String> _stderr; | 605 Stream<String> _stderr; |
553 | 606 |
554 PubProcess.start(executable, arguments, | 607 PubProcess.start(executable, arguments, |
555 {workingDirectory, environment, String description, | 608 {workingDirectory, environment, String description, |
(...skipping 411 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
967 _lastMatcher.matches(item.last, matchState); | 1020 _lastMatcher.matches(item.last, matchState); |
968 } | 1021 } |
969 | 1022 |
970 Description describe(Description description) { | 1023 Description describe(Description description) { |
971 return description.addAll("(", ", ", ")", [_firstMatcher, _lastMatcher]); | 1024 return description.addAll("(", ", ", ")", [_firstMatcher, _lastMatcher]); |
972 } | 1025 } |
973 } | 1026 } |
974 | 1027 |
975 /// A [StreamMatcher] that matches multiple lines of output. | 1028 /// A [StreamMatcher] that matches multiple lines of output. |
976 StreamMatcher emitsLines(String output) => inOrder(output.split("\n")); | 1029 StreamMatcher emitsLines(String output) => inOrder(output.split("\n")); |
OLD | NEW |