diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index f1b8eb9f3a2..e354a204d02 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -335,7 +335,6 @@ jobs: - name: Install test dependencies and coverage tools run: | - .venv/Scripts/pip install -v ./cuda_python_test_helpers .venv/Scripts/pip install coverage pytest-cov Cython .venv/Scripts/pip install --group ./cuda_pathfinder/pyproject.toml:test .venv/Scripts/pip install --group ./cuda_bindings/pyproject.toml:test diff --git a/cuda_bindings/cuda/bindings/_test_helpers/__init__.py b/cuda_bindings/cuda/bindings/_test_helpers/__init__.py deleted file mode 100644 index 2cfab242d2a..00000000000 --- a/cuda_bindings/cuda/bindings/_test_helpers/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - - -# This package contains test helper utilities that may also be useful for other libraries outside of `cuda.bindings`, -# such as `cuda.core`. These utilities are not part of the public API of `cuda.bindings` and may change without notice. diff --git a/cuda_bindings/cuda/bindings/_test_helpers/arch_check.py b/cuda_bindings/cuda/bindings/_test_helpers/arch_check.py deleted file mode 100644 index 3ab48be6e02..00000000000 --- a/cuda_bindings/cuda/bindings/_test_helpers/arch_check.py +++ /dev/null @@ -1,70 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - - -from contextlib import contextmanager -from functools import cache - -import pytest - -from cuda.bindings import nvml -from cuda.bindings._internal.utils import FunctionNotFoundError as NvmlSymbolNotFoundError - - -@cache -def hardware_supports_nvml(): - """ - Tries to call the simplest NVML API possible to see if just the basics - works. If not we are probably on one of the platforms where NVML is not - supported at all (e.g. Jetson Orin). - """ - nvml.init_v2() - try: - nvml.system_get_driver_branch() - except (nvml.NotSupportedError, nvml.UnknownError): - return False - else: - return True - finally: - nvml.shutdown() - - -@contextmanager -def unsupported_before(device: int, expected_device_arch: nvml.DeviceArch | str | None): - device_arch = nvml.device_get_architecture(device) - - if isinstance(expected_device_arch, nvml.DeviceArch): - expected_device_arch_int = int(expected_device_arch) - elif expected_device_arch == "FERMI": - expected_device_arch_int = 1 - else: - expected_device_arch_int = 0 - - if expected_device_arch is None or expected_device_arch == "HAS_INFOROM" or device_arch == nvml.DeviceArch.UNKNOWN: - # In this case, we don't /know/ if it will fail, but we are ok if it - # does or does not. - - # TODO: There are APIs that are documented as supported only if the - # device has an InfoROM, but I couldn't find a way to detect that. For - # now, they are just handled as "possibly failing". - - try: - yield - except (nvml.NotSupportedError, nvml.FunctionNotFoundError, NvmlSymbolNotFoundError): - # The API call raised NotSupportedError, NVML status FunctionNotFoundError, - # or NvmlSymbolNotFoundError (symbol absent from the loaded NVML DLL), so we - # skip the test but don't fail it - pytest.skip( - f"Unsupported call for device architecture {nvml.DeviceArch(device_arch).name} " - f"on device '{nvml.device_get_name(device)}'" - ) - # If the API call worked, just continue - elif int(device_arch) < expected_device_arch_int: - # In this case, we /know/ if will fail, and we want to assert that it does. - with pytest.raises(nvml.NotSupportedError): - yield - # The above call was unsupported, so the rest of the test is skipped - pytest.skip(f"Unsupported before {expected_device_arch.name}, got {nvml.device_get_name(device)}") - else: - # In this case, we /know/ it should work, and if it fails, the test should fail. - yield diff --git a/cuda_bindings/pixi.toml b/cuda_bindings/pixi.toml index 943d57e11bb..9c97d16c981 100644 --- a/cuda_bindings/pixi.toml +++ b/cuda_bindings/pixi.toml @@ -15,6 +15,9 @@ cuda-version = ["12.*", "13.3.*"] [feature.test.dependencies] cuda-bindings = { path = "." } pytest = ">=6.2.4" + +[feature.test.pypi-dependencies] +cuda-python-test-helpers = { path = "../cuda_python_test_helpers", editable = true } pytest-benchmark = ">=3.4.1" pytest-randomly = "*" pytest-repeat = "*" diff --git a/cuda_bindings/tests/conftest.py b/cuda_bindings/tests/conftest.py index 1618d63a133..fada7d95601 100644 --- a/cuda_bindings/tests/conftest.py +++ b/cuda_bindings/tests/conftest.py @@ -2,30 +2,32 @@ # SPDX-License-Identifier: Apache-2.0 import functools +import importlib import inspect import pathlib import sys from contextlib import contextmanager -from importlib.metadata import PackageNotFoundError, distribution import pytest import cuda.bindings.driver as cuda -# Import shared test helpers for tests across subprojects. -# PLEASE KEEP IN SYNC with copies in other conftest.py in this repo. -_test_helpers_root = pathlib.Path(__file__).resolve().parents[2] / "cuda_python_test_helpers" +# Keep in sync with cuda_core/tests/conftest.py. try: - distribution("cuda-python-test-helpers") -except PackageNotFoundError as exc: + import cuda_python_test_helpers._pytest_plugin # noqa: F401 +except ImportError as e: + # Don't call .resolve(): resolving symlinks can make parents[2] point + # somewhere other than the monorepo root if a sub-directory is symlinked. + _test_helpers_root = pathlib.Path(__file__).parents[2] / "cuda_python_test_helpers" if not _test_helpers_root.is_dir(): - raise RuntimeError( - f"cuda-python-test-helpers not installed; expected checkout path {_test_helpers_root}" - ) from exc - - test_helpers_root = str(_test_helpers_root) - if test_helpers_root not in sys.path: - sys.path.insert(0, test_helpers_root) + raise RuntimeError(f"cuda-python-test-helpers not installed and not found at {_test_helpers_root}") from e + for _k in list(sys.modules): + if _k == "cuda_python_test_helpers" or _k.startswith("cuda_python_test_helpers."): + del sys.modules[_k] + sys.path.insert(0, str(_test_helpers_root)) + importlib.invalidate_caches() + +pytest_plugins = ["cuda_python_test_helpers._pytest_plugin"] def pytest_configure(config): diff --git a/cuda_bindings/tests/nvml/__init__.py b/cuda_bindings/tests/nvml/__init__.py index c746f897d2d..4baf1b49bc0 100644 --- a/cuda_bindings/tests/nvml/__init__.py +++ b/cuda_bindings/tests/nvml/__init__.py @@ -3,8 +3,7 @@ import pytest - -from cuda.bindings._test_helpers.arch_check import hardware_supports_nvml +from cuda_python_test_helpers.arch_check import hardware_supports_nvml if not hardware_supports_nvml(): pytest.skip("NVML not supported on this platform", allow_module_level=True) diff --git a/cuda_bindings/tests/nvml/conftest.py b/cuda_bindings/tests/nvml/conftest.py index 9897420e38d..7fb1aed4be4 100644 --- a/cuda_bindings/tests/nvml/conftest.py +++ b/cuda_bindings/tests/nvml/conftest.py @@ -4,9 +4,9 @@ from collections import namedtuple import pytest +from cuda_python_test_helpers.arch_check import unsupported_before # noqa: F401 from cuda.bindings import nvml -from cuda.bindings._test_helpers.arch_check import unsupported_before # noqa: F401 class NVMLInitializer: diff --git a/cuda_bindings/tests/nvml/test_pynvml.py b/cuda_bindings/tests/nvml/test_pynvml.py index 2d1029e9d9b..4f190a20114 100644 --- a/cuda_bindings/tests/nvml/test_pynvml.py +++ b/cuda_bindings/tests/nvml/test_pynvml.py @@ -9,8 +9,8 @@ import pytest from cuda.bindings import nvml +from cuda_python_test_helpers import IS_WINDOWS, IS_WSL -from . import util from .conftest import unsupported_before XFAIL_LEGACY_NVLINK_MSG = "Legacy NVLink test expected to fail." @@ -64,7 +64,7 @@ def test_device_get_handle_by_pci_bus_id(ngpus, pci_info): @pytest.mark.parametrize("scope", [nvml.AffinityScope.NODE, nvml.AffinityScope.SOCKET]) -@pytest.mark.skipif(util.is_wsl() or util.is_windows(), reason="Not supported on WSL or Windows") +@pytest.mark.skipif(IS_WSL or IS_WINDOWS, reason="Not supported on WSL or Windows") def test_device_get_memory_affinity(handles, scope): size = 1024 for handle in handles: @@ -75,7 +75,7 @@ def test_device_get_memory_affinity(handles, scope): @pytest.mark.parametrize("scope", [nvml.AffinityScope.NODE, nvml.AffinityScope.SOCKET]) -@pytest.mark.skipif(util.is_wsl() or util.is_windows(), reason="Not supported on WSL or Windows") +@pytest.mark.skipif(IS_WSL or IS_WINDOWS, reason="Not supported on WSL or Windows") def test_device_get_cpu_affinity_within_scope(handles, scope): size = 1024 for handle in handles: diff --git a/cuda_bindings/tests/nvml/util.py b/cuda_bindings/tests/nvml/util.py index 038fe58d8be..129ded8f83c 100644 --- a/cuda_bindings/tests/nvml/util.py +++ b/cuda_bindings/tests/nvml/util.py @@ -2,29 +2,8 @@ # SPDX-License-Identifier: Apache-2.0 -import functools -import platform -from pathlib import Path - from cuda.bindings import nvml -current_os = platform.system() -if current_os == "VMkernel": - current_os = "Linux" # Treat VMkernel as Linux - - -def is_windows(os=current_os): - return os == "Windows" - - -def is_linux(os=current_os): - return os == "Linux" - - -@functools.cache -def is_wsl(os=current_os): - return os == "Linux" and "microsoft" in Path("/proc/version").read_text().lower() - def is_vgpu(device): """ diff --git a/cuda_bindings/tests/test_cuda.py b/cuda_bindings/tests/test_cuda.py index 192ad0f72fe..cecc99371cf 100644 --- a/cuda_bindings/tests/test_cuda.py +++ b/cuda_bindings/tests/test_cuda.py @@ -10,11 +10,11 @@ import numpy as np import pytest +from cuda_python_test_helpers.mempool import xfail_if_mempool_oom import cuda.bindings.driver as cuda import cuda.bindings.runtime as cudart from cuda.bindings import driver -from cuda.bindings._test_helpers.mempool import xfail_if_mempool_oom def driverVersionLessThan(target): diff --git a/cuda_bindings/tests/test_cudart.py b/cuda_bindings/tests/test_cudart.py index ddb4448499b..79a3acf6d83 100644 --- a/cuda_bindings/tests/test_cudart.py +++ b/cuda_bindings/tests/test_cudart.py @@ -6,12 +6,12 @@ import numpy as np import pytest +from cuda_python_test_helpers.mempool import xfail_if_mempool_oom import cuda.bindings.driver as cuda import cuda.bindings.runtime as cudart from cuda import pathfinder from cuda.bindings import runtime -from cuda.bindings._test_helpers.mempool import xfail_if_mempool_oom def isSuccess(err): diff --git a/cuda_bindings/tests/test_examples.py b/cuda_bindings/tests/test_examples.py index 63a56c78fb7..652515830f8 100644 --- a/cuda_bindings/tests/test_examples.py +++ b/cuda_bindings/tests/test_examples.py @@ -7,8 +7,7 @@ import sys import pytest - -from cuda.bindings._test_helpers.pep723 import has_package_requirements_or_skip +from cuda_python_test_helpers.pep723 import has_package_requirements_or_skip examples_path = os.path.join(os.path.dirname(__file__), "..", "examples") examples_files = glob.glob(os.path.join(examples_path, "**/*.py"), recursive=True) diff --git a/cuda_bindings/tests/test_interoperability.py b/cuda_bindings/tests/test_interoperability.py index 18a37ec6b4e..08bac311a2d 100644 --- a/cuda_bindings/tests/test_interoperability.py +++ b/cuda_bindings/tests/test_interoperability.py @@ -3,10 +3,10 @@ import numpy as np import pytest +from cuda_python_test_helpers.mempool import xfail_if_mempool_oom import cuda.bindings.driver as cuda import cuda.bindings.runtime as cudart -from cuda.bindings._test_helpers.mempool import xfail_if_mempool_oom def supportsMemoryPool(): diff --git a/cuda_core/pixi.toml b/cuda_core/pixi.toml index 8772ed4e88b..a3cad6c58ae 100644 --- a/cuda_core/pixi.toml +++ b/cuda_core/pixi.toml @@ -16,6 +16,9 @@ cuda-version = ["12.*", "13.3.*"] cuda-core = { path = "." } ml_dtypes = "*" pytest = "*" + +[feature.test.pypi-dependencies] +cuda-python-test-helpers = { path = "../cuda_python_test_helpers", editable = true } pytest-benchmark = "*" pytest-randomly = "*" pytest-repeat = "*" diff --git a/cuda_core/tests/conftest.py b/cuda_core/tests/conftest.py index 7106b1e31f6..6ef1ca86441 100644 --- a/cuda_core/tests/conftest.py +++ b/cuda_core/tests/conftest.py @@ -2,15 +2,35 @@ # SPDX-License-Identifier: Apache-2.0 import functools +import importlib import multiprocessing import os import pathlib import sys from contextlib import contextmanager -from importlib.metadata import PackageNotFoundError, distribution import pytest +# Keep in sync with cuda_bindings/tests/conftest.py. +try: + import cuda_python_test_helpers._pytest_plugin # noqa: F401 +except ImportError as e: + # Don't call .resolve(): resolving symlinks can make parents[2] point + # somewhere other than the monorepo root if a sub-directory is symlinked. + _test_helpers_root = pathlib.Path(__file__).parents[2] / "cuda_python_test_helpers" + if not _test_helpers_root.is_dir(): + raise RuntimeError(f"cuda-python-test-helpers not installed and not found at {_test_helpers_root}") from e + for _k in list(sys.modules): + if _k == "cuda_python_test_helpers" or _k.startswith("cuda_python_test_helpers."): + del sys.modules[_k] + sys.path.insert(0, str(_test_helpers_root)) + importlib.invalidate_caches() + +pytest_plugins = ["cuda_python_test_helpers._pytest_plugin"] + +from cuda_python_test_helpers.marks import skipif_need_cuda_headers # noqa: F401 (re-exported for tests) +from cuda_python_test_helpers.mempool import xfail_if_mempool_oom + import cuda.core from cuda.bindings import driver from cuda.core import ( @@ -24,72 +44,6 @@ _device, ) from cuda.core._utils.cuda_utils import CUDAError, handle_return -from cuda.pathfinder import get_cuda_path_or_home - -try: - from cuda.bindings._test_helpers.mempool import xfail_if_mempool_oom -except ModuleNotFoundError: - # Older cuda.bindings artifacts (for example 12.9.x backports) do not ship - # this helper yet. Keep the fallback local so tests against published - # bindings still xfail the known Windows MCDM mempool setup issue. - # - # Keep in sync with cuda_bindings/cuda/bindings/_test_helpers/mempool.py. - # This copy is intentionally simpler because it only handles cuda_core - # CUDAError exceptions when the shared helper is absent. - def _is_windows_mcdm_device(device=0): - if sys.platform != "win32": - return False - import cuda.bindings.nvml as nvml - - device_id = int(getattr(device, "device_id", device)) - (err,) = driver.cuInit(0) - if err != driver.CUresult.CUDA_SUCCESS: - return False - err, pci_bus_id = driver.cuDeviceGetPCIBusId(13, device_id) - if err != driver.CUresult.CUDA_SUCCESS: - return False - pci_bus_id = pci_bus_id.split(b"\x00", 1)[0].decode("ascii") - nvml.init_v2() - try: - handle = nvml.device_get_handle_by_pci_bus_id_v2(pci_bus_id) - current, _ = nvml.device_get_driver_model_v2(handle) - return current == nvml.DriverModel.DRIVER_MCDM - finally: - nvml.shutdown() - - def xfail_if_mempool_oom(err_or_exc, api_name=None, device=0): - if api_name is not None and not isinstance(api_name, str): - device = api_name - api_name = None - - if "CUDA_ERROR_OUT_OF_MEMORY" not in str(err_or_exc): - return - try: - is_windows_mcdm = _is_windows_mcdm_device(device) - except Exception: - # If MCDM detection fails, leave the primary test failure visible. - return - if not is_windows_mcdm: - return - - api_context = f"{api_name} " if api_name else "" - pytest.xfail(f"{api_context}could not reserve VA for mempool operations on Windows MCDM") - - -# Import shared test helpers for tests across subprojects. -# PLEASE KEEP IN SYNC with copies in other conftest.py in this repo. -_test_helpers_root = pathlib.Path(__file__).resolve().parents[2] / "cuda_python_test_helpers" -try: - distribution("cuda-python-test-helpers") -except PackageNotFoundError as exc: - if not _test_helpers_root.is_dir(): - raise RuntimeError( - f"cuda-python-test-helpers not installed; expected checkout path {_test_helpers_root}" - ) from exc - - test_helpers_root = str(_test_helpers_root) - if test_helpers_root not in sys.path: - sys.path.insert(0, test_helpers_root) def pytest_configure(config): @@ -240,7 +194,9 @@ def _device_id_from_resource_options(device, args, kwargs): def _require_ipc_mempool_devices(devices): """Return devices if they all support IPC-enabled mempools, otherwise skip.""" - from helpers import IS_WSL, supports_ipc_mempool + from helpers import supports_ipc_mempool + + from cuda_python_test_helpers import IS_WSL checked_devices = tuple(devices) @@ -433,24 +389,3 @@ def test_something(memory_resource_factory): mr = MRClass() """ return request.param - - -# Please keep in sync with the copy in the top-level conftest.py. -def _cuda_headers_available() -> bool: - """Return True if CUDA headers are available, False if no CUDA path is set. - - Raises AssertionError if a CUDA path is set but has no include/ subdirectory. - """ - cuda_path = get_cuda_path_or_home() - if cuda_path is None: - return False - assert os.path.isdir(os.path.join(cuda_path, "include")), ( - f"CUDA path {cuda_path} does not contain an 'include' subdirectory" - ) - return True - - -skipif_need_cuda_headers = pytest.mark.skipif( - not _cuda_headers_available(), - reason="need CUDA header", -) diff --git a/cuda_core/tests/example_tests/test_basic_examples.py b/cuda_core/tests/example_tests/test_basic_examples.py index a8a47791991..bf423758366 100644 --- a/cuda_core/tests/example_tests/test_basic_examples.py +++ b/cuda_core/tests/example_tests/test_basic_examples.py @@ -11,17 +11,11 @@ import warnings import pytest +from cuda_python_test_helpers.pep723 import has_package_requirements_or_skip from cuda.core import Device, ManagedMemoryResource, system from cuda.core._program import _can_load_generated_ptx -try: - from cuda.bindings._test_helpers.pep723 import has_package_requirements_or_skip -except ImportError: - # If the import fails, we define a dummy function that will cause all tests to be skipped. - def has_package_requirements_or_skip(example): - pytest.skip("PEP 723 test helper is not available") - def has_compute_capability_9_or_higher() -> bool: return Device().compute_capability >= (9, 0) diff --git a/cuda_core/tests/graph/test_device_launch.py b/cuda_core/tests/graph/test_device_launch.py index 221b09bd815..d77ceeec37f 100644 --- a/cuda_core/tests/graph/test_device_launch.py +++ b/cuda_core/tests/graph/test_device_launch.py @@ -5,7 +5,7 @@ import numpy as np import pytest -from helpers.marks import requires_module +from cuda_python_test_helpers.marks import requires_module from cuda.core import ( Device, diff --git a/cuda_core/tests/graph/test_graph_builder.py b/cuda_core/tests/graph/test_graph_builder.py index 6484c2f8129..17eab461be6 100644 --- a/cuda_core/tests/graph/test_graph_builder.py +++ b/cuda_core/tests/graph/test_graph_builder.py @@ -7,8 +7,8 @@ import numpy as np import pytest +from cuda_python_test_helpers.marks import requires_module from helpers.graph_kernels import compile_common_kernels, compile_conditional_kernels -from helpers.marks import requires_module from helpers.misc import try_create_condition from cuda.core import Device, LaunchConfig, LegacyPinnedMemoryResource, launch diff --git a/cuda_core/tests/graph/test_graph_builder_conditional.py b/cuda_core/tests/graph/test_graph_builder_conditional.py index 0bb779a8bf7..150d43bfc14 100644 --- a/cuda_core/tests/graph/test_graph_builder_conditional.py +++ b/cuda_core/tests/graph/test_graph_builder_conditional.py @@ -7,8 +7,8 @@ import numpy as np import pytest +from cuda_python_test_helpers.marks import requires_module from helpers.graph_kernels import compile_conditional_kernels -from helpers.marks import requires_module from cuda.core import Device, LaunchConfig, LegacyPinnedMemoryResource, launch from cuda.core.graph import GraphBuilder diff --git a/cuda_core/tests/graph/test_graph_definition.py b/cuda_core/tests/graph/test_graph_definition.py index 50d4b4ac253..ca8b406434b 100644 --- a/cuda_core/tests/graph/test_graph_definition.py +++ b/cuda_core/tests/graph/test_graph_definition.py @@ -7,10 +7,10 @@ from dataclasses import dataclass, field import pytest +from conftest import xfail_on_graph_mempool_oom from helpers.graph_kernels import compile_common_kernels from helpers.misc import try_create_condition -from conftest import xfail_on_graph_mempool_oom from cuda.core import Device, LaunchConfig from cuda.core.graph import ( AllocNode, diff --git a/cuda_core/tests/graph/test_graph_definition_errors.py b/cuda_core/tests/graph/test_graph_definition_errors.py index a8a3c9b8f09..d80118cdf7c 100644 --- a/cuda_core/tests/graph/test_graph_definition_errors.py +++ b/cuda_core/tests/graph/test_graph_definition_errors.py @@ -6,10 +6,10 @@ import ctypes import pytest +from conftest import xfail_on_graph_mempool_oom from helpers.graph_kernels import compile_common_kernels from helpers.misc import try_create_condition -from conftest import xfail_on_graph_mempool_oom from cuda.core import Device, LaunchConfig from cuda.core._utils.cuda_utils import CUDAError from cuda.core.graph import ( diff --git a/cuda_core/tests/graph/test_graph_definition_integration.py b/cuda_core/tests/graph/test_graph_definition_integration.py index 12b57bb73a5..58f96e1bab3 100644 --- a/cuda_core/tests/graph/test_graph_definition_integration.py +++ b/cuda_core/tests/graph/test_graph_definition_integration.py @@ -7,8 +7,8 @@ import numpy as np import pytest - from conftest import xfail_on_graph_mempool_oom + from cuda.core import Device, EventOptions, LaunchConfig, Program, ProgramOptions from cuda.core._utils.cuda_utils import driver, handle_return from cuda.core.graph import GraphDefinition diff --git a/cuda_core/tests/graph/test_graph_definition_lifetime.py b/cuda_core/tests/graph/test_graph_definition_lifetime.py index a01ff8162f3..893e500948b 100644 --- a/cuda_core/tests/graph/test_graph_definition_lifetime.py +++ b/cuda_core/tests/graph/test_graph_definition_lifetime.py @@ -13,10 +13,10 @@ import weakref import pytest +from conftest import xfail_on_graph_mempool_oom from helpers.graph_kernels import compile_common_kernels from helpers.misc import try_create_condition -from conftest import xfail_on_graph_mempool_oom from cuda_python_test_helpers import under_compute_sanitizer # Resource finalization triggered by graph destruction is not synchronous. A diff --git a/cuda_core/tests/graph/test_graph_definition_mutation.py b/cuda_core/tests/graph/test_graph_definition_mutation.py index 7ac3a9e9853..7a890040c40 100644 --- a/cuda_core/tests/graph/test_graph_definition_mutation.py +++ b/cuda_core/tests/graph/test_graph_definition_mutation.py @@ -5,9 +5,9 @@ import numpy as np import pytest +from cuda_python_test_helpers.marks import requires_module from helpers.collection_interface_testers import assert_mutable_set_interface from helpers.graph_kernels import compile_parallel_kernels -from helpers.marks import requires_module from cuda.core import Device, LaunchConfig, LegacyPinnedMemoryResource from cuda.core._utils.cuda_utils import CUDAError diff --git a/cuda_core/tests/graph/test_graph_memory_resource.py b/cuda_core/tests/graph/test_graph_memory_resource.py index 9fc794f4cca..517f9c080b7 100644 --- a/cuda_core/tests/graph/test_graph_memory_resource.py +++ b/cuda_core/tests/graph/test_graph_memory_resource.py @@ -5,10 +5,9 @@ """Tests for GraphMemoryResource allocation and attributes during graph capture.""" import pytest -from helpers import IS_WINDOWS, IS_WSL +from conftest import xfail_on_graph_mempool_oom from helpers.buffers import compare_buffer_to_constant, make_scratch_buffer, set_buffer -from conftest import xfail_on_graph_mempool_oom from cuda.core import ( Device, DeviceMemoryResource, @@ -20,6 +19,7 @@ ) from cuda.core._utils.cuda_utils import CUDAError from cuda.core.graph import GraphCompleteOptions +from cuda_python_test_helpers import IS_WINDOWS, IS_WSL def _common_kernels_alloc(): diff --git a/cuda_core/tests/graph/test_graph_update.py b/cuda_core/tests/graph/test_graph_update.py index 556c187aeef..6674a9166d0 100644 --- a/cuda_core/tests/graph/test_graph_update.py +++ b/cuda_core/tests/graph/test_graph_update.py @@ -5,8 +5,8 @@ import numpy as np import pytest +from cuda_python_test_helpers.marks import requires_module from helpers.graph_kernels import compile_common_kernels, compile_conditional_kernels -from helpers.marks import requires_module from cuda.core import Device, LaunchConfig, LegacyPinnedMemoryResource, launch from cuda.core._utils.cuda_utils import CUDAError diff --git a/cuda_core/tests/memory/test_managed_ops.py b/cuda_core/tests/memory/test_managed_ops.py index 33def77935f..ed7f44a97f4 100644 --- a/cuda_core/tests/memory/test_managed_ops.py +++ b/cuda_core/tests/memory/test_managed_ops.py @@ -4,9 +4,9 @@ import mmap import pytest +from conftest import create_managed_memory_resource_or_skip from helpers.buffers import DummyDeviceMemoryResource, DummyUnifiedMemoryResource -from conftest import create_managed_memory_resource_or_skip from cuda.bindings import driver from cuda.core import Device, Host, ManagedBuffer from cuda.core._memory._managed_buffer import _get_int_attr diff --git a/cuda_core/tests/system/conftest.py b/cuda_core/tests/system/conftest.py deleted file mode 100644 index 8708b3f06fc..00000000000 --- a/cuda_core/tests/system/conftest.py +++ /dev/null @@ -1,28 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: Apache-2.0 - - -import pytest - -from cuda.core import system - -SHOULD_SKIP_NVML_TESTS = not system.CUDA_BINDINGS_NVML_IS_COMPATIBLE - - -if system.CUDA_BINDINGS_NVML_IS_COMPATIBLE: - from cuda.bindings._test_helpers.arch_check import hardware_supports_nvml - - SHOULD_SKIP_NVML_TESTS |= not hardware_supports_nvml() - - -skip_if_nvml_unsupported = pytest.mark.skipif( - SHOULD_SKIP_NVML_TESTS, - reason="NVML support requires cuda.bindings version 12.9.6+ for CUDA 12.x or 13.2.0+ for CUDA 13.x, and hardware that supports NVML", -) - - -def unsupported_before(device, expected_device_arch): - from cuda.bindings._test_helpers.arch_check import unsupported_before as nvml_unsupported_before - - return nvml_unsupported_before(device._handle, expected_device_arch) diff --git a/cuda_core/tests/system/test_nvml_context.py b/cuda_core/tests/system/test_nvml_context.py index 16bc97f385c..03c3fbefe8b 100644 --- a/cuda_core/tests/system/test_nvml_context.py +++ b/cuda_core/tests/system/test_nvml_context.py @@ -1,9 +1,9 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 -from .conftest import skip_if_nvml_unsupported +from cuda_python_test_helpers.arch_check import skip_if_nvml_unsupported pytestmark = skip_if_nvml_unsupported diff --git a/cuda_core/tests/system/test_system_device.py b/cuda_core/tests/system/test_system_device.py index b5fe8cccbfa..4cdb8b1e8fc 100644 --- a/cuda_core/tests/system/test_system_device.py +++ b/cuda_core/tests/system/test_system_device.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 -from .conftest import skip_if_nvml_unsupported, unsupported_before +from cuda_python_test_helpers.arch_check import skip_if_nvml_unsupported, unsupported_before pytestmark = skip_if_nvml_unsupported diff --git a/cuda_core/tests/system/test_system_events.py b/cuda_core/tests/system/test_system_events.py index ce204001a4e..d2684bebd0b 100644 --- a/cuda_core/tests/system/test_system_events.py +++ b/cuda_core/tests/system/test_system_events.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 -from .conftest import skip_if_nvml_unsupported +from cuda_python_test_helpers.arch_check import skip_if_nvml_unsupported pytestmark = skip_if_nvml_unsupported diff --git a/cuda_core/tests/system/test_system_system.py b/cuda_core/tests/system/test_system_system.py index 460078918f5..28173836ff4 100644 --- a/cuda_core/tests/system/test_system_system.py +++ b/cuda_core/tests/system/test_system_system.py @@ -6,13 +6,12 @@ import os import pytest +from cuda_python_test_helpers.arch_check import skip_if_nvml_unsupported from cuda.bindings import driver from cuda.core import system from cuda.core._utils.cuda_utils import handle_return -from .conftest import skip_if_nvml_unsupported - def test_user_mode_driver_version(): umd = system.get_user_mode_driver_version() diff --git a/cuda_core/tests/test_device.py b/cuda_core/tests/test_device.py index 6971911cec5..4cbd28398f3 100644 --- a/cuda_core/tests/test_device.py +++ b/cuda_core/tests/test_device.py @@ -27,7 +27,7 @@ def test_to_system_device(deinit_cuda): device.to_system_device() pytest.skip("NVML support requires cuda.bindings version 12.9.6+ for CUDA 12.x or 13.2.0+ for CUDA 13.x") - from cuda.bindings._test_helpers.arch_check import hardware_supports_nvml + from cuda_python_test_helpers.arch_check import hardware_supports_nvml if not hardware_supports_nvml(): pytest.skip("NVML not supported on this platform") diff --git a/cuda_core/tests/test_helpers.py b/cuda_core/tests/test_helpers.py index 9cf93fbd21d..0b755d063b8 100644 --- a/cuda_core/tests/test_helpers.py +++ b/cuda_core/tests/test_helpers.py @@ -5,13 +5,12 @@ import time import pytest -from helpers import IS_WINDOWS, IS_WSL from helpers.buffers import PatternGen, compare_equal_buffers, make_scratch_buffer from helpers.latch import LatchKernel from helpers.logging import TimestampedLogger from cuda.core import Device -from cuda_python_test_helpers import under_compute_sanitizer +from cuda_python_test_helpers import IS_WINDOWS, IS_WSL, under_compute_sanitizer ENABLE_LOGGING = False # Set True for test debugging and development NBYTES = 64 diff --git a/cuda_core/tests/test_launcher.py b/cuda_core/tests/test_launcher.py index 942952d29b8..98f4a740469 100644 --- a/cuda_core/tests/test_launcher.py +++ b/cuda_core/tests/test_launcher.py @@ -4,7 +4,7 @@ import ctypes import helpers -from helpers.marks import requires_module +from cuda_python_test_helpers.marks import requires_module from helpers.misc import StreamWrapper try: @@ -13,8 +13,8 @@ cp = None import numpy as np import pytest - from conftest import skipif_need_cuda_headers + from cuda.core import ( Device, DeviceMemoryResource, diff --git a/cuda_core/tests/test_managed_memory_warning.py b/cuda_core/tests/test_managed_memory_warning.py index 01dd840e2ef..f0596db2fdf 100644 --- a/cuda_core/tests/test_managed_memory_warning.py +++ b/cuda_core/tests/test_managed_memory_warning.py @@ -11,9 +11,9 @@ import warnings import pytest +from conftest import create_managed_memory_resource_or_skip, xfail_if_mempool_oom import cuda.bindings -from conftest import create_managed_memory_resource_or_skip, xfail_if_mempool_oom from cuda.core import Device, ManagedMemoryResource, ManagedMemoryResourceOptions from cuda.core._memory._managed_memory_resource import reset_concurrent_access_warning from cuda.core._utils.cuda_utils import CUDAError diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index 5ef48919a50..d43c6ad8796 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -14,15 +14,15 @@ import re import pytest -from helpers import IS_WINDOWS, supports_ipc_mempool -from helpers.buffers import DummyDeviceMemoryResource, DummyUnifiedMemoryResource, TrackingMR - from conftest import ( create_managed_memory_resource_or_skip, create_pinned_memory_resource_or_xfail, skip_if_managed_memory_unsupported, skip_if_pinned_memory_unsupported, ) +from helpers import supports_ipc_mempool +from helpers.buffers import DummyDeviceMemoryResource, DummyUnifiedMemoryResource, TrackingMR + from cuda.core import ( Buffer, Device, @@ -53,6 +53,7 @@ VirtualMemoryLocationType, ) from cuda.core.utils import StridedMemoryView +from cuda_python_test_helpers import IS_WINDOWS POOL_SIZE = 2097152 # 2MB size diff --git a/cuda_core/tests/test_object_protocols.py b/cuda_core/tests/test_object_protocols.py index e8391c75678..f843f451683 100644 --- a/cuda_core/tests/test_object_protocols.py +++ b/cuda_core/tests/test_object_protocols.py @@ -13,10 +13,10 @@ import weakref import pytest +from conftest import xfail_on_graph_mempool_oom from helpers.graph_kernels import compile_common_kernels from helpers.misc import try_create_condition -from conftest import xfail_on_graph_mempool_oom from cuda.core import ( Buffer, Device, diff --git a/cuda_core/tests/test_tensor_map.py b/cuda_core/tests/test_tensor_map.py index 7abbaadb483..4a5a3652887 100644 --- a/cuda_core/tests/test_tensor_map.py +++ b/cuda_core/tests/test_tensor_map.py @@ -3,8 +3,8 @@ import numpy as np import pytest - from conftest import create_managed_memory_resource_or_skip, skip_if_managed_memory_unsupported + from cuda.core import ( Device, ManagedMemoryResourceOptions, diff --git a/cuda_core/tests/test_utils.py b/cuda_core/tests/test_utils.py index ebee8d87b04..02e7833f2b5 100644 --- a/cuda_core/tests/test_utils.py +++ b/cuda_core/tests/test_utils.py @@ -26,7 +26,7 @@ ml_dtypes = None import numpy as np import pytest -from helpers.marks import requires_module +from cuda_python_test_helpers.marks import requires_module from cuda.core import Device from cuda.core._dlpack import DLDeviceType diff --git a/cuda_pathfinder/tests/test_driver_lib_loading.py b/cuda_pathfinder/tests/test_driver_lib_loading.py index b97453c9b5a..9436736310c 100644 --- a/cuda_pathfinder/tests/test_driver_lib_loading.py +++ b/cuda_pathfinder/tests/test_driver_lib_loading.py @@ -15,8 +15,8 @@ build_child_process_failed_for_libname_message, run_load_nvidia_dynamic_lib_in_subprocess, ) - from conftest import skip_if_missing_libnvcudla_so + from cuda.pathfinder._dynamic_libs.lib_descriptor import LIB_DESCRIPTORS from cuda.pathfinder._dynamic_libs.load_dl_common import DynamicLibNotFoundError, LoadedDL from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import ( diff --git a/cuda_pathfinder/tests/test_find_nvidia_headers.py b/cuda_pathfinder/tests/test_find_nvidia_headers.py index 90fe3cf9815..20190725884 100644 --- a/cuda_pathfinder/tests/test_find_nvidia_headers.py +++ b/cuda_pathfinder/tests/test_find_nvidia_headers.py @@ -20,9 +20,9 @@ from pathlib import Path import pytest +from conftest import skip_if_missing_libnvcudla_so import cuda.pathfinder._headers.find_nvidia_headers as find_nvidia_headers_module -from conftest import skip_if_missing_libnvcudla_so from cuda.pathfinder import LocatedHeaderDir, find_nvidia_header_directory, locate_nvidia_header_directory from cuda.pathfinder._dynamic_libs.load_nvidia_dynamic_lib import ( _resolve_system_loaded_abs_path_in_subprocess, diff --git a/cuda_pathfinder/tests/test_load_nvidia_dynamic_lib.py b/cuda_pathfinder/tests/test_load_nvidia_dynamic_lib.py index 3e240dcf468..4d5b064a7da 100644 --- a/cuda_pathfinder/tests/test_load_nvidia_dynamic_lib.py +++ b/cuda_pathfinder/tests/test_load_nvidia_dynamic_lib.py @@ -9,9 +9,9 @@ build_child_process_failed_for_libname_message, run_load_nvidia_dynamic_lib_in_subprocess, ) +from conftest import skip_if_missing_libnvcudla_so from local_helpers import have_distribution -from conftest import skip_if_missing_libnvcudla_so from cuda.pathfinder import DynamicLibNotAvailableError, DynamicLibUnknownError, load_nvidia_dynamic_lib from cuda.pathfinder._dynamic_libs import load_nvidia_dynamic_lib as load_nvidia_dynamic_lib_module from cuda.pathfinder._dynamic_libs import supported_nvidia_libs diff --git a/cuda_python_test_helpers/cuda_python_test_helpers/__init__.py b/cuda_python_test_helpers/cuda_python_test_helpers/__init__.py index 342c2477ffc..c67162483f5 100644 --- a/cuda_python_test_helpers/cuda_python_test_helpers/__init__.py +++ b/cuda_python_test_helpers/cuda_python_test_helpers/__init__.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import ctypes @@ -8,6 +8,7 @@ from contextlib import suppress __all__ = [ + "IS_LINUX", "IS_WINDOWS", "IS_WSL", "libc", @@ -26,6 +27,7 @@ def _detect_wsl() -> bool: IS_WSL: bool = _detect_wsl() IS_WINDOWS: bool = platform.system() == "Windows" or sys.platform.startswith("win") +IS_LINUX: bool = not IS_WINDOWS and not IS_WSL and platform.system() == "Linux" if IS_WINDOWS: libc = ctypes.CDLL("msvcrt.dll") diff --git a/conftest.py b/cuda_python_test_helpers/cuda_python_test_helpers/_pytest_plugin.py similarity index 72% rename from conftest.py rename to cuda_python_test_helpers/cuda_python_test_helpers/_pytest_plugin.py index 7a0c59065d5..e1da55dcaf0 100644 --- a/conftest.py +++ b/cuda_python_test_helpers/cuda_python_test_helpers/_pytest_plugin.py @@ -1,27 +1,18 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +"""Pytest plugin registered via the ``pytest11`` entry point. -import os +Automatically tags collected items with package markers and gates cython +tests on CUDA header availability. Loaded by pytest whenever +``cuda-python-test-helpers`` is installed, and also explicitly via +``pytest_plugins`` in each subpackage conftest so the fallback sys.path +install path is covered too. +""" import pytest -from cuda.pathfinder import get_cuda_path_or_home - - -# Please keep in sync with the copy in cuda_core/tests/conftest.py. -def _cuda_headers_available() -> bool: - """Return True if CUDA headers are available, False if no CUDA path is set. - - Raises AssertionError if a CUDA path is set but has no include/ subdirectory. - """ - cuda_path = get_cuda_path_or_home() - if cuda_path is None: - return False - assert os.path.isdir(os.path.join(cuda_path, "include")), ( - f"CUDA path {cuda_path} does not contain an 'include' subdirectory" - ) - return True +from cuda_python_test_helpers.marks import _cuda_headers_available def pytest_collection_modifyitems(config, items): # noqa: ARG001 diff --git a/cuda_python_test_helpers/cuda_python_test_helpers/arch_check.py b/cuda_python_test_helpers/cuda_python_test_helpers/arch_check.py new file mode 100644 index 00000000000..adb3563821f --- /dev/null +++ b/cuda_python_test_helpers/cuda_python_test_helpers/arch_check.py @@ -0,0 +1,93 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +from contextlib import contextmanager +from functools import cache + +import pytest + + +@cache +def hardware_supports_nvml(): + """Try the simplest NVML API to verify basic functionality. + + Returns False on platforms where NVML is unsupported (e.g. Jetson Orin). + """ + from cuda.bindings import nvml + from cuda.bindings._internal.utils import FunctionNotFoundError as NvmlSymbolNotFoundError # noqa: F401 + + nvml.init_v2() + try: + nvml.system_get_driver_branch() + except (nvml.NotSupportedError, nvml.UnknownError): + return False + else: + return True + finally: + nvml.shutdown() + + +def _should_skip_nvml_tests() -> bool: + """Return True if NVML tests should be skipped on this system. + + Checks cuda.core's compatibility gate first (if cuda.core is installed), + then falls back to a hardware-level NVML probe. + """ + try: + from cuda.core import system + + if not system.CUDA_BINDINGS_NVML_IS_COMPATIBLE: + return True + except ImportError: + pass # cuda.core not installed; skip the compat gate + return not hardware_supports_nvml() + + +skip_if_nvml_unsupported = pytest.mark.skipif( + _should_skip_nvml_tests(), + reason="NVML support requires cuda.bindings version 12.9.6+ for CUDA 12.x or 13.2.0+ for CUDA 13.x, and hardware that supports NVML", +) + + +@contextmanager +def unsupported_before(device, expected_device_arch): + """Context manager that skips or xfails when an NVML API is not supported on this device. + + ``device`` may be a raw NVML device handle (int) or any object that exposes + the handle via a ``._handle`` attribute (e.g. ``cuda.core.system.Device``). + """ + from cuda.bindings import nvml + from cuda.bindings._internal.utils import FunctionNotFoundError as NvmlSymbolNotFoundError + + handle = getattr(device, "_handle", device) + device_arch = nvml.device_get_architecture(handle) + + if isinstance(expected_device_arch, nvml.DeviceArch): + expected_device_arch_int = int(expected_device_arch) + elif expected_device_arch == "FERMI": + expected_device_arch_int = 1 + else: + expected_device_arch_int = 0 + + if expected_device_arch is None or expected_device_arch == "HAS_INFOROM" or device_arch == nvml.DeviceArch.UNKNOWN: + # We don't know if it will fail, so we tolerate either outcome. + # + # TODO: There are APIs that are documented as supported only if the + # device has an InfoROM, but I couldn't find a way to detect that. For + # now, they are just handled as "possibly failing". + try: + yield + except (nvml.NotSupportedError, nvml.FunctionNotFoundError, NvmlSymbolNotFoundError): + pytest.skip( + f"Unsupported call for device architecture {nvml.DeviceArch(device_arch).name} " + f"on device '{nvml.device_get_name(handle)}'" + ) + elif int(device_arch) < expected_device_arch_int: + # We know it will fail; assert that it does. + with pytest.raises(nvml.NotSupportedError): + yield + pytest.skip(f"Unsupported before {expected_device_arch.name}, got {nvml.device_get_name(handle)}") + else: + yield diff --git a/cuda_core/tests/helpers/marks.py b/cuda_python_test_helpers/cuda_python_test_helpers/marks.py similarity index 66% rename from cuda_core/tests/helpers/marks.py rename to cuda_python_test_helpers/cuda_python_test_helpers/marks.py index 53fcc544eb7..03d6ff2b622 100644 --- a/cuda_core/tests/helpers/marks.py +++ b/cuda_python_test_helpers/cuda_python_test_helpers/marks.py @@ -1,12 +1,15 @@ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Reusable pytest marks for cuda_core tests.""" +"""Reusable pytest marks and skip helpers for CUDA Python test suites.""" import inspect +import os import pytest +from cuda.pathfinder import get_cuda_path_or_home + def requires_module(module, *args, **kwargs): """Skip the test if a module is missing or older than required. @@ -43,3 +46,23 @@ def test_bar(): ... return pytest.mark.skipif(True, reason=str(exc)) else: return pytest.mark.skipif(False, reason="") + + +def _cuda_headers_available() -> bool: + """Return True if CUDA headers are available, False if no CUDA path is set. + + Raises AssertionError if a CUDA path is set but has no include/ subdirectory. + """ + cuda_path = get_cuda_path_or_home() + if cuda_path is None: + return False + assert os.path.isdir(os.path.join(cuda_path, "include")), ( + f"CUDA path {cuda_path} does not contain an 'include' subdirectory" + ) + return True + + +skipif_need_cuda_headers = pytest.mark.skipif( + not _cuda_headers_available(), + reason="need CUDA header", +) diff --git a/cuda_bindings/cuda/bindings/_test_helpers/mempool.py b/cuda_python_test_helpers/cuda_python_test_helpers/mempool.py similarity index 84% rename from cuda_bindings/cuda/bindings/_test_helpers/mempool.py rename to cuda_python_test_helpers/cuda_python_test_helpers/mempool.py index e2a61e48c53..c1fad576da9 100644 --- a/cuda_bindings/cuda/bindings/_test_helpers/mempool.py +++ b/cuda_python_test_helpers/cuda_python_test_helpers/mempool.py @@ -5,16 +5,11 @@ import pytest -from cuda.bindings import driver, runtime - -# Keep in sync with the fallback in cuda_core/tests/conftest.py. The cuda_core -# copy is intentionally simpler because it only handles cuda_core CUDAError -# exceptions when this helper is absent from older published bindings. def is_windows_mcdm_device(device=0): if sys.platform != "win32": return False - import cuda.bindings.nvml as nvml + from cuda.bindings import driver, nvml device_id = int(getattr(device, "device_id", device)) (err,) = driver.cuInit(0) @@ -34,6 +29,8 @@ def is_windows_mcdm_device(device=0): def xfail_if_mempool_oom(err_or_exc, api_name=None, device=0): + from cuda.bindings import driver, runtime + if api_name is not None and not isinstance(api_name, str): device = api_name api_name = None diff --git a/cuda_bindings/cuda/bindings/_test_helpers/pep723.py b/cuda_python_test_helpers/cuda_python_test_helpers/pep723.py similarity index 100% rename from cuda_bindings/cuda/bindings/_test_helpers/pep723.py rename to cuda_python_test_helpers/cuda_python_test_helpers/pep723.py