Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(651)

Side by Side Diff: tests/standalone/packages_file_test.dart

Issue 2020653002: Add package-config tests where test is running in Isolate.spawn-isolates. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: More tests Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tests/standalone/standalone.status » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 }
OLDNEW
« no previous file with comments | « no previous file | tests/standalone/standalone.status » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698