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

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

Issue 10540151: First pass at version constraint solver. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 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 | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 #library('pub_update_test');
6
7 #import('dart:io');
8
9 #import('../../pub/package.dart');
10 #import('../../pub/pubspec.dart');
11 #import('../../pub/source.dart');
12 #import('../../pub/source_registry.dart');
13 #import('../../pub/version.dart');
14 #import('../../pub/version_solver.dart');
15 #import('../../../lib/unittest/unittest.dart');
16
17 final noVersion = 'no version';
18 final disjointConstraint = 'disjoint';
19 final couldNotSolve = 'unsolved';
20
21 main() {
22 testResolve('no dependencies', {
23 'myapp 0.0.0': {}
24 }, result: {
25 'myapp': '0.0.0'
26 });
27
28 testResolve('simple dependency tree', {
29 'myapp 0.0.0': {
30 'a': '1.0.0',
31 'b': '1.0.0'
32 },
33 'a 1.0.0': {
34 'aa': '1.0.0',
35 'ab': '1.0.0'
36 },
37 'aa 1.0.0': {},
38 'ab 1.0.0': {},
39 'b 1.0.0': {
40 'ba': '1.0.0',
41 'bb': '1.0.0'
42 },
43 'ba 1.0.0': {},
44 'bb 1.0.0': {}
45 }, result: {
46 'myapp': '0.0.0',
47 'a': '1.0.0',
48 'aa': '1.0.0',
49 'ab': '1.0.0',
50 'b': '1.0.0',
51 'ba': '1.0.0',
52 'bb': '1.0.0'
53 });
54
55 testResolve('shared dependency with overlapping constraints', {
56 'myapp 0.0.0': {
57 'a': '1.0.0',
58 'b': '1.0.0'
59 },
60 'a 1.0.0': {
61 'shared': '>=2.0.0 <4.0.0'
62 },
63 'b 1.0.0': {
64 'shared': '>=3.0.0 <5.0.0'
65 },
66 'shared 2.0.0': {},
67 'shared 3.0.0': {},
68 'shared 3.6.9': {},
69 'shared 4.0.0': {},
70 'shared 5.0.0': {},
71 }, result: {
72 'myapp': '0.0.0',
73 'a': '1.0.0',
74 'b': '1.0.0',
75 'shared': '3.6.9'
76 });
77
78 testResolve('shared dependency where dependent version in turn affects '
79 'other dependencies', {
80 'myapp 0.0.0': {
81 'foo': '<=1.0.2',
82 'bar': '1.0.0'
83 },
84 'foo 1.0.0': {},
85 'foo 1.0.1': { 'bang': '1.0.0' },
86 'foo 1.0.2': { 'whoop': '1.0.0' },
87 'foo 1.0.3': { 'zoop': '1.0.0' },
88 'bar 1.0.0': { 'foo': '<=1.0.1' },
89 'bang 1.0.0': {},
90 'whoop 1.0.0': {},
91 'zoop 1.0.0': {}
92 }, result: {
93 'myapp': '0.0.0',
94 'foo': '1.0.1',
95 'bar': '1.0.0',
96 'bang': '1.0.0'
97 });
98
99 testResolve('no version that matches requirement', {
100 'myapp 0.0.0': {
101 'foo': '>=1.0.0 <2.0.0'
102 },
103 'foo 2.0.0': {},
104 'foo 2.1.3': {}
105 }, error: noVersion);
106
107 testResolve('no version that matches combined constraint', {
108 'myapp 0.0.0': {
109 'foo': '1.0.0',
110 'bar': '1.0.0'
111 },
112 'foo 1.0.0': {
113 'shared': '>=2.0.0 <3.0.0'
114 },
115 'bar 1.0.0': {
116 'shared': '>=2.9.0 <4.0.0'
117 },
118 'shared 2.5.0': {},
119 'shared 3.5.0': {}
120 }, error: noVersion);
121
122 testResolve('disjoint constraints', {
123 'myapp 0.0.0': {
124 'foo': '1.0.0',
125 'bar': '1.0.0'
126 },
127 'foo 1.0.0': {
128 'shared': '<2.0.0'
129 },
130 'bar 1.0.0': {
131 'shared': '>3.0.0'
132 },
133 'shared 2.0.0': {},
134 'shared 4.0.0': {}
135 }, error: disjointConstraint);
136
137 // TODO(rnystrom): Uncomment this when the resolver doesn't get stuck in an
138 // infinite loop on unstable graphs.
nweiz 2012/06/18 18:29:19 Remove TODO
Bob Nystrom 2012/06/20 01:40:04 Done.
139 testResolve('unstable dependency graph', {
nweiz 2012/06/18 18:29:19 Shouldn't this stabilize at a=1.0.0?
Bob Nystrom 2012/06/20 01:40:04 That would be a valid solution, but the solver is
nweiz 2012/06/20 21:08:48 But shouldn't it resolve this like so: * Set a=2.
Bob Nystrom 2012/06/20 22:29:54 When it sets a=1.0.0, that removes the dependency
nweiz 2012/06/20 23:47:45 Ah, I see now.
140 'myapp 0.0.0': {
141 'a': '>=1.0.0'
142 },
143 'a 1.0.0': {},
144 'a 2.0.0': {
145 'b': '1.0.0'
146 },
147 'b 1.0.0': {
148 'a': '1.0.0'
149 }
150 }, error: couldNotSolve);
151
152 // TODO(rnystrom): More stuff to test:
153 // - Root depends on A, A depends back on root, but doesn't allow current
154 // version. Should fail.
155 // - Root depends on A, A depends back on root, and does allow current version.
156 // - Two packages depend on the same package, but from different sources. Should
157 // fail.
158 // - Depending on a non-existent package.
159 // - Test that only a certain number requests are sent to the mock source so we
160 // can keep track of server traffic.
161 }
162
163 testResolve(description, packages, [result, error]) {
164 test(description, () {
165 var sources = new SourceRegistry();
166 var source = new MockSource(sources);
167 sources.register(source);
168 sources.setDefault(source.name);
169
170 // Build the test package graph.
171 var root;
172 packages.forEach((nameVersion, dependencies) {
173 var parts = nameVersion.split(' ');
174 var name = parts[0];
175 var version = parts[1];
176 var package = source.mockPackage(name, version, dependencies);
177 if (name == 'myapp') root = package;
178 });
179
180 // Clean up the expectation.
181 if (result != null) {
182 result.forEach((name, version) {
183 result[name] = new Version.parse(version);
184 });
185 }
186
187 // Resolve the versions.
188 var future = resolveVersions(sources, root);
189
190 if (result != null) {
191 expect(future, completion(recursivelyMatches(result)));
192 } else if (error == noVersion) {
193 expect(future, throwsA(new isInstanceOf<NoVersionException>()));
194 } else if (error == disjointConstraint) {
195 expect(future, throwsA(new isInstanceOf<DisjointConstraintException>()));
196 } else if (error == couldNotSolve) {
197 expect(future, throwsA(new isInstanceOf<CouldNotSolveException>()));
198 } else {
199 expect(future, throwsA(error));
200 }
201
202 // Uncomment this if you want to debug why a resolve is throwing an
203 // exception.
nweiz 2012/06/18 18:29:19 It seems like sufficient information for debugging
Bob Nystrom 2012/06/20 01:40:04 Done.
204 /*
205 future.handleException((ex) {
206 print(ex);
207 print(future.stackTrace);
208 return true;
209 });
210 */
211 });
212 }
213
214 class MockSource extends Source {
nweiz 2012/06/18 18:29:19 Maybe it's time to make MockSource its own library
Bob Nystrom 2012/06/20 01:40:04 Maybe. I think each one does different things, and
nweiz 2012/06/20 21:08:48 I prefer libraries that are re-usable, personally.
215 final SourceRegistry _sources;
216 final Map<String, Map<Version, Package>> _packages;
217
218 String get name() => 'mock';
219 bool get shouldCache() => true;
220
221 MockSource(this._sources)
222 : _packages = <Map<Version, Package>>{};
223
224 Future<Version> findVersion(String name,
225 VersionConstraint constraint) {
226 return fakeAsync(() {
227 var best = null;
228 for (var version in _packages[name].getKeys()) {
229 if (constraint.allows(version)) {
230 if (best == null || version > best) best = version;
231 }
232 }
233
234 return best;
235 });
236 }
237
238 Future<Pubspec> describe(String package, Version version) {
239 return fakeAsync(() {
240 return _packages[package][version].pubspec;
241 });
242 }
243
244 Future<bool> install(PackageId id, String path) {
245 throw 'no';
246 }
247
248 Package mockPackage(String name, String version, Map dependencies) {
nweiz 2012/06/18 18:29:19 It seems like you could simplify this method and a
Bob Nystrom 2012/06/20 01:40:04 Done.
249 // Build the pubspec string.
250 var pubspec = new StringBuffer();
251 pubspec.add('version: $version\n');
252 if (!dependencies.isEmpty()) {
253 pubspec.add('dependencies:\n');
254 dependencies.forEach((name, version) {
255 pubspec.add(' $name: "$version"\n');
256 });
257 }
258
259 var package = new Package.inMemory(name,
260 new Pubspec.parse(pubspec.toString(), _sources));
261
262 _packages.putIfAbsent(name, () => new Map<Version, Package>());
263 _packages[name][package.version] = package;
264 return package;
265 }
266 }
267
268 Future fakeAsync(callback()) {
269 var completer = new Completer();
270 new Timer(0, (_) {
271 completer.complete(callback());
272 });
273
274 return completer.future;
275 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698