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

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

Issue 14297021: Move pub into sdk/lib/_internal. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Disallow package: imports of pub. Created 7 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « utils/tests/pub/validator/utils.dart ('k') | utils/tests/pub/version_test.dart » ('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_update_test;
6
7 import 'dart:async';
8 import 'dart:io';
9
10 import 'package:unittest/unittest.dart';
11
12 import '../../pub/lock_file.dart';
13 import '../../pub/package.dart';
14 import '../../pub/pubspec.dart';
15 import '../../pub/sdk.dart' as sdk;
16 import '../../pub/source.dart';
17 import '../../pub/source_registry.dart';
18 import '../../pub/system_cache.dart';
19 import '../../pub/utils.dart';
20 import '../../pub/version.dart';
21 import '../../pub/solver/version_solver.dart';
22 import 'test_pub.dart';
23
24 MockSource source1;
25 MockSource source2;
26
27 main() {
28 initConfig();
29
30 // Since this test isn't run from the SDK, it can't find the "version" file
31 // to load. Instead, just manually inject a version.
32 sdk.version = new Version(1, 2, 3);
33
34 group('basic graph', basicGraph);
35 group('with lockfile', withLockFile);
36 group('root dependency', rootDependency);
37 group('dev dependency', devDependency);
38 group('unsolvable', unsolvable);
39 group('backtracking', backtracking);
40 group('SDK constraint', sdkConstraint);
41 }
42
43 void basicGraph() {
44 testResolve('no dependencies', {
45 'myapp 0.0.0': {}
46 }, result: {
47 'myapp from root': '0.0.0'
48 });
49
50 testResolve('simple dependency tree', {
51 'myapp 0.0.0': {
52 'a': '1.0.0',
53 'b': '1.0.0'
54 },
55 'a 1.0.0': {
56 'aa': '1.0.0',
57 'ab': '1.0.0'
58 },
59 'aa 1.0.0': {},
60 'ab 1.0.0': {},
61 'b 1.0.0': {
62 'ba': '1.0.0',
63 'bb': '1.0.0'
64 },
65 'ba 1.0.0': {},
66 'bb 1.0.0': {}
67 }, result: {
68 'myapp from root': '0.0.0',
69 'a': '1.0.0',
70 'aa': '1.0.0',
71 'ab': '1.0.0',
72 'b': '1.0.0',
73 'ba': '1.0.0',
74 'bb': '1.0.0'
75 });
76
77 testResolve('shared dependency with overlapping constraints', {
78 'myapp 0.0.0': {
79 'a': '1.0.0',
80 'b': '1.0.0'
81 },
82 'a 1.0.0': {
83 'shared': '>=2.0.0 <4.0.0'
84 },
85 'b 1.0.0': {
86 'shared': '>=3.0.0 <5.0.0'
87 },
88 'shared 2.0.0': {},
89 'shared 3.0.0': {},
90 'shared 3.6.9': {},
91 'shared 4.0.0': {},
92 'shared 5.0.0': {},
93 }, result: {
94 'myapp from root': '0.0.0',
95 'a': '1.0.0',
96 'b': '1.0.0',
97 'shared': '3.6.9'
98 });
99
100 testResolve('shared dependency where dependent version in turn affects '
101 'other dependencies', {
102 'myapp 0.0.0': {
103 'foo': '<=1.0.2',
104 'bar': '1.0.0'
105 },
106 'foo 1.0.0': {},
107 'foo 1.0.1': { 'bang': '1.0.0' },
108 'foo 1.0.2': { 'whoop': '1.0.0' },
109 'foo 1.0.3': { 'zoop': '1.0.0' },
110 'bar 1.0.0': { 'foo': '<=1.0.1' },
111 'bang 1.0.0': {},
112 'whoop 1.0.0': {},
113 'zoop 1.0.0': {}
114 }, result: {
115 'myapp from root': '0.0.0',
116 'foo': '1.0.1',
117 'bar': '1.0.0',
118 'bang': '1.0.0'
119 }, maxTries: 2);
120
121 testResolve('circular dependency', {
122 'myapp 1.0.0': {
123 'foo': '1.0.0'
124 },
125 'foo 1.0.0': {
126 'bar': '1.0.0'
127 },
128 'bar 1.0.0': {
129 'foo': '1.0.0'
130 }
131 }, result: {
132 'myapp from root': '1.0.0',
133 'foo': '1.0.0',
134 'bar': '1.0.0'
135 });
136 }
137
138 withLockFile() {
139 testResolve('with compatible locked dependency', {
140 'myapp 0.0.0': {
141 'foo': 'any'
142 },
143 'foo 1.0.0': { 'bar': '1.0.0' },
144 'foo 1.0.1': { 'bar': '1.0.1' },
145 'foo 1.0.2': { 'bar': '1.0.2' },
146 'bar 1.0.0': {},
147 'bar 1.0.1': {},
148 'bar 1.0.2': {}
149 }, lockfile: {
150 'foo': '1.0.1'
151 }, result: {
152 'myapp from root': '0.0.0',
153 'foo': '1.0.1',
154 'bar': '1.0.1'
155 });
156
157 testResolve('with incompatible locked dependency', {
158 'myapp 0.0.0': {
159 'foo': '>1.0.1'
160 },
161 'foo 1.0.0': { 'bar': '1.0.0' },
162 'foo 1.0.1': { 'bar': '1.0.1' },
163 'foo 1.0.2': { 'bar': '1.0.2' },
164 'bar 1.0.0': {},
165 'bar 1.0.1': {},
166 'bar 1.0.2': {}
167 }, lockfile: {
168 'foo': '1.0.1'
169 }, result: {
170 'myapp from root': '0.0.0',
171 'foo': '1.0.2',
172 'bar': '1.0.2'
173 });
174
175 testResolve('with unrelated locked dependency', {
176 'myapp 0.0.0': {
177 'foo': 'any'
178 },
179 'foo 1.0.0': { 'bar': '1.0.0' },
180 'foo 1.0.1': { 'bar': '1.0.1' },
181 'foo 1.0.2': { 'bar': '1.0.2' },
182 'bar 1.0.0': {},
183 'bar 1.0.1': {},
184 'bar 1.0.2': {},
185 'baz 1.0.0': {}
186 }, lockfile: {
187 'baz': '1.0.0'
188 }, result: {
189 'myapp from root': '0.0.0',
190 'foo': '1.0.2',
191 'bar': '1.0.2'
192 });
193
194 testResolve('unlocks dependencies if necessary to ensure that a new '
195 'dependency is satisfied', {
196 'myapp 0.0.0': {
197 'foo': 'any',
198 'newdep': 'any'
199 },
200 'foo 1.0.0': { 'bar': '<2.0.0' },
201 'bar 1.0.0': { 'baz': '<2.0.0' },
202 'baz 1.0.0': { 'qux': '<2.0.0' },
203 'qux 1.0.0': {},
204 'foo 2.0.0': { 'bar': '<3.0.0' },
205 'bar 2.0.0': { 'baz': '<3.0.0' },
206 'baz 2.0.0': { 'qux': '<3.0.0' },
207 'qux 2.0.0': {},
208 'newdep 2.0.0': { 'baz': '>=1.5.0' }
209 }, lockfile: {
210 'foo': '1.0.0',
211 'bar': '1.0.0',
212 'baz': '1.0.0',
213 'qux': '1.0.0'
214 }, result: {
215 'myapp from root': '0.0.0',
216 'foo': '2.0.0',
217 'bar': '2.0.0',
218 'baz': '2.0.0',
219 'qux': '1.0.0',
220 'newdep': '2.0.0'
221 }, maxTries: 3);
222 }
223
224 rootDependency() {
225 testResolve('with root source', {
226 'myapp 1.0.0': {
227 'foo': '1.0.0'
228 },
229 'foo 1.0.0': {
230 'myapp from root': '>=1.0.0'
231 }
232 }, result: {
233 'myapp from root': '1.0.0',
234 'foo': '1.0.0'
235 });
236
237 testResolve('with different source', {
238 'myapp 1.0.0': {
239 'foo': '1.0.0'
240 },
241 'foo 1.0.0': {
242 'myapp': '>=1.0.0'
243 }
244 }, result: {
245 'myapp from root': '1.0.0',
246 'foo': '1.0.0'
247 });
248
249 testResolve('with mismatched sources', {
250 'myapp 1.0.0': {
251 'foo': '1.0.0',
252 'bar': '1.0.0'
253 },
254 'foo 1.0.0': {
255 'myapp': '>=1.0.0'
256 },
257 'bar 1.0.0': {
258 'myapp from mock2': '>=1.0.0'
259 }
260 }, error: sourceMismatch('foo', 'bar'));
261
262 testResolve('with wrong version', {
263 'myapp 1.0.0': {
264 'foo': '1.0.0'
265 },
266 'foo 1.0.0': {
267 'myapp': '<1.0.0'
268 }
269 }, error: couldNotSolve);
270 }
271
272 devDependency() {
273 testResolve("includes root package's dev dependencies", {
274 'myapp 1.0.0': {
275 '(dev) foo': '1.0.0',
276 '(dev) bar': '1.0.0'
277 },
278 'foo 1.0.0': {},
279 'bar 1.0.0': {}
280 }, result: {
281 'myapp from root': '1.0.0',
282 'foo': '1.0.0',
283 'bar': '1.0.0'
284 });
285
286 testResolve("includes dev dependency's transitive dependencies", {
287 'myapp 1.0.0': {
288 '(dev) foo': '1.0.0'
289 },
290 'foo 1.0.0': {
291 'bar': '1.0.0'
292 },
293 'bar 1.0.0': {}
294 }, result: {
295 'myapp from root': '1.0.0',
296 'foo': '1.0.0',
297 'bar': '1.0.0'
298 });
299
300 testResolve("ignores transitive dependency's dev dependencies", {
301 'myapp 1.0.0': {
302 'foo': '1.0.0'
303 },
304 'foo 1.0.0': {
305 '(dev) bar': '1.0.0'
306 },
307 'bar 1.0.0': {}
308 }, result: {
309 'myapp from root': '1.0.0',
310 'foo': '1.0.0'
311 });
312 }
313
314 unsolvable() {
315 testResolve('no version that matches requirement', {
316 'myapp 0.0.0': {
317 'foo': '>=1.0.0 <2.0.0'
318 },
319 'foo 2.0.0': {},
320 'foo 2.1.3': {}
321 }, error: noVersion(['myapp']));
322
323 testResolve('no version that matches combined constraint', {
324 'myapp 0.0.0': {
325 'foo': '1.0.0',
326 'bar': '1.0.0'
327 },
328 'foo 1.0.0': {
329 'shared': '>=2.0.0 <3.0.0'
330 },
331 'bar 1.0.0': {
332 'shared': '>=2.9.0 <4.0.0'
333 },
334 'shared 2.5.0': {},
335 'shared 3.5.0': {}
336 }, error: noVersion(['foo', 'bar']));
337
338 testResolve('disjoint constraints', {
339 'myapp 0.0.0': {
340 'foo': '1.0.0',
341 'bar': '1.0.0'
342 },
343 'foo 1.0.0': {
344 'shared': '<=2.0.0'
345 },
346 'bar 1.0.0': {
347 'shared': '>3.0.0'
348 },
349 'shared 2.0.0': {},
350 'shared 4.0.0': {}
351 }, error: disjointConstraint(['foo', 'bar']));
352
353 testResolve('mismatched descriptions', {
354 'myapp 0.0.0': {
355 'foo': '1.0.0',
356 'bar': '1.0.0'
357 },
358 'foo 1.0.0': {
359 'shared-x': '1.0.0'
360 },
361 'bar 1.0.0': {
362 'shared-y': '1.0.0'
363 },
364 'shared-x 1.0.0': {},
365 'shared-y 1.0.0': {}
366 }, error: descriptionMismatch('foo', 'bar'));
367
368 testResolve('mismatched sources', {
369 'myapp 0.0.0': {
370 'foo': '1.0.0',
371 'bar': '1.0.0'
372 },
373 'foo 1.0.0': {
374 'shared': '1.0.0'
375 },
376 'bar 1.0.0': {
377 'shared from mock2': '1.0.0'
378 },
379 'shared 1.0.0': {},
380 'shared 1.0.0 from mock2': {}
381 }, error: sourceMismatch('foo', 'bar'));
382
383 testResolve('no valid solution', {
384 'myapp 0.0.0': {
385 'a': 'any',
386 'b': 'any'
387 },
388 'a 1.0.0': {
389 'b': '1.0.0'
390 },
391 'a 2.0.0': {
392 'b': '2.0.0'
393 },
394 'b 1.0.0': {
395 'a': '2.0.0'
396 },
397 'b 2.0.0': {
398 'a': '1.0.0'
399 }
400 }, error: couldNotSolve, maxTries: 4);
401 }
402
403 backtracking() {
404 testResolve('circular dependency on older version', {
405 'myapp 0.0.0': {
406 'a': '>=1.0.0'
407 },
408 'a 1.0.0': {},
409 'a 2.0.0': {
410 'b': '1.0.0'
411 },
412 'b 1.0.0': {
413 'a': '1.0.0'
414 }
415 }, result: {
416 'myapp from root': '0.0.0',
417 'a': '1.0.0'
418 }, maxTries: 2);
419
420 // The latest versions of a and b disagree on c. An older version of either
421 // will resolve the problem. This test validates that b, which is farther
422 // in the dependency graph from myapp is downgraded first.
423 testResolve('rolls back leaf versions first', {
424 'myapp 0.0.0': {
425 'a': 'any'
426 },
427 'a 1.0.0': {
428 'b': 'any'
429 },
430 'a 2.0.0': {
431 'b': 'any',
432 'c': '2.0.0'
433 },
434 'b 1.0.0': {},
435 'b 2.0.0': {
436 'c': '1.0.0'
437 },
438 'c 1.0.0': {},
439 'c 2.0.0': {}
440 }, result: {
441 'myapp from root': '0.0.0',
442 'a': '2.0.0',
443 'b': '1.0.0',
444 'c': '2.0.0'
445 }, maxTries: 2);
446
447 // Only one version of baz, so foo and bar will have to downgrade until they
448 // reach it.
449 testResolve('simple transitive', {
450 'myapp 0.0.0': {'foo': 'any'},
451 'foo 1.0.0': {'bar': '1.0.0'},
452 'foo 2.0.0': {'bar': '2.0.0'},
453 'foo 3.0.0': {'bar': '3.0.0'},
454 'bar 1.0.0': {'baz': 'any'},
455 'bar 2.0.0': {'baz': '2.0.0'},
456 'bar 3.0.0': {'baz': '3.0.0'},
457 'baz 1.0.0': {}
458 }, result: {
459 'myapp from root': '0.0.0',
460 'foo': '1.0.0',
461 'bar': '1.0.0',
462 'baz': '1.0.0'
463 }, maxTries: 3);
464
465 // This ensures it doesn't exhaustively search all versions of b when it's
466 // a-2.0.0 whose dependency on c-2.0.0-nonexistent led to the problem. We
467 // make sure b has more versions than a so that the solver tries a first
468 // since it sorts sibling dependencies by number of versions.
469 testResolve('backjump to nearer unsatisfied package', {
470 'myapp 0.0.0': {
471 'a': 'any',
472 'b': 'any'
473 },
474 'a 1.0.0': { 'c': '1.0.0' },
475 'a 2.0.0': { 'c': '2.0.0-nonexistent' },
476 'b 1.0.0': {},
477 'b 2.0.0': {},
478 'b 3.0.0': {},
479 'c 1.0.0': {},
480 }, result: {
481 'myapp from root': '0.0.0',
482 'a': '1.0.0',
483 'b': '3.0.0',
484 'c': '1.0.0'
485 }, maxTries: 2);
486
487 // Dependencies are ordered so that packages with fewer versions are tried
488 // first. Here, there are two valid solutions (either a or b must be
489 // downgraded once). The chosen one depends on which dep is traversed first.
490 // Since b has fewer versions, it will be traversed first, which means a will
491 // come later. Since later selections are revised first, a gets downgraded.
492 testResolve('traverse into package with fewer versions first', {
493 'myapp 0.0.0': {
494 'a': 'any',
495 'b': 'any'
496 },
497 'a 1.0.0': {'c': 'any'},
498 'a 2.0.0': {'c': 'any'},
499 'a 3.0.0': {'c': 'any'},
500 'a 4.0.0': {'c': 'any'},
501 'a 5.0.0': {'c': '1.0.0'},
502 'b 1.0.0': {'c': 'any'},
503 'b 2.0.0': {'c': 'any'},
504 'b 3.0.0': {'c': 'any'},
505 'b 4.0.0': {'c': '2.0.0'},
506 'c 1.0.0': {},
507 'c 2.0.0': {},
508 }, result: {
509 'myapp from root': '0.0.0',
510 'a': '4.0.0',
511 'b': '4.0.0',
512 'c': '2.0.0'
513 }, maxTries: 2);
514
515 // This sets up a hundred versions of foo and bar, 0.0.0 through 9.9.0. Each
516 // version of foo depends on a baz with the same major version. Each version
517 // of bar depends on a baz with the same minor version. There is only one
518 // version of baz, 0.0.0, so only older versions of foo and bar will
519 // satisfy it.
520 var map = {
521 'myapp 0.0.0': {
522 'foo': 'any',
523 'bar': 'any'
524 },
525 'baz 0.0.0': {}
526 };
527
528 for (var i = 0; i < 10; i++) {
529 for (var j = 0; j < 10; j++) {
530 map['foo $i.$j.0'] = {'baz': '$i.0.0'};
531 map['bar $i.$j.0'] = {'baz': '0.$j.0'};
532 }
533 }
534
535 testResolve('complex backtrack', map, result: {
536 'myapp from root': '0.0.0',
537 'foo': '0.9.0',
538 'bar': '9.0.0',
539 'baz': '0.0.0'
540 }, maxTries: 100);
541
542 // TODO(rnystrom): More tests. In particular:
543 // - Tests that demonstrate backtracking for every case that can cause a
544 // solution to fail (no versions, disjoint, etc.)
545 // - Tests where there are multiple valid solutions and "best" is possibly
546 // ambiguous to nail down which order the backtracker tries solutions.
547 }
548
549 sdkConstraint() {
550 var badVersion = '0.0.0-nope';
551 var goodVersion = sdk.version.toString();
552
553 testResolve('root matches SDK', {
554 'myapp 0.0.0': {'sdk': goodVersion }
555 }, result: {
556 'myapp from root': '0.0.0'
557 });
558
559 testResolve('root does not match SDK', {
560 'myapp 0.0.0': {'sdk': badVersion }
561 }, error: couldNotSolve);
562
563 testResolve('dependency does not match SDK', {
564 'myapp 0.0.0': {'foo': 'any'},
565 'foo 0.0.0': {'sdk': badVersion }
566 }, error: couldNotSolve);
567
568 testResolve('transitive dependency does not match SDK', {
569 'myapp 0.0.0': {'foo': 'any'},
570 'foo 0.0.0': {'bar': 'any'},
571 'bar 0.0.0': {'sdk': badVersion }
572 }, error: couldNotSolve);
573
574 testResolve('selects a dependency version that allows the SDK', {
575 'myapp 0.0.0': {'foo': 'any'},
576 'foo 1.0.0': {'sdk': goodVersion },
577 'foo 2.0.0': {'sdk': goodVersion },
578 'foo 3.0.0': {'sdk': badVersion },
579 'foo 4.0.0': {'sdk': badVersion }
580 }, result: {
581 'myapp from root': '0.0.0',
582 'foo': '2.0.0'
583 }, maxTries: 3);
584
585 testResolve('selects a transitive dependency version that allows the SDK', {
586 'myapp 0.0.0': {'foo': 'any'},
587 'foo 1.0.0': {'bar': 'any'},
588 'bar 1.0.0': {'sdk': goodVersion },
589 'bar 2.0.0': {'sdk': goodVersion },
590 'bar 3.0.0': {'sdk': badVersion },
591 'bar 4.0.0': {'sdk': badVersion }
592 }, result: {
593 'myapp from root': '0.0.0',
594 'foo': '1.0.0',
595 'bar': '2.0.0'
596 }, maxTries: 3);
597
598 testResolve('selects a dependency version that allows a transitive '
599 'dependency that allows the SDK', {
600 'myapp 0.0.0': {'foo': 'any'},
601 'foo 1.0.0': {'bar': '1.0.0'},
602 'foo 2.0.0': {'bar': '2.0.0'},
603 'foo 3.0.0': {'bar': '3.0.0'},
604 'foo 4.0.0': {'bar': '4.0.0'},
605 'bar 1.0.0': {'sdk': goodVersion },
606 'bar 2.0.0': {'sdk': goodVersion },
607 'bar 3.0.0': {'sdk': badVersion },
608 'bar 4.0.0': {'sdk': badVersion }
609 }, result: {
610 'myapp from root': '0.0.0',
611 'foo': '2.0.0',
612 'bar': '2.0.0'
613 }, maxTries: 3);
614
615 testResolve('ignores SDK constraints on bleeding edge', {
616 'myapp 0.0.0': {'sdk': badVersion }
617 }, result: {
618 'myapp from root': '0.0.0'
619 }, useBleedingEdgeSdkVersion: true);
620 }
621
622 testResolve(description, packages,
623 {lockfile, result, FailMatcherBuilder error, int maxTries,
624 bool useBleedingEdgeSdkVersion}) {
625 if (maxTries == null) maxTries = 1;
626 if (useBleedingEdgeSdkVersion == null) useBleedingEdgeSdkVersion = false;
627
628 test(description, () {
629 var cache = new SystemCache('.');
630 source1 = new MockSource('mock1');
631 source2 = new MockSource('mock2');
632 cache.register(source1);
633 cache.register(source2);
634 cache.sources.setDefault(source1.name);
635
636 // Build the test package graph.
637 var root;
638 packages.forEach((nameVersion, dependencies) {
639 var parsed = parseSource(nameVersion, (isDev, nameVersion, source) {
640 var parts = nameVersion.split(' ');
641 var name = parts[0];
642 var version = parts[1];
643
644 var package = mockPackage(name, version, dependencies);
645 if (name == 'myapp') {
646 // Don't add the root package to the server, so we can verify that Pub
647 // doesn't try to look up information about the local package on the
648 // remote server.
649 root = package;
650 } else {
651 source.addPackage(name, package);
652 }
653 });
654 });
655
656 // Clean up the expectation.
657 if (result != null) {
658 var newResult = {};
659 result.forEach((name, version) {
660 parseSource(name, (isDev, name, source) {
661 version = new Version.parse(version);
662 newResult[name] = new PackageId(name, source, version, name);
663 });
664 });
665 result = newResult;
666 }
667
668 var realLockFile = new LockFile.empty();
669 if (lockfile != null) {
670 lockfile.forEach((name, version) {
671 version = new Version.parse(version);
672 realLockFile.packages[name] =
673 new PackageId(name, source1, version, name);
674 });
675 }
676
677 // Make a version number like the continuous build's version.
678 var previousVersion = sdk.version;
679 if (useBleedingEdgeSdkVersion) {
680 sdk.version = new Version(0, 1, 2, build: '0_r12345_juser');
681 }
682
683 // Resolve the versions.
684 var future = resolveVersions(cache.sources, root,
685 lockFile: realLockFile);
686
687 var matcher;
688 if (result != null) {
689 matcher = new SolveSuccessMatcher(result, maxTries);
690 } else if (error != null) {
691 matcher = error(maxTries);
692 }
693
694 future = future.whenComplete(() {
695 if (useBleedingEdgeSdkVersion) {
696 sdk.version = previousVersion;
697 }
698 });
699
700 expect(future, completion(matcher));
701 });
702 }
703
704 typedef SolveFailMatcher FailMatcherBuilder(int maxTries);
705
706 FailMatcherBuilder noVersion(List<String> packages) {
707 return (maxTries) => new SolveFailMatcher(packages, maxTries,
708 NoVersionException);
709 }
710
711 FailMatcherBuilder disjointConstraint(List<String> packages) {
712 return (maxTries) => new SolveFailMatcher(packages, maxTries,
713 DisjointConstraintException);
714 }
715
716 FailMatcherBuilder descriptionMismatch(String package1, String package2) {
717 return (maxTries) => new SolveFailMatcher([package1, package2], maxTries,
718 DescriptionMismatchException);
719 }
720
721 // If no solution can be found, the solver just reports the last failure that
722 // happened during propagation. Since we don't specify the order that solutions
723 // are tried, this just validates that *some* failure occurred, but not which.
724 SolveFailMatcher couldNotSolve(maxTries) =>
725 new SolveFailMatcher([], maxTries, null);
726
727 FailMatcherBuilder sourceMismatch(String package1, String package2) {
728 return (maxTries) => new SolveFailMatcher([package1, package2], maxTries,
729 SourceMismatchException);
730 }
731
732 class SolveSuccessMatcher implements Matcher {
733 /// The expected concrete package selections.
734 final Map<String, PackageId> _expected;
735
736 /// The maximum number of attempts that should have been tried before finding
737 /// the solution.
738 final int _maxTries;
739
740 SolveSuccessMatcher(this._expected, this._maxTries);
741
742 Description describe(Description description) {
743 return description.add(
744 'Solver to use at most $_maxTries attempts to find:\n'
745 '${_listPackages(_expected.values)}');
746 }
747
748 Description describeMismatch(SolveResult result,
749 Description description,
750 MatchState state, bool verbose) {
751 if (!result.succeeded) {
752 description.add('Solver failed with:\n${result.error}');
753 return;
754 }
755
756 description.add('Resolved:\n${_listPackages(result.packages)}\n');
757 description.add(state.state);
758 return description;
759 }
760
761 bool matches(SolveResult result, MatchState state) {
762 if (!result.succeeded) return false;
763
764 var expected = new Map.from(_expected);
765 var failures = new StringBuffer();
766
767 for (var id in result.packages) {
768 if (!expected.containsKey(id.name)) {
769 failures.writeln('Should not have selected $id');
770 } else {
771 var expectedId = expected.remove(id.name);
772 if (id != expectedId) {
773 failures.writeln('Expected $expectedId, not $id');
774 }
775 }
776 }
777
778 if (!expected.isEmpty) {
779 failures.writeln('Missing:\n${_listPackages(expected.values)}');
780 }
781
782 // Allow 1 here because the greedy solver will only make one attempt.
783 if (result.attemptedSolutions != 1 &&
784 result.attemptedSolutions != _maxTries) {
785 failures.writeln('Took ${result.attemptedSolutions} attempts');
786 }
787
788 if (!failures.isEmpty) {
789 state.state = failures.toString();
790 return false;
791 }
792
793 return true;
794 }
795
796 String _listPackages(Iterable<PackageId> packages) {
797 return '- ${packages.join('\n- ')}';
798 }
799 }
800
801 class SolveFailMatcher implements Matcher {
802 /// The strings that should appear in the resulting error message.
803 // TODO(rnystrom): This seems to always be package names. Make that explicit.
804 final Iterable<String> _expected;
805
806 /// The maximum number of attempts that should be tried before failing.
807 final int _maxTries;
808
809 /// The concrete error type that should be found, or `null` if any
810 /// [SolveFailure] is allowed.
811 final Type _expectedType;
812
813 SolveFailMatcher(this._expected, this._maxTries, this._expectedType);
814
815 Description describe(Description description) {
816 description.add('Solver should fail after at most $_maxTries attempts.');
817 if (!_expected.isEmpty) {
818 var textList = _expected.map((s) => '"$s"').join(", ");
819 description.add(' The error should contain $textList.');
820 }
821 return description;
822 }
823
824 Description describeMismatch(SolveResult result,
825 Description description,
826 MatchState state, bool verbose) {
827 description.add(state.state);
828 return description;
829 }
830
831 bool matches(SolveResult result, MatchState state) {
832 var failures = new StringBuffer();
833
834 if (result.succeeded) {
835 failures.writeln('Solver succeeded');
836 } else {
837 if (_expectedType != null && result.error.runtimeType != _expectedType) {
838 failures.writeln('Should have error type $_expectedType, got '
839 '${result.error.runtimeType}');
840 }
841
842 var message = result.error.toString();
843 for (var expected in _expected) {
844 if (!message.contains(expected)) {
845 failures.writeln(
846 'Expected error to contain "$expected", got:\n$message');
847 }
848 }
849
850 // Allow 1 here because the greedy solver will only make one attempt.
851 if (result.attemptedSolutions != 1 &&
852 result.attemptedSolutions != _maxTries) {
853 failures.writeln('Took ${result.attemptedSolutions} attempts');
854 }
855 }
856
857 if (!failures.isEmpty) {
858 state.state = failures.toString();
859 return false;
860 }
861
862 return true;
863 }
864 }
865
866 /// A source used for testing. This both creates mock package objects and acts
867 /// as a source for them.
868 ///
869 /// In order to support testing packages that have the same name but different
870 /// descriptions, a package's name is calculated by taking the description
871 /// string and stripping off any trailing hyphen followed by non-hyphen
872 /// characters.
873 class MockSource extends Source {
874 final _packages = <String, Map<Version, Package>>{};
875
876 /// Keeps track of which package version lists have been requested. Ensures
877 /// that a source is only hit once for a given package and that pub
878 /// internally caches the results.
879 final _requestedVersions = new Set<String>();
880
881 /// Keeps track of which package pubspecs have been requested. Ensures that a
882 /// source is only hit once for a given package and that pub internally
883 /// caches the results.
884 final _requestedPubspecs = new Map<String, Set<Version>>();
885
886 final String name;
887 bool get shouldCache => true;
888
889 MockSource(this.name);
890
891 Future<String> systemCacheDirectory(PackageId id) {
892 return new Future.value('${id.name}-${id.version}');
893 }
894
895 Future<List<Version>> getVersions(String name, String description) {
896 return new Future.sync(() {
897 // Make sure the solver doesn't request the same thing twice.
898 if (_requestedVersions.contains(description)) {
899 throw new Exception('Version list for $description was already '
900 'requested.');
901 }
902
903 _requestedVersions.add(description);
904
905 if (!_packages.containsKey(description)){
906 throw new Exception('MockSource does not have a package matching '
907 '"$description".');
908 }
909 return _packages[description].keys.toList();
910 });
911 }
912
913 Future<Pubspec> describe(PackageId id) {
914 return new Future.sync(() {
915 // Make sure the solver doesn't request the same thing twice.
916 if (_requestedPubspecs.containsKey(id.description) &&
917 _requestedPubspecs[id.description].contains(id.version)) {
918 throw new Exception('Pubspec for $id was already requested.');
919 }
920
921 _requestedPubspecs.putIfAbsent(id.description, () => new Set<Version>());
922 _requestedPubspecs[id.description].add(id.version);
923
924 return _packages[id.description][id.version].pubspec;
925 });
926 }
927
928 Future<bool> install(PackageId id, String path) {
929 throw new Exception('no');
930 }
931
932 void addPackage(String description, Package package) {
933 _packages.putIfAbsent(description, () => new Map<Version, Package>());
934 _packages[description][package.version] = package;
935 }
936 }
937
938 Package mockPackage(String description, String version,
939 Map dependencyStrings) {
940 var sdkConstraint = null;
941
942 // Build the pubspec dependencies.
943 var dependencies = <PackageRef>[];
944 var devDependencies = <PackageRef>[];
945
946 dependencyStrings.forEach((name, constraint) {
947 parseSource(name, (isDev, name, source) {
948 var packageName = name.replaceFirst(new RegExp(r"-[^-]+$"), "");
949 constraint = new VersionConstraint.parse(constraint);
950
951 if (name == 'sdk') {
952 sdkConstraint = constraint;
953 return;
954 }
955
956 var ref = new PackageRef(packageName, source, constraint, name);
957
958 if (isDev) {
959 devDependencies.add(ref);
960 } else {
961 dependencies.add(ref);
962 }
963 });
964 });
965
966 var name = description.replaceFirst(new RegExp(r"-[^-]+$"), "");
967 var pubspec = new Pubspec(
968 name, new Version.parse(version), dependencies, devDependencies,
969 new PubspecEnvironment(sdkConstraint));
970 return new Package.inMemory(pubspec);
971 }
972
973 void parseSource(String description,
974 callback(bool isDev, String name, Source source)) {
975 var isDev = false;
976
977 if (description.startsWith("(dev) ")) {
978 description = description.substring("(dev) ".length);
979 isDev = true;
980 }
981
982 var name = description;
983 var source = source1;
984
985 var sourceNames = {
986 'mock1': source1,
987 'mock2': source2,
988 'root': null
989 };
990
991 var match = new RegExp(r"(.*) from (.*)").firstMatch(description);
992 if (match != null) {
993 name = match[1];
994 source = sourceNames[match[2]];
995 }
996
997 callback(isDev, name, source);
998 }
OLDNEW
« no previous file with comments | « utils/tests/pub/validator/utils.dart ('k') | utils/tests/pub/version_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698