• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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