OLD | NEW |
---|---|
(Empty) | |
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 | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 import "dart:async"; | |
6 import "dart:io"; | |
7 import "dart:isolate"; | |
8 import "dart:convert" show JSON; | |
9 import "package:path/path.dart" as p; | |
10 import "package:expect/expect.dart"; | |
11 import "package:async_helper/async_helper.dart"; | |
12 | |
13 main() async { | |
14 asyncStart(); | |
15 | |
16 await test("file: no resolution", | |
floitsch
2016/05/26 19:09:55
Add a comment on the first test that explains that
| |
17 "%file/main.dart", | |
18 file: {"main": testMain}, | |
19 expect: {"foo.x": null}); | |
20 | |
21 // An HTTP script with no ".packages" file assumes a "packages" dir. | |
22 await test("http: no resolution", "%http/main.dart", | |
23 http: {"main": testMain}, | |
24 expect: { | |
25 // "foo": null, | |
26 "foo/": "%http/packages/foo/", | |
27 "foo/bar": "%http/packages/foo/bar", | |
28 "foo.x": null, | |
29 }); | |
30 | |
31 for (var scheme in ["file", "http"]) { | |
32 | |
33 testScheme(name, main, {expect, files, args, root, config}) { | |
34 return test("$scheme: $name", main, expect: expect, | |
35 root: root, config: config, args: args, | |
36 file: scheme == "file" ? files : null, | |
37 http: scheme == "http" ? files : null); | |
38 } | |
39 | |
40 { | |
41 var files = {"main": testMain, "packages": fooPackage}; | |
42 // Expect implicitly detected package dir. | |
43 await testScheme("implicit packages dir","%$scheme/main.dart", | |
44 files: files, | |
45 expect: { | |
46 "iroot": "%$scheme/packages/", | |
47 // "foo": null, | |
48 "foo/": "%$scheme/packages/foo/", | |
49 "foo/bar": "%$scheme/packages/foo/bar", | |
50 }); | |
51 } | |
52 | |
53 { | |
54 var files = {"sub": {"main": testMain, "packages": fooPackage}, | |
55 ".packages": ""}; | |
floitsch
2016/05/26 19:09:56
Provide comment what an empty ".packages" should d
Lasse Reichstein Nielsen
2016/05/27 10:53:56
Will do.
We don't ignore it - the test should not
| |
56 // Expect implicitly detected package dir. | |
57 await testScheme("implicit packages dir 2", "%$scheme/sub/main.dart", | |
58 files: files, | |
59 expect: { | |
60 "iroot": "%$scheme/sub/packages/", | |
61 // "foo": null, | |
62 "foo/": "%$scheme/sub/packages/foo/", | |
63 "foo/bar": "%$scheme/sub/packages/foo/bar", | |
64 }); | |
65 } | |
66 | |
67 { | |
68 var files = {"main": testMain, | |
69 ".packages": "foo:pkgs/foo/", | |
70 "pkgs": fooPackage}; | |
71 await testScheme("implicit .packages file", "%$scheme/main.dart", | |
72 files: files, | |
73 expect: { | |
74 "iconf": "%$scheme/.packages", | |
75 // "foo": null, | |
76 "foo/": "%$scheme/pkgs/foo/", | |
77 "foo/bar": "%$scheme/pkgs/foo/bar", | |
78 }); | |
79 } | |
80 | |
81 { | |
82 var files = {"main": testMain, | |
83 ".packages": "foo:packages/foo/", | |
84 "packages": fooPackage, | |
85 "pkgs": fooPackage}; | |
86 await testScheme("explicit package root, no slash", "%$scheme/main.dart", | |
87 files: files, | |
88 root: "%$scheme/pkgs", | |
89 expect: { | |
90 "proot": "%$scheme/pkgs/", | |
91 "iroot": "%$scheme/pkgs/", | |
92 // "foo": null, | |
93 "foo/": "%$scheme/pkgs/foo/", | |
94 "foo/bar": "%$scheme/pkgs/foo/bar", | |
95 }); | |
96 } | |
97 | |
98 { | |
99 var files = {"main": testMain, | |
100 ".packages": "foo:packages/foo/", | |
101 "packages": fooPackage, | |
102 "pkgs": fooPackage}; | |
103 await testScheme("explicit package root, slash", "%$scheme/main.dart", | |
104 files: files, | |
105 root: "%$scheme/pkgs", | |
106 expect: { | |
107 "proot": "%$scheme/pkgs/", | |
108 "iroot": "%$scheme/pkgs/", | |
109 // "foo": null, | |
110 "foo/": "%$scheme/pkgs/foo/", | |
111 "foo/bar": "%$scheme/pkgs/foo/bar", | |
112 }); | |
113 } | |
114 | |
115 { | |
116 var files = {"main": testMain, | |
117 ".packages": "foo:packages/foo/", | |
118 "packages": fooPackage, | |
119 ".pkgs": "foo:pkgs/foo/", | |
120 "pkgs": fooPackage}; | |
121 await testScheme("explicit package config file", "%$scheme/main.dart", | |
122 files: files, | |
123 config: "%$scheme/.pkgs", | |
124 expect: { | |
125 "pconf": "%$scheme/.pkgs", | |
126 "iconf": "%$scheme/.pkgs", | |
127 // "foo": null, | |
128 "foo/": "%$scheme/pkgs/foo/", | |
129 "foo/bar": "%$scheme/pkgs/foo/bar", | |
130 }); | |
131 } | |
132 | |
133 { | |
134 var files = {"main": testMain, | |
135 ".packages": "foo:packages/foo/", | |
136 "packages": fooPackage, | |
137 "pkgs": fooPackage}; | |
138 var dataUri = "data:,foo:%$scheme/pkgs/foo/\n"; | |
139 await testScheme("explicit data: config file", "%$scheme/main.dart", | |
140 files: files, | |
141 config: dataUri, | |
142 expect: { | |
143 "pconf": dataUri, | |
144 "iconf": dataUri, | |
145 // "foo": null, | |
146 "foo/": "%$scheme/pkgs/foo/", | |
147 "foo/bar": "%$scheme/pkgs/foo/bar", | |
148 }); | |
149 } | |
150 } | |
151 | |
152 { | |
153 // With a file: URI, the lookup checks for a .packages file in superdirs. | |
154 var files = {"sub": { "main": testMain }, | |
155 ".packages": "foo:pkgs/foo/", | |
156 "pkgs": fooPackage}; | |
157 await test("file: implicit .packages file in ..", "%file/sub/main.dart", | |
158 file: files, | |
159 expect: { | |
160 "iconf": "%file/.packages", | |
161 // "foo": null, | |
162 "foo/": "%file/pkgs/foo/", | |
163 "foo/bar": "%file/pkgs/foo/bar", | |
164 }); | |
165 } | |
166 | |
167 { | |
168 // With a non-file: URI, the lookup assumes a packges/ dir. | |
floitsch
2016/05/26 19:09:56
packages
Lasse Reichstein Nielsen
2016/05/27 10:53:56
I like to use .packages to signify the file and pa
| |
169 var files = {"sub": { "main": testMain }, | |
170 ".packages": "foo:pkgs/foo/", | |
171 "pkgs": fooPackage}; | |
172 // Expect implicitly detected .package file. | |
173 await test("http: implicit packages dir", "%http/sub/main.dart", | |
174 http: files, | |
175 expect: { | |
176 "iroot": "%http/sub/packages/", | |
177 // "foo": null, | |
178 "foo/": "%http/sub/packages/foo/", | |
179 "foo/bar": "%http/sub/packages/foo/bar", | |
180 "foo.x": null, | |
181 }); | |
182 } | |
183 | |
184 | |
185 if (failingTests.isNotEmpty) { | |
186 print("Errors found in tests:\n ${failingTests.join("\n ")}\n"); | |
187 exit(255); | |
188 } | |
189 asyncEnd(); | |
190 } | |
191 | |
192 // --------------------------------------------------------- | |
193 // Helper functionality. | |
194 | |
195 var failingTests = new Set(); | |
196 | |
197 var fileHttpRegexp = new RegExp(r"%(?:file|http)/"); | |
198 | |
199 Future test(String name, String main, | |
200 {String root, String config, List<String> args, | |
201 Map file, Map http, Map expect}) async { | |
202 String fileRoot = "<no files configured>"; | |
203 String httpRoot = "<not http server configured>"; | |
floitsch
2016/05/26 19:09:56
no
Lasse Reichstein Nielsen
2016/05/27 10:53:56
Will do.
| |
204 | |
205 String fixPaths(String source) { | |
206 if (source == null) return null; | |
207 var result = source.replaceAllMapped(fileHttpRegexp, (match) { | |
208 if (source.startsWith("file", match.start + 1)) return fileRoot; | |
209 return httpRoot; | |
210 }); | |
211 return result; | |
212 } | |
213 | |
214 Directory tmpDir; | |
215 var https; | |
216 if (file != null) { | |
217 tmpDir = createTempDir(); | |
218 fileRoot = new Uri.directory(tmpDir.path).toString(); | |
219 } | |
220 if (http != null) { | |
221 https = await startServer(http, fixPaths); | |
222 httpRoot = "http://${https.address.address}:${https.port}/"; | |
223 } | |
224 if (file != null) { | |
225 createFiles(tmpDir, file, fixPaths); | |
226 } | |
227 | |
228 try { | |
229 var output = await runDart(fixPaths(main), | |
230 root: fixPaths(root), | |
231 config: fixPaths(config), | |
232 scriptArgs: args?.map(fixPaths)); | |
233 // These expectations are default. If not overridden the value will be | |
234 // expected to be null. That is, you can't avoid testing the actual | |
235 // value of these, you can only change what value to expect. | |
236 // For values not included here (commented out), the result is not tested | |
237 // unless a value (maybe null) is provided. | |
238 var expects = { | |
239 "pconf": null, | |
240 "proot": null, | |
241 "iconf": null, | |
242 "iconf": null, | |
243 // "foo": null, | |
244 "foo/": null, | |
245 "foo/bar": null, | |
246 "foo.x": "qux", | |
247 }..addAll(expect); | |
248 match(JSON.decode(output), expects, fixPaths, name); | |
249 } catch (e) { | |
250 // Unexpected error calling runDart or parsing the result. | |
251 // Report it and continue. | |
252 print("ERROR running $name: $e\n$s"); | |
253 failingTests.add(name); | |
254 } finally { | |
255 if (https != null) await https.close(); | |
256 if (tmpDir != null) tmpDir.deleteSync(recursive: true); | |
257 } | |
258 } | |
259 | |
260 | |
261 /// Test that the output of running testMain matches the expectations. | |
262 /// | |
263 /// The output is a string which is parse as a JSON literal. | |
264 /// The resulting map is always mapping strings to strings, or possibly `null`. | |
265 /// The expectations can have non-string values other than null, | |
266 /// they are `toString`'ed before being compared (so the caller can use a URI | |
267 /// or a File/Directory directly as an expectation). | |
268 void match(Map actuals, Map expectations, String fixPaths(String expectation), | |
269 String name) { | |
270 for (var key in expectations.keys) { | |
271 var expectation = fixPaths(expectations[key]?.toString()); | |
272 var actual = actuals[key]; | |
273 if (expectation != actual) { | |
274 print("ERROR: $name: $key: Expected: <$expectation> Found: <$actual>"); | |
275 failingTests.add(name); | |
276 } | |
277 } | |
278 } | |
279 | |
280 /// Script that prints the current state and the result of resolving | |
281 /// a few package URIs. This script will be invoked in different settings, | |
282 /// and the result will be parsed and compared to the expectations. | |
283 const String testMain = r""" | |
284 import "dart:convert" show JSON; | |
285 import "dart:io" show Platform, Directory; | |
286 import "dart:isolate" show Isolate; | |
287 import "package:foo/foo.dart" deferred as foo; | |
288 main(_) async { | |
289 String platformRoot = await Platform.packageRoot; | |
290 String platformConfig = await Platform.packageConfig; | |
291 Directory cwd = Directory.current; | |
292 Uri script = Platform.script; | |
293 Uri isolateRoot = await Isolate.packageRoot; | |
294 Uri isolateConfig = await Isolate.packageConfig; | |
295 Uri base = Uri.base; | |
296 Uri res1 = await Isolate.resolvePackageUri(Uri.parse("package:foo")); | |
297 Uri res2 = await Isolate.resolvePackageUri(Uri.parse("package:foo/")); | |
298 Uri res3 = await Isolate.resolvePackageUri(Uri.parse("package:foo/bar")); | |
299 String fooX = await foo | |
300 .loadLibrary() | |
301 .timeout(const Duration(seconds: 1)) | |
302 .then((_) => foo.x, onError: (_) => null); | |
303 print(JSON.encode({ | |
304 "cwd": cwd.path, | |
305 "base": base?.toString(), | |
306 "script": script?.toString(), | |
307 "proot": platformRoot, | |
308 "pconf": platformConfig, | |
309 "iroot" : isolateRoot?.toString(), | |
310 "iconf" : isolateConfig?.toString(), | |
311 "foo": res1?.toString(), | |
312 "foo/": res2?.toString(), | |
313 "foo/bar": res3?.toString(), | |
314 "foo.x": fooX?.toString(), | |
315 })); | |
316 } | |
317 """; | |
318 | |
319 /// Script that spawns a new Isolate using Isolate.spawnUri. | |
320 /// | |
321 /// Takes URI of target isolate, package config and package root as | |
322 /// command line arguments. Any further arguments are forwarded to the | |
323 /// spawned isolate. | |
324 const String spawnUriMain = r""" | |
floitsch
2016/05/26 19:09:56
unused.
Lasse Reichstein Nielsen
2016/05/27 10:53:56
Yes. Reserved for later use.
That's the part I'm g
| |
325 import "dart:isolate"; | |
326 main(args) async { | |
327 Uri target = Uri.parse(args[0]); | |
328 Uri conf = args.length > 1 && args[1].isNotEmpty ? Uri.parse(args[1]) : null; | |
329 Uri root = args.length > 2 && args[2].isNotEmpty ? Uri.parse(args[2]) : null; | |
330 var restArgs = args.skip(3).toList(); | |
331 var isolate = await Isolate.spawnUri(target, restArgs, | |
332 packageRoot: root, packageConfig: conf, paused: true); | |
333 // Wait for isolate to exit before exiting the main isolate. | |
334 var done = new RawReceivePort(); | |
335 done.handler = (_) { done.close(); }; | |
336 isolate.addExitHandler(done.sendPort); | |
337 isolate.resume(isolate.pauseCapability); | |
338 } | |
339 """; | |
340 | |
341 /// Script that spawns a new Isolate using Isolate.spawn. | |
342 const String spawnMain = r""" | |
floitsch
2016/05/26 19:09:56
unused.
Lasse Reichstein Nielsen
2016/05/27 10:53:56
Ditto.
| |
343 import "dart:isolate"; | |
344 import "testmain.dart" as test; | |
345 main() async { | |
346 var isolate = await Isolate.spawn(test.main, [], paused: true); | |
347 // Wait for isolate to exit before exiting the main isolate. | |
348 var done = new RawReceivePort(); | |
349 done.handler = (_) { done.close(); }; | |
350 isolate.addExitHandler(done.sendPort); | |
351 isolate.resume(isolate.pauseCapability); | |
352 } | |
353 """; | |
354 | |
355 /// A package directory containing only one package, "foo", with one file. | |
356 const Map fooPackage = const { "foo": const { "foo": "var x = 'qux';" }}; | |
357 | |
358 /// Runs the Dart executable with the provided parameters. | |
359 /// | |
360 /// Captures and returns the output. | |
361 Future<String> runDart(String script, | |
362 {String root, String config, | |
363 Iterable<String> scriptArgs}) async { | |
364 // TODO: Find a way to change CWD before running script. | |
365 var executable = Platform.executable; | |
366 var args = []; | |
367 if (root != null) args..add("-p")..add(root); | |
368 if (config != null) args..add("--packages=$config"); | |
369 args.add(script); | |
370 if (scriptArgs != null) { | |
371 args.addAll(scriptArgs); | |
372 } | |
373 return Process.run(executable, args).then((results) { | |
374 if (results.exitCode != 0) { | |
375 throw results.stderr; | |
376 } | |
377 return results.stdout; | |
378 }); | |
379 } | |
380 | |
381 /// Creates a number of files and subdirectories. | |
382 /// | |
383 /// The [content] is the content of the directory itself. The map keys are | |
384 /// names and the values are either strings that represent Dart file contents | |
385 /// or maps that represent subdirectories. | |
386 /// Subdirectories may include a package directory. If [packageDir] | |
387 /// is provided, a `.packages` file is created for the content of that | |
388 /// directory. | |
389 void createFiles(Directory tempDir, Map content, String fixPaths(String text), | |
390 [String packageDir]) { | |
391 Directory createDir(Directory base, String name) { | |
392 Directory newDir = new Directory(p.join(base.path, name)); | |
393 newDir.createSync(); | |
394 return newDir; | |
395 } | |
396 | |
397 void createTextFile(Directory base, String name, String content) { | |
398 File newFile = new File(p.join(base.path, name)); | |
399 newFile.writeAsStringSync(fixPaths(content)); | |
400 } | |
401 | |
402 void createRecursive(Directory dir, Map map) { | |
403 for (var name in map.keys) { | |
404 var content = map[name]; | |
405 if (content is String) { | |
406 // If the name starts with "." it's a .packages file, otherwise it's | |
407 // a dart file. Those are the only files we care about in this test. | |
408 createTextFile(dir, | |
409 name.startsWith(".") ? name : name + ".dart", | |
410 content); | |
411 } else { | |
412 assert(content is Map); | |
413 var subdir = createDir(dir, name); | |
414 createRecursive(subdir, content); | |
415 } | |
416 } | |
417 } | |
418 | |
419 createRecursive(tempDir, content); | |
420 if (packageDir != null) { | |
421 // Unused? | |
422 Map packages = content[packageDir]; | |
423 var entries = | |
424 packages.keys.map((key) => "$key:$packageDir/$key").join("\n"); | |
425 createTextFile(tempDir, ".packages", entries); | |
426 } | |
427 return tempDir; | |
428 } | |
429 | |
430 /// Start an HTTP server which serves a directory/file structure. | |
431 /// | |
432 /// The directories and files are described by [files]. | |
433 /// | |
434 /// Each map key is an entry in a directory. A `Map` value is a sub-directory | |
435 /// and a `String` value is a text file. | |
436 /// The file contents are run through [fixPaths] to allow them to be self- | |
437 /// referential. | |
438 Future<HttpServer> startServer(Map files, String fixPaths(String text)) async { | |
439 return (await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 0)) | |
440 ..forEach((request) { | |
441 var result = files; | |
442 onFailure: { | |
443 for (var part in request.uri.pathSegments) { | |
444 if (part.endsWith(".dart")) { | |
445 part = part.substring(0, part.length - 5); | |
446 } | |
447 if (result is Map) { | |
448 result = result[part]; | |
449 } else { | |
450 break onFailure; | |
451 } | |
452 } | |
453 if (result is String) { | |
454 request.response..write(fixPaths(result)) | |
455 ..close(); | |
456 return; | |
457 } | |
458 } | |
459 request.response..statusCode = HttpStatus.NOT_FOUND | |
460 ..close(); | |
461 }); | |
462 } | |
463 | |
464 // Counter used to avoid reusing temporary directory names. | |
465 // Some platforms are timer based, and creating two temp-dirs withing a short | |
466 // duration may cause a collision. | |
467 int tmpDirCounter = 0; | |
468 | |
469 Directory createTempDir() { | |
470 return Directory.systemTemp.createTempSync("pftest-${tmpDirCounter++}-"); | |
471 } | |
OLD | NEW |