| OLD | NEW | 
 | (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 } |  | 
| OLD | NEW |