| # 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. |
| |
| "" |
| |
| load("@pythons_hub//:versions.bzl", "MINOR_MAPPING") |
| load("@rules_testing//lib:test_suite.bzl", "test_suite") |
| load("//python/private:python.bzl", "parse_modules") # buildifier: disable=bzl-visibility |
| |
| _tests = [] |
| |
| def _mock_mctx(*modules, environ = {}): |
| return struct( |
| os = struct(environ = environ), |
| modules = [ |
| struct( |
| name = modules[0].name, |
| tags = modules[0].tags, |
| is_root = modules[0].is_root, |
| ), |
| ] + [ |
| struct( |
| name = mod.name, |
| tags = mod.tags, |
| is_root = False, |
| ) |
| for mod in modules[1:] |
| ], |
| ) |
| |
| def _mod(*, name, toolchain = [], override = [], single_version_override = [], single_version_platform_override = [], is_root = True): |
| return struct( |
| name = name, |
| tags = struct( |
| toolchain = toolchain, |
| override = override, |
| single_version_override = single_version_override, |
| single_version_platform_override = single_version_platform_override, |
| ), |
| is_root = is_root, |
| ) |
| |
| def _toolchain(python_version, *, is_default = False, **kwargs): |
| return struct( |
| is_default = is_default, |
| python_version = python_version, |
| **kwargs |
| ) |
| |
| def _override( |
| auth_patterns = {}, |
| available_python_versions = [], |
| base_url = "", |
| ignore_root_user_error = False, |
| minor_mapping = {}, |
| netrc = "", |
| register_all_versions = False): |
| return struct( |
| auth_patterns = auth_patterns, |
| available_python_versions = available_python_versions, |
| base_url = base_url, |
| ignore_root_user_error = ignore_root_user_error, |
| minor_mapping = minor_mapping, |
| netrc = netrc, |
| register_all_versions = register_all_versions, |
| ) |
| |
| def _single_version_override( |
| python_version = "", |
| sha256 = {}, |
| urls = [], |
| patch_strip = 0, |
| patches = [], |
| strip_prefix = "python", |
| distutils_content = "", |
| distutils = None): |
| if not python_version: |
| fail("missing mandatory args: python_version ({})".format(python_version)) |
| |
| return struct( |
| python_version = python_version, |
| sha256 = sha256, |
| urls = urls, |
| patch_strip = patch_strip, |
| patches = patches, |
| strip_prefix = strip_prefix, |
| distutils_content = distutils_content, |
| distutils = distutils, |
| ) |
| |
| def _single_version_platform_override( |
| coverage_tool = None, |
| patch_strip = 0, |
| patches = [], |
| platform = "", |
| python_version = "", |
| sha256 = "", |
| strip_prefix = "python", |
| urls = []): |
| if not platform or not python_version: |
| fail("missing mandatory args: platform ({}) and python_version ({})".format(platform, python_version)) |
| |
| return struct( |
| sha256 = sha256, |
| urls = urls, |
| strip_prefix = strip_prefix, |
| platform = platform, |
| coverage_tool = coverage_tool, |
| python_version = python_version, |
| patch_strip = patch_strip, |
| patches = patches, |
| ) |
| |
| def _test_default(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod(name = "rules_python", toolchain = [_toolchain("3.11")]), |
| ), |
| ) |
| |
| # The value there should be consistent in bzlmod with the automatically |
| # calculated value Please update the MINOR_MAPPING in //python:versions.bzl |
| # when this part starts failing. |
| env.expect.that_dict(py.config.minor_mapping).contains_exactly(MINOR_MAPPING) |
| env.expect.that_collection(py.config.kwargs).has_size(0) |
| env.expect.that_collection(py.config.default.keys()).contains_exactly([ |
| "base_url", |
| "ignore_root_user_error", |
| "tool_versions", |
| ]) |
| env.expect.that_bool(py.config.default["ignore_root_user_error"]).equals(False) |
| env.expect.that_str(py.default_python_version).equals("3.11") |
| |
| want_toolchain = struct( |
| name = "python_3_11", |
| python_version = "3.11", |
| register_coverage_tool = False, |
| ) |
| env.expect.that_collection(py.toolchains).contains_exactly([want_toolchain]) |
| |
| _tests.append(_test_default) |
| |
| def _test_default_some_module(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod(name = "rules_python", toolchain = [_toolchain("3.11")], is_root = False), |
| ), |
| ) |
| |
| env.expect.that_str(py.default_python_version).equals("3.11") |
| |
| want_toolchain = struct( |
| name = "python_3_11", |
| python_version = "3.11", |
| register_coverage_tool = False, |
| ) |
| env.expect.that_collection(py.toolchains).contains_exactly([want_toolchain]) |
| |
| _tests.append(_test_default_some_module) |
| |
| def _test_default_with_patch_version(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod(name = "rules_python", toolchain = [_toolchain("3.11.2")]), |
| ), |
| ) |
| |
| env.expect.that_str(py.default_python_version).equals("3.11.2") |
| |
| want_toolchain = struct( |
| name = "python_3_11_2", |
| python_version = "3.11.2", |
| register_coverage_tool = False, |
| ) |
| env.expect.that_collection(py.toolchains).contains_exactly([want_toolchain]) |
| |
| _tests.append(_test_default_with_patch_version) |
| |
| def _test_default_non_rules_python(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| # NOTE @aignas 2024-09-06: the first item in the module_ctx.modules |
| # could be a non-root module, which is the case if the root module |
| # does not make any calls to the extension. |
| _mod(name = "rules_python", toolchain = [_toolchain("3.11")], is_root = False), |
| ), |
| ) |
| |
| env.expect.that_str(py.default_python_version).equals("3.11") |
| rules_python_toolchain = struct( |
| name = "python_3_11", |
| python_version = "3.11", |
| register_coverage_tool = False, |
| ) |
| env.expect.that_collection(py.toolchains).contains_exactly([rules_python_toolchain]) |
| |
| _tests.append(_test_default_non_rules_python) |
| |
| def _test_default_non_rules_python_ignore_root_user_error(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod( |
| name = "my_module", |
| toolchain = [_toolchain("3.12", ignore_root_user_error = True)], |
| ), |
| _mod(name = "rules_python", toolchain = [_toolchain("3.11")]), |
| ), |
| ) |
| |
| env.expect.that_bool(py.config.default["ignore_root_user_error"]).equals(True) |
| env.expect.that_str(py.default_python_version).equals("3.12") |
| |
| my_module_toolchain = struct( |
| name = "python_3_12", |
| python_version = "3.12", |
| register_coverage_tool = False, |
| ) |
| rules_python_toolchain = struct( |
| name = "python_3_11", |
| python_version = "3.11", |
| register_coverage_tool = False, |
| ) |
| env.expect.that_collection(py.toolchains).contains_exactly([ |
| rules_python_toolchain, |
| my_module_toolchain, |
| ]).in_order() |
| |
| _tests.append(_test_default_non_rules_python_ignore_root_user_error) |
| |
| def _test_default_non_rules_python_ignore_root_user_error_override(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod( |
| name = "my_module", |
| toolchain = [_toolchain("3.12")], |
| override = [_override(ignore_root_user_error = True)], |
| ), |
| _mod(name = "rules_python", toolchain = [_toolchain("3.11")]), |
| ), |
| ) |
| |
| env.expect.that_bool(py.config.default["ignore_root_user_error"]).equals(True) |
| env.expect.that_str(py.default_python_version).equals("3.12") |
| |
| my_module_toolchain = struct( |
| name = "python_3_12", |
| python_version = "3.12", |
| register_coverage_tool = False, |
| ) |
| rules_python_toolchain = struct( |
| name = "python_3_11", |
| python_version = "3.11", |
| register_coverage_tool = False, |
| ) |
| env.expect.that_collection(py.toolchains).contains_exactly([ |
| rules_python_toolchain, |
| my_module_toolchain, |
| ]).in_order() |
| |
| _tests.append(_test_default_non_rules_python_ignore_root_user_error_override) |
| |
| def _test_default_non_rules_python_ignore_root_user_error_non_root_module(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod(name = "my_module", toolchain = [_toolchain("3.13")]), |
| _mod(name = "some_module", toolchain = [_toolchain("3.12", ignore_root_user_error = True)]), |
| _mod(name = "rules_python", toolchain = [_toolchain("3.11")]), |
| ), |
| ) |
| |
| env.expect.that_str(py.default_python_version).equals("3.13") |
| env.expect.that_bool(py.config.default["ignore_root_user_error"]).equals(False) |
| |
| my_module_toolchain = struct( |
| name = "python_3_13", |
| python_version = "3.13", |
| register_coverage_tool = False, |
| ) |
| some_module_toolchain = struct( |
| name = "python_3_12", |
| python_version = "3.12", |
| register_coverage_tool = False, |
| ) |
| rules_python_toolchain = struct( |
| name = "python_3_11", |
| python_version = "3.11", |
| register_coverage_tool = False, |
| ) |
| env.expect.that_collection(py.toolchains).contains_exactly([ |
| some_module_toolchain, |
| rules_python_toolchain, |
| my_module_toolchain, # this was the only toolchain, default to that |
| ]).in_order() |
| |
| _tests.append(_test_default_non_rules_python_ignore_root_user_error_non_root_module) |
| |
| def _test_first_occurance_of_the_toolchain_wins(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod(name = "my_module", toolchain = [_toolchain("3.12")]), |
| _mod(name = "some_module", toolchain = [_toolchain("3.12", configure_coverage_tool = True)]), |
| _mod(name = "rules_python", toolchain = [_toolchain("3.11")]), |
| environ = { |
| "RULES_PYTHON_BZLMOD_DEBUG": "1", |
| }, |
| ), |
| ) |
| |
| env.expect.that_str(py.default_python_version).equals("3.12") |
| |
| my_module_toolchain = struct( |
| name = "python_3_12", |
| python_version = "3.12", |
| # NOTE: coverage stays disabled even though `some_module` was |
| # configuring something else. |
| register_coverage_tool = False, |
| ) |
| rules_python_toolchain = struct( |
| name = "python_3_11", |
| python_version = "3.11", |
| register_coverage_tool = False, |
| ) |
| env.expect.that_collection(py.toolchains).contains_exactly([ |
| rules_python_toolchain, |
| my_module_toolchain, # default toolchain is last |
| ]).in_order() |
| |
| env.expect.that_dict(py.debug_info).contains_exactly({ |
| "toolchains_registered": [ |
| {"ignore_root_user_error": False, "module": {"is_root": True, "name": "my_module"}, "name": "python_3_12"}, |
| {"ignore_root_user_error": False, "module": {"is_root": False, "name": "rules_python"}, "name": "python_3_11"}, |
| ], |
| }) |
| |
| _tests.append(_test_first_occurance_of_the_toolchain_wins) |
| |
| def _test_auth_overrides(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod( |
| name = "my_module", |
| toolchain = [_toolchain("3.12")], |
| override = [ |
| _override( |
| netrc = "/my/netrc", |
| auth_patterns = {"foo": "bar"}, |
| ), |
| ], |
| ), |
| _mod(name = "rules_python", toolchain = [_toolchain("3.11")]), |
| ), |
| ) |
| |
| env.expect.that_dict(py.config.default).contains_at_least({ |
| "auth_patterns": {"foo": "bar"}, |
| "ignore_root_user_error": False, |
| "netrc": "/my/netrc", |
| }) |
| env.expect.that_str(py.default_python_version).equals("3.12") |
| |
| my_module_toolchain = struct( |
| name = "python_3_12", |
| python_version = "3.12", |
| register_coverage_tool = False, |
| ) |
| rules_python_toolchain = struct( |
| name = "python_3_11", |
| python_version = "3.11", |
| register_coverage_tool = False, |
| ) |
| env.expect.that_collection(py.toolchains).contains_exactly([ |
| rules_python_toolchain, |
| my_module_toolchain, |
| ]).in_order() |
| |
| _tests.append(_test_auth_overrides) |
| |
| def _test_add_new_version(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod( |
| name = "my_module", |
| toolchain = [_toolchain("3.13")], |
| single_version_override = [ |
| _single_version_override( |
| python_version = "3.13.0", |
| sha256 = { |
| "aarch64-unknown-linux-gnu": "deadbeef", |
| }, |
| urls = ["example.org"], |
| patch_strip = 0, |
| patches = [], |
| strip_prefix = "prefix", |
| distutils_content = "", |
| distutils = None, |
| ), |
| ], |
| single_version_platform_override = [ |
| _single_version_platform_override( |
| sha256 = "deadb00f", |
| urls = ["something.org", "else.org"], |
| strip_prefix = "python", |
| platform = "aarch64-unknown-linux-gnu", |
| coverage_tool = "specific_cov_tool", |
| python_version = "3.13.1", |
| patch_strip = 2, |
| patches = ["specific-patch.txt"], |
| ), |
| ], |
| override = [ |
| _override( |
| base_url = "", |
| available_python_versions = ["3.12.4", "3.13.0", "3.13.1"], |
| minor_mapping = { |
| "3.13": "3.13.0", |
| }, |
| ), |
| ], |
| ), |
| ), |
| ) |
| |
| env.expect.that_str(py.default_python_version).equals("3.13") |
| env.expect.that_collection(py.config.default["tool_versions"].keys()).contains_exactly([ |
| "3.12.4", |
| "3.13.0", |
| "3.13.1", |
| ]) |
| env.expect.that_dict(py.config.default["tool_versions"]["3.13.0"]).contains_exactly({ |
| "sha256": {"aarch64-unknown-linux-gnu": "deadbeef"}, |
| "strip_prefix": {"aarch64-unknown-linux-gnu": "prefix"}, |
| "url": {"aarch64-unknown-linux-gnu": ["example.org"]}, |
| }) |
| env.expect.that_dict(py.config.default["tool_versions"]["3.13.1"]).contains_exactly({ |
| "coverage_tool": {"aarch64-unknown-linux-gnu": "specific_cov_tool"}, |
| "patch_strip": {"aarch64-unknown-linux-gnu": 2}, |
| "patches": {"aarch64-unknown-linux-gnu": ["specific-patch.txt"]}, |
| "sha256": {"aarch64-unknown-linux-gnu": "deadb00f"}, |
| "strip_prefix": {"aarch64-unknown-linux-gnu": "python"}, |
| "url": {"aarch64-unknown-linux-gnu": ["something.org", "else.org"]}, |
| }) |
| env.expect.that_dict(py.config.minor_mapping).contains_exactly({ |
| "3.12": "3.12.4", # The `minor_mapping` will be overriden only for the missing keys |
| "3.13": "3.13.0", |
| }) |
| env.expect.that_collection(py.toolchains).contains_exactly([ |
| struct( |
| name = "python_3_13", |
| python_version = "3.13", |
| register_coverage_tool = False, |
| ), |
| ]) |
| |
| _tests.append(_test_add_new_version) |
| |
| def _test_register_all_versions(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod( |
| name = "my_module", |
| toolchain = [_toolchain("3.13")], |
| single_version_override = [ |
| _single_version_override( |
| python_version = "3.13.0", |
| sha256 = { |
| "aarch64-unknown-linux-gnu": "deadbeef", |
| }, |
| urls = ["example.org"], |
| ), |
| ], |
| single_version_platform_override = [ |
| _single_version_platform_override( |
| sha256 = "deadb00f", |
| urls = ["something.org"], |
| platform = "aarch64-unknown-linux-gnu", |
| python_version = "3.13.1", |
| ), |
| ], |
| override = [ |
| _override( |
| base_url = "", |
| available_python_versions = ["3.12.4", "3.13.0", "3.13.1"], |
| register_all_versions = True, |
| ), |
| ], |
| ), |
| ), |
| ) |
| |
| env.expect.that_str(py.default_python_version).equals("3.13") |
| env.expect.that_collection(py.config.default["tool_versions"].keys()).contains_exactly([ |
| "3.12.4", |
| "3.13.0", |
| "3.13.1", |
| ]) |
| env.expect.that_dict(py.config.minor_mapping).contains_exactly({ |
| # The mapping is calculated automatically |
| "3.12": "3.12.4", |
| "3.13": "3.13.1", |
| }) |
| env.expect.that_collection(py.toolchains).contains_exactly([ |
| struct( |
| name = name, |
| python_version = version, |
| register_coverage_tool = False, |
| ) |
| for name, version in { |
| "python_3_12": "3.12", |
| "python_3_12_4": "3.12.4", |
| "python_3_13": "3.13", |
| "python_3_13_0": "3.13.0", |
| "python_3_13_1": "3.13.1", |
| }.items() |
| ]) |
| |
| _tests.append(_test_register_all_versions) |
| |
| def _test_add_patches(env): |
| py = parse_modules( |
| module_ctx = _mock_mctx( |
| _mod( |
| name = "my_module", |
| toolchain = [_toolchain("3.13")], |
| single_version_override = [ |
| _single_version_override( |
| python_version = "3.13.0", |
| sha256 = { |
| "aarch64-apple-darwin": "deadbeef", |
| "aarch64-unknown-linux-gnu": "deadbeef", |
| }, |
| urls = ["example.org"], |
| patch_strip = 1, |
| patches = ["common.txt"], |
| strip_prefix = "prefix", |
| distutils_content = "", |
| distutils = None, |
| ), |
| ], |
| single_version_platform_override = [ |
| _single_version_platform_override( |
| sha256 = "deadb00f", |
| urls = ["something.org", "else.org"], |
| strip_prefix = "python", |
| platform = "aarch64-unknown-linux-gnu", |
| coverage_tool = "specific_cov_tool", |
| python_version = "3.13.0", |
| patch_strip = 2, |
| patches = ["specific-patch.txt"], |
| ), |
| ], |
| override = [ |
| _override( |
| base_url = "", |
| available_python_versions = ["3.13.0"], |
| minor_mapping = { |
| "3.13": "3.13.0", |
| }, |
| ), |
| ], |
| ), |
| ), |
| ) |
| |
| env.expect.that_str(py.default_python_version).equals("3.13") |
| env.expect.that_dict(py.config.default["tool_versions"]).contains_exactly({ |
| "3.13.0": { |
| "coverage_tool": {"aarch64-unknown-linux-gnu": "specific_cov_tool"}, |
| "patch_strip": {"aarch64-apple-darwin": 1, "aarch64-unknown-linux-gnu": 2}, |
| "patches": { |
| "aarch64-apple-darwin": ["common.txt"], |
| "aarch64-unknown-linux-gnu": ["specific-patch.txt"], |
| }, |
| "sha256": {"aarch64-apple-darwin": "deadbeef", "aarch64-unknown-linux-gnu": "deadb00f"}, |
| "strip_prefix": {"aarch64-apple-darwin": "prefix", "aarch64-unknown-linux-gnu": "python"}, |
| "url": { |
| "aarch64-apple-darwin": ["example.org"], |
| "aarch64-unknown-linux-gnu": ["something.org", "else.org"], |
| }, |
| }, |
| }) |
| env.expect.that_dict(py.config.minor_mapping).contains_exactly({ |
| "3.13": "3.13.0", |
| }) |
| env.expect.that_collection(py.toolchains).contains_exactly([ |
| struct( |
| name = "python_3_13", |
| python_version = "3.13", |
| register_coverage_tool = False, |
| ), |
| ]) |
| |
| _tests.append(_test_add_patches) |
| |
| def _test_fail_two_overrides(env): |
| errors = [] |
| parse_modules( |
| module_ctx = _mock_mctx( |
| _mod( |
| name = "my_module", |
| toolchain = [_toolchain("3.13")], |
| override = [ |
| _override(base_url = "foo"), |
| _override(base_url = "bar"), |
| ], |
| ), |
| ), |
| _fail = errors.append, |
| ) |
| env.expect.that_collection(errors).contains_exactly([ |
| "Only a single 'python.override' can be present", |
| ]) |
| |
| _tests.append(_test_fail_two_overrides) |
| |
| def _test_single_version_override_errors(env): |
| for test in [ |
| struct( |
| overrides = [ |
| _single_version_override(python_version = "3.12.4", distutils_content = "foo"), |
| _single_version_override(python_version = "3.12.4", distutils_content = "foo"), |
| ], |
| want_error = "Only a single 'python.single_version_override' can be present for '3.12.4'", |
| ), |
| struct( |
| overrides = [ |
| _single_version_override(python_version = "3.12.4+3", distutils_content = "foo"), |
| ], |
| want_error = "The 'python_version' attribute needs to specify an 'X.Y.Z' semver-compatible version, got: '3.12.4+3'", |
| ), |
| ]: |
| errors = [] |
| parse_modules( |
| module_ctx = _mock_mctx( |
| _mod( |
| name = "my_module", |
| toolchain = [_toolchain("3.13")], |
| single_version_override = test.overrides, |
| ), |
| ), |
| _fail = errors.append, |
| ) |
| env.expect.that_collection(errors).contains_exactly([test.want_error]) |
| |
| _tests.append(_test_single_version_override_errors) |
| |
| def _test_single_version_platform_override_errors(env): |
| for test in [ |
| struct( |
| overrides = [ |
| _single_version_platform_override(python_version = "3.12.4", platform = "foo", coverage_tool = "foo"), |
| _single_version_platform_override(python_version = "3.12.4", platform = "foo", coverage_tool = "foo"), |
| ], |
| want_error = "Only a single 'python.single_version_platform_override' can be present for '(\"3.12.4\", \"foo\")'", |
| ), |
| struct( |
| overrides = [ |
| _single_version_platform_override(python_version = "3.12", platform = "foo"), |
| ], |
| want_error = "The 'python_version' attribute needs to specify an 'X.Y.Z' semver-compatible version, got: '3.12'", |
| ), |
| struct( |
| overrides = [ |
| _single_version_platform_override(python_version = "3.12.1+my_build", platform = "foo"), |
| ], |
| want_error = "The 'python_version' attribute needs to specify an 'X.Y.Z' semver-compatible version, got: '3.12.1+my_build'", |
| ), |
| ]: |
| errors = [] |
| parse_modules( |
| module_ctx = _mock_mctx( |
| _mod( |
| name = "my_module", |
| toolchain = [_toolchain("3.13")], |
| single_version_platform_override = test.overrides, |
| ), |
| ), |
| _fail = errors.append, |
| ) |
| env.expect.that_collection(errors).contains_exactly([test.want_error]) |
| |
| _tests.append(_test_single_version_platform_override_errors) |
| |
| # TODO @aignas 2024-09-03: add failure tests: |
| # * incorrect platform failure |
| # * missing python_version failure |
| |
| def python_test_suite(name): |
| """Create the test suite. |
| |
| Args: |
| name: the name of the test suite |
| """ |
| test_suite(name = name, basic_tests = _tests) |