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

Side by Side Diff: utils/tests/pub/test_pub.dart

Issue 10868059: Add a number of convenience methods to test_pub. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fix some checked-mode issues Created 8 years, 4 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 | Annotate | Revision Log
« no previous file with comments | « utils/tests/pub/pub_test.dart ('k') | no next file » | 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) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 /** 5 /**
6 * Test infrastructure for testing pub. Unlike typical unit tests, most pub 6 * Test infrastructure for testing pub. Unlike typical unit tests, most pub
7 * tests are integration tests that stage some stuff on the file system, run 7 * tests are integration tests that stage some stuff on the file system, run
8 * pub, and then validate the results. This library provides an API to build 8 * pub, and then validate the results. This library provides an API to build
9 * tests like that. 9 * tests like that.
10 */ 10 */
11 #library('test_pub'); 11 #library('test_pub');
12 12
13 #import('dart:io'); 13 #import('dart:io');
14 #import('dart:isolate'); 14 #import('dart:isolate');
15 #import('dart:json'); 15 #import('dart:json');
16 #import('dart:math'); 16 #import('dart:math');
17 #import('dart:uri'); 17 #import('dart:uri');
18 18
19 #import('../../../pkg/unittest/unittest.dart'); 19 #import('../../../pkg/unittest/unittest.dart');
20 #import('../../lib/file_system.dart', prefix: 'fs'); 20 #import('../../lib/file_system.dart', prefix: 'fs');
21 #import('../../pub/git_source.dart');
21 #import('../../pub/io.dart'); 22 #import('../../pub/io.dart');
23 #import('../../pub/repo_source.dart');
24 #import('../../pub/sdk_source.dart');
22 #import('../../pub/utils.dart'); 25 #import('../../pub/utils.dart');
23 #import('../../pub/yaml/yaml.dart'); 26 #import('../../pub/yaml/yaml.dart');
24 27
25 /** 28 /**
26 * Creates a new [FileDescriptor] with [name] and [contents]. 29 * Creates a new [FileDescriptor] with [name] and [contents].
27 */ 30 */
28 FileDescriptor file(Pattern name, String contents) => 31 FileDescriptor file(Pattern name, String contents) =>
29 new FileDescriptor(name, contents); 32 new FileDescriptor(name, contents);
30 33
31 /** 34 /**
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
116 _server.close(); 119 _server.close();
117 _server = null; 120 _server = null;
118 // TODO(nweiz): Remove this once issue 4155 is fixed. Pumping the event loop 121 // TODO(nweiz): Remove this once issue 4155 is fixed. Pumping the event loop
119 // *seems* to be enough to ensure that the server is actually closed, but I'm 122 // *seems* to be enough to ensure that the server is actually closed, but I'm
120 // putting this at 10ms to be safe. 123 // putting this at 10ms to be safe.
121 return sleep(10); 124 return sleep(10);
122 } 125 }
123 126
124 /** 127 /**
125 * Creates an HTTP server that replicates the structure of pub.dartlang.org. 128 * Creates an HTTP server that replicates the structure of pub.dartlang.org.
126 * [pubspecs] is a list of YAML-format pubspecs representing the packages to 129 * [pubspecs] is a list of unserialized pubspecs representing the packages to
127 * serve. 130 * serve.
128 */ 131 */
129 void servePackages(String host, int port, List<String> pubspecs) { 132 void servePackages(String host, int port, List<Map> pubspecs) {
130 var packages = <String, Map<String, String>>{}; 133 var packages = <String, Map<String, String>>{};
131 pubspecs.forEach((spec) { 134 for (var spec in pubspecs) {
132 var parsed = loadYaml(spec); 135 var name = spec['name'];
133 var name = parsed['name']; 136 var version = spec['version'];
134 var version = parsed['version']; 137 packages.putIfAbsent(name, () => <String, String>{})[version] = yaml(spec);
135 packages.putIfAbsent(name, () => <String, String>{})[version] = spec; 138 }
136 });
137 139
138 serve(host, port, [ 140 serve(host, port, [
139 dir('packages', flatten(packages.getKeys().map((name) { 141 dir('packages', flatten(packages.getKeys().map((name) {
140 return [ 142 return [
141 file('$name.json', 143 file('$name.json',
142 JSON.stringify({'versions': packages[name].getKeys()})), 144 JSON.stringify({'versions': packages[name].getKeys()})),
143 dir(name, [ 145 dir(name, [
144 dir('versions', flatten(packages[name].getKeys().map((version) { 146 dir('versions', flatten(packages[name].getKeys().map((version) {
145 return [ 147 return [
146 file('$version.yaml', packages[name][version]), 148 file('$version.yaml', packages[name][version]),
147 tar('$version.tar.gz', [ 149 tar('$version.tar.gz', [
148 file('pubspec.yaml', packages[name][version]), 150 file('pubspec.yaml', packages[name][version]),
149 file('$name.dart', 'main() => print("$name $version");') 151 file('$name.dart', 'main() => print("$name $version");')
150 ]) 152 ])
151 ]; 153 ];
152 }))) 154 })))
153 ]) 155 ])
154 ]; 156 ];
155 }))) 157 })))
156 ]); 158 ]);
157 } 159 }
158 160
161 /** Converts [value] into a YAML string. */
162 String yaml(value) => JSON.stringify(value);
163
164 /**
165 * Describes a file named `pubspec.yaml` with the given YAML-serialized
166 * [contents], which should be a serializable object.
167 *
168 * [contents] may contain [Future]s that resolve to serializable objects, which
169 * may in turn contain [Future]s recursively.
170 */
171 Descriptor pubspec(Map contents) {
172 return async(_awaitObject(contents).transform((resolvedContents) =>
173 file("pubspec.yaml", yaml(resolvedContents))));
174 }
175
176 /**
177 * Describes a file named `pubspec.yaml` for an application package with the
178 * given [dependencies].
179 */
180 Descriptor appPubspec(List dependencies) =>
181 pubspec({"dependencies": _dependencyListToMap(dependencies)});
182
183 /**
184 * Describes a file named `pubspec.yaml` for a library package with the given
185 * [name], [version], and [dependencies].
186 */
187 Descriptor libPubspec(String name, String version, [List dependencies]) =>
188 pubspec(package(name, version, dependencies));
189
190 /**
191 * Describes a map representing a library package with the given [name],
192 * [version], and [dependencies].
193 */
194 Map package(String name, String version, [List dependencies]) {
195 var package = {"name": name, "version": version};
196 if (dependencies != null) {
197 package["dependencies"] = _dependencyListToMap(dependencies);
198 }
199 return package;
200 }
201
202 /**
203 * Describes a map representing a dependency on a package in the package
204 * repository.
205 */
206 Map dependency(String name, [String versionConstraint]) {
207 var dependency = {"repo": {"name": name, "url": "http://localhost:3123"}};
208 if (versionConstraint != null) dependency["version"] = versionConstraint;
209 return dependency;
210 }
211
212 /**
213 * Describes a directory for a package installed from the mock package repo.
214 * This directory is of the form found in the `packages/` directory.
215 */
216 DirectoryDescriptor packageDir(String name, String version) {
217 return dir(name, [
218 file("$name.dart", 'main() => print("$name $version");')
219 ]);
220 }
221
222 /**
223 * Describes a directory for a package installed from the mock package server.
224 * This directory is of the form found in the global package cache.
225 */
226 DirectoryDescriptor packageCacheDir(String name, String version) {
227 return dir("$name-$version", [
228 file("$name.dart", 'main() => print("$name $version");')
229 ]);
230 }
231
232 /**
233 * Describes a directory for a Git package. This directory is of the form found
234 * in the global package cache.
235 */
236 DirectoryDescriptor gitPackageCacheDir(String name, [int modifier]) {
237 var value = name;
238 if (modifier != null) value = "$name $modifier";
239 return dir(new RegExp("$name${@'-[a-f0-9]+'}"), [
240 file('$name.dart', 'main() => "$value";')
241 ]);
242 }
243
244 /**
245 * Describes the `packages/` directory containing all the given [packages],
246 * which should be name/version pairs. The packages will be validated against
247 * the format produced by the mock package server.
248 */
249 DirectoryDescriptor packagesDir(Map<String, String> packages) {
250 var contents = <Descriptor>[];
251 packages.forEach((name, version) {
252 contents.add(packageDir(name, version));
253 });
254 return dir(packagesPath, contents);
255 }
256
257 /**
258 * Describes the global package cache directory containing all the given
259 * [packages], which should be name/version pairs. The packages will be
260 * validated against the format produced by the mock package server.
261 *
262 * A package's value may also be a list of versions, in which case all versions
263 * are expected to be installed.
264 */
265 DirectoryDescriptor cacheDir(Map packages) {
266 var contents = <Descriptor>[];
267 packages.forEach((name, versions) {
268 if (versions is! List) versions = [versions];
269 for (var version in versions) {
270 contents.add(packageCacheDir(name, version));
271 }
272 });
273 return dir(cachePath, [
274 dir('repo', [dir('localhost%583123', contents)])
275 ]);
276 }
277
278 /**
279 * Describes the application directory, containing only a pubspec specifying the
280 * given [dependencies].
281 */
282 DirectoryDescriptor appDir(List dependencies) =>
283 dir(appPath, [appPubspec(dependencies)]);
284
285 /**
286 * Converts a list of dependencies as passed to [package] into a hash as used in
287 * a pubspec.
288 */
289 Map _dependencyListToMap(List<Map> dependencies) {
290 var result = <String, Map>{};
291 dependencies.map((dependency) {
292 var keys = dependency.getKeys().filter((key) => key != "version");
293 var sourceName = only(keys);
294 var source;
295 switch (sourceName) {
296 case "git":
297 source = new GitSource();
298 break;
299 case "repo":
300 source = new RepoSource();
301 break;
302 case "sdk":
303 source = new SdkSource('');
304 break;
305 default:
306 throw 'Unknown source "$sourceName"';
307 }
308
309 result[source.packageName(dependency[sourceName])] = dependency;
310 });
311 return result;
312 }
313
159 /** 314 /**
160 * The path of the package cache directory used for tests. Relative to the 315 * The path of the package cache directory used for tests. Relative to the
161 * sandbox directory. 316 * sandbox directory.
162 */ 317 */
163 final String cachePath = "cache"; 318 final String cachePath = "cache";
164 319
165 /** 320 /**
166 * The path of the mock SDK directory used for tests. Relative to the sandbox 321 * The path of the mock SDK directory used for tests. Relative to the sandbox
167 * directory. 322 * directory.
168 */ 323 */
(...skipping 308 matching lines...) Expand 10 before | Expand all | Expand 10 after
477 } 632 }
478 633
479 return listDir(dir).chain((files) { 634 return listDir(dir).chain((files) {
480 var matches = files.filter((file) => endsWithPattern(file, name)); 635 var matches = files.filter((file) => endsWithPattern(file, name));
481 if (matches.length == 0) { 636 if (matches.length == 0) {
482 Expect.fail('No files in $dir match pattern $name.'); 637 Expect.fail('No files in $dir match pattern $name.');
483 } 638 }
484 if (matches.length == 1) return validate(matches[0]); 639 if (matches.length == 1) return validate(matches[0]);
485 640
486 var failures = []; 641 var failures = [];
642 var successes = 0;
487 var completer = new Completer(); 643 var completer = new Completer();
644 checkComplete() {
645 if (failures.length + successes != matches.length) return;
646 if (successes > 0) {
647 completer.complete(null);
648 return;
649 }
650
651 var error = new StringBuffer();
652 error.add("No files named $name in $dir were valid:\n");
653 for (var failure in failures) {
654 error.add(" ").add(failure).add("\n");
655 }
656 completer.completeException(new ExpectException(error.toString()));
657 }
658
488 for (var match in matches) { 659 for (var match in matches) {
489 var future = validate(match); 660 var future = validate(match);
490 661
491 future.handleException((e) { 662 future.handleException((e) {
492 failures.add(e); 663 failures.add(e);
493 if (failures.length != matches.length) return true; 664 checkComplete();
494
495 var error = new StringBuffer();
496 error.add("No files named $name in $dir were valid:\n");
497 for (var failure in failures) {
498 error.add(" ").add(failure).add("\n");
499 }
500 completer.completeException(new ExpectException(error.toString()));
501 return true; 665 return true;
502 }); 666 });
503 667
504 future.then(completer.complete); 668 future.then((_) {
669 successes++;
670 checkComplete();
671 });
505 } 672 }
506 return completer.future; 673 return completer.future;
507 }); 674 });
508 } 675 }
509 } 676 }
510 677
511 /** 678 /**
512 * Describes a file. These are used both for setting up an expected directory 679 * Describes a file. These are used both for setting up an expected directory
513 * tree before running a test, and for validating that the file system matches 680 * tree before running a test, and for validating that the file system matches
514 * some expectations after running it. 681 * some expectations after running it.
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after
578 */ 745 */
579 final List<Descriptor> contents; 746 final List<Descriptor> contents;
580 747
581 DirectoryDescriptor(Pattern name, this.contents) : super(name); 748 DirectoryDescriptor(Pattern name, this.contents) : super(name);
582 749
583 /** 750 /**
584 * Creates the file within [dir]. Returns a [Future] that is completed after 751 * Creates the file within [dir]. Returns a [Future] that is completed after
585 * the creation is done. 752 * the creation is done.
586 */ 753 */
587 Future<Directory> create(parentDir) { 754 Future<Directory> create(parentDir) {
588 final completer = new Completer<Directory>(); 755 // Create the directory.
756 return ensureDir(join(parentDir, _stringName)).chain((dir) {
757 if (contents == null) return new Future<Directory>.immediate(dir);
589 758
590 // Create the directory. 759 // Recursively create all of its children.
591 ensureDir(join(parentDir, _stringName)).then((dir) { 760 final childFutures = contents.map((child) => child.create(dir));
592 if (contents == null) { 761 // Only complete once all of the children have been created too.
593 completer.complete(dir); 762 return Futures.wait(childFutures).transform((_) => dir);
594 } else {
595 // Recursively create all of its children.
596 final childFutures = contents.map((child) => child.create(dir));
597 Futures.wait(childFutures).then((_) {
598 // Only complete once all of the children have been created too.
599 completer.complete(dir);
600 });
601 }
602 }); 763 });
603
604 return completer.future;
605 } 764 }
606 765
607 /** 766 /**
608 * Deletes the directory within [dir]. Returns a [Future] that is completed 767 * Deletes the directory within [dir]. Returns a [Future] that is completed
609 * after the deletion is done. 768 * after the deletion is done.
610 */ 769 */
611 Future delete(dir) { 770 Future delete(dir) {
612 return deleteDir(join(dir, _stringName)); 771 return deleteDir(join(dir, _stringName));
613 } 772 }
614 773
(...skipping 182 matching lines...) Expand 10 before | Expand all | Expand 10 after
797 }).then((tar) { 956 }).then((tar) {
798 var sourceStream = tar.openInputStream(); 957 var sourceStream = tar.openInputStream();
799 pipeInputToInput( 958 pipeInputToInput(
800 sourceStream, sinkStream, onClosed: tempDir.deleteRecursively); 959 sourceStream, sinkStream, onClosed: tempDir.deleteRecursively);
801 }); 960 });
802 return sinkStream; 961 return sinkStream;
803 } 962 }
804 } 963 }
805 964
806 /** 965 /**
966 * Takes a simple data structure (composed of [Map]s, [List]s, scalar objects,
967 * and [Future]s) and recursively resolves all the [Future]s contained within.
968 * Completes with the fully resolved structure.
969 */
970 Future _awaitObject(object) {
971 // Unroll nested futures.
972 if (object is Future) return object.chain(_awaitObject);
973 if (object is Collection) return Futures.wait(object.map(_awaitObject));
974 if (object is! Map) return new Future.immediate(object);
975
976 var pairs = <Future<Pair>>[];
977 object.forEach((key, value) {
978 pairs.add(_awaitObject(value)
979 .transform((resolved) => new Pair(key, resolved)));
980 });
981 return Futures.wait(pairs).transform((resolvedPairs) {
982 var map = {};
983 for (var pair in resolvedPairs) {
984 map[pair.first] = pair.last;
985 }
986 return map;
987 });
988 }
989
990 /**
807 * Schedules a callback to be called as part of the test case. 991 * Schedules a callback to be called as part of the test case.
808 */ 992 */
809 void _schedule(_ScheduledEvent event) { 993 void _schedule(_ScheduledEvent event) {
810 if (_scheduled == null) _scheduled = []; 994 if (_scheduled == null) _scheduled = [];
811 _scheduled.add(event); 995 _scheduled.add(event);
812 } 996 }
813 997
814 /** 998 /**
815 * Schedules a callback to be called after Pub is run with [runPub], even if it 999 * Schedules a callback to be called after Pub is run with [runPub], even if it
816 * fails. 1000 * fails.
817 */ 1001 */
818 void _scheduleCleanup(_ScheduledEvent event) { 1002 void _scheduleCleanup(_ScheduledEvent event) {
819 if (_scheduledCleanup == null) _scheduledCleanup = []; 1003 if (_scheduledCleanup == null) _scheduledCleanup = [];
820 _scheduledCleanup.add(event); 1004 _scheduledCleanup.add(event);
821 } 1005 }
OLDNEW
« no previous file with comments | « utils/tests/pub/pub_test.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698