| 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 /** | 5 /// Test infrastructure for testing pub. Unlike typical unit tests, most pub |
| 6 * 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 |
| 7 * 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 |
| 8 * pub, and then validate the results. This library provides an API to build | 8 /// tests like that. |
| 9 * tests like that. | |
| 10 */ | |
| 11 library test_pub; | 9 library test_pub; |
| 12 | 10 |
| 13 import 'dart:io'; | 11 import 'dart:io'; |
| 14 import 'dart:isolate'; | 12 import 'dart:isolate'; |
| 15 import 'dart:json'; | 13 import 'dart:json'; |
| 16 import 'dart:math'; | 14 import 'dart:math'; |
| 17 import 'dart:uri'; | 15 import 'dart:uri'; |
| 18 | 16 |
| 19 import '../../../pkg/oauth2/lib/oauth2.dart' as oauth2; | 17 import '../../../pkg/oauth2/lib/oauth2.dart' as oauth2; |
| 20 import '../../../pkg/path/lib/path.dart' as path; | 18 import '../../../pkg/path/lib/path.dart' as path; |
| 21 import '../../../pkg/unittest/lib/unittest.dart'; | 19 import '../../../pkg/unittest/lib/unittest.dart'; |
| 22 import '../../../pkg/http/lib/testing.dart'; | 20 import '../../../pkg/http/lib/testing.dart'; |
| 23 import '../../lib/file_system.dart' as fs; | 21 import '../../lib/file_system.dart' as fs; |
| 24 import '../../pub/entrypoint.dart'; | 22 import '../../pub/entrypoint.dart'; |
| 25 import '../../pub/git_source.dart'; | 23 import '../../pub/git_source.dart'; |
| 26 import '../../pub/hosted_source.dart'; | 24 import '../../pub/hosted_source.dart'; |
| 27 import '../../pub/http.dart'; | 25 import '../../pub/http.dart'; |
| 28 import '../../pub/io.dart'; | 26 import '../../pub/io.dart'; |
| 29 import '../../pub/sdk_source.dart'; | 27 import '../../pub/sdk_source.dart'; |
| 30 import '../../pub/system_cache.dart'; | 28 import '../../pub/system_cache.dart'; |
| 31 import '../../pub/utils.dart'; | 29 import '../../pub/utils.dart'; |
| 32 import '../../pub/validator.dart'; | 30 import '../../pub/validator.dart'; |
| 33 import '../../pub/yaml/yaml.dart'; | 31 import '../../pub/yaml/yaml.dart'; |
| 34 | 32 |
| 35 /** | 33 /// Creates a new [FileDescriptor] with [name] and [contents]. |
| 36 * Creates a new [FileDescriptor] with [name] and [contents]. | |
| 37 */ | |
| 38 FileDescriptor file(Pattern name, String contents) => | 34 FileDescriptor file(Pattern name, String contents) => |
| 39 new FileDescriptor(name, contents); | 35 new FileDescriptor(name, contents); |
| 40 | 36 |
| 41 /** | 37 /// Creates a new [DirectoryDescriptor] with [name] and [contents]. |
| 42 * Creates a new [DirectoryDescriptor] with [name] and [contents]. | |
| 43 */ | |
| 44 DirectoryDescriptor dir(Pattern name, [List<Descriptor> contents]) => | 38 DirectoryDescriptor dir(Pattern name, [List<Descriptor> contents]) => |
| 45 new DirectoryDescriptor(name, contents); | 39 new DirectoryDescriptor(name, contents); |
| 46 | 40 |
| 47 /** | 41 /// Creates a new [FutureDescriptor] wrapping [future]. |
| 48 * Creates a new [FutureDescriptor] wrapping [future]. | |
| 49 */ | |
| 50 FutureDescriptor async(Future<Descriptor> future) => | 42 FutureDescriptor async(Future<Descriptor> future) => |
| 51 new FutureDescriptor(future); | 43 new FutureDescriptor(future); |
| 52 | 44 |
| 53 /** | 45 /// Creates a new [GitRepoDescriptor] with [name] and [contents]. |
| 54 * Creates a new [GitRepoDescriptor] with [name] and [contents]. | |
| 55 */ | |
| 56 GitRepoDescriptor git(Pattern name, [List<Descriptor> contents]) => | 46 GitRepoDescriptor git(Pattern name, [List<Descriptor> contents]) => |
| 57 new GitRepoDescriptor(name, contents); | 47 new GitRepoDescriptor(name, contents); |
| 58 | 48 |
| 59 /** | 49 /// Creates a new [TarFileDescriptor] with [name] and [contents]. |
| 60 * Creates a new [TarFileDescriptor] with [name] and [contents]. | |
| 61 */ | |
| 62 TarFileDescriptor tar(Pattern name, [List<Descriptor> contents]) => | 50 TarFileDescriptor tar(Pattern name, [List<Descriptor> contents]) => |
| 63 new TarFileDescriptor(name, contents); | 51 new TarFileDescriptor(name, contents); |
| 64 | 52 |
| 65 /** | 53 /// Creates a new [NothingDescriptor] with [name]. |
| 66 * Creates a new [NothingDescriptor] with [name]. | |
| 67 */ | |
| 68 NothingDescriptor nothing(String name) => new NothingDescriptor(name); | 54 NothingDescriptor nothing(String name) => new NothingDescriptor(name); |
| 69 | 55 |
| 70 /** | 56 /// The current [HttpServer] created using [serve]. |
| 71 * The current [HttpServer] created using [serve]. | |
| 72 */ | |
| 73 var _server; | 57 var _server; |
| 74 | 58 |
| 75 /** The cached value for [_portCompleter]. */ | 59 /// The cached value for [_portCompleter]. |
| 76 Completer<int> _portCompleterCache; | 60 Completer<int> _portCompleterCache; |
| 77 | 61 |
| 78 /** The completer for [port]. */ | 62 /// The completer for [port]. |
| 79 Completer<int> get _portCompleter { | 63 Completer<int> get _portCompleter { |
| 80 if (_portCompleterCache != null) return _portCompleterCache; | 64 if (_portCompleterCache != null) return _portCompleterCache; |
| 81 _portCompleterCache = new Completer<int>(); | 65 _portCompleterCache = new Completer<int>(); |
| 82 _scheduleCleanup((_) { | 66 _scheduleCleanup((_) { |
| 83 _portCompleterCache = null; | 67 _portCompleterCache = null; |
| 84 }); | 68 }); |
| 85 return _portCompleterCache; | 69 return _portCompleterCache; |
| 86 } | 70 } |
| 87 | 71 |
| 88 /** | 72 /// A future that will complete to the port used for the current server. |
| 89 * A future that will complete to the port used for the current server. | |
| 90 */ | |
| 91 Future<int> get port => _portCompleter.future; | 73 Future<int> get port => _portCompleter.future; |
| 92 | 74 |
| 93 /** | 75 /// Creates an HTTP server to serve [contents] as static files. This server will |
| 94 * Creates an HTTP server to serve [contents] as static files. This server will | 76 /// exist only for the duration of the pub run. |
| 95 * exist only for the duration of the pub run. | 77 /// |
| 96 * | 78 /// Subsequent calls to [serve] will replace the previous server. |
| 97 * Subsequent calls to [serve] will replace the previous server. | |
| 98 */ | |
| 99 void serve([List<Descriptor> contents]) { | 79 void serve([List<Descriptor> contents]) { |
| 100 var baseDir = dir("serve-dir", contents); | 80 var baseDir = dir("serve-dir", contents); |
| 101 | 81 |
| 102 _schedule((_) { | 82 _schedule((_) { |
| 103 return _closeServer().transform((_) { | 83 return _closeServer().transform((_) { |
| 104 _server = new HttpServer(); | 84 _server = new HttpServer(); |
| 105 _server.defaultRequestHandler = (request, response) { | 85 _server.defaultRequestHandler = (request, response) { |
| 106 var path = request.uri.replaceFirst("/", "").split("/"); | 86 var path = request.uri.replaceFirst("/", "").split("/"); |
| 107 response.persistentConnection = false; | 87 response.persistentConnection = false; |
| 108 var stream; | 88 var stream; |
| (...skipping 22 matching lines...) Expand all Loading... |
| 131 }); | 111 }); |
| 132 }; | 112 }; |
| 133 _server.listen("127.0.0.1", 0); | 113 _server.listen("127.0.0.1", 0); |
| 134 _portCompleter.complete(_server.port); | 114 _portCompleter.complete(_server.port); |
| 135 _scheduleCleanup((_) => _closeServer()); | 115 _scheduleCleanup((_) => _closeServer()); |
| 136 return null; | 116 return null; |
| 137 }); | 117 }); |
| 138 }); | 118 }); |
| 139 } | 119 } |
| 140 | 120 |
| 141 /** | 121 /// Closes [_server]. Returns a [Future] that will complete after the [_server] |
| 142 * Closes [_server]. Returns a [Future] that will complete after the [_server] | 122 /// is closed. |
| 143 * is closed. | |
| 144 */ | |
| 145 Future _closeServer() { | 123 Future _closeServer() { |
| 146 if (_server == null) return new Future.immediate(null); | 124 if (_server == null) return new Future.immediate(null); |
| 147 _server.close(); | 125 _server.close(); |
| 148 _server = null; | 126 _server = null; |
| 149 _portCompleterCache = null; | 127 _portCompleterCache = null; |
| 150 // TODO(nweiz): Remove this once issue 4155 is fixed. Pumping the event loop | 128 // TODO(nweiz): Remove this once issue 4155 is fixed. Pumping the event loop |
| 151 // *seems* to be enough to ensure that the server is actually closed, but I'm | 129 // *seems* to be enough to ensure that the server is actually closed, but I'm |
| 152 // putting this at 10ms to be safe. | 130 // putting this at 10ms to be safe. |
| 153 return sleep(10); | 131 return sleep(10); |
| 154 } | 132 } |
| 155 | 133 |
| 156 /** | 134 /// The [DirectoryDescriptor] describing the server layout of packages that are |
| 157 * The [DirectoryDescriptor] describing the server layout of packages that are | 135 /// being served via [servePackages]. This is `null` if [servePackages] has not |
| 158 * being served via [servePackages]. This is `null` if [servePackages] has not | 136 /// yet been called for this test. |
| 159 * yet been called for this test. | |
| 160 */ | |
| 161 DirectoryDescriptor _servedPackageDir; | 137 DirectoryDescriptor _servedPackageDir; |
| 162 | 138 |
| 163 /** | 139 /// A map from package names to version numbers to YAML-serialized pubspecs for |
| 164 * A map from package names to version numbers to YAML-serialized pubspecs for | 140 /// those packages. This represents the packages currently being served by |
| 165 * those packages. This represents the packages currently being served by | 141 /// [servePackages], and is `null` if [servePackages] has not yet been called |
| 166 * [servePackages], and is `null` if [servePackages] has not yet been called for | 142 /// for this test. |
| 167 * this test. | |
| 168 */ | |
| 169 Map<String, Map<String, String>> _servedPackages; | 143 Map<String, Map<String, String>> _servedPackages; |
| 170 | 144 |
| 171 /** | 145 /// Creates an HTTP server that replicates the structure of pub.dartlang.org. |
| 172 * Creates an HTTP server that replicates the structure of pub.dartlang.org. | 146 /// [pubspecs] is a list of unserialized pubspecs representing the packages to |
| 173 * [pubspecs] is a list of unserialized pubspecs representing the packages to | 147 /// serve. |
| 174 * serve. | 148 /// |
| 175 * | 149 /// Subsequent calls to [servePackages] will add to the set of packages that |
| 176 * Subsequent calls to [servePackages] will add to the set of packages that are | 150 /// are being served. Previous packages will continue to be served. |
| 177 * being served. Previous packages will continue to be served. | |
| 178 */ | |
| 179 void servePackages(List<Map> pubspecs) { | 151 void servePackages(List<Map> pubspecs) { |
| 180 if (_servedPackages == null || _servedPackageDir == null) { | 152 if (_servedPackages == null || _servedPackageDir == null) { |
| 181 _servedPackages = <String, Map<String, String>>{}; | 153 _servedPackages = <String, Map<String, String>>{}; |
| 182 _servedPackageDir = dir('packages', []); | 154 _servedPackageDir = dir('packages', []); |
| 183 serve([_servedPackageDir]); | 155 serve([_servedPackageDir]); |
| 184 | 156 |
| 185 _scheduleCleanup((_) { | 157 _scheduleCleanup((_) { |
| 186 _servedPackages = null; | 158 _servedPackages = null; |
| 187 _servedPackageDir = null; | 159 _servedPackageDir = null; |
| 188 }); | 160 }); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 214 ]) | 186 ]) |
| 215 ]; | 187 ]; |
| 216 }))) | 188 }))) |
| 217 ]) | 189 ]) |
| 218 ]); | 190 ]); |
| 219 } | 191 } |
| 220 }); | 192 }); |
| 221 }); | 193 }); |
| 222 } | 194 } |
| 223 | 195 |
| 224 /** Converts [value] into a YAML string. */ | 196 /// Converts [value] into a YAML string. |
| 225 String yaml(value) => JSON.stringify(value); | 197 String yaml(value) => JSON.stringify(value); |
| 226 | 198 |
| 227 /// Describes a package that passes all validation. | 199 /// Describes a package that passes all validation. |
| 228 Descriptor get normalPackage => dir(appPath, [ | 200 Descriptor get normalPackage => dir(appPath, [ |
| 229 libPubspec("test_pkg", "1.0.0"), | 201 libPubspec("test_pkg", "1.0.0"), |
| 230 file("LICENSE", "Eh, do what you want."), | 202 file("LICENSE", "Eh, do what you want."), |
| 231 dir("lib", [ | 203 dir("lib", [ |
| 232 file("test_pkg.dart", "int i = 1;") | 204 file("test_pkg.dart", "int i = 1;") |
| 233 ]) | 205 ]) |
| 234 ]); | 206 ]); |
| 235 | 207 |
| 236 /** | 208 /// Describes a file named `pubspec.yaml` with the given YAML-serialized |
| 237 * Describes a file named `pubspec.yaml` with the given YAML-serialized | 209 /// [contents], which should be a serializable object. |
| 238 * [contents], which should be a serializable object. | 210 /// |
| 239 * | 211 /// [contents] may contain [Future]s that resolve to serializable objects, |
| 240 * [contents] may contain [Future]s that resolve to serializable objects, which | 212 /// which may in turn contain [Future]s recursively. |
| 241 * may in turn contain [Future]s recursively. | |
| 242 */ | |
| 243 Descriptor pubspec(Map contents) { | 213 Descriptor pubspec(Map contents) { |
| 244 return async(_awaitObject(contents).transform((resolvedContents) => | 214 return async(_awaitObject(contents).transform((resolvedContents) => |
| 245 file("pubspec.yaml", yaml(resolvedContents)))); | 215 file("pubspec.yaml", yaml(resolvedContents)))); |
| 246 } | 216 } |
| 247 | 217 |
| 248 /** | 218 /// Describes a file named `pubspec.yaml` for an application package with the |
| 249 * Describes a file named `pubspec.yaml` for an application package with the | 219 /// given [dependencies]. |
| 250 * given [dependencies]. | |
| 251 */ | |
| 252 Descriptor appPubspec(List dependencies) { | 220 Descriptor appPubspec(List dependencies) { |
| 253 return pubspec({ | 221 return pubspec({ |
| 254 "name": "myapp", | 222 "name": "myapp", |
| 255 "dependencies": _dependencyListToMap(dependencies) | 223 "dependencies": _dependencyListToMap(dependencies) |
| 256 }); | 224 }); |
| 257 } | 225 } |
| 258 | 226 |
| 259 /** | 227 /// Describes a file named `pubspec.yaml` for a library package with the given |
| 260 * Describes a file named `pubspec.yaml` for a library package with the given | 228 /// [name], [version], and [dependencies]. |
| 261 * [name], [version], and [dependencies]. | |
| 262 */ | |
| 263 Descriptor libPubspec(String name, String version, [List dependencies]) => | 229 Descriptor libPubspec(String name, String version, [List dependencies]) => |
| 264 pubspec(package(name, version, dependencies)); | 230 pubspec(package(name, version, dependencies)); |
| 265 | 231 |
| 266 /** | 232 /// Describes a directory named `lib` containing a single dart file named |
| 267 * Describes a directory named `lib` containing a single dart file named | 233 /// `<name>.dart` that contains a line of Dart code. |
| 268 * `<name>.dart` that contains a line of Dart code. | |
| 269 */ | |
| 270 Descriptor libDir(String name, [String code]) { | 234 Descriptor libDir(String name, [String code]) { |
| 271 // Default to printing the name if no other code was given. | 235 // Default to printing the name if no other code was given. |
| 272 if (code == null) { | 236 if (code == null) { |
| 273 code = name; | 237 code = name; |
| 274 } | 238 } |
| 275 | 239 |
| 276 return dir("lib", [ | 240 return dir("lib", [ |
| 277 file("$name.dart", 'main() => "$code";') | 241 file("$name.dart", 'main() => "$code";') |
| 278 ]); | 242 ]); |
| 279 } | 243 } |
| 280 | 244 |
| 281 /** | 245 /// Describes a map representing a library package with the given [name], |
| 282 * Describes a map representing a library package with the given [name], | 246 /// [version], and [dependencies]. |
| 283 * [version], and [dependencies]. | |
| 284 */ | |
| 285 Map package(String name, String version, [List dependencies]) { | 247 Map package(String name, String version, [List dependencies]) { |
| 286 var package = { | 248 var package = { |
| 287 "name": name, | 249 "name": name, |
| 288 "version": version, | 250 "version": version, |
| 289 "author": "Nathan Weizenbaum <nweiz@google.com>", | 251 "author": "Nathan Weizenbaum <nweiz@google.com>", |
| 290 "homepage": "http://pub.dartlang.org", | 252 "homepage": "http://pub.dartlang.org", |
| 291 "description": "A package, I guess." | 253 "description": "A package, I guess." |
| 292 }; | 254 }; |
| 293 if (dependencies != null) { | 255 if (dependencies != null) { |
| 294 package["dependencies"] = _dependencyListToMap(dependencies); | 256 package["dependencies"] = _dependencyListToMap(dependencies); |
| 295 } | 257 } |
| 296 return package; | 258 return package; |
| 297 } | 259 } |
| 298 | 260 |
| 299 /** | 261 /// Describes a map representing a dependency on a package in the package |
| 300 * Describes a map representing a dependency on a package in the package | 262 /// repository. |
| 301 * repository. | |
| 302 */ | |
| 303 Map dependency(String name, [String versionConstraint]) { | 263 Map dependency(String name, [String versionConstraint]) { |
| 304 var url = port.transform((p) => "http://localhost:$p"); | 264 var url = port.transform((p) => "http://localhost:$p"); |
| 305 var dependency = {"hosted": {"name": name, "url": url}}; | 265 var dependency = {"hosted": {"name": name, "url": url}}; |
| 306 if (versionConstraint != null) dependency["version"] = versionConstraint; | 266 if (versionConstraint != null) dependency["version"] = versionConstraint; |
| 307 return dependency; | 267 return dependency; |
| 308 } | 268 } |
| 309 | 269 |
| 310 /** | 270 /// Describes a directory for a package installed from the mock package server. |
| 311 * Describes a directory for a package installed from the mock package server. | 271 /// This directory is of the form found in the global package cache. |
| 312 * This directory is of the form found in the global package cache. | |
| 313 */ | |
| 314 DirectoryDescriptor packageCacheDir(String name, String version) { | 272 DirectoryDescriptor packageCacheDir(String name, String version) { |
| 315 return dir("$name-$version", [ | 273 return dir("$name-$version", [ |
| 316 libDir(name, '$name $version') | 274 libDir(name, '$name $version') |
| 317 ]); | 275 ]); |
| 318 } | 276 } |
| 319 | 277 |
| 320 /** | 278 /// Describes a directory for a Git package. This directory is of the form |
| 321 * Describes a directory for a Git package. This directory is of the form found | 279 /// found in the revision cache of the global package cache. |
| 322 * in the revision cache of the global package cache. | |
| 323 */ | |
| 324 DirectoryDescriptor gitPackageRevisionCacheDir(String name, [int modifier]) { | 280 DirectoryDescriptor gitPackageRevisionCacheDir(String name, [int modifier]) { |
| 325 var value = name; | 281 var value = name; |
| 326 if (modifier != null) value = "$name $modifier"; | 282 if (modifier != null) value = "$name $modifier"; |
| 327 return dir(new RegExp("$name${r'-[a-f0-9]+'}"), [ | 283 return dir(new RegExp("$name${r'-[a-f0-9]+'}"), [ |
| 328 libDir(name, value) | 284 libDir(name, value) |
| 329 ]); | 285 ]); |
| 330 } | 286 } |
| 331 | 287 |
| 332 /** | 288 /// Describes a directory for a Git package. This directory is of the form |
| 333 * Describes a directory for a Git package. This directory is of the form found | 289 /// found in the repo cache of the global package cache. |
| 334 * in the repo cache of the global package cache. | |
| 335 */ | |
| 336 DirectoryDescriptor gitPackageRepoCacheDir(String name) { | 290 DirectoryDescriptor gitPackageRepoCacheDir(String name) { |
| 337 return dir(new RegExp("$name${r'-[a-f0-9]+'}"), [ | 291 return dir(new RegExp("$name${r'-[a-f0-9]+'}"), [ |
| 338 dir('hooks'), | 292 dir('hooks'), |
| 339 dir('info'), | 293 dir('info'), |
| 340 dir('objects'), | 294 dir('objects'), |
| 341 dir('refs') | 295 dir('refs') |
| 342 ]); | 296 ]); |
| 343 } | 297 } |
| 344 | 298 |
| 345 /** | 299 /// Describes the `packages/` directory containing all the given [packages], |
| 346 * Describes the `packages/` directory containing all the given [packages], | 300 /// which should be name/version pairs. The packages will be validated against |
| 347 * which should be name/version pairs. The packages will be validated against | 301 /// the format produced by the mock package server. |
| 348 * the format produced by the mock package server. | 302 /// |
| 349 * | 303 /// A package with a null version should not be installed. |
| 350 * A package with a null version should not be installed. | |
| 351 */ | |
| 352 DirectoryDescriptor packagesDir(Map<String, String> packages) { | 304 DirectoryDescriptor packagesDir(Map<String, String> packages) { |
| 353 var contents = <Descriptor>[]; | 305 var contents = <Descriptor>[]; |
| 354 packages.forEach((name, version) { | 306 packages.forEach((name, version) { |
| 355 if (version == null) { | 307 if (version == null) { |
| 356 contents.add(nothing(name)); | 308 contents.add(nothing(name)); |
| 357 } else { | 309 } else { |
| 358 contents.add(dir(name, [ | 310 contents.add(dir(name, [ |
| 359 file("$name.dart", 'main() => "$name $version";') | 311 file("$name.dart", 'main() => "$name $version";') |
| 360 ])); | 312 ])); |
| 361 } | 313 } |
| 362 }); | 314 }); |
| 363 return dir(packagesPath, contents); | 315 return dir(packagesPath, contents); |
| 364 } | 316 } |
| 365 | 317 |
| 366 /** | 318 /// Describes the global package cache directory containing all the given |
| 367 * Describes the global package cache directory containing all the given | 319 /// [packages], which should be name/version pairs. The packages will be |
| 368 * [packages], which should be name/version pairs. The packages will be | 320 /// validated against the format produced by the mock package server. |
| 369 * validated against the format produced by the mock package server. | 321 /// |
| 370 * | 322 /// A package's value may also be a list of versions, in which case all |
| 371 * A package's value may also be a list of versions, in which case all versions | 323 /// versions are expected to be installed. |
| 372 * are expected to be installed. | |
| 373 */ | |
| 374 DirectoryDescriptor cacheDir(Map packages) { | 324 DirectoryDescriptor cacheDir(Map packages) { |
| 375 var contents = <Descriptor>[]; | 325 var contents = <Descriptor>[]; |
| 376 packages.forEach((name, versions) { | 326 packages.forEach((name, versions) { |
| 377 if (versions is! List) versions = [versions]; | 327 if (versions is! List) versions = [versions]; |
| 378 for (var version in versions) { | 328 for (var version in versions) { |
| 379 contents.add(packageCacheDir(name, version)); | 329 contents.add(packageCacheDir(name, version)); |
| 380 } | 330 } |
| 381 }); | 331 }); |
| 382 return dir(cachePath, [ | 332 return dir(cachePath, [ |
| 383 dir('hosted', [ | 333 dir('hosted', [ |
| (...skipping 15 matching lines...) Expand all Loading... |
| 399 file('credentials.json', new oauth2.Credentials( | 349 file('credentials.json', new oauth2.Credentials( |
| 400 accessToken, | 350 accessToken, |
| 401 refreshToken, | 351 refreshToken, |
| 402 url.resolve('/token'), | 352 url.resolve('/token'), |
| 403 ['https://www.googleapis.com/auth/userinfo.email'], | 353 ['https://www.googleapis.com/auth/userinfo.email'], |
| 404 expiration).toJson()) | 354 expiration).toJson()) |
| 405 ]); | 355 ]); |
| 406 })); | 356 })); |
| 407 } | 357 } |
| 408 | 358 |
| 409 /** | 359 /// Describes the application directory, containing only a pubspec specifying |
| 410 * Describes the application directory, containing only a pubspec specifying the | 360 /// the given [dependencies]. |
| 411 * given [dependencies]. | |
| 412 */ | |
| 413 DirectoryDescriptor appDir(List dependencies) => | 361 DirectoryDescriptor appDir(List dependencies) => |
| 414 dir(appPath, [appPubspec(dependencies)]); | 362 dir(appPath, [appPubspec(dependencies)]); |
| 415 | 363 |
| 416 /** | 364 /// Converts a list of dependencies as passed to [package] into a hash as used |
| 417 * Converts a list of dependencies as passed to [package] into a hash as used in | 365 /// in a pubspec. |
| 418 * a pubspec. | |
| 419 */ | |
| 420 Future<Map> _dependencyListToMap(List<Map> dependencies) { | 366 Future<Map> _dependencyListToMap(List<Map> dependencies) { |
| 421 return _awaitObject(dependencies).transform((resolvedDependencies) { | 367 return _awaitObject(dependencies).transform((resolvedDependencies) { |
| 422 var result = <String, Map>{}; | 368 var result = <String, Map>{}; |
| 423 for (var dependency in resolvedDependencies) { | 369 for (var dependency in resolvedDependencies) { |
| 424 var keys = dependency.keys.filter((key) => key != "version"); | 370 var keys = dependency.keys.filter((key) => key != "version"); |
| 425 var sourceName = only(keys); | 371 var sourceName = only(keys); |
| 426 var source; | 372 var source; |
| 427 switch (sourceName) { | 373 switch (sourceName) { |
| 428 case "git": | 374 case "git": |
| 429 source = new GitSource(); | 375 source = new GitSource(); |
| (...skipping 24 matching lines...) Expand all Loading... |
| 454 case "hosted": | 400 case "hosted": |
| 455 if (description is String) return description; | 401 if (description is String) return description; |
| 456 return description['name']; | 402 return description['name']; |
| 457 case "sdk": | 403 case "sdk": |
| 458 return description; | 404 return description; |
| 459 default: | 405 default: |
| 460 return description; | 406 return description; |
| 461 } | 407 } |
| 462 } | 408 } |
| 463 | 409 |
| 464 /** | 410 /// The path of the package cache directory used for tests. Relative to the |
| 465 * The path of the package cache directory used for tests. Relative to the | 411 /// sandbox directory. |
| 466 * sandbox directory. | |
| 467 */ | |
| 468 final String cachePath = "cache"; | 412 final String cachePath = "cache"; |
| 469 | 413 |
| 470 /** | 414 /// The path of the mock SDK directory used for tests. Relative to the sandbox |
| 471 * The path of the mock SDK directory used for tests. Relative to the sandbox | 415 /// directory. |
| 472 * directory. | |
| 473 */ | |
| 474 final String sdkPath = "sdk"; | 416 final String sdkPath = "sdk"; |
| 475 | 417 |
| 476 /** | 418 /// The path of the mock app directory used for tests. Relative to the sandbox |
| 477 * The path of the mock app directory used for tests. Relative to the sandbox | 419 /// directory. |
| 478 * directory. | |
| 479 */ | |
| 480 final String appPath = "myapp"; | 420 final String appPath = "myapp"; |
| 481 | 421 |
| 482 /** | 422 /// The path of the packages directory in the mock app used for tests. Relative |
| 483 * The path of the packages directory in the mock app used for tests. Relative | 423 /// to the sandbox directory. |
| 484 * to the sandbox directory. | |
| 485 */ | |
| 486 final String packagesPath = "$appPath/packages"; | 424 final String packagesPath = "$appPath/packages"; |
| 487 | 425 |
| 488 /** | 426 /// The type for callbacks that will be fired during [runPub]. Takes the |
| 489 * The type for callbacks that will be fired during [runPub]. Takes the sandbox | 427 /// sandbox directory as a parameter. |
| 490 * directory as a parameter. | |
| 491 */ | |
| 492 typedef Future _ScheduledEvent(Directory parentDir); | 428 typedef Future _ScheduledEvent(Directory parentDir); |
| 493 | 429 |
| 494 /** | 430 /// The list of events that are scheduled to run as part of the test case. |
| 495 * The list of events that are scheduled to run as part of the test case. | |
| 496 */ | |
| 497 List<_ScheduledEvent> _scheduled; | 431 List<_ScheduledEvent> _scheduled; |
| 498 | 432 |
| 499 /** | 433 /// The list of events that are scheduled to run after the test case, even if |
| 500 * The list of events that are scheduled to run after the test case, even if it | 434 /// it failed. |
| 501 * failed. | |
| 502 */ | |
| 503 List<_ScheduledEvent> _scheduledCleanup; | 435 List<_ScheduledEvent> _scheduledCleanup; |
| 504 | 436 |
| 505 /// The list of events that are scheduled to run after the test case only if it | 437 /// The list of events that are scheduled to run after the test case only if it |
| 506 /// failed. | 438 /// failed. |
| 507 List<_ScheduledEvent> _scheduledOnException; | 439 List<_ScheduledEvent> _scheduledOnException; |
| 508 | 440 |
| 509 /** | 441 /// Set to true when the current batch of scheduled events should be aborted. |
| 510 * Set to true when the current batch of scheduled events should be aborted. | |
| 511 */ | |
| 512 bool _abortScheduled = false; | 442 bool _abortScheduled = false; |
| 513 | 443 |
| 514 /// The time (in milliseconds) to wait for the entire scheduled test to | 444 /// The time (in milliseconds) to wait for the entire scheduled test to |
| 515 /// complete. | 445 /// complete. |
| 516 final _TIMEOUT = 30000; | 446 final _TIMEOUT = 30000; |
| 517 | 447 |
| 518 /** | 448 /// Runs all the scheduled events for a test case. This should only be called |
| 519 * Runs all the scheduled events for a test case. This should only be called | 449 /// once per test case. |
| 520 * once per test case. | |
| 521 */ | |
| 522 void run() { | 450 void run() { |
| 523 var createdSandboxDir; | 451 var createdSandboxDir; |
| 524 | 452 |
| 525 var asyncDone = expectAsync0(() {}); | 453 var asyncDone = expectAsync0(() {}); |
| 526 | 454 |
| 527 Future cleanup() { | 455 Future cleanup() { |
| 528 return _runScheduled(createdSandboxDir, _scheduledCleanup).chain((_) { | 456 return _runScheduled(createdSandboxDir, _scheduledCleanup).chain((_) { |
| 529 _scheduled = null; | 457 _scheduled = null; |
| 530 _scheduledCleanup = null; | 458 _scheduledCleanup = null; |
| 531 _scheduledOnException = null; | 459 _scheduledOnException = null; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 553 }); | 481 }); |
| 554 subFuture.then((_) => registerException(error, future.stackTrace)); | 482 subFuture.then((_) => registerException(error, future.stackTrace)); |
| 555 return true; | 483 return true; |
| 556 }); | 484 }); |
| 557 | 485 |
| 558 timeout(future, _TIMEOUT, 'waiting for a test to complete') | 486 timeout(future, _TIMEOUT, 'waiting for a test to complete') |
| 559 .chain((_) => cleanup()) | 487 .chain((_) => cleanup()) |
| 560 .then((_) => asyncDone()); | 488 .then((_) => asyncDone()); |
| 561 } | 489 } |
| 562 | 490 |
| 563 /// Get the path to the root "util/test/pub" directory containing the pub tests. | 491 /// Get the path to the root "util/test/pub" directory containing the pub |
| 492 /// tests. |
| 564 String get testDirectory { | 493 String get testDirectory { |
| 565 var dir = new Options().script; | 494 var dir = new Options().script; |
| 566 while (basename(dir) != 'pub') dir = dirname(dir); | 495 while (basename(dir) != 'pub') dir = dirname(dir); |
| 567 | 496 |
| 568 return getFullPath(dir); | 497 return getFullPath(dir); |
| 569 } | 498 } |
| 570 | 499 |
| 571 /** | 500 /// Schedules a call to the Pub command-line utility. Runs Pub with [args] and |
| 572 * Schedules a call to the Pub command-line utility. Runs Pub with [args] and | 501 /// validates that its results match [output], [error], and [exitCode]. |
| 573 * validates that its results match [output], [error], and [exitCode]. | |
| 574 */ | |
| 575 void schedulePub({List args, Pattern output, Pattern error, | 502 void schedulePub({List args, Pattern output, Pattern error, |
| 576 Future<Uri> tokenEndpoint, int exitCode: 0}) { | 503 Future<Uri> tokenEndpoint, int exitCode: 0}) { |
| 577 _schedule((sandboxDir) { | 504 _schedule((sandboxDir) { |
| 578 return _doPub(runProcess, sandboxDir, args, tokenEndpoint) | 505 return _doPub(runProcess, sandboxDir, args, tokenEndpoint) |
| 579 .transform((result) { | 506 .transform((result) { |
| 580 var failures = []; | 507 var failures = []; |
| 581 | 508 |
| 582 _validateOutput(failures, 'stdout', output, result.stdout); | 509 _validateOutput(failures, 'stdout', output, result.stdout); |
| 583 _validateOutput(failures, 'stderr', error, result.stderr); | 510 _validateOutput(failures, 'stderr', error, result.stderr); |
| 584 | 511 |
| (...skipping 102 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 687 | 614 |
| 688 if (tokenEndpoint != null) { | 615 if (tokenEndpoint != null) { |
| 689 environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint.toString(); | 616 environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint.toString(); |
| 690 } | 617 } |
| 691 | 618 |
| 692 return fn(dartBin, dartArgs, workingDir: pathInSandbox(appPath), | 619 return fn(dartBin, dartArgs, workingDir: pathInSandbox(appPath), |
| 693 environment: environment); | 620 environment: environment); |
| 694 }); | 621 }); |
| 695 } | 622 } |
| 696 | 623 |
| 697 /** | 624 /// Skips the current test if Git is not installed. This validates that the |
| 698 * Skips the current test if Git is not installed. This validates that the | 625 /// current test is running on a buildbot in which case we expect git to be |
| 699 * current test is running on a buildbot in which case we expect git to be | 626 /// installed. If we are not running on the buildbot, we will instead see if |
| 700 * installed. If we are not running on the buildbot, we will instead see if git | 627 /// git is installed and skip the test if not. This way, users don't need to |
| 701 * is installed and skip the test if not. This way, users don't need to have git | 628 /// have git installed to run the tests locally (unless they actually care |
| 702 * installed to run the tests locally (unless they actually care about the pub | 629 /// about the pub git tests). |
| 703 * git tests). | |
| 704 */ | |
| 705 void ensureGit() { | 630 void ensureGit() { |
| 706 _schedule((_) { | 631 _schedule((_) { |
| 707 return isGitInstalled.transform((installed) { | 632 return isGitInstalled.transform((installed) { |
| 708 if (!installed && | 633 if (!installed && |
| 709 !Platform.environment.containsKey('BUILDBOT_BUILDERNAME')) { | 634 !Platform.environment.containsKey('BUILDBOT_BUILDERNAME')) { |
| 710 _abortScheduled = true; | 635 _abortScheduled = true; |
| 711 } | 636 } |
| 712 return null; | 637 return null; |
| 713 }); | 638 }); |
| 714 }); | 639 }); |
| (...skipping 28 matching lines...) Expand all Loading... |
| 743 if (future != null) { | 668 if (future != null) { |
| 744 return future.chain(runNextEvent); | 669 return future.chain(runNextEvent); |
| 745 } else { | 670 } else { |
| 746 return runNextEvent(null); | 671 return runNextEvent(null); |
| 747 } | 672 } |
| 748 } | 673 } |
| 749 | 674 |
| 750 return runNextEvent(null); | 675 return runNextEvent(null); |
| 751 } | 676 } |
| 752 | 677 |
| 753 /** | 678 /// Compares the [actual] output from running pub with [expected]. For [String] |
| 754 * Compares the [actual] output from running pub with [expected]. For [String] | 679 /// patterns, ignores leading and trailing whitespace differences and tries to |
| 755 * patterns, ignores leading and trailing whitespace differences and tries to | 680 /// report the offending difference in a nice way. For other [Pattern]s, just |
| 756 * report the offending difference in a nice way. For other [Pattern]s, just | 681 /// reports whether the output contained the pattern. |
| 757 * reports whether the output contained the pattern. | |
| 758 */ | |
| 759 void _validateOutput(List<String> failures, String pipe, Pattern expected, | 682 void _validateOutput(List<String> failures, String pipe, Pattern expected, |
| 760 List<String> actual) { | 683 List<String> actual) { |
| 761 if (expected == null) return; | 684 if (expected == null) return; |
| 762 | 685 |
| 763 if (expected is RegExp) { | 686 if (expected is RegExp) { |
| 764 _validateOutputRegex(failures, pipe, expected, actual); | 687 _validateOutputRegex(failures, pipe, expected, actual); |
| 765 } else { | 688 } else { |
| 766 _validateOutputString(failures, pipe, expected, actual); | 689 _validateOutputString(failures, pipe, expected, actual); |
| 767 } | 690 } |
| 768 } | 691 } |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 820 | 743 |
| 821 // If any lines mismatched, show the expected and actual. | 744 // If any lines mismatched, show the expected and actual. |
| 822 if (failed) { | 745 if (failed) { |
| 823 failures.add('Expected $pipe:'); | 746 failures.add('Expected $pipe:'); |
| 824 failures.addAll(expected.map((line) => '| $line')); | 747 failures.addAll(expected.map((line) => '| $line')); |
| 825 failures.add('Got:'); | 748 failures.add('Got:'); |
| 826 failures.addAll(results); | 749 failures.addAll(results); |
| 827 } | 750 } |
| 828 } | 751 } |
| 829 | 752 |
| 830 /** | 753 /// Base class for [FileDescriptor] and [DirectoryDescriptor] so that a |
| 831 * Base class for [FileDescriptor] and [DirectoryDescriptor] so that a | 754 /// directory can contain a heterogeneous collection of files and |
| 832 * directory can contain a heterogeneous collection of files and | 755 /// subdirectories. |
| 833 * subdirectories. | |
| 834 */ | |
| 835 abstract class Descriptor { | 756 abstract class Descriptor { |
| 836 /** | 757 /// The name of this file or directory. This must be a [String] if the file |
| 837 * The name of this file or directory. This must be a [String] if the fiel or | 758 /// or directory is going to be created. |
| 838 * directory is going to be created. | |
| 839 */ | |
| 840 final Pattern name; | 759 final Pattern name; |
| 841 | 760 |
| 842 Descriptor(this.name); | 761 Descriptor(this.name); |
| 843 | 762 |
| 844 /** | 763 /// Creates the file or directory within [dir]. Returns a [Future] that is |
| 845 * Creates the file or directory within [dir]. Returns a [Future] that is | 764 /// completed after the creation is done. |
| 846 * completed after the creation is done. | |
| 847 */ | |
| 848 Future create(dir); | 765 Future create(dir); |
| 849 | 766 |
| 850 /** | 767 /// Validates that this descriptor correctly matches the corresponding file |
| 851 * Validates that this descriptor correctly matches the corresponding file | 768 /// system entry within [dir]. Returns a [Future] that completes to `null` if |
| 852 * system entry within [dir]. Returns a [Future] that completes to `null` if | 769 /// the entry is valid, or throws an error if it failed. |
| 853 * the entry is valid, or throws an error if it failed. | |
| 854 */ | |
| 855 Future validate(String dir); | 770 Future validate(String dir); |
| 856 | 771 |
| 857 /** | 772 /// Deletes the file or directory within [dir]. Returns a [Future] that is |
| 858 * Deletes the file or directory within [dir]. Returns a [Future] that is | 773 /// completed after the deletion is done. |
| 859 * completed after the deletion is done. | |
| 860 */ | |
| 861 Future delete(String dir); | 774 Future delete(String dir); |
| 862 | 775 |
| 863 /** | 776 /// Loads the file at [path] from within this descriptor. If [path] is empty, |
| 864 * Loads the file at [path] from within this descriptor. If [path] is empty, | 777 /// loads the contents of the descriptor itself. |
| 865 * loads the contents of the descriptor itself. | |
| 866 */ | |
| 867 InputStream load(List<String> path); | 778 InputStream load(List<String> path); |
| 868 | 779 |
| 869 /** | 780 /// Schedules the directory to be created before Pub is run with [runPub]. |
| 870 * Schedules the directory to be created before Pub is run with [runPub]. The | 781 /// The directory will be created relative to the sandbox directory. |
| 871 * directory will be created relative to the sandbox directory. | |
| 872 */ | |
| 873 // TODO(nweiz): Use implicit closurization once issue 2984 is fixed. | 782 // TODO(nweiz): Use implicit closurization once issue 2984 is fixed. |
| 874 void scheduleCreate() => _schedule((dir) => this.create(dir)); | 783 void scheduleCreate() => _schedule((dir) => this.create(dir)); |
| 875 | 784 |
| 876 /** | 785 /// Schedules the file or directory to be deleted recursively. |
| 877 * Schedules the file or directory to be deleted recursively. | |
| 878 */ | |
| 879 void scheduleDelete() => _schedule((dir) => this.delete(dir)); | 786 void scheduleDelete() => _schedule((dir) => this.delete(dir)); |
| 880 | 787 |
| 881 /** | 788 /// Schedules the directory to be validated after Pub is run with [runPub]. |
| 882 * Schedules the directory to be validated after Pub is run with [runPub]. The | 789 /// The directory will be validated relative to the sandbox directory. |
| 883 * directory will be validated relative to the sandbox directory. | |
| 884 */ | |
| 885 void scheduleValidate() => _schedule((parentDir) => validate(parentDir.path)); | 790 void scheduleValidate() => _schedule((parentDir) => validate(parentDir.path)); |
| 886 | 791 |
| 887 /** | 792 /// Asserts that the name of the descriptor is a [String] and returns it. |
| 888 * Asserts that the name of the descriptor is a [String] and returns it. | |
| 889 */ | |
| 890 String get _stringName { | 793 String get _stringName { |
| 891 if (name is String) return name; | 794 if (name is String) return name; |
| 892 throw 'Pattern $name must be a string.'; | 795 throw 'Pattern $name must be a string.'; |
| 893 } | 796 } |
| 894 | 797 |
| 895 /** | 798 /// Validates that at least one file in [dir] matching [name] is valid |
| 896 * Validates that at least one file in [dir] matching [name] is valid | 799 /// according to [validate]. [validate] should complete to an exception if |
| 897 * according to [validate]. [validate] should complete to an exception if the | 800 /// the input path is invalid. |
| 898 * input path is invalid. | |
| 899 */ | |
| 900 Future _validateOneMatch(String dir, Future validate(String path)) { | 801 Future _validateOneMatch(String dir, Future validate(String path)) { |
| 901 // Special-case strings to support multi-level names like "myapp/packages". | 802 // Special-case strings to support multi-level names like "myapp/packages". |
| 902 if (name is String) { | 803 if (name is String) { |
| 903 var path = join(dir, name); | 804 var path = join(dir, name); |
| 904 return exists(path).chain((exists) { | 805 return exists(path).chain((exists) { |
| 905 if (!exists) Expect.fail('File $name in $dir not found.'); | 806 if (!exists) Expect.fail('File $name in $dir not found.'); |
| 906 return validate(path); | 807 return validate(path); |
| 907 }); | 808 }); |
| 908 } | 809 } |
| 909 | 810 |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 953 future.then((_) { | 854 future.then((_) { |
| 954 successes++; | 855 successes++; |
| 955 checkComplete(); | 856 checkComplete(); |
| 956 }); | 857 }); |
| 957 } | 858 } |
| 958 return completer.future; | 859 return completer.future; |
| 959 }); | 860 }); |
| 960 } | 861 } |
| 961 } | 862 } |
| 962 | 863 |
| 963 /** | 864 /// Describes a file. These are used both for setting up an expected directory |
| 964 * Describes a file. These are used both for setting up an expected directory | 865 /// tree before running a test, and for validating that the file system matches |
| 965 * tree before running a test, and for validating that the file system matches | 866 /// some expectations after running it. |
| 966 * some expectations after running it. | |
| 967 */ | |
| 968 class FileDescriptor extends Descriptor { | 867 class FileDescriptor extends Descriptor { |
| 969 /** | 868 /// The text contents of the file. |
| 970 * The text contents of the file. | |
| 971 */ | |
| 972 final String contents; | 869 final String contents; |
| 973 | 870 |
| 974 FileDescriptor(Pattern name, this.contents) : super(name); | 871 FileDescriptor(Pattern name, this.contents) : super(name); |
| 975 | 872 |
| 976 /** | 873 /// Creates the file within [dir]. Returns a [Future] that is completed after |
| 977 * Creates the file within [dir]. Returns a [Future] that is completed after | 874 /// the creation is done. |
| 978 * the creation is done. | |
| 979 */ | |
| 980 Future<File> create(dir) { | 875 Future<File> create(dir) { |
| 981 return writeTextFile(join(dir, _stringName), contents); | 876 return writeTextFile(join(dir, _stringName), contents); |
| 982 } | 877 } |
| 983 | 878 |
| 984 /** | 879 /// Deletes the file within [dir]. Returns a [Future] that is completed after |
| 985 * Deletes the file within [dir]. Returns a [Future] that is completed after | 880 /// the deletion is done. |
| 986 * the deletion is done. | |
| 987 */ | |
| 988 Future delete(dir) { | 881 Future delete(dir) { |
| 989 return deleteFile(join(dir, _stringName)); | 882 return deleteFile(join(dir, _stringName)); |
| 990 } | 883 } |
| 991 | 884 |
| 992 /** | 885 /// Validates that this file correctly matches the actual file at [path]. |
| 993 * Validates that this file correctly matches the actual file at [path]. | |
| 994 */ | |
| 995 Future validate(String path) { | 886 Future validate(String path) { |
| 996 return _validateOneMatch(path, (file) { | 887 return _validateOneMatch(path, (file) { |
| 997 return readTextFile(file).transform((text) { | 888 return readTextFile(file).transform((text) { |
| 998 if (text == contents) return null; | 889 if (text == contents) return null; |
| 999 | 890 |
| 1000 Expect.fail('File $file should contain:\n\n$contents\n\n' | 891 Expect.fail('File $file should contain:\n\n$contents\n\n' |
| 1001 'but contained:\n\n$text'); | 892 'but contained:\n\n$text'); |
| 1002 }); | 893 }); |
| 1003 }); | 894 }); |
| 1004 } | 895 } |
| 1005 | 896 |
| 1006 /** | 897 /// Loads the contents of the file. |
| 1007 * Loads the contents of the file. | |
| 1008 */ | |
| 1009 InputStream load(List<String> path) { | 898 InputStream load(List<String> path) { |
| 1010 if (!path.isEmpty) { | 899 if (!path.isEmpty) { |
| 1011 var joinedPath = Strings.join(path, '/'); | 900 var joinedPath = Strings.join(path, '/'); |
| 1012 throw "Can't load $joinedPath from within $name: not a directory."; | 901 throw "Can't load $joinedPath from within $name: not a directory."; |
| 1013 } | 902 } |
| 1014 | 903 |
| 1015 var stream = new ListInputStream(); | 904 var stream = new ListInputStream(); |
| 1016 stream.write(contents.charCodes); | 905 stream.write(contents.charCodes); |
| 1017 stream.markEndOfStream(); | 906 stream.markEndOfStream(); |
| 1018 return stream; | 907 return stream; |
| 1019 } | 908 } |
| 1020 } | 909 } |
| 1021 | 910 |
| 1022 /** | 911 /// Describes a directory and its contents. These are used both for setting up |
| 1023 * Describes a directory and its contents. These are used both for setting up | 912 /// an expected directory tree before running a test, and for validating that |
| 1024 * an expected directory tree before running a test, and for validating that | 913 /// the file system matches some expectations after running it. |
| 1025 * the file system matches some expectations after running it. | |
| 1026 */ | |
| 1027 class DirectoryDescriptor extends Descriptor { | 914 class DirectoryDescriptor extends Descriptor { |
| 1028 /** | 915 /// The files and directories contained in this directory. |
| 1029 * The files and directories contained in this directory. | |
| 1030 */ | |
| 1031 final List<Descriptor> contents; | 916 final List<Descriptor> contents; |
| 1032 | 917 |
| 1033 DirectoryDescriptor(Pattern name, List<Descriptor> contents) | 918 DirectoryDescriptor(Pattern name, List<Descriptor> contents) |
| 1034 : this.contents = contents == null ? <Descriptor>[] : contents, | 919 : this.contents = contents == null ? <Descriptor>[] : contents, |
| 1035 super(name); | 920 super(name); |
| 1036 | 921 |
| 1037 /** | 922 /// Creates the file within [dir]. Returns a [Future] that is completed after |
| 1038 * Creates the file within [dir]. Returns a [Future] that is completed after | 923 /// the creation is done. |
| 1039 * the creation is done. | |
| 1040 */ | |
| 1041 Future<Directory> create(parentDir) { | 924 Future<Directory> create(parentDir) { |
| 1042 // Create the directory. | 925 // Create the directory. |
| 1043 return ensureDir(join(parentDir, _stringName)).chain((dir) { | 926 return ensureDir(join(parentDir, _stringName)).chain((dir) { |
| 1044 if (contents == null) return new Future<Directory>.immediate(dir); | 927 if (contents == null) return new Future<Directory>.immediate(dir); |
| 1045 | 928 |
| 1046 // Recursively create all of its children. | 929 // Recursively create all of its children. |
| 1047 final childFutures = contents.map((child) => child.create(dir)); | 930 final childFutures = contents.map((child) => child.create(dir)); |
| 1048 // Only complete once all of the children have been created too. | 931 // Only complete once all of the children have been created too. |
| 1049 return Futures.wait(childFutures).transform((_) => dir); | 932 return Futures.wait(childFutures).transform((_) => dir); |
| 1050 }); | 933 }); |
| 1051 } | 934 } |
| 1052 | 935 |
| 1053 /** | 936 /// Deletes the directory within [dir]. Returns a [Future] that is completed |
| 1054 * Deletes the directory within [dir]. Returns a [Future] that is completed | 937 /// after the deletion is done. |
| 1055 * after the deletion is done. | |
| 1056 */ | |
| 1057 Future delete(dir) { | 938 Future delete(dir) { |
| 1058 return deleteDir(join(dir, _stringName)); | 939 return deleteDir(join(dir, _stringName)); |
| 1059 } | 940 } |
| 1060 | 941 |
| 1061 /** | 942 /// Validates that the directory at [path] contains all of the expected |
| 1062 * Validates that the directory at [path] contains all of the expected | 943 /// contents in this descriptor. Note that this does *not* check that the |
| 1063 * contents in this descriptor. Note that this does *not* check that the | 944 /// directory doesn't contain other unexpected stuff, just that it *does* |
| 1064 * directory doesn't contain other unexpected stuff, just that it *does* | 945 /// contain the stuff we do expect. |
| 1065 * contain the stuff we do expect. | |
| 1066 */ | |
| 1067 Future validate(String path) { | 946 Future validate(String path) { |
| 1068 return _validateOneMatch(path, (dir) { | 947 return _validateOneMatch(path, (dir) { |
| 1069 // Validate each of the items in this directory. | 948 // Validate each of the items in this directory. |
| 1070 final entryFutures = contents.map((entry) => entry.validate(dir)); | 949 final entryFutures = contents.map((entry) => entry.validate(dir)); |
| 1071 | 950 |
| 1072 // If they are all valid, the directory is valid. | 951 // If they are all valid, the directory is valid. |
| 1073 return Futures.wait(entryFutures).transform((entries) => null); | 952 return Futures.wait(entryFutures).transform((entries) => null); |
| 1074 }); | 953 }); |
| 1075 } | 954 } |
| 1076 | 955 |
| 1077 /** | 956 /// Loads [path] from within this directory. |
| 1078 * Loads [path] from within this directory. | |
| 1079 */ | |
| 1080 InputStream load(List<String> path) { | 957 InputStream load(List<String> path) { |
| 1081 if (path.isEmpty) { | 958 if (path.isEmpty) { |
| 1082 throw "Can't load the contents of $name: is a directory."; | 959 throw "Can't load the contents of $name: is a directory."; |
| 1083 } | 960 } |
| 1084 | 961 |
| 1085 for (var descriptor in contents) { | 962 for (var descriptor in contents) { |
| 1086 if (descriptor.name == path[0]) { | 963 if (descriptor.name == path[0]) { |
| 1087 return descriptor.load(path.getRange(1, path.length - 1)); | 964 return descriptor.load(path.getRange(1, path.length - 1)); |
| 1088 } | 965 } |
| 1089 } | 966 } |
| 1090 | 967 |
| 1091 throw "Directory $name doesn't contain ${Strings.join(path, '/')}."; | 968 throw "Directory $name doesn't contain ${Strings.join(path, '/')}."; |
| 1092 } | 969 } |
| 1093 } | 970 } |
| 1094 | 971 |
| 1095 /** | 972 /// Wraps a [Future] that will complete to a [Descriptor] and makes it behave |
| 1096 * Wraps a [Future] that will complete to a [Descriptor] and makes it behave | 973 /// like a concrete [Descriptor]. This is necessary when the contents of the |
| 1097 * like a concrete [Descriptor]. This is necessary when the contents of the | 974 /// descriptor depends on information that's not available until part of the |
| 1098 * descriptor depends on information that's not available until part of the test | 975 /// test run is completed. |
| 1099 * run is completed. | |
| 1100 */ | |
| 1101 class FutureDescriptor extends Descriptor { | 976 class FutureDescriptor extends Descriptor { |
| 1102 Future<Descriptor> _future; | 977 Future<Descriptor> _future; |
| 1103 | 978 |
| 1104 FutureDescriptor(this._future) : super('<unknown>'); | 979 FutureDescriptor(this._future) : super('<unknown>'); |
| 1105 | 980 |
| 1106 Future create(dir) => _future.chain((desc) => desc.create(dir)); | 981 Future create(dir) => _future.chain((desc) => desc.create(dir)); |
| 1107 | 982 |
| 1108 Future validate(dir) => _future.chain((desc) => desc.validate(dir)); | 983 Future validate(dir) => _future.chain((desc) => desc.validate(dir)); |
| 1109 | 984 |
| 1110 Future delete(dir) => _future.chain((desc) => desc.delete(dir)); | 985 Future delete(dir) => _future.chain((desc) => desc.delete(dir)); |
| 1111 | 986 |
| 1112 InputStream load(List<String> path) { | 987 InputStream load(List<String> path) { |
| 1113 var resultStream = new ListInputStream(); | 988 var resultStream = new ListInputStream(); |
| 1114 _future.then((desc) => pipeInputToInput(desc.load(path), resultStream)); | 989 _future.then((desc) => pipeInputToInput(desc.load(path), resultStream)); |
| 1115 return resultStream; | 990 return resultStream; |
| 1116 } | 991 } |
| 1117 } | 992 } |
| 1118 | 993 |
| 1119 /** | 994 /// Describes a Git repository and its contents. |
| 1120 * Describes a Git repository and its contents. | |
| 1121 */ | |
| 1122 class GitRepoDescriptor extends DirectoryDescriptor { | 995 class GitRepoDescriptor extends DirectoryDescriptor { |
| 1123 GitRepoDescriptor(Pattern name, List<Descriptor> contents) | 996 GitRepoDescriptor(Pattern name, List<Descriptor> contents) |
| 1124 : super(name, contents); | 997 : super(name, contents); |
| 1125 | 998 |
| 1126 /** | 999 /// Creates the Git repository and commits the contents. |
| 1127 * Creates the Git repository and commits the contents. | |
| 1128 */ | |
| 1129 Future<Directory> create(parentDir) { | 1000 Future<Directory> create(parentDir) { |
| 1130 return _runGitCommands(parentDir, [ | 1001 return _runGitCommands(parentDir, [ |
| 1131 ['init'], | 1002 ['init'], |
| 1132 ['add', '.'], | 1003 ['add', '.'], |
| 1133 ['commit', '-m', 'initial commit'] | 1004 ['commit', '-m', 'initial commit'] |
| 1134 ]); | 1005 ]); |
| 1135 } | 1006 } |
| 1136 | 1007 |
| 1137 /** | 1008 /// Commits any changes to the Git repository. |
| 1138 * Commits any changes to the Git repository. | |
| 1139 */ | |
| 1140 Future commit(parentDir) { | 1009 Future commit(parentDir) { |
| 1141 return _runGitCommands(parentDir, [ | 1010 return _runGitCommands(parentDir, [ |
| 1142 ['add', '.'], | 1011 ['add', '.'], |
| 1143 ['commit', '-m', 'update'] | 1012 ['commit', '-m', 'update'] |
| 1144 ]); | 1013 ]); |
| 1145 } | 1014 } |
| 1146 | 1015 |
| 1147 /** | 1016 /// Schedules changes to be committed to the Git repository. |
| 1148 * Schedules changes to be committed to the Git repository. | |
| 1149 */ | |
| 1150 void scheduleCommit() => _schedule((dir) => this.commit(dir)); | 1017 void scheduleCommit() => _schedule((dir) => this.commit(dir)); |
| 1151 | 1018 |
| 1152 /** | 1019 /// Return a Future that completes to the commit in the git repository |
| 1153 * Return a Future that completes to the commit in the git repository referred | 1020 /// referred to by [ref] at the current point in the scheduled test run. |
| 1154 * to by [ref] at the current point in the scheduled test run. | |
| 1155 */ | |
| 1156 Future<String> revParse(String ref) { | 1021 Future<String> revParse(String ref) { |
| 1157 return _scheduleValue((parentDir) { | 1022 return _scheduleValue((parentDir) { |
| 1158 return super.create(parentDir).chain((rootDir) { | 1023 return super.create(parentDir).chain((rootDir) { |
| 1159 return _runGit(['rev-parse', ref], rootDir); | 1024 return _runGit(['rev-parse', ref], rootDir); |
| 1160 }).transform((output) => output[0]); | 1025 }).transform((output) => output[0]); |
| 1161 }); | 1026 }); |
| 1162 } | 1027 } |
| 1163 | 1028 |
| 1164 /// Schedule a Git command to run in this repository. | 1029 /// Schedule a Git command to run in this repository. |
| 1165 void scheduleGit(List<String> args) { | 1030 void scheduleGit(List<String> args) { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1199 if (!result.success) { | 1064 if (!result.success) { |
| 1200 throw "Error running: git ${Strings.join(args, ' ')}\n" | 1065 throw "Error running: git ${Strings.join(args, ' ')}\n" |
| 1201 "${Strings.join(result.stderr, '\n')}"; | 1066 "${Strings.join(result.stderr, '\n')}"; |
| 1202 } | 1067 } |
| 1203 | 1068 |
| 1204 return result.stdout; | 1069 return result.stdout; |
| 1205 }); | 1070 }); |
| 1206 } | 1071 } |
| 1207 } | 1072 } |
| 1208 | 1073 |
| 1209 /** | 1074 /// Describes a gzipped tar file and its contents. |
| 1210 * Describes a gzipped tar file and its contents. | |
| 1211 */ | |
| 1212 class TarFileDescriptor extends Descriptor { | 1075 class TarFileDescriptor extends Descriptor { |
| 1213 final List<Descriptor> contents; | 1076 final List<Descriptor> contents; |
| 1214 | 1077 |
| 1215 TarFileDescriptor(Pattern name, this.contents) | 1078 TarFileDescriptor(Pattern name, this.contents) |
| 1216 : super(name); | 1079 : super(name); |
| 1217 | 1080 |
| 1218 /** | 1081 /// Creates the files and directories within this tar file, then archives |
| 1219 * Creates the files and directories within this tar file, then archives them, | 1082 /// them, compresses them, and saves the result to [parentDir]. |
| 1220 * compresses them, and saves the result to [parentDir]. | |
| 1221 */ | |
| 1222 Future<File> create(parentDir) { | 1083 Future<File> create(parentDir) { |
| 1223 // TODO(rnystrom): Use withTempDir(). | 1084 // TODO(rnystrom): Use withTempDir(). |
| 1224 var tempDir; | 1085 var tempDir; |
| 1225 return createTempDir().chain((_tempDir) { | 1086 return createTempDir().chain((_tempDir) { |
| 1226 tempDir = _tempDir; | 1087 tempDir = _tempDir; |
| 1227 return Futures.wait(contents.map((child) => child.create(tempDir))); | 1088 return Futures.wait(contents.map((child) => child.create(tempDir))); |
| 1228 }).chain((createdContents) { | 1089 }).chain((createdContents) { |
| 1229 return consumeInputStream(createTarGz(createdContents, baseDir: tempDir)); | 1090 return consumeInputStream(createTarGz(createdContents, baseDir: tempDir)); |
| 1230 }).chain((bytes) { | 1091 }).chain((bytes) { |
| 1231 return new File(join(parentDir, _stringName)).writeAsBytes(bytes); | 1092 return new File(join(parentDir, _stringName)).writeAsBytes(bytes); |
| 1232 }).chain((file) { | 1093 }).chain((file) { |
| 1233 return deleteDir(tempDir).transform((_) => file); | 1094 return deleteDir(tempDir).transform((_) => file); |
| 1234 }); | 1095 }); |
| 1235 } | 1096 } |
| 1236 | 1097 |
| 1237 /** | 1098 /// Validates that the `.tar.gz` file at [path] contains the expected |
| 1238 * Validates that the `.tar.gz` file at [path] contains the expected contents. | 1099 /// contents. |
| 1239 */ | |
| 1240 Future validate(String path) { | 1100 Future validate(String path) { |
| 1241 throw "TODO(nweiz): implement this"; | 1101 throw "TODO(nweiz): implement this"; |
| 1242 } | 1102 } |
| 1243 | 1103 |
| 1244 Future delete(dir) { | 1104 Future delete(dir) { |
| 1245 throw new UnsupportedError(''); | 1105 throw new UnsupportedError(''); |
| 1246 } | 1106 } |
| 1247 | 1107 |
| 1248 /** | 1108 /// Loads the contents of this tar file. |
| 1249 * Loads the contents of this tar file. | |
| 1250 */ | |
| 1251 InputStream load(List<String> path) { | 1109 InputStream load(List<String> path) { |
| 1252 if (!path.isEmpty) { | 1110 if (!path.isEmpty) { |
| 1253 var joinedPath = Strings.join(path, '/'); | 1111 var joinedPath = Strings.join(path, '/'); |
| 1254 throw "Can't load $joinedPath from within $name: not a directory."; | 1112 throw "Can't load $joinedPath from within $name: not a directory."; |
| 1255 } | 1113 } |
| 1256 | 1114 |
| 1257 var sinkStream = new ListInputStream(); | 1115 var sinkStream = new ListInputStream(); |
| 1258 var tempDir; | 1116 var tempDir; |
| 1259 // TODO(rnystrom): Use withTempDir() here. | 1117 // TODO(rnystrom): Use withTempDir() here. |
| 1260 // TODO(nweiz): propagate any errors to the return value. See issue 3657. | 1118 // TODO(nweiz): propagate any errors to the return value. See issue 3657. |
| 1261 createTempDir().chain((_tempDir) { | 1119 createTempDir().chain((_tempDir) { |
| 1262 tempDir = _tempDir; | 1120 tempDir = _tempDir; |
| 1263 return create(tempDir); | 1121 return create(tempDir); |
| 1264 }).then((tar) { | 1122 }).then((tar) { |
| 1265 var sourceStream = tar.openInputStream(); | 1123 var sourceStream = tar.openInputStream(); |
| 1266 pipeInputToInput(sourceStream, sinkStream).then((_) { | 1124 pipeInputToInput(sourceStream, sinkStream).then((_) { |
| 1267 tempDir.delete(recursive: true); | 1125 tempDir.delete(recursive: true); |
| 1268 }); | 1126 }); |
| 1269 }); | 1127 }); |
| 1270 return sinkStream; | 1128 return sinkStream; |
| 1271 } | 1129 } |
| 1272 } | 1130 } |
| 1273 | 1131 |
| 1274 /** | 1132 /// A descriptor that validates that no file exists with the given name. |
| 1275 * A descriptor that validates that no file exists with the given name. | |
| 1276 */ | |
| 1277 class NothingDescriptor extends Descriptor { | 1133 class NothingDescriptor extends Descriptor { |
| 1278 NothingDescriptor(String name) : super(name); | 1134 NothingDescriptor(String name) : super(name); |
| 1279 | 1135 |
| 1280 Future create(dir) => new Future.immediate(null); | 1136 Future create(dir) => new Future.immediate(null); |
| 1281 Future delete(dir) => new Future.immediate(null); | 1137 Future delete(dir) => new Future.immediate(null); |
| 1282 | 1138 |
| 1283 Future validate(String dir) { | 1139 Future validate(String dir) { |
| 1284 return exists(join(dir, name)).transform((exists) { | 1140 return exists(join(dir, name)).transform((exists) { |
| 1285 if (exists) Expect.fail('File $name in $dir should not exist.'); | 1141 if (exists) Expect.fail('File $name in $dir should not exist.'); |
| 1286 }); | 1142 }); |
| (...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1532 return; | 1388 return; |
| 1533 }); | 1389 }); |
| 1534 } | 1390 } |
| 1535 | 1391 |
| 1536 return printStream('stdout', _stdout.value) | 1392 return printStream('stdout', _stdout.value) |
| 1537 .chain((_) => printStream('stderr', _stderr.value)); | 1393 .chain((_) => printStream('stderr', _stderr.value)); |
| 1538 } | 1394 } |
| 1539 } | 1395 } |
| 1540 | 1396 |
| 1541 /// A class representing an [HttpServer] that's scheduled to run in the course | 1397 /// A class representing an [HttpServer] that's scheduled to run in the course |
| 1542 /// of the test. This class allows the server's request handling to be scheduled | 1398 /// of the test. This class allows the server's request handling to be |
| 1543 /// synchronously. All operations on this class are scheduled. | 1399 /// scheduled synchronously. All operations on this class are scheduled. |
| 1544 class ScheduledServer { | 1400 class ScheduledServer { |
| 1545 /// The wrapped server. | 1401 /// The wrapped server. |
| 1546 final Future<HttpServer> _server; | 1402 final Future<HttpServer> _server; |
| 1547 | 1403 |
| 1548 /// The queue of handlers to run for upcoming requests. | 1404 /// The queue of handlers to run for upcoming requests. |
| 1549 final _handlers = new Queue<Future>(); | 1405 final _handlers = new Queue<Future>(); |
| 1550 | 1406 |
| 1551 /// The requests to be ignored. | 1407 /// The requests to be ignored. |
| 1552 final _ignored = new Set<Pair<String, String>>(); | 1408 final _ignored = new Set<Pair<String, String>>(); |
| 1553 | 1409 |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1611 } | 1467 } |
| 1612 return _handlers.removeFirst(); | 1468 return _handlers.removeFirst(); |
| 1613 }).transform((handler) { | 1469 }).transform((handler) { |
| 1614 handler(request, response); | 1470 handler(request, response); |
| 1615 }), _SCHEDULE_TIMEOUT, "waiting for a handler for ${request.method} " | 1471 }), _SCHEDULE_TIMEOUT, "waiting for a handler for ${request.method} " |
| 1616 "${request.path}"); | 1472 "${request.path}"); |
| 1617 expect(future, completes); | 1473 expect(future, completes); |
| 1618 } | 1474 } |
| 1619 } | 1475 } |
| 1620 | 1476 |
| 1621 /** | 1477 /// Takes a simple data structure (composed of [Map]s, [List]s, scalar objects, |
| 1622 * Takes a simple data structure (composed of [Map]s, [List]s, scalar objects, | 1478 /// and [Future]s) and recursively resolves all the [Future]s contained within. |
| 1623 * and [Future]s) and recursively resolves all the [Future]s contained within. | 1479 /// Completes with the fully resolved structure. |
| 1624 * Completes with the fully resolved structure. | |
| 1625 */ | |
| 1626 Future _awaitObject(object) { | 1480 Future _awaitObject(object) { |
| 1627 // Unroll nested futures. | 1481 // Unroll nested futures. |
| 1628 if (object is Future) return object.chain(_awaitObject); | 1482 if (object is Future) return object.chain(_awaitObject); |
| 1629 if (object is Collection) return Futures.wait(object.map(_awaitObject)); | 1483 if (object is Collection) return Futures.wait(object.map(_awaitObject)); |
| 1630 if (object is! Map) return new Future.immediate(object); | 1484 if (object is! Map) return new Future.immediate(object); |
| 1631 | 1485 |
| 1632 var pairs = <Future<Pair>>[]; | 1486 var pairs = <Future<Pair>>[]; |
| 1633 object.forEach((key, value) { | 1487 object.forEach((key, value) { |
| 1634 pairs.add(_awaitObject(value) | 1488 pairs.add(_awaitObject(value) |
| 1635 .transform((resolved) => new Pair(key, resolved))); | 1489 .transform((resolved) => new Pair(key, resolved))); |
| 1636 }); | 1490 }); |
| 1637 return Futures.wait(pairs).transform((resolvedPairs) { | 1491 return Futures.wait(pairs).transform((resolvedPairs) { |
| 1638 var map = {}; | 1492 var map = {}; |
| 1639 for (var pair in resolvedPairs) { | 1493 for (var pair in resolvedPairs) { |
| 1640 map[pair.first] = pair.last; | 1494 map[pair.first] = pair.last; |
| 1641 } | 1495 } |
| 1642 return map; | 1496 return map; |
| 1643 }); | 1497 }); |
| 1644 } | 1498 } |
| 1645 | 1499 |
| 1646 /** | 1500 /// Schedules a callback to be called as part of the test case. |
| 1647 * Schedules a callback to be called as part of the test case. | |
| 1648 */ | |
| 1649 void _schedule(_ScheduledEvent event) { | 1501 void _schedule(_ScheduledEvent event) { |
| 1650 if (_scheduled == null) _scheduled = []; | 1502 if (_scheduled == null) _scheduled = []; |
| 1651 _scheduled.add(event); | 1503 _scheduled.add(event); |
| 1652 } | 1504 } |
| 1653 | 1505 |
| 1654 /// Like [_schedule], but pipes the return value of [event] to a returned | 1506 /// Like [_schedule], but pipes the return value of [event] to a returned |
| 1655 /// [Future]. | 1507 /// [Future]. |
| 1656 Future _scheduleValue(_ScheduledEvent event) { | 1508 Future _scheduleValue(_ScheduledEvent event) { |
| 1657 var completer = new Completer(); | 1509 var completer = new Completer(); |
| 1658 _schedule((parentDir) { | 1510 _schedule((parentDir) { |
| 1659 chainToCompleter(event(parentDir), completer); | 1511 chainToCompleter(event(parentDir), completer); |
| 1660 return completer.future; | 1512 return completer.future; |
| 1661 }); | 1513 }); |
| 1662 return completer.future; | 1514 return completer.future; |
| 1663 } | 1515 } |
| 1664 | 1516 |
| 1665 /// Schedules a callback to be called after the test case has completed, even if | 1517 /// Schedules a callback to be called after the test case has completed, even |
| 1666 /// it failed. | 1518 /// if it failed. |
| 1667 void _scheduleCleanup(_ScheduledEvent event) { | 1519 void _scheduleCleanup(_ScheduledEvent event) { |
| 1668 if (_scheduledCleanup == null) _scheduledCleanup = []; | 1520 if (_scheduledCleanup == null) _scheduledCleanup = []; |
| 1669 _scheduledCleanup.add(event); | 1521 _scheduledCleanup.add(event); |
| 1670 } | 1522 } |
| 1671 | 1523 |
| 1672 /// Schedules a callback to be called after the test case has completed, but | 1524 /// Schedules a callback to be called after the test case has completed, but |
| 1673 /// only if it failed. | 1525 /// only if it failed. |
| 1674 void _scheduleOnException(_ScheduledEvent event) { | 1526 void _scheduleOnException(_ScheduledEvent event) { |
| 1675 if (_scheduledOnException == null) _scheduledOnException = []; | 1527 if (_scheduledOnException == null) _scheduledOnException = []; |
| 1676 _scheduledOnException.add(event); | 1528 _scheduledOnException.add(event); |
| 1677 } | 1529 } |
| 1678 | 1530 |
| 1679 /// Like [expect], but for [Future]s that complete as part of the scheduled | 1531 /// Like [expect], but for [Future]s that complete as part of the scheduled |
| 1680 /// test. This is necessary to ensure that the exception thrown by the | 1532 /// test. This is necessary to ensure that the exception thrown by the |
| 1681 /// expectation failing is handled by the scheduler. | 1533 /// expectation failing is handled by the scheduler. |
| 1682 /// | 1534 /// |
| 1683 /// Note that [matcher] matches against the completed value of [actual], so | 1535 /// Note that [matcher] matches against the completed value of [actual], so |
| 1684 /// calling [completion] is unnecessary. | 1536 /// calling [completion] is unnecessary. |
| 1685 void expectLater(Future actual, matcher, {String reason, | 1537 void expectLater(Future actual, matcher, {String reason, |
| 1686 FailureHandler failureHandler, bool verbose: false}) { | 1538 FailureHandler failureHandler, bool verbose: false}) { |
| 1687 _schedule((_) { | 1539 _schedule((_) { |
| 1688 return actual.transform((value) { | 1540 return actual.transform((value) { |
| 1689 expect(value, matcher, reason: reason, failureHandler: failureHandler, | 1541 expect(value, matcher, reason: reason, failureHandler: failureHandler, |
| 1690 verbose: false); | 1542 verbose: false); |
| 1691 }); | 1543 }); |
| 1692 }); | 1544 }); |
| 1693 } | 1545 } |
| OLD | NEW |