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 |