1import inspect 2import re 3import textwrap 4import functools 5 6import pytest 7 8import pkg_resources 9 10from .test_resources import Metadata 11 12 13def strip_comments(s): 14 return '\n'.join( 15 l for l in s.split('\n') 16 if l.strip() and not l.strip().startswith('#') 17 ) 18 19 20def parse_distributions(s): 21 ''' 22 Parse a series of distribution specs of the form: 23 {project_name}-{version} 24 [optional, indented requirements specification] 25 26 Example: 27 28 foo-0.2 29 bar-1.0 30 foo>=3.0 31 [feature] 32 baz 33 34 yield 2 distributions: 35 - project_name=foo, version=0.2 36 - project_name=bar, version=1.0, 37 requires=['foo>=3.0', 'baz; extra=="feature"'] 38 ''' 39 s = s.strip() 40 for spec in re.split('\n(?=[^\s])', s): 41 if not spec: 42 continue 43 fields = spec.split('\n', 1) 44 assert 1 <= len(fields) <= 2 45 name, version = fields.pop(0).split('-') 46 if fields: 47 requires = textwrap.dedent(fields.pop(0)) 48 metadata = Metadata(('requires.txt', requires)) 49 else: 50 metadata = None 51 dist = pkg_resources.Distribution(project_name=name, 52 version=version, 53 metadata=metadata) 54 yield dist 55 56 57class FakeInstaller(object): 58 59 def __init__(self, installable_dists): 60 self._installable_dists = installable_dists 61 62 def __call__(self, req): 63 return next(iter(filter(lambda dist: dist in req, 64 self._installable_dists)), None) 65 66 67def parametrize_test_working_set_resolve(*test_list): 68 idlist = [] 69 argvalues = [] 70 for test in test_list: 71 ( 72 name, 73 installed_dists, 74 installable_dists, 75 requirements, 76 expected1, expected2 77 ) = [ 78 strip_comments(s.lstrip()) for s in 79 textwrap.dedent(test).lstrip().split('\n\n', 5) 80 ] 81 installed_dists = list(parse_distributions(installed_dists)) 82 installable_dists = list(parse_distributions(installable_dists)) 83 requirements = list(pkg_resources.parse_requirements(requirements)) 84 for id_, replace_conflicting, expected in ( 85 (name, False, expected1), 86 (name + '_replace_conflicting', True, expected2), 87 ): 88 idlist.append(id_) 89 expected = strip_comments(expected.strip()) 90 if re.match('\w+$', expected): 91 expected = getattr(pkg_resources, expected) 92 assert issubclass(expected, Exception) 93 else: 94 expected = list(parse_distributions(expected)) 95 argvalues.append(pytest.param(installed_dists, installable_dists, 96 requirements, replace_conflicting, 97 expected)) 98 return pytest.mark.parametrize('installed_dists,installable_dists,' 99 'requirements,replace_conflicting,' 100 'resolved_dists_or_exception', 101 argvalues, ids=idlist) 102 103 104@parametrize_test_working_set_resolve( 105 ''' 106 # id 107 noop 108 109 # installed 110 111 # installable 112 113 # wanted 114 115 # resolved 116 117 # resolved [replace conflicting] 118 ''', 119 120 ''' 121 # id 122 already_installed 123 124 # installed 125 foo-3.0 126 127 # installable 128 129 # wanted 130 foo>=2.1,!=3.1,<4 131 132 # resolved 133 foo-3.0 134 135 # resolved [replace conflicting] 136 foo-3.0 137 ''', 138 139 ''' 140 # id 141 installable_not_installed 142 143 # installed 144 145 # installable 146 foo-3.0 147 foo-4.0 148 149 # wanted 150 foo>=2.1,!=3.1,<4 151 152 # resolved 153 foo-3.0 154 155 # resolved [replace conflicting] 156 foo-3.0 157 ''', 158 159 ''' 160 # id 161 not_installable 162 163 # installed 164 165 # installable 166 167 # wanted 168 foo>=2.1,!=3.1,<4 169 170 # resolved 171 DistributionNotFound 172 173 # resolved [replace conflicting] 174 DistributionNotFound 175 ''', 176 177 ''' 178 # id 179 no_matching_version 180 181 # installed 182 183 # installable 184 foo-3.1 185 186 # wanted 187 foo>=2.1,!=3.1,<4 188 189 # resolved 190 DistributionNotFound 191 192 # resolved [replace conflicting] 193 DistributionNotFound 194 ''', 195 196 ''' 197 # id 198 installable_with_installed_conflict 199 200 # installed 201 foo-3.1 202 203 # installable 204 foo-3.5 205 206 # wanted 207 foo>=2.1,!=3.1,<4 208 209 # resolved 210 VersionConflict 211 212 # resolved [replace conflicting] 213 foo-3.5 214 ''', 215 216 ''' 217 # id 218 not_installable_with_installed_conflict 219 220 # installed 221 foo-3.1 222 223 # installable 224 225 # wanted 226 foo>=2.1,!=3.1,<4 227 228 # resolved 229 VersionConflict 230 231 # resolved [replace conflicting] 232 DistributionNotFound 233 ''', 234 235 ''' 236 # id 237 installed_with_installed_require 238 239 # installed 240 foo-3.9 241 baz-0.1 242 foo>=2.1,!=3.1,<4 243 244 # installable 245 246 # wanted 247 baz 248 249 # resolved 250 foo-3.9 251 baz-0.1 252 253 # resolved [replace conflicting] 254 foo-3.9 255 baz-0.1 256 ''', 257 258 ''' 259 # id 260 installed_with_conflicting_installed_require 261 262 # installed 263 foo-5 264 baz-0.1 265 foo>=2.1,!=3.1,<4 266 267 # installable 268 269 # wanted 270 baz 271 272 # resolved 273 VersionConflict 274 275 # resolved [replace conflicting] 276 DistributionNotFound 277 ''', 278 279 ''' 280 # id 281 installed_with_installable_conflicting_require 282 283 # installed 284 foo-5 285 baz-0.1 286 foo>=2.1,!=3.1,<4 287 288 # installable 289 foo-2.9 290 291 # wanted 292 baz 293 294 # resolved 295 VersionConflict 296 297 # resolved [replace conflicting] 298 baz-0.1 299 foo-2.9 300 ''', 301 302 ''' 303 # id 304 installed_with_installable_require 305 306 # installed 307 baz-0.1 308 foo>=2.1,!=3.1,<4 309 310 # installable 311 foo-3.9 312 313 # wanted 314 baz 315 316 # resolved 317 foo-3.9 318 baz-0.1 319 320 # resolved [replace conflicting] 321 foo-3.9 322 baz-0.1 323 ''', 324 325 ''' 326 # id 327 installable_with_installed_require 328 329 # installed 330 foo-3.9 331 332 # installable 333 baz-0.1 334 foo>=2.1,!=3.1,<4 335 336 # wanted 337 baz 338 339 # resolved 340 foo-3.9 341 baz-0.1 342 343 # resolved [replace conflicting] 344 foo-3.9 345 baz-0.1 346 ''', 347 348 ''' 349 # id 350 installable_with_installable_require 351 352 # installed 353 354 # installable 355 foo-3.9 356 baz-0.1 357 foo>=2.1,!=3.1,<4 358 359 # wanted 360 baz 361 362 # resolved 363 foo-3.9 364 baz-0.1 365 366 # resolved [replace conflicting] 367 foo-3.9 368 baz-0.1 369 ''', 370 371 ''' 372 # id 373 installable_with_conflicting_installable_require 374 375 # installed 376 foo-5 377 378 # installable 379 foo-2.9 380 baz-0.1 381 foo>=2.1,!=3.1,<4 382 383 # wanted 384 baz 385 386 # resolved 387 VersionConflict 388 389 # resolved [replace conflicting] 390 baz-0.1 391 foo-2.9 392 ''', 393 394 ''' 395 # id 396 conflicting_installables 397 398 # installed 399 400 # installable 401 foo-2.9 402 foo-5.0 403 404 # wanted 405 foo>=2.1,!=3.1,<4 406 foo>=4 407 408 # resolved 409 VersionConflict 410 411 # resolved [replace conflicting] 412 VersionConflict 413 ''', 414 415 ''' 416 # id 417 installables_with_conflicting_requires 418 419 # installed 420 421 # installable 422 foo-2.9 423 dep==1.0 424 baz-5.0 425 dep==2.0 426 dep-1.0 427 dep-2.0 428 429 # wanted 430 foo 431 baz 432 433 # resolved 434 VersionConflict 435 436 # resolved [replace conflicting] 437 VersionConflict 438 ''', 439 440 ''' 441 # id 442 installables_with_conflicting_nested_requires 443 444 # installed 445 446 # installable 447 foo-2.9 448 dep1 449 dep1-1.0 450 subdep<1.0 451 baz-5.0 452 dep2 453 dep2-1.0 454 subdep>1.0 455 subdep-0.9 456 subdep-1.1 457 458 # wanted 459 foo 460 baz 461 462 # resolved 463 VersionConflict 464 465 # resolved [replace conflicting] 466 VersionConflict 467 ''', 468) 469def test_working_set_resolve(installed_dists, installable_dists, requirements, 470 replace_conflicting, resolved_dists_or_exception): 471 ws = pkg_resources.WorkingSet([]) 472 list(map(ws.add, installed_dists)) 473 resolve_call = functools.partial( 474 ws.resolve, 475 requirements, installer=FakeInstaller(installable_dists), 476 replace_conflicting=replace_conflicting, 477 ) 478 if inspect.isclass(resolved_dists_or_exception): 479 with pytest.raises(resolved_dists_or_exception): 480 resolve_call() 481 else: 482 assert sorted(resolve_call()) == sorted(resolved_dists_or_exception) 483