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 collections 6import itertools 7import os 8import platform 9import sys 10 11import pytest 12 13from packaging.markers import ( 14 InvalidMarker, 15 Marker, 16 Node, 17 UndefinedComparison, 18 UndefinedEnvironmentName, 19 default_environment, 20 format_full_version, 21) 22 23VARIABLES = [ 24 "extra", 25 "implementation_name", 26 "implementation_version", 27 "os_name", 28 "platform_machine", 29 "platform_release", 30 "platform_system", 31 "platform_version", 32 "python_full_version", 33 "python_version", 34 "platform_python_implementation", 35 "sys_platform", 36] 37 38PEP_345_VARIABLES = [ 39 "os.name", 40 "sys.platform", 41 "platform.version", 42 "platform.machine", 43 "platform.python_implementation", 44] 45 46SETUPTOOLS_VARIABLES = ["python_implementation"] 47 48OPERATORS = ["===", "==", ">=", "<=", "!=", "~=", ">", "<", "in", "not in"] 49 50VALUES = [ 51 "1.0", 52 "5.6a0", 53 "dog", 54 "freebsd", 55 "literally any string can go here", 56 "things @#4 dsfd (((", 57] 58 59 60class TestNode: 61 @pytest.mark.parametrize("value", ["one", "two", None, 3, 5, []]) 62 def test_accepts_value(self, value): 63 assert Node(value).value == value 64 65 @pytest.mark.parametrize("value", ["one", "two", None, 3, 5, []]) 66 def test_str(self, value): 67 assert str(Node(value)) == str(value) 68 69 @pytest.mark.parametrize("value", ["one", "two", None, 3, 5, []]) 70 def test_repr(self, value): 71 assert repr(Node(value)) == f"<Node({str(value)!r})>" 72 73 def test_base_class(self): 74 with pytest.raises(NotImplementedError): 75 Node("cover all the code").serialize() 76 77 78class TestOperatorEvaluation: 79 def test_prefers_pep440(self): 80 assert Marker('"2.7.9" < "foo"').evaluate(dict(foo="2.7.10")) 81 82 def test_falls_back_to_python(self): 83 assert Marker('"b" > "a"').evaluate(dict(a="a")) 84 85 def test_fails_when_undefined(self): 86 with pytest.raises(UndefinedComparison): 87 Marker("'2.7.0' ~= os_name").evaluate() 88 89 90FakeVersionInfo = collections.namedtuple( 91 "FakeVersionInfo", ["major", "minor", "micro", "releaselevel", "serial"] 92) 93 94 95class TestDefaultEnvironment: 96 def test_matches_expected(self): 97 environment = default_environment() 98 99 iver = "{0.major}.{0.minor}.{0.micro}".format(sys.implementation.version) 100 if sys.implementation.version.releaselevel != "final": 101 iver = "{0}{1[0]}{2}".format( 102 iver, 103 sys.implementation.version.releaselevel, 104 sys.implementation.version.serial, 105 ) 106 107 assert environment == { 108 "implementation_name": sys.implementation.name, 109 "implementation_version": iver, 110 "os_name": os.name, 111 "platform_machine": platform.machine(), 112 "platform_release": platform.release(), 113 "platform_system": platform.system(), 114 "platform_version": platform.version(), 115 "python_full_version": platform.python_version(), 116 "platform_python_implementation": platform.python_implementation(), 117 "python_version": ".".join(platform.python_version_tuple()[:2]), 118 "sys_platform": sys.platform, 119 } 120 121 def test_multidigit_minor_version(self, monkeypatch): 122 version_info = (3, 10, 0, "final", 0) 123 monkeypatch.setattr(sys, "version_info", version_info, raising=False) 124 125 monkeypatch.setattr(platform, "python_version", lambda: "3.10.0", raising=False) 126 monkeypatch.setattr( 127 platform, "python_version_tuple", lambda: ("3", "10", "0"), raising=False 128 ) 129 130 environment = default_environment() 131 assert environment["python_version"] == "3.10" 132 133 def tests_when_releaselevel_final(self): 134 v = FakeVersionInfo(3, 4, 2, "final", 0) 135 assert format_full_version(v) == "3.4.2" 136 137 def tests_when_releaselevel_not_final(self): 138 v = FakeVersionInfo(3, 4, 2, "beta", 4) 139 assert format_full_version(v) == "3.4.2b4" 140 141 142class TestMarker: 143 @pytest.mark.parametrize( 144 "marker_string", 145 [ 146 "{} {} {!r}".format(*i) 147 for i in itertools.product(VARIABLES, OPERATORS, VALUES) 148 ] 149 + [ 150 "{2!r} {1} {0}".format(*i) 151 for i in itertools.product(VARIABLES, OPERATORS, VALUES) 152 ], 153 ) 154 def test_parses_valid(self, marker_string): 155 Marker(marker_string) 156 157 @pytest.mark.parametrize( 158 "marker_string", 159 [ 160 "this_isnt_a_real_variable >= '1.0'", 161 "python_version", 162 "(python_version)", 163 "python_version >= 1.0 and (python_version)", 164 ], 165 ) 166 def test_parses_invalid(self, marker_string): 167 with pytest.raises(InvalidMarker): 168 Marker(marker_string) 169 170 @pytest.mark.parametrize( 171 ("marker_string", "expected"), 172 [ 173 # Test the different quoting rules 174 ("python_version == '2.7'", 'python_version == "2.7"'), 175 ('python_version == "2.7"', 'python_version == "2.7"'), 176 # Test and/or expressions 177 ( 178 'python_version == "2.7" and os_name == "linux"', 179 'python_version == "2.7" and os_name == "linux"', 180 ), 181 ( 182 'python_version == "2.7" or os_name == "linux"', 183 'python_version == "2.7" or os_name == "linux"', 184 ), 185 ( 186 'python_version == "2.7" and os_name == "linux" or ' 187 'sys_platform == "win32"', 188 'python_version == "2.7" and os_name == "linux" or ' 189 'sys_platform == "win32"', 190 ), 191 # Test nested expressions and grouping with () 192 ('(python_version == "2.7")', 'python_version == "2.7"'), 193 ( 194 '(python_version == "2.7" and sys_platform == "win32")', 195 'python_version == "2.7" and sys_platform == "win32"', 196 ), 197 ( 198 'python_version == "2.7" and (sys_platform == "win32" or ' 199 'sys_platform == "linux")', 200 'python_version == "2.7" and (sys_platform == "win32" or ' 201 'sys_platform == "linux")', 202 ), 203 ], 204 ) 205 def test_str_and_repr(self, marker_string, expected): 206 m = Marker(marker_string) 207 assert str(m) == expected 208 assert repr(m) == f"<Marker({str(m)!r})>" 209 210 def test_extra_with_no_extra_in_environment(self): 211 # We can't evaluate an extra if no extra is passed into the environment 212 m = Marker("extra == 'security'") 213 with pytest.raises(UndefinedEnvironmentName): 214 m.evaluate() 215 216 @pytest.mark.parametrize( 217 ("marker_string", "environment", "expected"), 218 [ 219 (f"os_name == '{os.name}'", None, True), 220 ("os_name == 'foo'", {"os_name": "foo"}, True), 221 ("os_name == 'foo'", {"os_name": "bar"}, False), 222 ("'2.7' in python_version", {"python_version": "2.7.5"}, True), 223 ("'2.7' not in python_version", {"python_version": "2.7"}, False), 224 ( 225 "os_name == 'foo' and python_version ~= '2.7.0'", 226 {"os_name": "foo", "python_version": "2.7.6"}, 227 True, 228 ), 229 ( 230 "python_version ~= '2.7.0' and (os_name == 'foo' or " 231 "os_name == 'bar')", 232 {"os_name": "foo", "python_version": "2.7.4"}, 233 True, 234 ), 235 ( 236 "python_version ~= '2.7.0' and (os_name == 'foo' or " 237 "os_name == 'bar')", 238 {"os_name": "bar", "python_version": "2.7.4"}, 239 True, 240 ), 241 ( 242 "python_version ~= '2.7.0' and (os_name == 'foo' or " 243 "os_name == 'bar')", 244 {"os_name": "other", "python_version": "2.7.4"}, 245 False, 246 ), 247 ("extra == 'security'", {"extra": "quux"}, False), 248 ("extra == 'security'", {"extra": "security"}, True), 249 ], 250 ) 251 def test_evaluates(self, marker_string, environment, expected): 252 args = [] if environment is None else [environment] 253 assert Marker(marker_string).evaluate(*args) == expected 254 255 @pytest.mark.parametrize( 256 "marker_string", 257 [ 258 "{} {} {!r}".format(*i) 259 for i in itertools.product(PEP_345_VARIABLES, OPERATORS, VALUES) 260 ] 261 + [ 262 "{2!r} {1} {0}".format(*i) 263 for i in itertools.product(PEP_345_VARIABLES, OPERATORS, VALUES) 264 ], 265 ) 266 def test_parses_pep345_valid(self, marker_string): 267 Marker(marker_string) 268 269 @pytest.mark.parametrize( 270 ("marker_string", "environment", "expected"), 271 [ 272 (f"os.name == '{os.name}'", None, True), 273 ("sys.platform == 'win32'", {"sys_platform": "linux2"}, False), 274 ("platform.version in 'Ubuntu'", {"platform_version": "#39"}, False), 275 ("platform.machine=='x86_64'", {"platform_machine": "x86_64"}, True), 276 ( 277 "platform.python_implementation=='Jython'", 278 {"platform_python_implementation": "CPython"}, 279 False, 280 ), 281 ( 282 "python_version == '2.5' and platform.python_implementation" 283 "!= 'Jython'", 284 {"python_version": "2.7"}, 285 False, 286 ), 287 ], 288 ) 289 def test_evaluate_pep345_markers(self, marker_string, environment, expected): 290 args = [] if environment is None else [environment] 291 assert Marker(marker_string).evaluate(*args) == expected 292 293 @pytest.mark.parametrize( 294 "marker_string", 295 [ 296 "{} {} {!r}".format(*i) 297 for i in itertools.product(SETUPTOOLS_VARIABLES, OPERATORS, VALUES) 298 ] 299 + [ 300 "{2!r} {1} {0}".format(*i) 301 for i in itertools.product(SETUPTOOLS_VARIABLES, OPERATORS, VALUES) 302 ], 303 ) 304 def test_parses_setuptools_legacy_valid(self, marker_string): 305 Marker(marker_string) 306 307 def test_evaluate_setuptools_legacy_markers(self): 308 marker_string = "python_implementation=='Jython'" 309 args = [{"platform_python_implementation": "CPython"}] 310 assert Marker(marker_string).evaluate(*args) is False 311