Index: tests/standalone/packages_file_test.dart |
diff --git a/tests/standalone/packages_file_test.dart b/tests/standalone/packages_file_test.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..299c4fa251dadd41be7133cda54f67caad2e4907 |
--- /dev/null |
+++ b/tests/standalone/packages_file_test.dart |
@@ -0,0 +1,432 @@ |
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+import "dart:async"; |
+import "dart:io"; |
+import "dart:isolate"; |
+import "dart:convert" show JSON; |
+import "package:path/path.dart" as p; |
+import "package:expect/expect.dart"; |
+import "package:async_helper/async_helper.dart"; |
+ |
+main() async { |
+ asyncStart(); |
+ |
+ for (var test in [testDirect]) { |
floitsch
2016/05/18 13:36:32
As long as there is just one, I would just rename
|
+ await test("file: no resolution", (run, dir) async { |
+ // Expect everything to be null. |
+ 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
|
+ }, files: {"main": testMain}); |
+ |
+ await test("http: no resolution", (run, dir) async { |
+ // An HTTP script with no ".packages" file assumes a "packages" dir. |
+ await run(dir.resolve("main.dart"), { |
+ // "foo": null, |
+ "foo/": dir.resolve("packages/foo/"), |
+ "foo/bar": dir.resolve("packages/foo/bar"), |
+ "foo.x": null, |
+ }); |
+ }, http: {"main": testMain}); |
+ |
+ for (var scheme in ["file", "http"]) { |
+ { |
+ var files = {"main": testMain, "packages": fooPackage}; |
+ await test("$scheme: implicit packages dir", (run, dir) async { |
+ // Expect implicitly detected package dir. |
+ await run(dir.resolve("main.dart"), { |
+ "iroot": dir.resolve("packages/"), |
+ // "foo": null, |
+ "foo/": dir.resolve("packages/foo/"), |
+ "foo/bar": dir.resolve("packages/foo/bar"), |
+ }); |
+ }, files: scheme == "file" ? files : null, |
+ http: scheme == "http" ? files : null); |
+ } |
+ |
+ { |
+ var files = {"sub": {"main": testMain, "packages": fooPackage}, |
+ ".packages": ""}; |
+ await test("$scheme: implicit packages dir 2", (run, dir) async { |
+ // Expect implicitly detected package dir. |
+ await run(dir.resolve("sub/main.dart"), { |
+ "iroot": dir.resolve("sub/packages/"), |
+ // "foo": null, |
+ "foo/": dir.resolve("sub/packages/foo/"), |
+ "foo/bar": dir.resolve("sub/packages/foo/bar"), |
+ }); |
+ }, files: scheme == "file" ? files : null, |
+ http: scheme == "http" ? files : null); |
+ } |
+ |
+ { |
+ var files = {"main": testMain, |
+ ".packages": "foo:pkgs/foo/", |
+ "pkgs": fooPackage}; |
+ await test("$scheme: implicit .packages file", (run, dir) async { |
+ // Expect implicitly detected .package file. |
+ await run(dir.resolve("main.dart"), { |
+ "iconf": dir.resolve(".packages"), |
+ // "foo": null, |
+ "foo/": dir.resolve("pkgs/foo/"), |
+ "foo/bar": dir.resolve("pkgs/foo/bar"), |
+ }); |
+ }, files: scheme == "file" ? files : null, |
+ http: scheme == "http" ? files : null); |
+ } |
+ |
+ { |
+ var files = {"main": testMain, |
+ ".packages": "foo:packages/foo/", |
+ "packages": fooPackage, |
+ "pkgs": fooPackage}; |
+ await test("$scheme: explicit package root, no slash", (run, dir) async { |
floitsch
2016/05/18 13:36:32
long line.
|
+ await run(dir.resolve("main.dart"), { |
+ "proot": dir.resolve("pkgs/"), |
+ "iroot": dir.resolve("pkgs/"), |
+ // "foo": null, |
+ "foo/": dir.resolve("pkgs/foo/"), |
+ "foo/bar": dir.resolve("pkgs/foo/bar"), |
+ }, root: dir.resolve("pkgs")); |
+ }, files: scheme == "file" ? files : null, |
+ http: scheme == "http" ? files : null); |
+ |
+ await test("$scheme: explicit package root, slash", (run, dir) async { |
+ await run(dir.resolve("main.dart"), { |
+ "proot": dir.resolve("pkgs/"), |
+ "iroot": dir.resolve("pkgs/"), |
+ // "foo": null, |
+ "foo/": dir.resolve("pkgs/foo/"), |
+ "foo/bar": dir.resolve("pkgs/foo/bar"), |
+ }, root: dir.resolve("pkgs/")); |
+ }, files: scheme == "file" ? files : null, |
+ http: scheme == "http" ? files : null); |
+ } |
+ |
+ { |
+ var files = {"main": testMain, |
+ ".packages": "foo:packages/foo/", |
+ "packages": fooPackage, |
+ ".pkgs": "foo:pkgs/foo/", |
+ "pkgs": fooPackage}; |
+ await test("$scheme: explicit package config file", (run, dir) async { |
+ await run(dir.resolve("main.dart"), { |
+ "pconf": dir.resolve(".pkgs"), |
+ "iconf": dir.resolve(".pkgs"), |
+ // "foo": null, |
+ "foo/": dir.resolve("pkgs/foo/"), |
+ "foo/bar": dir.resolve("pkgs/foo/bar"), |
+ }, config: dir.resolve(".pkgs")); |
+ }, files: scheme == "file" ? files : null, |
+ http: scheme == "http" ? files : null); |
+ } |
+ |
+ { |
+ var files = {"main": testMain, |
+ ".packages": "foo:packages/foo/", |
+ "packages": fooPackage, |
+ "pkgs": fooPackage}; |
+ await test("$scheme: explicit data: config file", (run, dir) async { |
+ var dataUri = new UriData.fromString( |
+ "foo:${dir.resolve("pkgs/foo/")}").uri; |
+ await run(dir.resolve("main.dart"), { |
+ "pconf": dataUri, |
+ "iconf": dataUri, |
+ // "foo": null, |
+ "foo/": dir.resolve("pkgs/foo/"), |
+ "foo/bar": dir.resolve("pkgs/foo/bar"), |
+ }, config: dataUri); |
+ }, files: scheme == "file" ? files : null, |
+ http: scheme == "http" ? files : null); |
+ } |
+ } |
+ |
+ { |
+ // With a file: URI, the lookup checks for a .packages file in superdirs. |
+ var files = {"sub": { "main": testMain }, |
+ ".packages": "foo:pkgs/foo/", |
+ "pkgs": fooPackage}; |
+ await test("file: implicit .packages file in ..", (run, dir) async { |
+ // Expect implicitly detected .package file. |
+ await run(dir.resolve("sub/main.dart"), { |
+ "iconf": dir.resolve(".packages"), |
+ // "foo": null, |
+ "foo/": dir.resolve("pkgs/foo/"), |
+ "foo/bar": dir.resolve("pkgs/foo/bar"), |
+ }); |
+ }, files: files); |
+ } |
+ |
+ { |
+ // With a non-file: URI, the lookup assumes a packges/ dir. |
+ var files = {"sub": { "main": testMain }, |
+ ".packages": "foo:pkgs/foo/", |
+ "pkgs": fooPackage}; |
+ await test("http: implicit packages dir", (run, dir) async { |
+ // Expect implicitly detected .package file. |
+ await run(dir.resolve("sub/main.dart"), { |
+ "iroot": dir.resolve("sub/packages/"), |
+ // "foo": null, |
+ "foo/": dir.resolve("sub/packages/foo/"), |
+ "foo/bar": dir.resolve("sub/packages/foo/bar"), |
+ "foo.x": null, |
+ }); |
+ }, http: files); |
+ } |
+ } |
+ |
+ |
+ if (failingTests.isNotEmpty) { |
+ print("Errors found in tests:\n ${failingTests.join("\n ")}\n"); |
+ exit(255); |
+ } |
+ asyncEnd(); |
+} |
+ |
+// --------------------------------------------------------- |
+// Helper fnuctionality. |
floitsch
2016/05/18 13:36:32
functionality
|
+ |
+var failingTests = new Set(); |
+ |
+Future testDirect(name, Function test, {Map files, Map http}) async { |
+ var tmpDir; |
+ var https; |
+ if (files != null) tmpDir = createFiles(files); |
+ if (http != null) https = await startServer(http); |
+ runTest(main, expectations, {Uri root, Uri config}) async { |
floitsch
2016/05/18 13:36:32
Please keep one line before and after nested funct
|
+ var output = await runDart(main, root: root, config: config); |
+ // These expectations are default. If not overridden the value will be |
+ // expected to be null. That is, you can't avoid testing the actual |
+ // value of these, you can only change what value to expect. |
+ // For values not included here (commented out), the result is not tested |
+ // unless a value (maybe null) is provided. |
+ var expects = { |
+ "pconf": null, |
+ "proot": null, |
+ "iconf": null, |
+ "iconf": null, |
+ // "foo": null, |
+ "foo/": null, |
+ "foo/bar": null, |
+ "foo.x": "qux", |
+ }..addAll(expectations); |
+ match(JSON.decode(output), expects, name); |
+ } |
+ // Call test with 1-3 parameters depending on which setups were made. |
+ try { |
+ if (https == null) { |
+ if (files == null) { |
+ await test(runTest); // Data-schemes only, no files or http available. |
+ } else { |
+ await test(runTest, tmpDir.uri); |
+ } |
+ } else if (files == null) { |
+ await test(runTest, serverUri(https)); |
+ } else { |
+ 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
|
+ } |
+ } catch (e) { |
+ 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.
|
+ } finally { |
+ if (https != null) await https.close(); |
+ if (tmpDir != null) tmpDir.deleteSync(recursive: true); |
+ } |
+} |
+ |
+ |
+ |
+/// Test that the output of running testMain matches the expectations. |
+/// |
+/// The output is a string which is parse as a JSON literal. |
+/// The resulting map is always mapping strings to strings, or possibly `null`. |
+/// The expectations can have non-string values other than null, |
+/// they are `toString`'ed before being compared (so the caller can use a URI |
+/// or a File/Directory directly as an expectation). |
+void match(Map actuals, Map expectations, String name) { |
+ for (var key in expectations.keys) { |
+ var expectation = expectations[key]; |
+ var actual = actuals[key]; |
+ if (expectation?.toString() != actual) { |
+ print("ERROR: $name: $key: Expected: <$expectation> Found: <$actual>"); |
+ failingTests.add(name); |
+ } |
+ } |
+} |
+ |
+/// Script that prints the current state and the result of resolving |
+/// a few package URIs. This script will be invoked in different settings, |
+/// and the result will be parsed and compared to the expectations. |
+const String testMain = r""" |
+import "dart:convert" show JSON; |
+import "dart:io" show Platform, Directory; |
+import "dart:isolate" show Isolate; |
+import "package:foo/foo.dart" deferred as foo; |
+main(_) async { |
+ String platformRoot = await Platform.packageRoot; |
+ String platformConfig = await Platform.packageConfig; |
+ Directory cwd = Directory.current; |
+ Uri script = Platform.script; |
+ Uri isolateRoot = await Isolate.packageRoot; |
+ Uri isolateConfig = await Isolate.packageConfig; |
+ Uri base = Uri.base; |
+ Uri res1 = await Isolate.resolvePackageUri(Uri.parse("package:foo")); |
+ Uri res2 = await Isolate.resolvePackageUri(Uri.parse("package:foo/")); |
+ Uri res3 = await Isolate.resolvePackageUri(Uri.parse("package:foo/bar")); |
+ String fooX = await foo |
+ .loadLibrary() |
+ .timeout(const Duration(seconds: 1)) |
+ .then((_) => foo.x, onError: (_) => null); |
+ print(JSON.encode({ |
+ "cwd": cwd.path, |
+ "base": base?.toString(), |
+ "script": script?.toString(), |
+ "proot": platformRoot, |
+ "pconf": platformConfig, |
+ "iroot" : isolateRoot?.toString(), |
+ "iconf" : isolateConfig?.toString(), |
+ "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.
|
+ "foo/": res2?.toString(), |
+ "foo/bar": res3?.toString(), |
+ "foo.x": fooX?.toString(), |
+ })); |
+} |
+"""; |
+ |
+/// Script that spawns a new Isolate using Isolate.spawnUri. |
+/// |
+/// Takes URI of target isolate, package config and package root as |
+/// command line arguments. Any further arguments are forwarded to the |
+/// spawned isolate. |
+const String spawnUriMain = r""" |
+import "dart:isolate"; |
+main(args) async { |
+ Uri target = Uri.parse(args[0]); |
+ Uri conf = args.length > 1 && args[1].isNotEmpty ? Uri.parse(args[1]) : null; |
+ Uri root = args.length > 2 && args[2].isNotEmpty ? Uri.parse(args[2]) : null; |
+ var restArgs = args.skip(3).toList(); |
+ var isolate = await Isolate.spawnUri(target, restArgs, |
+ packageRoot: root, packageConfig: conf, paused: true); |
+ // Wait for isolate to exit before exiting the main isolate. |
+ var done = new RawReceivePort(); |
+ done.handler = (_) { done.close(); }; |
+ isolate.addExitHandler(done.sendPort); |
+ isolate.resume(isolate.pauseCapability); |
+} |
+"""; |
+ |
+/// Script that spawns a new Isolate using Isolate.spawn. |
+const String spawnMain = r""" |
+import "dart:isolate"; |
+import "testmain.dart" as test; |
+main() async { |
+ var isolate = await Isolate.spawn(test.main, [], paused: true); |
+ // Wait for isolate to exit before exiting the main isolate. |
+ var done = new RawReceivePort(); |
+ done.handler = (_) { done.close(); }; |
+ isolate.addExitHandler(done.sendPort); |
+ isolate.resume(isolate.pauseCapability); |
+} |
+"""; |
+ |
+/// A package directory containing only one package, "foo", with one file. |
+const Map fooPackage = const { "foo": const { "foo": "var x = 'qux';" }}; |
+ |
+/// Runs the Dart executable with the provided parameters. |
+/// |
+/// Optionally sets CWD before running the script. |
+/// Captures and returns the output. |
+Future<String> runDart(Uri script, |
+ {Uri root, Uri config, List<String> scriptArgs}) async { |
+ var executable = Platform.executable; |
+ var args = []; |
+ if (root != null) args..add("-p")..add(root.toString()); |
+ if (config != null) args..add("--packages=$config"); |
+ args.add(script.toString()); |
+ args.addAll(scriptArgs ?? []); |
+ return Process.run(executable, args).then((results) { |
+ if (results.exitCode != 0) { |
+ throw results.stderr; |
+ } |
+ return results.stdout; |
+ }); |
+} |
+ |
+/// Creates a temporary directory and a number of files and subdirectories. |
+/// |
+/// The [content] is the content of the directory itself. The map keys are |
+/// names and the values are either strings that represent Dart file contents |
+/// or maps that represent subdirectories. |
+/// Subdirectories may include a package directory. If [packageDir] |
+/// is provided, a `.packages` file is created for the content of that |
+/// directory. |
+Directory createFiles(Map content, |
+ [String packageDir]) { |
+ Directory createDir(Directory base, String name) { |
floitsch
2016/05/18 13:36:32
New lines before and after nested functions.
|
+ Directory newDir = new Directory(p.join(base.path, name)); |
+ newDir.createSync(); |
+ return newDir; |
+ } |
+ void createTextFile(Directory base, String name, String content) { |
+ File newFile = new File(p.join(base.path, name)); |
+ newFile.writeAsStringSync(content); |
+ } |
+ void create(Directory dir, Map map) { |
+ for (var name in map.keys) { |
+ var content = map[name]; |
+ if (content is String) { |
+ // If the name starts with "." it's a .packages file, otherwise it's |
+ // a dart file. Those are the only files we care about in this test. |
+ createTextFile(dir, |
+ name.startsWith(".") ? name : name + ".dart", |
+ content); |
+ } else { |
+ var subdir = createDir(dir, name); |
+ create(subdir, content); |
+ } |
+ } |
+ } |
+ var tempDir = Directory.systemTemp.createTempSync("pftest-${tmpDirCounter++}-"); |
floitsch
2016/05/18 13:36:32
long line.
|
+ create(tempDir, content); |
+ if (packageDir != null) { |
+ Map packages = content[packageDir]; |
+ var entries = |
+ packages.keys.map((key) => "$key:$packageDir/$key").join("\n"); |
+ createTextFile(tempDir, ".packages", entries); |
+ } |
+ return tempDir; |
+} |
+ |
+// Counter used to avoid reusing temporary directory names. |
+int tmpDirCounter = 0; |
+ |
+Future<HttpServer> startServer(Map files) async { |
+ return (await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0)) |
+ ..forEach((request) { |
+ var result = files; |
+ onFailure: { |
+ for (var part in request.uri.pathSegments) { |
+ if (part.endsWith(".dart")) { |
+ part = part.substring(0, part.length - 5); |
+ } |
+ if (result is Map) { |
+ result = result[part]; |
+ } else { |
+ break onFailure; |
+ } |
+ } |
+ if (result is String) { |
+ request.response..write(result) |
+ ..close(); |
+ return; |
+ } |
+ } |
+ request.response..statusCode = HttpStatus.NOT_FOUND |
+ ..close(); |
+ }); |
+} |
+ |
+Uri serverUri(HttpServer server) => |
+ new Uri(scheme: "http", |
+ host: server.address.address, |
+ port: server.port, |
+ path: "/"); |