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