From ccb957b59f959ecef97d4f0b53f09cd829fc87d6 Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Tue, 4 Aug 2026 13:04:34 -0700 Subject: [PATCH 1/4] Migrate _binaries and _utils path handling to pathlib Part 3 of the series proposed in #2410. _binaries/find_nvidia_binary_utility.py now works in Path internally: _is_executable_candidate, _ctk_bin_subdirs and _resolve_in_trusted_dirs take and return Path. str() is applied once, on the public return of find_nvidia_binary_utility(), which is unchanged. SITE_PACKAGES_BINDIRS holds path components instead of joined strings. The caller immediately did sub_dir.split(os.sep) to undo the join, and find_sub_dirs_all_sitepackages wants components anyway. find_sub_dirs_no_cache walks in Path. Its return type stays list[str]: _binaries, _dynamic_libs, _headers and _static_libs all consume it, so flipping it is better done on its own once those have moved. Its directory test goes through a small _is_dir() helper rather than calling Path.is_dir() directly. The two are not interchangeable here: os.path.isdir() returns False for any stat error, while Path.is_dir() only swallows the errnos in pathlib's ignore list and propagates the rest. This function walks site-packages trees that nobody here controls, so a single unreadable directory would have turned a clean "not found" into a PermissionError. _utils/env_vars.py uses Path.exists/Path.samefile. Both calls are already inside the existing try/except OSError, so the same error-handling difference does not apply. os.path.normcase and os.path.normpath stay in _paths_differ: PurePath does not collapse "..", which test_paths_differ_text_only depends on, and Path.resolve() would also follow symlinks. os.path.abspath likewise stays in _resolve_in_trusted_dirs, since Path.absolute() does not normalize. test_find_nvidia_binaries.py moves with the module: it asserts on the exact values passed to and returned by these private helpers, so it cannot be separated from the signature change. Verified by differential fuzzing of find_sub_dirs_no_cache against the previous implementation: 3000 calls over randomized trees comparing result order, plus 720 calls over trees containing unreadable directories. Identical, except that a parent dir spelled with redundant separators now yields the normalized form. Signed-off-by: LeSingh1 --- .../_binaries/find_nvidia_binary_utility.py | 51 +++--- .../_binaries/supported_nvidia_binaries.py | 12 +- .../cuda/pathfinder/_utils/env_vars.py | 9 +- .../cuda/pathfinder/_utils/find_sub_dirs.py | 35 ++-- .../tests/test_find_nvidia_binaries.py | 154 +++++++++--------- 5 files changed, 142 insertions(+), 119 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py b/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py index 42dcfda1cfb..7332d31aef4 100644 --- a/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py +++ b/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py @@ -4,6 +4,7 @@ import functools import os from collections.abc import Iterable +from pathlib import Path from cuda.pathfinder._binaries import supported_nvidia_binaries, windows_nsight from cuda.pathfinder._utils.ctk_root_canary import CTK_ROOT_CANARY_ANCHOR_LIBNAMES @@ -29,22 +30,23 @@ def _normalize_utility_name(utility_name: str) -> str: return utility_name -def _is_executable_candidate(path: str) -> bool: - if not os.path.isfile(path): +def _is_executable_candidate(path: Path) -> bool: + if not path.is_file(): return False if IS_WINDOWS: return True + # pathlib has no access() equivalent. return os.access(path, os.X_OK) -def _ctk_bin_subdirs(root: str) -> list[str]: +def _ctk_bin_subdirs(root: Path) -> list[Path]: if IS_WINDOWS: return [ - os.path.join(root, "bin", "x64"), - os.path.join(root, "bin", "x86_64"), - os.path.join(root, "bin"), + root / "bin" / "x64", + root / "bin" / "x86_64", + root / "bin", ] - return [os.path.join(root, "bin")] + return [root / "bin"] def _resolve_candidate_paths(candidates: Iterable[str]) -> str | None: @@ -54,7 +56,7 @@ def _resolve_candidate_paths(candidates: Iterable[str]) -> str | None: if candidate in seen: continue seen.add(candidate) - if _is_executable_candidate(candidate): + if _is_executable_candidate(Path(candidate)): return os.path.abspath(candidate) return None @@ -75,31 +77,34 @@ def _resolve_ctk_root_via_canary() -> str | None: return ctk_root -def _resolve_in_trusted_dirs(normalized_name: str, dirs: list[str]) -> str | None: +def _resolve_in_trusted_dirs(normalized_name: str, dirs: list[Path]) -> str | None: """Resolve ``normalized_name`` against ``dirs`` in order.""" - seen: set[str] = set() + seen: set[Path] = set() for directory in dirs: if directory in seen: continue - assert directory + # Path("") is Path("."), which would silently search the CWD (#2119). + assert directory != Path() seen.add(directory) - candidate = os.path.join(directory, normalized_name) + candidate = directory / normalized_name if _is_executable_candidate(candidate): # Return an absolute path, as the docstring promises (a relative - # search dir would otherwise leak a relative result). + # search dir would otherwise leak a relative result). os.path.abspath + # has no pathlib equivalent: Path.absolute() does not normalize and + # Path.resolve() would also follow symlinks. return os.path.abspath(candidate) return None -def _resolve_names_in_trusted_dirs(candidate_names: tuple[str, ...], dirs: list[str]) -> str | None: +def _resolve_names_in_trusted_dirs(candidate_names: tuple[str, ...], dirs: list[Path]) -> str | None: """Resolve ordered candidate names within each trusted directory.""" - seen: set[str] = set() + seen: set[Path] = set() for directory in dirs: if directory in seen: continue - assert directory + assert directory != Path() seen.add(directory) - found = _resolve_candidate_paths(os.path.join(directory, name) for name in candidate_names) + found = _resolve_candidate_paths(str(directory / name) for name in candidate_names) if found is not None: return found return None @@ -193,17 +198,15 @@ def find_nvidia_binary_utility(utility_name: str) -> str | None: # 1. Search in site-packages (NVIDIA wheels) candidate_dirs = supported_nvidia_binaries.SITE_PACKAGES_BINDIRS.get(utility_name, ()) - dirs = [] + dirs: list[Path] = [] for sub_dir in candidate_dirs: - dirs.extend(find_sub_dirs_all_sitepackages(sub_dir.split(os.sep))) + dirs.extend(Path(abs_dir) for abs_dir in find_sub_dirs_all_sitepackages(sub_dir)) # 2. Search in Conda environment if (conda_prefix := os.environ.get("CONDA_PREFIX")) is not None: - if IS_WINDOWS: - dirs.append(os.path.join(conda_prefix, "Library", "bin")) - else: - dirs.append(os.path.join(conda_prefix, "bin")) + conda_root = Path(conda_prefix) + dirs.append(conda_root / "Library" / "bin" if IS_WINDOWS else conda_root / "bin") normalized_name = _normalize_utility_name(utility_name) if IS_WINDOWS and utility_name in ("compute-sanitizer", "ncu"): @@ -235,5 +238,5 @@ def find_nvidia_binary_utility(utility_name: str) -> str | None: if ctk_root is not None: if IS_WINDOWS and utility_name == "compute-sanitizer": return _find_windows_compute_sanitizer(ctk_root) - return _resolve_in_trusted_dirs(normalized_name, _ctk_bin_subdirs(ctk_root)) + return _resolve_in_trusted_dirs(normalized_name, _ctk_bin_subdirs(Path(ctk_root))) return None diff --git a/cuda_pathfinder/cuda/pathfinder/_binaries/supported_nvidia_binaries.py b/cuda_pathfinder/cuda/pathfinder/_binaries/supported_nvidia_binaries.py index ac70378f112..19b79fd45f3 100644 --- a/cuda_pathfinder/cuda/pathfinder/_binaries/supported_nvidia_binaries.py +++ b/cuda_pathfinder/cuda/pathfinder/_binaries/supported_nvidia_binaries.py @@ -1,13 +1,13 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -import os # Site-packages bin directories where binaries might be found -# Based on NVIDIA wheel layouts (same for Linux and Windows) -_CUDA_NVCC_BIN = os.path.join("nvidia", "cuda_nvcc", "bin") -_CUDA13_BIN = os.path.join("nvidia", "cu13", "bin") -_NSIGHT_SYSTEMS_BIN = os.path.join("nvidia", "nsight_systems", "bin") -_NSIGHT_COMPUTE_BIN = os.path.join("nvidia", "nsight_compute", "bin") +# Based on NVIDIA wheel layouts (same for Linux and Windows). +# Path components, because that is what find_sub_dirs_all_sitepackages takes. +_CUDA_NVCC_BIN = ("nvidia", "cuda_nvcc", "bin") +_CUDA13_BIN = ("nvidia", "cu13", "bin") +_NSIGHT_SYSTEMS_BIN = ("nvidia", "nsight_systems", "bin") +_NSIGHT_COMPUTE_BIN = ("nvidia", "nsight_compute", "bin") # Common CUDA binary utilities available on both Linux and Windows SITE_PACKAGES_BINDIRS = { diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/env_vars.py b/cuda_pathfinder/cuda/pathfinder/_utils/env_vars.py index 12198ac9f7f..e23fd35c610 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/env_vars.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/env_vars.py @@ -22,6 +22,7 @@ import functools import os import warnings +from pathlib import Path _CUDA_PATH_ENV_VARS_ORDERED = ("CUDA_PATH", "CUDA_HOME") @@ -36,15 +37,19 @@ def _paths_differ(a: str, b: str) -> bool: 2) If still different AND both exist, use os.path.samefile to resolve symlinks/junctions. 3) Otherwise (nonexistent paths or samefile unavailable), treat as different. """ + # normcase/normpath have no pathlib equivalent: PurePath does not collapse + # "..", Path.resolve() would also follow symlinks, and comparing PurePath + # objects would only case-fold on Windows. norm_a = os.path.normcase(os.path.normpath(a)) norm_b = os.path.normcase(os.path.normpath(b)) if norm_a == norm_b: return False + path_a, path_b = Path(a), Path(b) try: - if os.path.exists(a) and os.path.exists(b): + if path_a.exists() and path_b.exists(): # samefile raises on non-existent paths; only call when both exist. - return not os.path.samefile(a, b) + return not path_a.samefile(path_b) except OSError: # Fall through to "different" if samefile isn't applicable/available. pass diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py b/cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py index ebb7b13f488..f4154f089e8 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py @@ -2,36 +2,51 @@ # SPDX-License-Identifier: Apache-2.0 import functools -import os import site import sys from collections.abc import Sequence +from pathlib import Path + + +def _is_dir(path: Path) -> bool: + """``path.is_dir()``, but False instead of raising on an inaccessible path. + + This walks directories nobody here controls, so it has to tolerate whatever + it runs into. Path.is_dir() only swallows the errnos in pathlib's ignore + list, and raises for the rest (EACCES, ENAMETOOLONG); os.path.isdir, which + this replaces, returned False for all of them. + """ + try: + return path.is_dir() + except OSError: + return False def find_sub_dirs_no_cache(parent_dirs: Sequence[str], sub_dirs: Sequence[str]) -> list[str]: + # Results stay str: they are consumed by _binaries, _dynamic_libs, _headers + # and _static_libs, so the type flip belongs in its own change. results = [] for base in parent_dirs: - stack = [(base, 0)] # (current_path, index into sub_dirs) + stack = [(Path(base), 0)] # (current_path, index into sub_dirs) while stack: current_path, idx = stack.pop() if idx == len(sub_dirs): - if os.path.isdir(current_path): - results.append(current_path) + if _is_dir(current_path): + results.append(str(current_path)) continue sub = sub_dirs[idx] if sub == "*": try: - entries = sorted(os.listdir(current_path)) + entries = sorted(current_path.iterdir(), key=lambda entry: entry.name) except OSError: continue - for entry in entries: - entry_path = os.path.join(current_path, entry) - if os.path.isdir(entry_path): + for entry_path in entries: + if _is_dir(entry_path): stack.append((entry_path, idx + 1)) else: - next_path = os.path.join(current_path, sub) - if os.path.isdir(next_path): + next_path = current_path / sub + if _is_dir(next_path): stack.append((next_path, idx + 1)) return results diff --git a/cuda_pathfinder/tests/test_find_nvidia_binaries.py b/cuda_pathfinder/tests/test_find_nvidia_binaries.py index 9d08f5f4d5c..9455fc7b4ed 100644 --- a/cuda_pathfinder/tests/test_find_nvidia_binaries.py +++ b/cuda_pathfinder/tests/test_find_nvidia_binaries.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import os +from pathlib import Path import pytest @@ -25,7 +26,7 @@ def test_find_binary_utilities(info_summary_append, utility_name): bin_path = find_nvidia_binary_utility(utility_name) info_summary_append(f"{bin_path=!r}") - assert bin_path is None or os.path.isfile(bin_path) + assert bin_path is None or Path(bin_path).is_file() def test_supported_binaries_consistency(): @@ -48,7 +49,7 @@ def _patch_exec_probe(mocker, existing=()): candidates so tests can assert the deterministic search order. """ existing = set(existing) - checked: list[str] = [] + checked: list[Path] = [] def fake_is_executable_candidate(path): checked.append(path) @@ -60,10 +61,10 @@ def fake_is_executable_candidate(path): @pytest.mark.usefixtures("clear_find_binary_cache") def test_find_binary_search_path_includes_site_packages_conda_cuda(monkeypatch, mocker): - conda_prefix = os.path.join(os.sep, "conda") - cuda_home = os.path.join(os.sep, "cuda") - site_key = os.path.join("nvidia", "cuda_nvcc", "bin") - site_dir = os.path.join("site-packages", "cuda_nvcc", "bin") + conda_prefix = Path(os.sep, "conda") + cuda_home = Path(os.sep, "cuda") + site_key = ("nvidia", "cuda_nvcc", "bin") + site_dir = Path("site-packages", "cuda_nvcc", "bin") mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=False) mocker.patch.object( @@ -72,15 +73,15 @@ def test_find_binary_search_path_includes_site_packages_conda_cuda(monkeypatch, {"nvcc": (site_key,)}, ) find_sub_dirs_mock = mocker.patch.object( - binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[site_dir] + binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[str(site_dir)] ) - monkeypatch.setenv("CONDA_PREFIX", conda_prefix) - mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=cuda_home) + monkeypatch.setenv("CONDA_PREFIX", str(conda_prefix)) + mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=str(cuda_home)) mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=None) expected_dirs = [ site_dir, - os.path.join(conda_prefix, "bin"), - os.path.join(cuda_home, "bin"), + conda_prefix / "bin", + cuda_home / "bin", ] checked = _patch_exec_probe(mocker) @@ -88,16 +89,16 @@ def test_find_binary_search_path_includes_site_packages_conda_cuda(monkeypatch, # No directory contains the binary, so every trusted dir is probed in order. assert result is None - find_sub_dirs_mock.assert_called_once_with(site_key.split(os.sep)) - assert checked == [os.path.join(d, "nvcc") for d in expected_dirs] + find_sub_dirs_mock.assert_called_once_with(site_key) + assert checked == [d / "nvcc" for d in expected_dirs] @pytest.mark.usefixtures("clear_find_binary_cache") def test_find_binary_windows_extension_and_search_dirs(monkeypatch, mocker): - conda_prefix = os.path.join(os.sep, "conda") - cuda_home = os.path.join(os.sep, "cuda") - site_key = os.path.join("nvidia", "cuda_nvcc", "bin") - site_dir = os.path.join("site-packages", "cuda_nvcc", "bin") + conda_prefix = Path(os.sep, "conda") + cuda_home = Path(os.sep, "cuda") + site_key = ("nvidia", "cuda_nvcc", "bin") + site_dir = Path("site-packages", "cuda_nvcc", "bin") mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) mocker.patch.object( @@ -106,17 +107,17 @@ def test_find_binary_windows_extension_and_search_dirs(monkeypatch, mocker): {"nvcc": (site_key,)}, ) find_sub_dirs_mock = mocker.patch.object( - binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[site_dir] + binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[str(site_dir)] ) - monkeypatch.setenv("CONDA_PREFIX", conda_prefix) - mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=cuda_home) + monkeypatch.setenv("CONDA_PREFIX", str(conda_prefix)) + mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=str(cuda_home)) mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=None) expected_dirs = [ site_dir, - os.path.join(conda_prefix, "Library", "bin"), - os.path.join(cuda_home, "bin", "x64"), - os.path.join(cuda_home, "bin", "x86_64"), - os.path.join(cuda_home, "bin"), + conda_prefix / "Library" / "bin", + cuda_home / "bin" / "x64", + cuda_home / "bin" / "x86_64", + cuda_home / "bin", ] checked = _patch_exec_probe(mocker) @@ -124,8 +125,8 @@ def test_find_binary_windows_extension_and_search_dirs(monkeypatch, mocker): # The .exe extension is appended and the Windows-specific dirs are probed in order. assert result is None - find_sub_dirs_mock.assert_called_once_with(site_key.split(os.sep)) - assert checked == [os.path.join(d, "nvcc.exe") for d in expected_dirs] + find_sub_dirs_mock.assert_called_once_with(site_key) + assert checked == [d / "nvcc.exe" for d in expected_dirs] @pytest.mark.parametrize( @@ -380,10 +381,10 @@ def test_find_windows_nsight_legacy_names_remain_literal_in_ctk(monkeypatch, moc @pytest.mark.usefixtures("clear_find_binary_cache") def test_find_binary_first_matching_dir_wins(monkeypatch, mocker): - conda_prefix = os.path.join(os.sep, "conda") - cuda_home = os.path.join(os.sep, "cuda") - site_key = os.path.join("nvidia", "cuda_nvcc", "bin") - site_dir = os.path.join("site-packages", "cuda_nvcc", "bin") + conda_prefix = Path(os.sep, "conda") + cuda_home = Path(os.sep, "cuda") + site_key = ("nvidia", "cuda_nvcc", "bin") + site_dir = Path("site-packages", "cuda_nvcc", "bin") mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=False) mocker.patch.object( @@ -391,34 +392,34 @@ def test_find_binary_first_matching_dir_wins(monkeypatch, mocker): "SITE_PACKAGES_BINDIRS", {"nvcc": (site_key,)}, ) - mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[site_dir]) - monkeypatch.setenv("CONDA_PREFIX", conda_prefix) - mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=cuda_home) + mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[str(site_dir)]) + monkeypatch.setenv("CONDA_PREFIX", str(conda_prefix)) + mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=str(cuda_home)) mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=None) - conda_nvcc = os.path.join(conda_prefix, "bin", "nvcc") - cuda_nvcc = os.path.join(cuda_home, "bin", "nvcc") + conda_nvcc = conda_prefix / "bin" / "nvcc" + cuda_nvcc = cuda_home / "bin" / "nvcc" checked = _patch_exec_probe(mocker, existing=[conda_nvcc, cuda_nvcc]) result = find_nvidia_binary_utility("nvcc") # Conda comes before CUDA_HOME, so the Conda hit wins and CUDA_HOME is never probed. assert result == os.path.abspath(conda_nvcc) - assert checked == [os.path.join(site_dir, "nvcc"), conda_nvcc] + assert checked == [site_dir / "nvcc", conda_nvcc] @pytest.mark.usefixtures("clear_find_binary_cache") def test_find_binary_ctk_root_canary_fallback(monkeypatch, mocker): # When the explicit trusted dirs (wheels, conda, CUDA_HOME/PATH) all miss, # the cudart-canary-derived CTK root is searched last. - ctk_root = os.path.join(os.sep, "opt", "cuda") + ctk_root = Path(os.sep, "opt", "cuda") mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=False) mocker.patch.object(binary_finder_module.supported_nvidia_binaries, "SITE_PACKAGES_BINDIRS", {}) mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[]) monkeypatch.delenv("CONDA_PREFIX", raising=False) mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=None) - canary_mock = mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=ctk_root) - ctk_nvcc = os.path.join(ctk_root, "bin", "nvcc") + canary_mock = mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=str(ctk_root)) + ctk_nvcc = ctk_root / "bin" / "nvcc" checked = _patch_exec_probe(mocker, existing=[ctk_nvcc]) result = find_nvidia_binary_utility("nvcc") @@ -431,39 +432,39 @@ def test_find_binary_ctk_root_canary_fallback(monkeypatch, mocker): @pytest.mark.usefixtures("clear_find_binary_cache") def test_find_binary_canary_windows_bin_layout(monkeypatch, mocker): - ctk_root = os.path.join("C:", os.sep, "cuda") + ctk_root = Path("C:", os.sep, "cuda") mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) mocker.patch.object(binary_finder_module.supported_nvidia_binaries, "SITE_PACKAGES_BINDIRS", {}) mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[]) monkeypatch.delenv("CONDA_PREFIX", raising=False) mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=None) - mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=ctk_root) + mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=str(ctk_root)) expected_dirs = [ - os.path.join(ctk_root, "bin", "x64"), - os.path.join(ctk_root, "bin", "x86_64"), - os.path.join(ctk_root, "bin"), + ctk_root / "bin" / "x64", + ctk_root / "bin" / "x86_64", + ctk_root / "bin", ] checked = _patch_exec_probe(mocker) result = find_nvidia_binary_utility("nvcc") assert result is None - assert checked == [os.path.join(d, "nvcc.exe") for d in expected_dirs] + assert checked == [d / "nvcc.exe" for d in expected_dirs] @pytest.mark.usefixtures("clear_find_binary_cache") def test_find_binary_canary_not_consulted_when_found_earlier(monkeypatch, mocker): # An earlier trusted dir hit must short-circuit before the canary subprocess. - conda_prefix = os.path.join(os.sep, "conda") + conda_prefix = Path(os.sep, "conda") mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=False) mocker.patch.object(binary_finder_module.supported_nvidia_binaries, "SITE_PACKAGES_BINDIRS", {}) mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[]) - monkeypatch.setenv("CONDA_PREFIX", conda_prefix) + monkeypatch.setenv("CONDA_PREFIX", str(conda_prefix)) mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=None) canary_mock = mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=None) - conda_nvcc = os.path.join(conda_prefix, "bin", "nvcc") + conda_nvcc = conda_prefix / "bin" / "nvcc" _patch_exec_probe(mocker, existing=[conda_nvcc]) result = find_nvidia_binary_utility("nvcc") @@ -474,7 +475,7 @@ def test_find_binary_canary_not_consulted_when_found_earlier(monkeypatch, mocker @pytest.mark.usefixtures("clear_find_binary_cache") def test_find_binary_returns_none_with_no_candidates(monkeypatch, mocker): - site_key = os.path.join("nvidia", "cuda_nvcc", "bin") + site_key = ("nvidia", "cuda_nvcc", "bin") mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=False) mocker.patch.object( @@ -491,25 +492,25 @@ def test_find_binary_returns_none_with_no_candidates(monkeypatch, mocker): result = find_nvidia_binary_utility("nvcc") assert result is None - find_sub_dirs_mock.assert_called_once_with(site_key.split(os.sep)) + find_sub_dirs_mock.assert_called_once_with(site_key) # No trusted dirs were assembled, so nothing is probed at all. assert checked == [] @pytest.mark.usefixtures("clear_find_binary_cache") def test_find_binary_without_site_packages_entry(monkeypatch, mocker): - conda_prefix = os.path.join(os.sep, "conda") - cuda_home = os.path.join(os.sep, "cuda") + conda_prefix = Path(os.sep, "conda") + cuda_home = Path(os.sep, "cuda") mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=False) mocker.patch.object(binary_finder_module.supported_nvidia_binaries, "SITE_PACKAGES_BINDIRS", {}) find_sub_dirs_mock = mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[]) - monkeypatch.setenv("CONDA_PREFIX", conda_prefix) - mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=cuda_home) + monkeypatch.setenv("CONDA_PREFIX", str(conda_prefix)) + mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=str(cuda_home)) mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=None) expected_dirs = [ - os.path.join(conda_prefix, "bin"), - os.path.join(cuda_home, "bin"), + conda_prefix / "bin", + cuda_home / "bin", ] checked = _patch_exec_probe(mocker) @@ -517,7 +518,7 @@ def test_find_binary_without_site_packages_entry(monkeypatch, mocker): assert result is None find_sub_dirs_mock.assert_not_called() - assert checked == [os.path.join(d, "nvcc") for d in expected_dirs] + assert checked == [d / "nvcc" for d in expected_dirs] @pytest.mark.usefixtures("clear_find_binary_cache") @@ -546,10 +547,9 @@ class TestResolveInTrustedDirs: @staticmethod def _make_executable(directory, name): - path = os.path.join(str(directory), name) - with open(path, "w", encoding="utf-8") as handle: - handle.write("") - os.chmod(path, 0o700) + path = directory / name + path.write_text("", encoding="utf-8") + path.chmod(0o700) return path def test_cwd_is_not_searched(self, tmp_path, monkeypatch): @@ -566,9 +566,9 @@ def test_cwd_is_not_searched(self, tmp_path, monkeypatch): monkeypatch.chdir(evil_cwd) # A trusted dir with no binary returns None, never the CWD copy. - assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [str(empty)]) is None + assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [empty]) is None # When a trusted dir holds it, that path wins regardless of CWD. - assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [str(empty), str(trusted)]) == trusted_nvcc + assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [empty, trusted]) == trusted_nvcc def test_first_trusted_dir_wins(self, tmp_path): first = tmp_path / "a" @@ -577,30 +577,30 @@ def test_first_trusted_dir_wins(self, tmp_path): second.mkdir() first_nvcc = self._make_executable(first, "nvcc") self._make_executable(second, "nvcc") - assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [str(first), str(second)]) == first_nvcc + assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [first, second]) == first_nvcc def test_duplicate_dirs_skipped(self, tmp_path): present = tmp_path / "p" present.mkdir() nvcc = self._make_executable(present, "nvcc") - assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [str(present), str(present)]) == nvcc + assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [present, present]) == nvcc assert binary_finder_module._resolve_in_trusted_dirs("nvcc", []) is None def test_empty_dir_asserts(self): + # Path("") is Path("."), which would make the CWD a search dir. with pytest.raises(AssertionError): - binary_finder_module._resolve_in_trusted_dirs("nvcc", [""]) + binary_finder_module._resolve_in_trusted_dirs("nvcc", [Path("")]) @pytest.mark.skipif(binary_finder_module.IS_WINDOWS, reason="POSIX execute-bit semantics") def test_non_executable_file_rejected_on_posix(self, tmp_path): directory = tmp_path / "d" directory.mkdir() - path = os.path.join(str(directory), "nvcc") - with open(path, "w", encoding="utf-8") as handle: - handle.write("") - os.chmod(path, 0o644) - assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [str(directory)]) is None - os.chmod(path, 0o700) - assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [str(directory)]) == path + path = directory / "nvcc" + path.write_text("", encoding="utf-8") + path.chmod(0o644) + assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [directory]) is None + path.chmod(0o700) + assert binary_finder_module._resolve_in_trusted_dirs("nvcc", [directory]) == path @pytest.mark.usefixtures("clear_find_binary_cache") @@ -630,8 +630,8 @@ def test_resolve_in_trusted_dirs_returns_absolute_path(tmp_path, monkeypatch, mo leaked a relative path that would re-resolve against a possibly different CWD at execution time. """ - rel_dir = os.path.join("some", "relative", "bin") - candidate = os.path.join(rel_dir, "nvcc") + rel_dir = Path("some", "relative", "bin") + candidate = rel_dir / "nvcc" mocker.patch.object( binary_finder_module, "_is_executable_candidate", @@ -642,5 +642,5 @@ def test_resolve_in_trusted_dirs_returns_absolute_path(tmp_path, monkeypatch, mo monkeypatch.chdir(tmp_path) result = binary_finder_module._resolve_in_trusted_dirs("nvcc", [rel_dir]) - assert os.path.isabs(result) - assert result == os.path.abspath(os.path.join(str(tmp_path), candidate)) + assert result.is_absolute() + assert result == tmp_path / candidate From 184881adef65e6c28e7b24e8148cce01aa2d3fe5 Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Fri, 7 Aug 2026 16:02:18 -0700 Subject: [PATCH 2/4] Drop _is_dir helper, call Path.is_dir() directly Per review: accept the behavioral change from os.path.isdir (False on any stat error) to Path.is_dir() (propagates EACCES/ENAMETOOLONG). --- .../cuda/pathfinder/_utils/find_sub_dirs.py | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py b/cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py index f4154f089e8..8dbf4d30ceb 100644 --- a/cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py +++ b/cuda_pathfinder/cuda/pathfinder/_utils/find_sub_dirs.py @@ -8,20 +8,6 @@ from pathlib import Path -def _is_dir(path: Path) -> bool: - """``path.is_dir()``, but False instead of raising on an inaccessible path. - - This walks directories nobody here controls, so it has to tolerate whatever - it runs into. Path.is_dir() only swallows the errnos in pathlib's ignore - list, and raises for the rest (EACCES, ENAMETOOLONG); os.path.isdir, which - this replaces, returned False for all of them. - """ - try: - return path.is_dir() - except OSError: - return False - - def find_sub_dirs_no_cache(parent_dirs: Sequence[str], sub_dirs: Sequence[str]) -> list[str]: # Results stay str: they are consumed by _binaries, _dynamic_libs, _headers # and _static_libs, so the type flip belongs in its own change. @@ -31,7 +17,7 @@ def find_sub_dirs_no_cache(parent_dirs: Sequence[str], sub_dirs: Sequence[str]) while stack: current_path, idx = stack.pop() if idx == len(sub_dirs): - if _is_dir(current_path): + if current_path.is_dir(): results.append(str(current_path)) continue @@ -42,11 +28,11 @@ def find_sub_dirs_no_cache(parent_dirs: Sequence[str], sub_dirs: Sequence[str]) except OSError: continue for entry_path in entries: - if _is_dir(entry_path): + if entry_path.is_dir(): stack.append((entry_path, idx + 1)) else: next_path = current_path / sub - if _is_dir(next_path): + if next_path.is_dir(): stack.append((next_path, idx + 1)) return results From 2d5b704065ad30416d0367847420294a680ea211 Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Tue, 11 Aug 2026 08:56:32 -0700 Subject: [PATCH 3/4] Move the remaining test os.path calls to pathlib The three os.path.abspath assertions in test_find_nvidia_binaries.py become str(...absolute()), and the os.path.isdir in test_utils_find_sub_dirs.py becomes Path.is_dir(). import os stays in the binaries test for os.sep. --- .../_binaries/find_nvidia_binary_utility.py | 13 ++++++++----- cuda_pathfinder/tests/test_find_nvidia_binaries.py | 6 +++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py b/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py index 7332d31aef4..ec4cfdbe1df 100644 --- a/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py +++ b/cuda_pathfinder/cuda/pathfinder/_binaries/find_nvidia_binary_utility.py @@ -77,7 +77,7 @@ def _resolve_ctk_root_via_canary() -> str | None: return ctk_root -def _resolve_in_trusted_dirs(normalized_name: str, dirs: list[Path]) -> str | None: +def _resolve_in_trusted_dirs(normalized_name: str, dirs: list[Path]) -> Path | None: """Resolve ``normalized_name`` against ``dirs`` in order.""" seen: set[Path] = set() for directory in dirs: @@ -92,7 +92,7 @@ def _resolve_in_trusted_dirs(normalized_name: str, dirs: list[Path]) -> str | No # search dir would otherwise leak a relative result). os.path.abspath # has no pathlib equivalent: Path.absolute() does not normalize and # Path.resolve() would also follow symlinks. - return os.path.abspath(candidate) + return Path(os.path.abspath(candidate)) return None @@ -213,7 +213,8 @@ def find_nvidia_binary_utility(utility_name: str) -> str | None: candidate_names = (f"{utility_name}.bat", normalized_name) found = _resolve_names_in_trusted_dirs(candidate_names, dirs) else: - found = _resolve_in_trusted_dirs(normalized_name, dirs) + resolved = _resolve_in_trusted_dirs(normalized_name, dirs) + found = None if resolved is None else str(resolved) if found is not None: return found @@ -229,7 +230,8 @@ def find_nvidia_binary_utility(utility_name: str) -> str | None: if IS_WINDOWS and utility_name == "compute-sanitizer": found = _find_windows_compute_sanitizer(cuda_path) else: - found = _resolve_in_trusted_dirs(normalized_name, _ctk_bin_subdirs(cuda_path)) + resolved = _resolve_in_trusted_dirs(normalized_name, _ctk_bin_subdirs(Path(cuda_path))) + found = None if resolved is None else str(resolved) if found is not None: return found @@ -238,5 +240,6 @@ def find_nvidia_binary_utility(utility_name: str) -> str | None: if ctk_root is not None: if IS_WINDOWS and utility_name == "compute-sanitizer": return _find_windows_compute_sanitizer(ctk_root) - return _resolve_in_trusted_dirs(normalized_name, _ctk_bin_subdirs(Path(ctk_root))) + resolved = _resolve_in_trusted_dirs(normalized_name, _ctk_bin_subdirs(Path(ctk_root))) + return None if resolved is None else str(resolved) return None diff --git a/cuda_pathfinder/tests/test_find_nvidia_binaries.py b/cuda_pathfinder/tests/test_find_nvidia_binaries.py index 9455fc7b4ed..3aeebfb8c67 100644 --- a/cuda_pathfinder/tests/test_find_nvidia_binaries.py +++ b/cuda_pathfinder/tests/test_find_nvidia_binaries.py @@ -403,7 +403,7 @@ def test_find_binary_first_matching_dir_wins(monkeypatch, mocker): result = find_nvidia_binary_utility("nvcc") # Conda comes before CUDA_HOME, so the Conda hit wins and CUDA_HOME is never probed. - assert result == os.path.abspath(conda_nvcc) + assert result == str(conda_nvcc.absolute()) assert checked == [site_dir / "nvcc", conda_nvcc] @@ -424,7 +424,7 @@ def test_find_binary_ctk_root_canary_fallback(monkeypatch, mocker): result = find_nvidia_binary_utility("nvcc") - assert result == os.path.abspath(ctk_nvcc) + assert result == str(ctk_nvcc.absolute()) canary_mock.assert_called_once_with() # No earlier trusted dirs existed, so the only probe is the canary bin dir. assert checked == [ctk_nvcc] @@ -469,7 +469,7 @@ def test_find_binary_canary_not_consulted_when_found_earlier(monkeypatch, mocker result = find_nvidia_binary_utility("nvcc") - assert result == os.path.abspath(conda_nvcc) + assert result == str(conda_nvcc.absolute()) canary_mock.assert_not_called() From c2d9e9977674c9751ee987c2c4c3cabbb6928d3d Mon Sep 17 00:00:00 2001 From: LeSingh1 Date: Wed, 26 Aug 2026 22:15:44 -0700 Subject: [PATCH 4/4] Fix the Windows test failures from the str-to-Path probe change _is_executable_candidate now takes a Path, but the tests that drive it through _patch_exec_probe still built their expected paths with os.path.join. A str never compares equal to a Path, so the fake probe reported every candidate as missing and each lookup returned None. Only Windows ran these in CI, which is why it looked platform-specific. Converts the remaining tests to Path and asserts in the probe helper that 'existing' holds Paths, so the same mismatch fails loudly next time. --- .../tests/test_find_nvidia_binaries.py | 102 +++++++++--------- 1 file changed, 54 insertions(+), 48 deletions(-) diff --git a/cuda_pathfinder/tests/test_find_nvidia_binaries.py b/cuda_pathfinder/tests/test_find_nvidia_binaries.py index 3aeebfb8c67..aaa8d6c8782 100644 --- a/cuda_pathfinder/tests/test_find_nvidia_binaries.py +++ b/cuda_pathfinder/tests/test_find_nvidia_binaries.py @@ -48,7 +48,10 @@ def _patch_exec_probe(mocker, existing=()): candidate is treated as missing. Returns the list that accumulates probed candidates so tests can assert the deterministic search order. """ + # Paths, not strings: _is_executable_candidate takes a Path, so a str here + # would never compare equal and would silently turn every lookup into None. existing = set(existing) + assert all(isinstance(candidate, Path) for candidate in existing) checked: list[Path] = [] def fake_is_executable_candidate(path): @@ -132,13 +135,13 @@ def test_find_binary_windows_extension_and_search_dirs(monkeypatch, mocker): @pytest.mark.parametrize( ("launcher_exists", "expected_rel", "checked_rels"), ( - (True, os.path.join("bin", "compute-sanitizer.bat"), (os.path.join("bin", "compute-sanitizer.bat"),)), + (True, Path("bin", "compute-sanitizer.bat"), (Path("bin", "compute-sanitizer.bat"),)), ( False, - os.path.join("compute-sanitizer", "compute-sanitizer.exe"), + Path("compute-sanitizer", "compute-sanitizer.exe"), ( - os.path.join("bin", "compute-sanitizer.bat"), - os.path.join("compute-sanitizer", "compute-sanitizer.exe"), + Path("bin", "compute-sanitizer.bat"), + Path("compute-sanitizer", "compute-sanitizer.exe"), ), ), ), @@ -148,36 +151,36 @@ def test_find_binary_windows_extension_and_search_dirs(monkeypatch, mocker): def test_find_compute_sanitizer_prefers_ctk_launcher_with_executable_fallback( monkeypatch, mocker, launcher_exists, expected_rel, checked_rels ): - cuda_home = os.path.join(os.sep, "cuda") - launcher = os.path.join(cuda_home, "bin", "compute-sanitizer.bat") - executable = os.path.join(cuda_home, "compute-sanitizer", "compute-sanitizer.exe") + cuda_home = Path(os.sep, "cuda") + launcher = cuda_home / "bin" / "compute-sanitizer.bat" + executable = cuda_home / "compute-sanitizer" / "compute-sanitizer.exe" mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) mocker.patch.object(binary_finder_module.supported_nvidia_binaries, "SITE_PACKAGES_BINDIRS", {}) monkeypatch.delenv("CONDA_PREFIX", raising=False) - mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=cuda_home) + mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=str(cuda_home)) canary_mock = mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary") existing = [executable] if launcher_exists: existing.append(launcher) checked = _patch_exec_probe(mocker, existing=existing) - assert find_nvidia_binary_utility("compute-sanitizer") == os.path.abspath(os.path.join(cuda_home, expected_rel)) - assert checked == [os.path.join(cuda_home, rel) for rel in checked_rels] + assert find_nvidia_binary_utility("compute-sanitizer") == os.path.abspath(cuda_home / expected_rel) + assert checked == [cuda_home / rel for rel in checked_rels] canary_mock.assert_not_called() @pytest.mark.usefixtures("clear_find_binary_cache") @pytest.mark.agent_authored(model="gpt-5.6") def test_find_compute_sanitizer_uses_canary_ctk_root(monkeypatch, mocker): - ctk_root = os.path.join(os.sep, "cuda") - launcher = os.path.join(ctk_root, "bin", "compute-sanitizer.bat") + ctk_root = Path(os.sep, "cuda") + launcher = ctk_root / "bin" / "compute-sanitizer.bat" mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) mocker.patch.object(binary_finder_module.supported_nvidia_binaries, "SITE_PACKAGES_BINDIRS", {}) monkeypatch.delenv("CONDA_PREFIX", raising=False) mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=None) - canary = mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=ctk_root) + canary = mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary", return_value=str(ctk_root)) checked = _patch_exec_probe(mocker, existing=[launcher]) assert find_nvidia_binary_utility("compute-sanitizer") == os.path.abspath(launcher) @@ -195,14 +198,14 @@ def test_find_compute_sanitizer_uses_canary_ctk_root(monkeypatch, mocker): @pytest.mark.usefixtures("clear_find_binary_cache") @pytest.mark.agent_authored(model="gpt-5.6") def test_find_binary_windows_nsight_conda_precedes_registry(monkeypatch, mocker, utility_name, candidate_names): - site_dir = os.path.join(os.sep, "site-packages", utility_name, "bin") - conda_prefix = os.path.join(os.sep, "conda") - conda_bin = os.path.join(conda_prefix, "Library", "bin") - expected = os.path.join(conda_bin, candidate_names[0]) + site_dir = Path(os.sep, "site-packages", utility_name, "bin") + conda_prefix = Path(os.sep, "conda") + conda_bin = conda_prefix / "Library" / "bin" + expected = conda_bin / candidate_names[0] mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) - mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[site_dir]) - monkeypatch.setenv("CONDA_PREFIX", conda_prefix) + mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[str(site_dir)]) + monkeypatch.setenv("CONDA_PREFIX", str(conda_prefix)) candidate_paths = mocker.patch.object(binary_finder_module.windows_nsight, f"{utility_name}_candidate_paths") get_cuda_path = mocker.patch.object(binary_finder_module, "get_cuda_path_or_home") canary = mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary") @@ -210,8 +213,8 @@ def test_find_binary_windows_nsight_conda_precedes_registry(monkeypatch, mocker, assert find_nvidia_binary_utility(utility_name) == os.path.abspath(expected) assert checked == [ - *(os.path.join(site_dir, name) for name in candidate_names), - os.path.join(conda_bin, candidate_names[0]), + *(site_dir / name for name in candidate_names), + conda_bin / candidate_names[0], ] candidate_paths.assert_not_called() get_cuda_path.assert_not_called() @@ -244,17 +247,18 @@ def test_find_binary_windows_nsight_conda_precedes_registry(monkeypatch, mocker, def test_find_binary_windows_nsight_composes_registry_and_native_target( monkeypatch, mocker, utility_name, product, machine_arch, target_rel, candidate_names ): - site_dir = os.path.join(os.sep, "site-packages", utility_name, "bin") - conda_prefix = os.path.join(os.sep, "conda") - conda_bin = os.path.join(conda_prefix, "Library", "bin") - install_root = os.path.join(os.sep, "Program Files", utility_name) - expected = os.path.join(install_root, target_rel) + site_dir = Path(os.sep, "site-packages", utility_name, "bin") + conda_prefix = Path(os.sep, "conda") + conda_bin = conda_prefix / "Library" / "bin" + install_root = Path(os.sep, "Program Files", utility_name) + expected = install_root / target_rel mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) - mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[site_dir]) - monkeypatch.setenv("CONDA_PREFIX", conda_prefix) + mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[str(site_dir)]) + monkeypatch.setenv("CONDA_PREFIX", str(conda_prefix)) + # windows_nsight still works in str; it is not part of this migration step. registry_root = mocker.patch.object( - binary_finder_module.windows_nsight, "_installed_product_root", return_value=install_root + binary_finder_module.windows_nsight, "_installed_product_root", return_value=str(install_root) ) machine_arch_mock = mocker.patch.object( binary_finder_module.windows_nsight, "windows_machine_arch", return_value=machine_arch @@ -265,8 +269,8 @@ def test_find_binary_windows_nsight_composes_registry_and_native_target( assert find_nvidia_binary_utility(utility_name) == os.path.abspath(expected) assert checked == [ - *(os.path.join(directory, name) for directory in (site_dir, conda_bin) for name in candidate_names), - *((os.path.join(install_root, "ncu.bat"),) if utility_name == "ncu" else ()), + *(directory / name for directory in (site_dir, conda_bin) for name in candidate_names), + *((install_root / "ncu.bat",) if utility_name == "ncu" else ()), expected, ] registry_root.assert_called_once_with(product) @@ -278,14 +282,14 @@ def test_find_binary_windows_nsight_composes_registry_and_native_target( @pytest.mark.usefixtures("clear_find_binary_cache") @pytest.mark.agent_authored(model="gpt-5.6") def test_find_binary_windows_ncu_launcher_hit_does_not_resolve_machine_arch(monkeypatch, mocker): - install_root = os.path.join(os.sep, "Program Files", "Nsight Compute") - launcher = os.path.join(install_root, "ncu.bat") + install_root = Path(os.sep, "Program Files", "Nsight Compute") + launcher = install_root / "ncu.bat" mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) mocker.patch.object(binary_finder_module.supported_nvidia_binaries, "SITE_PACKAGES_BINDIRS", {}) monkeypatch.delenv("CONDA_PREFIX", raising=False) registry_root = mocker.patch.object( - binary_finder_module.windows_nsight, "_installed_product_root", return_value=install_root + binary_finder_module.windows_nsight, "_installed_product_root", return_value=str(install_root) ) machine_arch = mocker.patch.object(binary_finder_module.windows_nsight, "windows_machine_arch") get_cuda_path = mocker.patch.object(binary_finder_module, "get_cuda_path_or_home") @@ -325,11 +329,11 @@ def test_find_binary_windows_nsight_registry_miss_is_terminal(monkeypatch, mocke @pytest.mark.usefixtures("clear_find_binary_cache") @pytest.mark.agent_authored(model="gpt-5.6") def test_find_windows_nsight_legacy_names_remain_literal_in_early_search(monkeypatch, mocker, utility_name): - site_key = os.path.join("nvidia", utility_name, "bin") - site_dir = os.path.join(os.sep, "site-packages", utility_name, "bin") - conda_prefix = os.path.join(os.sep, "conda") - conda_bin = os.path.join(conda_prefix, "Library", "bin") - expected = os.path.join(conda_bin, f"{utility_name}.exe") + site_key = ("nvidia", utility_name, "bin") + site_dir = Path(os.sep, "site-packages", utility_name, "bin") + conda_prefix = Path(os.sep, "conda") + conda_bin = conda_prefix / "Library" / "bin" + expected = conda_bin / f"{utility_name}.exe" mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) mocker.patch.object( @@ -337,16 +341,18 @@ def test_find_windows_nsight_legacy_names_remain_literal_in_early_search(monkeyp "SITE_PACKAGES_BINDIRS", {utility_name: (site_key,)}, ) - find_sub_dirs = mocker.patch.object(binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[site_dir]) - monkeypatch.setenv("CONDA_PREFIX", conda_prefix) + find_sub_dirs = mocker.patch.object( + binary_finder_module, "find_sub_dirs_all_sitepackages", return_value=[str(site_dir)] + ) + monkeypatch.setenv("CONDA_PREFIX", str(conda_prefix)) get_cuda_path = mocker.patch.object(binary_finder_module, "get_cuda_path_or_home") nsys_candidates = mocker.patch.object(binary_finder_module.windows_nsight, "nsys_candidate_paths") ncu_candidates = mocker.patch.object(binary_finder_module.windows_nsight, "ncu_candidate_paths") checked = _patch_exec_probe(mocker, existing=[expected]) assert find_nvidia_binary_utility(utility_name) == os.path.abspath(expected) - assert checked == [os.path.join(site_dir, f"{utility_name}.exe"), expected] - find_sub_dirs.assert_called_once_with(site_key.split(os.sep)) + assert checked == [site_dir / f"{utility_name}.exe", expected] + find_sub_dirs.assert_called_once_with(site_key) get_cuda_path.assert_not_called() nsys_candidates.assert_not_called() ncu_candidates.assert_not_called() @@ -356,13 +362,13 @@ def test_find_windows_nsight_legacy_names_remain_literal_in_early_search(monkeyp @pytest.mark.usefixtures("clear_find_binary_cache") @pytest.mark.agent_authored(model="gpt-5.6") def test_find_windows_nsight_legacy_names_remain_literal_in_ctk(monkeypatch, mocker, utility_name): - cuda_home = os.path.join(os.sep, "cuda") - expected = os.path.join(cuda_home, "bin", f"{utility_name}.exe") + cuda_home = Path(os.sep, "cuda") + expected = cuda_home / "bin" / f"{utility_name}.exe" mocker.patch.object(binary_finder_module, "IS_WINDOWS", new=True) mocker.patch.object(binary_finder_module.supported_nvidia_binaries, "SITE_PACKAGES_BINDIRS", {}) monkeypatch.delenv("CONDA_PREFIX", raising=False) - mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=cuda_home) + mocker.patch.object(binary_finder_module, "get_cuda_path_or_home", return_value=str(cuda_home)) nsys_candidates = mocker.patch.object(binary_finder_module.windows_nsight, "nsys_candidate_paths") ncu_candidates = mocker.patch.object(binary_finder_module.windows_nsight, "ncu_candidate_paths") canary = mocker.patch.object(binary_finder_module, "_resolve_ctk_root_via_canary") @@ -370,8 +376,8 @@ def test_find_windows_nsight_legacy_names_remain_literal_in_ctk(monkeypatch, moc assert find_nvidia_binary_utility(utility_name) == os.path.abspath(expected) assert checked == [ - os.path.join(cuda_home, "bin", "x64", f"{utility_name}.exe"), - os.path.join(cuda_home, "bin", "x86_64", f"{utility_name}.exe"), + cuda_home / "bin" / "x64" / f"{utility_name}.exe", + cuda_home / "bin" / "x86_64" / f"{utility_name}.exe", expected, ] nsys_candidates.assert_not_called()