blob: 7843f88e89701bbab098658eb811cddbcb2e2381 [file] [log] [blame]
# Copyright 2024 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for construction of Python version matching config settings."""
load("@rules_testing//lib:test_suite.bzl", "test_suite")
load("//python/private/pypi:pep508_env.bzl", pep508_env = "env") # buildifier: disable=bzl-visibility
load("//python/private/pypi:pep508_evaluate.bzl", "evaluate", "tokenize") # buildifier: disable=bzl-visibility
_tests = []
def _check_evaluate(env, expr, expected, values, strict = True):
env.expect.where(
expression = expr,
values = values,
).that_bool(evaluate(expr, env = values, strict = strict)).equals(expected)
def _tokenize_tests(env):
for input, want in {
"": [],
"'osx' == os_name": ['"osx"', "==", "os_name"],
"'x' not in os_name": ['"x"', "not in", "os_name"],
"()": ["(", ")"],
"(os_name == 'osx' and not os_name == 'posix') or os_name == \"win\"": [
"(",
"os_name",
"==",
'"osx"',
"and",
"not",
"os_name",
"==",
'"posix"',
")",
"or",
"os_name",
"==",
'"win"',
],
"os_name\t==\t'osx'": ["os_name", "==", '"osx"'],
"os_name == 'osx'": ["os_name", "==", '"osx"'],
"python_version <= \"1.0\"": ["python_version", "<=", '"1.0"'],
"python_version>='1.0.0'": ["python_version", ">=", '"1.0.0"'],
"python_version~='1.0.0'": ["python_version", "~=", '"1.0.0"'],
}.items():
got = tokenize(input)
env.expect.that_collection(got).contains_exactly(want).in_order()
_tests.append(_tokenize_tests)
def _evaluate_non_version_env_tests(env):
for var_name in [
"implementation_name",
"os_name",
"platform_machine",
"platform_python_implementation",
"platform_release",
"platform_system",
"sys_platform",
"extra",
]:
# Given
marker_env = {var_name: "osx"}
# When
for input, want in {
"'osx' != {}".format(var_name): False,
"'osx' < {}".format(var_name): False,
"'osx' <= {}".format(var_name): True,
"'osx' == {}".format(var_name): True,
"'osx' >= {}".format(var_name): True,
"'w' not in {}".format(var_name): True,
"'x' in {}".format(var_name): True,
"{} != 'osx'".format(var_name): False,
"{} < 'osx'".format(var_name): False,
"{} <= 'osx'".format(var_name): True,
"{} == 'osx'".format(var_name): True,
"{} > 'osx'".format(var_name): False,
"{} >= 'osx'".format(var_name): True,
}.items():
_check_evaluate(env, input, want, marker_env)
# Check that the non-strict eval gives us back the input when no
# env is supplied.
_check_evaluate(env, input, input.replace("'", '"'), {}, strict = False)
_tests.append(_evaluate_non_version_env_tests)
def _evaluate_version_env_tests(env):
for var_name in [
"python_version",
"implementation_version",
"platform_version",
"python_full_version",
]:
# Given
marker_env = {var_name: "3.7.9"}
# When
for input, want in {
"{} < '3.8'".format(var_name): True,
"{} > '3.7'".format(var_name): True,
"{} >= '3.7.9'".format(var_name): True,
"{} >= '3.7.10'".format(var_name): False,
"{} >= '3.7.8'".format(var_name): True,
"{} <= '3.7.9'".format(var_name): True,
"{} <= '3.7.10'".format(var_name): True,
"{} <= '3.7.8'".format(var_name): False,
"{} == '3.7.9'".format(var_name): True,
"{} == '3.7.*'".format(var_name): True,
"{} != '3.7.9'".format(var_name): False,
"{} ~= '3.7.1'".format(var_name): True,
"{} ~= '3.7.10'".format(var_name): False,
"{} ~= '3.8.0'".format(var_name): False,
"{} === '3.7.9+rc2'".format(var_name): False,
"{} === '3.7.9'".format(var_name): True,
"{} == '3.7.9+rc2'".format(var_name): True,
}.items(): # buildifier: @unsorted-dict-items
_check_evaluate(env, input, want, marker_env)
# Check that the non-strict eval gives us back the input when no
# env is supplied.
_check_evaluate(env, input, input.replace("'", '"'), {}, strict = False)
_tests.append(_evaluate_version_env_tests)
def _evaluate_platform_version_is_special(env):
# Given
marker_env = {"platform_version": "FooBar Linux v1.2.3"}
# When the platform version is not
input = "platform_version == '0'"
_check_evaluate(env, input, False, marker_env)
# And when I compare it as string
input = "'FooBar' in platform_version"
_check_evaluate(env, input, True, marker_env)
# Check that the non-strict eval gives us back the input when no
# env is supplied.
_check_evaluate(env, input, input.replace("'", '"'), {}, strict = False)
_tests.append(_evaluate_platform_version_is_special)
def _logical_expression_tests(env):
for input, want in {
# Basic
"": True,
"(())": True,
"()": True,
# expr
"os_name == 'fo'": False,
"(os_name == 'fo')": False,
"((os_name == 'fo'))": False,
"((os_name == 'foo'))": True,
"not (os_name == 'fo')": True,
# and
"os_name == 'fo' and os_name == 'foo'": False,
# and not
"os_name == 'fo' and not os_name == 'foo'": False,
# or
"os_name == 'oo' or os_name == 'foo'": True,
# or not
"os_name == 'foo' or not os_name == 'foo'": True,
# multiple or
"os_name == 'oo' or os_name == 'fo' or os_name == 'foo'": True,
"os_name == 'oo' or os_name == 'foo' or os_name == 'fo'": True,
# multiple and
"os_name == 'foo' and os_name == 'foo' and os_name == 'fo'": False,
# x or not y and z != (x or not y), but is instead evaluated as x or (not y and z)
"os_name == 'foo' or not os_name == 'fo' and os_name == 'fo'": True,
# x or y and z != (x or y) and z, but is instead evaluated as x or (y and z)
"os_name == 'foo' or os_name == 'fo' and os_name == 'fo'": True,
"not (os_name == 'foo' or os_name == 'fo' and os_name == 'fo')": False,
# x or y and z and w != (x or y and z) and w, but is instead evaluated as x or (y and z and w)
"os_name == 'foo' or os_name == 'fo' and os_name == 'fo' and os_name == 'fo'": True,
# not not True
"not not os_name == 'foo'": True,
"not not not os_name == 'foo'": False,
}.items(): # buildifier: @unsorted-dict-items
_check_evaluate(env, input, want, {"os_name": "foo"})
if not input.strip("()"):
# These cases will just return True, because they will be evaluated
# and the brackets will be processed.
continue
# Check that the non-strict eval gives us back the input when no env
# is supplied.
_check_evaluate(env, input, input.replace("'", '"'), {}, strict = False)
_tests.append(_logical_expression_tests)
def _evaluate_partial_only_extra(env):
# Given
extra = "foo"
# When
for input, want in {
"os_name == 'osx' and extra == 'bar'": False,
"os_name == 'osx' and extra == 'foo'": "os_name == \"osx\"",
"platform_system == 'aarch64' and os_name == 'osx' and extra == 'foo'": "platform_system == \"aarch64\" and os_name == \"osx\"",
"platform_system == 'aarch64' and extra == 'foo' and os_name == 'osx'": "platform_system == \"aarch64\" and os_name == \"osx\"",
"os_name == 'osx' or extra == 'bar'": "os_name == \"osx\"",
"os_name == 'osx' or extra == 'foo'": "",
"extra == 'bar' or os_name == 'osx'": "os_name == \"osx\"",
"extra == 'foo' or os_name == 'osx'": "",
"os_name == 'win' or extra == 'bar' or os_name == 'osx'": "os_name == \"win\" or os_name == \"osx\"",
"os_name == 'win' or extra == 'foo' or os_name == 'osx'": "",
}.items(): # buildifier: @unsorted-dict-items
got = evaluate(
input,
env = {
"extra": extra,
},
strict = False,
)
env.expect.that_bool(got).equals(want)
_check_evaluate(env, input, want, {"extra": extra}, strict = False)
_tests.append(_evaluate_partial_only_extra)
def _evaluate_with_aliases(env):
# When
for (os, cpu), tests in {
# buildifier: @unsorted-dict-items
("osx", "aarch64"): {
"platform_system == 'Darwin' and platform_machine == 'arm64'": True,
"platform_system == 'Darwin' and platform_machine == 'aarch64'": True,
"platform_system == 'Darwin' and platform_machine == 'amd64'": False,
},
("osx", "x86_64"): {
"platform_system == 'Darwin' and platform_machine == 'amd64'": True,
"platform_system == 'Darwin' and platform_machine == 'x86_64'": True,
},
("osx", "x86_32"): {
"platform_system == 'Darwin' and platform_machine == 'i386'": True,
"platform_system == 'Darwin' and platform_machine == 'i686'": True,
"platform_system == 'Darwin' and platform_machine == 'x86_32'": True,
"platform_system == 'Darwin' and platform_machine == 'x86_64'": False,
},
("freebsd", "x86_32"): {
"platform_system == 'FreeBSD' and platform_machine == 'i386'": True,
"platform_system == 'FreeBSD' and platform_machine == 'i686'": True,
"platform_system == 'FreeBSD' and platform_machine == 'x86_32'": True,
"platform_system == 'FreeBSD' and platform_machine == 'x86_64'": False,
"platform_system == 'FreeBSD' and os_name == 'posix'": True,
},
}.items(): # buildifier: @unsorted-dict-items
for input, want in tests.items():
_check_evaluate(env, input, want, pep508_env(
os = os,
arch = cpu,
python_version = "3.2",
))
_tests.append(_evaluate_with_aliases)
def _expr_case(expr, want, env):
return struct(expr = expr.strip(), want = want, env = env)
_MISC_EXPRESSIONS = [
_expr_case('python_version == "3.*"', True, {"python_version": "3.10.1"}),
_expr_case('python_version != "3.10.*"', False, {"python_version": "3.10.1"}),
_expr_case('python_version != "3.11.*"', True, {"python_version": "3.10.1"}),
_expr_case('python_version != "3.10"', False, {"python_version": "3.10.0"}),
_expr_case('python_version == "3.10"', True, {"python_version": "3.10.0"}),
# Cases for the '>' operator
# Taken from spec: https://peps.python.org/pep-0440/#exclusive-ordered-comparison
_expr_case('python_version > "1.7"', True, {"python_version": "1.7.1"}),
_expr_case('python_version > "1.7"', False, {"python_version": "1.7.0.post0"}),
_expr_case('python_version > "1.7"', True, {"python_version": "1.7.1"}),
_expr_case('python_version > "1.7.post2"', True, {"python_version": "1.7.1"}),
_expr_case('python_version > "1.7.post2"', True, {"python_version": "1.7.post3"}),
_expr_case('python_version > "1.7.post2"', False, {"python_version": "1.7.0"}),
_expr_case('python_version > "1.7.1+local"', False, {"python_version": "1.7.1"}),
_expr_case('python_version > "1.7.1+local"', True, {"python_version": "1.7.2"}),
# Extra cases for the '<' operator
_expr_case('python_version < "1.7.1"', False, {"python_version": "1.7.2"}),
_expr_case('python_version < "1.7.3"', True, {"python_version": "1.7.2"}),
_expr_case('python_version < "1.7.1"', True, {"python_version": "1.7"}),
_expr_case('python_version < "1.7.1"', False, {"python_version": "1.7.1-rc2"}),
_expr_case('python_version < "1.7.1-rc3"', True, {"python_version": "1.7.1-rc2"}),
_expr_case('python_version < "1.7.1-rc1"', False, {"python_version": "1.7.1-rc2"}),
# Extra tests
_expr_case('python_version <= "1.7.1"', True, {"python_version": "1.7.1"}),
_expr_case('python_version <= "1.7.2"', True, {"python_version": "1.7.1"}),
_expr_case('python_version >= "1.7.1"', True, {"python_version": "1.7.1"}),
_expr_case('python_version >= "1.7.0"', True, {"python_version": "1.7.1"}),
# Compatible version tests:
# https://packaging.python.org/en/latest/specifications/version-specifiers/#compatible-release
_expr_case('python_version ~= "2.2"', True, {"python_version": "2.3"}),
_expr_case('python_version ~= "2.2"', False, {"python_version": "2.1"}),
_expr_case('python_version ~= "2.2.post3"', False, {"python_version": "2.2"}),
_expr_case('python_version ~= "2.2.post3"', True, {"python_version": "2.3"}),
_expr_case('python_version ~= "2.2.post3"', False, {"python_version": "3.0"}),
_expr_case('python_version ~= "1!2.2"', False, {"python_version": "2.7"}),
_expr_case('python_version ~= "0!2.2"', True, {"python_version": "2.7"}),
_expr_case('python_version ~= "1!2.2"', True, {"python_version": "1!2.7"}),
_expr_case('python_version ~= "1.2.3"', True, {"python_version": "1.2.4"}),
_expr_case('python_version ~= "1.2.3"', False, {"python_version": "1.3.2"}),
]
def _misc_expressions(env):
for case in _MISC_EXPRESSIONS:
_check_evaluate(env, case.expr, case.want, case.env)
_tests.append(_misc_expressions)
def evaluate_test_suite(name): # buildifier: disable=function-docstring
test_suite(
name = name,
basic_tests = _tests,
)