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