• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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""
16
17load("@rules_testing//lib:test_suite.bzl", "test_suite")
18load("@rules_testing//lib:truth.bzl", "subjects")
19load("//python/private/pypi:extension.bzl", "parse_modules")  # buildifier: disable=bzl-visibility
20load("//python/private/pypi:whl_config_setting.bzl", "whl_config_setting")  # buildifier: disable=bzl-visibility
21
22_tests = []
23
24def _mock_mctx(*modules, environ = {}, read = None):
25    return struct(
26        os = struct(
27            environ = environ,
28            name = "unittest",
29            arch = "exotic",
30        ),
31        read = read or (lambda _: "simple==0.0.1 --hash=sha256:deadbeef --hash=sha256:deadbaaf"),
32        modules = [
33            struct(
34                name = modules[0].name,
35                tags = modules[0].tags,
36                is_root = modules[0].is_root,
37            ),
38        ] + [
39            struct(
40                name = mod.name,
41                tags = mod.tags,
42                is_root = False,
43            )
44            for mod in modules[1:]
45        ],
46    )
47
48def _mod(*, name, parse = [], override = [], whl_mods = [], is_root = True):
49    return struct(
50        name = name,
51        tags = struct(
52            parse = parse,
53            override = override,
54            whl_mods = whl_mods,
55        ),
56        is_root = is_root,
57    )
58
59def _parse_modules(env, **kwargs):
60    return env.expect.that_struct(
61        parse_modules(**kwargs),
62        attrs = dict(
63            is_reproducible = subjects.bool,
64            exposed_packages = subjects.dict,
65            hub_group_map = subjects.dict,
66            hub_whl_map = subjects.dict,
67            whl_libraries = subjects.dict,
68            whl_mods = subjects.dict,
69        ),
70    )
71
72def _parse(
73        *,
74        hub_name,
75        python_version,
76        _evaluate_markers_srcs = [],
77        auth_patterns = {},
78        download_only = False,
79        enable_implicit_namespace_pkgs = False,
80        environment = {},
81        envsubst = {},
82        experimental_index_url = "",
83        experimental_requirement_cycles = {},
84        experimental_target_platforms = [],
85        extra_hub_aliases = {},
86        extra_pip_args = [],
87        isolated = True,
88        netrc = None,
89        parse_all_requirements_files = True,
90        pip_data_exclude = None,
91        python_interpreter = None,
92        python_interpreter_target = None,
93        quiet = True,
94        requirements_by_platform = {},
95        requirements_darwin = None,
96        requirements_linux = None,
97        requirements_lock = None,
98        requirements_windows = None,
99        timeout = 600,
100        whl_modifications = {},
101        **kwargs):
102    return struct(
103        _evaluate_markers_srcs = _evaluate_markers_srcs,
104        auth_patterns = auth_patterns,
105        download_only = download_only,
106        enable_implicit_namespace_pkgs = enable_implicit_namespace_pkgs,
107        environment = environment,
108        envsubst = envsubst,
109        experimental_index_url = experimental_index_url,
110        experimental_requirement_cycles = experimental_requirement_cycles,
111        experimental_target_platforms = experimental_target_platforms,
112        extra_hub_aliases = extra_hub_aliases,
113        extra_pip_args = extra_pip_args,
114        hub_name = hub_name,
115        isolated = isolated,
116        netrc = netrc,
117        parse_all_requirements_files = parse_all_requirements_files,
118        pip_data_exclude = pip_data_exclude,
119        python_interpreter = python_interpreter,
120        python_interpreter_target = python_interpreter_target,
121        python_version = python_version,
122        quiet = quiet,
123        requirements_by_platform = requirements_by_platform,
124        requirements_darwin = requirements_darwin,
125        requirements_linux = requirements_linux,
126        requirements_lock = requirements_lock,
127        requirements_windows = requirements_windows,
128        timeout = timeout,
129        whl_modifications = whl_modifications,
130        # The following are covered by other unit tests
131        experimental_extra_index_urls = [],
132        parallel_download = False,
133        experimental_index_url_overrides = {},
134        **kwargs
135    )
136
137def _test_simple(env):
138    pypi = _parse_modules(
139        env,
140        module_ctx = _mock_mctx(
141            _mod(
142                name = "rules_python",
143                parse = [
144                    _parse(
145                        hub_name = "pypi",
146                        python_version = "3.15",
147                        requirements_lock = "requirements.txt",
148                    ),
149                ],
150            ),
151        ),
152        available_interpreters = {
153            "python_3_15_host": "unit_test_interpreter_target",
154        },
155    )
156
157    pypi.is_reproducible().equals(True)
158    pypi.exposed_packages().contains_exactly({"pypi": ["simple"]})
159    pypi.hub_group_map().contains_exactly({"pypi": {}})
160    pypi.hub_whl_map().contains_exactly({"pypi": {
161        "simple": {
162            "pypi_315_simple": [
163                whl_config_setting(
164                    version = "3.15",
165                ),
166            ],
167        },
168    }})
169    pypi.whl_libraries().contains_exactly({
170        "pypi_315_simple": {
171            "dep_template": "@pypi//{name}:{target}",
172            "python_interpreter_target": "unit_test_interpreter_target",
173            "repo": "pypi_315",
174            "requirement": "simple==0.0.1 --hash=sha256:deadbeef --hash=sha256:deadbaaf",
175        },
176    })
177    pypi.whl_mods().contains_exactly({})
178
179_tests.append(_test_simple)
180
181def _test_simple_multiple_requirements(env):
182    pypi = _parse_modules(
183        env,
184        module_ctx = _mock_mctx(
185            _mod(
186                name = "rules_python",
187                parse = [
188                    _parse(
189                        hub_name = "pypi",
190                        python_version = "3.15",
191                        requirements_darwin = "darwin.txt",
192                        requirements_windows = "win.txt",
193                    ),
194                ],
195            ),
196            read = lambda x: {
197                "darwin.txt": "simple==0.0.2 --hash=sha256:deadb00f",
198                "win.txt": "simple==0.0.1 --hash=sha256:deadbeef",
199            }[x],
200        ),
201        available_interpreters = {
202            "python_3_15_host": "unit_test_interpreter_target",
203        },
204    )
205
206    pypi.is_reproducible().equals(True)
207    pypi.exposed_packages().contains_exactly({"pypi": ["simple"]})
208    pypi.hub_group_map().contains_exactly({"pypi": {}})
209    pypi.hub_whl_map().contains_exactly({"pypi": {
210        "simple": {
211            "pypi_315_simple_osx_aarch64_osx_x86_64": [
212                whl_config_setting(
213                    target_platforms = [
214                        "cp315_osx_aarch64",
215                        "cp315_osx_x86_64",
216                    ],
217                    version = "3.15",
218                ),
219            ],
220            "pypi_315_simple_windows_x86_64": [
221                whl_config_setting(
222                    target_platforms = [
223                        "cp315_windows_x86_64",
224                    ],
225                    version = "3.15",
226                ),
227            ],
228        },
229    }})
230    pypi.whl_libraries().contains_exactly({
231        "pypi_315_simple_osx_aarch64_osx_x86_64": {
232            "dep_template": "@pypi//{name}:{target}",
233            "python_interpreter_target": "unit_test_interpreter_target",
234            "repo": "pypi_315",
235            "requirement": "simple==0.0.2 --hash=sha256:deadb00f",
236        },
237        "pypi_315_simple_windows_x86_64": {
238            "dep_template": "@pypi//{name}:{target}",
239            "python_interpreter_target": "unit_test_interpreter_target",
240            "repo": "pypi_315",
241            "requirement": "simple==0.0.1 --hash=sha256:deadbeef",
242        },
243    })
244    pypi.whl_mods().contains_exactly({})
245
246_tests.append(_test_simple_multiple_requirements)
247
248def _test_simple_with_markers(env):
249    pypi = _parse_modules(
250        env,
251        module_ctx = _mock_mctx(
252            _mod(
253                name = "rules_python",
254                parse = [
255                    _parse(
256                        hub_name = "pypi",
257                        python_version = "3.15",
258                        requirements_lock = "universal.txt",
259                    ),
260                ],
261            ),
262            read = lambda x: {
263                "universal.txt": """\
264torch==2.4.1+cpu ; platform_machine == 'x86_64'
265torch==2.4.1 ; platform_machine != 'x86_64'
266""",
267            }[x],
268        ),
269        available_interpreters = {
270            "python_3_15_host": "unit_test_interpreter_target",
271        },
272        evaluate_markers = lambda _, requirements, **__: {
273            key: [
274                platform
275                for platform in platforms
276                if ("x86_64" in platform and "platform_machine ==" in key) or ("x86_64" not in platform and "platform_machine !=" in key)
277            ]
278            for key, platforms in requirements.items()
279        },
280    )
281
282    pypi.is_reproducible().equals(True)
283    pypi.exposed_packages().contains_exactly({"pypi": ["torch"]})
284    pypi.hub_group_map().contains_exactly({"pypi": {}})
285    pypi.hub_whl_map().contains_exactly({"pypi": {
286        "torch": {
287            "pypi_315_torch_linux_aarch64_linux_arm_linux_ppc_linux_s390x_osx_aarch64": [
288                whl_config_setting(
289                    target_platforms = [
290                        "cp315_linux_aarch64",
291                        "cp315_linux_arm",
292                        "cp315_linux_ppc",
293                        "cp315_linux_s390x",
294                        "cp315_osx_aarch64",
295                    ],
296                    version = "3.15",
297                ),
298            ],
299            "pypi_315_torch_linux_x86_64_osx_x86_64_windows_x86_64": [
300                whl_config_setting(
301                    target_platforms = [
302                        "cp315_linux_x86_64",
303                        "cp315_osx_x86_64",
304                        "cp315_windows_x86_64",
305                    ],
306                    version = "3.15",
307                ),
308            ],
309        },
310    }})
311    pypi.whl_libraries().contains_exactly({
312        "pypi_315_torch_linux_aarch64_linux_arm_linux_ppc_linux_s390x_osx_aarch64": {
313            "dep_template": "@pypi//{name}:{target}",
314            "python_interpreter_target": "unit_test_interpreter_target",
315            "repo": "pypi_315",
316            "requirement": "torch==2.4.1 ; platform_machine != 'x86_64'",
317        },
318        "pypi_315_torch_linux_x86_64_osx_x86_64_windows_x86_64": {
319            "dep_template": "@pypi//{name}:{target}",
320            "python_interpreter_target": "unit_test_interpreter_target",
321            "repo": "pypi_315",
322            "requirement": "torch==2.4.1+cpu ; platform_machine == 'x86_64'",
323        },
324    })
325    pypi.whl_mods().contains_exactly({})
326
327_tests.append(_test_simple_with_markers)
328
329def _test_download_only_multiple(env):
330    pypi = _parse_modules(
331        env,
332        module_ctx = _mock_mctx(
333            _mod(
334                name = "rules_python",
335                parse = [
336                    _parse(
337                        hub_name = "pypi",
338                        python_version = "3.15",
339                        download_only = True,
340                        requirements_by_platform = {
341                            "requirements.linux_x86_64.txt": "linux_x86_64",
342                            "requirements.osx_aarch64.txt": "osx_aarch64",
343                        },
344                    ),
345                ],
346            ),
347            read = lambda x: {
348                "requirements.linux_x86_64.txt": """\
349--platform=manylinux_2_17_x86_64
350--python-version=315
351--implementation=cp
352--abi=cp315
353
354simple==0.0.1 --hash=sha256:deadbeef
355extra==0.0.1 --hash=sha256:deadb00f
356""",
357                "requirements.osx_aarch64.txt": """\
358--platform=macosx_10_9_arm64
359--python-version=315
360--implementation=cp
361--abi=cp315
362
363simple==0.0.3 --hash=sha256:deadbaaf
364""",
365            }[x],
366        ),
367        available_interpreters = {
368            "python_3_15_host": "unit_test_interpreter_target",
369        },
370    )
371
372    pypi.is_reproducible().equals(True)
373    pypi.exposed_packages().contains_exactly({"pypi": ["simple"]})
374    pypi.hub_group_map().contains_exactly({"pypi": {}})
375    pypi.hub_whl_map().contains_exactly({"pypi": {
376        "extra": {
377            "pypi_315_extra": [
378                whl_config_setting(version = "3.15"),
379            ],
380        },
381        "simple": {
382            "pypi_315_simple_linux_x86_64": [
383                whl_config_setting(
384                    target_platforms = ["cp315_linux_x86_64"],
385                    version = "3.15",
386                ),
387            ],
388            "pypi_315_simple_osx_aarch64": [
389                whl_config_setting(
390                    target_platforms = ["cp315_osx_aarch64"],
391                    version = "3.15",
392                ),
393            ],
394        },
395    }})
396    pypi.whl_libraries().contains_exactly({
397        "pypi_315_extra": {
398            "dep_template": "@pypi//{name}:{target}",
399            "download_only": True,
400            "experimental_target_platforms": ["cp315_linux_x86_64"],
401            "extra_pip_args": ["--platform=manylinux_2_17_x86_64", "--python-version=315", "--implementation=cp", "--abi=cp315"],
402            "python_interpreter_target": "unit_test_interpreter_target",
403            "repo": "pypi_315",
404            "requirement": "extra==0.0.1 --hash=sha256:deadb00f",
405        },
406        "pypi_315_simple_linux_x86_64": {
407            "dep_template": "@pypi//{name}:{target}",
408            "download_only": True,
409            "experimental_target_platforms": ["cp315_linux_x86_64"],
410            "extra_pip_args": ["--platform=manylinux_2_17_x86_64", "--python-version=315", "--implementation=cp", "--abi=cp315"],
411            "python_interpreter_target": "unit_test_interpreter_target",
412            "repo": "pypi_315",
413            "requirement": "simple==0.0.1 --hash=sha256:deadbeef",
414        },
415        "pypi_315_simple_osx_aarch64": {
416            "dep_template": "@pypi//{name}:{target}",
417            "download_only": True,
418            "experimental_target_platforms": ["cp315_osx_aarch64"],
419            "extra_pip_args": ["--platform=macosx_10_9_arm64", "--python-version=315", "--implementation=cp", "--abi=cp315"],
420            "python_interpreter_target": "unit_test_interpreter_target",
421            "repo": "pypi_315",
422            "requirement": "simple==0.0.3 --hash=sha256:deadbaaf",
423        },
424    })
425    pypi.whl_mods().contains_exactly({})
426
427_tests.append(_test_download_only_multiple)
428
429def _test_simple_get_index(env):
430    got_simpleapi_download_args = []
431    got_simpleapi_download_kwargs = {}
432
433    def mocksimpleapi_download(*args, **kwargs):
434        got_simpleapi_download_args.extend(args)
435        got_simpleapi_download_kwargs.update(kwargs)
436        return {
437            "simple": struct(
438                whls = {
439                    "deadb00f": struct(
440                        yanked = False,
441                        filename = "simple-0.0.1-py3-none-any.whl",
442                        sha256 = "deadb00f",
443                        url = "example2.org",
444                    ),
445                },
446                sdists = {
447                    "deadbeef": struct(
448                        yanked = False,
449                        filename = "simple-0.0.1.tar.gz",
450                        sha256 = "deadbeef",
451                        url = "example.org",
452                    ),
453                },
454            ),
455        }
456
457    pypi = _parse_modules(
458        env,
459        module_ctx = _mock_mctx(
460            _mod(
461                name = "rules_python",
462                parse = [
463                    _parse(
464                        hub_name = "pypi",
465                        python_version = "3.15",
466                        requirements_lock = "requirements.txt",
467                        experimental_index_url = "pypi.org",
468                        extra_pip_args = [
469                            "--extra-args-for-sdist-building",
470                        ],
471                    ),
472                ],
473            ),
474            read = lambda x: {
475                "requirements.txt": """
476simple==0.0.1 --hash=sha256:deadbeef --hash=sha256:deadb00f
477some_pkg==0.0.1
478""",
479            }[x],
480        ),
481        available_interpreters = {
482            "python_3_15_host": "unit_test_interpreter_target",
483        },
484        simpleapi_download = mocksimpleapi_download,
485    )
486
487    pypi.is_reproducible().equals(False)
488    pypi.exposed_packages().contains_exactly({"pypi": ["simple", "some_pkg"]})
489    pypi.hub_group_map().contains_exactly({"pypi": {}})
490    pypi.hub_whl_map().contains_exactly({
491        "pypi": {
492            "simple": {
493                "pypi_315_simple_py3_none_any_deadb00f": [
494                    whl_config_setting(
495                        filename = "simple-0.0.1-py3-none-any.whl",
496                        version = "3.15",
497                    ),
498                ],
499                "pypi_315_simple_sdist_deadbeef": [
500                    whl_config_setting(
501                        filename = "simple-0.0.1.tar.gz",
502                        version = "3.15",
503                    ),
504                ],
505            },
506            "some_pkg": {
507                "pypi_315_some_pkg": [whl_config_setting(version = "3.15")],
508            },
509        },
510    })
511    pypi.whl_libraries().contains_exactly({
512        "pypi_315_simple_py3_none_any_deadb00f": {
513            "dep_template": "@pypi//{name}:{target}",
514            "experimental_target_platforms": [
515                "cp315_linux_aarch64",
516                "cp315_linux_arm",
517                "cp315_linux_ppc",
518                "cp315_linux_s390x",
519                "cp315_linux_x86_64",
520                "cp315_osx_aarch64",
521                "cp315_osx_x86_64",
522                "cp315_windows_x86_64",
523            ],
524            "filename": "simple-0.0.1-py3-none-any.whl",
525            "python_interpreter_target": "unit_test_interpreter_target",
526            "repo": "pypi_315",
527            "requirement": "simple==0.0.1",
528            "sha256": "deadb00f",
529            "urls": ["example2.org"],
530        },
531        "pypi_315_simple_sdist_deadbeef": {
532            "dep_template": "@pypi//{name}:{target}",
533            "experimental_target_platforms": [
534                "cp315_linux_aarch64",
535                "cp315_linux_arm",
536                "cp315_linux_ppc",
537                "cp315_linux_s390x",
538                "cp315_linux_x86_64",
539                "cp315_osx_aarch64",
540                "cp315_osx_x86_64",
541                "cp315_windows_x86_64",
542            ],
543            "extra_pip_args": ["--extra-args-for-sdist-building"],
544            "filename": "simple-0.0.1.tar.gz",
545            "python_interpreter_target": "unit_test_interpreter_target",
546            "repo": "pypi_315",
547            "requirement": "simple==0.0.1",
548            "sha256": "deadbeef",
549            "urls": ["example.org"],
550        },
551        # We are falling back to regular `pip`
552        "pypi_315_some_pkg": {
553            "dep_template": "@pypi//{name}:{target}",
554            "extra_pip_args": ["--extra-args-for-sdist-building"],
555            "python_interpreter_target": "unit_test_interpreter_target",
556            "repo": "pypi_315",
557            "requirement": "some_pkg==0.0.1",
558        },
559    })
560    pypi.whl_mods().contains_exactly({})
561    env.expect.that_dict(got_simpleapi_download_kwargs).contains_exactly({
562        "attr": struct(
563            auth_patterns = {},
564            envsubst = {},
565            extra_index_urls = [],
566            index_url = "pypi.org",
567            index_url_overrides = {},
568            netrc = None,
569            sources = ["simple", "some_pkg"],
570        ),
571        "cache": {},
572        "parallel_download": False,
573    })
574
575_tests.append(_test_simple_get_index)
576
577def extension_test_suite(name):
578    """Create the test suite.
579
580    Args:
581        name: the name of the test suite
582    """
583    test_suite(name = name, basic_tests = _tests)
584