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