1from __future__ import unicode_literals 2 3import os 4import sys 5import string 6import platform 7import itertools 8 9from pkg_resources.extern.six.moves import map 10 11import pytest 12from pkg_resources.extern import packaging 13 14import pkg_resources 15from pkg_resources import ( 16 parse_requirements, VersionConflict, parse_version, 17 Distribution, EntryPoint, Requirement, safe_version, safe_name, 18 WorkingSet) 19 20 21# from Python 3.6 docs. 22def pairwise(iterable): 23 "s -> (s0,s1), (s1,s2), (s2, s3), ..." 24 a, b = itertools.tee(iterable) 25 next(b, None) 26 return zip(a, b) 27 28 29class Metadata(pkg_resources.EmptyProvider): 30 """Mock object to return metadata as if from an on-disk distribution""" 31 32 def __init__(self, *pairs): 33 self.metadata = dict(pairs) 34 35 def has_metadata(self, name): 36 return name in self.metadata 37 38 def get_metadata(self, name): 39 return self.metadata[name] 40 41 def get_metadata_lines(self, name): 42 return pkg_resources.yield_lines(self.get_metadata(name)) 43 44 45dist_from_fn = pkg_resources.Distribution.from_filename 46 47 48class TestDistro: 49 def testCollection(self): 50 # empty path should produce no distributions 51 ad = pkg_resources.Environment([], platform=None, python=None) 52 assert list(ad) == [] 53 assert ad['FooPkg'] == [] 54 ad.add(dist_from_fn("FooPkg-1.3_1.egg")) 55 ad.add(dist_from_fn("FooPkg-1.4-py2.4-win32.egg")) 56 ad.add(dist_from_fn("FooPkg-1.2-py2.4.egg")) 57 58 # Name is in there now 59 assert ad['FooPkg'] 60 # But only 1 package 61 assert list(ad) == ['foopkg'] 62 63 # Distributions sort by version 64 expected = ['1.4', '1.3-1', '1.2'] 65 assert [dist.version for dist in ad['FooPkg']] == expected 66 67 # Removing a distribution leaves sequence alone 68 ad.remove(ad['FooPkg'][1]) 69 assert [dist.version for dist in ad['FooPkg']] == ['1.4', '1.2'] 70 71 # And inserting adds them in order 72 ad.add(dist_from_fn("FooPkg-1.9.egg")) 73 assert [dist.version for dist in ad['FooPkg']] == ['1.9', '1.4', '1.2'] 74 75 ws = WorkingSet([]) 76 foo12 = dist_from_fn("FooPkg-1.2-py2.4.egg") 77 foo14 = dist_from_fn("FooPkg-1.4-py2.4-win32.egg") 78 req, = parse_requirements("FooPkg>=1.3") 79 80 # Nominal case: no distros on path, should yield all applicable 81 assert ad.best_match(req, ws).version == '1.9' 82 # If a matching distro is already installed, should return only that 83 ws.add(foo14) 84 assert ad.best_match(req, ws).version == '1.4' 85 86 # If the first matching distro is unsuitable, it's a version conflict 87 ws = WorkingSet([]) 88 ws.add(foo12) 89 ws.add(foo14) 90 with pytest.raises(VersionConflict): 91 ad.best_match(req, ws) 92 93 # If more than one match on the path, the first one takes precedence 94 ws = WorkingSet([]) 95 ws.add(foo14) 96 ws.add(foo12) 97 ws.add(foo14) 98 assert ad.best_match(req, ws).version == '1.4' 99 100 def checkFooPkg(self, d): 101 assert d.project_name == "FooPkg" 102 assert d.key == "foopkg" 103 assert d.version == "1.3.post1" 104 assert d.py_version == "2.4" 105 assert d.platform == "win32" 106 assert d.parsed_version == parse_version("1.3-1") 107 108 def testDistroBasics(self): 109 d = Distribution( 110 "/some/path", 111 project_name="FooPkg", 112 version="1.3-1", 113 py_version="2.4", 114 platform="win32", 115 ) 116 self.checkFooPkg(d) 117 118 d = Distribution("/some/path") 119 assert d.py_version == sys.version[:3] 120 assert d.platform is None 121 122 def testDistroParse(self): 123 d = dist_from_fn("FooPkg-1.3.post1-py2.4-win32.egg") 124 self.checkFooPkg(d) 125 d = dist_from_fn("FooPkg-1.3.post1-py2.4-win32.egg-info") 126 self.checkFooPkg(d) 127 128 def testDistroMetadata(self): 129 d = Distribution( 130 "/some/path", project_name="FooPkg", 131 py_version="2.4", platform="win32", 132 metadata=Metadata( 133 ('PKG-INFO', "Metadata-Version: 1.0\nVersion: 1.3-1\n") 134 ), 135 ) 136 self.checkFooPkg(d) 137 138 def distRequires(self, txt): 139 return Distribution("/foo", metadata=Metadata(('depends.txt', txt))) 140 141 def checkRequires(self, dist, txt, extras=()): 142 assert list(dist.requires(extras)) == list(parse_requirements(txt)) 143 144 def testDistroDependsSimple(self): 145 for v in "Twisted>=1.5", "Twisted>=1.5\nZConfig>=2.0": 146 self.checkRequires(self.distRequires(v), v) 147 148 def testResolve(self): 149 ad = pkg_resources.Environment([]) 150 ws = WorkingSet([]) 151 # Resolving no requirements -> nothing to install 152 assert list(ws.resolve([], ad)) == [] 153 # Request something not in the collection -> DistributionNotFound 154 with pytest.raises(pkg_resources.DistributionNotFound): 155 ws.resolve(parse_requirements("Foo"), ad) 156 157 Foo = Distribution.from_filename( 158 "/foo_dir/Foo-1.2.egg", 159 metadata=Metadata(('depends.txt', "[bar]\nBaz>=2.0")) 160 ) 161 ad.add(Foo) 162 ad.add(Distribution.from_filename("Foo-0.9.egg")) 163 164 # Request thing(s) that are available -> list to activate 165 for i in range(3): 166 targets = list(ws.resolve(parse_requirements("Foo"), ad)) 167 assert targets == [Foo] 168 list(map(ws.add, targets)) 169 with pytest.raises(VersionConflict): 170 ws.resolve(parse_requirements("Foo==0.9"), ad) 171 ws = WorkingSet([]) # reset 172 173 # Request an extra that causes an unresolved dependency for "Baz" 174 with pytest.raises(pkg_resources.DistributionNotFound): 175 ws.resolve(parse_requirements("Foo[bar]"), ad) 176 Baz = Distribution.from_filename( 177 "/foo_dir/Baz-2.1.egg", metadata=Metadata(('depends.txt', "Foo")) 178 ) 179 ad.add(Baz) 180 181 # Activation list now includes resolved dependency 182 assert ( 183 list(ws.resolve(parse_requirements("Foo[bar]"), ad)) 184 == [Foo, Baz] 185 ) 186 # Requests for conflicting versions produce VersionConflict 187 with pytest.raises(VersionConflict) as vc: 188 ws.resolve(parse_requirements("Foo==1.2\nFoo!=1.2"), ad) 189 190 msg = 'Foo 0.9 is installed but Foo==1.2 is required' 191 assert vc.value.report() == msg 192 193 def test_environment_marker_evaluation_negative(self): 194 """Environment markers are evaluated at resolution time.""" 195 ad = pkg_resources.Environment([]) 196 ws = WorkingSet([]) 197 res = ws.resolve(parse_requirements("Foo;python_version<'2'"), ad) 198 assert list(res) == [] 199 200 def test_environment_marker_evaluation_positive(self): 201 ad = pkg_resources.Environment([]) 202 ws = WorkingSet([]) 203 Foo = Distribution.from_filename("/foo_dir/Foo-1.2.dist-info") 204 ad.add(Foo) 205 res = ws.resolve(parse_requirements("Foo;python_version>='2'"), ad) 206 assert list(res) == [Foo] 207 208 def test_environment_marker_evaluation_called(self): 209 """ 210 If one package foo requires bar without any extras, 211 markers should pass for bar without extras. 212 """ 213 parent_req, = parse_requirements("foo") 214 req, = parse_requirements("bar;python_version>='2'") 215 req_extras = pkg_resources._ReqExtras({req: parent_req.extras}) 216 assert req_extras.markers_pass(req) 217 218 parent_req, = parse_requirements("foo[]") 219 req, = parse_requirements("bar;python_version>='2'") 220 req_extras = pkg_resources._ReqExtras({req: parent_req.extras}) 221 assert req_extras.markers_pass(req) 222 223 def test_marker_evaluation_with_extras(self): 224 """Extras are also evaluated as markers at resolution time.""" 225 ad = pkg_resources.Environment([]) 226 ws = WorkingSet([]) 227 Foo = Distribution.from_filename( 228 "/foo_dir/Foo-1.2.dist-info", 229 metadata=Metadata(("METADATA", "Provides-Extra: baz\n" 230 "Requires-Dist: quux; extra=='baz'")) 231 ) 232 ad.add(Foo) 233 assert list(ws.resolve(parse_requirements("Foo"), ad)) == [Foo] 234 quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info") 235 ad.add(quux) 236 res = list(ws.resolve(parse_requirements("Foo[baz]"), ad)) 237 assert res == [Foo, quux] 238 239 def test_marker_evaluation_with_extras_normlized(self): 240 """Extras are also evaluated as markers at resolution time.""" 241 ad = pkg_resources.Environment([]) 242 ws = WorkingSet([]) 243 Foo = Distribution.from_filename( 244 "/foo_dir/Foo-1.2.dist-info", 245 metadata=Metadata(("METADATA", "Provides-Extra: baz-lightyear\n" 246 "Requires-Dist: quux; extra=='baz-lightyear'")) 247 ) 248 ad.add(Foo) 249 assert list(ws.resolve(parse_requirements("Foo"), ad)) == [Foo] 250 quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info") 251 ad.add(quux) 252 res = list(ws.resolve(parse_requirements("Foo[baz-lightyear]"), ad)) 253 assert res == [Foo, quux] 254 255 def test_marker_evaluation_with_multiple_extras(self): 256 ad = pkg_resources.Environment([]) 257 ws = WorkingSet([]) 258 Foo = Distribution.from_filename( 259 "/foo_dir/Foo-1.2.dist-info", 260 metadata=Metadata(("METADATA", "Provides-Extra: baz\n" 261 "Requires-Dist: quux; extra=='baz'\n" 262 "Provides-Extra: bar\n" 263 "Requires-Dist: fred; extra=='bar'\n")) 264 ) 265 ad.add(Foo) 266 quux = Distribution.from_filename("/foo_dir/quux-1.0.dist-info") 267 ad.add(quux) 268 fred = Distribution.from_filename("/foo_dir/fred-0.1.dist-info") 269 ad.add(fred) 270 res = list(ws.resolve(parse_requirements("Foo[baz,bar]"), ad)) 271 assert sorted(res) == [fred, quux, Foo] 272 273 def test_marker_evaluation_with_extras_loop(self): 274 ad = pkg_resources.Environment([]) 275 ws = WorkingSet([]) 276 a = Distribution.from_filename( 277 "/foo_dir/a-0.2.dist-info", 278 metadata=Metadata(("METADATA", "Requires-Dist: c[a]")) 279 ) 280 b = Distribution.from_filename( 281 "/foo_dir/b-0.3.dist-info", 282 metadata=Metadata(("METADATA", "Requires-Dist: c[b]")) 283 ) 284 c = Distribution.from_filename( 285 "/foo_dir/c-1.0.dist-info", 286 metadata=Metadata(("METADATA", "Provides-Extra: a\n" 287 "Requires-Dist: b;extra=='a'\n" 288 "Provides-Extra: b\n" 289 "Requires-Dist: foo;extra=='b'")) 290 ) 291 foo = Distribution.from_filename("/foo_dir/foo-0.1.dist-info") 292 for dist in (a, b, c, foo): 293 ad.add(dist) 294 res = list(ws.resolve(parse_requirements("a"), ad)) 295 assert res == [a, c, b, foo] 296 297 def testDistroDependsOptions(self): 298 d = self.distRequires(""" 299 Twisted>=1.5 300 [docgen] 301 ZConfig>=2.0 302 docutils>=0.3 303 [fastcgi] 304 fcgiapp>=0.1""") 305 self.checkRequires(d, "Twisted>=1.5") 306 self.checkRequires( 307 d, "Twisted>=1.5 ZConfig>=2.0 docutils>=0.3".split(), ["docgen"] 308 ) 309 self.checkRequires( 310 d, "Twisted>=1.5 fcgiapp>=0.1".split(), ["fastcgi"] 311 ) 312 self.checkRequires( 313 d, "Twisted>=1.5 ZConfig>=2.0 docutils>=0.3 fcgiapp>=0.1".split(), 314 ["docgen", "fastcgi"] 315 ) 316 self.checkRequires( 317 d, "Twisted>=1.5 fcgiapp>=0.1 ZConfig>=2.0 docutils>=0.3".split(), 318 ["fastcgi", "docgen"] 319 ) 320 with pytest.raises(pkg_resources.UnknownExtra): 321 d.requires(["foo"]) 322 323 324class TestWorkingSet: 325 def test_find_conflicting(self): 326 ws = WorkingSet([]) 327 Foo = Distribution.from_filename("/foo_dir/Foo-1.2.egg") 328 ws.add(Foo) 329 330 # create a requirement that conflicts with Foo 1.2 331 req = next(parse_requirements("Foo<1.2")) 332 333 with pytest.raises(VersionConflict) as vc: 334 ws.find(req) 335 336 msg = 'Foo 1.2 is installed but Foo<1.2 is required' 337 assert vc.value.report() == msg 338 339 def test_resolve_conflicts_with_prior(self): 340 """ 341 A ContextualVersionConflict should be raised when a requirement 342 conflicts with a prior requirement for a different package. 343 """ 344 # Create installation where Foo depends on Baz 1.0 and Bar depends on 345 # Baz 2.0. 346 ws = WorkingSet([]) 347 md = Metadata(('depends.txt', "Baz==1.0")) 348 Foo = Distribution.from_filename("/foo_dir/Foo-1.0.egg", metadata=md) 349 ws.add(Foo) 350 md = Metadata(('depends.txt', "Baz==2.0")) 351 Bar = Distribution.from_filename("/foo_dir/Bar-1.0.egg", metadata=md) 352 ws.add(Bar) 353 Baz = Distribution.from_filename("/foo_dir/Baz-1.0.egg") 354 ws.add(Baz) 355 Baz = Distribution.from_filename("/foo_dir/Baz-2.0.egg") 356 ws.add(Baz) 357 358 with pytest.raises(VersionConflict) as vc: 359 ws.resolve(parse_requirements("Foo\nBar\n")) 360 361 msg = "Baz 1.0 is installed but Baz==2.0 is required by " 362 msg += repr(set(['Bar'])) 363 assert vc.value.report() == msg 364 365 366class TestEntryPoints: 367 def assertfields(self, ep): 368 assert ep.name == "foo" 369 assert ep.module_name == "pkg_resources.tests.test_resources" 370 assert ep.attrs == ("TestEntryPoints",) 371 assert ep.extras == ("x",) 372 assert ep.load() is TestEntryPoints 373 expect = "foo = pkg_resources.tests.test_resources:TestEntryPoints [x]" 374 assert str(ep) == expect 375 376 def setup_method(self, method): 377 self.dist = Distribution.from_filename( 378 "FooPkg-1.2-py2.4.egg", metadata=Metadata(('requires.txt', '[x]'))) 379 380 def testBasics(self): 381 ep = EntryPoint( 382 "foo", "pkg_resources.tests.test_resources", ["TestEntryPoints"], 383 ["x"], self.dist 384 ) 385 self.assertfields(ep) 386 387 def testParse(self): 388 s = "foo = pkg_resources.tests.test_resources:TestEntryPoints [x]" 389 ep = EntryPoint.parse(s, self.dist) 390 self.assertfields(ep) 391 392 ep = EntryPoint.parse("bar baz= spammity[PING]") 393 assert ep.name == "bar baz" 394 assert ep.module_name == "spammity" 395 assert ep.attrs == () 396 assert ep.extras == ("ping",) 397 398 ep = EntryPoint.parse(" fizzly = wocka:foo") 399 assert ep.name == "fizzly" 400 assert ep.module_name == "wocka" 401 assert ep.attrs == ("foo",) 402 assert ep.extras == () 403 404 # plus in the name 405 spec = "html+mako = mako.ext.pygmentplugin:MakoHtmlLexer" 406 ep = EntryPoint.parse(spec) 407 assert ep.name == 'html+mako' 408 409 reject_specs = "foo", "x=a:b:c", "q=x/na", "fez=pish:tush-z", "x=f[a]>2" 410 411 @pytest.mark.parametrize("reject_spec", reject_specs) 412 def test_reject_spec(self, reject_spec): 413 with pytest.raises(ValueError): 414 EntryPoint.parse(reject_spec) 415 416 def test_printable_name(self): 417 """ 418 Allow any printable character in the name. 419 """ 420 # Create a name with all printable characters; strip the whitespace. 421 name = string.printable.strip() 422 spec = "{name} = module:attr".format(**locals()) 423 ep = EntryPoint.parse(spec) 424 assert ep.name == name 425 426 def checkSubMap(self, m): 427 assert len(m) == len(self.submap_expect) 428 for key, ep in self.submap_expect.items(): 429 assert m.get(key).name == ep.name 430 assert m.get(key).module_name == ep.module_name 431 assert sorted(m.get(key).attrs) == sorted(ep.attrs) 432 assert sorted(m.get(key).extras) == sorted(ep.extras) 433 434 submap_expect = dict( 435 feature1=EntryPoint('feature1', 'somemodule', ['somefunction']), 436 feature2=EntryPoint( 437 'feature2', 'another.module', ['SomeClass'], ['extra1', 'extra2']), 438 feature3=EntryPoint('feature3', 'this.module', extras=['something']) 439 ) 440 submap_str = """ 441 # define features for blah blah 442 feature1 = somemodule:somefunction 443 feature2 = another.module:SomeClass [extra1,extra2] 444 feature3 = this.module [something] 445 """ 446 447 def testParseList(self): 448 self.checkSubMap(EntryPoint.parse_group("xyz", self.submap_str)) 449 with pytest.raises(ValueError): 450 EntryPoint.parse_group("x a", "foo=bar") 451 with pytest.raises(ValueError): 452 EntryPoint.parse_group("x", ["foo=baz", "foo=bar"]) 453 454 def testParseMap(self): 455 m = EntryPoint.parse_map({'xyz': self.submap_str}) 456 self.checkSubMap(m['xyz']) 457 assert list(m.keys()) == ['xyz'] 458 m = EntryPoint.parse_map("[xyz]\n" + self.submap_str) 459 self.checkSubMap(m['xyz']) 460 assert list(m.keys()) == ['xyz'] 461 with pytest.raises(ValueError): 462 EntryPoint.parse_map(["[xyz]", "[xyz]"]) 463 with pytest.raises(ValueError): 464 EntryPoint.parse_map(self.submap_str) 465 466 467class TestRequirements: 468 def testBasics(self): 469 r = Requirement.parse("Twisted>=1.2") 470 assert str(r) == "Twisted>=1.2" 471 assert repr(r) == "Requirement.parse('Twisted>=1.2')" 472 assert r == Requirement("Twisted>=1.2") 473 assert r == Requirement("twisTed>=1.2") 474 assert r != Requirement("Twisted>=2.0") 475 assert r != Requirement("Zope>=1.2") 476 assert r != Requirement("Zope>=3.0") 477 assert r != Requirement("Twisted[extras]>=1.2") 478 479 def testOrdering(self): 480 r1 = Requirement("Twisted==1.2c1,>=1.2") 481 r2 = Requirement("Twisted>=1.2,==1.2c1") 482 assert r1 == r2 483 assert str(r1) == str(r2) 484 assert str(r2) == "Twisted==1.2c1,>=1.2" 485 486 def testBasicContains(self): 487 r = Requirement("Twisted>=1.2") 488 foo_dist = Distribution.from_filename("FooPkg-1.3_1.egg") 489 twist11 = Distribution.from_filename("Twisted-1.1.egg") 490 twist12 = Distribution.from_filename("Twisted-1.2.egg") 491 assert parse_version('1.2') in r 492 assert parse_version('1.1') not in r 493 assert '1.2' in r 494 assert '1.1' not in r 495 assert foo_dist not in r 496 assert twist11 not in r 497 assert twist12 in r 498 499 def testOptionsAndHashing(self): 500 r1 = Requirement.parse("Twisted[foo,bar]>=1.2") 501 r2 = Requirement.parse("Twisted[bar,FOO]>=1.2") 502 assert r1 == r2 503 assert set(r1.extras) == set(("foo", "bar")) 504 assert set(r2.extras) == set(("foo", "bar")) 505 assert hash(r1) == hash(r2) 506 assert ( 507 hash(r1) 508 == 509 hash(( 510 "twisted", 511 packaging.specifiers.SpecifierSet(">=1.2"), 512 frozenset(["foo", "bar"]), 513 None 514 )) 515 ) 516 517 def testVersionEquality(self): 518 r1 = Requirement.parse("foo==0.3a2") 519 r2 = Requirement.parse("foo!=0.3a4") 520 d = Distribution.from_filename 521 522 assert d("foo-0.3a4.egg") not in r1 523 assert d("foo-0.3a1.egg") not in r1 524 assert d("foo-0.3a4.egg") not in r2 525 526 assert d("foo-0.3a2.egg") in r1 527 assert d("foo-0.3a2.egg") in r2 528 assert d("foo-0.3a3.egg") in r2 529 assert d("foo-0.3a5.egg") in r2 530 531 def testSetuptoolsProjectName(self): 532 """ 533 The setuptools project should implement the setuptools package. 534 """ 535 536 assert ( 537 Requirement.parse('setuptools').project_name == 'setuptools') 538 # setuptools 0.7 and higher means setuptools. 539 assert ( 540 Requirement.parse('setuptools == 0.7').project_name 541 == 'setuptools' 542 ) 543 assert ( 544 Requirement.parse('setuptools == 0.7a1').project_name 545 == 'setuptools' 546 ) 547 assert ( 548 Requirement.parse('setuptools >= 0.7').project_name 549 == 'setuptools' 550 ) 551 552 553class TestParsing: 554 def testEmptyParse(self): 555 assert list(parse_requirements('')) == [] 556 557 def testYielding(self): 558 for inp, out in [ 559 ([], []), ('x', ['x']), ([[]], []), (' x\n y', ['x', 'y']), 560 (['x\n\n', 'y'], ['x', 'y']), 561 ]: 562 assert list(pkg_resources.yield_lines(inp)) == out 563 564 def testSplitting(self): 565 sample = """ 566 x 567 [Y] 568 z 569 570 a 571 [b ] 572 # foo 573 c 574 [ d] 575 [q] 576 v 577 """ 578 assert ( 579 list(pkg_resources.split_sections(sample)) 580 == 581 [ 582 (None, ["x"]), 583 ("Y", ["z", "a"]), 584 ("b", ["c"]), 585 ("d", []), 586 ("q", ["v"]), 587 ] 588 ) 589 with pytest.raises(ValueError): 590 list(pkg_resources.split_sections("[foo")) 591 592 def testSafeName(self): 593 assert safe_name("adns-python") == "adns-python" 594 assert safe_name("WSGI Utils") == "WSGI-Utils" 595 assert safe_name("WSGI Utils") == "WSGI-Utils" 596 assert safe_name("Money$$$Maker") == "Money-Maker" 597 assert safe_name("peak.web") != "peak-web" 598 599 def testSafeVersion(self): 600 assert safe_version("1.2-1") == "1.2.post1" 601 assert safe_version("1.2 alpha") == "1.2.alpha" 602 assert safe_version("2.3.4 20050521") == "2.3.4.20050521" 603 assert safe_version("Money$$$Maker") == "Money-Maker" 604 assert safe_version("peak.web") == "peak.web" 605 606 def testSimpleRequirements(self): 607 assert ( 608 list(parse_requirements('Twis-Ted>=1.2-1')) 609 == 610 [Requirement('Twis-Ted>=1.2-1')] 611 ) 612 assert ( 613 list(parse_requirements('Twisted >=1.2, \\ # more\n<2.0')) 614 == 615 [Requirement('Twisted>=1.2,<2.0')] 616 ) 617 assert ( 618 Requirement.parse("FooBar==1.99a3") 619 == 620 Requirement("FooBar==1.99a3") 621 ) 622 with pytest.raises(ValueError): 623 Requirement.parse(">=2.3") 624 with pytest.raises(ValueError): 625 Requirement.parse("x\\") 626 with pytest.raises(ValueError): 627 Requirement.parse("x==2 q") 628 with pytest.raises(ValueError): 629 Requirement.parse("X==1\nY==2") 630 with pytest.raises(ValueError): 631 Requirement.parse("#") 632 633 def test_requirements_with_markers(self): 634 assert ( 635 Requirement.parse("foobar;os_name=='a'") 636 == 637 Requirement.parse("foobar;os_name=='a'") 638 ) 639 assert ( 640 Requirement.parse("name==1.1;python_version=='2.7'") 641 != 642 Requirement.parse("name==1.1;python_version=='3.3'") 643 ) 644 assert ( 645 Requirement.parse("name==1.0;python_version=='2.7'") 646 != 647 Requirement.parse("name==1.2;python_version=='2.7'") 648 ) 649 assert ( 650 Requirement.parse("name[foo]==1.0;python_version=='3.3'") 651 != 652 Requirement.parse("name[foo,bar]==1.0;python_version=='3.3'") 653 ) 654 655 def test_local_version(self): 656 req, = parse_requirements('foo==1.0.org1') 657 658 def test_spaces_between_multiple_versions(self): 659 req, = parse_requirements('foo>=1.0, <3') 660 req, = parse_requirements('foo >= 1.0, < 3') 661 662 @pytest.mark.parametrize( 663 ['lower', 'upper'], 664 [ 665 ('1.2-rc1', '1.2rc1'), 666 ('0.4', '0.4.0'), 667 ('0.4.0.0', '0.4.0'), 668 ('0.4.0-0', '0.4-0'), 669 ('0post1', '0.0post1'), 670 ('0pre1', '0.0c1'), 671 ('0.0.0preview1', '0c1'), 672 ('0.0c1', '0-rc1'), 673 ('1.2a1', '1.2.a.1'), 674 ('1.2.a', '1.2a'), 675 ], 676 ) 677 def testVersionEquality(self, lower, upper): 678 assert parse_version(lower) == parse_version(upper) 679 680 torture = """ 681 0.80.1-3 0.80.1-2 0.80.1-1 0.79.9999+0.80.0pre4-1 682 0.79.9999+0.80.0pre2-3 0.79.9999+0.80.0pre2-2 683 0.77.2-1 0.77.1-1 0.77.0-1 684 """ 685 686 @pytest.mark.parametrize( 687 ['lower', 'upper'], 688 [ 689 ('2.1', '2.1.1'), 690 ('2a1', '2b0'), 691 ('2a1', '2.1'), 692 ('2.3a1', '2.3'), 693 ('2.1-1', '2.1-2'), 694 ('2.1-1', '2.1.1'), 695 ('2.1', '2.1post4'), 696 ('2.1a0-20040501', '2.1'), 697 ('1.1', '02.1'), 698 ('3.2', '3.2.post0'), 699 ('3.2post1', '3.2post2'), 700 ('0.4', '4.0'), 701 ('0.0.4', '0.4.0'), 702 ('0post1', '0.4post1'), 703 ('2.1.0-rc1', '2.1.0'), 704 ('2.1dev', '2.1a0'), 705 ] + list(pairwise(reversed(torture.split()))), 706 ) 707 def testVersionOrdering(self, lower, upper): 708 assert parse_version(lower) < parse_version(upper) 709 710 def testVersionHashable(self): 711 """ 712 Ensure that our versions stay hashable even though we've subclassed 713 them and added some shim code to them. 714 """ 715 assert ( 716 hash(parse_version("1.0")) 717 == 718 hash(parse_version("1.0")) 719 ) 720 721 722class TestNamespaces: 723 724 ns_str = "__import__('pkg_resources').declare_namespace(__name__)\n" 725 726 @pytest.yield_fixture 727 def symlinked_tmpdir(self, tmpdir): 728 """ 729 Where available, return the tempdir as a symlink, 730 which as revealed in #231 is more fragile than 731 a natural tempdir. 732 """ 733 if not hasattr(os, 'symlink'): 734 yield str(tmpdir) 735 return 736 737 link_name = str(tmpdir) + '-linked' 738 os.symlink(str(tmpdir), link_name) 739 try: 740 yield type(tmpdir)(link_name) 741 finally: 742 os.unlink(link_name) 743 744 @pytest.yield_fixture(autouse=True) 745 def patched_path(self, tmpdir): 746 """ 747 Patch sys.path to include the 'site-pkgs' dir. Also 748 restore pkg_resources._namespace_packages to its 749 former state. 750 """ 751 saved_ns_pkgs = pkg_resources._namespace_packages.copy() 752 saved_sys_path = sys.path[:] 753 site_pkgs = tmpdir.mkdir('site-pkgs') 754 sys.path.append(str(site_pkgs)) 755 try: 756 yield 757 finally: 758 pkg_resources._namespace_packages = saved_ns_pkgs 759 sys.path = saved_sys_path 760 761 issue591 = pytest.mark.xfail(platform.system() == 'Windows', reason="#591") 762 763 @issue591 764 def test_two_levels_deep(self, symlinked_tmpdir): 765 """ 766 Test nested namespace packages 767 Create namespace packages in the following tree : 768 site-packages-1/pkg1/pkg2 769 site-packages-2/pkg1/pkg2 770 Check both are in the _namespace_packages dict and that their __path__ 771 is correct 772 """ 773 real_tmpdir = symlinked_tmpdir.realpath() 774 tmpdir = symlinked_tmpdir 775 sys.path.append(str(tmpdir / 'site-pkgs2')) 776 site_dirs = tmpdir / 'site-pkgs', tmpdir / 'site-pkgs2' 777 for site in site_dirs: 778 pkg1 = site / 'pkg1' 779 pkg2 = pkg1 / 'pkg2' 780 pkg2.ensure_dir() 781 (pkg1 / '__init__.py').write_text(self.ns_str, encoding='utf-8') 782 (pkg2 / '__init__.py').write_text(self.ns_str, encoding='utf-8') 783 import pkg1 784 assert "pkg1" in pkg_resources._namespace_packages 785 # attempt to import pkg2 from site-pkgs2 786 import pkg1.pkg2 787 # check the _namespace_packages dict 788 assert "pkg1.pkg2" in pkg_resources._namespace_packages 789 assert pkg_resources._namespace_packages["pkg1"] == ["pkg1.pkg2"] 790 # check the __path__ attribute contains both paths 791 expected = [ 792 str(real_tmpdir / "site-pkgs" / "pkg1" / "pkg2"), 793 str(real_tmpdir / "site-pkgs2" / "pkg1" / "pkg2"), 794 ] 795 assert pkg1.pkg2.__path__ == expected 796 797 @issue591 798 def test_path_order(self, symlinked_tmpdir): 799 """ 800 Test that if multiple versions of the same namespace package subpackage 801 are on different sys.path entries, that only the one earliest on 802 sys.path is imported, and that the namespace package's __path__ is in 803 the correct order. 804 805 Regression test for https://github.com/pypa/setuptools/issues/207 806 """ 807 808 tmpdir = symlinked_tmpdir 809 site_dirs = ( 810 tmpdir / "site-pkgs", 811 tmpdir / "site-pkgs2", 812 tmpdir / "site-pkgs3", 813 ) 814 815 vers_str = "__version__ = %r" 816 817 for number, site in enumerate(site_dirs, 1): 818 if number > 1: 819 sys.path.append(str(site)) 820 nspkg = site / 'nspkg' 821 subpkg = nspkg / 'subpkg' 822 subpkg.ensure_dir() 823 (nspkg / '__init__.py').write_text(self.ns_str, encoding='utf-8') 824 (subpkg / '__init__.py').write_text( 825 vers_str % number, encoding='utf-8') 826 827 import nspkg.subpkg 828 import nspkg 829 expected = [ 830 str(site.realpath() / 'nspkg') 831 for site in site_dirs 832 ] 833 assert nspkg.__path__ == expected 834 assert nspkg.subpkg.__version__ == 1 835