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 |