Chromium Code Reviews| Index: tests/standalone/packages_file_test.dart |
| diff --git a/tests/standalone/packages_file_test.dart b/tests/standalone/packages_file_test.dart |
| index 4d695eae5283ac4768547a75595358dcbbd799a0..c87acc603f3cd8a9b793cccb5ecab68cd455ced8 100644 |
| --- a/tests/standalone/packages_file_test.dart |
| +++ b/tests/standalone/packages_file_test.dart |
| @@ -8,284 +8,610 @@ import "dart:convert" show JSON; |
| import "package:path/path.dart" as p; |
| import "package:async_helper/async_helper.dart"; |
| +/// Root directory of generated files. |
| +/// Path contains trailing slash. |
| +/// Each configuration gets its own sub-directory. |
| +Directory fileRoot; |
| +/// Shared HTTP server serving the files in [httpFiles]. |
| +/// Each configuration gets its own "sub-dir" entry in `httpFiles`. |
| +HttpServer httpServer; |
| +/// Directory structure served by HTTP server. |
| +Map<String, dynamic> httpFiles = {}; |
| +/// List of configurations. |
| +List<Configuration> configurations = []; |
| +/// Collection of failing tests and their failure messages. |
| +/// |
| +/// Each test may fail in more than one way. |
| +var failingTests = <String, List<String>>{}; |
| + |
| main() async { |
| asyncStart(); |
| + await setUp(); |
| + |
| + |
| + await runTests(); |
| + await runTests([spawn]); |
| + await runTests([spawn, spawn]); |
| + await runTests([spawnUriInherit]); |
| + await runTests([spawnUriInherit, spawn]); |
| + await runTests([spawn, spawnUriInherit]); |
| + |
| + // Test that spawnUri can reproduce the behavior of VM command line parameters |
| + // exactly. |
| + for (var other in configurations) { |
| + await runTests([spawnUriOther(other)]); |
| + } |
| + // Test that spawning a new VM with file paths instead of URIs as arguments |
| + // gives the same URIs in the internal values. |
| + await runTests([asPath]); |
| + |
| + |
| + await tearDown(); |
| + |
| + if (failingTests.isNotEmpty) { |
| + print("Errors found in tests:"); |
| + failingTests.forEach((test, actual) { |
| + print("$test:\n ${actual.join("\n ")}"); |
| + }); |
| + exit(255); |
| + } |
| + |
| + asyncEnd(); |
| +} |
| + |
| +/// Test running the test of the configuration through [Isolate.spawn]. |
| +/// |
| +/// This should not change the expected results compared to running it |
| +/// directly. |
| +Configuration spawn(Configuration conf) { |
| + // TEMPORARY FIX FOR ISSUE #26555 (http://dartbug.com/26555) |
|
floitsch
2016/06/02 14:17:46
Somewhere you should have
"TODO(26555)"
this is th
Lasse Reichstein Nielsen
2016/06/06 11:36:32
Done.
|
| + if (conf.expect["iroot"] == null && |
| + conf.expect["iconf"] == null && |
| + conf.expect["pconf"] != null) { |
| + // The spawned isolate will do a search for a package file or root, |
| + // which is not what the original did. Skip test for now. |
| + return null; |
| + } |
| + // REMOVE WHEN ISSUE FIXED! |
| + return conf.update( |
| + description: conf.description + "/spawn", |
| + main: "spawnMain", |
| + newArgs: [conf.mainType], |
| + // TEMPORARY FIX FOR ISSUE #26555 (http://dartbug.com/26555) |
| + expect: { |
| + "proot": conf.expect["iroot"], |
| + "pconf": conf.expect["iconf"], |
| + } |
| + // REMOVE WHEN ISSUE FIXED! |
| + ); |
| +} |
| + |
| +/// Tests running a spawnUri on top of the configuration before testing. |
| +/// |
| +/// The `spawnUri` call has no explicit root or config parameter, and |
| +/// shouldn't search for one, so it implicitly inherits the current isolate's |
| +/// actual root or configuration. |
| +Configuration spawnUriInherit(Configuration conf) { |
| + if (conf.expect["iroot"] == null && |
| + conf.expect["iconf"] == null && |
| + conf.expect["pconf"] != null) { |
| + // This means that the specified configuration file didn't exist. |
| + // spawning a new URI to "inherit" that will actually do an automatic |
| + // package resolution search with results that are unpredictable. |
| + // That behavior will be tested in a setting where we have more control over |
| + // the files around the spawned URI. |
| + return null; |
| + } |
| + return conf.update( |
| + description: conf.description + "/spawnUri-inherit", |
| + main: "spawnUriMain", |
| + newArgs: [conf.mainFile, "", "", "false"], |
| + expect: { |
| + "proot": conf.expect["iroot"], |
| + "pconf": conf.expect["iconf"], |
| + } |
| + ); |
| +} |
| + |
| +/// Tests running a spawnUri with an explicit configuration different |
| +/// from the original configuration. |
| +/// |
| +/// Duplicates the explicit parameters as arguments to the spawned isolate. |
| +ConfigurationTransformer spawnUriOther(Configuration other) { |
| + return (Configuration conf) { |
| + bool search = (other.config == null) && (other.root == null); |
| + return conf.update( |
| + description: "${conf.description} -spawnUri-> ${other.description}", |
| + main: "spawnUriMain", |
| + newArgs: [other.mainFile, |
| + other.config ?? "", other.root ?? "", "$search"], |
| + expect: other.expect |
| + ); |
| + }; |
| +} |
| + |
| + |
| +/// Convert command line parameters to file paths. |
| +/// |
| +/// This only works on the command line, not with `spawnUri`. |
| +Configuration asPath(Configuration conf) { |
| + bool change = false; |
| + String toPath(String string) { |
|
floitsch
2016/06/02 14:17:46
new line before and after nested functions.
Lasse Reichstein Nielsen
2016/06/06 11:36:32
Done.
|
| + if (string == null) return null; |
| + if (string.startsWith("file:")) { |
| + change = true; |
| + return new File.fromUri(Uri.parse(string)).path; |
| + } |
| + return string; |
| + } |
| + var mainFile = toPath(conf.mainFile); |
| + var root = toPath(conf.root); |
| + var config = toPath(conf.config); |
| + if (!change) return null; |
| + return conf.update(description: conf.description + "/as path", |
| + mainFile: mainFile, root: root, config: config); |
| +} |
| + |
| +/// -------------------------------------------------------------- |
| + |
| + |
| +Future setUp() async { |
| + fileRoot = createTempDir(); |
| + httpServer = await startServer(httpFiles); |
| + createConfigurations(); |
| +} |
| + |
| +Future tearDown() async { |
| + //fileRoot.deleteSync(recursive: true); |
| + await httpServer.close(); |
| +} |
| + |
| +typedef Configuration ConfigurationTransformer(Configuration conf); |
| + |
| +Future runTests([List<ConfigurationTransformer> transformations]) async { |
| + outer: for (var config in configurations) { |
| + if (transformations != null) { |
| + for (int i = transformations.length - 1; i >= 0; i--) { |
| + config = transformations[i](config); |
| + if (config == null) { |
| + continue outer; // Can be used to skip some tests. |
| + } |
| + } |
| + } |
| + await testConfiguration(config); |
| + } |
| +} |
| + |
| +/// Creates a combination of configurations for running the Dart VM. |
| +/// |
| +/// The combinations covers most configurations of implicit and explicit |
| +/// package configurations over both file: and http: file sources. |
| +/// It also specifies the expected values of the following for a VM |
| +/// run in that configuration. |
| +/// |
| +/// * `Process.packageRoot` |
| +/// * `Process.packageConfig` |
| +/// * `Isolate.packageRoot` |
| +/// * `Isolate.packageRoot` |
| +/// * `Isolate.resolvePacakgeUri` of various inputs. |
| +/// * A variable defined in a library loaded using a `package:` URI. |
| +/// |
| +/// The configurations all have URIs as `root`, `config` and `mainFile` strings, |
| +/// has empty argument lists and run the `main.dart` main file. |
|
floitsch
2016/06/02 14:17:46
have
Lasse Reichstein Nielsen
2016/06/06 11:36:32
Done.
|
| +void createConfigurations() { |
| + add(String description, String mainDir, {String root, String config, |
| + Map file, Map http, Map expect}) { |
| + var id = freshName("conf"); |
| + |
| + file ??= {}; |
| + http ??= {}; |
| + |
| + // Fix-up paths. |
| + String fileUri = fileRoot.uri.resolve("$id/").toString(); |
| + String httpUri = |
| + "http://${httpServer.address.address}:${httpServer.port}/$id/"; |
| + |
| + String fixPath(String path) { |
| + return path?.replaceAllMapped(fileHttpRegexp, (match) { |
| + if (path.startsWith("%file/", match.start)) return fileUri; |
| + return httpUri; |
| + }); |
| + } |
| + |
| + String fixPaths(Map dirs) { |
| + for (var name in dirs.keys) { |
| + var value = dirs[name]; |
| + if (value is Map) { |
| + Map subDir = value; |
| + fixPaths(subDir); |
| + } else { |
| + var newValue = fixPath(value); |
| + if (newValue != value) dirs[name] = newValue; |
| + } |
| + } |
| + } |
| + |
| + if (!mainDir.endsWith("/")) mainDir += "/"; |
| + // Insert main files into the main-dir map. |
| + Map mainDirMap; |
| + { |
| + if (mainDir.startsWith("%file/")) { |
| + mainDirMap = file; |
| + } else { |
| + mainDirMap = http; |
| + |
| + } |
| + var parts = mainDir.split('/'); |
| + for (int i = 1; i < parts.length - 1; i++) { |
| + var dirName = parts[i]; |
| + mainDirMap = mainDirMap[dirName] ?? (mainDirMap[dirName] = {}); |
| + } |
| + } |
| + mainDir = fixPath(mainDir); |
| + |
| + mainDirMap["main"] = testMain; |
| + mainDirMap["spawnMain"] = spawnMain.replaceAll("%mainDir/", mainDir); |
| + mainDirMap["spawnUriMain"] = spawnUriMain; |
| + |
| + root = fixPath(root); |
| + config = fixPath(config); |
| + fixPaths(file); |
| + fixPaths(http); |
| + // 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. |
| + fixPaths(expect); |
| + |
| + expect = { |
| + "pconf": null, |
| + "proot": null, |
| + "iconf": null, |
| + "iroot": null, |
| + // "foo": null, |
| + "foo/": null, |
| + "foo/bar": null, |
| + "foo.x": "qux", |
| + }..addAll(expect ?? const {}); |
| + |
| + // Add http files to the http server. |
| + if (http.isNotEmpty) { |
| + httpFiles[id] = http; |
| + } |
| + // Add file files to the file system. |
| + if (file.isNotEmpty) { |
| + createFiles(fileRoot, id, file); |
| + } |
| + |
| + configurations.add(new Configuration( |
| + description: description, |
| + root: root, |
| + config: config, |
| + mainFile: mainDir + "main.dart", |
| + args: const [], |
| + expect: expect)); |
| + } |
| // The `test` function can generate file or http resources. |
| // It replaces "%file/" with URI of the root directory of generated files and |
| // "%http/" with the URI of the HTTP server's root in appropriate contexts |
| // (all file contents and parameters). |
| - // With no specified resolutiuon and no implicit .packages or packages/ |
| - // available, nothing can be resolved and the package can't be imported. |
| - await test("file: no resolution", |
| - "%file/main.dart", |
| - file: {"main": testMain}, |
| - expect: {"foo.x": null}); |
| - |
| - // An HTTP script with no ".packages" file assumes a "packages" dir. |
| - // All packages are resolved relative to that dir, whether it exists or not. |
| - await test("http: no resolution", "%http/main.dart", |
| - http: {"main": testMain}, |
| - expect: { |
| - "iroot": "%http/packages/", |
| - // "foo": null, |
| - "foo/": "%http/packages/foo/", |
| - "foo/bar": "%http/packages/foo/bar", |
| - "foo.x": null, |
| - }); |
| - |
| - // A number of tests which behave similarly whether run as local files or |
| - // over HTTP. |
| + // Tests that only use one scheme to access files. |
| for (var scheme in ["file", "http"]) { |
| /// Run a test in the current scheme. |
| /// |
| /// The files are served either through HTTP or in a local directory. |
| /// Use "%$scheme/" to refer to the root of the served files. |
| - testScheme(name, main, {expect, files, args, root, config}) { |
| - return test("$scheme: $name", main, expect: expect, |
| - root: root, config: config, args: args, |
| - file: scheme == "file" ? files : null, |
| - http: scheme == "http" ? files : null); |
| + addScheme(description, main, {expect, files, args, root, config}) { |
| + add("$scheme/$description", main, expect: expect, |
| + root: root, config: config, |
| + file: (scheme == "file") ? files : null, |
| + http: (scheme == "http") ? files : null); |
| } |
| { |
| - var files = {"main": testMain, "packages": fooPackage}; |
| - // Expect implicitly detected package dir. |
| - await testScheme("implicit packages dir","%$scheme/main.dart", |
| + // No parameters, no .packages files or packages/ dir. |
| + // A "file:" source realizes there is no configuration and can't resolve |
| + // any packages, but a "http:" source assumes a "packages/" directory. |
| + addScheme("no resolution", |
| + "%$scheme/", |
| + files: {}, |
| + expect: (scheme == "file") ? { |
| + "foo.x": null |
| + } : { |
| + "iroot": "%http/packages/", |
| + "foo/": "%http/packages/foo/", |
| + "foo/bar": "%http/packages/foo/bar", |
| + "foo.x": null, |
| + }); |
| + } |
| + |
| + { |
| + // No parameters, no .packages files, |
| + // packages/ dir exists and is detected. |
| + var files = {"packages": fooPackage}; |
| + addScheme("implicit packages dir","%$scheme/", |
| files: files, |
| expect: { |
| "iroot": "%$scheme/packages/", |
| - // "foo": null, |
| "foo/": "%$scheme/packages/foo/", |
| "foo/bar": "%$scheme/packages/foo/bar", |
| }); |
| } |
| { |
| - var files = {"sub": {"main": testMain, "packages": fooPackage}, |
| - ".packages": ""}; |
| - // Expect implicitly detected package dir. |
| + // No parameters, no .packages files in current dir, but one in parent, |
| + // packages/ dir exists and is used. |
| + // |
| // Should not detect the .packages file in parent directory. |
| // That file is empty, so if it is used, the system cannot resolve "foo". |
| - await testScheme("implicit packages dir 2", "%$scheme/sub/main.dart", |
| + var files = {"sub": {"packages": fooPackage}, |
| + ".packages": ""}; |
| + addScheme("implicit packages dir overrides parent .packages", |
| + "%$scheme/sub/", |
| files: files, |
| expect: { |
| "iroot": "%$scheme/sub/packages/", |
| - // "foo": null, |
| "foo/": "%$scheme/sub/packages/foo/", |
| "foo/bar": "%$scheme/sub/packages/foo/bar", |
| + // "foo.x": "qux", // Blocked by issue http://dartbug.com/26482 |
| }); |
| } |
| { |
| - var files = {"main": testMain, |
| - ".packages": "foo:pkgs/foo/", |
| + // No parameters, a .packages file next to entry is found and used. |
| + // A packages/ directory is ignored. |
| + var files = {".packages": "foo:pkgs/foo/", |
| + "packages": {}, |
| "pkgs": fooPackage}; |
| - await testScheme("implicit .packages file", "%$scheme/main.dart", |
| + addScheme("implicit .packages file", "%$scheme/", |
| files: files, |
| expect: { |
| "iconf": "%$scheme/.packages", |
| - // "foo": null, |
| "foo/": "%$scheme/pkgs/foo/", |
| "foo/bar": "%$scheme/pkgs/foo/bar", |
| }); |
| } |
| { |
| - var files = {"main": testMain, |
| - ".packages": "foo:packages/foo/", |
| - "packages": fooPackage, |
| + // No parameters, a .packages file in parent dir, no packages/ dir. |
| + // With a file: URI, find the .packages file. |
| + // WIth a http: URI, assume a packages/ dir. |
| + var files = {"sub": {}, |
| + ".packages": "foo:pkgs/foo/", |
| + "pkgs": fooPackage}; |
| + addScheme(".packages file in parent", "%$scheme/sub/", |
| + files: files, |
| + expect: (scheme == "file") ? { |
| + "iconf": "%file/.packages", |
| + "foo/": "%file/pkgs/foo/", |
| + "foo/bar": "%file/pkgs/foo/bar", |
| + } : { |
| + "iroot": "%http/sub/packages/", |
| + "foo/": "%http/sub/packages/foo/", |
| + "foo/bar": "%http/sub/packages/foo/bar", |
| + "foo.x": null, |
| + }); |
| + } |
| + |
| + { |
| + // Specified package root that doesn't exist. |
| + // Ignores existing .packages file and packages/ dir. |
| + addScheme("explicit root not there", |
| + "%$scheme/", |
| + files: {"packages": fooPackage, |
| + ".packages": "foo:%$scheme/packages/"}, |
| + root: "%$scheme/notthere/", |
| + expect: { |
| + "proot": "%$scheme/notthere/", |
| + "iroot": "%$scheme/notthere/", |
| + "foo/": "%$scheme/notthere/foo/", |
| + "foo/bar": "%$scheme/notthere/foo/bar", |
| + "foo.x": null, |
| + }); |
| + } |
| + |
| + { |
| + // Specified package config that doesn't exist. |
| + // Ignores existing .packages file and packages/ dir. |
| + addScheme("explicit config not there", |
| + "%$scheme/", |
| + files: {".packages": "foo:packages/foo/", |
| + "packages": fooPackage}, |
| + config: "%$scheme/.notthere", |
| + expect: { |
| + "pconf": "%$scheme/.notthere", |
| + "iconf": null, // <- Only there if actually loaded (unspecified). |
| + "foo/": null, |
| + "foo/bar": null, |
| + "foo.x": null, |
| + }); |
| + } |
| + |
| + { |
| + // Specified package root with no trailing slash. |
| + // The Platform.packageRoot and Isolate.packageRoot has a trailing slash. |
| + var files = {".packages": "foo:packages/foo/", |
| + "packages": {}, |
| "pkgs": fooPackage}; |
| - await testScheme("explicit package root, no slash", "%$scheme/main.dart", |
| + addScheme("explicit package root, no slash", "%$scheme/", |
| files: files, |
| root: "%$scheme/pkgs", |
| expect: { |
| "proot": "%$scheme/pkgs/", |
| "iroot": "%$scheme/pkgs/", |
| - // "foo": null, |
| "foo/": "%$scheme/pkgs/foo/", |
| "foo/bar": "%$scheme/pkgs/foo/bar", |
| }); |
| } |
| { |
| - var files = {"main": testMain, |
| - ".packages": "foo:packages/foo/", |
| - "packages": fooPackage, |
| + // Specified package root with trailing slash. |
| + var files = {".packages": "foo:packages/foo/", |
| + "packages": {}, |
| "pkgs": fooPackage}; |
| - await testScheme("explicit package root, slash", "%$scheme/main.dart", |
| + addScheme("explicit package root, slash", "%$scheme/", |
| files: files, |
| root: "%$scheme/pkgs", |
| expect: { |
| "proot": "%$scheme/pkgs/", |
| "iroot": "%$scheme/pkgs/", |
| - // "foo": null, |
| "foo/": "%$scheme/pkgs/foo/", |
| "foo/bar": "%$scheme/pkgs/foo/bar", |
| }); |
| } |
| { |
| - var files = {"main": testMain, |
| - ".packages": "foo:packages/foo/", |
| - "packages": fooPackage, |
| + // Specified package config. |
| + var files = {".packages": "foo:packages/foo/", |
| + "packages": {}, |
| ".pkgs": "foo:pkgs/foo/", |
| "pkgs": fooPackage}; |
| - await testScheme("explicit package config file", "%$scheme/main.dart", |
| + addScheme("explicit package config file", "%$scheme/", |
| files: files, |
| config: "%$scheme/.pkgs", |
| expect: { |
| "pconf": "%$scheme/.pkgs", |
| "iconf": "%$scheme/.pkgs", |
| - // "foo": null, |
| "foo/": "%$scheme/pkgs/foo/", |
| "foo/bar": "%$scheme/pkgs/foo/bar", |
| }); |
| } |
| { |
| + // Specified package config as data: URI. |
| /// The package config can be specified as a data: URI. |
| /// (In that case, relative URI references in the config file won't work). |
| - var files = {"main": testMain, |
| - ".packages": "foo:packages/foo/", |
| - "packages": fooPackage, |
| + var files = {".packages": "foo:packages/foo/", |
| + "packages": {}, |
| "pkgs": fooPackage}; |
| var dataUri = "data:,foo:%$scheme/pkgs/foo/\n"; |
| - await testScheme("explicit data: config file", "%$scheme/main.dart", |
| + addScheme("explicit data: config file", "%$scheme/", |
| files: files, |
| config: dataUri, |
| expect: { |
| "pconf": dataUri, |
| "iconf": dataUri, |
| - // "foo": null, |
| "foo/": "%$scheme/pkgs/foo/", |
| "foo/bar": "%$scheme/pkgs/foo/bar", |
| }); |
| } |
| } |
| - { |
| - // With a file: URI, the lookup checks for a .packages file in superdirs |
| - // when it fails to find a ,packages file or packages/ directory next to |
| - // the entry point. |
| - var files = {"sub": { "main": testMain }, |
| - ".packages": "foo:pkgs/foo/", |
| - "pkgs": fooPackage}; |
| - await test("file: implicit .packages file in ..", "%file/sub/main.dart", |
| - file: files, |
| - expect: { |
| - "iconf": "%file/.packages", |
| - // "foo": null, |
| - "foo/": "%file/pkgs/foo/", |
| - "foo/bar": "%file/pkgs/foo/bar", |
| - }); |
| - } |
| - |
| - { |
| - // With a non-file: URI, the lookup assumes a packges/ dir. |
| - // The absence of a .packages file next to the entry point means |
| - // that the resolution assumes a packages directory, whether it exists or |
| - // not. It should not find the .packages file in the parent directory. |
| - var files = {"sub": { "main": testMain }, |
| - ".packages": "foo:pkgs/foo/", |
| - "pkgs": fooPackage}; |
| - await test("http: implicit packages dir", "%http/sub/main.dart", |
| - http: files, |
| - expect: { |
| - "iroot": "%http/sub/packages/", |
| - // "foo": null, |
| - "foo/": "%http/sub/packages/foo/", |
| - "foo/bar": "%http/sub/packages/foo/bar", |
| - "foo.x": null, |
| - }); |
| - } |
| - |
| - |
| - if (failingTests.isNotEmpty) { |
| - print("Errors found in tests:\n ${failingTests.join("\n ")}\n"); |
| - exit(255); |
| + /// Tests where there are files on both http: and file: sources. |
| + |
| + for (var entryScheme in const ["file", "http"]) { |
| + for (var pkgScheme in const ["file", "http"]) { |
| + // Package root. |
| + if (entryScheme != pkgScheme) { |
| + // Package dir and entry point on different schemes. |
| + var files = {}; |
| + var https = {}; |
| + (entryScheme == "file" ? files : https)["main"] = testMain; |
| + (pkgScheme == "file" ? files : https)["pkgs"] = fooPackage; |
| + add("$pkgScheme pkg/$entryScheme main", "%$entryScheme/", |
| + file: files, http: https, |
| + root: "%$pkgScheme/pkgs/", |
| + expect: { |
| + "proot": "%$pkgScheme/pkgs/", |
| + "iroot": "%$pkgScheme/pkgs/", |
| + "foo/": "%$pkgScheme/pkgs/foo/", |
| + "foo/bar": "%$pkgScheme/pkgs/foo/bar", |
| + "foo.x": "qux", |
| + }); |
| + } |
| + // Package config. The configuration file may also be on either source. |
| + for (var configScheme in const ["file", "http"]) { |
| + // Don't do the boring stuff! |
| + if (entryScheme == configScheme && entryScheme == pkgScheme) continue; |
| + // Package config, packages and entry point not all on same scheme. |
| + var files = {}; |
| + var https = {}; |
| + (entryScheme == "file" ? files : https)["main"] = testMain; |
| + (configScheme == "file" ? files : https)[".pkgs"] = |
| + "foo:%$pkgScheme/pkgs/foo/\n"; |
| + (pkgScheme == "file" ? files : https)["pkgs"] = fooPackage; |
| + add("$pkgScheme pkg/$configScheme config/$entryScheme main", |
| + "%$entryScheme/", |
| + file: files, http: https, |
| + config: "%$configScheme/.pkgs", |
| + expect: { |
| + "pconf": "%$configScheme/.pkgs", |
| + "iconf": "%$configScheme/.pkgs", |
| + "foo/": "%$pkgScheme/pkgs/foo/", |
| + "foo/bar": "%$pkgScheme/pkgs/foo/bar", |
| + "foo.x": "qux", |
| + }); |
| + } |
| + } |
| } |
| - asyncEnd(); |
| } |
| + |
| // --------------------------------------------------------- |
| // Helper functionality. |
| -var failingTests = new Set(); |
| - |
| var fileHttpRegexp = new RegExp(r"%(?:file|http)/"); |
| -Future test(String name, String main, |
| - {String root, String config, List<String> args, |
| - Map file, Map http, Map expect}) async { |
| - // Default values that are easily recognized in output. |
| - String fileRoot = "<no files configured>"; |
| - String httpRoot = "<no http server configured>"; |
| - |
| - /// Replaces markers `%file/` and `%http/` with the actual locations. |
| - /// |
| - /// Accepts a `null` [source] and returns `null` again. |
| - String fixPaths(String source) { |
| - if (source == null) return null; |
| - var result = source.replaceAllMapped(fileHttpRegexp, (match) { |
| - if (source.startsWith("file", match.start + 1)) return fileRoot; |
| - return httpRoot; |
| - }); |
| - return result; |
| - } |
| - |
| - // Set up temporary directory or HTTP server. |
| - Directory tmpDir; |
| - var https; |
| - if (file != null) { |
| - tmpDir = createTempDir(); |
| - fileRoot = new Uri.directory(tmpDir.path).toString(); |
| - } |
| - if (http != null) { |
| - https = await startServer(http, fixPaths); |
| - httpRoot = "http://${https.address.address}:${https.port}/"; |
| - } |
| - if (file != null) { |
| - // Create files after both roots are known, to allow file content |
| - // to refer to the them. |
| - createFiles(tmpDir, file, fixPaths); |
| - } |
| - |
| +/// Executes a test in a configuration. |
| +/// |
| +/// The test must specify which main file to use |
| +/// (`main`, `spawnMain` or `spawnUriMain`) |
| +/// and any arguments which will be used by `spawnMain` and `spawnUriMain`. |
| +/// |
| +/// The [expect] map may be used to override the expectations of the |
| +/// configuration on a value-by-value basis. Passing, e.g., `{"pconf": null}` |
| +/// will override only the `pconf` (`Platform.packageConfig`) expectation. |
| +Future testConfiguration(Configuration conf) async { |
| + print("-- ${conf.description}"); |
| + //print("DEBUG:\n$conf"); |
| + var description = conf.description; |
| try { |
| - var output = await runDart(fixPaths(main), |
| - root: fixPaths(root), |
| - config: fixPaths(config), |
| - scriptArgs: args?.map(fixPaths)); |
| - // 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, |
| - "iroot": null, |
| - // "foo": null, |
| - "foo/": null, |
| - "foo/bar": null, |
| - "foo.x": "qux", |
| - }..addAll(expect); |
| - match(JSON.decode(output), expects, fixPaths, name); |
| + var output = await execDart(conf.mainFile, |
| + root: conf.root, |
| + config: conf.config, |
| + scriptArgs: conf.args); |
| + match(JSON.decode(output), conf.expect, description, output); |
| } catch (e, s) { |
| - // Unexpected error calling runDart or parsing the result. |
| + // Unexpected error calling execDart or parsing the result. |
| // Report it and continue. |
| - print("ERROR running $name: $e\n$s"); |
| - failingTests.add(name); |
| - } finally { |
| - if (https != null) await https.close(); |
| - if (tmpDir != null) tmpDir.deleteSync(recursive: true); |
| + print("ERROR running $description: $e\n$s"); |
| + failingTests.putIfAbsent(description, () => []).add("$e"); |
| } |
| } |
| +/// Creates a new isolate which inherits the configuration of the current |
| +/// isolate, which is set up according to the configuration. |
| +/// |
| +/// Inheriting is chosen by passing no `packageConfig`, `packageRoot` or |
| +/// `automaticPackageResolution` parameters to `spawnUri`. |
| +/// That defaults to passing the current configuration taken from either |
| +/// [Isolate.packageRoot] or [Isolate.packageConfig], or passing `true` as |
| +/// `automaticPackageResolution` of both of the previous values are missing. |
| +void testSpawnUriInherit(Configuration conf) { |
| + // TODO |
| + // Implicit root/config becomes explicit parameters when |
| + // inheriting using spawnUri with no explicit parameters. |
| + // expect["proot"] = expect["iroot"]; |
| + // expect["pconf"] = expect["iconf"]; |
| +} |
| + |
| +/// Creates a new isolate which does not inherit the configuration |
| +/// of the current isolate, but passes the existing configuration |
| +/// explicitly. |
| +void testSpawnUri(Configuration conf) { |
| +// bool search = conf.root == null && conf.config == null; |
|
floitsch
2016/06/02 14:17:46
Why is this commented out?
Lasse Reichstein Nielsen
2016/06/06 11:36:32
Ak, old code, no longer used. Neither is the one a
|
| +// conf.args.insertAllAt(0, [conf.main, conf.config ?? "", conf.root ?? "", |
| +// "$search"]) |
| +// conf.main = conf.insertAt(conf.main, |
| +// freshName("spawnUriMain"), |
| +// spawnUriMain); |
| +} |
| + |
| /// Test that the output of running testMain matches the expectations. |
| /// |
| /// The output is a string which is parse as a JSON literal. |
| @@ -293,14 +619,14 @@ Future test(String name, String main, |
| /// 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 fixPaths(String expectation), |
| - String name) { |
| +void match(Map actuals, Map expectations, String desc, String actualJson) { |
| for (var key in expectations.keys) { |
| - var expectation = fixPaths(expectations[key]?.toString()); |
| + var expectation = expectations[key]?.toString(); |
| var actual = actuals[key]; |
| if (expectation != actual) { |
| - print("ERROR: $name: $key: Expected: <$expectation> Found: <$actual>"); |
| - failingTests.add(name); |
| + print("ERROR: $desc: $key: Expected: <$expectation> Found: <$actual>"); |
| + failingTests.putIfAbsent(desc, ()=>[]).add( |
| + "$key: $expectation != $actual"); |
| } |
| } |
| } |
| @@ -346,60 +672,89 @@ main(_) async { |
| /// 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. |
| +/// Takes URI of target isolate, package config, package root and |
| +/// automatic package resolution-flag parameters as command line arguments. |
| +/// Any further arguments are forwarded to the spawned isolate. |
| const String spawnUriMain = r""" |
| import "dart:isolate"; |
| +import "dart:async"; |
| 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); |
| + Uri config = args[1].isNotEmpty ? Uri.parse(args[1]) : null; |
| + Uri root = args[2].isNotEmpty ? Uri.parse(args[2]) : null; |
| + bool search = args[3] == "true"; |
| + var restArgs = args.skip(4).toList(); |
| + // Port keeps isolate alive until spawned isolate terminates. |
| + var port = new RawReceivePort(); |
| + port.handler = (res) async { |
| + port.close(); // Close on exit or first error. |
| + if (res != null) { |
| + await new Future.error(res[0], new StackTrace.fromString(res[1])); |
| + } |
| + }; |
| + Isolate.spawnUri(target, restArgs, null, |
| + packageRoot: root, packageConfig: config, |
| + automaticPackageResolution: search, |
| + onError: port.sendPort, onExit: port.sendPort); |
| } |
| """; |
| /// Script that spawns a new Isolate using Isolate.spawn. |
| +/// |
| +/// Uses the first argument to select which target to spawn. |
| +/// Should be either "test", "uri" or "spawn". |
| const String spawnMain = r""" |
| +import "dart:async"; |
| 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); |
| +import "%mainDir/main.dart" as test; |
| +import "%mainDir/spawnUriMain.dart" as spawnUri; |
| +main(List<String> args) async { |
| + // Port keeps isolate alive until spawned isolate terminates. |
| + var port = new RawReceivePort(); |
| + port.handler = (res) async { |
| + port.close(); // Close on exit or first error. |
| + if (res != null) { |
| + await new Future.error(res[0], new StackTrace.fromString(res[1])); |
| + } |
| + }; |
| + var arg = args.first; |
| + var rest = args.skip(1).toList(); |
| + var target; |
| + if (arg == "main") { |
| + target = test.main; |
| + } else if (arg == "spawnUriMain") { |
| + target = spawnUri.main; |
| + } else { |
| + target = main; |
| + } |
| + Isolate.spawn(target, rest, onError: port.sendPort, onExit: port.sendPort); |
| } |
| """; |
| /// 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. |
| /// |
| /// Captures and returns the output. |
| -Future<String> runDart(String script, |
| - {String root, String config, |
| - Iterable<String> scriptArgs}) async { |
| +Future<String> execDart(String script, |
| + {String root, String config, |
| + Iterable<String> scriptArgs}) async { |
| + var checked = false; |
| + assert((checked = true)); |
| // TODO: Find a way to change CWD before running script. |
| var executable = Platform.executable; |
| var args = []; |
| - if (root != null) args..add("-p")..add(root); |
| - if (config != null) args..add("--packages=$config"); |
| + if (checked) args.add("--checked"); |
| + if (root != null) args.add("--package-root=$root"); |
| + if (config != null) args.add("--packages=$config"); |
| args.add(script); |
| if (scriptArgs != null) { |
| args.addAll(scriptArgs); |
| } |
| return Process.run(executable, args).then((results) { |
| - if (results.exitCode != 0) { |
| + if (results.exitCode != 0 || results.stderr.isNotEmpty) { |
| throw results.stderr; |
| } |
| return results.stdout; |
| @@ -411,11 +766,7 @@ Future<String> runDart(String script, |
| /// 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. |
| -void createFiles(Directory tempDir, Map content, String fixPaths(String text), |
| - [String packageDir]) { |
| +void createFiles(Directory tempDir, String subDir, Map content) { |
| Directory createDir(Directory base, String name) { |
| Directory newDir = new Directory(p.join(base.path, name)); |
| newDir.createSync(); |
| @@ -424,7 +775,7 @@ void createFiles(Directory tempDir, Map content, String fixPaths(String text), |
| void createTextFile(Directory base, String name, String content) { |
| File newFile = new File(p.join(base.path, name)); |
| - newFile.writeAsStringSync(fixPaths(content)); |
| + newFile.writeAsStringSync(content); |
| } |
| void createRecursive(Directory dir, Map map) { |
| @@ -444,14 +795,7 @@ void createFiles(Directory tempDir, Map content, String fixPaths(String text), |
| } |
| } |
| - createRecursive(tempDir, content); |
| - if (packageDir != null) { |
| - // Unused? |
| - Map packages = content[packageDir]; |
| - var entries = |
| - packages.keys.map((key) => "$key:$packageDir/$key").join("\n"); |
| - createTextFile(tempDir, ".packages", entries); |
| - } |
| + createRecursive(createDir(tempDir, subDir), content); |
| } |
| /// Start an HTTP server which serves a directory/file structure. |
| @@ -462,7 +806,7 @@ void createFiles(Directory tempDir, Map content, String fixPaths(String text), |
| /// and a `String` value is a text file. |
| /// The file contents are run through [fixPaths] to allow them to be self- |
| /// referential. |
| -Future<HttpServer> startServer(Map files, String fixPaths(String text)) async { |
| +Future<HttpServer> startServer(Map files) async { |
| return (await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0)) |
| ..forEach((request) { |
| var result = files; |
| @@ -478,7 +822,7 @@ Future<HttpServer> startServer(Map files, String fixPaths(String text)) async { |
| } |
| } |
| if (result is String) { |
| - request.response..write(fixPaths(result)) |
| + request.response..write(result) |
| ..close(); |
| return; |
| } |
| @@ -488,11 +832,142 @@ Future<HttpServer> startServer(Map files, String fixPaths(String text)) async { |
| }); |
| } |
| -// Counter used to avoid reusing temporary directory names. |
| -// Some platforms are timer based, and creating two temp-dirs withing a short |
| -// duration may cause a collision. |
| -int tmpDirCounter = 0; |
| +// Counter used to avoid reusing temporary file or directory names. |
| +// |
| +// Used when adding extra files to an existing directory structure, |
| +// and when creating temporary directories. |
| +// |
| +// Some platform temporary-directory implementations are timer based, |
| +// and creating two temp-dirs withing a short duration may cause a collision. |
| +int tmpNameCounter = 0; |
| + |
| +// Fresh file name. |
| +String freshName([String base = "tmp"]) => "$base${tmpNameCounter++}"; |
| Directory createTempDir() { |
| - return Directory.systemTemp.createTempSync("pftest-${tmpDirCounter++}-"); |
| + return Directory.systemTemp.createTempSync(freshName("pftest-")); |
| +} |
| + |
| +typedef void ConfigUpdate(Configuration configuration); |
| + |
| +/// The configuration for a single test. |
| +class Configuration { |
| + /// The "description" of the test - a description of the set-up. |
| + final String description; |
| + /// The package root parameter passed to the Dart isolate. |
| + /// |
| + /// At most one of [root] and [config] should be supplied. If both are |
| + /// omitted, a VM will search for a packages file or dir. |
| + final String root; |
| + /// The package configuration file location passed to the Dart isolate. |
| + final String config; |
| + /// Path to the main file to run. |
| + final String mainFile; |
| + /// List of arguments to pass to the main function. |
| + final List<String> args; |
| + /// The expected values for `Platform.package{Root,Config}`, |
| + /// `Isolate.package{Root,Config}` and resolution of package URIs |
| + /// in a `foo` package. |
| + /// |
| + /// The results are found by running the `main.dart` file inside [mainDir]. |
| + /// The tests can run this file after doing other `spawn` or `spawnUri` calls. |
| + final Map expect; |
| + |
| + Configuration({this.description, |
| + this.root, |
| + this.config, |
| + this.mainFile, |
| + this.args, |
| + this.expect}); |
| + |
| + // Gets the type of main file, one of `main`, `spawnMain` or `spawnUriMain`. |
| + String get mainType { |
| + var lastSlash = mainFile.lastIndexOf("/"); |
| + if (lastSlash < 0) { |
| + // Assume it's a Windows path. |
| + lastSlash = mainFile.lastIndexOf(r"\"); |
| + } |
| + var name = mainFile.substring(lastSlash + 1, mainFile.length - 5); |
| + assert(name == "main" || name == "spawnMain" || name == "spawnUriMain"); |
| + return name; |
| + } |
| + |
| + String get mainPath { |
| + var lastSlash = mainFile.lastIndexOf("/"); |
| + if (lastSlash < 0) { |
| + // Assume it's a Windows path. |
| + lastSlash = mainFile.lastIndexOf(r"\"); |
| + } |
| + return mainFile.substring(0, lastSlash + 1); |
| + } |
| + |
| + /// Create a new configuration from the old one. |
| + /// |
| + /// [description] is new description. |
| + /// |
| + /// [main] is one of `main`, `spawnMain` or `spawnUriMain`, and changes |
| + /// the [Configuration.mainFile] to a different file in the same directory. |
| + /// |
| + /// [mainFile] overrides [Configuration.mainFile] completely, and ignores |
| + /// [main]. |
| + /// |
| + /// [newArgs] are prepended to the existing [Configuration.args]. |
| + /// |
| + /// [args] overrides [Configuration.args] completely and ignores [newArgs]. |
| + /// |
| + /// [expect] overrides individual expectations. |
| + /// |
| + /// [root] and [config] overrides the existing values. |
| + Configuration update({ |
| + String description, |
| + String main, |
| + String mainFile, |
| + String root, |
| + String config, |
| + List<String> args, |
| + List<String> newArgs, |
| + Map expect |
| + }) { |
| + return new Configuration( |
| + description: description ?? this.description, |
| + root: root ?? this.root, |
| + config: config ?? this.config, |
| + mainFile: mainFile ?? |
| + ((main == null) ? this.mainFile : "${this.mainPath}$main.dart"), |
| + args: args ?? ([]..addAll(newArgs ?? const [])..addAll(this.args)), |
| + expect: expect == null |
| + ? this.expect |
| + : new Map.from(this.expect)..addAll(expect ?? const {})); |
| + } |
| + |
| + // For debugging. |
| + String toString() { |
| + return "Configuration($description\n" |
| + " root : $root\n" |
| + " config: $config\n" |
| + " main : $mainFile\n" |
| + " args : ${args.map((x) => '"$x"').join(" ")}\n" |
| + ") : expect {\n${expect.keys.map((k) => |
| + ' "$k"'.padRight(6) + ":${JSON.encode(expect[k])}\n").join()}" |
| + "}"; |
| + } |
| +} |
| + |
| + |
| +// Inserts the file with generalized [name] at [path] with [content]. |
| +// |
| +// The [path] is a directory where the file is created. It must start with |
| +// either '%file/' or '%http/' to select the structure to put it into. |
| +// |
| +// The [name] should not have a trailing ".dart" for Dart files. Any file |
| +// not starting with "." is assumed to be a ".dart" file. |
| +void insertFileAt(Map file, Map http, |
| + String path, String name, String content) { |
| + var parts = path.split('/').toList(); |
| + var dir = (parts[0] == "%file") ? file : http; |
| + for (var i = 1; i < parts.length - 1; i++) { |
| + var entry = parts[i]; |
| + dir = dir[entry] ?? (dir[entry] = {}); |
| + } |
| + dir[name] = content; |
| } |