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

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

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

Powered by Google App Engine
This is Rietveld 408576698