xref: /aosp_15_r20/external/bazelbuild-rules_python/tests/base_rules/precompile/precompile_tests.bzl (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
1# Copyright 2024 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Tests for precompiling behavior."""
16
17load("@rules_python_internal//:rules_python_config.bzl", rp_config = "config")
18load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
19load("@rules_testing//lib:test_suite.bzl", "test_suite")
20load("@rules_testing//lib:truth.bzl", "matching")
21load("@rules_testing//lib:util.bzl", rt_util = "util")
22load("//python:py_binary.bzl", "py_binary")
23load("//python:py_info.bzl", "PyInfo")
24load("//python:py_library.bzl", "py_library")
25load("//python:py_test.bzl", "py_test")
26load("//tests/support:py_info_subject.bzl", "py_info_subject")
27load(
28    "//tests/support:support.bzl",
29    "CC_TOOLCHAIN",
30    "EXEC_TOOLS_TOOLCHAIN",
31    "PRECOMPILE",
32    "PRECOMPILE_ADD_TO_RUNFILES",
33    "PRECOMPILE_SOURCE_RETENTION",
34    "PY_TOOLCHAINS",
35)
36
37_COMMON_CONFIG_SETTINGS = {
38    # This isn't enabled in all environments the tests run in, so disable
39    # it for conformity.
40    "//command_line_option:allow_unresolved_symlinks": True,
41    "//command_line_option:extra_toolchains": [PY_TOOLCHAINS, CC_TOOLCHAIN],
42    EXEC_TOOLS_TOOLCHAIN: "enabled",
43}
44
45_tests = []
46
47def _test_precompile_enabled_setup(name, py_rule, **kwargs):
48    if not rp_config.enable_pystar:
49        rt_util.skip_test(name = name)
50        return
51    rt_util.helper_target(
52        py_rule,
53        name = name + "_subject",
54        precompile = "enabled",
55        srcs = ["main.py"],
56        deps = [name + "_lib"],
57        **kwargs
58    )
59    rt_util.helper_target(
60        py_library,
61        name = name + "_lib",
62        srcs = ["lib.py"],
63        precompile = "enabled",
64    )
65    analysis_test(
66        name = name,
67        impl = _test_precompile_enabled_impl,
68        target = name + "_subject",
69        config_settings = _COMMON_CONFIG_SETTINGS,
70    )
71
72def _test_precompile_enabled_impl(env, target):
73    target = env.expect.that_target(target)
74    runfiles = target.runfiles()
75    runfiles.contains_predicate(
76        matching.str_matches("__pycache__/main.fakepy-45.pyc"),
77    )
78    runfiles.contains_predicate(
79        matching.str_matches("/main.py"),
80    )
81    target.default_outputs().contains_at_least_predicates([
82        matching.file_path_matches("__pycache__/main.fakepy-45.pyc"),
83        matching.file_path_matches("/main.py"),
84    ])
85    py_info = target.provider(PyInfo, factory = py_info_subject)
86    py_info.direct_pyc_files().contains_exactly([
87        "{package}/__pycache__/main.fakepy-45.pyc",
88    ])
89    py_info.transitive_pyc_files().contains_exactly([
90        "{package}/__pycache__/main.fakepy-45.pyc",
91        "{package}/__pycache__/lib.fakepy-45.pyc",
92    ])
93
94def _test_precompile_enabled_py_binary(name):
95    _test_precompile_enabled_setup(name = name, py_rule = py_binary, main = "main.py")
96
97_tests.append(_test_precompile_enabled_py_binary)
98
99def _test_precompile_enabled_py_test(name):
100    _test_precompile_enabled_setup(name = name, py_rule = py_test, main = "main.py")
101
102_tests.append(_test_precompile_enabled_py_test)
103
104def _test_precompile_enabled_py_library(name):
105    _test_precompile_enabled_setup(name = name, py_rule = py_library)
106
107_tests.append(_test_precompile_enabled_py_library)
108
109def _test_pyc_only(name):
110    if not rp_config.enable_pystar:
111        rt_util.skip_test(name = name)
112        return
113    rt_util.helper_target(
114        py_binary,
115        name = name + "_subject",
116        precompile = "enabled",
117        srcs = ["main.py"],
118        main = "main.py",
119        precompile_source_retention = "omit_source",
120    )
121    analysis_test(
122        name = name,
123        impl = _test_pyc_only_impl,
124        config_settings = _COMMON_CONFIG_SETTINGS | {
125            ##PRECOMPILE_SOURCE_RETENTION: "omit_source",
126            PRECOMPILE: "enabled",
127        },
128        target = name + "_subject",
129    )
130
131_tests.append(_test_pyc_only)
132
133def _test_pyc_only_impl(env, target):
134    target = env.expect.that_target(target)
135    runfiles = target.runfiles()
136    runfiles.contains_predicate(
137        matching.str_matches("/main.pyc"),
138    )
139    runfiles.not_contains_predicate(
140        matching.str_endswith("/main.py"),
141    )
142    target.default_outputs().contains_at_least_predicates([
143        matching.file_path_matches("/main.pyc"),
144    ])
145    target.default_outputs().not_contains_predicate(
146        matching.file_basename_equals("main.py"),
147    )
148
149def _test_precompile_if_generated(name):
150    if not rp_config.enable_pystar:
151        rt_util.skip_test(name = name)
152        return
153    rt_util.helper_target(
154        py_binary,
155        name = name + "_subject",
156        srcs = [
157            "main.py",
158            rt_util.empty_file("generated1.py"),
159        ],
160        main = "main.py",
161        precompile = "if_generated_source",
162    )
163    analysis_test(
164        name = name,
165        impl = _test_precompile_if_generated_impl,
166        target = name + "_subject",
167        config_settings = _COMMON_CONFIG_SETTINGS,
168    )
169
170_tests.append(_test_precompile_if_generated)
171
172def _test_precompile_if_generated_impl(env, target):
173    target = env.expect.that_target(target)
174    runfiles = target.runfiles()
175    runfiles.contains_predicate(
176        matching.str_matches("/__pycache__/generated1.fakepy-45.pyc"),
177    )
178    runfiles.not_contains_predicate(
179        matching.str_matches("main.*pyc"),
180    )
181    target.default_outputs().contains_at_least_predicates([
182        matching.file_path_matches("/__pycache__/generated1.fakepy-45.pyc"),
183    ])
184    target.default_outputs().not_contains_predicate(
185        matching.file_path_matches("main.*pyc"),
186    )
187
188def _test_omit_source_if_generated_source(name):
189    if not rp_config.enable_pystar:
190        rt_util.skip_test(name = name)
191        return
192    rt_util.helper_target(
193        py_binary,
194        name = name + "_subject",
195        srcs = [
196            "main.py",
197            rt_util.empty_file("generated2.py"),
198        ],
199        main = "main.py",
200        precompile = "enabled",
201    )
202    analysis_test(
203        name = name,
204        impl = _test_omit_source_if_generated_source_impl,
205        target = name + "_subject",
206        config_settings = _COMMON_CONFIG_SETTINGS | {
207            PRECOMPILE_SOURCE_RETENTION: "omit_if_generated_source",
208        },
209    )
210
211_tests.append(_test_omit_source_if_generated_source)
212
213def _test_omit_source_if_generated_source_impl(env, target):
214    target = env.expect.that_target(target)
215    runfiles = target.runfiles()
216    runfiles.contains_predicate(
217        matching.str_matches("/generated2.pyc"),
218    )
219    runfiles.contains_predicate(
220        matching.str_matches("__pycache__/main.fakepy-45.pyc"),
221    )
222    target.default_outputs().contains_at_least_predicates([
223        matching.file_path_matches("generated2.pyc"),
224    ])
225    target.default_outputs().contains_predicate(
226        matching.file_path_matches("__pycache__/main.fakepy-45.pyc"),
227    )
228
229def _test_precompile_add_to_runfiles_decided_elsewhere(name):
230    if not rp_config.enable_pystar:
231        rt_util.skip_test(name = name)
232        return
233    rt_util.helper_target(
234        py_binary,
235        name = name + "_binary",
236        srcs = ["bin.py"],
237        main = "bin.py",
238        deps = [name + "_lib"],
239        pyc_collection = "include_pyc",
240    )
241    rt_util.helper_target(
242        py_library,
243        name = name + "_lib",
244        srcs = ["lib.py"],
245    )
246    analysis_test(
247        name = name,
248        impl = _test_precompile_add_to_runfiles_decided_elsewhere_impl,
249        targets = {
250            "binary": name + "_binary",
251            "library": name + "_lib",
252        },
253        config_settings = _COMMON_CONFIG_SETTINGS | {
254            PRECOMPILE_ADD_TO_RUNFILES: "decided_elsewhere",
255            PRECOMPILE: "enabled",
256        },
257    )
258
259_tests.append(_test_precompile_add_to_runfiles_decided_elsewhere)
260
261def _test_precompile_add_to_runfiles_decided_elsewhere_impl(env, targets):
262    env.expect.that_target(targets.binary).runfiles().contains_at_least([
263        "{workspace}/{package}/__pycache__/bin.fakepy-45.pyc",
264        "{workspace}/{package}/__pycache__/lib.fakepy-45.pyc",
265        "{workspace}/{package}/bin.py",
266        "{workspace}/{package}/lib.py",
267    ])
268
269    env.expect.that_target(targets.library).runfiles().contains_exactly([
270        "{workspace}/{package}/lib.py",
271    ])
272
273def _test_precompiler_action(name):
274    if not rp_config.enable_pystar:
275        rt_util.skip_test(name = name)
276        return
277    rt_util.helper_target(
278        py_binary,
279        name = name + "_subject",
280        srcs = ["main2.py"],
281        main = "main2.py",
282        precompile = "enabled",
283        precompile_optimize_level = 2,
284        precompile_invalidation_mode = "unchecked_hash",
285    )
286    analysis_test(
287        name = name,
288        impl = _test_precompiler_action_impl,
289        target = name + "_subject",
290        config_settings = _COMMON_CONFIG_SETTINGS,
291    )
292
293_tests.append(_test_precompiler_action)
294
295def _test_precompiler_action_impl(env, target):
296    action = env.expect.that_target(target).action_named("PyCompile")
297    action.contains_flag_values([
298        ("--optimize", "2"),
299        ("--python_version", "4.5"),
300        ("--invalidation_mode", "unchecked_hash"),
301    ])
302    action.has_flags_specified(["--src", "--pyc", "--src_name"])
303    action.env().contains_at_least({
304        "PYTHONHASHSEED": "0",
305        "PYTHONNOUSERSITE": "1",
306        "PYTHONSAFEPATH": "1",
307    })
308
309def precompile_test_suite(name):
310    test_suite(
311        name = name,
312        tests = _tests,
313    )
314