Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 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 | 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 import "dart:async"; | 5 import "dart:async"; |
| 6 import "dart:io"; | 6 import "dart:io"; |
| 7 import "dart:convert" show JSON; | 7 import "dart:convert" show JSON; |
| 8 import "package:path/path.dart" as p; | 8 import "package:path/path.dart" as p; |
| 9 import "package:async_helper/async_helper.dart"; | 9 import "package:async_helper/async_helper.dart"; |
| 10 | 10 |
| 11 /// Root directory of generated files. | |
| 12 /// Path contains trailing slash. | |
| 13 /// Each configuration gets its own sub-directory. | |
| 14 Directory fileRoot; | |
| 15 /// Shared HTTP server serving the files in [httpFiles]. | |
| 16 /// Each configuration gets its own "sub-dir" entry in `httpFiles`. | |
| 17 HttpServer httpServer; | |
| 18 /// Directory structure served by HTTP server. | |
| 19 Map<String, dynamic> httpFiles = {}; | |
| 20 /// List of configurations. | |
| 21 List<Configuration> configurations = []; | |
| 22 /// Collection of failing tests and their failure messages. | |
| 23 /// | |
| 24 /// Each test may fail in more than one way. | |
| 25 var failingTests = <String, List<String>>{}; | |
| 26 | |
| 11 main() async { | 27 main() async { |
| 12 asyncStart(); | 28 asyncStart(); |
| 13 | 29 await setUp(); |
| 14 // The `test` function can generate file or http resources. | 30 |
| 15 // It replaces "%file/" with URI of the root directory of generated files and | 31 |
| 16 // "%http/" with the URI of the HTTP server's root in appropriate contexts | 32 await runTests(); |
| 17 // (all file contents and parameters). | 33 await runTests([spawn]); |
| 18 | 34 await runTests([spawn, spawn]); |
| 19 // With no specified resolutiuon and no implicit .packages or packages/ | 35 await runTests([spawnUriInherit]); |
| 20 // available, nothing can be resolved and the package can't be imported. | 36 await runTests([spawnUriInherit, spawn]); |
| 21 await test("file: no resolution", | 37 await runTests([spawn, spawnUriInherit]); |
| 22 "%file/main.dart", | 38 |
| 23 file: {"main": testMain}, | 39 // Test that spawnUri can reproduce the behavior of VM command line parameters |
| 24 expect: {"foo.x": null}); | 40 // exactly. |
| 25 | 41 for (var other in configurations) { |
| 26 // An HTTP script with no ".packages" file assumes a "packages" dir. | 42 await runTests([spawnUriOther(other)]); |
| 27 // All packages are resolved relative to that dir, whether it exists or not. | 43 } |
| 28 await test("http: no resolution", "%http/main.dart", | 44 // Test that spawning a new VM with file paths instead of URIs as arguments |
| 29 http: {"main": testMain}, | 45 // gives the same URIs in the internal values. |
| 46 await runTests([asPath]); | |
| 47 | |
| 48 | |
| 49 await tearDown(); | |
| 50 | |
| 51 if (failingTests.isNotEmpty) { | |
| 52 print("Errors found in tests:"); | |
| 53 failingTests.forEach((test, actual) { | |
| 54 print("$test:\n ${actual.join("\n ")}"); | |
| 55 }); | |
| 56 exit(255); | |
| 57 } | |
| 58 | |
| 59 asyncEnd(); | |
| 60 } | |
| 61 | |
| 62 /// Test running the test of the configuration through [Isolate.spawn]. | |
| 63 /// | |
| 64 /// This should not change the expected results compared to running it | |
| 65 /// directly. | |
| 66 Configuration spawn(Configuration conf) { | |
| 67 // 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.
| |
| 68 if (conf.expect["iroot"] == null && | |
| 69 conf.expect["iconf"] == null && | |
| 70 conf.expect["pconf"] != null) { | |
| 71 // The spawned isolate will do a search for a package file or root, | |
| 72 // which is not what the original did. Skip test for now. | |
| 73 return null; | |
| 74 } | |
| 75 // REMOVE WHEN ISSUE FIXED! | |
| 76 return conf.update( | |
| 77 description: conf.description + "/spawn", | |
| 78 main: "spawnMain", | |
| 79 newArgs: [conf.mainType], | |
| 80 // TEMPORARY FIX FOR ISSUE #26555 (http://dartbug.com/26555) | |
| 30 expect: { | 81 expect: { |
| 31 "iroot": "%http/packages/", | 82 "proot": conf.expect["iroot"], |
| 32 // "foo": null, | 83 "pconf": conf.expect["iconf"], |
| 33 "foo/": "%http/packages/foo/", | 84 } |
| 34 "foo/bar": "%http/packages/foo/bar", | 85 // REMOVE WHEN ISSUE FIXED! |
| 35 "foo.x": null, | 86 ); |
| 36 }); | 87 } |
| 37 | 88 |
| 38 // A number of tests which behave similarly whether run as local files or | 89 /// Tests running a spawnUri on top of the configuration before testing. |
| 39 // over HTTP. | 90 /// |
| 40 for (var scheme in ["file", "http"]) { | 91 /// The `spawnUri` call has no explicit root or config parameter, and |
| 41 | 92 /// shouldn't search for one, so it implicitly inherits the current isolate's |
| 42 /// Run a test in the current scheme. | 93 /// actual root or configuration. |
| 43 /// | 94 Configuration spawnUriInherit(Configuration conf) { |
| 44 /// The files are served either through HTTP or in a local directory. | 95 if (conf.expect["iroot"] == null && |
| 45 /// Use "%$scheme/" to refer to the root of the served files. | 96 conf.expect["iconf"] == null && |
| 46 testScheme(name, main, {expect, files, args, root, config}) { | 97 conf.expect["pconf"] != null) { |
| 47 return test("$scheme: $name", main, expect: expect, | 98 // This means that the specified configuration file didn't exist. |
| 48 root: root, config: config, args: args, | 99 // spawning a new URI to "inherit" that will actually do an automatic |
| 49 file: scheme == "file" ? files : null, | 100 // package resolution search with results that are unpredictable. |
| 50 http: scheme == "http" ? files : null); | 101 // That behavior will be tested in a setting where we have more control over |
| 51 } | 102 // the files around the spawned URI. |
| 52 | 103 return null; |
| 53 { | 104 } |
| 54 var files = {"main": testMain, "packages": fooPackage}; | 105 return conf.update( |
| 55 // Expect implicitly detected package dir. | 106 description: conf.description + "/spawnUri-inherit", |
| 56 await testScheme("implicit packages dir","%$scheme/main.dart", | 107 main: "spawnUriMain", |
| 57 files: files, | 108 newArgs: [conf.mainFile, "", "", "false"], |
| 58 expect: { | 109 expect: { |
| 59 "iroot": "%$scheme/packages/", | 110 "proot": conf.expect["iroot"], |
| 60 // "foo": null, | 111 "pconf": conf.expect["iconf"], |
| 61 "foo/": "%$scheme/packages/foo/", | 112 } |
| 62 "foo/bar": "%$scheme/packages/foo/bar", | 113 ); |
| 63 }); | 114 } |
| 64 } | 115 |
| 65 | 116 /// Tests running a spawnUri with an explicit configuration different |
| 66 { | 117 /// from the original configuration. |
| 67 var files = {"sub": {"main": testMain, "packages": fooPackage}, | 118 /// |
| 68 ".packages": ""}; | 119 /// Duplicates the explicit parameters as arguments to the spawned isolate. |
| 69 // Expect implicitly detected package dir. | 120 ConfigurationTransformer spawnUriOther(Configuration other) { |
| 70 // Should not detect the .packages file in parent directory. | 121 return (Configuration conf) { |
| 71 // That file is empty, so if it is used, the system cannot resolve "foo". | 122 bool search = (other.config == null) && (other.root == null); |
| 72 await testScheme("implicit packages dir 2", "%$scheme/sub/main.dart", | 123 return conf.update( |
| 73 files: files, | 124 description: "${conf.description} -spawnUri-> ${other.description}", |
| 74 expect: { | 125 main: "spawnUriMain", |
| 75 "iroot": "%$scheme/sub/packages/", | 126 newArgs: [other.mainFile, |
| 76 // "foo": null, | 127 other.config ?? "", other.root ?? "", "$search"], |
| 77 "foo/": "%$scheme/sub/packages/foo/", | 128 expect: other.expect |
| 78 "foo/bar": "%$scheme/sub/packages/foo/bar", | 129 ); |
| 79 }); | 130 }; |
| 80 } | 131 } |
| 81 | 132 |
| 82 { | 133 |
| 83 var files = {"main": testMain, | 134 /// Convert command line parameters to file paths. |
| 84 ".packages": "foo:pkgs/foo/", | 135 /// |
| 85 "pkgs": fooPackage}; | 136 /// This only works on the command line, not with `spawnUri`. |
| 86 await testScheme("implicit .packages file", "%$scheme/main.dart", | 137 Configuration asPath(Configuration conf) { |
| 87 files: files, | 138 bool change = false; |
| 88 expect: { | 139 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.
| |
| 89 "iconf": "%$scheme/.packages", | 140 if (string == null) return null; |
| 90 // "foo": null, | 141 if (string.startsWith("file:")) { |
| 91 "foo/": "%$scheme/pkgs/foo/", | 142 change = true; |
| 92 "foo/bar": "%$scheme/pkgs/foo/bar", | 143 return new File.fromUri(Uri.parse(string)).path; |
| 93 }); | 144 } |
| 94 } | 145 return string; |
| 95 | 146 } |
| 96 { | 147 var mainFile = toPath(conf.mainFile); |
| 97 var files = {"main": testMain, | 148 var root = toPath(conf.root); |
| 98 ".packages": "foo:packages/foo/", | 149 var config = toPath(conf.config); |
| 99 "packages": fooPackage, | 150 if (!change) return null; |
| 100 "pkgs": fooPackage}; | 151 return conf.update(description: conf.description + "/as path", |
| 101 await testScheme("explicit package root, no slash", "%$scheme/main.dart", | 152 mainFile: mainFile, root: root, config: config); |
| 102 files: files, | 153 } |
| 103 root: "%$scheme/pkgs", | 154 |
| 104 expect: { | 155 /// -------------------------------------------------------------- |
| 105 "proot": "%$scheme/pkgs/", | 156 |
| 106 "iroot": "%$scheme/pkgs/", | 157 |
| 107 // "foo": null, | 158 Future setUp() async { |
| 108 "foo/": "%$scheme/pkgs/foo/", | 159 fileRoot = createTempDir(); |
| 109 "foo/bar": "%$scheme/pkgs/foo/bar", | 160 httpServer = await startServer(httpFiles); |
| 110 }); | 161 createConfigurations(); |
| 111 } | 162 } |
| 112 | 163 |
| 113 { | 164 Future tearDown() async { |
| 114 var files = {"main": testMain, | 165 //fileRoot.deleteSync(recursive: true); |
| 115 ".packages": "foo:packages/foo/", | 166 await httpServer.close(); |
| 116 "packages": fooPackage, | 167 } |
| 117 "pkgs": fooPackage}; | 168 |
| 118 await testScheme("explicit package root, slash", "%$scheme/main.dart", | 169 typedef Configuration ConfigurationTransformer(Configuration conf); |
| 119 files: files, | 170 |
| 120 root: "%$scheme/pkgs", | 171 Future runTests([List<ConfigurationTransformer> transformations]) async { |
| 121 expect: { | 172 outer: for (var config in configurations) { |
| 122 "proot": "%$scheme/pkgs/", | 173 if (transformations != null) { |
| 123 "iroot": "%$scheme/pkgs/", | 174 for (int i = transformations.length - 1; i >= 0; i--) { |
| 124 // "foo": null, | 175 config = transformations[i](config); |
| 125 "foo/": "%$scheme/pkgs/foo/", | 176 if (config == null) { |
| 126 "foo/bar": "%$scheme/pkgs/foo/bar", | 177 continue outer; // Can be used to skip some tests. |
| 127 }); | 178 } |
| 128 } | 179 } |
| 129 | 180 } |
| 130 { | 181 await testConfiguration(config); |
| 131 var files = {"main": testMain, | 182 } |
| 132 ".packages": "foo:packages/foo/", | 183 } |
| 133 "packages": fooPackage, | 184 |
| 134 ".pkgs": "foo:pkgs/foo/", | 185 /// Creates a combination of configurations for running the Dart VM. |
| 135 "pkgs": fooPackage}; | 186 /// |
| 136 await testScheme("explicit package config file", "%$scheme/main.dart", | 187 /// The combinations covers most configurations of implicit and explicit |
| 137 files: files, | 188 /// package configurations over both file: and http: file sources. |
| 138 config: "%$scheme/.pkgs", | 189 /// It also specifies the expected values of the following for a VM |
| 139 expect: { | 190 /// run in that configuration. |
| 140 "pconf": "%$scheme/.pkgs", | 191 /// |
| 141 "iconf": "%$scheme/.pkgs", | 192 /// * `Process.packageRoot` |
| 142 // "foo": null, | 193 /// * `Process.packageConfig` |
| 143 "foo/": "%$scheme/pkgs/foo/", | 194 /// * `Isolate.packageRoot` |
| 144 "foo/bar": "%$scheme/pkgs/foo/bar", | 195 /// * `Isolate.packageRoot` |
| 145 }); | 196 /// * `Isolate.resolvePacakgeUri` of various inputs. |
| 146 } | 197 /// * A variable defined in a library loaded using a `package:` URI. |
| 147 | 198 /// |
| 148 { | 199 /// The configurations all have URIs as `root`, `config` and `mainFile` strings, |
| 149 /// The package config can be specified as a data: URI. | 200 /// 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.
| |
| 150 /// (In that case, relative URI references in the config file won't work). | 201 void createConfigurations() { |
| 151 var files = {"main": testMain, | 202 add(String description, String mainDir, {String root, String config, |
| 152 ".packages": "foo:packages/foo/", | 203 Map file, Map http, Map expect}) { |
| 153 "packages": fooPackage, | 204 var id = freshName("conf"); |
| 154 "pkgs": fooPackage}; | 205 |
| 155 var dataUri = "data:,foo:%$scheme/pkgs/foo/\n"; | 206 file ??= {}; |
| 156 await testScheme("explicit data: config file", "%$scheme/main.dart", | 207 http ??= {}; |
| 157 files: files, | 208 |
| 158 config: dataUri, | 209 // Fix-up paths. |
| 159 expect: { | 210 String fileUri = fileRoot.uri.resolve("$id/").toString(); |
| 160 "pconf": dataUri, | 211 String httpUri = |
| 161 "iconf": dataUri, | 212 "http://${httpServer.address.address}:${httpServer.port}/$id/"; |
| 162 // "foo": null, | 213 |
| 163 "foo/": "%$scheme/pkgs/foo/", | 214 String fixPath(String path) { |
| 164 "foo/bar": "%$scheme/pkgs/foo/bar", | 215 return path?.replaceAllMapped(fileHttpRegexp, (match) { |
| 165 }); | 216 if (path.startsWith("%file/", match.start)) return fileUri; |
| 166 } | 217 return httpUri; |
| 167 } | |
| 168 | |
| 169 { | |
| 170 // With a file: URI, the lookup checks for a .packages file in superdirs | |
| 171 // when it fails to find a ,packages file or packages/ directory next to | |
| 172 // the entry point. | |
| 173 var files = {"sub": { "main": testMain }, | |
| 174 ".packages": "foo:pkgs/foo/", | |
| 175 "pkgs": fooPackage}; | |
| 176 await test("file: implicit .packages file in ..", "%file/sub/main.dart", | |
| 177 file: files, | |
| 178 expect: { | |
| 179 "iconf": "%file/.packages", | |
| 180 // "foo": null, | |
| 181 "foo/": "%file/pkgs/foo/", | |
| 182 "foo/bar": "%file/pkgs/foo/bar", | |
| 183 }); | 218 }); |
| 184 } | 219 } |
| 185 | 220 |
| 186 { | 221 String fixPaths(Map dirs) { |
| 187 // With a non-file: URI, the lookup assumes a packges/ dir. | 222 for (var name in dirs.keys) { |
| 188 // The absence of a .packages file next to the entry point means | 223 var value = dirs[name]; |
| 189 // that the resolution assumes a packages directory, whether it exists or | 224 if (value is Map) { |
| 190 // not. It should not find the .packages file in the parent directory. | 225 Map subDir = value; |
| 191 var files = {"sub": { "main": testMain }, | 226 fixPaths(subDir); |
| 192 ".packages": "foo:pkgs/foo/", | 227 } else { |
| 193 "pkgs": fooPackage}; | 228 var newValue = fixPath(value); |
| 194 await test("http: implicit packages dir", "%http/sub/main.dart", | 229 if (newValue != value) dirs[name] = newValue; |
| 195 http: files, | 230 } |
| 196 expect: { | 231 } |
| 197 "iroot": "%http/sub/packages/", | 232 } |
| 198 // "foo": null, | 233 |
| 199 "foo/": "%http/sub/packages/foo/", | 234 if (!mainDir.endsWith("/")) mainDir += "/"; |
| 200 "foo/bar": "%http/sub/packages/foo/bar", | 235 // Insert main files into the main-dir map. |
| 201 "foo.x": null, | 236 Map mainDirMap; |
| 202 }); | 237 { |
| 203 } | 238 if (mainDir.startsWith("%file/")) { |
| 204 | 239 mainDirMap = file; |
| 205 | 240 } else { |
| 206 if (failingTests.isNotEmpty) { | 241 mainDirMap = http; |
| 207 print("Errors found in tests:\n ${failingTests.join("\n ")}\n"); | 242 |
| 208 exit(255); | 243 } |
| 209 } | 244 var parts = mainDir.split('/'); |
| 210 asyncEnd(); | 245 for (int i = 1; i < parts.length - 1; i++) { |
| 211 } | 246 var dirName = parts[i]; |
| 212 | 247 mainDirMap = mainDirMap[dirName] ?? (mainDirMap[dirName] = {}); |
| 213 // --------------------------------------------------------- | 248 } |
| 214 // Helper functionality. | 249 } |
| 215 | 250 mainDir = fixPath(mainDir); |
| 216 var failingTests = new Set(); | 251 |
| 217 | 252 mainDirMap["main"] = testMain; |
| 218 var fileHttpRegexp = new RegExp(r"%(?:file|http)/"); | 253 mainDirMap["spawnMain"] = spawnMain.replaceAll("%mainDir/", mainDir); |
| 219 | 254 mainDirMap["spawnUriMain"] = spawnUriMain; |
| 220 Future test(String name, String main, | 255 |
| 221 {String root, String config, List<String> args, | 256 root = fixPath(root); |
| 222 Map file, Map http, Map expect}) async { | 257 config = fixPath(config); |
| 223 // Default values that are easily recognized in output. | 258 fixPaths(file); |
| 224 String fileRoot = "<no files configured>"; | 259 fixPaths(http); |
| 225 String httpRoot = "<no http server configured>"; | |
| 226 | |
| 227 /// Replaces markers `%file/` and `%http/` with the actual locations. | |
| 228 /// | |
| 229 /// Accepts a `null` [source] and returns `null` again. | |
| 230 String fixPaths(String source) { | |
| 231 if (source == null) return null; | |
| 232 var result = source.replaceAllMapped(fileHttpRegexp, (match) { | |
| 233 if (source.startsWith("file", match.start + 1)) return fileRoot; | |
| 234 return httpRoot; | |
| 235 }); | |
| 236 return result; | |
| 237 } | |
| 238 | |
| 239 // Set up temporary directory or HTTP server. | |
| 240 Directory tmpDir; | |
| 241 var https; | |
| 242 if (file != null) { | |
| 243 tmpDir = createTempDir(); | |
| 244 fileRoot = new Uri.directory(tmpDir.path).toString(); | |
| 245 } | |
| 246 if (http != null) { | |
| 247 https = await startServer(http, fixPaths); | |
| 248 httpRoot = "http://${https.address.address}:${https.port}/"; | |
| 249 } | |
| 250 if (file != null) { | |
| 251 // Create files after both roots are known, to allow file content | |
| 252 // to refer to the them. | |
| 253 createFiles(tmpDir, file, fixPaths); | |
| 254 } | |
| 255 | |
| 256 try { | |
| 257 var output = await runDart(fixPaths(main), | |
| 258 root: fixPaths(root), | |
| 259 config: fixPaths(config), | |
| 260 scriptArgs: args?.map(fixPaths)); | |
| 261 // These expectations are default. If not overridden the value will be | 260 // These expectations are default. If not overridden the value will be |
| 262 // expected to be null. That is, you can't avoid testing the actual | 261 // expected to be null. That is, you can't avoid testing the actual |
| 263 // value of these, you can only change what value to expect. | 262 // value of these, you can only change what value to expect. |
| 264 // For values not included here (commented out), the result is not tested | 263 // For values not included here (commented out), the result is not tested |
| 265 // unless a value (maybe null) is provided. | 264 // unless a value (maybe null) is provided. |
| 266 var expects = { | 265 fixPaths(expect); |
| 267 "pconf": null, | 266 |
| 268 "proot": null, | 267 expect = { |
| 269 "iconf": null, | 268 "pconf": null, |
| 270 "iroot": null, | 269 "proot": null, |
| 271 // "foo": null, | 270 "iconf": null, |
| 272 "foo/": null, | 271 "iroot": null, |
| 273 "foo/bar": null, | 272 // "foo": null, |
| 274 "foo.x": "qux", | 273 "foo/": null, |
| 275 }..addAll(expect); | 274 "foo/bar": null, |
| 276 match(JSON.decode(output), expects, fixPaths, name); | 275 "foo.x": "qux", |
| 276 }..addAll(expect ?? const {}); | |
| 277 | |
| 278 // Add http files to the http server. | |
| 279 if (http.isNotEmpty) { | |
| 280 httpFiles[id] = http; | |
| 281 } | |
| 282 // Add file files to the file system. | |
| 283 if (file.isNotEmpty) { | |
| 284 createFiles(fileRoot, id, file); | |
| 285 } | |
| 286 | |
| 287 configurations.add(new Configuration( | |
| 288 description: description, | |
| 289 root: root, | |
| 290 config: config, | |
| 291 mainFile: mainDir + "main.dart", | |
| 292 args: const [], | |
| 293 expect: expect)); | |
| 294 } | |
| 295 | |
| 296 // The `test` function can generate file or http resources. | |
| 297 // It replaces "%file/" with URI of the root directory of generated files and | |
| 298 // "%http/" with the URI of the HTTP server's root in appropriate contexts | |
| 299 // (all file contents and parameters). | |
| 300 | |
| 301 // Tests that only use one scheme to access files. | |
| 302 for (var scheme in ["file", "http"]) { | |
| 303 | |
| 304 /// Run a test in the current scheme. | |
| 305 /// | |
| 306 /// The files are served either through HTTP or in a local directory. | |
| 307 /// Use "%$scheme/" to refer to the root of the served files. | |
| 308 addScheme(description, main, {expect, files, args, root, config}) { | |
| 309 add("$scheme/$description", main, expect: expect, | |
| 310 root: root, config: config, | |
| 311 file: (scheme == "file") ? files : null, | |
| 312 http: (scheme == "http") ? files : null); | |
| 313 } | |
| 314 | |
| 315 { | |
| 316 // No parameters, no .packages files or packages/ dir. | |
| 317 // A "file:" source realizes there is no configuration and can't resolve | |
| 318 // any packages, but a "http:" source assumes a "packages/" directory. | |
| 319 addScheme("no resolution", | |
| 320 "%$scheme/", | |
| 321 files: {}, | |
| 322 expect: (scheme == "file") ? { | |
| 323 "foo.x": null | |
| 324 } : { | |
| 325 "iroot": "%http/packages/", | |
| 326 "foo/": "%http/packages/foo/", | |
| 327 "foo/bar": "%http/packages/foo/bar", | |
| 328 "foo.x": null, | |
| 329 }); | |
| 330 } | |
| 331 | |
| 332 { | |
| 333 // No parameters, no .packages files, | |
| 334 // packages/ dir exists and is detected. | |
| 335 var files = {"packages": fooPackage}; | |
| 336 addScheme("implicit packages dir","%$scheme/", | |
| 337 files: files, | |
| 338 expect: { | |
| 339 "iroot": "%$scheme/packages/", | |
| 340 "foo/": "%$scheme/packages/foo/", | |
| 341 "foo/bar": "%$scheme/packages/foo/bar", | |
| 342 }); | |
| 343 } | |
| 344 | |
| 345 { | |
| 346 // No parameters, no .packages files in current dir, but one in parent, | |
| 347 // packages/ dir exists and is used. | |
| 348 // | |
| 349 // Should not detect the .packages file in parent directory. | |
| 350 // That file is empty, so if it is used, the system cannot resolve "foo". | |
| 351 var files = {"sub": {"packages": fooPackage}, | |
| 352 ".packages": ""}; | |
| 353 addScheme("implicit packages dir overrides parent .packages", | |
| 354 "%$scheme/sub/", | |
| 355 files: files, | |
| 356 expect: { | |
| 357 "iroot": "%$scheme/sub/packages/", | |
| 358 "foo/": "%$scheme/sub/packages/foo/", | |
| 359 "foo/bar": "%$scheme/sub/packages/foo/bar", | |
| 360 // "foo.x": "qux", // Blocked by issue http://dartbug.com/26482 | |
| 361 }); | |
| 362 } | |
| 363 | |
| 364 { | |
| 365 // No parameters, a .packages file next to entry is found and used. | |
| 366 // A packages/ directory is ignored. | |
| 367 var files = {".packages": "foo:pkgs/foo/", | |
| 368 "packages": {}, | |
| 369 "pkgs": fooPackage}; | |
| 370 addScheme("implicit .packages file", "%$scheme/", | |
| 371 files: files, | |
| 372 expect: { | |
| 373 "iconf": "%$scheme/.packages", | |
| 374 "foo/": "%$scheme/pkgs/foo/", | |
| 375 "foo/bar": "%$scheme/pkgs/foo/bar", | |
| 376 }); | |
| 377 } | |
| 378 | |
| 379 { | |
| 380 // No parameters, a .packages file in parent dir, no packages/ dir. | |
| 381 // With a file: URI, find the .packages file. | |
| 382 // WIth a http: URI, assume a packages/ dir. | |
| 383 var files = {"sub": {}, | |
| 384 ".packages": "foo:pkgs/foo/", | |
| 385 "pkgs": fooPackage}; | |
| 386 addScheme(".packages file in parent", "%$scheme/sub/", | |
| 387 files: files, | |
| 388 expect: (scheme == "file") ? { | |
| 389 "iconf": "%file/.packages", | |
| 390 "foo/": "%file/pkgs/foo/", | |
| 391 "foo/bar": "%file/pkgs/foo/bar", | |
| 392 } : { | |
| 393 "iroot": "%http/sub/packages/", | |
| 394 "foo/": "%http/sub/packages/foo/", | |
| 395 "foo/bar": "%http/sub/packages/foo/bar", | |
| 396 "foo.x": null, | |
| 397 }); | |
| 398 } | |
| 399 | |
| 400 { | |
| 401 // Specified package root that doesn't exist. | |
| 402 // Ignores existing .packages file and packages/ dir. | |
| 403 addScheme("explicit root not there", | |
| 404 "%$scheme/", | |
| 405 files: {"packages": fooPackage, | |
| 406 ".packages": "foo:%$scheme/packages/"}, | |
| 407 root: "%$scheme/notthere/", | |
| 408 expect: { | |
| 409 "proot": "%$scheme/notthere/", | |
| 410 "iroot": "%$scheme/notthere/", | |
| 411 "foo/": "%$scheme/notthere/foo/", | |
| 412 "foo/bar": "%$scheme/notthere/foo/bar", | |
| 413 "foo.x": null, | |
| 414 }); | |
| 415 } | |
| 416 | |
| 417 { | |
| 418 // Specified package config that doesn't exist. | |
| 419 // Ignores existing .packages file and packages/ dir. | |
| 420 addScheme("explicit config not there", | |
| 421 "%$scheme/", | |
| 422 files: {".packages": "foo:packages/foo/", | |
| 423 "packages": fooPackage}, | |
| 424 config: "%$scheme/.notthere", | |
| 425 expect: { | |
| 426 "pconf": "%$scheme/.notthere", | |
| 427 "iconf": null, // <- Only there if actually loaded (unspecified). | |
| 428 "foo/": null, | |
| 429 "foo/bar": null, | |
| 430 "foo.x": null, | |
| 431 }); | |
| 432 } | |
| 433 | |
| 434 { | |
| 435 // Specified package root with no trailing slash. | |
| 436 // The Platform.packageRoot and Isolate.packageRoot has a trailing slash. | |
| 437 var files = {".packages": "foo:packages/foo/", | |
| 438 "packages": {}, | |
| 439 "pkgs": fooPackage}; | |
| 440 addScheme("explicit package root, no slash", "%$scheme/", | |
| 441 files: files, | |
| 442 root: "%$scheme/pkgs", | |
| 443 expect: { | |
| 444 "proot": "%$scheme/pkgs/", | |
| 445 "iroot": "%$scheme/pkgs/", | |
| 446 "foo/": "%$scheme/pkgs/foo/", | |
| 447 "foo/bar": "%$scheme/pkgs/foo/bar", | |
| 448 }); | |
| 449 } | |
| 450 | |
| 451 { | |
| 452 // Specified package root with trailing slash. | |
| 453 var files = {".packages": "foo:packages/foo/", | |
| 454 "packages": {}, | |
| 455 "pkgs": fooPackage}; | |
| 456 addScheme("explicit package root, slash", "%$scheme/", | |
| 457 files: files, | |
| 458 root: "%$scheme/pkgs", | |
| 459 expect: { | |
| 460 "proot": "%$scheme/pkgs/", | |
| 461 "iroot": "%$scheme/pkgs/", | |
| 462 "foo/": "%$scheme/pkgs/foo/", | |
| 463 "foo/bar": "%$scheme/pkgs/foo/bar", | |
| 464 }); | |
| 465 } | |
| 466 | |
| 467 { | |
| 468 // Specified package config. | |
| 469 var files = {".packages": "foo:packages/foo/", | |
| 470 "packages": {}, | |
| 471 ".pkgs": "foo:pkgs/foo/", | |
| 472 "pkgs": fooPackage}; | |
| 473 addScheme("explicit package config file", "%$scheme/", | |
| 474 files: files, | |
| 475 config: "%$scheme/.pkgs", | |
| 476 expect: { | |
| 477 "pconf": "%$scheme/.pkgs", | |
| 478 "iconf": "%$scheme/.pkgs", | |
| 479 "foo/": "%$scheme/pkgs/foo/", | |
| 480 "foo/bar": "%$scheme/pkgs/foo/bar", | |
| 481 }); | |
| 482 } | |
| 483 | |
| 484 { | |
| 485 // Specified package config as data: URI. | |
| 486 /// The package config can be specified as a data: URI. | |
| 487 /// (In that case, relative URI references in the config file won't work). | |
| 488 var files = {".packages": "foo:packages/foo/", | |
| 489 "packages": {}, | |
| 490 "pkgs": fooPackage}; | |
| 491 var dataUri = "data:,foo:%$scheme/pkgs/foo/\n"; | |
| 492 addScheme("explicit data: config file", "%$scheme/", | |
| 493 files: files, | |
| 494 config: dataUri, | |
| 495 expect: { | |
| 496 "pconf": dataUri, | |
| 497 "iconf": dataUri, | |
| 498 "foo/": "%$scheme/pkgs/foo/", | |
| 499 "foo/bar": "%$scheme/pkgs/foo/bar", | |
| 500 }); | |
| 501 } | |
| 502 } | |
| 503 | |
| 504 /// Tests where there are files on both http: and file: sources. | |
| 505 | |
| 506 for (var entryScheme in const ["file", "http"]) { | |
| 507 for (var pkgScheme in const ["file", "http"]) { | |
| 508 // Package root. | |
| 509 if (entryScheme != pkgScheme) { | |
| 510 // Package dir and entry point on different schemes. | |
| 511 var files = {}; | |
| 512 var https = {}; | |
| 513 (entryScheme == "file" ? files : https)["main"] = testMain; | |
| 514 (pkgScheme == "file" ? files : https)["pkgs"] = fooPackage; | |
| 515 add("$pkgScheme pkg/$entryScheme main", "%$entryScheme/", | |
| 516 file: files, http: https, | |
| 517 root: "%$pkgScheme/pkgs/", | |
| 518 expect: { | |
| 519 "proot": "%$pkgScheme/pkgs/", | |
| 520 "iroot": "%$pkgScheme/pkgs/", | |
| 521 "foo/": "%$pkgScheme/pkgs/foo/", | |
| 522 "foo/bar": "%$pkgScheme/pkgs/foo/bar", | |
| 523 "foo.x": "qux", | |
| 524 }); | |
| 525 } | |
| 526 // Package config. The configuration file may also be on either source. | |
| 527 for (var configScheme in const ["file", "http"]) { | |
| 528 // Don't do the boring stuff! | |
| 529 if (entryScheme == configScheme && entryScheme == pkgScheme) continue; | |
| 530 // Package config, packages and entry point not all on same scheme. | |
| 531 var files = {}; | |
| 532 var https = {}; | |
| 533 (entryScheme == "file" ? files : https)["main"] = testMain; | |
| 534 (configScheme == "file" ? files : https)[".pkgs"] = | |
| 535 "foo:%$pkgScheme/pkgs/foo/\n"; | |
| 536 (pkgScheme == "file" ? files : https)["pkgs"] = fooPackage; | |
| 537 add("$pkgScheme pkg/$configScheme config/$entryScheme main", | |
| 538 "%$entryScheme/", | |
| 539 file: files, http: https, | |
| 540 config: "%$configScheme/.pkgs", | |
| 541 expect: { | |
| 542 "pconf": "%$configScheme/.pkgs", | |
| 543 "iconf": "%$configScheme/.pkgs", | |
| 544 "foo/": "%$pkgScheme/pkgs/foo/", | |
| 545 "foo/bar": "%$pkgScheme/pkgs/foo/bar", | |
| 546 "foo.x": "qux", | |
| 547 }); | |
| 548 } | |
| 549 } | |
| 550 } | |
| 551 } | |
| 552 | |
| 553 | |
| 554 // --------------------------------------------------------- | |
| 555 // Helper functionality. | |
| 556 | |
| 557 var fileHttpRegexp = new RegExp(r"%(?:file|http)/"); | |
| 558 | |
| 559 /// Executes a test in a configuration. | |
| 560 /// | |
| 561 /// The test must specify which main file to use | |
| 562 /// (`main`, `spawnMain` or `spawnUriMain`) | |
| 563 /// and any arguments which will be used by `spawnMain` and `spawnUriMain`. | |
| 564 /// | |
| 565 /// The [expect] map may be used to override the expectations of the | |
| 566 /// configuration on a value-by-value basis. Passing, e.g., `{"pconf": null}` | |
| 567 /// will override only the `pconf` (`Platform.packageConfig`) expectation. | |
| 568 Future testConfiguration(Configuration conf) async { | |
| 569 print("-- ${conf.description}"); | |
| 570 //print("DEBUG:\n$conf"); | |
| 571 var description = conf.description; | |
| 572 try { | |
| 573 var output = await execDart(conf.mainFile, | |
| 574 root: conf.root, | |
| 575 config: conf.config, | |
| 576 scriptArgs: conf.args); | |
| 577 match(JSON.decode(output), conf.expect, description, output); | |
| 277 } catch (e, s) { | 578 } catch (e, s) { |
| 278 // Unexpected error calling runDart or parsing the result. | 579 // Unexpected error calling execDart or parsing the result. |
| 279 // Report it and continue. | 580 // Report it and continue. |
| 280 print("ERROR running $name: $e\n$s"); | 581 print("ERROR running $description: $e\n$s"); |
| 281 failingTests.add(name); | 582 failingTests.putIfAbsent(description, () => []).add("$e"); |
| 282 } finally { | 583 } |
| 283 if (https != null) await https.close(); | 584 } |
| 284 if (tmpDir != null) tmpDir.deleteSync(recursive: true); | 585 |
| 285 } | 586 |
| 286 } | 587 /// Creates a new isolate which inherits the configuration of the current |
| 287 | 588 /// isolate, which is set up according to the configuration. |
| 589 /// | |
| 590 /// Inheriting is chosen by passing no `packageConfig`, `packageRoot` or | |
| 591 /// `automaticPackageResolution` parameters to `spawnUri`. | |
| 592 /// That defaults to passing the current configuration taken from either | |
| 593 /// [Isolate.packageRoot] or [Isolate.packageConfig], or passing `true` as | |
| 594 /// `automaticPackageResolution` of both of the previous values are missing. | |
| 595 void testSpawnUriInherit(Configuration conf) { | |
| 596 // TODO | |
| 597 // Implicit root/config becomes explicit parameters when | |
| 598 // inheriting using spawnUri with no explicit parameters. | |
| 599 // expect["proot"] = expect["iroot"]; | |
| 600 // expect["pconf"] = expect["iconf"]; | |
| 601 } | |
| 602 | |
| 603 /// Creates a new isolate which does not inherit the configuration | |
| 604 /// of the current isolate, but passes the existing configuration | |
| 605 /// explicitly. | |
| 606 void testSpawnUri(Configuration conf) { | |
| 607 // 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
| |
| 608 // conf.args.insertAllAt(0, [conf.main, conf.config ?? "", conf.root ?? "", | |
| 609 // "$search"]) | |
| 610 // conf.main = conf.insertAt(conf.main, | |
| 611 // freshName("spawnUriMain"), | |
| 612 // spawnUriMain); | |
| 613 } | |
| 288 | 614 |
| 289 /// Test that the output of running testMain matches the expectations. | 615 /// Test that the output of running testMain matches the expectations. |
| 290 /// | 616 /// |
| 291 /// The output is a string which is parse as a JSON literal. | 617 /// The output is a string which is parse as a JSON literal. |
| 292 /// The resulting map is always mapping strings to strings, or possibly `null`. | 618 /// The resulting map is always mapping strings to strings, or possibly `null`. |
| 293 /// The expectations can have non-string values other than null, | 619 /// The expectations can have non-string values other than null, |
| 294 /// they are `toString`'ed before being compared (so the caller can use a URI | 620 /// they are `toString`'ed before being compared (so the caller can use a URI |
| 295 /// or a File/Directory directly as an expectation). | 621 /// or a File/Directory directly as an expectation). |
| 296 void match(Map actuals, Map expectations, String fixPaths(String expectation), | 622 void match(Map actuals, Map expectations, String desc, String actualJson) { |
| 297 String name) { | |
| 298 for (var key in expectations.keys) { | 623 for (var key in expectations.keys) { |
| 299 var expectation = fixPaths(expectations[key]?.toString()); | 624 var expectation = expectations[key]?.toString(); |
| 300 var actual = actuals[key]; | 625 var actual = actuals[key]; |
| 301 if (expectation != actual) { | 626 if (expectation != actual) { |
| 302 print("ERROR: $name: $key: Expected: <$expectation> Found: <$actual>"); | 627 print("ERROR: $desc: $key: Expected: <$expectation> Found: <$actual>"); |
| 303 failingTests.add(name); | 628 failingTests.putIfAbsent(desc, ()=>[]).add( |
| 629 "$key: $expectation != $actual"); | |
| 304 } | 630 } |
| 305 } | 631 } |
| 306 } | 632 } |
| 307 | 633 |
| 308 /// Script that prints the current state and the result of resolving | 634 /// Script that prints the current state and the result of resolving |
| 309 /// a few package URIs. This script will be invoked in different settings, | 635 /// a few package URIs. This script will be invoked in different settings, |
| 310 /// and the result will be parsed and compared to the expectations. | 636 /// and the result will be parsed and compared to the expectations. |
| 311 const String testMain = r""" | 637 const String testMain = r""" |
| 312 import "dart:convert" show JSON; | 638 import "dart:convert" show JSON; |
| 313 import "dart:io" show Platform, Directory; | 639 import "dart:io" show Platform, Directory; |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 339 "foo": res1?.toString(), | 665 "foo": res1?.toString(), |
| 340 "foo/": res2?.toString(), | 666 "foo/": res2?.toString(), |
| 341 "foo/bar": res3?.toString(), | 667 "foo/bar": res3?.toString(), |
| 342 "foo.x": fooX?.toString(), | 668 "foo.x": fooX?.toString(), |
| 343 })); | 669 })); |
| 344 } | 670 } |
| 345 """; | 671 """; |
| 346 | 672 |
| 347 /// Script that spawns a new Isolate using Isolate.spawnUri. | 673 /// Script that spawns a new Isolate using Isolate.spawnUri. |
| 348 /// | 674 /// |
| 349 /// Takes URI of target isolate, package config and package root as | 675 /// Takes URI of target isolate, package config, package root and |
| 350 /// command line arguments. Any further arguments are forwarded to the | 676 /// automatic package resolution-flag parameters as command line arguments. |
| 351 /// spawned isolate. | 677 /// Any further arguments are forwarded to the spawned isolate. |
| 352 const String spawnUriMain = r""" | 678 const String spawnUriMain = r""" |
| 353 import "dart:isolate"; | 679 import "dart:isolate"; |
| 680 import "dart:async"; | |
| 354 main(args) async { | 681 main(args) async { |
| 355 Uri target = Uri.parse(args[0]); | 682 Uri target = Uri.parse(args[0]); |
| 356 Uri conf = args.length > 1 && args[1].isNotEmpty ? Uri.parse(args[1]) : null; | 683 Uri config = args[1].isNotEmpty ? Uri.parse(args[1]) : null; |
| 357 Uri root = args.length > 2 && args[2].isNotEmpty ? Uri.parse(args[2]) : null; | 684 Uri root = args[2].isNotEmpty ? Uri.parse(args[2]) : null; |
| 358 var restArgs = args.skip(3).toList(); | 685 bool search = args[3] == "true"; |
| 359 var isolate = await Isolate.spawnUri(target, restArgs, | 686 var restArgs = args.skip(4).toList(); |
| 360 packageRoot: root, packageConfig: conf, paused: true); | 687 // Port keeps isolate alive until spawned isolate terminates. |
| 361 // Wait for isolate to exit before exiting the main isolate. | 688 var port = new RawReceivePort(); |
| 362 var done = new RawReceivePort(); | 689 port.handler = (res) async { |
| 363 done.handler = (_) { done.close(); }; | 690 port.close(); // Close on exit or first error. |
| 364 isolate.addExitHandler(done.sendPort); | 691 if (res != null) { |
| 365 isolate.resume(isolate.pauseCapability); | 692 await new Future.error(res[0], new StackTrace.fromString(res[1])); |
| 693 } | |
| 694 }; | |
| 695 Isolate.spawnUri(target, restArgs, null, | |
| 696 packageRoot: root, packageConfig: config, | |
| 697 automaticPackageResolution: search, | |
| 698 onError: port.sendPort, onExit: port.sendPort); | |
| 366 } | 699 } |
| 367 """; | 700 """; |
| 368 | 701 |
| 369 /// Script that spawns a new Isolate using Isolate.spawn. | 702 /// Script that spawns a new Isolate using Isolate.spawn. |
| 703 /// | |
| 704 /// Uses the first argument to select which target to spawn. | |
| 705 /// Should be either "test", "uri" or "spawn". | |
| 370 const String spawnMain = r""" | 706 const String spawnMain = r""" |
| 707 import "dart:async"; | |
| 371 import "dart:isolate"; | 708 import "dart:isolate"; |
| 372 import "testmain.dart" as test; | 709 import "%mainDir/main.dart" as test; |
| 373 main() async { | 710 import "%mainDir/spawnUriMain.dart" as spawnUri; |
| 374 var isolate = await Isolate.spawn(test.main, [], paused: true); | 711 main(List<String> args) async { |
| 375 // Wait for isolate to exit before exiting the main isolate. | 712 // Port keeps isolate alive until spawned isolate terminates. |
| 376 var done = new RawReceivePort(); | 713 var port = new RawReceivePort(); |
| 377 done.handler = (_) { done.close(); }; | 714 port.handler = (res) async { |
| 378 isolate.addExitHandler(done.sendPort); | 715 port.close(); // Close on exit or first error. |
| 379 isolate.resume(isolate.pauseCapability); | 716 if (res != null) { |
| 717 await new Future.error(res[0], new StackTrace.fromString(res[1])); | |
| 718 } | |
| 719 }; | |
| 720 var arg = args.first; | |
| 721 var rest = args.skip(1).toList(); | |
| 722 var target; | |
| 723 if (arg == "main") { | |
| 724 target = test.main; | |
| 725 } else if (arg == "spawnUriMain") { | |
| 726 target = spawnUri.main; | |
| 727 } else { | |
| 728 target = main; | |
| 729 } | |
| 730 Isolate.spawn(target, rest, onError: port.sendPort, onExit: port.sendPort); | |
| 380 } | 731 } |
| 381 """; | 732 """; |
| 382 | 733 |
| 383 /// A package directory containing only one package, "foo", with one file. | 734 /// A package directory containing only one package, "foo", with one file. |
| 384 const Map fooPackage = const { "foo": const { "foo": "var x = 'qux';" }}; | 735 const Map fooPackage = const { "foo": const { "foo": "var x = 'qux';" }}; |
| 385 | 736 |
| 737 | |
| 386 /// Runs the Dart executable with the provided parameters. | 738 /// Runs the Dart executable with the provided parameters. |
| 387 /// | 739 /// |
| 388 /// Captures and returns the output. | 740 /// Captures and returns the output. |
| 389 Future<String> runDart(String script, | 741 Future<String> execDart(String script, |
| 390 {String root, String config, | 742 {String root, String config, |
| 391 Iterable<String> scriptArgs}) async { | 743 Iterable<String> scriptArgs}) async { |
| 744 var checked = false; | |
| 745 assert((checked = true)); | |
| 392 // TODO: Find a way to change CWD before running script. | 746 // TODO: Find a way to change CWD before running script. |
| 393 var executable = Platform.executable; | 747 var executable = Platform.executable; |
| 394 var args = []; | 748 var args = []; |
| 395 if (root != null) args..add("-p")..add(root); | 749 if (checked) args.add("--checked"); |
| 396 if (config != null) args..add("--packages=$config"); | 750 if (root != null) args.add("--package-root=$root"); |
| 751 if (config != null) args.add("--packages=$config"); | |
| 397 args.add(script); | 752 args.add(script); |
| 398 if (scriptArgs != null) { | 753 if (scriptArgs != null) { |
| 399 args.addAll(scriptArgs); | 754 args.addAll(scriptArgs); |
| 400 } | 755 } |
| 401 return Process.run(executable, args).then((results) { | 756 return Process.run(executable, args).then((results) { |
| 402 if (results.exitCode != 0) { | 757 if (results.exitCode != 0 || results.stderr.isNotEmpty) { |
| 403 throw results.stderr; | 758 throw results.stderr; |
| 404 } | 759 } |
| 405 return results.stdout; | 760 return results.stdout; |
| 406 }); | 761 }); |
| 407 } | 762 } |
| 408 | 763 |
| 409 /// Creates a number of files and subdirectories. | 764 /// Creates a number of files and subdirectories. |
| 410 /// | 765 /// |
| 411 /// The [content] is the content of the directory itself. The map keys are | 766 /// The [content] is the content of the directory itself. The map keys are |
| 412 /// names and the values are either strings that represent Dart file contents | 767 /// names and the values are either strings that represent Dart file contents |
| 413 /// or maps that represent subdirectories. | 768 /// or maps that represent subdirectories. |
| 414 /// Subdirectories may include a package directory. If [packageDir] | 769 void createFiles(Directory tempDir, String subDir, Map content) { |
| 415 /// is provided, a `.packages` file is created for the content of that | |
| 416 /// directory. | |
| 417 void createFiles(Directory tempDir, Map content, String fixPaths(String text), | |
| 418 [String packageDir]) { | |
| 419 Directory createDir(Directory base, String name) { | 770 Directory createDir(Directory base, String name) { |
| 420 Directory newDir = new Directory(p.join(base.path, name)); | 771 Directory newDir = new Directory(p.join(base.path, name)); |
| 421 newDir.createSync(); | 772 newDir.createSync(); |
| 422 return newDir; | 773 return newDir; |
| 423 } | 774 } |
| 424 | 775 |
| 425 void createTextFile(Directory base, String name, String content) { | 776 void createTextFile(Directory base, String name, String content) { |
| 426 File newFile = new File(p.join(base.path, name)); | 777 File newFile = new File(p.join(base.path, name)); |
| 427 newFile.writeAsStringSync(fixPaths(content)); | 778 newFile.writeAsStringSync(content); |
| 428 } | 779 } |
| 429 | 780 |
| 430 void createRecursive(Directory dir, Map map) { | 781 void createRecursive(Directory dir, Map map) { |
| 431 for (var name in map.keys) { | 782 for (var name in map.keys) { |
| 432 var content = map[name]; | 783 var content = map[name]; |
| 433 if (content is String) { | 784 if (content is String) { |
| 434 // If the name starts with "." it's a .packages file, otherwise it's | 785 // If the name starts with "." it's a .packages file, otherwise it's |
| 435 // a dart file. Those are the only files we care about in this test. | 786 // a dart file. Those are the only files we care about in this test. |
| 436 createTextFile(dir, | 787 createTextFile(dir, |
| 437 name.startsWith(".") ? name : name + ".dart", | 788 name.startsWith(".") ? name : name + ".dart", |
| 438 content); | 789 content); |
| 439 } else { | 790 } else { |
| 440 assert(content is Map); | 791 assert(content is Map); |
| 441 var subdir = createDir(dir, name); | 792 var subdir = createDir(dir, name); |
| 442 createRecursive(subdir, content); | 793 createRecursive(subdir, content); |
| 443 } | 794 } |
| 444 } | 795 } |
| 445 } | 796 } |
| 446 | 797 |
| 447 createRecursive(tempDir, content); | 798 createRecursive(createDir(tempDir, subDir), content); |
| 448 if (packageDir != null) { | |
| 449 // Unused? | |
| 450 Map packages = content[packageDir]; | |
| 451 var entries = | |
| 452 packages.keys.map((key) => "$key:$packageDir/$key").join("\n"); | |
| 453 createTextFile(tempDir, ".packages", entries); | |
| 454 } | |
| 455 } | 799 } |
| 456 | 800 |
| 457 /// Start an HTTP server which serves a directory/file structure. | 801 /// Start an HTTP server which serves a directory/file structure. |
| 458 /// | 802 /// |
| 459 /// The directories and files are described by [files]. | 803 /// The directories and files are described by [files]. |
| 460 /// | 804 /// |
| 461 /// Each map key is an entry in a directory. A `Map` value is a sub-directory | 805 /// Each map key is an entry in a directory. A `Map` value is a sub-directory |
| 462 /// and a `String` value is a text file. | 806 /// and a `String` value is a text file. |
| 463 /// The file contents are run through [fixPaths] to allow them to be self- | 807 /// The file contents are run through [fixPaths] to allow them to be self- |
| 464 /// referential. | 808 /// referential. |
| 465 Future<HttpServer> startServer(Map files, String fixPaths(String text)) async { | 809 Future<HttpServer> startServer(Map files) async { |
| 466 return (await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0)) | 810 return (await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0)) |
| 467 ..forEach((request) { | 811 ..forEach((request) { |
| 468 var result = files; | 812 var result = files; |
| 469 onFailure: { | 813 onFailure: { |
| 470 for (var part in request.uri.pathSegments) { | 814 for (var part in request.uri.pathSegments) { |
| 471 if (part.endsWith(".dart")) { | 815 if (part.endsWith(".dart")) { |
| 472 part = part.substring(0, part.length - 5); | 816 part = part.substring(0, part.length - 5); |
| 473 } | 817 } |
| 474 if (result is Map) { | 818 if (result is Map) { |
| 475 result = result[part]; | 819 result = result[part]; |
| 476 } else { | 820 } else { |
| 477 break onFailure; | 821 break onFailure; |
| 478 } | 822 } |
| 479 } | 823 } |
| 480 if (result is String) { | 824 if (result is String) { |
| 481 request.response..write(fixPaths(result)) | 825 request.response..write(result) |
| 482 ..close(); | 826 ..close(); |
| 483 return; | 827 return; |
| 484 } | 828 } |
| 485 } | 829 } |
| 486 request.response..statusCode = HttpStatus.NOT_FOUND | 830 request.response..statusCode = HttpStatus.NOT_FOUND |
| 487 ..close(); | 831 ..close(); |
| 488 }); | 832 }); |
| 489 } | 833 } |
| 490 | 834 |
| 491 // Counter used to avoid reusing temporary directory names. | 835 // Counter used to avoid reusing temporary file or directory names. |
| 492 // Some platforms are timer based, and creating two temp-dirs withing a short | 836 // |
| 493 // duration may cause a collision. | 837 // Used when adding extra files to an existing directory structure, |
| 494 int tmpDirCounter = 0; | 838 // and when creating temporary directories. |
| 839 // | |
| 840 // Some platform temporary-directory implementations are timer based, | |
| 841 // and creating two temp-dirs withing a short duration may cause a collision. | |
| 842 int tmpNameCounter = 0; | |
| 843 | |
| 844 // Fresh file name. | |
| 845 String freshName([String base = "tmp"]) => "$base${tmpNameCounter++}"; | |
| 495 | 846 |
| 496 Directory createTempDir() { | 847 Directory createTempDir() { |
| 497 return Directory.systemTemp.createTempSync("pftest-${tmpDirCounter++}-"); | 848 return Directory.systemTemp.createTempSync(freshName("pftest-")); |
| 498 } | 849 } |
| 850 | |
| 851 typedef void ConfigUpdate(Configuration configuration); | |
| 852 | |
| 853 /// The configuration for a single test. | |
| 854 class Configuration { | |
| 855 /// The "description" of the test - a description of the set-up. | |
| 856 final String description; | |
| 857 /// The package root parameter passed to the Dart isolate. | |
| 858 /// | |
| 859 /// At most one of [root] and [config] should be supplied. If both are | |
| 860 /// omitted, a VM will search for a packages file or dir. | |
| 861 final String root; | |
| 862 /// The package configuration file location passed to the Dart isolate. | |
| 863 final String config; | |
| 864 /// Path to the main file to run. | |
| 865 final String mainFile; | |
| 866 /// List of arguments to pass to the main function. | |
| 867 final List<String> args; | |
| 868 /// The expected values for `Platform.package{Root,Config}`, | |
| 869 /// `Isolate.package{Root,Config}` and resolution of package URIs | |
| 870 /// in a `foo` package. | |
| 871 /// | |
| 872 /// The results are found by running the `main.dart` file inside [mainDir]. | |
| 873 /// The tests can run this file after doing other `spawn` or `spawnUri` calls. | |
| 874 final Map expect; | |
| 875 | |
| 876 Configuration({this.description, | |
| 877 this.root, | |
| 878 this.config, | |
| 879 this.mainFile, | |
| 880 this.args, | |
| 881 this.expect}); | |
| 882 | |
| 883 // Gets the type of main file, one of `main`, `spawnMain` or `spawnUriMain`. | |
| 884 String get mainType { | |
| 885 var lastSlash = mainFile.lastIndexOf("/"); | |
| 886 if (lastSlash < 0) { | |
| 887 // Assume it's a Windows path. | |
| 888 lastSlash = mainFile.lastIndexOf(r"\"); | |
| 889 } | |
| 890 var name = mainFile.substring(lastSlash + 1, mainFile.length - 5); | |
| 891 assert(name == "main" || name == "spawnMain" || name == "spawnUriMain"); | |
| 892 return name; | |
| 893 } | |
| 894 | |
| 895 String get mainPath { | |
| 896 var lastSlash = mainFile.lastIndexOf("/"); | |
| 897 if (lastSlash < 0) { | |
| 898 // Assume it's a Windows path. | |
| 899 lastSlash = mainFile.lastIndexOf(r"\"); | |
| 900 } | |
| 901 return mainFile.substring(0, lastSlash + 1); | |
| 902 } | |
| 903 | |
| 904 /// Create a new configuration from the old one. | |
| 905 /// | |
| 906 /// [description] is new description. | |
| 907 /// | |
| 908 /// [main] is one of `main`, `spawnMain` or `spawnUriMain`, and changes | |
| 909 /// the [Configuration.mainFile] to a different file in the same directory. | |
| 910 /// | |
| 911 /// [mainFile] overrides [Configuration.mainFile] completely, and ignores | |
| 912 /// [main]. | |
| 913 /// | |
| 914 /// [newArgs] are prepended to the existing [Configuration.args]. | |
| 915 /// | |
| 916 /// [args] overrides [Configuration.args] completely and ignores [newArgs]. | |
| 917 /// | |
| 918 /// [expect] overrides individual expectations. | |
| 919 /// | |
| 920 /// [root] and [config] overrides the existing values. | |
| 921 Configuration update({ | |
| 922 String description, | |
| 923 String main, | |
| 924 String mainFile, | |
| 925 String root, | |
| 926 String config, | |
| 927 List<String> args, | |
| 928 List<String> newArgs, | |
| 929 Map expect | |
| 930 }) { | |
| 931 return new Configuration( | |
| 932 description: description ?? this.description, | |
| 933 root: root ?? this.root, | |
| 934 config: config ?? this.config, | |
| 935 mainFile: mainFile ?? | |
| 936 ((main == null) ? this.mainFile : "${this.mainPath}$main.dart"), | |
| 937 args: args ?? ([]..addAll(newArgs ?? const [])..addAll(this.args)), | |
| 938 expect: expect == null | |
| 939 ? this.expect | |
| 940 : new Map.from(this.expect)..addAll(expect ?? const {})); | |
| 941 } | |
| 942 | |
| 943 // For debugging. | |
| 944 String toString() { | |
| 945 return "Configuration($description\n" | |
| 946 " root : $root\n" | |
| 947 " config: $config\n" | |
| 948 " main : $mainFile\n" | |
| 949 " args : ${args.map((x) => '"$x"').join(" ")}\n" | |
| 950 ") : expect {\n${expect.keys.map((k) => | |
| 951 ' "$k"'.padRight(6) + ":${JSON.encode(expect[k])}\n").join()}" | |
| 952 "}"; | |
| 953 } | |
| 954 } | |
| 955 | |
| 956 | |
| 957 // Inserts the file with generalized [name] at [path] with [content]. | |
| 958 // | |
| 959 // The [path] is a directory where the file is created. It must start with | |
| 960 // either '%file/' or '%http/' to select the structure to put it into. | |
| 961 // | |
| 962 // The [name] should not have a trailing ".dart" for Dart files. Any file | |
| 963 // not starting with "." is assumed to be a ".dart" file. | |
| 964 void insertFileAt(Map file, Map http, | |
| 965 String path, String name, String content) { | |
| 966 var parts = path.split('/').toList(); | |
| 967 var dir = (parts[0] == "%file") ? file : http; | |
| 968 for (var i = 1; i < parts.length - 1; i++) { | |
| 969 var entry = parts[i]; | |
| 970 dir = dir[entry] ?? (dir[entry] = {}); | |
| 971 } | |
| 972 dir[name] = content; | |
| 973 } | |
| OLD | NEW |