1# This file is dual licensed under the terms of the Apache License, Version
2# 2.0, and the BSD License. See the LICENSE file in the root of this repository
3# for complete details.
4
5import pytest
6
7from packaging.markers import Marker
8from packaging.requirements import URL, URL_AND_MARKER, InvalidRequirement, Requirement
9from packaging.specifiers import SpecifierSet
10
11
12class TestRequirements:
13    def test_string_specifier_marker(self):
14        requirement = 'name[bar]>=3; python_version == "2.7"'
15        req = Requirement(requirement)
16        assert str(req) == requirement
17
18    def test_string_url(self):
19        requirement = "name@ http://foo.com"
20        req = Requirement(requirement)
21        assert str(req) == requirement
22
23    def test_string_url_with_marker(self):
24        requirement = 'name@ http://foo.com ; extra == "feature"'
25        req = Requirement(requirement)
26        assert str(req) == requirement
27
28    def test_repr(self):
29        req = Requirement("name")
30        assert repr(req) == "<Requirement('name')>"
31
32    def _assert_requirement(
33        self, req, name, url=None, extras=[], specifier="", marker=None
34    ):
35        assert req.name == name
36        assert req.url == url
37        assert sorted(req.extras) == sorted(extras)
38        assert str(req.specifier) == specifier
39        if marker:
40            assert str(req.marker) == marker
41        else:
42            assert req.marker is None
43
44    def test_simple_names(self):
45        for name in ("A", "aa", "name"):
46            req = Requirement(name)
47            self._assert_requirement(req, name)
48
49    def test_name_with_other_characters(self):
50        name = "foo-bar.quux_baz"
51        req = Requirement(name)
52        self._assert_requirement(req, name)
53
54    def test_invalid_name(self):
55        with pytest.raises(InvalidRequirement):
56            Requirement("foo!")
57
58    def test_name_with_version(self):
59        req = Requirement("name>=3")
60        self._assert_requirement(req, "name", specifier=">=3")
61
62    def test_with_legacy_version(self):
63        req = Requirement("name==1.0.org1")
64        self._assert_requirement(req, "name", specifier="==1.0.org1")
65
66    def test_with_legacy_version_and_marker(self):
67        req = Requirement("name>=1.x.y;python_version=='2.6'")
68        self._assert_requirement(
69            req, "name", specifier=">=1.x.y", marker='python_version == "2.6"'
70        )
71
72    def test_version_with_parens_and_whitespace(self):
73        req = Requirement("name (==4)")
74        self._assert_requirement(req, "name", specifier="==4")
75
76    def test_name_with_multiple_versions(self):
77        req = Requirement("name>=3,<2")
78        self._assert_requirement(req, "name", specifier="<2,>=3")
79
80    def test_name_with_multiple_versions_and_whitespace(self):
81        req = Requirement("name >=2, <3")
82        self._assert_requirement(req, "name", specifier="<3,>=2")
83
84    def test_extras(self):
85        req = Requirement("foobar [quux,bar]")
86        self._assert_requirement(req, "foobar", extras=["bar", "quux"])
87
88    def test_empty_extras(self):
89        req = Requirement("foo[]")
90        self._assert_requirement(req, "foo")
91
92    def test_url(self):
93        url_section = "@ http://example.com"
94        parsed = URL.parseString(url_section)
95        assert parsed.url == "http://example.com"
96
97    def test_url_and_marker(self):
98        instring = "@ http://example.com ; os_name=='a'"
99        parsed = URL_AND_MARKER.parseString(instring)
100        assert parsed.url == "http://example.com"
101        assert str(parsed.marker) == 'os_name == "a"'
102
103    def test_invalid_url(self):
104        with pytest.raises(InvalidRequirement) as e:
105            Requirement("name @ gopher:/foo/com")
106        assert "Invalid URL: " in str(e.value)
107        assert "gopher:/foo/com" in str(e.value)
108
109    def test_file_url(self):
110        req = Requirement("name @ file:///absolute/path")
111        self._assert_requirement(req, "name", "file:///absolute/path")
112        req = Requirement("name @ file://.")
113        self._assert_requirement(req, "name", "file://.")
114
115    def test_invalid_file_urls(self):
116        with pytest.raises(InvalidRequirement):
117            Requirement("name @ file:.")
118        with pytest.raises(InvalidRequirement):
119            Requirement("name @ file:/.")
120
121    def test_extras_and_url_and_marker(self):
122        req = Requirement("name [fred,bar] @ http://foo.com ; python_version=='2.7'")
123        self._assert_requirement(
124            req,
125            "name",
126            extras=["bar", "fred"],
127            url="http://foo.com",
128            marker='python_version == "2.7"',
129        )
130
131    def test_complex_url_and_marker(self):
132        url = "https://example.com/name;v=1.1/?query=foo&bar=baz#blah"
133        req = Requirement("foo @ %s ; python_version=='3.4'" % url)
134        self._assert_requirement(req, "foo", url=url, marker='python_version == "3.4"')
135
136    def test_multiple_markers(self):
137        req = Requirement(
138            "name[quux, strange];python_version<'2.7' and " "platform_version=='2'"
139        )
140        marker = 'python_version < "2.7" and platform_version == "2"'
141        self._assert_requirement(req, "name", extras=["strange", "quux"], marker=marker)
142
143    def test_multiple_comparison_markers(self):
144        req = Requirement("name; os_name=='a' and os_name=='b' or os_name=='c'")
145        marker = 'os_name == "a" and os_name == "b" or os_name == "c"'
146        self._assert_requirement(req, "name", marker=marker)
147
148    def test_invalid_marker(self):
149        with pytest.raises(InvalidRequirement):
150            Requirement("name; foobar=='x'")
151
152    def test_types(self):
153        req = Requirement("foobar[quux]<2,>=3; os_name=='a'")
154        assert isinstance(req.name, str)
155        assert isinstance(req.extras, set)
156        assert req.url is None
157        assert isinstance(req.specifier, SpecifierSet)
158        assert isinstance(req.marker, Marker)
159
160    def test_types_with_nothing(self):
161        req = Requirement("foobar")
162        assert isinstance(req.name, str)
163        assert isinstance(req.extras, set)
164        assert req.url is None
165        assert isinstance(req.specifier, SpecifierSet)
166        assert req.marker is None
167
168    def test_types_with_url(self):
169        req = Requirement("foobar @ http://foo.com")
170        assert isinstance(req.name, str)
171        assert isinstance(req.extras, set)
172        assert isinstance(req.url, str)
173        assert isinstance(req.specifier, SpecifierSet)
174        assert req.marker is None
175
176    def test_sys_platform_linux_equal(self):
177        req = Requirement('something>=1.2.3; sys_platform == "foo"')
178
179        assert req.name == "something"
180        assert req.marker is not None
181        assert req.marker.evaluate(dict(sys_platform="foo")) is True
182        assert req.marker.evaluate(dict(sys_platform="bar")) is False
183
184    def test_sys_platform_linux_in(self):
185        req = Requirement("aviato>=1.2.3; 'f' in sys_platform")
186
187        assert req.name == "aviato"
188        assert req.marker is not None
189        assert req.marker.evaluate(dict(sys_platform="foo")) is True
190        assert req.marker.evaluate(dict(sys_platform="bar")) is False
191
192    def test_parseexception_error_msg(self):
193        with pytest.raises(InvalidRequirement) as e:
194            Requirement("toto 42")
195        assert "Expected stringEnd" in str(e.value) or (
196            "Expected string_end" in str(e.value)  # pyparsing>=3.0.0
197        )
198