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

Side by Side Diff: sdk/lib/_internal/pub/test/version_solver_test.dart

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

Powered by Google App Engine
This is Rietveld 408576698