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 await runTests(); /// 01: ok |
16 // "%http/" with the URI of the HTTP server's root in appropriate contexts | 32 await runTests([spawn]); /// 02: ok |
17 // (all file contents and parameters). | 33 await runTests([spawn, spawn]); /// 03: ok |
18 | 34 await runTests([spawnUriInherit]); /// 04: ok |
19 // With no specified resolutiuon and no implicit .packages or packages/ | 35 await runTests([spawnUriInherit, spawn]); /// 05: ok |
20 // available, nothing can be resolved and the package can't be imported. | 36 await runTests([spawn, spawnUriInherit]); /// 06: ok |
21 await test("file: no resolution", | 37 |
22 "%file/main.dart", | 38 // Test that spawning a new VM with file paths instead of URIs as arguments |
23 file: {"main": testMain}, | 39 // gives the same URIs in the internal values. |
24 expect: {"foo.x": null}); | 40 await runTests([asPath]); /// 07: ok |
25 | 41 |
26 // An HTTP script with no ".packages" file assumes a "packages" dir. | 42 // Test that spawnUri can reproduce the behavior of VM command line parameters |
27 // All packages are resolved relative to that dir, whether it exists or not. | 43 // exactly. |
28 await test("http: no resolution", "%http/main.dart", | 44 // (Don't run all configuration combinations in the same test, so |
29 http: {"main": testMain}, | 45 // unroll the configurations into multiple groups and run each group |
| 46 // as its own multitest. |
| 47 { |
| 48 var groupCount = 8; |
| 49 var groups = new List.generate(8, (_)=>[]); |
| 50 for (int i = 0; i < configurations.length; i++) { |
| 51 groups[i % groupCount].add(configurations[i]); |
| 52 } |
| 53 var group = -1; |
| 54 group = 0; /// 10: ok |
| 55 group = 1; /// 11: ok |
| 56 group = 2; /// 12: ok |
| 57 group = 3; /// 13: ok |
| 58 group = 4; /// 14: ok |
| 59 group = 5; /// 15: ok |
| 60 group = 6; /// 16: ok |
| 61 group = 7; /// 17: ok |
| 62 if (group >= 0) { |
| 63 for (var other in groups[group]) { |
| 64 await runTests([spawnUriOther(other)]); |
| 65 } |
| 66 } |
| 67 } |
| 68 |
| 69 |
| 70 await tearDown(); |
| 71 |
| 72 if (failingTests.isNotEmpty) { |
| 73 print("Errors found in tests:"); |
| 74 failingTests.forEach((test, actual) { |
| 75 print("$test:\n ${actual.join("\n ")}"); |
| 76 }); |
| 77 exit(255); |
| 78 } |
| 79 |
| 80 asyncEnd(); |
| 81 } |
| 82 |
| 83 /// Test running the test of the configuration through [Isolate.spawn]. |
| 84 /// |
| 85 /// This should not change the expected results compared to running it |
| 86 /// directly. |
| 87 Configuration spawn(Configuration conf) { |
| 88 // TODO(26555): Clean up when fixed. |
| 89 // TEMPORARY FIX FOR ISSUE #26555 (http://dartbug.com/26555) |
| 90 if (conf.expect["iroot"] == null && |
| 91 conf.expect["iconf"] == null && |
| 92 conf.expect["pconf"] != null) { |
| 93 // The spawned isolate will do a search for a package file or root, |
| 94 // which is not what the original did. Skip test for now. |
| 95 return null; |
| 96 } |
| 97 // REMOVE WHEN ISSUE FIXED! |
| 98 return conf.update( |
| 99 description: conf.description + "/spawn", |
| 100 main: "spawnMain", |
| 101 newArgs: [conf.mainType], |
| 102 // TEMPORARY FIX FOR ISSUE #26555 (http://dartbug.com/26555) |
30 expect: { | 103 expect: { |
31 "iroot": "%http/packages/", | 104 "proot": conf.expect["iroot"], |
32 // "foo": null, | 105 "pconf": conf.expect["iconf"], |
33 "foo/": "%http/packages/foo/", | 106 } |
34 "foo/bar": "%http/packages/foo/bar", | 107 // REMOVE WHEN ISSUE FIXED! |
35 "foo.x": null, | 108 ); |
36 }); | 109 } |
37 | 110 |
38 // A number of tests which behave similarly whether run as local files or | 111 /// Tests running a spawnUri on top of the configuration before testing. |
39 // over HTTP. | 112 /// |
40 for (var scheme in ["file", "http"]) { | 113 /// The `spawnUri` call has no explicit root or config parameter, and |
41 | 114 /// shouldn't search for one, so it implicitly inherits the current isolate's |
42 /// Run a test in the current scheme. | 115 /// actual root or configuration. |
43 /// | 116 Configuration spawnUriInherit(Configuration conf) { |
44 /// The files are served either through HTTP or in a local directory. | 117 if (conf.expect["iroot"] == null && |
45 /// Use "%$scheme/" to refer to the root of the served files. | 118 conf.expect["iconf"] == null && |
46 testScheme(name, main, {expect, files, args, root, config}) { | 119 conf.expect["pconf"] != null) { |
47 return test("$scheme: $name", main, expect: expect, | 120 // This means that the specified configuration file didn't exist. |
48 root: root, config: config, args: args, | 121 // spawning a new URI to "inherit" that will actually do an automatic |
49 file: scheme == "file" ? files : null, | 122 // package resolution search with results that are unpredictable. |
50 http: scheme == "http" ? files : null); | 123 // That behavior will be tested in a setting where we have more control over |
51 } | 124 // the files around the spawned URI. |
52 | 125 return null; |
53 { | 126 } |
54 var files = {"main": testMain, "packages": fooPackage}; | 127 return conf.update( |
55 // Expect implicitly detected package dir. | 128 description: conf.description + "/spawnUri-inherit", |
56 await testScheme("implicit packages dir","%$scheme/main.dart", | 129 main: "spawnUriMain", |
57 files: files, | 130 // encode null parameters as "-". Windows fails if using empty string. |
58 expect: { | 131 newArgs: [conf.mainFile, "-", "-", "false"], |
59 "iroot": "%$scheme/packages/", | 132 expect: { |
60 // "foo": null, | 133 "proot": conf.expect["iroot"], |
61 "foo/": "%$scheme/packages/foo/", | 134 "pconf": conf.expect["iconf"], |
62 "foo/bar": "%$scheme/packages/foo/bar", | 135 } |
63 }); | 136 ); |
64 } | 137 } |
65 | 138 |
66 { | 139 /// Tests running a spawnUri with an explicit configuration different |
67 var files = {"sub": {"main": testMain, "packages": fooPackage}, | 140 /// from the original configuration. |
68 ".packages": ""}; | 141 /// |
69 // Expect implicitly detected package dir. | 142 /// Duplicates the explicit parameters as arguments to the spawned isolate. |
70 // Should not detect the .packages file in parent directory. | 143 ConfigurationTransformer spawnUriOther(Configuration other) { |
71 // That file is empty, so if it is used, the system cannot resolve "foo". | 144 return (Configuration conf) { |
72 await testScheme("implicit packages dir 2", "%$scheme/sub/main.dart", | 145 bool search = (other.config == null) && (other.root == null); |
73 files: files, | 146 return conf.update( |
74 expect: { | 147 description: "${conf.description} -spawnUri-> ${other.description}", |
75 "iroot": "%$scheme/sub/packages/", | 148 main: "spawnUriMain", |
76 // "foo": null, | 149 newArgs: [other.mainFile, |
77 "foo/": "%$scheme/sub/packages/foo/", | 150 other.config ?? "-", other.root ?? "-", "$search"], |
78 "foo/bar": "%$scheme/sub/packages/foo/bar", | 151 expect: other.expect |
79 }); | 152 ); |
80 } | 153 }; |
81 | 154 } |
82 { | 155 |
83 var files = {"main": testMain, | 156 |
84 ".packages": "foo:pkgs/foo/", | 157 /// Convert command line parameters to file paths. |
85 "pkgs": fooPackage}; | 158 /// |
86 await testScheme("implicit .packages file", "%$scheme/main.dart", | 159 /// This only works on the command line, not with `spawnUri`. |
87 files: files, | 160 Configuration asPath(Configuration conf) { |
88 expect: { | 161 bool change = false; |
89 "iconf": "%$scheme/.packages", | 162 |
90 // "foo": null, | 163 String toPath(String string) { |
91 "foo/": "%$scheme/pkgs/foo/", | 164 if (string == null) return null; |
92 "foo/bar": "%$scheme/pkgs/foo/bar", | 165 if (string.startsWith("file:")) { |
93 }); | 166 change = true; |
94 } | 167 return new File.fromUri(Uri.parse(string)).path; |
95 | 168 } |
96 { | 169 return string; |
97 var files = {"main": testMain, | 170 } |
98 ".packages": "foo:packages/foo/", | 171 |
99 "packages": fooPackage, | 172 var mainFile = toPath(conf.mainFile); |
100 "pkgs": fooPackage}; | 173 var root = toPath(conf.root); |
101 await testScheme("explicit package root, no slash", "%$scheme/main.dart", | 174 var config = toPath(conf.config); |
102 files: files, | 175 if (!change) return null; |
103 root: "%$scheme/pkgs", | 176 return conf.update(description: conf.description + "/as path", |
104 expect: { | 177 mainFile: mainFile, root: root, config: config); |
105 "proot": "%$scheme/pkgs/", | 178 } |
106 "iroot": "%$scheme/pkgs/", | 179 |
107 // "foo": null, | 180 /// -------------------------------------------------------------- |
108 "foo/": "%$scheme/pkgs/foo/", | 181 |
109 "foo/bar": "%$scheme/pkgs/foo/bar", | 182 |
110 }); | 183 Future setUp() async { |
111 } | 184 fileRoot = createTempDir(); |
112 | 185 // print("FILES: $fileRoot"); |
113 { | 186 httpServer = await startServer(httpFiles); |
114 var files = {"main": testMain, | 187 // print("HTTPS: ${httpServer.address.address}:${httpServer.port}"); |
115 ".packages": "foo:packages/foo/", | 188 createConfigurations(); |
116 "packages": fooPackage, | 189 } |
117 "pkgs": fooPackage}; | 190 |
118 await testScheme("explicit package root, slash", "%$scheme/main.dart", | 191 Future tearDown() async { |
119 files: files, | 192 fileRoot.deleteSync(recursive: true); |
120 root: "%$scheme/pkgs", | 193 await httpServer.close(); |
121 expect: { | 194 } |
122 "proot": "%$scheme/pkgs/", | 195 |
123 "iroot": "%$scheme/pkgs/", | 196 typedef Configuration ConfigurationTransformer(Configuration conf); |
124 // "foo": null, | 197 |
125 "foo/": "%$scheme/pkgs/foo/", | 198 Future runTests([List<ConfigurationTransformer> transformations]) async { |
126 "foo/bar": "%$scheme/pkgs/foo/bar", | 199 outer: for (var config in configurations) { |
127 }); | 200 if (transformations != null) { |
128 } | 201 for (int i = transformations.length - 1; i >= 0; i--) { |
129 | 202 config = transformations[i](config); |
130 { | 203 if (config == null) { |
131 var files = {"main": testMain, | 204 continue outer; // Can be used to skip some tests. |
132 ".packages": "foo:packages/foo/", | 205 } |
133 "packages": fooPackage, | 206 } |
134 ".pkgs": "foo:pkgs/foo/", | 207 } |
135 "pkgs": fooPackage}; | 208 await testConfiguration(config); |
136 await testScheme("explicit package config file", "%$scheme/main.dart", | 209 } |
137 files: files, | 210 } |
138 config: "%$scheme/.pkgs", | 211 |
139 expect: { | 212 // Creates a combination of configurations for running the Dart VM. |
140 "pconf": "%$scheme/.pkgs", | 213 // |
141 "iconf": "%$scheme/.pkgs", | 214 // The combinations covers most configurations of implicit and explicit |
142 // "foo": null, | 215 // package configurations over both file: and http: file sources. |
143 "foo/": "%$scheme/pkgs/foo/", | 216 // It also specifies the expected values of the following for a VM |
144 "foo/bar": "%$scheme/pkgs/foo/bar", | 217 // run in that configuration. |
145 }); | 218 // |
146 } | 219 // * `Process.packageRoot` |
147 | 220 // * `Process.packageConfig` |
148 { | 221 // * `Isolate.packageRoot` |
149 /// The package config can be specified as a data: URI. | 222 // * `Isolate.packageRoot` |
150 /// (In that case, relative URI references in the config file won't work). | 223 // * `Isolate.resolvePacakgeUri` of various inputs. |
151 var files = {"main": testMain, | 224 // * A variable defined in a library loaded using a `package:` URI. |
152 ".packages": "foo:packages/foo/", | 225 // |
153 "packages": fooPackage, | 226 // The configurations all have URIs as `root`, `config` and `mainFile` strings, |
154 "pkgs": fooPackage}; | 227 // have empty argument lists and `mainFile` points to the the `main.dart` file. |
155 var dataUri = "data:,foo:%$scheme/pkgs/foo/\n"; | 228 void createConfigurations() { |
156 await testScheme("explicit data: config file", "%$scheme/main.dart", | 229 add(String description, String mainDir, {String root, String config, |
157 files: files, | 230 Map file, Map http, Map expect}) { |
158 config: dataUri, | 231 var id = freshName("conf"); |
159 expect: { | 232 |
160 "pconf": dataUri, | 233 file ??= {}; |
161 "iconf": dataUri, | 234 http ??= {}; |
162 // "foo": null, | 235 |
163 "foo/": "%$scheme/pkgs/foo/", | 236 // Fix-up paths. |
164 "foo/bar": "%$scheme/pkgs/foo/bar", | 237 String fileUri = fileRoot.uri.resolve("$id/").toString(); |
165 }); | 238 String httpUri = |
166 } | 239 "http://${httpServer.address.address}:${httpServer.port}/$id/"; |
167 } | 240 |
168 | 241 String fixPath(String path) { |
169 { | 242 return path?.replaceAllMapped(fileHttpRegexp, (match) { |
170 // With a file: URI, the lookup checks for a .packages file in superdirs | 243 if (path.startsWith("%file/", match.start)) return fileUri; |
171 // when it fails to find a ,packages file or packages/ directory next to | 244 return httpUri; |
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 }); | 245 }); |
184 } | 246 } |
185 | 247 |
186 { | 248 void fixPaths(Map dirs) { |
187 // With a non-file: URI, the lookup assumes a packges/ dir. | 249 for (var name in dirs.keys) { |
188 // The absence of a .packages file next to the entry point means | 250 var value = dirs[name]; |
189 // that the resolution assumes a packages directory, whether it exists or | 251 if (value is Map) { |
190 // not. It should not find the .packages file in the parent directory. | 252 Map subDir = value; |
191 var files = {"sub": { "main": testMain }, | 253 fixPaths(subDir); |
192 ".packages": "foo:pkgs/foo/", | 254 } else { |
193 "pkgs": fooPackage}; | 255 var newValue = fixPath(value); |
194 await test("http: implicit packages dir", "%http/sub/main.dart", | 256 if (newValue != value) dirs[name] = newValue; |
195 http: files, | 257 } |
196 expect: { | 258 } |
197 "iroot": "%http/sub/packages/", | 259 } |
198 // "foo": null, | 260 |
199 "foo/": "%http/sub/packages/foo/", | 261 if (!mainDir.endsWith("/")) mainDir += "/"; |
200 "foo/bar": "%http/sub/packages/foo/bar", | 262 // Insert main files into the main-dir map. |
201 "foo.x": null, | 263 Map mainDirMap; |
202 }); | 264 { |
203 } | 265 if (mainDir.startsWith("%file/")) { |
204 | 266 mainDirMap = file; |
205 | 267 } else { |
206 if (failingTests.isNotEmpty) { | 268 mainDirMap = http; |
207 print("Errors found in tests:\n ${failingTests.join("\n ")}\n"); | 269 |
208 exit(255); | 270 } |
209 } | 271 var parts = mainDir.split('/'); |
210 asyncEnd(); | 272 for (int i = 1; i < parts.length - 1; i++) { |
211 } | 273 var dirName = parts[i]; |
212 | 274 mainDirMap = mainDirMap[dirName] ?? (mainDirMap[dirName] = {}); |
213 // --------------------------------------------------------- | 275 } |
214 // Helper functionality. | 276 } |
215 | 277 |
216 var failingTests = new Set(); | 278 mainDirMap["main"] = testMain; |
217 | 279 mainDirMap["spawnMain"] = spawnMain.replaceAll("%mainDir/", mainDir); |
218 var fileHttpRegexp = new RegExp(r"%(?:file|http)/"); | 280 mainDirMap["spawnUriMain"] = spawnUriMain; |
219 | 281 |
220 Future test(String name, String main, | 282 mainDir = fixPath(mainDir); |
221 {String root, String config, List<String> args, | 283 root = fixPath(root); |
222 Map file, Map http, Map expect}) async { | 284 config = fixPath(config); |
223 // Default values that are easily recognized in output. | 285 fixPaths(file); |
224 String fileRoot = "<no files configured>"; | 286 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 | 287 // 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 | 288 // 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. | 289 // value of these, you can only change what value to expect. |
264 // For values not included here (commented out), the result is not tested | 290 // For values not included here (commented out), the result is not tested |
265 // unless a value (maybe null) is provided. | 291 // unless a value (maybe null) is provided. |
266 var expects = { | 292 fixPaths(expect); |
267 "pconf": null, | 293 |
268 "proot": null, | 294 expect = { |
269 "iconf": null, | 295 "pconf": null, |
270 "iroot": null, | 296 "proot": null, |
271 // "foo": null, | 297 "iconf": null, |
272 "foo/": null, | 298 "iroot": null, |
273 "foo/bar": null, | 299 // "foo": null, |
274 "foo.x": "qux", | 300 "foo/": null, |
275 }..addAll(expect); | 301 "foo/bar": null, |
276 match(JSON.decode(output), expects, fixPaths, name); | 302 "foo.x": "qux", |
| 303 }..addAll(expect ?? const {}); |
| 304 |
| 305 // Add http files to the http server. |
| 306 if (http.isNotEmpty) { |
| 307 httpFiles[id] = http; |
| 308 } |
| 309 // Add file files to the file system. |
| 310 if (file.isNotEmpty) { |
| 311 createFiles(fileRoot, id, file); |
| 312 } |
| 313 |
| 314 configurations.add(new Configuration( |
| 315 description: description, |
| 316 root: root, |
| 317 config: config, |
| 318 mainFile: mainDir + "main.dart", |
| 319 args: const [], |
| 320 expect: expect)); |
| 321 } |
| 322 |
| 323 // The `test` function can generate file or http resources. |
| 324 // It replaces "%file/" with URI of the root directory of generated files and |
| 325 // "%http/" with the URI of the HTTP server's root in appropriate contexts |
| 326 // (all file contents and parameters). |
| 327 |
| 328 // Tests that only use one scheme to access files. |
| 329 for (var scheme in ["file", "http"]) { |
| 330 |
| 331 /// Run a test in the current scheme. |
| 332 /// |
| 333 /// The files are served either through HTTP or in a local directory. |
| 334 /// Use "%$scheme/" to refer to the root of the served files. |
| 335 addScheme(description, main, {expect, files, args, root, config}) { |
| 336 add("$scheme/$description", main, expect: expect, |
| 337 root: root, config: config, |
| 338 file: (scheme == "file") ? files : null, |
| 339 http: (scheme == "http") ? files : null); |
| 340 } |
| 341 |
| 342 { |
| 343 // No parameters, no .packages files or packages/ dir. |
| 344 // A "file:" source realizes there is no configuration and can't resolve |
| 345 // any packages, but a "http:" source assumes a "packages/" directory. |
| 346 addScheme("no resolution", |
| 347 "%$scheme/", |
| 348 files: {}, |
| 349 expect: (scheme == "file") ? { |
| 350 "foo.x": null |
| 351 } : { |
| 352 "iroot": "%http/packages/", |
| 353 "foo/": "%http/packages/foo/", |
| 354 "foo/bar": "%http/packages/foo/bar", |
| 355 "foo.x": null, |
| 356 }); |
| 357 } |
| 358 |
| 359 { |
| 360 // No parameters, no .packages files, |
| 361 // packages/ dir exists and is detected. |
| 362 var files = {"packages": fooPackage}; |
| 363 addScheme("implicit packages dir","%$scheme/", |
| 364 files: files, |
| 365 expect: { |
| 366 "iroot": "%$scheme/packages/", |
| 367 "foo/": "%$scheme/packages/foo/", |
| 368 "foo/bar": "%$scheme/packages/foo/bar", |
| 369 }); |
| 370 } |
| 371 |
| 372 { |
| 373 // No parameters, no .packages files in current dir, but one in parent, |
| 374 // packages/ dir exists and is used. |
| 375 // |
| 376 // Should not detect the .packages file in parent directory. |
| 377 // That file is empty, so if it is used, the system cannot resolve "foo". |
| 378 var files = {"sub": {"packages": fooPackage}, |
| 379 ".packages": ""}; |
| 380 addScheme("implicit packages dir overrides parent .packages", |
| 381 "%$scheme/sub/", |
| 382 files: files, |
| 383 expect: { |
| 384 "iroot": "%$scheme/sub/packages/", |
| 385 "foo/": "%$scheme/sub/packages/foo/", |
| 386 "foo/bar": "%$scheme/sub/packages/foo/bar", |
| 387 // "foo.x": "qux", // Blocked by issue http://dartbug.com/26482 |
| 388 }); |
| 389 } |
| 390 |
| 391 { |
| 392 // No parameters, a .packages file next to entry is found and used. |
| 393 // A packages/ directory is ignored. |
| 394 var files = {".packages": "foo:pkgs/foo/", |
| 395 "packages": {}, |
| 396 "pkgs": fooPackage}; |
| 397 addScheme("implicit .packages file", "%$scheme/", |
| 398 files: files, |
| 399 expect: { |
| 400 "iconf": "%$scheme/.packages", |
| 401 "foo/": "%$scheme/pkgs/foo/", |
| 402 "foo/bar": "%$scheme/pkgs/foo/bar", |
| 403 }); |
| 404 } |
| 405 |
| 406 { |
| 407 // No parameters, a .packages file in parent dir, no packages/ dir. |
| 408 // With a file: URI, find the .packages file. |
| 409 // WIth a http: URI, assume a packages/ dir. |
| 410 var files = {"sub": {}, |
| 411 ".packages": "foo:pkgs/foo/", |
| 412 "pkgs": fooPackage}; |
| 413 addScheme(".packages file in parent", "%$scheme/sub/", |
| 414 files: files, |
| 415 expect: (scheme == "file") ? { |
| 416 "iconf": "%file/.packages", |
| 417 "foo/": "%file/pkgs/foo/", |
| 418 "foo/bar": "%file/pkgs/foo/bar", |
| 419 } : { |
| 420 "iroot": "%http/sub/packages/", |
| 421 "foo/": "%http/sub/packages/foo/", |
| 422 "foo/bar": "%http/sub/packages/foo/bar", |
| 423 "foo.x": null, |
| 424 }); |
| 425 } |
| 426 |
| 427 { |
| 428 // Specified package root that doesn't exist. |
| 429 // Ignores existing .packages file and packages/ dir. |
| 430 addScheme("explicit root not there", |
| 431 "%$scheme/", |
| 432 files: {"packages": fooPackage, |
| 433 ".packages": "foo:%$scheme/packages/"}, |
| 434 root: "%$scheme/notthere/", |
| 435 expect: { |
| 436 "proot": "%$scheme/notthere/", |
| 437 "iroot": "%$scheme/notthere/", |
| 438 "foo/": "%$scheme/notthere/foo/", |
| 439 "foo/bar": "%$scheme/notthere/foo/bar", |
| 440 "foo.x": null, |
| 441 }); |
| 442 } |
| 443 |
| 444 { |
| 445 // Specified package config that doesn't exist. |
| 446 // Ignores existing .packages file and packages/ dir. |
| 447 addScheme("explicit config not there", |
| 448 "%$scheme/", |
| 449 files: {".packages": "foo:packages/foo/", |
| 450 "packages": fooPackage}, |
| 451 config: "%$scheme/.notthere", |
| 452 expect: { |
| 453 "pconf": "%$scheme/.notthere", |
| 454 "iconf": null, // <- Only there if actually loaded (unspecified). |
| 455 "foo/": null, |
| 456 "foo/bar": null, |
| 457 "foo.x": null, |
| 458 }); |
| 459 } |
| 460 |
| 461 { |
| 462 // Specified package root with no trailing slash. |
| 463 // The Platform.packageRoot and Isolate.packageRoot has a trailing slash. |
| 464 var files = {".packages": "foo:packages/foo/", |
| 465 "packages": {}, |
| 466 "pkgs": fooPackage}; |
| 467 addScheme("explicit package root, no slash", "%$scheme/", |
| 468 files: files, |
| 469 root: "%$scheme/pkgs", |
| 470 expect: { |
| 471 "proot": "%$scheme/pkgs/", |
| 472 "iroot": "%$scheme/pkgs/", |
| 473 "foo/": "%$scheme/pkgs/foo/", |
| 474 "foo/bar": "%$scheme/pkgs/foo/bar", |
| 475 }); |
| 476 } |
| 477 |
| 478 { |
| 479 // Specified package root with trailing slash. |
| 480 var files = {".packages": "foo:packages/foo/", |
| 481 "packages": {}, |
| 482 "pkgs": fooPackage}; |
| 483 addScheme("explicit package root, slash", "%$scheme/", |
| 484 files: files, |
| 485 root: "%$scheme/pkgs", |
| 486 expect: { |
| 487 "proot": "%$scheme/pkgs/", |
| 488 "iroot": "%$scheme/pkgs/", |
| 489 "foo/": "%$scheme/pkgs/foo/", |
| 490 "foo/bar": "%$scheme/pkgs/foo/bar", |
| 491 }); |
| 492 } |
| 493 |
| 494 { |
| 495 // Specified package config. |
| 496 var files = {".packages": "foo:packages/foo/", |
| 497 "packages": {}, |
| 498 ".pkgs": "foo:pkgs/foo/", |
| 499 "pkgs": fooPackage}; |
| 500 addScheme("explicit package config file", "%$scheme/", |
| 501 files: files, |
| 502 config: "%$scheme/.pkgs", |
| 503 expect: { |
| 504 "pconf": "%$scheme/.pkgs", |
| 505 "iconf": "%$scheme/.pkgs", |
| 506 "foo/": "%$scheme/pkgs/foo/", |
| 507 "foo/bar": "%$scheme/pkgs/foo/bar", |
| 508 }); |
| 509 } |
| 510 |
| 511 { |
| 512 // Specified package config as data: URI. |
| 513 // The package config can be specified as a data: URI. |
| 514 // (In that case, relative URI references in the config file won't work). |
| 515 var files = {".packages": "foo:packages/foo/", |
| 516 "packages": {}, |
| 517 "pkgs": fooPackage}; |
| 518 var dataUri = "data:,foo:%$scheme/pkgs/foo/\n"; |
| 519 addScheme("explicit data: config file", "%$scheme/", |
| 520 files: files, |
| 521 config: dataUri, |
| 522 expect: { |
| 523 "pconf": dataUri, |
| 524 "iconf": dataUri, |
| 525 "foo/": "%$scheme/pkgs/foo/", |
| 526 "foo/bar": "%$scheme/pkgs/foo/bar", |
| 527 }); |
| 528 } |
| 529 } |
| 530 |
| 531 // Tests where there are files on both http: and file: sources. |
| 532 |
| 533 for (var entryScheme in const ["file", "http"]) { |
| 534 for (var pkgScheme in const ["file", "http"]) { |
| 535 // Package root. |
| 536 if (entryScheme != pkgScheme) { |
| 537 // Package dir and entry point on different schemes. |
| 538 var files = {}; |
| 539 var https = {}; |
| 540 (entryScheme == "file" ? files : https)["main"] = testMain; |
| 541 (pkgScheme == "file" ? files : https)["pkgs"] = fooPackage; |
| 542 add("$pkgScheme pkg/$entryScheme main", "%$entryScheme/", |
| 543 file: files, http: https, |
| 544 root: "%$pkgScheme/pkgs/", |
| 545 expect: { |
| 546 "proot": "%$pkgScheme/pkgs/", |
| 547 "iroot": "%$pkgScheme/pkgs/", |
| 548 "foo/": "%$pkgScheme/pkgs/foo/", |
| 549 "foo/bar": "%$pkgScheme/pkgs/foo/bar", |
| 550 "foo.x": "qux", |
| 551 }); |
| 552 } |
| 553 // Package config. The configuration file may also be on either source. |
| 554 for (var configScheme in const ["file", "http"]) { |
| 555 // Don't do the boring stuff! |
| 556 if (entryScheme == configScheme && entryScheme == pkgScheme) continue; |
| 557 // Package config, packages and entry point not all on same scheme. |
| 558 var files = {}; |
| 559 var https = {}; |
| 560 (entryScheme == "file" ? files : https)["main"] = testMain; |
| 561 (configScheme == "file" ? files : https)[".pkgs"] = |
| 562 "foo:%$pkgScheme/pkgs/foo/\n"; |
| 563 (pkgScheme == "file" ? files : https)["pkgs"] = fooPackage; |
| 564 add("$pkgScheme pkg/$configScheme config/$entryScheme main", |
| 565 "%$entryScheme/", |
| 566 file: files, http: https, |
| 567 config: "%$configScheme/.pkgs", |
| 568 expect: { |
| 569 "pconf": "%$configScheme/.pkgs", |
| 570 "iconf": "%$configScheme/.pkgs", |
| 571 "foo/": "%$pkgScheme/pkgs/foo/", |
| 572 "foo/bar": "%$pkgScheme/pkgs/foo/bar", |
| 573 "foo.x": "qux", |
| 574 }); |
| 575 } |
| 576 } |
| 577 } |
| 578 } |
| 579 |
| 580 |
| 581 // --------------------------------------------------------- |
| 582 // Helper functionality. |
| 583 |
| 584 var fileHttpRegexp = new RegExp(r"%(?:file|http)/"); |
| 585 |
| 586 // Executes a test in a configuration. |
| 587 // |
| 588 // The test must specify which main file to use |
| 589 // (`main`, `spawnMain` or `spawnUriMain`) |
| 590 // and any arguments which will be used by `spawnMain` and `spawnUriMain`. |
| 591 // |
| 592 // The [expect] map may be used to override the expectations of the |
| 593 // configuration on a value-by-value basis. Passing, e.g., `{"pconf": null}` |
| 594 // will override only the `pconf` (`Platform.packageConfig`) expectation. |
| 595 Future testConfiguration(Configuration conf) async { |
| 596 print("-- ${conf.description}"); |
| 597 var description = conf.description; |
| 598 try { |
| 599 var output = await execDart(conf.mainFile, |
| 600 root: conf.root, |
| 601 config: conf.config, |
| 602 scriptArgs: conf.args); |
| 603 match(JSON.decode(output), conf.expect, description, output); |
277 } catch (e, s) { | 604 } catch (e, s) { |
278 // Unexpected error calling runDart or parsing the result. | 605 // Unexpected error calling execDart or parsing the result. |
279 // Report it and continue. | 606 // Report it and continue. |
280 print("ERROR running $name: $e\n$s"); | 607 print("ERROR running $description: $e\n$s"); |
281 failingTests.add(name); | 608 failingTests.putIfAbsent(description, () => []).add("$e"); |
282 } finally { | 609 } |
283 if (https != null) await https.close(); | 610 } |
284 if (tmpDir != null) tmpDir.deleteSync(recursive: true); | 611 |
285 } | |
286 } | |
287 | |
288 | 612 |
289 /// Test that the output of running testMain matches the expectations. | 613 /// Test that the output of running testMain matches the expectations. |
290 /// | 614 /// |
291 /// The output is a string which is parse as a JSON literal. | 615 /// 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`. | 616 /// The resulting map is always mapping strings to strings, or possibly `null`. |
293 /// The expectations can have non-string values other than null, | 617 /// 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 | 618 /// they are `toString`'ed before being compared (so the caller can use a URI |
295 /// or a File/Directory directly as an expectation). | 619 /// or a File/Directory directly as an expectation). |
296 void match(Map actuals, Map expectations, String fixPaths(String expectation), | 620 void match(Map actuals, Map expectations, String desc, String actualJson) { |
297 String name) { | |
298 for (var key in expectations.keys) { | 621 for (var key in expectations.keys) { |
299 var expectation = fixPaths(expectations[key]?.toString()); | 622 var expectation = expectations[key]?.toString(); |
300 var actual = actuals[key]; | 623 var actual = actuals[key]; |
301 if (expectation != actual) { | 624 if (expectation != actual) { |
302 print("ERROR: $name: $key: Expected: <$expectation> Found: <$actual>"); | 625 print("ERROR: $desc: $key: Expected: <$expectation> Found: <$actual>"); |
303 failingTests.add(name); | 626 failingTests.putIfAbsent(desc, ()=>[]).add( |
| 627 "$key: $expectation != $actual"); |
304 } | 628 } |
305 } | 629 } |
306 } | 630 } |
307 | 631 |
| 632 const String improt = "import"; // Avoid multitest import rewriting. |
| 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 = """ |
312 import "dart:convert" show JSON; | 638 $improt "dart:convert" show JSON; |
313 import "dart:io" show Platform, Directory; | 639 $improt "dart:io" show Platform, Directory; |
314 import "dart:isolate" show Isolate; | 640 $improt "dart:isolate" show Isolate; |
315 import "package:foo/foo.dart" deferred as foo; | 641 $improt "package:foo/foo.dart" deferred as foo; |
316 main(_) async { | 642 main(_) async { |
317 String platformRoot = await Platform.packageRoot; | 643 String platformRoot = await Platform.packageRoot; |
318 String platformConfig = await Platform.packageConfig; | 644 String platformConfig = await Platform.packageConfig; |
319 Directory cwd = Directory.current; | 645 Directory cwd = Directory.current; |
320 Uri script = Platform.script; | 646 Uri script = Platform.script; |
321 Uri isolateRoot = await Isolate.packageRoot; | 647 Uri isolateRoot = await Isolate.packageRoot; |
322 Uri isolateConfig = await Isolate.packageConfig; | 648 Uri isolateConfig = await Isolate.packageConfig; |
323 Uri base = Uri.base; | 649 Uri base = Uri.base; |
324 Uri res1 = await Isolate.resolvePackageUri(Uri.parse("package:foo")); | 650 Uri res1 = await Isolate.resolvePackageUri(Uri.parse("package:foo")); |
325 Uri res2 = await Isolate.resolvePackageUri(Uri.parse("package:foo/")); | 651 Uri res2 = await Isolate.resolvePackageUri(Uri.parse("package:foo/")); |
(...skipping 13 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 = """ |
353 import "dart:isolate"; | 679 $improt "dart:isolate"; |
| 680 $improt "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] == "-") ? null : Uri.parse(args[1]); |
357 Uri root = args.length > 2 && args[2].isNotEmpty ? Uri.parse(args[2]) : null; | 684 Uri root = (args[2] == "-") ? null : Uri.parse(args[2]); |
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. |
370 const String spawnMain = r""" | 703 /// |
371 import "dart:isolate"; | 704 /// Uses the first argument to select which target to spawn. |
372 import "testmain.dart" as test; | 705 /// Should be either "test", "uri" or "spawn". |
373 main() async { | 706 const String spawnMain = """ |
374 var isolate = await Isolate.spawn(test.main, [], paused: true); | 707 $improt "dart:async"; |
375 // Wait for isolate to exit before exiting the main isolate. | 708 $improt "dart:isolate"; |
376 var done = new RawReceivePort(); | 709 $improt "%mainDir/main.dart" as test; |
377 done.handler = (_) { done.close(); }; | 710 $improt "%mainDir/spawnUriMain.dart" as spawnUri; |
378 isolate.addExitHandler(done.sendPort); | 711 main(List<String> args) async { |
379 isolate.resume(isolate.pauseCapability); | 712 // Port keeps isolate alive until spawned isolate terminates. |
| 713 var port = new RawReceivePort(); |
| 714 port.handler = (res) async { |
| 715 port.close(); // Close on exit or first error. |
| 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: |
| 938 args ?? (<String>[]..addAll(newArgs ?? const <String>[]) |
| 939 ..addAll(this.args)), |
| 940 expect: expect == null |
| 941 ? this.expect |
| 942 : new Map.from(this.expect)..addAll(expect ?? const {})); |
| 943 } |
| 944 |
| 945 // For debugging. |
| 946 String toString() { |
| 947 return "Configuration($description\n" |
| 948 " root : $root\n" |
| 949 " config: $config\n" |
| 950 " main : $mainFile\n" |
| 951 " args : ${args.map((x) => '"$x"').join(" ")}\n" |
| 952 ") : expect {\n${expect.keys.map((k) => |
| 953 ' "$k"'.padRight(6) + ":${JSON.encode(expect[k])}\n").join()}" |
| 954 "}"; |
| 955 } |
| 956 } |
| 957 |
| 958 |
| 959 // Inserts the file with generalized [name] at [path] with [content]. |
| 960 // |
| 961 // The [path] is a directory where the file is created. It must start with |
| 962 // either '%file/' or '%http/' to select the structure to put it into. |
| 963 // |
| 964 // The [name] should not have a trailing ".dart" for Dart files. Any file |
| 965 // not starting with "." is assumed to be a ".dart" file. |
| 966 void insertFileAt(Map file, Map http, |
| 967 String path, String name, String content) { |
| 968 var parts = path.split('/').toList(); |
| 969 var dir = (parts[0] == "%file") ? file : http; |
| 970 for (var i = 1; i < parts.length - 1; i++) { |
| 971 var entry = parts[i]; |
| 972 dir = dir[entry] ?? (dir[entry] = {}); |
| 973 } |
| 974 dir[name] = content; |
| 975 } |
OLD | NEW |