| 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. Unlike typical unit tests, most pub | 5 /// Test infrastructure for testing pub. |
| 6 /// tests are integration tests that stage some stuff on the file system, run | 6 /// |
| 7 /// pub, and then validate the results. This library provides an API to build | 7 /// Unlike typical unit tests, most pub tests are integration tests that stage |
| 8 /// tests like that. | 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 test_pub; | 10 library test_pub; |
| 10 | 11 |
| 11 import 'dart:async'; | 12 import 'dart:async'; |
| 12 import 'dart:convert'; | 13 import 'dart:convert'; |
| 13 import 'dart:io'; | 14 import 'dart:io'; |
| 14 import 'dart:math'; | 15 import 'dart:math'; |
| 15 | 16 |
| 16 import 'package:http/testing.dart'; | 17 import 'package:http/testing.dart'; |
| 17 import 'package:path/path.dart' as path; | 18 import 'package:path/path.dart' as path; |
| 18 import 'package:scheduled_test/scheduled_process.dart'; | 19 import 'package:scheduled_test/scheduled_process.dart'; |
| (...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 142 /// Gets the list of paths that have been requested from the server since the | 143 /// Gets the list of paths that have been requested from the server since the |
| 143 /// last time this was called (or since the server was first spun up). | 144 /// last time this was called (or since the server was first spun up). |
| 144 Future<List<String>> getRequestedPaths() { | 145 Future<List<String>> getRequestedPaths() { |
| 145 return schedule(() { | 146 return schedule(() { |
| 146 var paths = _requestedPaths.toList(); | 147 var paths = _requestedPaths.toList(); |
| 147 _requestedPaths.clear(); | 148 _requestedPaths.clear(); |
| 148 return paths; | 149 return paths; |
| 149 }, "get previous network requests"); | 150 }, "get previous network requests"); |
| 150 } | 151 } |
| 151 | 152 |
| 152 /// Creates an HTTP server to serve [contents] as static files. This server will | 153 /// Creates an HTTP server to serve [contents] as static files. |
| 153 /// exist only for the duration of the pub run. | |
| 154 /// | 154 /// |
| 155 /// Subsequent calls to [serve] will replace the previous server. | 155 /// This server will exist only for the duration of the pub run. Subsequent |
| 156 /// calls to [serve] replace the previous server. |
| 156 void serve([List<d.Descriptor> contents]) { | 157 void serve([List<d.Descriptor> contents]) { |
| 157 var baseDir = d.dir("serve-dir", contents); | 158 var baseDir = d.dir("serve-dir", contents); |
| 158 | 159 |
| 159 _hasServer = true; | 160 _hasServer = true; |
| 160 | 161 |
| 161 schedule(() { | 162 schedule(() { |
| 162 return _closeServer().then((_) { | 163 return _closeServer().then((_) { |
| 163 return shelf_io.serve((request) { | 164 return shelf_io.serve((request) { |
| 164 currentSchedule.heartbeat(); | 165 currentSchedule.heartbeat(); |
| 165 var path = request.url.path.replaceFirst("/", ""); | 166 var path = request.url.path.replaceFirst("/", ""); |
| 166 _requestedPaths.add(path); | 167 _requestedPaths.add(path); |
| 167 | 168 |
| 168 return validateStream(baseDir.load(path)) | 169 return validateStream(baseDir.load(path)) |
| 169 .then((stream) => new shelf.Response.ok(stream)) | 170 .then((stream) => new shelf.Response.ok(stream)) |
| 170 .catchError((error) { | 171 .catchError((error) { |
| 171 return new shelf.Response.notFound('File "$path" not found.'); | 172 return new shelf.Response.notFound('File "$path" not found.'); |
| 172 }); | 173 }); |
| 173 }, 'localhost', 0).then((server) { | 174 }, 'localhost', 0).then((server) { |
| 174 _server = server; | 175 _server = server; |
| 175 _portCompleter.complete(_server.port); | 176 _portCompleter.complete(_server.port); |
| 176 currentSchedule.onComplete.schedule(_closeServer); | 177 currentSchedule.onComplete.schedule(_closeServer); |
| 177 }); | 178 }); |
| 178 }); | 179 }); |
| 179 }, 'starting a server serving:\n${baseDir.describe()}'); | 180 }, 'starting a server serving:\n${baseDir.describe()}'); |
| 180 } | 181 } |
| 181 | 182 |
| 182 /// Closes [_server]. Returns a [Future] that will complete after the [_server] | 183 /// Closes [_server]. |
| 183 /// is closed. | 184 /// |
| 185 /// Returns a [Future] that completes after the [_server] is closed. |
| 184 Future _closeServer() { | 186 Future _closeServer() { |
| 185 if (_server == null) return new Future.value(); | 187 if (_server == null) return new Future.value(); |
| 186 var future = _server.close(); | 188 var future = _server.close(); |
| 187 _server = null; | 189 _server = null; |
| 188 _hasServer = false; | 190 _hasServer = false; |
| 189 _portCompleterCache = null; | 191 _portCompleterCache = null; |
| 190 return future; | 192 return future; |
| 191 } | 193 } |
| 192 | 194 |
| 193 /// `true` if the current test spins up an HTTP server. | 195 /// `true` if the current test spins up an HTTP server. |
| 194 bool _hasServer = false; | 196 bool _hasServer = false; |
| 195 | 197 |
| 196 /// The [d.DirectoryDescriptor] describing the server layout of `/api/packages` | 198 /// The [d.DirectoryDescriptor] describing the server layout of `/api/packages` |
| 197 /// on the test server. | 199 /// on the test server. |
| 198 /// | 200 /// |
| 199 /// This contains metadata for packages that are being served via | 201 /// This contains metadata for packages that are being served via |
| 200 /// [servePackages]. It's `null` if [servePackages] has not yet been called for | 202 /// [servePackages]. It's `null` if [servePackages] has not yet been called for |
| 201 /// this test. | 203 /// this test. |
| 202 d.DirectoryDescriptor _servedApiPackageDir; | 204 d.DirectoryDescriptor _servedApiPackageDir; |
| 203 | 205 |
| 204 /// The [d.DirectoryDescriptor] describing the server layout of `/packages` on | 206 /// The [d.DirectoryDescriptor] describing the server layout of `/packages` on |
| 205 /// the test server. | 207 /// the test server. |
| 206 /// | 208 /// |
| 207 /// This contains the tarballs for packages that are being served via | 209 /// This contains the tarballs for packages that are being served via |
| 208 /// [servePackages]. It's `null` if [servePackages] has not yet been called for | 210 /// [servePackages]. It's `null` if [servePackages] has not yet been called for |
| 209 /// this test. | 211 /// this test. |
| 210 d.DirectoryDescriptor _servedPackageDir; | 212 d.DirectoryDescriptor _servedPackageDir; |
| 211 | 213 |
| 212 /// A map from package names to parsed pubspec maps for those packages. This | 214 /// A map from package names to parsed pubspec maps for those packages. |
| 213 /// represents the packages currently being served by [servePackages], and is | 215 /// |
| 214 /// `null` if [servePackages] has not yet been called for this test. | 216 /// This represents the packages currently being served by [servePackages], and |
| 217 /// is `null` if [servePackages] has not yet been called for this test. |
| 215 Map<String, List<Map>> _servedPackages; | 218 Map<String, List<Map>> _servedPackages; |
| 216 | 219 |
| 217 /// Creates an HTTP server that replicates the structure of pub.dartlang.org. | 220 /// Creates an HTTP server that replicates the structure of pub.dartlang.org. |
| 221 /// |
| 218 /// [pubspecs] is a list of unserialized pubspecs representing the packages to | 222 /// [pubspecs] is a list of unserialized pubspecs representing the packages to |
| 219 /// serve. | 223 /// serve. |
| 220 /// | 224 /// |
| 221 /// If [replace] is false, subsequent calls to [servePackages] will add to the | 225 /// If [replace] is false, subsequent calls to [servePackages] will add to the |
| 222 /// set of packages that are being served. Previous packages will continue to be | 226 /// set of packages that are being served. Previous packages will continue to be |
| 223 /// served. Otherwise, the previous packages will no longer be served. | 227 /// served. Otherwise, the previous packages will no longer be served. |
| 224 void servePackages(List<Map> pubspecs, {bool replace: false}) { | 228 void servePackages(List<Map> pubspecs, {bool replace: false}) { |
| 225 if (_servedPackages == null || _servedPackageDir == null) { | 229 if (_servedPackages == null || _servedPackageDir == null) { |
| 226 _servedPackages = <String, List<Map>>{}; | 230 _servedPackages = <String, List<Map>>{}; |
| 227 _servedApiPackageDir = d.dir('packages', []); | 231 _servedApiPackageDir = d.dir('packages', []); |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 280 }, 'initializing the package server'); | 284 }, 'initializing the package server'); |
| 281 } | 285 } |
| 282 | 286 |
| 283 /// Converts [value] into a YAML string. | 287 /// Converts [value] into a YAML string. |
| 284 String yaml(value) => JSON.encode(value); | 288 String yaml(value) => JSON.encode(value); |
| 285 | 289 |
| 286 /// The full path to the created sandbox directory for an integration test. | 290 /// The full path to the created sandbox directory for an integration test. |
| 287 String get sandboxDir => _sandboxDir; | 291 String get sandboxDir => _sandboxDir; |
| 288 String _sandboxDir; | 292 String _sandboxDir; |
| 289 | 293 |
| 290 /// The path of the package cache directory used for tests. Relative to the | 294 /// The path of the package cache directory used for tests, relative to the |
| 291 /// sandbox directory. | 295 /// sandbox directory. |
| 292 final String cachePath = "cache"; | 296 final String cachePath = "cache"; |
| 293 | 297 |
| 294 /// The path of the mock app directory used for tests. Relative to the sandbox | 298 /// The path of the mock app directory used for tests, relative to the sandbox |
| 295 /// directory. | 299 /// directory. |
| 296 final String appPath = "myapp"; | 300 final String appPath = "myapp"; |
| 297 | 301 |
| 298 /// The path of the packages directory in the mock app used for tests. Relative | 302 /// The path of the packages directory in the mock app used for tests, relative |
| 299 /// to the sandbox directory. | 303 /// to the sandbox directory. |
| 300 final String packagesPath = "$appPath/packages"; | 304 final String packagesPath = "$appPath/packages"; |
| 301 | 305 |
| 302 /// Set to true when the current batch of scheduled events should be aborted. | 306 /// Set to true when the current batch of scheduled events should be aborted. |
| 303 bool _abortScheduled = false; | 307 bool _abortScheduled = false; |
| 304 | 308 |
| 305 /// Enum identifying a pub command that can be run with a well-defined success | 309 /// Enum identifying a pub command that can be run with a well-defined success |
| 306 /// output. | 310 /// output. |
| 307 class RunCommand { | 311 class RunCommand { |
| 308 static final get = new RunCommand('get', new RegExp(r'Got dependencies!$')); | 312 static final get = new RunCommand('get', new RegExp(r'Got dependencies!$')); |
| 309 static final upgrade = new RunCommand('upgrade', new RegExp( | 313 static final upgrade = new RunCommand('upgrade', new RegExp( |
| 310 r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$')); | 314 r'(No dependencies changed\.|Changed \d+ dependenc(y|ies)!)$')); |
| 311 | 315 |
| 312 final String name; | 316 final String name; |
| 313 final RegExp success; | 317 final RegExp success; |
| 314 RunCommand(this.name, this.success); | 318 RunCommand(this.name, this.success); |
| 315 } | 319 } |
| 316 | 320 |
| 321 /// Runs the tests defined within [callback] using both pub get and pub upgrade. |
| 322 /// |
| 317 /// Many tests validate behavior that is the same between pub get and | 323 /// Many tests validate behavior that is the same between pub get and |
| 318 /// upgrade have the same behavior. Instead of duplicating those tests, this | 324 /// upgrade have the same behavior. Instead of duplicating those tests, this |
| 319 /// takes a callback that defines get/upgrade agnostic tests and runs them | 325 /// takes a callback that defines get/upgrade agnostic tests and runs them |
| 320 /// with both commands. | 326 /// with both commands. |
| 321 void forBothPubGetAndUpgrade(void callback(RunCommand command)) { | 327 void forBothPubGetAndUpgrade(void callback(RunCommand command)) { |
| 322 group(RunCommand.get.name, () => callback(RunCommand.get)); | 328 group(RunCommand.get.name, () => callback(RunCommand.get)); |
| 323 group(RunCommand.upgrade.name, () => callback(RunCommand.upgrade)); | 329 group(RunCommand.upgrade.name, () => callback(RunCommand.upgrade)); |
| 324 } | 330 } |
| 325 | 331 |
| 326 /// Schedules an invocation of pub [command] and validates that it completes | 332 /// Schedules an invocation of pub [command] and validates that it completes |
| (...skipping 29 matching lines...) Expand all Loading... |
| 356 | 362 |
| 357 void pubGet({Iterable<String> args, error, warning}) { | 363 void pubGet({Iterable<String> args, error, warning}) { |
| 358 pubCommand(RunCommand.get, args: args, error: error, warning: warning); | 364 pubCommand(RunCommand.get, args: args, error: error, warning: warning); |
| 359 } | 365 } |
| 360 | 366 |
| 361 void pubUpgrade({Iterable<String> args, output, error, warning}) { | 367 void pubUpgrade({Iterable<String> args, output, error, warning}) { |
| 362 pubCommand(RunCommand.upgrade, args: args, output: output, error: error, | 368 pubCommand(RunCommand.upgrade, args: args, output: output, error: error, |
| 363 warning: warning); | 369 warning: warning); |
| 364 } | 370 } |
| 365 | 371 |
| 366 /// Defines an integration test. The [body] should schedule a series of | 372 /// Defines an integration test. |
| 367 /// operations which will be run asynchronously. | 373 /// |
| 374 /// The [body] should schedule a series of operations which will be run |
| 375 /// asynchronously. |
| 368 void integration(String description, void body()) => | 376 void integration(String description, void body()) => |
| 369 _integration(description, body, test); | 377 _integration(description, body, test); |
| 370 | 378 |
| 371 /// Like [integration], but causes only this test to run. | 379 /// Like [integration], but causes only this test to run. |
| 372 void solo_integration(String description, void body()) => | 380 void solo_integration(String description, void body()) => |
| 373 _integration(description, body, solo_test); | 381 _integration(description, body, solo_test); |
| 374 | 382 |
| 375 void _integration(String description, void body(), [Function testFn]) { | 383 void _integration(String description, void body(), [Function testFn]) { |
| 376 testFn(description, () { | 384 testFn(description, () { |
| 377 // TODO(nweiz): remove this when issue 15362 is fixed. | 385 // TODO(nweiz): remove this when issue 15362 is fixed. |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 468 /// Any futures in [args] will be resolved before the process is started. | 476 /// Any futures in [args] will be resolved before the process is started. |
| 469 ScheduledProcess startPublish(ScheduledServer server, {List args}) { | 477 ScheduledProcess startPublish(ScheduledServer server, {List args}) { |
| 470 var tokenEndpoint = server.url.then((url) => | 478 var tokenEndpoint = server.url.then((url) => |
| 471 url.resolve('/token').toString()); | 479 url.resolve('/token').toString()); |
| 472 if (args == null) args = []; | 480 if (args == null) args = []; |
| 473 args = flatten(['lish', '--server', tokenEndpoint, args]); | 481 args = flatten(['lish', '--server', tokenEndpoint, args]); |
| 474 return startPub(args: args, tokenEndpoint: tokenEndpoint); | 482 return startPub(args: args, tokenEndpoint: tokenEndpoint); |
| 475 } | 483 } |
| 476 | 484 |
| 477 /// Handles the beginning confirmation process for uploading a packages. | 485 /// Handles the beginning confirmation process for uploading a packages. |
| 486 /// |
| 478 /// Ensures that the right output is shown and then enters "y" to confirm the | 487 /// Ensures that the right output is shown and then enters "y" to confirm the |
| 479 /// upload. | 488 /// upload. |
| 480 void confirmPublish(ScheduledProcess pub) { | 489 void confirmPublish(ScheduledProcess pub) { |
| 481 // TODO(rnystrom): This is overly specific and inflexible regarding different | 490 // TODO(rnystrom): This is overly specific and inflexible regarding different |
| 482 // test packages. Should validate this a little more loosely. | 491 // test packages. Should validate this a little more loosely. |
| 483 pub.stdout.expect(startsWith('Publishing test_pkg 1.0.0 to ')); | 492 pub.stdout.expect(startsWith('Publishing test_pkg 1.0.0 to ')); |
| 484 pub.stdout.expect(emitsLines( | 493 pub.stdout.expect(emitsLines( |
| 485 "|-- LICENSE\n" | 494 "|-- LICENSE\n" |
| 486 "|-- lib\n" | 495 "|-- lib\n" |
| 487 "| '-- test_pkg.dart\n" | 496 "| '-- test_pkg.dart\n" |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 646 void ensureGit() { | 655 void ensureGit() { |
| 647 if (Platform.operatingSystem == "windows") { | 656 if (Platform.operatingSystem == "windows") { |
| 648 currentSchedule.timeout = new Duration(seconds: 30); | 657 currentSchedule.timeout = new Duration(seconds: 30); |
| 649 } | 658 } |
| 650 | 659 |
| 651 if (!gitlib.isInstalled) { | 660 if (!gitlib.isInstalled) { |
| 652 throw new Exception("Git must be installed to run this test."); | 661 throw new Exception("Git must be installed to run this test."); |
| 653 } | 662 } |
| 654 } | 663 } |
| 655 | 664 |
| 656 /// Create a lock file for [package] without running `pub get`. | 665 /// Creates a lock file for [package] without running `pub get`. |
| 657 /// | 666 /// |
| 658 /// [sandbox] is a list of path dependencies to be found in the sandbox | 667 /// [sandbox] is a list of path dependencies to be found in the sandbox |
| 659 /// directory. [pkg] is a list of packages in the Dart repo's "pkg" directory; | 668 /// directory. [pkg] is a list of packages in the Dart repo's "pkg" directory; |
| 660 /// each package listed here and all its dependencies will be linked to the | 669 /// each package listed here and all its dependencies will be linked to the |
| 661 /// version in the Dart repo. | 670 /// version in the Dart repo. |
| 662 /// | 671 /// |
| 663 /// [hosted] is a list of package names to version strings for dependencies on | 672 /// [hosted] is a list of package names to version strings for dependencies on |
| 664 /// hosted packages. | 673 /// hosted packages. |
| 665 void createLockFile(String package, {Iterable<String> sandbox, | 674 void createLockFile(String package, {Iterable<String> sandbox, |
| 666 Iterable<String> pkg, Map<String, String> hosted}) { | 675 Iterable<String> pkg, Map<String, String> hosted}) { |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 720 } | 729 } |
| 721 | 730 |
| 722 var sources = new SourceRegistry(); | 731 var sources = new SourceRegistry(); |
| 723 sources.register(new HostedSource()); | 732 sources.register(new HostedSource()); |
| 724 sources.register(new PathSource()); | 733 sources.register(new PathSource()); |
| 725 | 734 |
| 726 d.file(path.join(package, 'pubspec.lock'), | 735 d.file(path.join(package, 'pubspec.lock'), |
| 727 lockFile.serialize(null, sources)).create(); | 736 lockFile.serialize(null, sources)).create(); |
| 728 } | 737 } |
| 729 | 738 |
| 730 /// Use [client] as the mock HTTP client for this test. | 739 /// Uses [client] as the mock HTTP client for this test. |
| 731 /// | 740 /// |
| 732 /// Note that this will only affect HTTP requests made via http.dart in the | 741 /// Note that this will only affect HTTP requests made via http.dart in the |
| 733 /// parent process. | 742 /// parent process. |
| 734 void useMockClient(MockClient client) { | 743 void useMockClient(MockClient client) { |
| 735 var oldInnerClient = httpClient.inner; | 744 var oldInnerClient = httpClient.inner; |
| 736 httpClient.inner = client; | 745 httpClient.inner = client; |
| 737 currentSchedule.onComplete.schedule(() { | 746 currentSchedule.onComplete.schedule(() { |
| 738 httpClient.inner = oldInnerClient; | 747 httpClient.inner = oldInnerClient; |
| 739 }, 'de-activating the mock client'); | 748 }, 'de-activating the mock client'); |
| 740 } | 749 } |
| (...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 868 failures.add(actualText); | 877 failures.add(actualText); |
| 869 } | 878 } |
| 870 | 879 |
| 871 // Match against the expectation. | 880 // Match against the expectation. |
| 872 expect(actual, expected); | 881 expect(actual, expected); |
| 873 } | 882 } |
| 874 | 883 |
| 875 /// A function that creates a [Validator] subclass. | 884 /// A function that creates a [Validator] subclass. |
| 876 typedef Validator ValidatorCreator(Entrypoint entrypoint); | 885 typedef Validator ValidatorCreator(Entrypoint entrypoint); |
| 877 | 886 |
| 878 /// Schedules a single [Validator] to run on the [appPath]. Returns a scheduled | 887 /// Schedules a single [Validator] to run on the [appPath]. |
| 879 /// Future that contains the errors and warnings produced by that validator. | 888 /// |
| 889 /// Returns a scheduled Future that contains the errors and warnings produced |
| 890 /// by that validator. |
| 880 Future<Pair<List<String>, List<String>>> schedulePackageValidation( | 891 Future<Pair<List<String>, List<String>>> schedulePackageValidation( |
| 881 ValidatorCreator fn) { | 892 ValidatorCreator fn) { |
| 882 return schedule(() { | 893 return schedule(() { |
| 883 var cache = new SystemCache.withSources(path.join(sandboxDir, cachePath)); | 894 var cache = new SystemCache.withSources(path.join(sandboxDir, cachePath)); |
| 884 | 895 |
| 885 return syncFuture(() { | 896 return syncFuture(() { |
| 886 var validator = fn(new Entrypoint(path.join(sandboxDir, appPath), cache)); | 897 var validator = fn(new Entrypoint(path.join(sandboxDir, appPath), cache)); |
| 887 return validator.validate().then((_) { | 898 return validator.validate().then((_) { |
| 888 return new Pair(validator.errors, validator.warnings); | 899 return new Pair(validator.errors, validator.warnings); |
| 889 }); | 900 }); |
| (...skipping 17 matching lines...) Expand all Loading... |
| 907 _lastMatcher.matches(item.last, matchState); | 918 _lastMatcher.matches(item.last, matchState); |
| 908 } | 919 } |
| 909 | 920 |
| 910 Description describe(Description description) { | 921 Description describe(Description description) { |
| 911 return description.addAll("(", ", ", ")", [_firstMatcher, _lastMatcher]); | 922 return description.addAll("(", ", ", ")", [_firstMatcher, _lastMatcher]); |
| 912 } | 923 } |
| 913 } | 924 } |
| 914 | 925 |
| 915 /// A [StreamMatcher] that matches multiple lines of output. | 926 /// A [StreamMatcher] that matches multiple lines of output. |
| 916 StreamMatcher emitsLines(String output) => inOrder(output.split("\n")); | 927 StreamMatcher emitsLines(String output) => inOrder(output.split("\n")); |
| OLD | NEW |