From f4c7360270bcac73245ed2e30d57be8d1340f8a8 Mon Sep 17 00:00:00 2001 From: Richard Levasseur Date: Thu, 22 Oct 2026 00:23:47 -0700 Subject: [PATCH] tests: add integration test for prebuilt exe_zip_maker toolchain Verify that the prebuilt toolchains infrastructure resolves and uses a prebuilt exe_zip_maker binary when rules_python is consumed in release mode (`TOOL_DEPS_DEV_ONLY = True`). The test stages a release-like copy of rules_python with a populated `artifacts_manifest.json` pointing to a locally built `exe_zip_maker` binary, then runs a nested Bzlmod workspace with `--override_module=rules_python` to confirm `py_binary` zipapps build and execute using the prebuilt toolchain. Work towards #4250 Work towards #4216 --- .bazelignore | 1 + .bazelrc.deleted_packages | 1 + tests/integration/BUILD.bazel | 10 +- tests/integration/integration_test.bzl | 13 ++- tests/integration/prebuilt_toolchain/.bazelrc | 3 + .../prebuilt_toolchain/BUILD.bazel | 15 +++ .../prebuilt_toolchain/MODULE.bazel | 10 ++ .../integration/prebuilt_toolchain/WORKSPACE | 1 + tests/integration/prebuilt_toolchain/main.py | 1 + tests/integration/prebuilt_toolchain_test.py | 98 +++++++++++++++++++ 10 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 tests/integration/prebuilt_toolchain/.bazelrc create mode 100644 tests/integration/prebuilt_toolchain/BUILD.bazel create mode 100644 tests/integration/prebuilt_toolchain/MODULE.bazel create mode 100644 tests/integration/prebuilt_toolchain/WORKSPACE create mode 100644 tests/integration/prebuilt_toolchain/main.py create mode 100644 tests/integration/prebuilt_toolchain_test.py diff --git a/.bazelignore b/.bazelignore index 88a4c32056..8a3cbf9068 100644 --- a/.bazelignore +++ b/.bazelignore @@ -33,6 +33,7 @@ sphinxdocs tests/integration/bzlmod_lockfile/bazel-bzlmod_lockfile tests/integration/compile_pip_requirements/bazel-compile_pip_requirements tests/integration/local_toolchains/bazel-local_toolchains +tests/integration/prebuilt_toolchain/bazel-prebuilt_toolchain tests/integration/py_cc_toolchain_registered/bazel-py_cc_toolchain_registered tests/integration/toolchain_target_settings/bazel-module_under_test tests/integration/unified_pypi/bazel-unified_pypi diff --git a/.bazelrc.deleted_packages b/.bazelrc.deleted_packages index 3eec28298e..d0c8d405e4 100644 --- a/.bazelrc.deleted_packages +++ b/.bazelrc.deleted_packages @@ -38,6 +38,7 @@ common --deleted_packages=tests/integration/local_toolchains common --deleted_packages=tests/integration/pip_parse common --deleted_packages=tests/integration/pip_parse/empty common --deleted_packages=tests/integration/pip_parse_isolated +common --deleted_packages=tests/integration/prebuilt_toolchain common --deleted_packages=tests/integration/py_cc_toolchain_registered common --deleted_packages=tests/integration/runtime_manifests common --deleted_packages=tests/integration/toolchain_target_settings diff --git a/tests/integration/BUILD.bazel b/tests/integration/BUILD.bazel index 2eae8dfecb..6c5d248202 100644 --- a/tests/integration/BUILD.bazel +++ b/tests/integration/BUILD.bazel @@ -15,7 +15,7 @@ load("@rules_bazel_integration_test//bazel_integration_test:defs.bzl", "default_test_runner") load("//python:py_binary.bzl", "py_binary") load("//python:py_library.bzl", "py_library") -load("//tests/support:support.bzl", "NOT_WINDOWS") +load("//tests/support:support.bzl", "NOT_WINDOWS", "SUPPORTS_BZLMOD") load(":integration_test.bzl", "rules_python_integration_test") licenses(["notice"]) @@ -115,6 +115,14 @@ rules_python_integration_test( py_main = "custom_commands_test.py", ) +rules_python_integration_test( + name = "prebuilt_toolchain_test", + py_data = ["//crates/exe_zip_maker"], + py_deps = ["//python/runfiles"], + py_main = "prebuilt_toolchain_test.py", + target_compatible_with = SUPPORTS_BZLMOD + NOT_WINDOWS, +) + rules_python_integration_test( name = "toolchain_target_settings_test", py_main = "toolchain_target_settings_test.py", diff --git a/tests/integration/integration_test.bzl b/tests/integration/integration_test.bzl index f3d5cb6967..fec4a8962f 100644 --- a/tests/integration/integration_test.bzl +++ b/tests/integration/integration_test.bzl @@ -21,13 +21,14 @@ load( ) load("//python:py_test.bzl", "py_test") -def _test_runner(*, name, bazel_version, py_main, bzlmod, py_deps): +def _test_runner(*, name, bazel_version, py_main, bzlmod, py_deps, py_data): if py_main: test_runner = "{}_bazel_{}_py_runner".format(name, bazel_version) py_test( name = test_runner, srcs = [py_main], main = py_main, + data = py_data, deps = [":runner_lib"] + py_deps, # Hide from ... patterns; should only be run as part # of the bazel integration test @@ -47,6 +48,7 @@ def rules_python_integration_test( tags = None, py_main = None, py_deps = None, + py_data = None, bazel_versions = None, **kwargs): """Runs a bazel-in-bazel integration test. @@ -62,11 +64,15 @@ def rules_python_integration_test( python based test runner is used, and this source file is the main entry point and responsible for executing tests. py_deps: Optional test runner deps to use for setup. + py_data: Optional test runner data files to use for setup. bazel_versions: `list[str] | None`, the bazel versions to test. I not specified, defaults to all configured bazel versions. **kwargs: Passed to the upstream `bazel_integration_tests` rule. """ workspace_path = workspace_path or name.removesuffix("_test") + py_deps = py_deps or [] + py_data = py_data or [] + tags = tags or [] # Because glob expansion happens at loading time, the bazel-* symlinks # in the workspaces can recursively expand to tens-of-thousands of entries, @@ -93,7 +99,8 @@ def rules_python_integration_test( name = name, bazel_version = bazel_version, py_main = py_main, - py_deps = py_deps or [], + py_deps = py_deps, + py_data = py_data, bzlmod = bzlmod, ) bazel_integration_test( @@ -103,7 +110,7 @@ def rules_python_integration_test( bazel_version = bazel_version, workspace_files = [name + "_workspace_files"], # Override the tags so that the `manual` tag isn't applied. - tags = (tags or []) + [ + tags = tags + [ # These tests are very heavy weight, so much so that only a couple # can be run in parallel without harming their reliability, # overall runtime, and the system's stability. Unfortunately, diff --git a/tests/integration/prebuilt_toolchain/.bazelrc b/tests/integration/prebuilt_toolchain/.bazelrc new file mode 100644 index 0000000000..e1df951d06 --- /dev/null +++ b/tests/integration/prebuilt_toolchain/.bazelrc @@ -0,0 +1,3 @@ +common --lockfile_mode=off +test --test_output=errors +build --enable_runfiles diff --git a/tests/integration/prebuilt_toolchain/BUILD.bazel b/tests/integration/prebuilt_toolchain/BUILD.bazel new file mode 100644 index 0000000000..18c2f0da1a --- /dev/null +++ b/tests/integration/prebuilt_toolchain/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_python//python:py_binary.bzl", "py_binary") +load("@rules_python//python/zipapp:py_zipapp_test.bzl", "py_zipapp_test") + +py_binary( + name = "main", + srcs = ["main.py"], + config_settings = { + "@rules_python//python/config_settings:bootstrap_impl": "script", + }, +) + +py_zipapp_test( + name = "zipapp_test", + binary = ":main", +) diff --git a/tests/integration/prebuilt_toolchain/MODULE.bazel b/tests/integration/prebuilt_toolchain/MODULE.bazel new file mode 100644 index 0000000000..edf2283fb1 --- /dev/null +++ b/tests/integration/prebuilt_toolchain/MODULE.bazel @@ -0,0 +1,10 @@ +module(name = "prebuilt_toolchain") + +bazel_dep(name = "rules_python", version = "0.0.0") +local_path_override( + module_name = "rules_python", + path = "../../..", +) + +python = use_extension("@rules_python//python/extensions:python.bzl", "python") +python.toolchain(python_version = "3.13") diff --git a/tests/integration/prebuilt_toolchain/WORKSPACE b/tests/integration/prebuilt_toolchain/WORKSPACE new file mode 100644 index 0000000000..d7be0c96c7 --- /dev/null +++ b/tests/integration/prebuilt_toolchain/WORKSPACE @@ -0,0 +1 @@ +# Intentionally blank; bzlmod is used. diff --git a/tests/integration/prebuilt_toolchain/main.py b/tests/integration/prebuilt_toolchain/main.py new file mode 100644 index 0000000000..6359eccbf8 --- /dev/null +++ b/tests/integration/prebuilt_toolchain/main.py @@ -0,0 +1 @@ +print("Hello from prebuilt exe_zip_maker zipapp") diff --git a/tests/integration/prebuilt_toolchain_test.py b/tests/integration/prebuilt_toolchain_test.py new file mode 100644 index 0000000000..ab12a9c3a6 --- /dev/null +++ b/tests/integration/prebuilt_toolchain_test.py @@ -0,0 +1,98 @@ +"""Integration test for prebuilt tool toolchain configuration.""" + +import hashlib +import json +import shutil +import unittest + +from python.runfiles import runfiles +from tests.integration import runner + +_USE_RUST_FLAG = "--@rules_python//python/private/toolchains:use_rust_exe_zip_maker=yes" + + +class PrebuiltToolchainTest(runner.TestCase): + def setUp(self): + super().setUp() + rf = runfiles.CreateOrRaise() + exe_zip_maker = ( + rf.root() / "rules_python/crates/exe_zip_maker/exe_zip_maker" + ).resolve() + self.assertTrue( + exe_zip_maker.is_file(), + f"Expected exe_zip_maker binary at {exe_zip_maker}", + ) + sha256 = hashlib.sha256(exe_zip_maker.read_bytes()).hexdigest() + + orig_rules_python = self.repo_root.parent.parent.parent + self.rules_python_dir = self.test_tmp_dir / "rules_python" + shutil.copytree( + orig_rules_python, + self.rules_python_dir, + ignore=shutil.ignore_patterns("tests", "bazel-*"), + ) + + # Simulate a release archive where tool dependencies (such as + # rules_rust) are dev-only and prebuilt toolchains are configured via + # artifacts_manifest.json. + module_bazel = self.rules_python_dir / "MODULE.bazel" + module_content = module_bazel.read_text(encoding="utf-8") + self.assertIn("\nTOOL_DEPS_DEV_ONLY = False\n", module_content) + module_bazel.write_text( + module_content.replace( + "\nTOOL_DEPS_DEV_ONLY = False\n", + "\nTOOL_DEPS_DEV_ONLY = True\n", + ), + encoding="utf-8", + ) + + manifest = { + "artifacts": { + "exe_zip_maker": [ + { + "exec_compatible_with": [ + "@platforms//os:windows", + ], + "name": "exe_zip_maker_unmatched", + "sha256": "0" * 64, + "urls": ["https://invalid.example.com/should-not-be-fetched"], + }, + { + "exec_compatible_with": [], + "name": "exe_zip_maker_prebuilt", + "sha256": sha256, + "urls": [exe_zip_maker.as_uri()], + }, + ], + }, + } + manifest_path = self.rules_python_dir / "python/private/artifacts_manifest.json" + manifest_path.write_text( + json.dumps(manifest, indent=2) + "\n", + encoding="utf-8", + ) + + def test_prebuilt_exe_zip_maker_toolchain(self): + override_flag = f"--override_module=rules_python={self.rules_python_dir}" + + self.run_bazel( + "test", + override_flag, + _USE_RUST_FLAG, + "//:zipapp_test", + ) + + aquery_res = self.run_bazel( + "aquery", + override_flag, + _USE_RUST_FLAG, + "mnemonic(PyZipAppCreateExecutableZip, //:zipapp_test)", + ) + self.assert_result_matches( + aquery_res, + r"rules_python_exe_zip_maker_prebuilt_file/file/downloaded", + ) + + +if __name__ == "__main__": + unittest.main()