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 81 matching lines...) Loading... |
92 /// A future that will complete to the port used for the current server. | 92 /// A future that will complete to the port used for the current server. |
93 Future<int> get port => _portCompleter.future; | 93 Future<int> get port => _portCompleter.future; |
94 | 94 |
95 /// Creates an HTTP server to serve [contents] as static files. This server will | 95 /// Creates an HTTP server to serve [contents] as static files. This server will |
96 /// exist only for the duration of the pub run. | 96 /// exist only for the duration of the pub run. |
97 /// | 97 /// |
98 /// Subsequent calls to [serve] will replace the previous server. | 98 /// Subsequent calls to [serve] will replace the previous server. |
99 void serve([List<Descriptor> contents]) { | 99 void serve([List<Descriptor> contents]) { |
100 var baseDir = dir("serve-dir", contents); | 100 var baseDir = dir("serve-dir", contents); |
101 | 101 |
102 _schedule((_) { | 102 schedule((_) { |
103 return _closeServer().then((_) { | 103 return _closeServer().then((_) { |
104 _server = new HttpServer(); | 104 _server = new HttpServer(); |
105 _server.defaultRequestHandler = (request, response) { | 105 _server.defaultRequestHandler = (request, response) { |
106 var path = request.uri.replaceFirst("/", "").split("/"); | 106 var path = request.uri.replaceFirst("/", "").split("/"); |
107 response.persistentConnection = false; | 107 response.persistentConnection = false; |
108 var stream; | 108 var stream; |
109 try { | 109 try { |
110 stream = baseDir.load(path); | 110 stream = baseDir.load(path); |
111 } catch (e) { | 111 } catch (e) { |
112 response.statusCode = 404; | 112 response.statusCode = 404; |
(...skipping 57 matching lines...) Loading... |
170 _servedPackages = <String, Map<String, String>>{}; | 170 _servedPackages = <String, Map<String, String>>{}; |
171 _servedPackageDir = dir('packages', []); | 171 _servedPackageDir = dir('packages', []); |
172 serve([_servedPackageDir]); | 172 serve([_servedPackageDir]); |
173 | 173 |
174 _scheduleCleanup((_) { | 174 _scheduleCleanup((_) { |
175 _servedPackages = null; | 175 _servedPackages = null; |
176 _servedPackageDir = null; | 176 _servedPackageDir = null; |
177 }); | 177 }); |
178 } | 178 } |
179 | 179 |
180 _schedule((_) { | 180 schedule((_) { |
181 return _awaitObject(pubspecs).then((resolvedPubspecs) { | 181 return _awaitObject(pubspecs).then((resolvedPubspecs) { |
182 for (var spec in resolvedPubspecs) { | 182 for (var spec in resolvedPubspecs) { |
183 var name = spec['name']; | 183 var name = spec['name']; |
184 var version = spec['version']; | 184 var version = spec['version']; |
185 var versions = _servedPackages.putIfAbsent( | 185 var versions = _servedPackages.putIfAbsent( |
186 name, () => <String, String>{}); | 186 name, () => <String, String>{}); |
187 versions[version] = yaml(spec); | 187 versions[version] = yaml(spec); |
188 } | 188 } |
189 | 189 |
190 _servedPackageDir.contents.clear(); | 190 _servedPackageDir.contents.clear(); |
(...skipping 165 matching lines...) Loading... |
356 contents.add(packageCacheDir(name, version)); | 356 contents.add(packageCacheDir(name, version)); |
357 } | 357 } |
358 }); | 358 }); |
359 return dir(cachePath, [ | 359 return dir(cachePath, [ |
360 dir('hosted', [ | 360 dir('hosted', [ |
361 async(port.then((p) => dir('localhost%58$p', contents))) | 361 async(port.then((p) => dir('localhost%58$p', contents))) |
362 ]) | 362 ]) |
363 ]); | 363 ]); |
364 } | 364 } |
365 | 365 |
| 366 /// Describes the directory in the system cache where the package [name] at |
| 367 /// [version] is stored when installed from the mock package server. |
| 368 Future<String> hostedCacheDir(String name, String version) { |
| 369 return port.then((p) { |
| 370 return path.join(cachePath, "hosted", "localhost%58$p", "$name-$version"); |
| 371 }); |
| 372 } |
| 373 |
366 /// Describes the file in the system cache that contains the client's OAuth2 | 374 /// Describes the file in the system cache that contains the client's OAuth2 |
367 /// credentials. The URL "/token" on [server] will be used as the token | 375 /// credentials. The URL "/token" on [server] will be used as the token |
368 /// endpoint for refreshing the access token. | 376 /// endpoint for refreshing the access token. |
369 Descriptor credentialsFile( | 377 Descriptor credentialsFile( |
370 ScheduledServer server, | 378 ScheduledServer server, |
371 String accessToken, | 379 String accessToken, |
372 {String refreshToken, | 380 {String refreshToken, |
373 DateTime expiration}) { | 381 DateTime expiration}) { |
374 return async(server.url.then((url) { | 382 return async(server.url.then((url) { |
375 return dir(cachePath, [ | 383 return dir(cachePath, [ |
(...skipping 153 matching lines...) Loading... |
529 var dir = new Options().script; | 537 var dir = new Options().script; |
530 while (basename(dir) != 'pub') dir = dirname(dir); | 538 while (basename(dir) != 'pub') dir = dirname(dir); |
531 | 539 |
532 return getFullPath(dir); | 540 return getFullPath(dir); |
533 } | 541 } |
534 | 542 |
535 /// Schedules a call to the Pub command-line utility. Runs Pub with [args] and | 543 /// Schedules a call to the Pub command-line utility. Runs Pub with [args] and |
536 /// validates that its results match [output], [error], and [exitCode]. | 544 /// validates that its results match [output], [error], and [exitCode]. |
537 void schedulePub({List args, Pattern output, Pattern error, | 545 void schedulePub({List args, Pattern output, Pattern error, |
538 Future<Uri> tokenEndpoint, int exitCode: 0}) { | 546 Future<Uri> tokenEndpoint, int exitCode: 0}) { |
539 _schedule((sandboxDir) { | 547 schedule((sandboxDir) { |
540 return _doPub(runProcess, sandboxDir, args, tokenEndpoint).then((result) { | 548 return _doPub(runProcess, sandboxDir, args, tokenEndpoint).then((result) { |
541 var failures = []; | 549 var failures = []; |
542 | 550 |
543 _validateOutput(failures, 'stdout', output, result.stdout); | 551 _validateOutput(failures, 'stdout', output, result.stdout); |
544 _validateOutput(failures, 'stderr', error, result.stderr); | 552 _validateOutput(failures, 'stderr', error, result.stderr); |
545 | 553 |
546 if (result.exitCode != exitCode) { | 554 if (result.exitCode != exitCode) { |
547 failures.add( | 555 failures.add( |
548 'Pub returned exit code ${result.exitCode}, expected $exitCode.'); | 556 'Pub returned exit code ${result.exitCode}, expected $exitCode.'); |
549 } | 557 } |
(...skipping 97 matching lines...) Loading... |
647 }); | 655 }); |
648 } | 656 } |
649 | 657 |
650 /// Skips the current test if Git is not installed. This validates that the | 658 /// Skips the current test if Git is not installed. This validates that the |
651 /// current test is running on a buildbot in which case we expect git to be | 659 /// current test is running on a buildbot in which case we expect git to be |
652 /// installed. If we are not running on the buildbot, we will instead see if | 660 /// installed. If we are not running on the buildbot, we will instead see if |
653 /// git is installed and skip the test if not. This way, users don't need to | 661 /// git is installed and skip the test if not. This way, users don't need to |
654 /// have git installed to run the tests locally (unless they actually care | 662 /// have git installed to run the tests locally (unless they actually care |
655 /// about the pub git tests). | 663 /// about the pub git tests). |
656 void ensureGit() { | 664 void ensureGit() { |
657 _schedule((_) { | 665 schedule((_) { |
658 return gitlib.isInstalled.then((installed) { | 666 return gitlib.isInstalled.then((installed) { |
659 if (!installed && | 667 if (!installed && |
660 !Platform.environment.containsKey('BUILDBOT_BUILDERNAME')) { | 668 !Platform.environment.containsKey('BUILDBOT_BUILDERNAME')) { |
661 _abortScheduled = true; | 669 _abortScheduled = true; |
662 } | 670 } |
663 return null; | 671 return null; |
664 }); | 672 }); |
665 }); | 673 }); |
666 } | 674 } |
667 | 675 |
(...skipping 130 matching lines...) Loading... |
798 Future delete(String dir); | 806 Future delete(String dir); |
799 | 807 |
800 /// Loads the file at [path] from within this descriptor. If [path] is empty, | 808 /// Loads the file at [path] from within this descriptor. If [path] is empty, |
801 /// loads the contents of the descriptor itself. | 809 /// loads the contents of the descriptor itself. |
802 ByteStream load(List<String> path); | 810 ByteStream load(List<String> path); |
803 | 811 |
804 /// Schedules the directory to be created before Pub is run with | 812 /// Schedules the directory to be created before Pub is run with |
805 /// [schedulePub]. The directory will be created relative to the sandbox | 813 /// [schedulePub]. The directory will be created relative to the sandbox |
806 /// directory. | 814 /// directory. |
807 // TODO(nweiz): Use implicit closurization once issue 2984 is fixed. | 815 // TODO(nweiz): Use implicit closurization once issue 2984 is fixed. |
808 void scheduleCreate() => _schedule((dir) => this.create(dir)); | 816 void scheduleCreate() => schedule((dir) => this.create(dir)); |
809 | 817 |
810 /// Schedules the file or directory to be deleted recursively. | 818 /// Schedules the file or directory to be deleted recursively. |
811 void scheduleDelete() => _schedule((dir) => this.delete(dir)); | 819 void scheduleDelete() => schedule((dir) => this.delete(dir)); |
812 | 820 |
813 /// Schedules the directory to be validated after Pub is run with | 821 /// Schedules the directory to be validated after Pub is run with |
814 /// [schedulePub]. The directory will be validated relative to the sandbox | 822 /// [schedulePub]. The directory will be validated relative to the sandbox |
815 /// directory. | 823 /// directory. |
816 void scheduleValidate() => _schedule((parentDir) => validate(parentDir.path)); | 824 void scheduleValidate() => schedule((parentDir) => validate(parentDir.path)); |
817 | 825 |
818 /// Asserts that the name of the descriptor is a [String] and returns it. | 826 /// Asserts that the name of the descriptor is a [String] and returns it. |
819 String get _stringName { | 827 String get _stringName { |
820 if (name is String) return name; | 828 if (name is String) return name; |
821 throw 'Pattern $name must be a string.'; | 829 throw 'Pattern $name must be a string.'; |
822 } | 830 } |
823 | 831 |
824 /// Validates that at least one file in [dir] matching [name] is valid | 832 /// Validates that at least one file in [dir] matching [name] is valid |
825 /// according to [validate]. [validate] should throw or complete to an | 833 /// according to [validate]. [validate] should throw or complete to an |
826 /// exception if the input path is invalid. | 834 /// exception if the input path is invalid. |
(...skipping 205 matching lines...) Loading... |
1032 | 1040 |
1033 /// Commits any changes to the Git repository. | 1041 /// Commits any changes to the Git repository. |
1034 Future commit(parentDir) { | 1042 Future commit(parentDir) { |
1035 return _runGitCommands(parentDir, [ | 1043 return _runGitCommands(parentDir, [ |
1036 ['add', '.'], | 1044 ['add', '.'], |
1037 ['commit', '-m', 'update'] | 1045 ['commit', '-m', 'update'] |
1038 ]); | 1046 ]); |
1039 } | 1047 } |
1040 | 1048 |
1041 /// Schedules changes to be committed to the Git repository. | 1049 /// Schedules changes to be committed to the Git repository. |
1042 void scheduleCommit() => _schedule((dir) => this.commit(dir)); | 1050 void scheduleCommit() => schedule((dir) => this.commit(dir)); |
1043 | 1051 |
1044 /// Return a Future that completes to the commit in the git repository | 1052 /// Return a Future that completes to the commit in the git repository |
1045 /// referred to by [ref] at the current point in the scheduled test run. | 1053 /// referred to by [ref] at the current point in the scheduled test run. |
1046 Future<String> revParse(String ref) { | 1054 Future<String> revParse(String ref) { |
1047 return _scheduleValue((parentDir) { | 1055 return _scheduleValue((parentDir) { |
1048 return super.create(parentDir).then((rootDir) { | 1056 return super.create(parentDir).then((rootDir) { |
1049 return _runGit(['rev-parse', ref], rootDir); | 1057 return _runGit(['rev-parse', ref], rootDir); |
1050 }).then((output) => output[0]); | 1058 }).then((output) => output[0]); |
1051 }); | 1059 }); |
1052 } | 1060 } |
1053 | 1061 |
1054 /// Schedule a Git command to run in this repository. | 1062 /// Schedule a Git command to run in this repository. |
1055 void scheduleGit(List<String> args) { | 1063 void scheduleGit(List<String> args) { |
1056 _schedule((parentDir) { | 1064 schedule((parentDir) { |
1057 var gitDir = new Directory(join(parentDir, name)); | 1065 var gitDir = new Directory(join(parentDir, name)); |
1058 return _runGit(args, gitDir); | 1066 return _runGit(args, gitDir); |
1059 }); | 1067 }); |
1060 } | 1068 } |
1061 | 1069 |
1062 Future _runGitCommands(parentDir, List<List<String>> commands) { | 1070 Future _runGitCommands(parentDir, List<List<String>> commands) { |
1063 var workingDir; | 1071 var workingDir; |
1064 | 1072 |
1065 Future runGitStep(_) { | 1073 Future runGitStep(_) { |
1066 if (commands.isEmpty) return new Future.immediate(workingDir); | 1074 if (commands.isEmpty) return new Future.immediate(workingDir); |
(...skipping 228 matching lines...) Loading... |
1295 var stderrPair = streamWithSubscription(stderrTee.last); | 1303 var stderrPair = streamWithSubscription(stderrTee.last); |
1296 _stderr = stderrPair.first; | 1304 _stderr = stderrPair.first; |
1297 _stderrSubscription = stderrPair.last; | 1305 _stderrSubscription = stderrPair.last; |
1298 | 1306 |
1299 return new Pair(stdoutTee.first, stderrTee.first); | 1307 return new Pair(stdoutTee.first, stderrTee.first); |
1300 }); | 1308 }); |
1301 | 1309 |
1302 _stdoutLines = pairFuture.then((pair) => pair.first.toList()); | 1310 _stdoutLines = pairFuture.then((pair) => pair.first.toList()); |
1303 _stderrLines = pairFuture.then((pair) => pair.last.toList()); | 1311 _stderrLines = pairFuture.then((pair) => pair.last.toList()); |
1304 | 1312 |
1305 _schedule((_) { | 1313 schedule((_) { |
1306 if (!_endScheduled) { | 1314 if (!_endScheduled) { |
1307 throw new StateError("Scheduled process $name must have shouldExit() " | 1315 throw new StateError("Scheduled process $name must have shouldExit() " |
1308 "or kill() called before the test is run."); | 1316 "or kill() called before the test is run."); |
1309 } | 1317 } |
1310 | 1318 |
1311 process.then((p) => p.exitCode).then((exitCode) { | 1319 process.then((p) => p.exitCode).then((exitCode) { |
1312 if (_endExpected) { | 1320 if (_endExpected) { |
1313 _exitCode = exitCode; | 1321 _exitCode = exitCode; |
1314 _exitCodeCompleter.complete(exitCode); | 1322 _exitCodeCompleter.complete(exitCode); |
1315 return; | 1323 return; |
(...skipping 81 matching lines...) Loading... |
1397 return _scheduleValue((_) { | 1405 return _scheduleValue((_) { |
1398 return timeout(_stderrFuture.then((stream) => stream.toList()) | 1406 return timeout(_stderrFuture.then((stream) => stream.toList()) |
1399 .then((lines) => lines.join("\n")), | 1407 .then((lines) => lines.join("\n")), |
1400 _SCHEDULE_TIMEOUT, | 1408 _SCHEDULE_TIMEOUT, |
1401 "waiting for the last stderr line from process $name"); | 1409 "waiting for the last stderr line from process $name"); |
1402 }); | 1410 }); |
1403 } | 1411 } |
1404 | 1412 |
1405 /// Writes [line] to the process as stdin. | 1413 /// Writes [line] to the process as stdin. |
1406 void writeLine(String line) { | 1414 void writeLine(String line) { |
1407 _schedule((_) => _processFuture.then( | 1415 schedule((_) => _processFuture.then( |
1408 (p) => p.stdin.add('$line\n'.charCodes))); | 1416 (p) => p.stdin.add('$line\n'.charCodes))); |
1409 } | 1417 } |
1410 | 1418 |
1411 /// Kills the process, and waits until it's dead. | 1419 /// Kills the process, and waits until it's dead. |
1412 void kill() { | 1420 void kill() { |
1413 _endScheduled = true; | 1421 _endScheduled = true; |
1414 _schedule((_) { | 1422 schedule((_) { |
1415 _endExpected = true; | 1423 _endExpected = true; |
1416 _process.kill(); | 1424 _process.kill(); |
1417 timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT, | 1425 timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT, |
1418 "waiting for process $name to die"); | 1426 "waiting for process $name to die"); |
1419 }); | 1427 }); |
1420 } | 1428 } |
1421 | 1429 |
1422 /// Waits for the process to exit, and verifies that the exit code matches | 1430 /// Waits for the process to exit, and verifies that the exit code matches |
1423 /// [expectedExitCode] (if given). | 1431 /// [expectedExitCode] (if given). |
1424 void shouldExit([int expectedExitCode]) { | 1432 void shouldExit([int expectedExitCode]) { |
1425 _endScheduled = true; | 1433 _endScheduled = true; |
1426 _schedule((_) { | 1434 schedule((_) { |
1427 _endExpected = true; | 1435 _endExpected = true; |
1428 return timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT, | 1436 return timeout(_exitCodeFuture, _SCHEDULE_TIMEOUT, |
1429 "waiting for process $name to exit").then((exitCode) { | 1437 "waiting for process $name to exit").then((exitCode) { |
1430 if (expectedExitCode != null) { | 1438 if (expectedExitCode != null) { |
1431 expect(exitCode, equals(expectedExitCode)); | 1439 expect(exitCode, equals(expectedExitCode)); |
1432 } | 1440 } |
1433 }); | 1441 }); |
1434 }); | 1442 }); |
1435 } | 1443 } |
1436 | 1444 |
(...skipping 117 matching lines...) Loading... |
1554 return Future.wait(pairs).then((resolvedPairs) { | 1562 return Future.wait(pairs).then((resolvedPairs) { |
1555 var map = {}; | 1563 var map = {}; |
1556 for (var pair in resolvedPairs) { | 1564 for (var pair in resolvedPairs) { |
1557 map[pair.first] = pair.last; | 1565 map[pair.first] = pair.last; |
1558 } | 1566 } |
1559 return map; | 1567 return map; |
1560 }); | 1568 }); |
1561 } | 1569 } |
1562 | 1570 |
1563 /// Schedules a callback to be called as part of the test case. | 1571 /// Schedules a callback to be called as part of the test case. |
1564 void _schedule(_ScheduledEvent event) { | 1572 void schedule(_ScheduledEvent event) { |
1565 if (_scheduled == null) _scheduled = []; | 1573 if (_scheduled == null) _scheduled = []; |
1566 _scheduled.add(event); | 1574 _scheduled.add(event); |
1567 } | 1575 } |
1568 | 1576 |
1569 /// Like [_schedule], but pipes the return value of [event] to a returned | 1577 /// Like [_schedule], but pipes the return value of [event] to a returned |
1570 /// [Future]. | 1578 /// [Future]. |
1571 Future _scheduleValue(_ScheduledEvent event) { | 1579 Future _scheduleValue(_ScheduledEvent event) { |
1572 var completer = new Completer(); | 1580 var completer = new Completer(); |
1573 _schedule((parentDir) { | 1581 schedule((parentDir) { |
1574 chainToCompleter(event(parentDir), completer); | 1582 chainToCompleter(event(parentDir), completer); |
1575 return completer.future; | 1583 return completer.future; |
1576 }); | 1584 }); |
1577 return completer.future; | 1585 return completer.future; |
1578 } | 1586 } |
1579 | 1587 |
1580 /// Schedules a callback to be called after the test case has completed, even | 1588 /// Schedules a callback to be called after the test case has completed, even |
1581 /// if it failed. | 1589 /// if it failed. |
1582 void _scheduleCleanup(_ScheduledEvent event) { | 1590 void _scheduleCleanup(_ScheduledEvent event) { |
1583 if (_scheduledCleanup == null) _scheduledCleanup = []; | 1591 if (_scheduledCleanup == null) _scheduledCleanup = []; |
1584 _scheduledCleanup.add(event); | 1592 _scheduledCleanup.add(event); |
1585 } | 1593 } |
1586 | 1594 |
1587 /// Schedules a callback to be called after the test case has completed, but | 1595 /// Schedules a callback to be called after the test case has completed, but |
1588 /// only if it failed. | 1596 /// only if it failed. |
1589 void _scheduleOnException(_ScheduledEvent event) { | 1597 void _scheduleOnException(_ScheduledEvent event) { |
1590 if (_scheduledOnException == null) _scheduledOnException = []; | 1598 if (_scheduledOnException == null) _scheduledOnException = []; |
1591 _scheduledOnException.add(event); | 1599 _scheduledOnException.add(event); |
1592 } | 1600 } |
1593 | 1601 |
1594 /// Like [expect], but for [Future]s that complete as part of the scheduled | 1602 /// Like [expect], but for [Future]s that complete as part of the scheduled |
1595 /// test. This is necessary to ensure that the exception thrown by the | 1603 /// test. This is necessary to ensure that the exception thrown by the |
1596 /// expectation failing is handled by the scheduler. | 1604 /// expectation failing is handled by the scheduler. |
1597 /// | 1605 /// |
1598 /// Note that [matcher] matches against the completed value of [actual], so | 1606 /// Note that [matcher] matches against the completed value of [actual], so |
1599 /// calling [completion] is unnecessary. | 1607 /// calling [completion] is unnecessary. |
1600 void expectLater(Future actual, matcher, {String reason, | 1608 void expectLater(Future actual, matcher, {String reason, |
1601 FailureHandler failureHandler, bool verbose: false}) { | 1609 FailureHandler failureHandler, bool verbose: false}) { |
1602 _schedule((_) { | 1610 schedule((_) { |
1603 return actual.then((value) { | 1611 return actual.then((value) { |
1604 expect(value, matcher, reason: reason, failureHandler: failureHandler, | 1612 expect(value, matcher, reason: reason, failureHandler: failureHandler, |
1605 verbose: false); | 1613 verbose: false); |
1606 }); | 1614 }); |
1607 }); | 1615 }); |
1608 } | 1616 } |
OLD | NEW |