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

Side by Side Diff: test/version_solver_test.dart

Issue 2172523002: Refactor version_solver_test. (Closed) Base URL: git@github.com:dart-lang/pub.git@master
Patch Set: Created 4 years, 5 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 | « test/test_pub.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 import 'dart:async'; 5 import 'dart:async';
6 6
7 import 'package:path/path.dart' as p;
8 import 'package:scheduled_test/scheduled_test.dart';
9
7 import 'package:pub/src/lock_file.dart'; 10 import 'package:pub/src/lock_file.dart';
8 import 'package:pub/src/log.dart' as log; 11 import 'package:pub/src/log.dart' as log;
9 import 'package:pub/src/package.dart'; 12 import 'package:pub/src/package.dart';
10 import 'package:pub/src/pubspec.dart'; 13 import 'package:pub/src/pubspec.dart';
11 import 'package:pub/src/sdk.dart' as sdk; 14 import 'package:pub/src/sdk.dart' as sdk;
12 import 'package:pub/src/solver/version_solver.dart'; 15 import 'package:pub/src/solver/version_solver.dart';
13 import 'package:pub/src/source.dart'; 16 import 'package:pub/src/source.dart';
14 import 'package:pub/src/source/cached.dart'; 17 import 'package:pub/src/source/cached.dart';
18 import 'package:pub/src/source/hosted.dart';
15 import 'package:pub/src/source_registry.dart'; 19 import 'package:pub/src/source_registry.dart';
16 import 'package:pub/src/system_cache.dart'; 20 import 'package:pub/src/system_cache.dart';
17 import 'package:pub/src/utils.dart'; 21 import 'package:pub/src/utils.dart';
18 import 'package:pub_semver/pub_semver.dart'; 22 import 'package:pub_semver/pub_semver.dart';
19 import 'package:test/test.dart';
20 23
21 MockSource source1; 24 import 'descriptor.dart' as d;
22 MockSource source2; 25 import 'test_pub.dart';
23 26
24 main() { 27 main() {
25 // Uncomment this to debug failing tests.
26 // log.verbosity = log.Verbosity.SOLVER;
27
28 // Since this test isn't run from the SDK, it can't find the "version" file
29 // to load. Instead, just manually inject a version.
30 sdk.version = new Version(1, 2, 3);
31
32 group('basic graph', basicGraph); 28 group('basic graph', basicGraph);
33 group('with lockfile', withLockFile); 29 group('with lockfile', withLockFile);
34 group('root dependency', rootDependency); 30 group('root dependency', rootDependency);
35 group('dev dependency', devDependency); 31 group('dev dependency', devDependency);
36 group('unsolvable', unsolvable); 32 group('unsolvable', unsolvable);
37 group('bad source', badSource); 33 group('bad source', badSource);
38 group('backtracking', backtracking); 34 group('backtracking', backtracking);
39 group('SDK constraint', sdkConstraint); 35 group('SDK constraint', sdkConstraint);
40 group('pre-release', prerelease); 36 group('pre-release', prerelease);
41 group('override', override); 37 group('override', override);
42 group('downgrade', downgrade); 38 group('downgrade', downgrade);
43 } 39 }
44 40
45 void basicGraph() { 41 void basicGraph() {
46 testResolve('no dependencies', { 42 integration('no dependencies', () {
47 'myapp 0.0.0': {} 43 d.appDir().create();
48 }, result: { 44 expectResolves(result: {});
49 'myapp from root': '0.0.0' 45 });
50 }); 46
51 47 integration('simple dependency tree', () {
52 testResolve('simple dependency tree', { 48 servePackages((builder) {
53 'myapp 0.0.0': { 49 builder.serve('a', '1.0.0', deps: {'aa': '1.0.0', 'ab': '1.0.0'});
50 builder.serve('aa', '1.0.0');
51 builder.serve('ab', '1.0.0');
52 builder.serve('b', '1.0.0', deps: {'ba': '1.0.0', 'bb': '1.0.0'});
53 builder.serve('ba', '1.0.0');
54 builder.serve('bb', '1.0.0');
55 });
56
57 d.appDir({'a': '1.0.0', 'b': '1.0.0'}).create();
58 expectResolves(result: {
54 'a': '1.0.0', 59 'a': '1.0.0',
55 'b': '1.0.0'
56 },
57 'a 1.0.0': {
58 'aa': '1.0.0', 60 'aa': '1.0.0',
59 'ab': '1.0.0' 61 'ab': '1.0.0',
60 }, 62 'b': '1.0.0',
61 'aa 1.0.0': {},
62 'ab 1.0.0': {},
63 'b 1.0.0': {
64 'ba': '1.0.0', 63 'ba': '1.0.0',
65 'bb': '1.0.0' 64 'bb': '1.0.0'
66 }, 65 });
67 'ba 1.0.0': {}, 66 });
68 'bb 1.0.0': {} 67
69 }, result: { 68 integration('shared dependency with overlapping constraints', () {
70 'myapp from root': '0.0.0', 69 servePackages((builder) {
71 'a': '1.0.0', 70 builder.serve('a', '1.0.0', deps: {'shared': '>=2.0.0 <4.0.0'});
72 'aa': '1.0.0', 71 builder.serve('b', '1.0.0', deps: {'shared': '>=3.0.0 <5.0.0'});
73 'ab': '1.0.0', 72 builder.serve('shared', '2.0.0');
74 'b': '1.0.0', 73 builder.serve('shared', '3.0.0');
75 'ba': '1.0.0', 74 builder.serve('shared', '3.6.9');
76 'bb': '1.0.0' 75 builder.serve('shared', '4.0.0');
77 }); 76 builder.serve('shared', '5.0.0');
78 77 });
79 testResolve('shared dependency with overlapping constraints', { 78
80 'myapp 0.0.0': { 79 d.appDir({'a': '1.0.0', 'b': '1.0.0'}).create();
81 'a': '1.0.0', 80 expectResolves(result: {'a': '1.0.0', 'b': '1.0.0', 'shared': '3.6.9'});
82 'b': '1.0.0' 81 });
83 }, 82
84 'a 1.0.0': { 83 integration('shared dependency where dependent version in turn affects other '
85 'shared': '>=2.0.0 <4.0.0' 84 'dependencies', () {
86 }, 85 servePackages((builder) {
87 'b 1.0.0': { 86 builder.serve('foo', '1.0.0');
88 'shared': '>=3.0.0 <5.0.0' 87 builder.serve('foo', '1.0.1', deps: {'bang': '1.0.0'});
89 }, 88 builder.serve('foo', '1.0.2', deps: {'whoop': '1.0.0'});
90 'shared 2.0.0': {}, 89 builder.serve('foo', '1.0.3', deps: {'zoop': '1.0.0'});
91 'shared 3.0.0': {}, 90 builder.serve('bar', '1.0.0', deps: {'foo': '<=1.0.1'});
92 'shared 3.6.9': {}, 91 builder.serve('bang', '1.0.0');
93 'shared 4.0.0': {}, 92 builder.serve('whoop', '1.0.0');
94 'shared 5.0.0': {}, 93 builder.serve('zoop', '1.0.0');
95 }, result: { 94 });
96 'myapp from root': '0.0.0', 95
97 'a': '1.0.0', 96 d.appDir({'foo': '<=1.0.2', 'bar': '1.0.0'}).create();
98 'b': '1.0.0', 97 expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.0', 'bang': '1.0.0'});
99 'shared': '3.6.9' 98 });
100 }); 99
101 100 integration('circular dependency', () {
102 testResolve('shared dependency where dependent version in turn affects ' 101 servePackages((builder) {
103 'other dependencies', { 102 builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
104 'myapp 0.0.0': { 103 builder.serve('bar', '1.0.0', deps: {'foo': '1.0.0'});
105 'foo': '<=1.0.2', 104 });
106 'bar': '1.0.0' 105
107 }, 106 d.appDir({'foo': '1.0.0'}).create();
108 'foo 1.0.0': {}, 107 expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'});
109 'foo 1.0.1': { 'bang': '1.0.0' }, 108 });
110 'foo 1.0.2': { 'whoop': '1.0.0' }, 109
111 'foo 1.0.3': { 'zoop': '1.0.0' }, 110 integration('removed dependency', () {
112 'bar 1.0.0': { 'foo': '<=1.0.1' }, 111 servePackages((builder) {
113 'bang 1.0.0': {}, 112 builder.serve('foo', '1.0.0');
114 'whoop 1.0.0': {}, 113 builder.serve('foo', '2.0.0');
115 'zoop 1.0.0': {} 114 builder.serve('bar', '1.0.0');
116 }, result: { 115 builder.serve('bar', '2.0.0', deps: {'baz': '1.0.0'});
117 'myapp from root': '0.0.0', 116 builder.serve('baz', '1.0.0', deps: {'foo': '2.0.0'});
118 'foo': '1.0.1', 117 });
119 'bar': '1.0.0', 118
120 'bang': '1.0.0' 119 d.appDir({'foo': '1.0.0', 'bar': 'any'}).create();
121 }, maxTries: 2); 120 expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'}, tries: 2);
122 121 });
123 testResolve('circular dependency', { 122 }
124 'myapp 1.0.0': { 123
125 'foo': '1.0.0' 124 void withLockFile() {
126 }, 125 integration('with compatible locked dependency', () {
127 'foo 1.0.0': { 126 servePackages((builder) {
128 'bar': '1.0.0' 127 builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
129 }, 128 builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
130 'bar 1.0.0': { 129 builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
131 'foo': '1.0.0' 130 builder.serve('bar', '1.0.0');
132 } 131 builder.serve('bar', '1.0.1');
133 }, result: { 132 builder.serve('bar', '1.0.2');
134 'myapp from root': '1.0.0', 133 });
135 'foo': '1.0.0', 134
136 'bar': '1.0.0' 135 d.appDir({'foo': '1.0.1'}).create();
137 }); 136 expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
138 137
139 testResolve('removed dependency', { 138 d.appDir({'foo': 'any'}).create();
140 'myapp 1.0.0': { 139 expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
140 });
141
142 integration('with incompatible locked dependency', () {
143 servePackages((builder) {
144 builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
145 builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
146 builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
147 builder.serve('bar', '1.0.0');
148 builder.serve('bar', '1.0.1');
149 builder.serve('bar', '1.0.2');
150 });
151
152 d.appDir({'foo': '1.0.1'}).create();
153 expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
154
155 d.appDir({'foo': '>1.0.1'}).create();
156 expectResolves(result: {'foo': '1.0.2', 'bar': '1.0.2'});
157 });
158
159 integration('with unrelated locked dependency', () {
160 servePackages((builder) {
161 builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
162 builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
163 builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
164 builder.serve('bar', '1.0.0');
165 builder.serve('bar', '1.0.1');
166 builder.serve('bar', '1.0.2');
167 builder.serve('baz', '1.0.0');
168 });
169
170 d.appDir({'baz': '1.0.0'}).create();
171 expectResolves(result: {'baz': '1.0.0'});
172
173 d.appDir({'foo': 'any'}).create();
174 expectResolves(result: {'foo': '1.0.2', 'bar': '1.0.2'});
175 });
176
177 integration('unlocks dependencies if necessary to ensure that a new '
178 'dependency is satisfied', () {
179 servePackages((builder) {
180 builder.serve('foo', '1.0.0', deps: {'bar': '<2.0.0'});
181 builder.serve('bar', '1.0.0', deps: {'baz': '<2.0.0'});
182 builder.serve('baz', '1.0.0', deps: {'qux': '<2.0.0'});
183 builder.serve('qux', '1.0.0');
184 builder.serve('foo', '2.0.0', deps: {'bar': '<3.0.0'});
185 builder.serve('bar', '2.0.0', deps: {'baz': '<3.0.0'});
186 builder.serve('baz', '2.0.0', deps: {'qux': '<3.0.0'});
187 builder.serve('qux', '2.0.0');
188 builder.serve('newdep', '2.0.0', deps: {'baz': '>=1.5.0'});
189 });
190
191 d.appDir({'foo': '1.0.0'}).create();
192 expectResolves(result: {
141 'foo': '1.0.0', 193 'foo': '1.0.0',
142 'bar': 'any' 194 'bar': '1.0.0',
143 }, 195 'baz': '1.0.0',
144 'foo 1.0.0': {}, 196 'qux': '1.0.0'
145 'foo 2.0.0': {}, 197 });
146 'bar 1.0.0': {}, 198
147 'bar 2.0.0': { 199 d.appDir({'foo': 'any', 'newdep': '2.0.0'}).create();
148 'baz': '1.0.0' 200 expectResolves(result: {
149 }, 201 'foo': '2.0.0',
150 'baz 1.0.0': { 202 'bar': '2.0.0',
151 'foo': '2.0.0' 203 'baz': '2.0.0',
152 } 204 'qux': '1.0.0',
153 }, result: { 205 'newdep': '2.0.0'
154 'myapp from root': '1.0.0', 206 }, tries: 4);
155 'foo': '1.0.0', 207 });
156 'bar': '1.0.0' 208 }
157 }, maxTries: 2); 209
158 } 210 void rootDependency() {
159 211 integration('with root source', () {
160 withLockFile() { 212 servePackages((builder) {
161 testResolve('with compatible locked dependency', { 213 builder.serve('foo', '1.0.0', deps: {'myapp': 'any'});
162 'myapp 0.0.0': { 214 });
163 'foo': 'any' 215
164 }, 216 d.appDir({'foo': '1.0.0'}).create();
165 'foo 1.0.0': { 'bar': '1.0.0' }, 217 expectResolves(result: {'foo': '1.0.0'});
166 'foo 1.0.1': { 'bar': '1.0.1' }, 218 });
167 'foo 1.0.2': { 'bar': '1.0.2' }, 219
168 'bar 1.0.0': {}, 220 integration('with mismatched sources', () {
169 'bar 1.0.1': {}, 221 servePackages((builder) {
170 'bar 1.0.2': {} 222 builder.serve('foo', '1.0.0', deps: {'myapp': 'any'});
171 }, lockfile: { 223 builder.serve('bar', '1.0.0', deps: {'myapp': {'git': 'nowhere'}});
172 'foo': '1.0.1' 224 });
173 }, result: { 225
174 'myapp from root': '0.0.0', 226 d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
175 'foo': '1.0.1', 227 expectResolves(
176 'bar': '1.0.1' 228 error: "Incompatible dependencies on myapp:\n"
177 }); 229 "- bar 1.0.0 depends on it from source git\n"
178 230 "- foo 1.0.0 depends on it from source hosted");
179 testResolve('with incompatible locked dependency', { 231 });
180 'myapp 0.0.0': { 232
181 'foo': '>1.0.1' 233 integration('with wrong version', () {
182 }, 234 servePackages((builder) {
183 'foo 1.0.0': { 'bar': '1.0.0' }, 235 builder.serve('foo', '1.0.0', deps: {'myapp': '>0.0.0'});
184 'foo 1.0.1': { 'bar': '1.0.1' }, 236 });
185 'foo 1.0.2': { 'bar': '1.0.2' }, 237
186 'bar 1.0.0': {}, 238 d.appDir({'foo': '1.0.0'}).create();
187 'bar 1.0.1': {}, 239 expectResolves(
188 'bar 1.0.2': {} 240 error: "Package myapp has no versions that match >0.0.0 derived from:\n"
189 }, lockfile: { 241 "- foo 1.0.0 depends on version >0.0.0");
190 'foo': '1.0.1' 242 });
191 }, result: { 243 }
192 'myapp from root': '0.0.0', 244
193 'foo': '1.0.2', 245 void devDependency() {
194 'bar': '1.0.2' 246 integration("includes root package's dev dependencies", () {
195 }); 247 servePackages((builder) {
196 248 builder.serve('foo', '1.0.0');
197 testResolve('with unrelated locked dependency', { 249 builder.serve('bar', '1.0.0');
198 'myapp 0.0.0': { 250 });
199 'foo': 'any' 251
200 }, 252 d.dir(appPath, [
201 'foo 1.0.0': { 'bar': '1.0.0' }, 253 d.pubspec({
202 'foo 1.0.1': { 'bar': '1.0.1' }, 254 'name': 'myapp',
203 'foo 1.0.2': { 'bar': '1.0.2' }, 255 'dev_dependencies': {
204 'bar 1.0.0': {}, 256 'foo': '1.0.0',
205 'bar 1.0.1': {}, 257 'bar': '1.0.0'
206 'bar 1.0.2': {}, 258 }
207 'baz 1.0.0': {} 259 })
208 }, lockfile: { 260 ]).create();
209 'baz': '1.0.0' 261
210 }, result: { 262 expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'});
211 'myapp from root': '0.0.0', 263 });
212 'foo': '1.0.2', 264
213 'bar': '1.0.2' 265 integration("includes dev dependency's transitive dependencies", () {
214 }); 266 servePackages((builder) {
215 267 builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
216 testResolve('unlocks dependencies if necessary to ensure that a new ' 268 builder.serve('bar', '1.0.0');
217 'dependency is satisfied', { 269 });
218 'myapp 0.0.0': { 270
219 'foo': 'any', 271 d.dir(appPath, [
220 'newdep': 'any' 272 d.pubspec({
221 }, 273 'name': 'myapp',
222 'foo 1.0.0': { 'bar': '<2.0.0' }, 274 'dev_dependencies': {'foo': '1.0.0'}
223 'bar 1.0.0': { 'baz': '<2.0.0' }, 275 })
224 'baz 1.0.0': { 'qux': '<2.0.0' }, 276 ]).create();
225 'qux 1.0.0': {}, 277
226 'foo 2.0.0': { 'bar': '<3.0.0' }, 278 expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'});
227 'bar 2.0.0': { 'baz': '<3.0.0' }, 279 });
228 'baz 2.0.0': { 'qux': '<3.0.0' }, 280
229 'qux 2.0.0': {}, 281 integration("ignores transitive dependency's dev dependencies", () {
230 'newdep 2.0.0': { 'baz': '>=1.5.0' } 282 servePackages((builder) {
231 }, lockfile: { 283 builder.serve('foo', '1.0.0', pubspec: {
232 'foo': '1.0.0', 284 'dev_dependencies': {'bar': '1.0.0'}
233 'bar': '1.0.0', 285 });
234 'baz': '1.0.0', 286 });
235 'qux': '1.0.0' 287
236 }, result: { 288 d.appDir({'foo': '1.0.0'}).create();
237 'myapp from root': '0.0.0', 289 expectResolves(result: {'foo': '1.0.0'});
238 'foo': '2.0.0', 290 });
239 'bar': '2.0.0', 291 }
240 'baz': '2.0.0', 292
241 'qux': '1.0.0', 293 void unsolvable() {
242 'newdep': '2.0.0' 294 integration('no version that matches constraint', () {
243 }, maxTries: 4); 295 servePackages((builder) {
244 } 296 builder.serve('foo', '2.0.0');
245 297 builder.serve('foo', '2.1.3');
246 rootDependency() { 298 });
247 testResolve('with root source', { 299
248 'myapp 1.0.0': { 300 d.appDir({'foo': '>=1.0.0 <2.0.0'}).create();
249 'foo': '1.0.0' 301 expectResolves(
250 }, 302 error: 'Package foo has no versions that match >=1.0.0 <2.0.0 derived '
251 'foo 1.0.0': { 303 'from:\n'
252 'myapp from root': '>=1.0.0' 304 '- myapp depends on version >=1.0.0 <2.0.0');
253 } 305 });
254 }, result: { 306
255 'myapp from root': '1.0.0', 307 integration('no version that matches combined constraint', () {
256 'foo': '1.0.0' 308 servePackages((builder) {
257 }); 309 builder.serve('foo', '1.0.0', deps: {'shared': '>=2.0.0 <3.0.0'});
258 310 builder.serve('bar', '1.0.0', deps: {'shared': '>=2.9.0 <4.0.0'});
259 testResolve('with different source', { 311 builder.serve('shared', '2.5.0');
260 'myapp 1.0.0': { 312 builder.serve('shared', '3.5.0');
261 'foo': '1.0.0' 313 });
262 }, 314
263 'foo 1.0.0': { 315 d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
264 'myapp': '>=1.0.0' 316 expectResolves(
265 } 317 error: 'Package shared has no versions that match >=2.9.0 <3.0.0 '
266 }, result: { 318 'derived from:\n'
267 'myapp from root': '1.0.0', 319 '- bar 1.0.0 depends on version >=2.9.0 <4.0.0\n'
268 'foo': '1.0.0' 320 '- foo 1.0.0 depends on version >=2.0.0 <3.0.0');
269 }); 321 });
270 322
271 testResolve('with mismatched sources', { 323 integration('disjoint constraints', () {
272 'myapp 1.0.0': { 324 servePackages((builder) {
273 'foo': '1.0.0', 325 builder.serve('foo', '1.0.0', deps: {'shared': '<=2.0.0'});
274 'bar': '1.0.0' 326 builder.serve('bar', '1.0.0', deps: {'shared': '>3.0.0'});
275 }, 327 builder.serve('shared', '2.0.0');
276 'foo 1.0.0': { 328 builder.serve('shared', '4.0.0');
277 'myapp': '>=1.0.0' 329 });
278 }, 330
279 'bar 1.0.0': { 331 d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
280 'myapp from mock2': '>=1.0.0' 332 expectResolves(
281 } 333 error: 'Incompatible version constraints on shared:\n'
282 }, error: sourceMismatch('myapp', 'foo', 'bar')); 334 '- bar 1.0.0 depends on version >3.0.0\n'
283 335 '- foo 1.0.0 depends on version <=2.0.0');
284 testResolve('with wrong version', { 336 });
285 'myapp 1.0.0': { 337
286 'foo': '1.0.0' 338 integration('mismatched descriptions', () {
287 }, 339 var otherServer = new PackageServer((builder) {
288 'foo 1.0.0': { 340 builder.serve('shared', '1.0.0');
289 'myapp': '<1.0.0' 341 });
290 } 342
291 }, error: couldNotSolve); 343 servePackages((builder) {
292 } 344 builder.serve('foo', '1.0.0', deps: {'shared': '1.0.0'});
293 345 builder.serve('bar', '1.0.0', deps: {
294 devDependency() { 346 'shared': {
295 testResolve("includes root package's dev dependencies", { 347 'hosted': {'name': 'shared', 'url': otherServer.url},
296 'myapp 1.0.0': { 348 'version': '1.0.0'
297 '(dev) foo': '1.0.0', 349 }
298 '(dev) bar': '1.0.0' 350 });
299 }, 351 builder.serve('shared', '1.0.0');
300 'foo 1.0.0': {}, 352 });
301 'bar 1.0.0': {} 353
302 }, result: { 354 d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
303 'myapp from root': '1.0.0', 355 expectResolves(error: allOf([
304 'foo': '1.0.0', 356 contains('Incompatible dependencies on shared:'),
305 'bar': '1.0.0' 357 contains('- bar 1.0.0 depends on it with description'),
306 }); 358 contains('- foo 1.0.0 depends on it with description "shared"')
307 359 ]));
308 testResolve("includes dev dependency's transitive dependencies", { 360 });
309 'myapp 1.0.0': { 361
310 '(dev) foo': '1.0.0' 362 integration('mismatched sources', () {
311 }, 363 d.dir('shared', [d.libPubspec('shared', '1.0.0')]).create();
312 'foo 1.0.0': { 364
313 'bar': '1.0.0' 365 servePackages((builder) {
314 }, 366 builder.serve('foo', '1.0.0', deps: {'shared': '1.0.0'});
315 'bar 1.0.0': {} 367 builder.serve('bar', '1.0.0', deps: {
316 }, result: { 368 'shared': {'path': p.join(sandboxDir, 'shared')}
317 'myapp from root': '1.0.0', 369 });
318 'foo': '1.0.0', 370 builder.serve('shared', '1.0.0');
319 'bar': '1.0.0' 371 });
320 }); 372
321 373 d.appDir({'foo': '1.0.0', 'bar': '1.0.0'}).create();
322 testResolve("ignores transitive dependency's dev dependencies", { 374 expectResolves(
323 'myapp 1.0.0': { 375 error: 'Incompatible dependencies on shared:\n'
324 'foo': '1.0.0' 376 '- bar 1.0.0 depends on it from source path\n'
325 }, 377 '- foo 1.0.0 depends on it from source hosted');
326 'foo 1.0.0': { 378 });
327 '(dev) bar': '1.0.0' 379
328 }, 380 integration('no valid solution', () {
329 'bar 1.0.0': {} 381 servePackages((builder) {
330 }, result: { 382 builder.serve('a', '1.0.0', deps: {'b': '1.0.0'});
331 'myapp from root': '1.0.0', 383 builder.serve('a', '2.0.0', deps: {'b': '2.0.0'});
332 'foo': '1.0.0' 384 builder.serve('b', '1.0.0', deps: {'a': '2.0.0'});
333 }); 385 builder.serve('b', '2.0.0', deps: {'a': '1.0.0'});
334 } 386 });
335 387
336 unsolvable() { 388 d.appDir({'a': 'any', 'b': 'any'}).create();
337 testResolve('no version that matches requirement', { 389 expectResolves(
338 'myapp 0.0.0': { 390 error: 'Package a has no versions that match 2.0.0 derived from:\n'
339 'foo': '>=1.0.0 <2.0.0' 391 '- b 1.0.0 depends on version 2.0.0\n'
340 }, 392 '- myapp depends on version any',
341 'foo 2.0.0': {}, 393 tries: 2);
342 'foo 2.1.3': {} 394 });
343 }, error: noVersion(['myapp', 'foo']));
344
345 testResolve('no version that matches combined constraint', {
346 'myapp 0.0.0': {
347 'foo': '1.0.0',
348 'bar': '1.0.0'
349 },
350 'foo 1.0.0': {
351 'shared': '>=2.0.0 <3.0.0'
352 },
353 'bar 1.0.0': {
354 'shared': '>=2.9.0 <4.0.0'
355 },
356 'shared 2.5.0': {},
357 'shared 3.5.0': {}
358 }, error: noVersion(['shared', 'foo', 'bar']));
359
360 testResolve('disjoint constraints', {
361 'myapp 0.0.0': {
362 'foo': '1.0.0',
363 'bar': '1.0.0'
364 },
365 'foo 1.0.0': {
366 'shared': '<=2.0.0'
367 },
368 'bar 1.0.0': {
369 'shared': '>3.0.0'
370 },
371 'shared 2.0.0': {},
372 'shared 4.0.0': {}
373 }, error: disjointConstraint(['shared', 'foo', 'bar']));
374
375 testResolve('mismatched descriptions', {
376 'myapp 0.0.0': {
377 'foo': '1.0.0',
378 'bar': '1.0.0'
379 },
380 'foo 1.0.0': {
381 'shared-x': '1.0.0'
382 },
383 'bar 1.0.0': {
384 'shared-y': '1.0.0'
385 },
386 'shared-x 1.0.0': {},
387 'shared-y 1.0.0': {}
388 }, error: descriptionMismatch('shared', 'foo', 'bar'));
389
390 testResolve('mismatched sources', {
391 'myapp 0.0.0': {
392 'foo': '1.0.0',
393 'bar': '1.0.0'
394 },
395 'foo 1.0.0': {
396 'shared': '1.0.0'
397 },
398 'bar 1.0.0': {
399 'shared from mock2': '1.0.0'
400 },
401 'shared 1.0.0': {},
402 'shared 1.0.0 from mock2': {}
403 }, error: sourceMismatch('shared', 'foo', 'bar'));
404
405 testResolve('no valid solution', {
406 'myapp 0.0.0': {
407 'a': 'any',
408 'b': 'any'
409 },
410 'a 1.0.0': {
411 'b': '1.0.0'
412 },
413 'a 2.0.0': {
414 'b': '2.0.0'
415 },
416 'b 1.0.0': {
417 'a': '2.0.0'
418 },
419 'b 2.0.0': {
420 'a': '1.0.0'
421 }
422 }, error: couldNotSolve, maxTries: 2);
423 395
424 // This is a regression test for #15550. 396 // This is a regression test for #15550.
425 testResolve('no version that matches while backtracking', { 397 integration('no version that matches while backtracking', () {
426 'myapp 0.0.0': { 398 servePackages((builder) {
427 'a': 'any', 399 builder.serve('a', '1.0.0');
428 'b': '>1.0.0' 400 builder.serve('b', '1.0.0');
429 }, 401 });
430 'a 1.0.0': {}, 402
431 'b 1.0.0': {} 403 d.appDir({'a': 'any', 'b': '>1.0.0'}).create();
432 }, error: noVersion(['myapp', 'b']), maxTries: 1); 404 expectResolves(
433 405 error: 'Package b has no versions that match >1.0.0 derived from:\n'
406 '- myapp depends on version >1.0.0');
407 });
434 408
435 // This is a regression test for #18300. 409 // This is a regression test for #18300.
436 testResolve('...', { 410 integration('issue 18300', () {
437 "myapp 0.0.0": { 411 servePackages((builder) {
438 "angular": "any", 412 builder.serve('analyzer', '0.12.2');
439 "collection": "any" 413 builder.serve('angular', '0.10.0', deps: {
440 }, 414 'di': '>=0.0.32 <0.1.0',
441 "analyzer 0.12.2": {}, 415 'collection': '>=0.9.1 <1.0.0'
442 "angular 0.10.0": { 416 });
443 "di": ">=0.0.32 <0.1.0", 417 builder.serve('angular', '0.9.11', deps: {
444 "collection": ">=0.9.1 <1.0.0" 418 'di': '>=0.0.32 <0.1.0',
445 }, 419 'collection': '>=0.9.1 <1.0.0'
446 "angular 0.9.11": { 420 });
447 "di": ">=0.0.32 <0.1.0", 421 builder.serve('angular', '0.9.10', deps: {
448 "collection": ">=0.9.1 <1.0.0" 422 'di': '>=0.0.32 <0.1.0',
449 }, 423 'collection': '>=0.9.1 <1.0.0'
450 "angular 0.9.10": { 424 });
451 "di": ">=0.0.32 <0.1.0", 425 builder.serve('collection', '0.9.0');
452 "collection": ">=0.9.1 <1.0.0" 426 builder.serve('collection', '0.9.1');
453 }, 427 builder.serve('di', '0.0.37', deps: {'analyzer': '>=0.13.0 <0.14.0'});
454 "collection 0.9.0": {}, 428 builder.serve('di', '0.0.36', deps: {'analyzer': '>=0.13.0 <0.14.0'});
455 "collection 0.9.1": {}, 429 });
456 "di 0.0.37": {"analyzer": ">=0.13.0 <0.14.0"}, 430
457 "di 0.0.36": {"analyzer": ">=0.13.0 <0.14.0"} 431 d.appDir({'angular': 'any', 'collection': 'any'}).create();
458 }, error: noVersion(['analyzer', 'di']), maxTries: 2); 432 expectResolves(
459 } 433 error: 'Package analyzer has no versions that match >=0.13.0 <0.14.0 '
460 434 'derived from:\n'
461 badSource() { 435 '- di 0.0.36 depends on version >=0.13.0 <0.14.0',
462 testResolve('fail if the root package has a bad source in dep', { 436 tries: 2);
463 'myapp 0.0.0': { 437 });
464 'foo from bad': 'any' 438 }
465 }, 439
466 }, error: unknownSource('myapp', 'foo', 'bad')); 440 void badSource() {
467 441 integration('fail if the root package has a bad source in dep', () {
468 testResolve('fail if the root package has a bad source in dev dep', { 442 d.appDir({'foo': {'bad': 'any'}}).create();
469 'myapp 0.0.0': { 443 expectResolves(
470 '(dev) foo from bad': 'any' 444 error: 'Package myapp depends on foo from unknown source "bad".');
471 }, 445 });
472 }, error: unknownSource('myapp', 'foo', 'bad')); 446
473 447 integration('fail if the root package has a bad source in dev dep', () {
474 testResolve('fail if all versions have bad source in dep', { 448 d.dir(appPath, [
475 'myapp 0.0.0': { 449 d.pubspec({
476 'foo': 'any' 450 'name': 'myapp',
477 }, 451 'dev_dependencies': {'foo': {'bad': 'any'}}
478 'foo 1.0.0': { 452 })
479 'bar from bad': 'any' 453 ]).create();
480 }, 454
481 'foo 1.0.1': { 455 expectResolves(
482 'baz from bad': 'any' 456 error: 'Package myapp depends on foo from unknown source "bad".');
483 }, 457 });
484 'foo 1.0.3': { 458
485 'bang from bad': 'any' 459 integration('fail if all versions have bad source in dep', () {
486 }, 460 servePackages((builder) {
487 }, error: unknownSource('foo', 'bar', 'bad'), maxTries: 3); 461 builder.serve('foo', '1.0.0', deps: {'bar': {'bad': 'any'}});
488 462 builder.serve('foo', '1.0.1', deps: {'baz': {'bad': 'any'}});
489 testResolve('ignore versions with bad source in dep', { 463 builder.serve('foo', '1.0.2', deps: {'bang': {'bad': 'any'}});
490 'myapp 1.0.0': { 464 });
491 'foo': 'any' 465
492 }, 466 d.appDir({'foo': 'any'}).create();
493 'foo 1.0.0': { 467 expectResolves(
494 'bar': 'any' 468 error: 'Package foo depends on bar from unknown source "bad".');
495 }, 469 });
496 'foo 1.0.1': { 470
497 'bar from bad': 'any' 471 integration('ignore versions with bad source in dep', () {
498 }, 472 servePackages((builder) {
499 'foo 1.0.3': { 473 builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
500 'bar from bad': 'any' 474 builder.serve('foo', '1.0.1', deps: {'bar': {'bad': 'any'}});
501 }, 475 builder.serve('foo', '1.0.2', deps: {'bar': {'bad': 'any'}});
502 'bar 1.0.0': {} 476 builder.serve('bar', '1.0.0');
503 }, result: { 477 });
504 'myapp from root': '1.0.0', 478
505 'foo': '1.0.0', 479 d.appDir({'foo': 'any'}).create();
506 'bar': '1.0.0' 480 expectResolves(result: {'foo': '1.0.0', 'bar': '1.0.0'});
507 }, maxTries: 3); 481 });
508 } 482 }
509 483
510 backtracking() { 484 void backtracking() {
511 testResolve('circular dependency on older version', { 485 integration('circular dependency on older version', () {
512 'myapp 0.0.0': { 486 servePackages((builder) {
513 'a': '>=1.0.0' 487 builder.serve('a', '1.0.0');
514 }, 488 builder.serve('a', '2.0.0', deps: {'b': '1.0.0'});
515 'a 1.0.0': {}, 489 builder.serve('b', '1.0.0', deps: {'a': '1.0.0'});
516 'a 2.0.0': { 490 });
517 'b': '1.0.0' 491
518 }, 492 d.appDir({'a': '>=1.0.0'}).create();
519 'b 1.0.0': { 493 expectResolves(result: {'a': '1.0.0'}, tries: 2);
520 'a': '1.0.0' 494 });
521 }
522 }, result: {
523 'myapp from root': '0.0.0',
524 'a': '1.0.0'
525 }, maxTries: 2);
526 495
527 // The latest versions of a and b disagree on c. An older version of either 496 // The latest versions of a and b disagree on c. An older version of either
528 // will resolve the problem. This test validates that b, which is farther 497 // will resolve the problem. This test validates that b, which is farther
529 // in the dependency graph from myapp is downgraded first. 498 // in the dependency graph from myapp is downgraded first.
530 testResolve('rolls back leaf versions first', { 499 integration('rolls back leaf versions first', () {
531 'myapp 0.0.0': { 500 servePackages((builder) {
532 'a': 'any' 501 builder.serve('a', '1.0.0', deps: {'b': 'any'});
533 }, 502 builder.serve('a', '2.0.0', deps: {'b': 'any', 'c': '2.0.0'});
534 'a 1.0.0': { 503 builder.serve('b', '1.0.0');
535 'b': 'any' 504 builder.serve('b', '2.0.0', deps: {'c': '1.0.0'});
536 }, 505 builder.serve('c', '1.0.0');
537 'a 2.0.0': { 506 builder.serve('c', '2.0.0');
538 'b': 'any', 507 });
539 'c': '2.0.0' 508
540 }, 509 d.appDir({'a': 'any'}).create();
541 'b 1.0.0': {}, 510 expectResolves(
542 'b 2.0.0': { 511 result: {'a': '2.0.0', 'b': '1.0.0', 'c': '2.0.0'});
543 'c': '1.0.0' 512 });
544 },
545 'c 1.0.0': {},
546 'c 2.0.0': {}
547 }, result: {
548 'myapp from root': '0.0.0',
549 'a': '2.0.0',
550 'b': '1.0.0',
551 'c': '2.0.0'
552 }, maxTries: 2);
553 513
554 // Only one version of baz, so foo and bar will have to downgrade until they 514 // Only one version of baz, so foo and bar will have to downgrade until they
555 // reach it. 515 // reach it.
556 testResolve('simple transitive', { 516 integration('simple transitive', () {
557 'myapp 0.0.0': {'foo': 'any'}, 517 servePackages((builder) {
558 'foo 1.0.0': {'bar': '1.0.0'}, 518 builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
559 'foo 2.0.0': {'bar': '2.0.0'}, 519 builder.serve('foo', '2.0.0', deps: {'bar': '2.0.0'});
560 'foo 3.0.0': {'bar': '3.0.0'}, 520 builder.serve('foo', '3.0.0', deps: {'bar': '3.0.0'});
561 'bar 1.0.0': {'baz': 'any'}, 521 builder.serve('bar', '1.0.0', deps: {'baz': 'any'});
562 'bar 2.0.0': {'baz': '2.0.0'}, 522 builder.serve('bar', '2.0.0', deps: {'baz': '2.0.0'});
563 'bar 3.0.0': {'baz': '3.0.0'}, 523 builder.serve('bar', '3.0.0', deps: {'baz': '3.0.0'});
564 'baz 1.0.0': {} 524 builder.serve('baz', '1.0.0');
565 }, result: { 525 });
566 'myapp from root': '0.0.0', 526
567 'foo': '1.0.0', 527 d.appDir({'foo': 'any'}).create();
568 'bar': '1.0.0', 528 expectResolves(
569 'baz': '1.0.0' 529 result: {'foo': '1.0.0', 'bar': '1.0.0', 'baz': '1.0.0'},
570 }, maxTries: 3); 530 tries: 3);
531 });
571 532
572 // This ensures it doesn't exhaustively search all versions of b when it's 533 // This ensures it doesn't exhaustively search all versions of b when it's
573 // a-2.0.0 whose dependency on c-2.0.0-nonexistent led to the problem. We 534 // a-2.0.0 whose dependency on c-2.0.0-nonexistent led to the problem. We
574 // make sure b has more versions than a so that the solver tries a first 535 // make sure b has more versions than a so that the solver tries a first
575 // since it sorts sibling dependencies by number of versions. 536 // since it sorts sibling dependencies by number of versions.
576 testResolve('backjump to nearer unsatisfied package', { 537 integration('backjump to nearer unsatisfied package', () {
577 'myapp 0.0.0': { 538 servePackages((builder) {
578 'a': 'any', 539 builder.serve('a', '1.0.0', deps: {'c': '1.0.0'});
579 'b': 'any' 540 builder.serve('a', '2.0.0', deps: {'c': '2.0.0-nonexistent'});
580 }, 541 builder.serve('b', '1.0.0');
581 'a 1.0.0': { 'c': '1.0.0' }, 542 builder.serve('b', '2.0.0');
582 'a 2.0.0': { 'c': '2.0.0-nonexistent' }, 543 builder.serve('b', '3.0.0');
583 'b 1.0.0': {}, 544 builder.serve('c', '1.0.0');
584 'b 2.0.0': {}, 545 });
585 'b 3.0.0': {}, 546
586 'c 1.0.0': {}, 547 d.appDir({'a': 'any', 'b': 'any'}).create();
587 }, result: { 548 expectResolves(
588 'myapp from root': '0.0.0', 549 result: {'a': '1.0.0', 'b': '3.0.0', 'c': '1.0.0'},
589 'a': '1.0.0', 550 tries: 2);
590 'b': '3.0.0', 551 });
591 'c': '1.0.0'
592 }, maxTries: 2);
593 552
594 // Tests that the backjumper will jump past unrelated selections when a 553 // Tests that the backjumper will jump past unrelated selections when a
595 // source conflict occurs. This test selects, in order: 554 // source conflict occurs. This test selects, in order:
596 // - myapp -> a 555 // - myapp -> a
597 // - myapp -> b 556 // - myapp -> b
598 // - myapp -> c (1 of 5) 557 // - myapp -> c (1 of 5)
599 // - b -> a 558 // - b -> a
600 // It selects a and b first because they have fewer versions than c. It 559 // It selects a and b first because they have fewer versions than c. It
601 // traverses b's dependency on a after selecting a version of c because 560 // traverses b's dependency on a after selecting a version of c because
602 // dependencies are traversed breadth-first (all of myapps's immediate deps 561 // dependencies are traversed breadth-first (all of myapps's immediate deps
603 // before any other their deps). 562 // before any other their deps).
604 // 563 //
605 // This means it doesn't discover the source conflict until after selecting 564 // This means it doesn't discover the source conflict until after selecting
606 // c. When that happens, it should backjump past c instead of trying older 565 // c. When that happens, it should backjump past c instead of trying older
607 // versions of it since they aren't related to the conflict. 566 // versions of it since they aren't related to the conflict.
608 testResolve('backjump to conflicting source', { 567 integration('successful backjump to conflicting source', () {
609 'myapp 0.0.0': { 568 d.dir('a', [d.libPubspec('a', '1.0.0')]).create();
610 'a': 'any', 569
611 'b': 'any', 570 servePackages((builder) {
612 'c': 'any' 571 builder.serve('a', '1.0.0');
613 }, 572 builder.serve('b', '1.0.0', deps: {'a': 'any'});
614 'a 1.0.0': {}, 573 builder.serve('b', '2.0.0', deps: {
615 'a 1.0.0 from mock2': {}, 574 'a': {'path': p.join(sandboxDir, 'a')}
616 'b 1.0.0': { 575 });
617 'a': 'any' 576 builder.serve('c', '1.0.0');
618 }, 577 builder.serve('c', '2.0.0');
619 'b 2.0.0': { 578 builder.serve('c', '3.0.0');
620 'a from mock2': 'any' 579 builder.serve('c', '4.0.0');
621 }, 580 builder.serve('c', '5.0.0');
622 'c 1.0.0': {}, 581 });
623 'c 2.0.0': {}, 582
624 'c 3.0.0': {}, 583 d.appDir({'a': 'any', 'b': 'any', 'c': 'any'}).create();
625 'c 4.0.0': {}, 584 expectResolves(result: {'a': '1.0.0', 'b': '1.0.0', 'c': '5.0.0'});
626 'c 5.0.0': {}, 585 });
627 }, result: {
628 'myapp from root': '0.0.0',
629 'a': '1.0.0',
630 'b': '1.0.0',
631 'c': '5.0.0'
632 }, maxTries: 2);
633 586
634 // Like the above test, but for a conflicting description. 587 // Like the above test, but for a conflicting description.
635 testResolve('backjump to conflicting description', { 588 integration('successful backjump to conflicting description', () {
636 'myapp 0.0.0': { 589 var otherServer = new PackageServer((builder) {
637 'a-x': 'any', 590 builder.serve('a', '1.0.0');
638 'b': 'any', 591 });
639 'c': 'any' 592
640 }, 593 servePackages((builder) {
641 'a-x 1.0.0': {}, 594 builder.serve('a', '1.0.0');
642 'a-y 1.0.0': {}, 595 builder.serve('b', '1.0.0', deps: {'a': 'any'});
643 'b 1.0.0': { 596 builder.serve('b', '2.0.0', deps: {
644 'a-x': 'any' 597 'a': {'hosted': {'name': 'a', 'url': otherServer.url}}
645 }, 598 });
646 'b 2.0.0': { 599 builder.serve('c', '1.0.0');
647 'a-y': 'any' 600 builder.serve('c', '2.0.0');
648 }, 601 builder.serve('c', '3.0.0');
649 'c 1.0.0': {}, 602 builder.serve('c', '4.0.0');
650 'c 2.0.0': {}, 603 builder.serve('c', '5.0.0');
651 'c 3.0.0': {}, 604 });
652 'c 4.0.0': {}, 605
653 'c 5.0.0': {}, 606 d.appDir({'a': 'any', 'b': 'any', 'c': 'any'}).create();
654 }, result: { 607 expectResolves(result: {'a': '1.0.0', 'b': '1.0.0', 'c': '5.0.0'});
655 'myapp from root': '0.0.0', 608 });
656 'a-x': '1.0.0',
657 'b': '1.0.0',
658 'c': '5.0.0'
659 }, maxTries: 2);
660 609
661 // Similar to the above two tests but where there is no solution. It should 610 // Similar to the above two tests but where there is no solution. It should
662 // fail in this case with no backtracking. 611 // fail in this case with no backtracking.
663 testResolve('backjump to conflicting source', { 612 integration('failing backjump to conflicting source', () {
664 'myapp 0.0.0': { 613 d.dir('a', [d.libPubspec('a', '1.0.0')]).create();
665 'a': 'any',
666 'b': 'any',
667 'c': 'any'
668 },
669 'a 1.0.0': {},
670 'a 1.0.0 from mock2': {},
671 'b 1.0.0': {
672 'a from mock2': 'any'
673 },
674 'c 1.0.0': {},
675 'c 2.0.0': {},
676 'c 3.0.0': {},
677 'c 4.0.0': {},
678 'c 5.0.0': {},
679 }, error: sourceMismatch('a', 'myapp', 'b'), maxTries: 1);
680 614
681 testResolve('backjump to conflicting description', { 615 servePackages((builder) {
682 'myapp 0.0.0': { 616 builder.serve('a', '1.0.0');
683 'a-x': 'any', 617 builder.serve('b', '1.0.0', deps: {
684 'b': 'any', 618 'a': {'path': p.join(sandboxDir, 'shared')}
685 'c': 'any' 619 });
686 }, 620 builder.serve('c', '1.0.0');
687 'a-x 1.0.0': {}, 621 builder.serve('c', '2.0.0');
688 'a-y 1.0.0': {}, 622 builder.serve('c', '3.0.0');
689 'b 1.0.0': { 623 builder.serve('c', '4.0.0');
690 'a-y': 'any' 624 builder.serve('c', '5.0.0');
691 }, 625 });
692 'c 1.0.0': {}, 626
693 'c 2.0.0': {}, 627 d.appDir({'a': 'any', 'b': 'any', 'c': 'any'}).create();
694 'c 3.0.0': {}, 628 expectResolves(
695 'c 4.0.0': {}, 629 error: 'Incompatible dependencies on a:\n'
696 'c 5.0.0': {}, 630 '- b 1.0.0 depends on it from source path\n'
697 }, error: descriptionMismatch('a', 'myapp', 'b'), maxTries: 1); 631 '- myapp depends on it from source hosted');
632 });
633
634 integration('failing backjump to conflicting description', () {
635 var otherServer = new PackageServer((builder) {
636 builder.serve('a', '1.0.0');
637 });
638
639 servePackages((builder) {
640 builder.serve('a', '1.0.0');
641 builder.serve('b', '1.0.0', deps: {
642 'a': {'hosted': {'name': 'a', 'url': otherServer.url}}
643 });
644 builder.serve('c', '1.0.0');
645 builder.serve('c', '2.0.0');
646 builder.serve('c', '3.0.0');
647 builder.serve('c', '4.0.0');
648 builder.serve('c', '5.0.0');
649 });
650
651 d.appDir({'a': 'any', 'b': 'any', 'c': 'any'}).create();
652 expectResolves(error: allOf([
653 contains('Incompatible dependencies on a:'),
654 contains('- b 1.0.0 depends on it with description'),
655 contains('- myapp depends on it with description "a"')
656 ]));
657 });
698 658
699 // Dependencies are ordered so that packages with fewer versions are tried 659 // Dependencies are ordered so that packages with fewer versions are tried
700 // first. Here, there are two valid solutions (either a or b must be 660 // first. Here, there are two valid solutions (either a or b must be
701 // downgraded once). The chosen one depends on which dep is traversed first. 661 // downgraded once). The chosen one depends on which dep is traversed first.
702 // Since b has fewer versions, it will be traversed first, which means a will 662 // Since b has fewer versions, it will be traversed first, which means a will
703 // come later. Since later selections are revised first, a gets downgraded. 663 // come later. Since later selections are revised first, a gets downgraded.
704 testResolve('traverse into package with fewer versions first', { 664 integration('traverse into package with fewer versions first', () {
705 'myapp 0.0.0': { 665 servePackages((builder) {
706 'a': 'any', 666 builder.serve('a', '1.0.0', deps: {'c': 'any'});
707 'b': 'any' 667 builder.serve('a', '2.0.0', deps: {'c': 'any'});
708 }, 668 builder.serve('a', '3.0.0', deps: {'c': 'any'});
709 'a 1.0.0': {'c': 'any'}, 669 builder.serve('a', '4.0.0', deps: {'c': 'any'});
710 'a 2.0.0': {'c': 'any'}, 670 builder.serve('a', '5.0.0', deps: {'c': '1.0.0'});
711 'a 3.0.0': {'c': 'any'}, 671 builder.serve('b', '1.0.0', deps: {'c': 'any'});
712 'a 4.0.0': {'c': 'any'}, 672 builder.serve('b', '2.0.0', deps: {'c': 'any'});
713 'a 5.0.0': {'c': '1.0.0'}, 673 builder.serve('b', '3.0.0', deps: {'c': 'any'});
714 'b 1.0.0': {'c': 'any'}, 674 builder.serve('b', '4.0.0', deps: {'c': '2.0.0'});
715 'b 2.0.0': {'c': 'any'}, 675 builder.serve('c', '1.0.0');
716 'b 3.0.0': {'c': 'any'}, 676 builder.serve('c', '2.0.0');
717 'b 4.0.0': {'c': '2.0.0'}, 677 });
718 'c 1.0.0': {}, 678
719 'c 2.0.0': {}, 679 d.appDir({'a': 'any', 'b': 'any'}).create();
720 }, result: { 680 expectResolves(result: {'a': '4.0.0', 'b': '4.0.0', 'c': '2.0.0'});
721 'myapp from root': '0.0.0', 681 });
722 'a': '4.0.0',
723 'b': '4.0.0',
724 'c': '2.0.0'
725 }, maxTries: 2);
726 682
727 // This is similar to the above test. When getting the number of versions of 683 // This is similar to the above test. When getting the number of versions of
728 // a package to determine which to traverse first, versions that are 684 // a package to determine which to traverse first, versions that are
729 // disallowed by the root package's constraints should not be considered. 685 // disallowed by the root package's constraints should not be considered.
730 // Here, foo has more versions of bar in total (4), but fewer that meet 686 // Here, foo has more versions of bar in total (4), but fewer that meet
731 // myapp's constraints (only 2). There is no solution, but we will do less 687 // myapp's constraints (only 2). There is no solution, but we will do less
732 // backtracking if foo is tested first. 688 // backtracking if foo is tested first.
733 testResolve('take root package constraints into counting versions', { 689 integration('take root package constraints into counting versions', () {
734 "myapp 0.0.0": { 690 servePackages((builder) {
735 "foo": ">2.0.0", 691 builder.serve('foo', '1.0.0', deps: {'none': '2.0.0'});
736 "bar": "any" 692 builder.serve('foo', '2.0.0', deps: {'none': '2.0.0'});
737 }, 693 builder.serve('foo', '3.0.0', deps: {'none': '2.0.0'});
738 "foo 1.0.0": {"none": "2.0.0"}, 694 builder.serve('foo', '4.0.0', deps: {'none': '2.0.0'});
739 "foo 2.0.0": {"none": "2.0.0"}, 695 builder.serve('bar', '1.0.0');
740 "foo 3.0.0": {"none": "2.0.0"}, 696 builder.serve('bar', '2.0.0');
741 "foo 4.0.0": {"none": "2.0.0"}, 697 builder.serve('bar', '3.0.0');
742 "bar 1.0.0": {}, 698 builder.serve('none', '1.0.0');
743 "bar 2.0.0": {}, 699 });
744 "bar 3.0.0": {}, 700
745 "none 1.0.0": {} 701 d.appDir({"foo": ">2.0.0", "bar": "any"}).create();
746 }, error: noVersion(["foo", "none"]), maxTries: 2); 702 expectResolves(
747 703 error: 'Package none has no versions that match 2.0.0 derived from:\n'
748 // This sets up a hundred versions of foo and bar, 0.0.0 through 9.9.0. Each 704 '- foo 3.0.0 depends on version 2.0.0',
749 // version of foo depends on a baz with the same major version. Each version 705 tries: 2);
750 // of bar depends on a baz with the same minor version. There is only one 706 });
751 // version of baz, 0.0.0, so only older versions of foo and bar will 707
752 // satisfy it. 708 integration('complex backtrack', () {
753 var map = { 709 servePackages((builder) {
754 'myapp 0.0.0': { 710 // This sets up a hundred versions of foo and bar, 0.0.0 through 9.9.0. Ea ch
755 'foo': 'any', 711 // version of foo depends on a baz with the same major version. Each versi on
756 'bar': 'any' 712 // of bar depends on a baz with the same minor version. There is only one
757 }, 713 // version of baz, 0.0.0, so only older versions of foo and bar will
758 'baz 0.0.0': {} 714 // satisfy it.
759 }; 715 builder.serve('baz', '0.0.0');
760 716 for (var i = 0; i < 10; i++) {
761 for (var i = 0; i < 10; i++) { 717 for (var j = 0; j < 10; j++) {
762 for (var j = 0; j < 10; j++) { 718 builder.serve('foo', '$i.$j.0', deps: {'baz': '$i.0.0'});
763 map['foo $i.$j.0'] = {'baz': '$i.0.0'}; 719 builder.serve('bar', '$i.$j.0', deps: {'baz': '0.$j.0'});
764 map['bar $i.$j.0'] = {'baz': '0.$j.0'}; 720 }
765 } 721 }
766 } 722 });
767 723
768 testResolve('complex backtrack', map, result: { 724 d.appDir({'foo': 'any', 'bar': 'any'}).create();
769 'myapp from root': '0.0.0', 725 expectResolves(
770 'foo': '0.9.0', 726 result: {'foo': '0.9.0', 'bar': '9.0.0', 'baz': '0.0.0'},
771 'bar': '9.0.0', 727 tries: 10);
772 'baz': '0.0.0' 728 });
773 }, maxTries: 10);
774 729
775 // If there's a disjoint constraint on a package, then selecting other 730 // If there's a disjoint constraint on a package, then selecting other
776 // versions of it is a waste of time: no possible versions can match. We need 731 // versions of it is a waste of time: no possible versions can match. We need
777 // to jump past it to the most recent package that affected the constraint. 732 // to jump past it to the most recent package that affected the constraint.
778 testResolve('backjump past failed package on disjoint constraint', { 733 integration('backjump past failed package on disjoint constraint', () {
779 'myapp 0.0.0': { 734 servePackages((builder) {
780 'a': 'any', 735 builder.serve('a', '1.0.0', deps: {
781 'foo': '>2.0.0' 736 'foo': 'any' // ok
782 }, 737 });
783 'a 1.0.0': { 738 builder.serve('a', '2.0.0', deps: {
784 'foo': 'any' // ok 739 'foo': '<1.0.0' // disjoint with myapp's constraint on foo
785 }, 740 });
786 'a 2.0.0': { 741 builder.serve('foo', '2.0.0');
787 'foo': '<1.0.0' // disjoint with myapp's constraint on foo 742 builder.serve('foo', '2.0.1');
788 }, 743 builder.serve('foo', '2.0.2');
789 'foo 2.0.0': {}, 744 builder.serve('foo', '2.0.3');
790 'foo 2.0.1': {}, 745 builder.serve('foo', '2.0.4');
791 'foo 2.0.2': {}, 746 });
792 'foo 2.0.3': {}, 747
793 'foo 2.0.4': {} 748 d.appDir({'a': 'any', 'foo': '>2.0.0'}).create();
794 }, result: { 749 expectResolves(result: {'a': '1.0.0', 'foo': '2.0.4'});
795 'myapp from root': '0.0.0', 750 });
796 'a': '1.0.0',
797 'foo': '2.0.4'
798 }, maxTries: 2);
799 751
800 // This is a regression test for #18666. It was possible for the solver to 752 // This is a regression test for #18666. It was possible for the solver to
801 // "forget" that a package had previously led to an error. In that case, it 753 // "forget" that a package had previously led to an error. In that case, it
802 // would backtrack over the failed package instead of trying different 754 // would backtrack over the failed package instead of trying different
803 // versions of it. 755 // versions of it.
804 testResolve("finds solution with less strict constraint", { 756 integration("finds solution with less strict constraint", () {
805 "myapp 1.0.0": { 757 servePackages((builder) {
806 "a": "any", 758 builder.serve('a', '2.0.0');
807 "c": "any", 759 builder.serve('a', '1.0.0');
808 "d": "any" 760 builder.serve('b', '1.0.0', deps: {'a': '1.0.0'});
809 }, 761 builder.serve('c', '1.0.0', deps: {'b': 'any'});
810 "a 2.0.0": {}, 762 builder.serve('d', '2.0.0', deps: {'myapp': 'any'});
811 "a 1.0.0": {}, 763 builder.serve('d', '1.0.0', deps: {'myapp': '<1.0.0'});
812 "b 1.0.0": {"a": "1.0.0"}, 764 });
813 "c 1.0.0": {"b": "any"}, 765
814 "d 2.0.0": {"myapp": "any"}, 766 d.appDir({"a": "any", "c": "any", "d": "any"}).create();
815 "d 1.0.0": {"myapp": "<1.0.0"} 767 expectResolves(
816 }, result: { 768 result: {'a': '1.0.0', 'b': '1.0.0', 'c': '1.0.0', 'd': '2.0.0'});
817 'myapp from root': '1.0.0', 769 });
818 'a': '1.0.0', 770 }
819 'b': '1.0.0', 771
820 'c': '1.0.0', 772 void sdkConstraint() {
821 'd': '2.0.0' 773 integration('root matches SDK', () {
822 }, maxTries: 3); 774 d.dir(appPath, [
823 } 775 d.pubspec({
824 776 'name': 'myapp',
825 sdkConstraint() { 777 'environment': {'sdk': '0.1.2+3'}
826 var badVersion = '0.0.0-nope'; 778 })
827 var goodVersion = sdk.version.toString(); 779 ]).create();
828 780
829 testResolve('root matches SDK', { 781 expectResolves(result: {});
830 'myapp 0.0.0': {'sdk': goodVersion } 782 });
831 }, result: { 783
832 'myapp from root': '0.0.0' 784 integration('root does not match SDK', () {
833 }); 785 d.dir(appPath, [
834 786 d.pubspec({
835 testResolve('root does not match SDK', { 787 'name': 'myapp',
836 'myapp 0.0.0': {'sdk': badVersion } 788 'environment': {'sdk': '0.0.0'}
837 }, error: couldNotSolve); 789 })
838 790 ]).create();
839 testResolve('dependency does not match SDK', { 791
840 'myapp 0.0.0': {'foo': 'any'}, 792 expectResolves(error: 'Package myapp requires SDK version 0.0.0 but the '
841 'foo 0.0.0': {'sdk': badVersion } 793 'current SDK is 0.1.2+3.');
842 }, error: couldNotSolve); 794 });
843 795
844 testResolve('transitive dependency does not match SDK', { 796 integration('dependency does not match SDK', () {
845 'myapp 0.0.0': {'foo': 'any'}, 797 servePackages((builder) {
846 'foo 0.0.0': {'bar': 'any'}, 798 builder.serve('foo', '1.0.0', pubspec: {'environment': {'sdk': '0.0.0'}});
847 'bar 0.0.0': {'sdk': badVersion } 799 });
848 }, error: couldNotSolve); 800
849 801 d.appDir({'foo': 'any'}).create();
850 testResolve('selects a dependency version that allows the SDK', { 802 expectResolves(
851 'myapp 0.0.0': {'foo': 'any'}, 803 error: 'Package foo requires SDK version 0.0.0 but the '
852 'foo 1.0.0': {'sdk': goodVersion }, 804 'current SDK is 0.1.2+3.');
853 'foo 2.0.0': {'sdk': goodVersion }, 805 });
854 'foo 3.0.0': {'sdk': badVersion }, 806
855 'foo 4.0.0': {'sdk': badVersion } 807 integration('transitive dependency does not match SDK', () {
856 }, result: { 808 servePackages((builder) {
857 'myapp from root': '0.0.0', 809 builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
858 'foo': '2.0.0' 810 builder.serve('bar', '1.0.0', pubspec: {'environment': {'sdk': '0.0.0'}});
859 }, maxTries: 3); 811 });
860 812
861 testResolve('selects a transitive dependency version that allows the SDK', { 813 d.appDir({'foo': 'any'}).create();
862 'myapp 0.0.0': {'foo': 'any'}, 814 expectResolves(
863 'foo 1.0.0': {'bar': 'any'}, 815 error: 'Package bar requires SDK version 0.0.0 but the '
864 'bar 1.0.0': {'sdk': goodVersion }, 816 'current SDK is 0.1.2+3.');
865 'bar 2.0.0': {'sdk': goodVersion }, 817 });
866 'bar 3.0.0': {'sdk': badVersion }, 818
867 'bar 4.0.0': {'sdk': badVersion } 819 integration('selects a dependency version that allows the SDK', () {
868 }, result: { 820 servePackages((builder) {
869 'myapp from root': '0.0.0', 821 builder.serve('foo', '1.0.0',
870 'foo': '1.0.0', 822 pubspec: {'environment': {'sdk': '0.1.2+3'}});
871 'bar': '2.0.0' 823 builder.serve('foo', '2.0.0',
872 }, maxTries: 3); 824 pubspec: {'environment': {'sdk': '0.1.2+3'}});
873 825 builder.serve('foo', '3.0.0', pubspec: {'environment': {'sdk': '0.0.0'}});
874 testResolve('selects a dependency version that allows a transitive ' 826 builder.serve('foo', '4.0.0', pubspec: {'environment': {'sdk': '0.0.0'}});
875 'dependency that allows the SDK', { 827 });
876 'myapp 0.0.0': {'foo': 'any'}, 828
877 'foo 1.0.0': {'bar': '1.0.0'}, 829 d.appDir({'foo': 'any'}).create();
878 'foo 2.0.0': {'bar': '2.0.0'}, 830 expectResolves(result: {'foo': '2.0.0'});
879 'foo 3.0.0': {'bar': '3.0.0'}, 831 });
880 'foo 4.0.0': {'bar': '4.0.0'}, 832
881 'bar 1.0.0': {'sdk': goodVersion }, 833 integration('selects a transitive dependency version that allows the SDK',
882 'bar 2.0.0': {'sdk': goodVersion }, 834 () {
883 'bar 3.0.0': {'sdk': badVersion }, 835 servePackages((builder) {
884 'bar 4.0.0': {'sdk': badVersion } 836 builder.serve('foo', '1.0.0', deps: {'bar': 'any'});
885 }, result: { 837 builder.serve('bar', '1.0.0',
886 'myapp from root': '0.0.0', 838 pubspec: {'environment': {'sdk': '0.1.2+3'}});
887 'foo': '2.0.0', 839 builder.serve('bar', '2.0.0',
888 'bar': '2.0.0' 840 pubspec: {'environment': {'sdk': '0.1.2+3'}});
889 }, maxTries: 3); 841 builder.serve('bar', '3.0.0', pubspec: {'environment': {'sdk': '0.0.0'}});
842 builder.serve('bar', '4.0.0', pubspec: {'environment': {'sdk': '0.0.0'}});
843 });
844
845 d.appDir({'foo': 'any'}).create();
846 expectResolves(result: {'foo': '1.0.0', 'bar': '2.0.0'});
847 });
848
849 integration('selects a dependency version that allows a transitive '
850 'dependency that allows the SDK', () {
851 servePackages((builder) {
852 builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
853 builder.serve('foo', '2.0.0', deps: {'bar': '2.0.0'});
854 builder.serve('foo', '3.0.0', deps: {'bar': '3.0.0'});
855 builder.serve('foo', '4.0.0', deps: {'bar': '4.0.0'});
856 builder.serve('bar', '1.0.0',
857 pubspec: {'environment': {'sdk': '0.1.2+3'}});
858 builder.serve('bar', '2.0.0',
859 pubspec: {'environment': {'sdk': '0.1.2+3'}});
860 builder.serve('bar', '3.0.0', pubspec: {'environment': {'sdk': '0.0.0'}});
861 builder.serve('bar', '4.0.0', pubspec: {'environment': {'sdk': '0.0.0'}});
862 });
863
864 d.appDir({'foo': 'any'}).create();
865 expectResolves(result: {'foo': '2.0.0', 'bar': '2.0.0'}, tries: 3);
866 });
890 } 867 }
891 868
892 void prerelease() { 869 void prerelease() {
893 testResolve('prefer stable versions over unstable', { 870 integration('prefer stable versions over unstable', () {
894 'myapp 0.0.0': { 871 servePackages((builder) {
895 'a': 'any' 872 builder.serve('a', '1.0.0');
896 }, 873 builder.serve('a', '1.1.0-dev');
897 'a 1.0.0': {}, 874 builder.serve('a', '2.0.0-dev');
898 'a 1.1.0-dev': {}, 875 builder.serve('a', '3.0.0-dev');
899 'a 2.0.0-dev': {}, 876 });
900 'a 3.0.0-dev': {} 877
901 }, result: { 878 d.appDir({'a': 'any'}).create();
902 'myapp from root': '0.0.0', 879 expectResolves(result: {'a': '1.0.0'});
903 'a': '1.0.0' 880 });
904 }); 881
905 882 integration('use latest allowed prerelease if no stable versions match', () {
906 testResolve('use latest allowed prerelease if no stable versions match', { 883 servePackages((builder) {
907 'myapp 0.0.0': { 884 builder.serve('a', '1.0.0-dev');
908 'a': '<2.0.0' 885 builder.serve('a', '1.1.0-dev');
909 }, 886 builder.serve('a', '1.9.0-dev');
910 'a 1.0.0-dev': {}, 887 builder.serve('a', '3.0.0');
911 'a 1.1.0-dev': {}, 888 });
912 'a 1.9.0-dev': {}, 889
913 'a 3.0.0': {} 890 d.appDir({'a': '<2.0.0'}).create();
914 }, result: { 891 expectResolves(result: {'a': '1.9.0-dev'});
915 'myapp from root': '0.0.0', 892 });
916 'a': '1.9.0-dev' 893
917 }); 894 integration('use an earlier stable version on a < constraint', () {
918 895 servePackages((builder) {
919 testResolve('use an earlier stable version on a < constraint', { 896 builder.serve('a', '1.0.0');
920 'myapp 0.0.0': { 897 builder.serve('a', '1.1.0');
921 'a': '<2.0.0' 898 builder.serve('a', '2.0.0-dev');
922 }, 899 builder.serve('a', '2.0.0');
923 'a 1.0.0': {}, 900 });
924 'a 1.1.0': {}, 901
925 'a 2.0.0-dev': {}, 902 d.appDir({'a': '<2.0.0'}).create();
926 'a 2.0.0': {} 903 expectResolves(result: {'a': '1.1.0'});
927 }, result: { 904 });
928 'myapp from root': '0.0.0', 905
929 'a': '1.1.0' 906 integration('prefer a stable version even if constraint mentions unstable',
930 }); 907 () {
931 908 servePackages((builder) {
932 testResolve('prefer a stable version even if constraint mentions unstable', { 909 builder.serve('a', '1.0.0');
933 'myapp 0.0.0': { 910 builder.serve('a', '1.1.0');
934 'a': '<=2.0.0-dev' 911 builder.serve('a', '2.0.0-dev');
935 }, 912 builder.serve('a', '2.0.0');
936 'a 1.0.0': {}, 913 });
937 'a 1.1.0': {}, 914
938 'a 2.0.0-dev': {}, 915 d.appDir({'a': '<=2.0.0-dev'}).create();
939 'a 2.0.0': {} 916 expectResolves(result: {'a': '1.1.0'});
940 }, result: {
941 'myapp from root': '0.0.0',
942 'a': '1.1.0'
943 }); 917 });
944 } 918 }
945 919
946 void override() { 920 void override() {
947 testResolve('chooses best version matching override constraint', { 921 integration('chooses best version matching override constraint', () {
948 'myapp 0.0.0': { 922 servePackages((builder) {
949 'a': 'any' 923 builder.serve('a', '1.0.0');
950 }, 924 builder.serve('a', '2.0.0');
951 'a 1.0.0': {}, 925 builder.serve('a', '3.0.0');
952 'a 2.0.0': {}, 926 });
953 'a 3.0.0': {} 927
954 }, overrides: { 928 d.dir(appPath, [
955 'a': '<3.0.0' 929 d.pubspec({
956 }, result: { 930 'name': 'myapp',
957 'myapp from root': '0.0.0', 931 'dependencies': {'a': 'any'},
958 'a': '2.0.0' 932 'dependency_overrides': {'a': '<3.0.0'}
959 }); 933 })
960 934 ]).create();
961 testResolve('uses override as dependency', { 935
962 'myapp 0.0.0': {}, 936 expectResolves(result: {'a': '2.0.0'});
963 'a 1.0.0': {}, 937 });
964 'a 2.0.0': {}, 938
965 'a 3.0.0': {} 939 integration('uses override as dependency', () {
966 }, overrides: { 940 servePackages((builder) {
967 'a': '<3.0.0' 941 builder.serve('a', '1.0.0');
968 }, result: { 942 builder.serve('a', '2.0.0');
969 'myapp from root': '0.0.0', 943 builder.serve('a', '3.0.0');
970 'a': '2.0.0' 944 });
971 }); 945
972 946 d.dir(appPath, [
973 testResolve('ignores other constraints on overridden package', { 947 d.pubspec({
974 'myapp 0.0.0': { 948 'name': 'myapp',
975 'b': 'any', 949 'dependency_overrides': {'a': '<3.0.0'}
976 'c': 'any' 950 })
977 }, 951 ]).create();
978 'a 1.0.0': {}, 952
979 'a 2.0.0': {}, 953 expectResolves(result: {'a': '2.0.0'});
980 'a 3.0.0': {}, 954 });
981 'b 1.0.0': { 955
982 'a': '1.0.0' 956 integration('ignores other constraints on overridden package', () {
983 }, 957 servePackages((builder) {
984 'c 1.0.0': { 958 builder.serve('a', '1.0.0');
985 'a': '3.0.0' 959 builder.serve('a', '2.0.0');
960 builder.serve('a', '3.0.0');
961 builder.serve('b', '1.0.0', deps: {'a': '1.0.0'});
962 builder.serve('c', '1.0.0', deps: {'a': '3.0.0'});
963 });
964
965 d.dir(appPath, [
966 d.pubspec({
967 'name': 'myapp',
968 'dependencies': {'b': 'any', 'c': 'any'},
969 'dependency_overrides': {'a': '2.0.0'}
970 })
971 ]).create();
972
973 expectResolves(result: {'a': '2.0.0', 'b': '1.0.0', 'c': '1.0.0'});
974 });
975
976 integration('backtracks on overidden package for its constraints', () {
977 servePackages((builder) {
978 builder.serve('a', '1.0.0', deps: {'shared': 'any'});
979 builder.serve('a', '2.0.0', deps: {'shared': '1.0.0'});
980 builder.serve('shared', '1.0.0');
981 builder.serve('shared', '2.0.0');
982 });
983
984 d.dir(appPath, [
985 d.pubspec({
986 'name': 'myapp',
987 'dependencies': {'shared': '2.0.0'},
988 'dependency_overrides': {'a': '<3.0.0'}
989 })
990 ]).create();
991
992 expectResolves(result: {'a': '1.0.0', 'shared': '2.0.0'});
993 });
994
995 integration('override compatible with locked dependency', () {
996 servePackages((builder) {
997 builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
998 builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
999 builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
1000 builder.serve('bar', '1.0.0');
1001 builder.serve('bar', '1.0.1');
1002 builder.serve('bar', '1.0.2');
1003 });
1004
1005 d.appDir({'foo': '1.0.1'}).create();
1006 expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
1007
1008 d.dir(appPath, [
1009 d.pubspec({
1010 'name': 'myapp',
1011 'dependency_overrides': {'foo': '<1.0.2'}
1012 })
1013 ]).create();
1014
1015 expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
1016 });
1017
1018 integration('override incompatible with locked dependency', () {
1019 servePackages((builder) {
1020 builder.serve('foo', '1.0.0', deps: {'bar': '1.0.0'});
1021 builder.serve('foo', '1.0.1', deps: {'bar': '1.0.1'});
1022 builder.serve('foo', '1.0.2', deps: {'bar': '1.0.2'});
1023 builder.serve('bar', '1.0.0');
1024 builder.serve('bar', '1.0.1');
1025 builder.serve('bar', '1.0.2');
1026 });
1027
1028 d.appDir({'foo': '1.0.1'}).create();
1029 expectResolves(result: {'foo': '1.0.1', 'bar': '1.0.1'});
1030
1031 d.dir(appPath, [
1032 d.pubspec({
1033 'name': 'myapp',
1034 'dependency_overrides': {'foo': '>1.0.1'}
1035 })
1036 ]).create();
1037
1038 expectResolves(result: {'foo': '1.0.2', 'bar': '1.0.2'});
1039 });
1040
1041 integration('no version that matches override', () {
1042 servePackages((builder) {
1043 builder.serve('foo', '2.0.0');
1044 builder.serve('foo', '2.1.3');
1045 });
1046
1047 d.dir(appPath, [
1048 d.pubspec({
1049 'name': 'myapp',
1050 'dependency_overrides': {'foo': '>=1.0.0 <2.0.0'}
1051 })
1052 ]).create();
1053
1054 expectResolves(
1055 error: 'Package foo has no versions that match >=1.0.0 <2.0.0 derived '
1056 'from:\n'
1057 '- myapp depends on version >=1.0.0 <2.0.0');
1058 });
1059
1060 integration('override a bad source without error', () {
1061 servePackages((builder) {
1062 builder.serve('foo', '0.0.0');
1063 });
1064
1065 d.dir(appPath, [
1066 d.pubspec({
1067 'name': 'myapp',
1068 'dependencies': {'foo': {'bad': 'any'}},
1069 'dependency_overrides': {'foo': 'any'}
1070 })
1071 ]).create();
1072
1073 expectResolves(result: {'foo': '0.0.0'});
1074 });
1075 }
1076
1077 void downgrade() {
1078 integration("downgrades a dependency to the lowest matching version", () {
1079 servePackages((builder) {
1080 builder.serve('foo', '1.0.0');
1081 builder.serve('foo', '2.0.0-dev');
1082 builder.serve('foo', '2.0.0');
1083 builder.serve('foo', '2.1.0');
1084 });
1085
1086 d.appDir({'foo': '2.1.0'}).create();
1087 expectResolves(result: {'foo': '2.1.0'});
1088
1089 d.appDir({'foo': '>=2.0.0 <3.0.0'}).create();
1090 expectResolves(result: {'foo': '2.0.0'}, downgrade: true);
1091 });
1092
1093 integration('use earliest allowed prerelease if no stable versions match '
1094 'while downgrading', () {
1095 servePackages((builder) {
1096 builder.serve('a', '1.0.0');
1097 builder.serve('a', '2.0.0-dev.1');
1098 builder.serve('a', '2.0.0-dev.2');
1099 builder.serve('a', '2.0.0-dev.3');
1100 });
1101
1102 d.appDir({'a': '>=2.0.0-dev.1 <3.0.0'}).create();
1103 expectResolves(result: {'a': '2.0.0-dev.1'}, downgrade: true);
1104 });
1105 }
1106
1107 /// Runs "pub get" and makes assertions about its results.
1108 ///
1109 /// If [result] is passed, it's parsed as a pubspec-style dependency map, and
1110 /// this asserts that the resulting lockfile matches those dependencies, and
1111 /// that it contains only packages listed in [result].
1112 ///
1113 /// If [error] is passed, this asserts that pub's error output matches the
1114 /// value. It may be a String, a [RegExp], or a [Matcher].
1115 ///
1116 /// Asserts that version solving looks at exactly [tries] solutions. It defaults
1117 /// to allowing only a single solution.
1118 ///
1119 /// If [downgrade] is `true`, this runs "pub downgrade" instead of "pub get".
1120 void expectResolves({Map result, error, int tries, bool downgrade: false}) {
1121 schedulePub(
1122 args: [downgrade ? 'downgrade' : 'get'],
1123 output: error == null
1124 ? anyOf(
1125 contains('Got dependencies!'),
1126 matches(new RegExp(r'Changed \d+ dependenc(ies|y)!')))
1127 : null,
1128 error: error,
1129 silent: contains('Tried ${tries ?? 1} solutions'),
1130 exitCode: error == null ? 0 : 1);
1131
1132 if (result == null) return;
1133
1134 schedule(() async {
1135 var registry = new SourceRegistry();
1136 var lockFile = new LockFile.load(
1137 p.join(sandboxDir, appPath, 'pubspec.lock'),
1138 registry);
1139 var resultPubspec = new Pubspec.fromMap({"dependencies": result}, registry);
1140
1141 var ids = new Map.from(lockFile.packages);
1142 for (var dep in resultPubspec.dependencies) {
1143 expect(ids, contains(dep.name));
1144 var id = ids.remove(dep.name);
1145
1146 if (dep.source is HostedSource && dep.description is String) {
1147 // If the dep uses the default hosted source, grab it from the test
1148 // package server rather than pub.dartlang.org.
1149 dep = registry.hosted
1150 .refFor(dep.name, url: await globalPackageServer.url)
1151 .withConstraint(dep.constraint);
1152 }
1153 expect(dep.allows(id), isTrue, reason: "Expected $id to match $dep.");
986 } 1154 }
987 }, overrides: { 1155
988 'a': '2.0.0' 1156 expect(ids, isEmpty, reason: "Expected no additional packages.");
989 }, result: { 1157 });
990 'myapp from root': '0.0.0', 1158 }
991 'a': '2.0.0',
992 'b': '1.0.0',
993 'c': '1.0.0'
994 });
995
996 testResolve('backtracks on overidden package for its constraints', {
997 'myapp 0.0.0': {
998 'shared': '2.0.0'
999 },
1000 'a 1.0.0': {
1001 'shared': 'any'
1002 },
1003 'a 2.0.0': {
1004 'shared': '1.0.0'
1005 },
1006 'shared 1.0.0': {},
1007 'shared 2.0.0': {}
1008 }, overrides: {
1009 'a': '<3.0.0'
1010 }, result: {
1011 'myapp from root': '0.0.0',
1012 'a': '1.0.0',
1013 'shared': '2.0.0'
1014 }, maxTries: 2);
1015
1016 testResolve('override compatible with locked dependency', {
1017 'myapp 0.0.0': {
1018 'foo': 'any'
1019 },
1020 'foo 1.0.0': { 'bar': '1.0.0' },
1021 'foo 1.0.1': { 'bar': '1.0.1' },
1022 'foo 1.0.2': { 'bar': '1.0.2' },
1023 'bar 1.0.0': {},
1024 'bar 1.0.1': {},
1025 'bar 1.0.2': {}
1026 }, lockfile: {
1027 'foo': '1.0.1'
1028 }, overrides: {
1029 'foo': '<1.0.2'
1030 }, result: {
1031 'myapp from root': '0.0.0',
1032 'foo': '1.0.1',
1033 'bar': '1.0.1'
1034 });
1035
1036 testResolve('override incompatible with locked dependency', {
1037 'myapp 0.0.0': {
1038 'foo': 'any'
1039 },
1040 'foo 1.0.0': { 'bar': '1.0.0' },
1041 'foo 1.0.1': { 'bar': '1.0.1' },
1042 'foo 1.0.2': { 'bar': '1.0.2' },
1043 'bar 1.0.0': {},
1044 'bar 1.0.1': {},
1045 'bar 1.0.2': {}
1046 }, lockfile: {
1047 'foo': '1.0.1'
1048 }, overrides: {
1049 'foo': '>1.0.1'
1050 }, result: {
1051 'myapp from root': '0.0.0',
1052 'foo': '1.0.2',
1053 'bar': '1.0.2'
1054 });
1055
1056 testResolve('no version that matches override', {
1057 'myapp 0.0.0': {},
1058 'foo 2.0.0': {},
1059 'foo 2.1.3': {}
1060 }, overrides: {
1061 'foo': '>=1.0.0 <2.0.0'
1062 }, error: noVersion(['myapp']));
1063
1064 testResolve('override a bad source without error', {
1065 'myapp 0.0.0': {
1066 'foo from bad': 'any'
1067 },
1068 'foo 0.0.0': {}
1069 }, overrides: {
1070 'foo': 'any'
1071 }, result: {
1072 'myapp from root': '0.0.0',
1073 'foo': '0.0.0'
1074 });
1075 }
1076
1077 void downgrade() {
1078 testResolve("downgrades a dependency to the lowest matching version", {
1079 'myapp 0.0.0': {
1080 'foo': '>=2.0.0 <3.0.0'
1081 },
1082 'foo 1.0.0': {},
1083 'foo 2.0.0-dev': {},
1084 'foo 2.0.0': {},
1085 'foo 2.1.0': {}
1086 }, lockfile: {
1087 'foo': '2.1.0'
1088 }, result: {
1089 'myapp from root': '0.0.0',
1090 'foo': '2.0.0'
1091 }, downgrade: true);
1092
1093 testResolve('use earliest allowed prerelease if no stable versions match '
1094 'while downgrading', {
1095 'myapp 0.0.0': {
1096 'a': '>=2.0.0-dev.1 <3.0.0'
1097 },
1098 'a 1.0.0': {},
1099 'a 2.0.0-dev.1': {},
1100 'a 2.0.0-dev.2': {},
1101 'a 2.0.0-dev.3': {}
1102 }, result: {
1103 'myapp from root': '0.0.0',
1104 'a': '2.0.0-dev.1'
1105 }, downgrade: true);
1106 }
1107
1108 testResolve(String description, Map packages, {
1109 Map lockfile, Map overrides, Map result, FailMatcherBuilder error,
1110 int maxTries, bool downgrade: false}) {
1111 if (maxTries == null) maxTries = 1;
1112
1113 test(description, () {
1114 source1 = new MockSource('mock1');
1115 source2 = new MockSource('mock2');
1116
1117 var cache = new SystemCache(rootDir: '.');
1118 cache.sources.register(source1);
1119 cache.sources.register(source2);
1120 cache.sources.setDefault(source1.name);
1121
1122 // Build the test package graph.
1123 var root;
1124 packages.forEach((description, dependencies) {
1125 var id = parseSpec(cache.sources, description);
1126 var package = mockPackage(cache.sources, id, dependencies,
1127 id.name == 'myapp' ? overrides : null);
1128 if (id.name == 'myapp') {
1129 // Don't add the root package to the server, so we can verify that Pub
1130 // doesn't try to look up information about the local package on the
1131 // remote server.
1132 root = package;
1133 } else {
1134 (cache.source(id.source) as BoundMockSource)
1135 .addPackage(id.description, package);
1136 }
1137 });
1138
1139 // Clean up the expectation.
1140 if (result != null) {
1141 var newResult = {};
1142 result.forEach((description, version) {
1143 var id = parseSpec(cache.sources, description, version);
1144 newResult[id.name] = id;
1145 });
1146 result = newResult;
1147 }
1148
1149 // Parse the lockfile.
1150 var realLockFile;
1151 if (lockfile == null) {
1152 realLockFile = new LockFile.empty();
1153 } else {
1154 realLockFile = new LockFile(lockfile.keys.map((name) {
1155 var version = new Version.parse(lockfile[name]);
1156 return new PackageId(name, source1, version, name);
1157 }));
1158 }
1159
1160 // Resolve the versions.
1161 log.verbosity = log.Verbosity.NONE;
1162 var future = resolveVersions(
1163 downgrade ? SolveType.DOWNGRADE : SolveType.GET,
1164 cache, root, lockFile: realLockFile);
1165
1166 var matcher;
1167 if (result != null) {
1168 matcher = new SolveSuccessMatcher(result, maxTries);
1169 } else if (error != null) {
1170 matcher = error(maxTries);
1171 }
1172
1173 expect(future, completion(matcher));
1174 });
1175 }
1176
1177 typedef SolveFailMatcher FailMatcherBuilder(int maxTries);
1178
1179 FailMatcherBuilder noVersion(List<String> packages) {
1180 return (maxTries) => new SolveFailMatcher(packages, maxTries,
1181 NoVersionException);
1182 }
1183
1184 FailMatcherBuilder disjointConstraint(List<String> packages) {
1185 return (maxTries) => new SolveFailMatcher(packages, maxTries,
1186 DisjointConstraintException);
1187 }
1188
1189 FailMatcherBuilder descriptionMismatch(
1190 String package, String depender1, String depender2) {
1191 return (maxTries) => new SolveFailMatcher([package, depender1, depender2],
1192 maxTries, DescriptionMismatchException);
1193 }
1194
1195 // If no solution can be found, the solver just reports the last failure that
1196 // happened during propagation. Since we don't specify the order that solutions
1197 // are tried, this just validates that *some* failure occurred, but not which.
1198 SolveFailMatcher couldNotSolve(maxTries) =>
1199 new SolveFailMatcher([], maxTries, null);
1200
1201 FailMatcherBuilder sourceMismatch(
1202 String package, String depender1, String depender2) {
1203 return (maxTries) => new SolveFailMatcher([package, depender1, depender2],
1204 maxTries, SourceMismatchException);
1205 }
1206
1207 unknownSource(String depender, String dependency, String source) {
1208 return (maxTries) => new SolveFailMatcher([depender, dependency, source],
1209 maxTries, UnknownSourceException);
1210 }
1211
1212 class SolveSuccessMatcher implements Matcher {
1213 /// The expected concrete package selections.
1214 final Map<String, PackageId> _expected;
1215
1216 /// The maximum number of attempts that should have been tried before finding
1217 /// the solution.
1218 final int _maxTries;
1219
1220 SolveSuccessMatcher(this._expected, this._maxTries);
1221
1222 Description describe(Description description) {
1223 return description.add(
1224 'Solver to use at most $_maxTries attempts to find:\n'
1225 '${_listPackages(_expected.values)}');
1226 }
1227
1228 Description describeMismatch(SolveResult result,
1229 Description description,
1230 Map state, bool verbose) {
1231 if (!result.succeeded) {
1232 description.add('Solver failed with:\n${result.error}');
1233 return null;
1234 }
1235
1236 description.add('Resolved:\n${_listPackages(result.packages)}\n');
1237 description.add(state['failures']);
1238 return description;
1239 }
1240
1241 bool matches(SolveResult result, Map state) {
1242 if (!result.succeeded) return false;
1243
1244 var expected = new Map.from(_expected);
1245 var failures = new StringBuffer();
1246
1247 for (var id in result.packages) {
1248 if (!expected.containsKey(id.name)) {
1249 failures.writeln('Should not have selected $id');
1250 } else {
1251 var expectedId = expected.remove(id.name);
1252 if (id != expectedId) {
1253 failures.writeln('Expected $expectedId, not $id');
1254 }
1255 }
1256 }
1257
1258 if (!expected.isEmpty) {
1259 failures.writeln('Missing:\n${_listPackages(expected.values)}');
1260 }
1261
1262 // Allow 1 here because the greedy solver will only make one attempt.
1263 if (result.attemptedSolutions != 1 &&
1264 result.attemptedSolutions > _maxTries) {
1265 failures.writeln('Took ${result.attemptedSolutions} attempts');
1266 }
1267
1268 if (!failures.isEmpty) {
1269 state['failures'] = failures.toString();
1270 return false;
1271 }
1272
1273 return true;
1274 }
1275
1276 String _listPackages(Iterable<PackageId> packages) {
1277 return '- ${packages.join('\n- ')}';
1278 }
1279 }
1280
1281 class SolveFailMatcher implements Matcher {
1282 /// The strings that should appear in the resulting error message.
1283 // TODO(rnystrom): This seems to always be package names. Make that explicit.
1284 final Iterable<String> _expected;
1285
1286 /// The maximum number of attempts that should be tried before failing.
1287 final int _maxTries;
1288
1289 /// The concrete error type that should be found, or `null` if any
1290 /// [SolveFailure] is allowed.
1291 final Type _expectedType;
1292
1293 SolveFailMatcher(this._expected, this._maxTries, this._expectedType);
1294
1295 Description describe(Description description) {
1296 description.add('Solver should fail after at most $_maxTries attempts.');
1297 if (!_expected.isEmpty) {
1298 var textList = _expected.map((s) => '"$s"').join(", ");
1299 description.add(' The error should contain $textList.');
1300 }
1301 return description;
1302 }
1303
1304 Description describeMismatch(SolveResult result,
1305 Description description,
1306 Map state, bool verbose) {
1307 description.add(state['failures']);
1308 return description;
1309 }
1310
1311 bool matches(SolveResult result, Map state) {
1312 var failures = new StringBuffer();
1313
1314 if (result.succeeded) {
1315 failures.writeln('Solver succeeded');
1316 } else {
1317 if (_expectedType != null && result.error.runtimeType != _expectedType) {
1318 failures.writeln('Should have error type $_expectedType, got '
1319 '${result.error.runtimeType}');
1320 }
1321
1322 var message = result.error.toString();
1323 for (var expected in _expected) {
1324 if (!message.contains(expected)) {
1325 failures.writeln(
1326 'Expected error to contain "$expected", got:\n$message');
1327 }
1328 }
1329
1330 // Allow 1 here because the greedy solver will only make one attempt.
1331 if (result.attemptedSolutions != 1 &&
1332 result.attemptedSolutions > _maxTries) {
1333 failures.writeln('Took ${result.attemptedSolutions} attempts');
1334 }
1335 }
1336
1337 if (!failures.isEmpty) {
1338 state['failures'] = failures.toString();
1339 return false;
1340 }
1341
1342 return true;
1343 }
1344 }
1345
1346 /// A source used for testing. This both creates mock package objects and acts
1347 /// as a source for them.
1348 ///
1349 /// In order to support testing packages that have the same name but different
1350 /// descriptions, a package's name is calculated by taking the description
1351 /// string and stripping off any trailing hyphen followed by non-hyphen
1352 /// characters.
1353 class MockSource extends Source {
1354 final String name;
1355 final hasMultipleVersions = true;
1356
1357 MockSource(this.name);
1358
1359 BoundSource bind(SystemCache cache) => new BoundMockSource(this, cache);
1360
1361 PackageRef parseRef(String name, description, {String containingPath}) =>
1362 new PackageRef(name, this, description);
1363
1364 PackageId parseId(String name, Version version, description) =>
1365 new PackageId(name, this, version, description);
1366
1367 bool descriptionsEqual(description1, description2) =>
1368 description1 == description2;
1369
1370 int hashDescription(description) => description.hashCode;
1371 }
1372
1373 class BoundMockSource extends CachedSource {
1374 final SystemCache systemCache;
1375
1376 final MockSource source;
1377
1378 final _packages = <String, Map<Version, Package>>{};
1379
1380 /// Keeps track of which package version lists have been requested. Ensures
1381 /// that a source is only hit once for a given package and that pub
1382 /// internally caches the results.
1383 final _requestedVersions = new Set<String>();
1384
1385 /// Keeps track of which package pubspecs have been requested. Ensures that a
1386 /// source is only hit once for a given package and that pub internally
1387 /// caches the results.
1388 final _requestedPubspecs = new Map<String, Set<Version>>();
1389
1390 BoundMockSource(this.source, this.systemCache);
1391
1392 String getDirectory(PackageId id) => '${id.name}-${id.version}';
1393
1394 Future<List<PackageId>> doGetVersions(PackageRef ref) async {
1395 // Make sure the solver doesn't request the same thing twice.
1396 if (_requestedVersions.contains(ref.description)) {
1397 throw new Exception('Version list for ${ref.description} was already '
1398 'requested.');
1399 }
1400
1401 _requestedVersions.add(ref.description);
1402
1403 if (!_packages.containsKey(ref.description)){
1404 throw new Exception('MockSource does not have a package matching '
1405 '"${ref.description}".');
1406 }
1407
1408 return _packages[ref.description].values.map((package) {
1409 return new PackageId(ref.name, source, package.version, ref.description);
1410 }).toList();
1411 }
1412
1413 Future<Pubspec> describeUncached(PackageId id) {
1414 return new Future.sync(() {
1415 // Make sure the solver doesn't request the same thing twice.
1416 if (_requestedPubspecs.containsKey(id.description) &&
1417 _requestedPubspecs[id.description].contains(id.version)) {
1418 throw new Exception('Pubspec for $id was already requested.');
1419 }
1420
1421 _requestedPubspecs.putIfAbsent(id.description, () => new Set<Version>());
1422 _requestedPubspecs[id.description].add(id.version);
1423
1424 return _packages[id.description][id.version].pubspec;
1425 });
1426 }
1427
1428 Future<Package> downloadToSystemCache(PackageId id) =>
1429 throw new UnsupportedError('Cannot download mock packages');
1430
1431 List<Package> getCachedPackages() =>
1432 throw new UnsupportedError('Cannot get mock packages');
1433
1434 Future<Pair<List<PackageId>, List<PackageId>>> repairCachedPackages() =>
1435 throw new UnsupportedError('Cannot repair mock packages');
1436
1437 void addPackage(String description, Package package) {
1438 _packages.putIfAbsent(description, () => new Map<Version, Package>());
1439 _packages[description][package.version] = package;
1440 }
1441 }
1442
1443 Package mockPackage(SourceRegistry sources, PackageId id, Map dependencyStrings,
1444 Map overrides) {
1445 var sdkConstraint = null;
1446
1447 // Build the pubspec dependencies.
1448 var dependencies = <PackageDep>[];
1449 var devDependencies = <PackageDep>[];
1450
1451 dependencyStrings.forEach((spec, constraint) {
1452 var isDev = spec.startsWith("(dev) ");
1453 if (isDev) {
1454 spec = spec.substring("(dev) ".length);
1455 }
1456
1457 var dep = parseSpec(sources, spec).withConstraint(
1458 new VersionConstraint.parse(constraint));
1459
1460 if (dep.name == 'sdk') {
1461 sdkConstraint = dep.constraint;
1462 return;
1463 }
1464
1465 if (isDev) {
1466 devDependencies.add(dep);
1467 } else {
1468 dependencies.add(dep);
1469 }
1470 });
1471
1472 var dependencyOverrides = <PackageDep>[];
1473 if (overrides != null) {
1474 overrides.forEach((spec, constraint) {
1475 dependencyOverrides.add(parseSpec(sources, spec)
1476 .withConstraint(new VersionConstraint.parse(constraint)));
1477 });
1478 }
1479
1480 return new Package.inMemory(new Pubspec(id.name,
1481 version: id.version,
1482 dependencies: dependencies,
1483 devDependencies: devDependencies,
1484 dependencyOverrides: dependencyOverrides,
1485 sdkConstraint: sdkConstraint));
1486 }
1487
1488 /// Creates a new [PackageId] parsed from [text], which looks something like
1489 /// this:
1490 ///
1491 /// foo-xyz 1.0.0 from mock
1492 ///
1493 /// The package name is "foo". A hyphenated suffix like "-xyz" here is part
1494 /// of the package description, but not its name, so the description here is
1495 /// "foo-xyz".
1496 ///
1497 /// This is followed by an optional [Version]. If [version] is provided, then
1498 /// it is parsed to a [Version], and [text] should *not* also contain a
1499 /// version string.
1500 ///
1501 /// The "from mock" optional suffix is the name of a source for the package.
1502 /// If omitted, it defaults to "mock1".
1503 PackageId parseSpec(SourceRegistry sources, String text, [String version]) {
1504 var pattern = new RegExp(r"(([a-z_]*)(-[a-z_]+)?)( ([^ ]+))?( from (.*))?$");
1505 var match = pattern.firstMatch(text);
1506 if (match == null) {
1507 throw new FormatException("Could not parse spec '$text'.");
1508 }
1509
1510 var description = match[1];
1511 var name = match[2];
1512
1513 var parsedVersion;
1514 if (version != null) {
1515 // Spec string shouldn't also contain a version.
1516 if (match[5] != null) {
1517 throw new ArgumentError("Spec '$text' should not contain a version "
1518 "since '$version' was passed in explicitly.");
1519 }
1520 parsedVersion = new Version.parse(version);
1521 } else {
1522 if (match[5] != null) {
1523 parsedVersion = new Version.parse(match[5]);
1524 } else {
1525 parsedVersion = Version.none;
1526 }
1527 }
1528
1529 var source = sources["mock1"];
1530 if (match[7] != null) source = match[7] == "root" ? null : sources[match[7]];
1531
1532 return new PackageId(name, source, parsedVersion, description);
1533 }
OLDNEW
« no previous file with comments | « test/test_pub.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698