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 |