|
| 1 | +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +# SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +from __future__ import annotations |
| 5 | + |
| 6 | +import importlib |
| 7 | +import os |
| 8 | +from pathlib import Path |
| 9 | + |
| 10 | +import pytest |
| 11 | +from local_helpers import ( |
| 12 | + have_distribution, |
| 13 | + locate_real_cuda_toolkit_version_from_cuda_h, |
| 14 | +) |
| 15 | + |
| 16 | +import cuda.pathfinder._compatibility_guard_rails as compatibility_module |
| 17 | +from cuda.pathfinder import LoadedDL, LocatedBitcodeLib, LocatedStaticLib |
| 18 | +from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import _resolve_system_loaded_abs_path_in_subprocess |
| 19 | +from cuda.pathfinder._headers.find_nvidia_headers import ( |
| 20 | + locate_nvidia_header_directory as locate_nvidia_header_directory_raw, |
| 21 | +) |
| 22 | +from cuda.pathfinder._utils import driver_info |
| 23 | +from cuda.pathfinder._utils.driver_info import DriverCudaVersion, DriverReleaseVersion |
| 24 | +from cuda.pathfinder._utils.env_vars import get_cuda_path_or_home |
| 25 | +from cuda.pathfinder._utils.toolkit_info import read_cuda_header_version |
| 26 | + |
| 27 | +STRICTNESS = os.environ.get("CUDA_PATHFINDER_TEST_COMPATIBILITY_GUARD_RAILS_STRICTNESS", "see_what_works") |
| 28 | +assert STRICTNESS in ("see_what_works", "all_must_work") |
| 29 | +COMPATIBILITY_GUARD_RAILS_ENV_VAR = "CUDA_PATHFINDER_COMPATIBILITY_GUARD_RAILS" |
| 30 | +DRIVER_COMPATIBILITY_ENV_VAR = "CUDA_PATHFINDER_DRIVER_COMPATIBILITY" |
| 31 | +process_wide_module = importlib.import_module("cuda.pathfinder._process_wide_compatibility_guard_rails") |
| 32 | + |
| 33 | + |
| 34 | +@pytest.fixture(autouse=True) |
| 35 | +def _default_process_wide_guard_rails_mode(monkeypatch): |
| 36 | + monkeypatch.delenv(COMPATIBILITY_GUARD_RAILS_ENV_VAR, raising=False) |
| 37 | + monkeypatch.delenv(DRIVER_COMPATIBILITY_ENV_VAR, raising=False) |
| 38 | + |
| 39 | + |
| 40 | +@pytest.fixture |
| 41 | +def clear_real_host_probe_caches(): |
| 42 | + have_distribution.cache_clear() |
| 43 | + locate_real_cuda_toolkit_version_from_cuda_h.cache_clear() |
| 44 | + locate_nvidia_header_directory_raw.cache_clear() |
| 45 | + _resolve_system_loaded_abs_path_in_subprocess.cache_clear() |
| 46 | + get_cuda_path_or_home.cache_clear() |
| 47 | + read_cuda_header_version.cache_clear() |
| 48 | + driver_info._load_nvidia_dynamic_lib.cache_clear() |
| 49 | + driver_info.query_driver_cuda_version.cache_clear() |
| 50 | + driver_info.query_driver_release_version.cache_clear() |
| 51 | + yield |
| 52 | + have_distribution.cache_clear() |
| 53 | + locate_real_cuda_toolkit_version_from_cuda_h.cache_clear() |
| 54 | + locate_nvidia_header_directory_raw.cache_clear() |
| 55 | + _resolve_system_loaded_abs_path_in_subprocess.cache_clear() |
| 56 | + get_cuda_path_or_home.cache_clear() |
| 57 | + read_cuda_header_version.cache_clear() |
| 58 | + driver_info._load_nvidia_dynamic_lib.cache_clear() |
| 59 | + driver_info.query_driver_cuda_version.cache_clear() |
| 60 | + driver_info.query_driver_release_version.cache_clear() |
| 61 | + |
| 62 | + |
| 63 | +def _write_cuda_h( |
| 64 | + ctk_root: Path, |
| 65 | + toolkit_version: str, |
| 66 | + *, |
| 67 | + include_dir_parts: tuple[str, ...] = ("targets", "x86_64-linux", "include"), |
| 68 | +) -> None: |
| 69 | + parts = toolkit_version.split(".") |
| 70 | + if len(parts) < 2: |
| 71 | + raise AssertionError(f"Expected at least major.minor in toolkit version, got {toolkit_version!r}") |
| 72 | + encoded = int(parts[0]) * 1000 + int(parts[1]) * 10 |
| 73 | + cuda_h_path = ctk_root.joinpath(*include_dir_parts, "cuda.h") |
| 74 | + cuda_h_path.parent.mkdir(parents=True, exist_ok=True) |
| 75 | + cuda_h_path.write_text( |
| 76 | + f"#ifndef CUDA_H\n#define CUDA_H\n#define CUDA_VERSION {encoded}\n#endif\n", |
| 77 | + encoding="utf-8", |
| 78 | + ) |
| 79 | + |
| 80 | + |
| 81 | +def _touch(path: Path) -> str: |
| 82 | + path.parent.mkdir(parents=True, exist_ok=True) |
| 83 | + path.touch() |
| 84 | + return str(path) |
| 85 | + |
| 86 | + |
| 87 | +def _loaded_dl(abs_path: str, *, found_via: str = "CUDA_PATH") -> LoadedDL: |
| 88 | + return LoadedDL( |
| 89 | + abs_path=abs_path, |
| 90 | + was_already_loaded_from_elsewhere=False, |
| 91 | + _handle_uint=1, |
| 92 | + found_via=found_via, |
| 93 | + ) |
| 94 | + |
| 95 | + |
| 96 | +def _patch_dynamic_lib_loader(monkeypatch, **loaded_by_libname: LoadedDL) -> None: |
| 97 | + def fake_load_nvidia_dynamic_lib(libname: str) -> LoadedDL: |
| 98 | + loaded = loaded_by_libname.get(libname) |
| 99 | + if loaded is None: |
| 100 | + raise AssertionError(f"Unexpected libname: {libname!r}") |
| 101 | + return loaded |
| 102 | + |
| 103 | + monkeypatch.setattr(compatibility_module, "_load_nvidia_dynamic_lib", fake_load_nvidia_dynamic_lib) |
| 104 | + |
| 105 | + |
| 106 | +def _located_static_lib(name: str, abs_path: str) -> LocatedStaticLib: |
| 107 | + return LocatedStaticLib( |
| 108 | + name=name, |
| 109 | + abs_path=abs_path, |
| 110 | + filename=os.path.basename(abs_path), |
| 111 | + found_via="CUDA_PATH", |
| 112 | + ) |
| 113 | + |
| 114 | + |
| 115 | +def _located_bitcode_lib(name: str, abs_path: str) -> LocatedBitcodeLib: |
| 116 | + return LocatedBitcodeLib( |
| 117 | + name=name, |
| 118 | + abs_path=abs_path, |
| 119 | + filename=os.path.basename(abs_path), |
| 120 | + found_via="CUDA_PATH", |
| 121 | + ) |
| 122 | + |
| 123 | + |
| 124 | +def _driver_cuda_version(encoded: int) -> DriverCudaVersion: |
| 125 | + return DriverCudaVersion.from_encoded(encoded) |
| 126 | + |
| 127 | + |
| 128 | +def _driver_release_version(text: str) -> DriverReleaseVersion: |
| 129 | + return DriverReleaseVersion.from_text(text) |
| 130 | + |
| 131 | + |
| 132 | +class _FakeDistribution: |
| 133 | + def __init__( |
| 134 | + self, |
| 135 | + *, |
| 136 | + name: str, |
| 137 | + version: str, |
| 138 | + root: Path, |
| 139 | + files: tuple[str, ...] = (), |
| 140 | + requires: tuple[str, ...] = (), |
| 141 | + ) -> None: |
| 142 | + self.metadata = {"Name": name} |
| 143 | + self.version = version |
| 144 | + self.files = tuple(Path(file) for file in files) |
| 145 | + self.requires = list(requires) |
| 146 | + self._root = root |
| 147 | + |
| 148 | + def locate_file(self, file: Path) -> Path: |
| 149 | + return self._root / file |
| 150 | + |
| 151 | + |
| 152 | +def _assert_real_ctk_backed_path(path: str) -> None: |
| 153 | + norm_path = os.path.normpath(os.path.abspath(path)) |
| 154 | + if "site-packages" in Path(norm_path).parts: |
| 155 | + return |
| 156 | + current = Path(norm_path) |
| 157 | + if current.is_file(): |
| 158 | + current = current.parent |
| 159 | + for candidate in (current, *current.parents): |
| 160 | + if (candidate / "include" / "cuda.h").is_file(): |
| 161 | + return |
| 162 | + if any(path.is_file() for path in (candidate / "targets").glob("*/include/cuda.h")): |
| 163 | + return |
| 164 | + for env_var in ("CUDA_PATH", "CUDA_HOME"): |
| 165 | + ctk_root = os.environ.get(env_var) |
| 166 | + if not ctk_root: |
| 167 | + continue |
| 168 | + norm_ctk_root = os.path.normpath(os.path.abspath(ctk_root)) |
| 169 | + if os.path.commonpath((norm_path, norm_ctk_root)) == norm_ctk_root: |
| 170 | + return |
| 171 | + raise AssertionError( |
| 172 | + "Expected a site-packages path, a path under a CTK root with cuda.h, " |
| 173 | + f"or a path under CUDA_PATH/CUDA_HOME, got {path!r}" |
| 174 | + ) |
| 175 | + |
| 176 | + |
| 177 | +class _DelegatingProcessWideGuardRails: |
| 178 | + def __init__(self, method_name: str, return_value: object) -> None: |
| 179 | + self._method_name = method_name |
| 180 | + self._return_value = return_value |
| 181 | + self.calls: list[tuple[str, tuple[object, ...]]] = [] |
| 182 | + |
| 183 | + def __getattr__(self, name: str): |
| 184 | + if name != self._method_name: |
| 185 | + raise AttributeError(name) |
| 186 | + |
| 187 | + def delegated(*args: object) -> object: |
| 188 | + self.calls.append((name, args)) |
| 189 | + return self._return_value |
| 190 | + |
| 191 | + return delegated |
0 commit comments