From 4578a52ef99eab60cf62cad3f94b7ffedb9dd36b Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:22:34 -0500 Subject: [PATCH 1/2] chore: clean up cufile fixtures --- cuda_bindings/tests/test_cufile.py | 149 ++++++++++++----------------- 1 file changed, 60 insertions(+), 89 deletions(-) diff --git a/cuda_bindings/tests/test_cufile.py b/cuda_bindings/tests/test_cufile.py index 1f4735ba3..a6d82925a 100644 --- a/cuda_bindings/tests/test_cufile.py +++ b/cuda_bindings/tests/test_cufile.py @@ -1,6 +1,7 @@ # SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE +import contextlib import ctypes import errno import logging @@ -22,45 +23,20 @@ force=True, # Override any existing logging configuration ) -try: - from cuda.bindings import cufile -except ImportError: - cufile = cuFileError = None -else: - from cuda.bindings.cufile import cuFileError - - -def platform_is_wsl(): - """Check if running on Windows Subsystem for Linux (WSL).""" - return platform.system() == "Linux" and "microsoft" in pathlib.Path("/proc/version").read_text().lower() - - -if cufile is None: - pytest.skip("skipping tests on Windows", allow_module_level=True) - -if platform_is_wsl(): - pytest.skip("skipping cuFile tests on WSL", allow_module_level=True) +cufile = pytest.importorskip("cuda.bindings.cufile", reason="skipping tests on Windows") +# if the previous line is not skipped, then this by definition is accessible +cuFileError = cufile.cuFileError @pytest.fixture -def cufile_env_json(): +def cufile_env_json(monkeypatch): """Set CUFILE_ENV_PATH_JSON environment variable for async tests.""" - original_value = os.environ.get("CUFILE_ENV_PATH_JSON") - # Get absolute path to cufile.json in the same directory as this test file test_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(test_dir, "cufile.json") + monkeypatch.setenv("CUFILE_ENV_PATH_JSON", config_path) logging.info(f"Using cuFile config: {config_path}") assert os.path.isfile(config_path) - os.environ["CUFILE_ENV_PATH_JSON"] = config_path - - yield - - # Restore original value or remove if it wasn't set - if original_value is not None: - os.environ["CUFILE_ENV_PATH_JSON"] = original_value - else: - del os.environ["CUFILE_ENV_PATH_JSON"] @cache @@ -105,7 +81,13 @@ def isSupportedFilesystem(): # Global skip condition for all tests if cuFile library is not available -pytestmark = pytest.mark.skipif(not cufileLibraryAvailable(), reason="cuFile library not available on this system") +pytestmark = [ + pytest.mark.skipif(not cufileLibraryAvailable(), reason="cuFile library not available on this system"), + pytest.mark.skipif( + platform.system() == "Linux" and "microsoft" in pathlib.Path("/proc/version").read_text().lower(), + reason="skipping cuFile tests on WSL", + ), +] xfail_handle_register = pytest.mark.xfail( condition=isSupportedFilesystem() and os.environ.get("CI") is not None, @@ -139,11 +121,19 @@ def ctx(): cuda.cuDevicePrimaryCtxRelease(device) +@contextlib.contextmanager +def raw_driver(): + cufile.driver_open() + try: + yield + finally: + cufile.driver_close() + + @pytest.fixture def driver(ctx): - cufile.driver_open() - yield - cufile.driver_close() + with raw_driver(): + yield @pytest.mark.skipif(not isSupportedFilesystem(), reason="cuFile handle_register requires ext4 or xfs filesystem") @@ -612,13 +602,10 @@ def test_cufile_read_write_large(): @pytest.mark.skipif(not isSupportedFilesystem(), reason="cuFile handle_register requires ext4 or xfs filesystem") -@pytest.mark.usefixtures("ctx") +@pytest.mark.usefixtures("ctx", "cufile_env_json", "driver") @xfail_handle_register -def test_cufile_write_async(cufile_env_json): +def test_cufile_write_async(): """Test cuFile asynchronous write operations.""" - # Open cuFile driver - cufile.driver_open() - # Create test file file_path = "test_cufile_write_async.bin" fd = os.open(file_path, os.O_CREAT | os.O_RDWR | os.O_DIRECT, 0o600) @@ -690,17 +677,13 @@ def test_cufile_write_async(cufile_env_json): os.close(fd) with suppress(OSError): os.unlink(file_path) - cufile.driver_close() @pytest.mark.skipif(not isSupportedFilesystem(), reason="cuFile handle_register requires ext4 or xfs filesystem") -@pytest.mark.usefixtures("ctx") +@pytest.mark.usefixtures("ctx", "cufile_env_json", "driver") @xfail_handle_register -def test_cufile_read_async(cufile_env_json): +def test_cufile_read_async(): """Test cuFile asynchronous read operations.""" - # Open cuFile driver - cufile.driver_open() - # Create test file file_path = "test_cufile_read_async.bin" @@ -785,17 +768,13 @@ def test_cufile_read_async(cufile_env_json): os.close(fd) with suppress(OSError): os.unlink(file_path) - cufile.driver_close() @pytest.mark.skipif(not isSupportedFilesystem(), reason="cuFile handle_register requires ext4 or xfs filesystem") -@pytest.mark.usefixtures("ctx") @xfail_handle_register -def test_cufile_async_read_write(cufile_env_json): +@pytest.mark.usefixtures("ctx", "cufile_env_json", "driver") +def test_cufile_async_read_write(): """Test cuFile asynchronous read and write operations in sequence.""" - # Open cuFile driver - cufile.driver_open() - # Create test file file_path = "test_cufile_async_rw.bin" fd = os.open(file_path, os.O_CREAT | os.O_RDWR | os.O_DIRECT, 0o600) @@ -903,7 +882,6 @@ def test_cufile_async_read_write(cufile_env_json): os.close(fd) with suppress(OSError): os.unlink(file_path) - cufile.driver_close() @pytest.mark.skipif(not isSupportedFilesystem(), reason="cuFile handle_register requires ext4 or xfs filesystem") @@ -1856,30 +1834,30 @@ def test_get_bar_size_in_kb(): logging.info(f"GPU BAR size: {bar_size_kb} KB ({bar_size_kb / 1024 / 1024:.2f} GB)") -@pytest.mark.skipif( - cufileVersionLessThan(1150), reason="cuFile parameter APIs require cuFile library version 13.0 or later" -) -@pytest.mark.usefixtures("ctx") -def test_set_parameter_posix_pool_slab_array(): - """Test cuFile POSIX pool slab array configuration.""" - # Define slab sizes for POSIX I/O pool (common I/O buffer sizes) - BEFORE driver open - import ctypes - - slab_sizes = [ +@pytest.fixture(scope="module") +def slab_sizes(): + """Define slab sizes for POSIX I/O pool (common I/O buffer sizes) - BEFORE driver open""" + return [ 4096, # 4KB - small files 65536, # 64KB - medium files 1048576, # 1MB - large files 16777216, # 16MB - very large files ] - # Define counts for each slab size (number of buffers) - slab_counts = [ + +@pytest.fixture(scope="module") +def slab_counts(): + """Define counts for each slab size (number of buffers)""" + return [ 10, # 10 buffers of 4KB 5, # 5 buffers of 64KB 3, # 3 buffers of 1MB 2, # 2 buffers of 16MB ] + +@pytest.fixture +def driver_config(slab_sizes, slab_counts): # Convert to ctypes arrays size_array_type = ctypes.c_size_t * len(slab_sizes) count_array_type = ctypes.c_size_t * len(slab_counts) @@ -1891,32 +1869,25 @@ def test_set_parameter_posix_pool_slab_array(): ctypes.addressof(size_array), ctypes.addressof(count_array), len(slab_sizes) ) - # Open cuFile driver AFTER setting parameters - cufile.driver_open() - - try: - # After setting parameters, retrieve them back to verify - retrieved_sizes = (ctypes.c_size_t * len(slab_sizes))() - retrieved_counts = (ctypes.c_size_t * len(slab_counts))() - cufile.get_parameter_posix_pool_slab_array( - ctypes.addressof(retrieved_sizes), ctypes.addressof(retrieved_counts), len(slab_sizes) - ) +@pytest.mark.skipif( + cufileVersionLessThan(1150), reason="cuFile parameter APIs require cuFile library version 13.0 or later" +) +@pytest.mark.usefixtures("ctx") +def test_set_parameter_posix_pool_slab_array(slab_sizes, slab_counts, driver_config): + """Test cuFile POSIX pool slab array configuration.""" + # After setting parameters, retrieve them back to verify + n_slab_sizes = len(slab_sizes) + retrieved_sizes = (ctypes.c_size_t * n_slab_sizes)() + retrieved_counts = (ctypes.c_size_t * len(slab_counts))() - # Verify they match what we set - for i in range(len(slab_sizes)): - assert retrieved_sizes[i] == slab_sizes[i], ( - f"Size mismatch at index {i}: expected {slab_sizes[i]}, got {retrieved_sizes[i]}" - ) - assert retrieved_counts[i] == slab_counts[i], ( - f"Count mismatch at index {i}: expected {slab_counts[i]}, got {retrieved_counts[i]}" - ) + retrieved_sizes_addr = ctypes.addressof(retrieved_sizes) + retrieved_counts_addr = ctypes.addressof(retrieved_counts) - # Verify configuration was accepted successfully - logging.info(f"POSIX pool slab array configured with {len(slab_sizes)} slab sizes") - logging.info(f"Slab sizes: {[f'{size // 1024}KB' for size in slab_sizes]}") - logging.info("Round-trip verification successful: set and retrieved values match") + # Open cuFile driver AFTER setting parameters + with raw_driver(): + cufile.get_parameter_posix_pool_slab_array(retrieved_sizes_addr, retrieved_counts_addr, n_slab_sizes) - finally: - # Close cuFile driver - cufile.driver_close() + # Verify they match what we set + assert list(retrieved_sizes) == slab_sizes + assert list(retrieved_counts) == slab_counts From 6e3fa6aa78551a4bcba4cfdf5cad4a0bb4bba813 Mon Sep 17 00:00:00 2001 From: Phillip Cloud <417981+cpcloud@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:41:14 -0500 Subject: [PATCH 2/2] test: add pytest-randomly --- cuda_bindings/pixi.lock | 41 +++++++++++++++++++++++++++++++++++++++++ cuda_bindings/pixi.toml | 1 + 2 files changed, 42 insertions(+) diff --git a/cuda_bindings/pixi.lock b/cuda_bindings/pixi.lock index 2c5760ded..65c971a30 100644 --- a/cuda_bindings/pixi.lock +++ b/cuda_bindings/pixi.lock @@ -60,6 +60,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-15.2.0-h54ccb8d_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/harfbuzz-12.2.0-h15599e2_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-75.1-he02047a_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/intel-gmmlib-22.8.2-hb700be7_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/intel-media-driver-25.3.4-hecca717_0.conda @@ -168,6 +169,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-benchmark-5.2.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-randomly-3.15.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.0-h32b2ec7_102_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/rdma-core-60.0-hecca717_0.conda @@ -201,6 +203,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.4-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxscrnsaver-1.2.4-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda - conda: . subdir: linux-64 @@ -258,6 +261,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/gxx_impl_linux-aarch64-15.2.0-h0902481_7.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/harfbuzz-12.2.0-he4899c9_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/icu-75.1-hf9b3779_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-aarch64-4.18.0-h05a177a_8.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/lame-3.100-h4e544f5_1003.tar.bz2 @@ -357,6 +361,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-benchmark-5.2.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-randomly-3.15.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/python-3.14.0-hb06a95a_102_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/rdma-core-60.0-he839754_0.conda @@ -388,6 +393,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxfixes-6.0.2-he30d5cf_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxrandr-1.5.4-h86ecc28_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxrender-0.9.12-h86ecc28_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-hbcf94c1_2.conda - conda: . subdir: linux-aarch64 @@ -438,6 +444,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/gxx_impl_win-64-15.2.0-h4517dcd_7.conda - conda: https://conda.anaconda.org/conda-forge/win-64/harfbuzz-12.2.0-h5f2951f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/icu-75.1-he0c23c2_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lame-3.100-hcfcfb64_1003.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/ld_impl_win-64-2.45-h13c207b_0.conda @@ -497,6 +504,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.19.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.0.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-benchmark-5.2.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-randomly-3.15.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.0-h4b44e0e_102_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/win-64/sdl2-2.32.56-h5112557_0.conda @@ -517,6 +525,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.44.35208-h38c0c73_32.conda - conda: https://conda.anaconda.org/conda-forge/win-64/x264-1!164.3095-h8ffe710_2.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/x265-3.5-h2d74725_3.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-hbeecb71_2.conda - conda: . subdir: win-64 @@ -2282,6 +2291,17 @@ packages: license_family: MIT size: 14544252 timestamp: 1720853966338 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-8.7.0-pyhe01879c_1.conda + sha256: c18ab120a0613ada4391b15981d86ff777b5690ca461ea7e9e49531e8f374745 + md5: 63ccfdc3a3ce25b027b8767eb722fca8 + depends: + - python >=3.9 + - zipp >=3.20 + - python + license: Apache-2.0 + license_family: APACHE + size: 34641 + timestamp: 1747934053147 - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda sha256: e1a9e3b1c8fe62dc3932a616c284b5d8cbe3124bbfbedcf4ce5c828cb166ee19 md5: 9614359868482abba1bd15ce465e3c42 @@ -5213,6 +5233,17 @@ packages: license_family: BSD size: 43976 timestamp: 1762716480208 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-randomly-3.15.0-pyhd8ed1ab_0.conda + sha256: bd1953e4bc20ffd52cfee41b27b3a781ca6e281004d0dd59e2dd60b0192c7a86 + md5: 203b5d3f85a47940f7ec6b6e1747786e + depends: + - importlib-metadata >=3.6.0 + - pytest + - python >=3.6 + license: MIT + license_family: MIT + size: 14133 + timestamp: 1692131735622 - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.0-h32b2ec7_102_cp314.conda build_number: 102 sha256: 76d750045b94fded676323bfd01975a26a474023635735773d0e4d80aaa72518 @@ -6103,6 +6134,16 @@ packages: license_family: MIT size: 14412 timestamp: 1727899730073 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-3.23.0-pyhcf101f3_1.conda + sha256: b4533f7d9efc976511a73ef7d4a2473406d7f4c750884be8e8620b0ce70f4dae + md5: 30cd29cb87d819caead4d55184c1d115 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + size: 24194 + timestamp: 1764460141901 - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb md5: 6432cb5d4ac0046c3ac0a8a0f95842f9 diff --git a/cuda_bindings/pixi.toml b/cuda_bindings/pixi.toml index da4748516..8aa2713b3 100644 --- a/cuda_bindings/pixi.toml +++ b/cuda_bindings/pixi.toml @@ -16,6 +16,7 @@ cuda-bindings = { path = "." } [feature.test.dependencies] pytest = ">=6.2.4" pytest-benchmark = ">=3.4.1" +pytest-randomly = "*" pyglet = ">=2.1.9" numpy = "*"