Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions cuda_core/cuda/core/utils/_program_cache/_file_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,20 @@ def __init__(
*,
max_size_bytes: int | None = None,
) -> None:
if max_size_bytes is not None and max_size_bytes <= 0:
raise ValueError("max_size_bytes must be positive or None (0 would evict every write)")
if max_size_bytes is not None and (
# bool is an int subclass, so True would sail through as a 1-byte
# cap that discards every write while its twin False is rejected.
isinstance(max_size_bytes, bool) or not isinstance(max_size_bytes, int) or max_size_bytes <= 0
):
raise ValueError(
f"max_size_bytes must be a positive int or None (0 would evict every write), got {max_size_bytes!r}"
)
if path is not None and os.fspath(path) == "":
# Path("") is Path("."), so an empty string would quietly root the
# cache in the current working directory and create entries/ and
# tmp/ there. Callers reach this via os.environ.get("VAR", ""); say
# so rather than scribbling in whatever directory they ran from.
raise ValueError("path must be a non-empty directory, or None to use the default user cache directory")
self._root = Path(path) if path is not None else _default_cache_dir()
self._entries = self._root / _ENTRIES_SUBDIR
self._tmp = self._root / _TMP_SUBDIR
Expand Down
10 changes: 8 additions & 2 deletions cuda_core/cuda/core/utils/_program_cache/_in_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,14 @@ def __init__(
*,
max_size_bytes: int | None = None,
) -> None:
if max_size_bytes is not None and max_size_bytes <= 0:
raise ValueError("max_size_bytes must be positive or None (0 would evict every write)")
if max_size_bytes is not None and (
# bool is an int subclass, so True would sail through as a 1-byte
# cap that discards every write while its twin False is rejected.
isinstance(max_size_bytes, bool) or not isinstance(max_size_bytes, int) or max_size_bytes <= 0
):
raise ValueError(
f"max_size_bytes must be a positive int or None (0 would evict every write), got {max_size_bytes!r}"
)
self._max_size_bytes = max_size_bytes
# Key insertion order encodes LRU order: oldest first, newest last.
# Each value is ``(payload_bytes, payload_size)``; caching the size
Expand Down
8 changes: 8 additions & 0 deletions cuda_core/docs/source/release/1.2.0-notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ Fixes and enhancements
Windows, both ``ctypes.CFUNCTYPE`` and ``ctypes.WINFUNCTYPE`` are accepted.
(`#2439 <https://github.com/NVIDIA/cuda-python/issues/2439>`__)

- The program caches now reject a ``max_size_bytes`` that is not a positive
``int``. ``max_size_bytes=True`` was accepted as a one-byte cap that silently
discarded every write, even though ``False`` was rejected, and a ``str`` cap
raised ``TypeError`` from the comparison rather than the documented
``ValueError``. :class:`~cuda.core.utils.FileStreamProgramCache` also rejects
an empty ``path``: ``Path("")`` is ``Path(".")``, so it used to root the cache
in the current working directory.

Deprecation Notices
-------------------

Expand Down
54 changes: 54 additions & 0 deletions cuda_core/tests/test_program_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,51 @@ def test_filestream_cache_rejects_non_positive_size_cap(tmp_path, bad):
FileStreamProgramCache(tmp_path / "fc", max_size_bytes=bad)


# Values a bare `<= 0` test lets through. `True` is the sharp one: bool is an
# int subclass, so it becomes a 1-byte cap that silently discards every write,
# while its twin `False` is rejected.
NON_INT_SIZE_CAPS = [
pytest.param(True, id="bool-true"),
pytest.param(1.5, id="float"),
pytest.param("100", id="str"),
]


@pytest.mark.agent_authored(model="claude-opus-5")
@pytest.mark.parametrize("bad", NON_INT_SIZE_CAPS)
def test_filestream_cache_rejects_non_int_size_cap(tmp_path, bad):
from cuda.core.utils import FileStreamProgramCache

with pytest.raises(ValueError, match="positive"):
FileStreamProgramCache(tmp_path / "fc", max_size_bytes=bad)


@pytest.mark.agent_authored(model="claude-opus-5")
def test_filestream_cache_rejects_an_empty_path(tmp_path, monkeypatch):
"""`Path("")` is `Path(".")`, so an empty path would root the cache in the
current working directory and create entries/ and tmp/ there. Callers reach
this through `os.environ.get("VAR", "")`."""
from cuda.core.utils import FileStreamProgramCache

monkeypatch.chdir(tmp_path)
with pytest.raises(ValueError, match="non-empty directory"):
FileStreamProgramCache("")

assert sorted(p.name for p in tmp_path.iterdir()) == []


@pytest.mark.agent_authored(model="claude-opus-5")
def test_filestream_cache_still_accepts_an_explicit_dot(tmp_path, monkeypatch):
"""Only the empty string is rejected; an explicit relative path is fine."""
from cuda.core.utils import FileStreamProgramCache

monkeypatch.chdir(tmp_path)
with FileStreamProgramCache(".") as cache:
cache[b"k"] = b"hello"
assert cache[b"k"] == b"hello"
assert (tmp_path / "entries").is_dir()


def test_default_cache_dir_lives_under_user_cache_root(monkeypatch, tmp_path):
"""The cache root is platform-specific:

Expand Down Expand Up @@ -2442,6 +2487,15 @@ def test_inmemory_cache_rejects_non_positive_size_cap(bad):
InMemoryProgramCache(max_size_bytes=bad)


@pytest.mark.agent_authored(model="claude-opus-5")
@pytest.mark.parametrize("bad", NON_INT_SIZE_CAPS)
def test_inmemory_cache_rejects_non_int_size_cap(bad):
from cuda.core.utils import InMemoryProgramCache

with pytest.raises(ValueError, match="positive"):
InMemoryProgramCache(max_size_bytes=bad)


def test_inmemory_cache_size_cap_evicts_oldest():
from cuda.core.utils import InMemoryProgramCache

Expand Down
Loading