Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | |
| 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. | |
| 4 | |
| 5 import "dart:async"; | |
| 6 import "dart:io"; | |
| 7 import "dart:isolate"; | |
| 8 import "dart:convert" show JSON; | |
| 9 import "package:path/path.dart" as p; | |
| 10 import "package:expect/expect.dart"; | |
| 11 import "package:async_helper/async_helper.dart"; | |
| 12 | |
| 13 main() async { | |
| 14 asyncStart(); | |
| 15 | |
| 16 for (var test in [testDirect]) { | |
|
floitsch
2016/05/18 13:36:32
As long as there is just one, I would just rename
| |
| 17 await test("file: no resolution", (run, dir) async { | |
| 18 // Expect everything to be null. | |
| 19 await run(dir.resolve("main.dart"), {"foo.x": null}); | |
|
floitsch
2016/05/18 13:36:32
This ping-pong is too complicated.
Just add a tok
Lasse Reichstein Nielsen
2016/05/18 14:01:16
And I haven't even gotten started on nested calls
| |
| 20 }, files: {"main": testMain}); | |
| 21 | |
| 22 await test("http: no resolution", (run, dir) async { | |
| 23 // An HTTP script with no ".packages" file assumes a "packages" dir. | |
| 24 await run(dir.resolve("main.dart"), { | |
| 25 // "foo": null, | |
| 26 "foo/": dir.resolve("packages/foo/"), | |
| 27 "foo/bar": dir.resolve("packages/foo/bar"), | |
| 28 "foo.x": null, | |
| 29 }); | |
| 30 }, http: {"main": testMain}); | |
| 31 | |
| 32 for (var scheme in ["file", "http"]) { | |
| 33 { | |
| 34 var files = {"main": testMain, "packages": fooPackage}; | |
| 35 await test("$scheme: implicit packages dir", (run, dir) async { | |
| 36 // Expect implicitly detected package dir. | |
| 37 await run(dir.resolve("main.dart"), { | |
| 38 "iroot": dir.resolve("packages/"), | |
| 39 // "foo": null, | |
| 40 "foo/": dir.resolve("packages/foo/"), | |
| 41 "foo/bar": dir.resolve("packages/foo/bar"), | |
| 42 }); | |
| 43 }, files: scheme == "file" ? files : null, | |
| 44 http: scheme == "http" ? files : null); | |
| 45 } | |
| 46 | |
| 47 { | |
| 48 var files = {"sub": {"main": testMain, "packages": fooPackage}, | |
| 49 ".packages": ""}; | |
| 50 await test("$scheme: implicit packages dir 2", (run, dir) async { | |
| 51 // Expect implicitly detected package dir. | |
| 52 await run(dir.resolve("sub/main.dart"), { | |
| 53 "iroot": dir.resolve("sub/packages/"), | |
| 54 // "foo": null, | |
| 55 "foo/": dir.resolve("sub/packages/foo/"), | |
| 56 "foo/bar": dir.resolve("sub/packages/foo/bar"), | |
| 57 }); | |
| 58 }, files: scheme == "file" ? files : null, | |
| 59 http: scheme == "http" ? files : null); | |
| 60 } | |
| 61 | |
| 62 { | |
| 63 var files = {"main": testMain, | |
| 64 ".packages": "foo:pkgs/foo/", | |
| 65 "pkgs": fooPackage}; | |
| 66 await test("$scheme: implicit .packages file", (run, dir) async { | |
| 67 // Expect implicitly detected .package file. | |
| 68 await run(dir.resolve("main.dart"), { | |
| 69 "iconf": dir.resolve(".packages"), | |
| 70 // "foo": null, | |
| 71 "foo/": dir.resolve("pkgs/foo/"), | |
| 72 "foo/bar": dir.resolve("pkgs/foo/bar"), | |
| 73 }); | |
| 74 }, files: scheme == "file" ? files : null, | |
| 75 http: scheme == "http" ? files : null); | |
| 76 } | |
| 77 | |
| 78 { | |
| 79 var files = {"main": testMain, | |
| 80 ".packages": "foo:packages/foo/", | |
| 81 "packages": fooPackage, | |
| 82 "pkgs": fooPackage}; | |
| 83 await test("$scheme: explicit package root, no slash", (run, dir) async { | |
|
floitsch
2016/05/18 13:36:32
long line.
| |
| 84 await run(dir.resolve("main.dart"), { | |
| 85 "proot": dir.resolve("pkgs/"), | |
| 86 "iroot": dir.resolve("pkgs/"), | |
| 87 // "foo": null, | |
| 88 "foo/": dir.resolve("pkgs/foo/"), | |
| 89 "foo/bar": dir.resolve("pkgs/foo/bar"), | |
| 90 }, root: dir.resolve("pkgs")); | |
| 91 }, files: scheme == "file" ? files : null, | |
| 92 http: scheme == "http" ? files : null); | |
| 93 | |
| 94 await test("$scheme: explicit package root, slash", (run, dir) async { | |
| 95 await run(dir.resolve("main.dart"), { | |
| 96 "proot": dir.resolve("pkgs/"), | |
| 97 "iroot": dir.resolve("pkgs/"), | |
| 98 // "foo": null, | |
| 99 "foo/": dir.resolve("pkgs/foo/"), | |
| 100 "foo/bar": dir.resolve("pkgs/foo/bar"), | |
| 101 }, root: dir.resolve("pkgs/")); | |
| 102 }, files: scheme == "file" ? files : null, | |
| 103 http: scheme == "http" ? files : null); | |
| 104 } | |
| 105 | |
| 106 { | |
| 107 var files = {"main": testMain, | |
| 108 ".packages": "foo:packages/foo/", | |
| 109 "packages": fooPackage, | |
| 110 ".pkgs": "foo:pkgs/foo/", | |
| 111 "pkgs": fooPackage}; | |
| 112 await test("$scheme: explicit package config file", (run, dir) async { | |
| 113 await run(dir.resolve("main.dart"), { | |
| 114 "pconf": dir.resolve(".pkgs"), | |
| 115 "iconf": dir.resolve(".pkgs"), | |
| 116 // "foo": null, | |
| 117 "foo/": dir.resolve("pkgs/foo/"), | |
| 118 "foo/bar": dir.resolve("pkgs/foo/bar"), | |
| 119 }, config: dir.resolve(".pkgs")); | |
| 120 }, files: scheme == "file" ? files : null, | |
| 121 http: scheme == "http" ? files : null); | |
| 122 } | |
| 123 | |
| 124 { | |
| 125 var files = {"main": testMain, | |
| 126 ".packages": "foo:packages/foo/", | |
| 127 "packages": fooPackage, | |
| 128 "pkgs": fooPackage}; | |
| 129 await test("$scheme: explicit data: config file", (run, dir) async { | |
| 130 var dataUri = new UriData.fromString( | |
| 131 "foo:${dir.resolve("pkgs/foo/")}").uri; | |
| 132 await run(dir.resolve("main.dart"), { | |
| 133 "pconf": dataUri, | |
| 134 "iconf": dataUri, | |
| 135 // "foo": null, | |
| 136 "foo/": dir.resolve("pkgs/foo/"), | |
| 137 "foo/bar": dir.resolve("pkgs/foo/bar"), | |
| 138 }, config: dataUri); | |
| 139 }, files: scheme == "file" ? files : null, | |
| 140 http: scheme == "http" ? files : null); | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 { | |
| 145 // With a file: URI, the lookup checks for a .packages file in superdirs. | |
| 146 var files = {"sub": { "main": testMain }, | |
| 147 ".packages": "foo:pkgs/foo/", | |
| 148 "pkgs": fooPackage}; | |
| 149 await test("file: implicit .packages file in ..", (run, dir) async { | |
| 150 // Expect implicitly detected .package file. | |
| 151 await run(dir.resolve("sub/main.dart"), { | |
| 152 "iconf": dir.resolve(".packages"), | |
| 153 // "foo": null, | |
| 154 "foo/": dir.resolve("pkgs/foo/"), | |
| 155 "foo/bar": dir.resolve("pkgs/foo/bar"), | |
| 156 }); | |
| 157 }, files: files); | |
| 158 } | |
| 159 | |
| 160 { | |
| 161 // With a non-file: URI, the lookup assumes a packges/ dir. | |
| 162 var files = {"sub": { "main": testMain }, | |
| 163 ".packages": "foo:pkgs/foo/", | |
| 164 "pkgs": fooPackage}; | |
| 165 await test("http: implicit packages dir", (run, dir) async { | |
| 166 // Expect implicitly detected .package file. | |
| 167 await run(dir.resolve("sub/main.dart"), { | |
| 168 "iroot": dir.resolve("sub/packages/"), | |
| 169 // "foo": null, | |
| 170 "foo/": dir.resolve("sub/packages/foo/"), | |
| 171 "foo/bar": dir.resolve("sub/packages/foo/bar"), | |
| 172 "foo.x": null, | |
| 173 }); | |
| 174 }, http: files); | |
| 175 } | |
| 176 } | |
| 177 | |
| 178 | |
| 179 if (failingTests.isNotEmpty) { | |
| 180 print("Errors found in tests:\n ${failingTests.join("\n ")}\n"); | |
| 181 exit(255); | |
| 182 } | |
| 183 asyncEnd(); | |
| 184 } | |
| 185 | |
| 186 // --------------------------------------------------------- | |
| 187 // Helper fnuctionality. | |
|
floitsch
2016/05/18 13:36:32
functionality
| |
| 188 | |
| 189 var failingTests = new Set(); | |
| 190 | |
| 191 Future testDirect(name, Function test, {Map files, Map http}) async { | |
| 192 var tmpDir; | |
| 193 var https; | |
| 194 if (files != null) tmpDir = createFiles(files); | |
| 195 if (http != null) https = await startServer(http); | |
| 196 runTest(main, expectations, {Uri root, Uri config}) async { | |
|
floitsch
2016/05/18 13:36:32
Please keep one line before and after nested funct
| |
| 197 var output = await runDart(main, root: root, config: config); | |
| 198 // These expectations are default. If not overridden the value will be | |
| 199 // expected to be null. That is, you can't avoid testing the actual | |
| 200 // value of these, you can only change what value to expect. | |
| 201 // For values not included here (commented out), the result is not tested | |
| 202 // unless a value (maybe null) is provided. | |
| 203 var expects = { | |
| 204 "pconf": null, | |
| 205 "proot": null, | |
| 206 "iconf": null, | |
| 207 "iconf": null, | |
| 208 // "foo": null, | |
| 209 "foo/": null, | |
| 210 "foo/bar": null, | |
| 211 "foo.x": "qux", | |
| 212 }..addAll(expectations); | |
| 213 match(JSON.decode(output), expects, name); | |
| 214 } | |
| 215 // Call test with 1-3 parameters depending on which setups were made. | |
| 216 try { | |
| 217 if (https == null) { | |
| 218 if (files == null) { | |
| 219 await test(runTest); // Data-schemes only, no files or http available. | |
| 220 } else { | |
| 221 await test(runTest, tmpDir.uri); | |
| 222 } | |
| 223 } else if (files == null) { | |
| 224 await test(runTest, serverUri(https)); | |
| 225 } else { | |
| 226 await test(runTest, tmpDir.uri, serverUri(https)); | |
|
floitsch
2016/05/18 13:36:32
You don't seem to use this case.
Lasse Reichstein Nielsen
2016/05/18 14:01:16
Yet :)
I need to have cases where the script is fi
| |
| 227 } | |
| 228 } catch (e) { | |
| 229 print("ERROR in $name: $e"); | |
|
floitsch
2016/05/18 13:36:32
I would rethrow.
Lasse Reichstein Nielsen
2016/05/18 14:01:16
Whoops, left over debug print.
| |
| 230 } finally { | |
| 231 if (https != null) await https.close(); | |
| 232 if (tmpDir != null) tmpDir.deleteSync(recursive: true); | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 | |
| 237 | |
| 238 /// Test that the output of running testMain matches the expectations. | |
| 239 /// | |
| 240 /// The output is a string which is parse as a JSON literal. | |
| 241 /// The resulting map is always mapping strings to strings, or possibly `null`. | |
| 242 /// The expectations can have non-string values other than null, | |
| 243 /// they are `toString`'ed before being compared (so the caller can use a URI | |
| 244 /// or a File/Directory directly as an expectation). | |
| 245 void match(Map actuals, Map expectations, String name) { | |
| 246 for (var key in expectations.keys) { | |
| 247 var expectation = expectations[key]; | |
| 248 var actual = actuals[key]; | |
| 249 if (expectation?.toString() != actual) { | |
| 250 print("ERROR: $name: $key: Expected: <$expectation> Found: <$actual>"); | |
| 251 failingTests.add(name); | |
| 252 } | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 /// Script that prints the current state and the result of resolving | |
| 257 /// a few package URIs. This script will be invoked in different settings, | |
| 258 /// and the result will be parsed and compared to the expectations. | |
| 259 const String testMain = r""" | |
| 260 import "dart:convert" show JSON; | |
| 261 import "dart:io" show Platform, Directory; | |
| 262 import "dart:isolate" show Isolate; | |
| 263 import "package:foo/foo.dart" deferred as foo; | |
| 264 main(_) async { | |
| 265 String platformRoot = await Platform.packageRoot; | |
| 266 String platformConfig = await Platform.packageConfig; | |
| 267 Directory cwd = Directory.current; | |
| 268 Uri script = Platform.script; | |
| 269 Uri isolateRoot = await Isolate.packageRoot; | |
| 270 Uri isolateConfig = await Isolate.packageConfig; | |
| 271 Uri base = Uri.base; | |
| 272 Uri res1 = await Isolate.resolvePackageUri(Uri.parse("package:foo")); | |
| 273 Uri res2 = await Isolate.resolvePackageUri(Uri.parse("package:foo/")); | |
| 274 Uri res3 = await Isolate.resolvePackageUri(Uri.parse("package:foo/bar")); | |
| 275 String fooX = await foo | |
| 276 .loadLibrary() | |
| 277 .timeout(const Duration(seconds: 1)) | |
| 278 .then((_) => foo.x, onError: (_) => null); | |
| 279 print(JSON.encode({ | |
| 280 "cwd": cwd.path, | |
| 281 "base": base?.toString(), | |
| 282 "script": script?.toString(), | |
| 283 "proot": platformRoot, | |
| 284 "pconf": platformConfig, | |
| 285 "iroot" : isolateRoot?.toString(), | |
| 286 "iconf" : isolateConfig?.toString(), | |
| 287 "foo": res1?.toString(), | |
|
floitsch
2016/05/18 13:36:32
No need for "?.". Null supports 'toString' too.
Lasse Reichstein Nielsen
2016/05/18 14:01:16
But that would make at the string "null", not the
floitsch
2016/05/19 12:19:23
right.
| |
| 288 "foo/": res2?.toString(), | |
| 289 "foo/bar": res3?.toString(), | |
| 290 "foo.x": fooX?.toString(), | |
| 291 })); | |
| 292 } | |
| 293 """; | |
| 294 | |
| 295 /// Script that spawns a new Isolate using Isolate.spawnUri. | |
| 296 /// | |
| 297 /// Takes URI of target isolate, package config and package root as | |
| 298 /// command line arguments. Any further arguments are forwarded to the | |
| 299 /// spawned isolate. | |
| 300 const String spawnUriMain = r""" | |
| 301 import "dart:isolate"; | |
| 302 main(args) async { | |
| 303 Uri target = Uri.parse(args[0]); | |
| 304 Uri conf = args.length > 1 && args[1].isNotEmpty ? Uri.parse(args[1]) : null; | |
| 305 Uri root = args.length > 2 && args[2].isNotEmpty ? Uri.parse(args[2]) : null; | |
| 306 var restArgs = args.skip(3).toList(); | |
| 307 var isolate = await Isolate.spawnUri(target, restArgs, | |
| 308 packageRoot: root, packageConfig: conf, paused: true); | |
| 309 // Wait for isolate to exit before exiting the main isolate. | |
| 310 var done = new RawReceivePort(); | |
| 311 done.handler = (_) { done.close(); }; | |
| 312 isolate.addExitHandler(done.sendPort); | |
| 313 isolate.resume(isolate.pauseCapability); | |
| 314 } | |
| 315 """; | |
| 316 | |
| 317 /// Script that spawns a new Isolate using Isolate.spawn. | |
| 318 const String spawnMain = r""" | |
| 319 import "dart:isolate"; | |
| 320 import "testmain.dart" as test; | |
| 321 main() async { | |
| 322 var isolate = await Isolate.spawn(test.main, [], paused: true); | |
| 323 // Wait for isolate to exit before exiting the main isolate. | |
| 324 var done = new RawReceivePort(); | |
| 325 done.handler = (_) { done.close(); }; | |
| 326 isolate.addExitHandler(done.sendPort); | |
| 327 isolate.resume(isolate.pauseCapability); | |
| 328 } | |
| 329 """; | |
| 330 | |
| 331 /// A package directory containing only one package, "foo", with one file. | |
| 332 const Map fooPackage = const { "foo": const { "foo": "var x = 'qux';" }}; | |
| 333 | |
| 334 /// Runs the Dart executable with the provided parameters. | |
| 335 /// | |
| 336 /// Optionally sets CWD before running the script. | |
| 337 /// Captures and returns the output. | |
| 338 Future<String> runDart(Uri script, | |
| 339 {Uri root, Uri config, List<String> scriptArgs}) async { | |
| 340 var executable = Platform.executable; | |
| 341 var args = []; | |
| 342 if (root != null) args..add("-p")..add(root.toString()); | |
| 343 if (config != null) args..add("--packages=$config"); | |
| 344 args.add(script.toString()); | |
| 345 args.addAll(scriptArgs ?? []); | |
| 346 return Process.run(executable, args).then((results) { | |
| 347 if (results.exitCode != 0) { | |
| 348 throw results.stderr; | |
| 349 } | |
| 350 return results.stdout; | |
| 351 }); | |
| 352 } | |
| 353 | |
| 354 /// Creates a temporary directory and a number of files and subdirectories. | |
| 355 /// | |
| 356 /// The [content] is the content of the directory itself. The map keys are | |
| 357 /// names and the values are either strings that represent Dart file contents | |
| 358 /// or maps that represent subdirectories. | |
| 359 /// Subdirectories may include a package directory. If [packageDir] | |
| 360 /// is provided, a `.packages` file is created for the content of that | |
| 361 /// directory. | |
| 362 Directory createFiles(Map content, | |
| 363 [String packageDir]) { | |
| 364 Directory createDir(Directory base, String name) { | |
|
floitsch
2016/05/18 13:36:32
New lines before and after nested functions.
| |
| 365 Directory newDir = new Directory(p.join(base.path, name)); | |
| 366 newDir.createSync(); | |
| 367 return newDir; | |
| 368 } | |
| 369 void createTextFile(Directory base, String name, String content) { | |
| 370 File newFile = new File(p.join(base.path, name)); | |
| 371 newFile.writeAsStringSync(content); | |
| 372 } | |
| 373 void create(Directory dir, Map map) { | |
| 374 for (var name in map.keys) { | |
| 375 var content = map[name]; | |
| 376 if (content is String) { | |
| 377 // If the name starts with "." it's a .packages file, otherwise it's | |
| 378 // a dart file. Those are the only files we care about in this test. | |
| 379 createTextFile(dir, | |
| 380 name.startsWith(".") ? name : name + ".dart", | |
| 381 content); | |
| 382 } else { | |
| 383 var subdir = createDir(dir, name); | |
| 384 create(subdir, content); | |
| 385 } | |
| 386 } | |
| 387 } | |
| 388 var tempDir = Directory.systemTemp.createTempSync("pftest-${tmpDirCounter++}-" ); | |
|
floitsch
2016/05/18 13:36:32
long line.
| |
| 389 create(tempDir, content); | |
| 390 if (packageDir != null) { | |
| 391 Map packages = content[packageDir]; | |
| 392 var entries = | |
| 393 packages.keys.map((key) => "$key:$packageDir/$key").join("\n"); | |
| 394 createTextFile(tempDir, ".packages", entries); | |
| 395 } | |
| 396 return tempDir; | |
| 397 } | |
| 398 | |
| 399 // Counter used to avoid reusing temporary directory names. | |
| 400 int tmpDirCounter = 0; | |
| 401 | |
| 402 Future<HttpServer> startServer(Map files) async { | |
| 403 return (await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0)) | |
| 404 ..forEach((request) { | |
| 405 var result = files; | |
| 406 onFailure: { | |
| 407 for (var part in request.uri.pathSegments) { | |
| 408 if (part.endsWith(".dart")) { | |
| 409 part = part.substring(0, part.length - 5); | |
| 410 } | |
| 411 if (result is Map) { | |
| 412 result = result[part]; | |
| 413 } else { | |
| 414 break onFailure; | |
| 415 } | |
| 416 } | |
| 417 if (result is String) { | |
| 418 request.response..write(result) | |
| 419 ..close(); | |
| 420 return; | |
| 421 } | |
| 422 } | |
| 423 request.response..statusCode = HttpStatus.NOT_FOUND | |
| 424 ..close(); | |
| 425 }); | |
| 426 } | |
| 427 | |
| 428 Uri serverUri(HttpServer server) => | |
| 429 new Uri(scheme: "http", | |
| 430 host: server.address.address, | |
| 431 port: server.port, | |
| 432 path: "/"); | |
| OLD | NEW |