diff --git a/cuda_core/cuda/core/_context.pyx b/cuda_core/cuda/core/_context.pyx index 61fd0f79d4..9cbee57407 100644 --- a/cuda_core/cuda/core/_context.pyx +++ b/cuda_core/cuda/core/_context.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 @@ -48,6 +48,9 @@ cdef class Context: def __hash__(self) -> int: return hash(as_intptr(self._h_context)) + def __repr__(self) -> str: + return f"Context(handle={as_intptr(self._h_context):#x}, device={self._device_id})" + @dataclass class ContextOptions: diff --git a/cuda_core/cuda/core/_cpp/resource_handles.cpp b/cuda_core/cuda/core/_cpp/resource_handles.cpp index 3ae51dd310..581a7af69a 100644 --- a/cuda_core/cuda/core/_cpp/resource_handles.cpp +++ b/cuda_core/cuda/core/_cpp/resource_handles.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // // SPDX-License-Identifier: Apache-2.0 diff --git a/cuda_core/cuda/core/_cpp/resource_handles.hpp b/cuda_core/cuda/core/_cpp/resource_handles.hpp index 7a9a4752aa..b6118a07a2 100644 --- a/cuda_core/cuda/core/_cpp/resource_handles.hpp +++ b/cuda_core/cuda/core/_cpp/resource_handles.hpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. // // SPDX-License-Identifier: Apache-2.0 diff --git a/cuda_core/cuda/core/_event.pyx b/cuda_core/cuda/core/_event.pyx index 42916b257b..225417a9e8 100644 --- a/cuda_core/cuda/core/_event.pyx +++ b/cuda_core/cuda/core/_event.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 @@ -178,6 +178,9 @@ cdef class Event: cdef Event _other = other return as_intptr(self._h_event) == as_intptr(_other._h_event) + def __repr__(self) -> str: + return f"Event(handle={as_intptr(self._h_event):#x})" + def get_ipc_descriptor(self) -> IPCEventDescriptor: """Export an event allocated for sharing between processes.""" if self._ipc_descriptor is not None: diff --git a/cuda_core/cuda/core/_launch_config.pyx b/cuda_core/cuda/core/_launch_config.pyx index 0437a0634b..798df71d9e 100644 --- a/cuda_core/cuda/core/_launch_config.pyx +++ b/cuda_core/cuda/core/_launch_config.pyx @@ -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 diff --git a/cuda_core/cuda/core/_launcher.pyx b/cuda_core/cuda/core/_launcher.pyx index 48eb2038b2..ce5f7339e0 100644 --- a/cuda_core/cuda/core/_launcher.pyx +++ b/cuda_core/cuda/core/_launcher.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 diff --git a/cuda_core/cuda/core/_linker.py b/cuda_core/cuda/core/_linker.py index ab08709690..6490e87b07 100644 --- a/cuda_core/cuda/core/_linker.py +++ b/cuda_core/cuda/core/_linker.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 diff --git a/cuda_core/cuda/core/_memory/_buffer.pyx b/cuda_core/cuda/core/_memory/_buffer.pyx index 57ec315201..f5389cea82 100644 --- a/cuda_core/cuda/core/_memory/_buffer.pyx +++ b/cuda_core/cuda/core/_memory/_buffer.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 @@ -334,6 +334,9 @@ cdef class Buffer: def __hash__(self) -> int: return hash((as_intptr(self._h_ptr), self._size)) + def __repr__(self) -> str: + return f"Buffer(ptr={as_intptr(self._h_ptr):#x}, size={self._size})" + @property def is_device_accessible(self) -> bool: """Return True if this buffer can be accessed by the GPU, otherwise False.""" diff --git a/cuda_core/cuda/core/_memory/_memory_pool.pxd b/cuda_core/cuda/core/_memory/_memory_pool.pxd index bb192110c6..a8838bf9dc 100644 --- a/cuda_core/cuda/core/_memory/_memory_pool.pxd +++ b/cuda_core/cuda/core/_memory/_memory_pool.pxd @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 diff --git a/cuda_core/cuda/core/_memory/_memory_pool.pyx b/cuda_core/cuda/core/_memory/_memory_pool.pyx index f33a5dc077..1e9f5116c1 100644 --- a/cuda_core/cuda/core/_memory/_memory_pool.pyx +++ b/cuda_core/cuda/core/_memory/_memory_pool.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 diff --git a/cuda_core/cuda/core/_module.pxd b/cuda_core/cuda/core/_module.pxd index 991c5a48cd..9468de3dff 100644 --- a/cuda_core/cuda/core/_module.pxd +++ b/cuda_core/cuda/core/_module.pxd @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 diff --git a/cuda_core/cuda/core/_module.pyx b/cuda_core/cuda/core/_module.pyx index 88efe8e3b0..fed3a4a981 100644 --- a/cuda_core/cuda/core/_module.pyx +++ b/cuda_core/cuda/core/_module.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 @@ -613,6 +613,9 @@ cdef class Kernel: def __hash__(self) -> int: return hash(as_intptr(self._h_kernel)) + def __repr__(self) -> str: + return f"Kernel(handle={as_intptr(self._h_kernel):#x})" + CodeTypeT = bytes | bytearray | str @@ -864,3 +867,8 @@ cdef class ObjectCode: # Trigger lazy load to get the handle self._lazy_load_module() return hash(as_intptr(self._h_library)) + + def __repr__(self) -> str: + # Trigger lazy load to get the handle + self._lazy_load_module() + return f"ObjectCode(handle={as_intptr(self._h_library):#x}, code_type='{self._code_type}')" diff --git a/cuda_core/cuda/core/_resource_handles.pxd b/cuda_core/cuda/core/_resource_handles.pxd index d8914632dc..5f08172909 100644 --- a/cuda_core/cuda/core/_resource_handles.pxd +++ b/cuda_core/cuda/core/_resource_handles.pxd @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 diff --git a/cuda_core/cuda/core/_resource_handles.pyx b/cuda_core/cuda/core/_resource_handles.pyx index 9870f8a0f2..0d3e732a4f 100644 --- a/cuda_core/cuda/core/_resource_handles.pyx +++ b/cuda_core/cuda/core/_resource_handles.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 diff --git a/cuda_core/cuda/core/_stream.pyx b/cuda_core/cuda/core/_stream.pyx index 7f39b5a992..01b44dd3f8 100644 --- a/cuda_core/cuda/core/_stream.pyx +++ b/cuda_core/cuda/core/_stream.pyx @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: Apache-2.0 @@ -221,6 +221,9 @@ cdef class Stream: return NotImplemented return as_intptr(self._h_stream) == as_intptr((other)._h_stream) + def __repr__(self) -> str: + return f"Stream(handle={as_intptr(self._h_stream):#x})" + @property def handle(self) -> cuda.bindings.driver.CUstream: """Return the underlying ``CUstream`` object. diff --git a/cuda_core/tests/test_comparable.py b/cuda_core/tests/test_comparable.py deleted file mode 100644 index 281ed4ab1c..0000000000 --- a/cuda_core/tests/test_comparable.py +++ /dev/null @@ -1,157 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -""" -Tests for __eq__ and __ne__ implementations in cuda.core classes. - -These tests verify multi-type equality behavior and subclassing equality behavior -across Device, Stream, Event, and Context objects. -""" - -from cuda.core import Device, Stream -from cuda.core._stream import StreamOptions - -# ============================================================================ -# Equality Contract Tests -# ============================================================================ - - -def test_equality_is_not_identity(): - """Test that equality (==) is different from identity (is).""" - device = Device(0) - device.set_current() - - # Streams: Different objects can be equal - s1 = device.create_stream() - s2 = Stream.from_handle(int(s1.handle)) - - assert s1 == s2, "Streams with same handle are equal" - assert s1 is not s2, "But they are not the same object" - - # Device: Same object due to singleton (special case) - d1 = Device(0) - d2 = Device(0) - - assert d1 == d2, "Devices with same ID are equal" - assert d1 is d2, "And they ARE the same object (singleton)" - - -# ============================================================================ -# Subclassing Equality Tests -# ============================================================================ - - -def test_device_subclass_equality(init_cuda): - """Test Device subclass equality behavior. - - Device uses a singleton pattern where Device(0) always returns the same - cached instance. This means subclassing Device doesn't create new instances; - MyDevice(0) returns the original Device(0) instance from the cache. - """ - - class MyDevice(Device): - pass - - device = Device(0) - device.set_current() - my_device = MyDevice(0) - - # Due to singleton pattern, both return the exact same instance - assert device is my_device, "Device singleton returns same instance for same device_id" - assert type(device) is Device, "Singleton returns original Device type, not subclass" - assert type(my_device) is Device, "Even MyDevice(0) returns Device instance due to singleton" - - # Since they're the same object, they're equal - assert device == my_device - - -def test_stream_subclass_equality(init_cuda): - """Test Stream subclass equality behavior. - - Stream uses isinstance() for equality checking, which means a Stream instance - and a MyStream subclass instance wrapping the same handle will compare equal. - """ - - class MyStream(Stream): - pass - - device = Device(0) - device.set_current() - - # Create base Stream instance - stream = Stream._init(options=StreamOptions(), device_id=device.device_id) - - # Create another Stream wrapping same handle - stream2 = Stream.from_handle(int(stream.handle)) - assert stream == stream2, "Streams wrapping same handle are equal" - - # Create subclass instance with different handle - my_stream = MyStream._init(options=StreamOptions(), device_id=device.device_id) - - # Different handles -> not equal - assert stream != my_stream, "Streams with different handles are not equal" - assert stream.handle != my_stream.handle - - # sanity check: base and subclass compare equal (and hash equal) - stream_from_handle = MyStream.from_handle(int(my_stream.handle)) - assert my_stream == stream_from_handle, "MyStream and Stream wrapping same handle compare equal" - assert hash(my_stream) == hash(stream_from_handle) - - -def test_event_subclass_equality(init_cuda): - """Test Event subclass equality behavior. - - Event uses isinstance() for equality checking, similar to Stream. - """ - device = Device(0) - device.set_current() - - # Create events using public API - event1 = device.create_event() - event2 = device.create_event() - event3 = device.create_event() - - # Different events should not be equal (different handles) - assert event1 != event2, "Different Event instances are not equal" - assert event2 != event3, "Different Event instances are not equal" - - -def test_context_equality(init_cuda): - """Test Context equality behavior.""" - device = Device(0) - device.set_current() - - # Get context from different sources - stream1 = device.create_stream() - stream2 = device.create_stream() - context1 = stream1.context - context2 = stream2.context - device_context = device.context - - # Same device, same primary context, should be equal - assert context1 == context2, "Contexts from same device are equal" - assert context1 == device_context, "Stream context equals device context" - - -def test_subclass_type_safety(init_cuda): - """Test that equality checks with incompatible types return False or NotImplemented.""" - device = Device(0) - device.set_current() - - stream = device.create_stream() - event = stream.record() - context = stream.context - - # None of these should be equal to each other - assert device != stream - assert device != event - assert device != context - assert stream != event - assert stream != context - assert event != context - - # None should be equal to arbitrary types - assert device != "device" - assert stream != 123 - assert event != [] - assert context != {"key": "value"} diff --git a/cuda_core/tests/test_hashable.py b/cuda_core/tests/test_hashable.py deleted file mode 100644 index 18d9e047e4..0000000000 --- a/cuda_core/tests/test_hashable.py +++ /dev/null @@ -1,289 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -""" -Tests for __hash__ implementation in cuda.core classes. - -These tests verify: -1. Multi-type hash behavior and collision prevention -2. Subclassing hash behavior for all core types -3. Hash consistency (same object always returns same hash) -4. Dictionary/set usage patterns -5. Hash/equality contract compliance (if a == b, then hash(a) must equal hash(b)) -""" - -import pytest -from cuda.core import Device, LaunchConfig, Program -from cuda.core._stream import Stream, StreamOptions - -# ============================================================================ -# Fixtures for parameterized tests -# ============================================================================ - - -@pytest.fixture -def sample_device(init_cuda): - return Device() - - -@pytest.fixture -def sample_stream(sample_device): - return sample_device.create_stream() - - -@pytest.fixture -def sample_event(sample_device): - return sample_device.create_event() - - -@pytest.fixture -def sample_context(sample_device): - return sample_device.context - - -@pytest.fixture -def sample_buffer(sample_device): - return sample_device.allocate(1024) - - -@pytest.fixture -def sample_launch_config(): - return LaunchConfig(grid=(1,), block=(1,)) - - -@pytest.fixture -def sample_object_code(init_cuda): - prog = Program('extern "C" __global__ void test_kernel() {}', "c++") - return prog.compile("cubin") - - -@pytest.fixture -def sample_kernel(sample_object_code): - return sample_object_code.get_kernel("test_kernel") - - -# All hashable classes -HASHABLE = [ - "sample_device", - "sample_stream", - "sample_event", - "sample_context", - "sample_buffer", - "sample_launch_config", - "sample_object_code", - "sample_kernel", -] - - -# ============================================================================ -# Parameterized Hash Tests -# ============================================================================ - - -@pytest.mark.parametrize("fixture_name", HASHABLE) -def test_hash_consistency(fixture_name, request): - """Hash of same object is consistent across calls.""" - obj = request.getfixturevalue(fixture_name) - assert hash(obj) == hash(obj) - - -@pytest.mark.parametrize("fixture_name", HASHABLE) -def test_set_membership(fixture_name, request): - """Objects work correctly in sets.""" - obj = request.getfixturevalue(fixture_name) - s = {obj} - assert obj in s - assert len(s) == 1 - - -@pytest.mark.parametrize("fixture_name", HASHABLE) -def test_dict_key(fixture_name, request): - """Objects work correctly as dict keys.""" - obj = request.getfixturevalue(fixture_name) - d = {obj: "value"} - assert d[obj] == "value" - - -# ============================================================================ -# Integration Tests -# ============================================================================ - - -def test_mixed_type_dict(init_cuda): - """Test that different object types can coexist in dictionaries. - - Since each CUDA handle type has unique values within its type (handles are - memory addresses or unique identifiers), hash collisions between different - types are unlikely in practice. - """ - device = Device(0) - device.set_current() - - # Create objects of different types - stream = device.create_stream() - event = stream.record() - context = stream.context - - # Test 1: Verify all hashes are unique (no collisions between different types) - hashes = {hash(device), hash(stream), hash(event), hash(context)} - - assert len(hashes) == 4, f"Hash collision detected! Expected 4 unique hashes, got {len(hashes)}. " - - # Test 2: Verify all types can coexist in same dict without conflicts - mixed_cache = {stream: "stream_data", event: "event_data", context: "context_data", device: "device_data"} - - assert len(mixed_cache) == 4, "All object types should coexist in dict" - assert mixed_cache[stream] == "stream_data" - assert mixed_cache[event] == "event_data" - assert mixed_cache[context] == "context_data" - assert mixed_cache[device] == "device_data" - - -# ============================================================================ -# Subclassing Hash Tests -# ============================================================================ - - -def test_device_subclass_hash(init_cuda): - """Test Device subclass hash behavior. - - Device uses a singleton pattern where Device(0) always returns the same - cached instance. This means MyDevice(0) returns the original Device instance, - not a new MyDevice instance. - """ - - class MyDevice(Device): - pass - - device = Device(0) - device.set_current() - my_device = MyDevice(0) - - # Singleton returns same instance, so hashes are identical - assert device is my_device, "Singleton returns same instance" - assert device == my_device, "Singleton returns same instance" - assert hash(device) == hash(my_device), "Same object has same hash" - - # Verify hash consistency - hash1 = hash(device) - hash2 = hash(device) - assert hash1 == hash2, "Hash is consistent across multiple calls" - - -def test_stream_subclass_hash(init_cuda): - """Test Stream subclass hash behavior.""" - - class MyStream(Stream): - pass - - device = Device(0) - device.set_current() - - # Same type, same handle -> same hash - stream1 = Stream._init(options=StreamOptions(), device_id=device.device_id) - stream2 = Stream.from_handle(int(stream1.handle)) - assert hash(stream1) == hash(stream2), "Streams wrapping same handle have same hash" - - # Verify hash consistency - hash1 = hash(stream1) - hash2 = hash(stream1) - assert hash1 == hash2, "Hash is consistent across multiple calls" - - # Different type, same handle -> SAME hash (type not included in hash) - my_stream = MyStream._init(options=StreamOptions(), device_id=device.device_id) - stream_from_handle = Stream.from_handle(int(my_stream.handle)) - - assert type(stream_from_handle) is Stream, "from_handle returns Stream, not subclass" - assert hash(my_stream) == hash(stream_from_handle), ( - "Same handle produces same hash regardless of type (maintains hash/equality contract)" - ) - - # Verify equality matches hash - assert my_stream == stream_from_handle, "Equal due to isinstance() and same handle" - assert hash(my_stream) == hash(stream_from_handle), "Equal objects have equal hashes" - - # Different handles -> different hashes - my_stream2 = MyStream._init(options=StreamOptions(), device_id=device.device_id) - assert my_stream != my_stream2, "Different streams are not equal" - assert hash(my_stream) != hash(my_stream2), "Different streams have different hashes" - - -def test_event_hash(init_cuda): - """Test Event hash behavior.""" - device = Device(0) - device.set_current() - - # Create events using public API - event1 = device.create_event() - event2 = device.create_event() - - # Different events (different handles) -> different hashes - assert hash(event1) != hash(event2), "Different events have different hashes" - assert event1 != event2, "Different handles means not equal" - - # Verify hash consistency - hash1 = hash(event1) - hash2 = hash(event1) - assert hash1 == hash2, "Hash is consistent across multiple calls" - - # Both should be usable as dict keys - cache = {event1: "first", event2: "second"} - assert len(cache) == 2, "Different events are distinct dict keys" - assert cache[event1] == "first" - assert cache[event2] == "second" - - -def test_context_hash(init_cuda): - """Test Context hash behavior.""" - device = Device(0) - device.set_current() - - # Get context from different sources - stream1 = device.create_stream() - stream2 = device.create_stream() - context1 = stream1.context - context2 = stream2.context - - # Same underlying context -> same hash - assert hash(context1) == hash(context2), "Contexts with same handle have same hash" - - # Verify equality matches hash - assert context1 == context2, "Contexts with same handle are equal" - - # Verify hash consistency - hash1 = hash(context1) - hash2 = hash(context1) - assert hash1 == hash2, "Hash is consistent across multiple calls" - - -def test_hash_equality_contract_maintained(init_cuda): - """Verify that the hash/equality contract is maintained with subclasses. - - This test demonstrates that Stream (and other classes) now properly maintain - Python's invariant: if a == b, then hash(a) must equal hash(b) - - The fix: removed type(self) from __hash__ while keeping isinstance() in __eq__, - allowing cross-type equality with consistent hashing. - """ - - device = Device(0) - device.set_current() - - # Test Stream: two references to same handle - stream1 = device.create_stream() - stream2 = Stream.from_handle(int(stream1.handle)) - - assert stream1 == stream2, "Equal due to same handle" - assert hash(stream1) == hash(stream2), "Equal objects have equal hashes" - - # Test Context: contexts from same device share same underlying context - ctx1 = device.context - ctx2 = device.create_stream().context - - assert ctx1 == ctx2, "Equal contexts with same handle" - assert hash(ctx1) == hash(ctx2), "Equal objects have equal hashes" - - # Test that different handles still produce different hashes - stream3 = device.create_stream() - assert stream1 != stream3, "Different handles means not equal" - assert hash(stream1) != hash(stream3), "Different objects have different hashes" diff --git a/cuda_core/tests/test_memory.py b/cuda_core/tests/test_memory.py index 47091995e7..9a88f5f483 100644 --- a/cuda_core/tests/test_memory.py +++ b/cuda_core/tests/test_memory.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import ctypes diff --git a/cuda_core/tests/test_module.py b/cuda_core/tests/test_module.py index 72591b54d5..c0760cee45 100644 --- a/cuda_core/tests/test_module.py +++ b/cuda_core/tests/test_module.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 import ctypes diff --git a/cuda_core/tests/test_object_protocols.py b/cuda_core/tests/test_object_protocols.py new file mode 100644 index 0000000000..90d4be4320 --- /dev/null +++ b/cuda_core/tests/test_object_protocols.py @@ -0,0 +1,315 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +""" +Tests for Python object protocols (__eq__, __hash__, __weakref__, __repr__). + +This module tests that core cuda.core classes properly implement standard Python +object protocols for identity, hashing, weak references, and string representation. +""" + +import itertools +import re +import weakref + +import pytest +from cuda.core import Device, LaunchConfig, Program, system + +# ============================================================================= +# Fixtures - Primary samples +# ============================================================================= + + +@pytest.fixture +def sample_device(init_cuda): + """A sample Device object.""" + return Device(0) + + +@pytest.fixture +def sample_stream(sample_device): + """A sample Stream object.""" + return sample_device.create_stream() + + +@pytest.fixture +def sample_event(sample_device): + """A sample Event object.""" + return sample_device.create_event() + + +@pytest.fixture +def sample_context(sample_device): + """A sample Context object.""" + return sample_device.context + + +@pytest.fixture +def sample_buffer(sample_device): + """A sample Buffer object.""" + return sample_device.allocate(64) + + +@pytest.fixture +def sample_launch_config(): + """A sample LaunchConfig object.""" + return LaunchConfig(grid=(1,), block=(1,)) + + +@pytest.fixture +def sample_object_code(init_cuda): + """A sample ObjectCode object.""" + prog = Program('extern "C" __global__ void test_kernel() {}', "c++") + return prog.compile("cubin") + + +@pytest.fixture +def sample_kernel(sample_object_code): + """A sample Kernel object.""" + return sample_object_code.get_kernel("test_kernel") + + +# ============================================================================= +# Fixtures - Alternate samples (for inequality testing) +# ============================================================================= + + +@pytest.fixture +def sample_device_alt(init_cuda): + """An alternate Device object (requires multi-GPU).""" + if system.get_num_devices() < 2: + pytest.skip("requires multi-GPU") + return Device(1) + + +@pytest.fixture +def sample_stream_alt(sample_device): + """An alternate Stream object.""" + return sample_device.create_stream() + + +@pytest.fixture +def sample_event_alt(sample_device): + """An alternate Event object.""" + return sample_device.create_event() + + +@pytest.fixture +def sample_context_alt(sample_device_alt): + """An alternate Context object (requires multi-GPU).""" + sample_device_alt.set_current() + return sample_device_alt.context + + +@pytest.fixture +def sample_buffer_alt(sample_device): + """An alternate Buffer object.""" + return sample_device.allocate(1024) + + +@pytest.fixture +def sample_launch_config_alt(): + """An alternate LaunchConfig object.""" + return LaunchConfig(grid=(2,), block=(2,)) + + +@pytest.fixture +def sample_object_code_alt(init_cuda): + """An alternate ObjectCode object.""" + prog = Program('extern "C" __global__ void test_kernel_alt() {}', "c++") + return prog.compile("cubin") + + +@pytest.fixture +def sample_kernel_alt(sample_object_code_alt): + """An alternate Kernel object.""" + return sample_object_code_alt.get_kernel("test_kernel_alt") + + +# ============================================================================= +# Type groupings +# ============================================================================= + +# All types that should support weak references +API_TYPES = [ + "sample_device", + "sample_stream", + "sample_event", + "sample_context", + "sample_buffer", + "sample_launch_config", + "sample_object_code", + "sample_kernel", +] + +# Pairs of distinct objects of the same type (for inequality testing) +# Device and Context pairs require multi-GPU and will skip on single-GPU machines +SAME_TYPE_PAIRS = [ + ("sample_device", "sample_device_alt"), + ("sample_stream", "sample_stream_alt"), + ("sample_event", "sample_event_alt"), + ("sample_context", "sample_context_alt"), + ("sample_buffer", "sample_buffer_alt"), + ("sample_launch_config", "sample_launch_config_alt"), + ("sample_object_code", "sample_object_code_alt"), + ("sample_kernel", "sample_kernel_alt"), +] + +# Pairs of (fixture_name, regex_pattern) for repr format validation +REPR_PATTERNS = [ + ("sample_device", r""), + ("sample_stream", r"Stream\(handle=0x[0-9a-f]+\)"), + ("sample_event", r"Event\(handle=0x[0-9a-f]+\)"), + ("sample_context", r"Context\(handle=0x[0-9a-f]+, device=\d+\)"), + ("sample_buffer", r"Buffer\(ptr=0x[0-9a-f]+, size=\d+\)"), + ( + "sample_launch_config", + r"LaunchConfig\(grid=\(\d+, \d+, \d+\), cluster=.+, block=\(\d+, \d+, \d+\), " + r"shmem_size=\d+, cooperative_launch=(?:True|False)\)", + ), + ("sample_object_code", r"ObjectCode\(handle=0x[0-9a-f]+, code_type='.+'\)"), + ("sample_kernel", r"Kernel\(handle=0x[0-9a-f]+\)"), +] + + +# ============================================================================= +# Weak reference tests +# ============================================================================= + + +@pytest.mark.parametrize("fixture_name", API_TYPES) +def test_weakref_supported(fixture_name, request): + """Object supports weak references.""" + obj = request.getfixturevalue(fixture_name) + ref = weakref.ref(obj) + assert ref() is obj + + +# ============================================================================= +# Hash tests +# ============================================================================= + + +@pytest.mark.parametrize("fixture_name", API_TYPES) +def test_hash_consistency(fixture_name, request): + """Hash is consistent across multiple calls.""" + obj = request.getfixturevalue(fixture_name) + assert hash(obj) == hash(obj) + + +@pytest.mark.parametrize("fixture_name", API_TYPES) +def test_hash_not_small(fixture_name, request): + """Hash should not be a small number (guards against returning IDs or indices).""" + obj = request.getfixturevalue(fixture_name) + h = hash(obj) + assert abs(h) >= 10, f"hash {h} is suspiciously small" + + +@pytest.mark.parametrize("a_name,b_name", SAME_TYPE_PAIRS) +def test_hash_distinct_same_type(a_name, b_name, request): + """Distinct objects of the same type have different hashes.""" + obj_a = request.getfixturevalue(a_name) + obj_b = request.getfixturevalue(b_name) + assert hash(obj_a) != hash(obj_b), f"{a_name} and {b_name} have same hash but different handles" + + +@pytest.mark.parametrize("a_name,b_name", itertools.combinations(API_TYPES, 2)) +def test_hash_distinct_cross_type(a_name, b_name, request): + """Distinct objects of different types have different hashes.""" + obj_a = request.getfixturevalue(a_name) + obj_b = request.getfixturevalue(b_name) + assert hash(obj_a) != hash(obj_b), f"{a_name} and {b_name} have same hash" + + +# ============================================================================= +# Equality tests +# ============================================================================= + + +@pytest.mark.parametrize("fixture_name", API_TYPES) +def test_equality_basic(fixture_name, request): + """Object equality: reflexive, not equal to None or other types.""" + obj = request.getfixturevalue(fixture_name) + assert obj == obj, "reflexive equality failed" + assert obj != None, "should not equal None" # noqa: E711 + assert obj != "string", "should not equal unrelated type" + if hasattr(obj, "handle"): + assert obj != obj.handle, "should not equal its own handle" + + +@pytest.mark.parametrize("a_name,b_name", itertools.combinations(API_TYPES, 2)) +def test_no_cross_type_equality(a_name, b_name, request): + """No two distinct objects of different types should compare equal.""" + obj_a = request.getfixturevalue(a_name) + obj_b = request.getfixturevalue(b_name) + assert obj_a != obj_b, f"{a_name} == {b_name} but they are distinct objects" + + +@pytest.mark.parametrize("a_name,b_name", SAME_TYPE_PAIRS) +def test_same_type_inequality(a_name, b_name, request): + """Two distinct objects of the same type should not compare equal.""" + obj_a = request.getfixturevalue(a_name) + obj_b = request.getfixturevalue(b_name) + assert obj_a is not obj_b, f"{a_name} and {b_name} are the same object" + assert obj_a != obj_b, f"{a_name} == {b_name} but they have different handles" + + +# ============================================================================= +# Collection usage tests +# ============================================================================= + + +@pytest.mark.parametrize("fixture_name", API_TYPES) +def test_usable_as_dict_key(fixture_name, request): + """Object can be used as a dictionary key.""" + obj = request.getfixturevalue(fixture_name) + d = {obj: "value"} + assert d[obj] == "value" + assert obj in d + + +@pytest.mark.parametrize("fixture_name", API_TYPES) +def test_usable_in_set(fixture_name, request): + """Object can be added to a set.""" + obj = request.getfixturevalue(fixture_name) + s = {obj} + assert obj in s + + +@pytest.mark.parametrize("fixture_name", API_TYPES) +def test_usable_in_weak_value_dict(fixture_name, request): + """Object can be used as a WeakValueDictionary value.""" + obj = request.getfixturevalue(fixture_name) + wvd = weakref.WeakValueDictionary() + wvd["key"] = obj + assert wvd["key"] is obj + + +@pytest.mark.parametrize("fixture_name", API_TYPES) +def test_usable_in_weak_key_dict(fixture_name, request): + """Object can be used as a WeakKeyDictionary key.""" + obj = request.getfixturevalue(fixture_name) + wkd = weakref.WeakKeyDictionary() + wkd[obj] = "value" + assert wkd[obj] == "value" + + +@pytest.mark.parametrize("fixture_name", API_TYPES) +def test_usable_in_weak_set(fixture_name, request): + """Object can be added to a WeakSet.""" + obj = request.getfixturevalue(fixture_name) + ws = weakref.WeakSet() + ws.add(obj) + assert obj in ws + + +# ============================================================================= +# Repr tests +# ============================================================================= + + +@pytest.mark.parametrize("fixture_name,pattern", REPR_PATTERNS) +def test_repr_format(fixture_name, pattern, request): + """repr() returns a properly formatted string.""" + obj = request.getfixturevalue(fixture_name) + result = repr(obj) + assert re.fullmatch(pattern, result), f"repr {result!r} does not match {pattern!r}" diff --git a/cuda_core/tests/test_program.py b/cuda_core/tests/test_program.py index e2b3783dd7..259e6a9c98 100644 --- a/cuda_core/tests/test_program.py +++ b/cuda_core/tests/test_program.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # # SPDX-License-Identifier: LicenseRef-NVIDIA-SOFTWARE-LICENSE diff --git a/cuda_core/tests/test_weakref.py b/cuda_core/tests/test_weakref.py deleted file mode 100644 index 6167bbd733..0000000000 --- a/cuda_core/tests/test_weakref.py +++ /dev/null @@ -1,75 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# SPDX-License-Identifier: Apache-2.0 - -import weakref - -import pytest -from cuda.core import Device - - -@pytest.fixture(scope="module") -def device(): - dev = Device() - dev.set_current() - return dev - - -@pytest.fixture -def stream(device): - return device.create_stream() - - -@pytest.fixture -def event(device): - return device.create_event() - - -@pytest.fixture -def context(device): - return device.context - - -@pytest.fixture -def buffer(device): - return device.allocate(1024) - - -@pytest.fixture -def launch_config(): - from cuda.core import LaunchConfig - - return LaunchConfig(grid=(1,), block=(1,)) - - -@pytest.fixture -def object_code(): - from cuda.core import Program - - prog = Program('extern "C" __global__ void test_kernel() {}', "c++") - return prog.compile("cubin") - - -@pytest.fixture -def kernel(object_code): - return object_code.get_kernel("test_kernel") - - -WEAK_REFERENCEABLE = [ - "device", - "stream", - "event", - "context", - "buffer", - "launch_config", - "object_code", - "kernel", -] - - -@pytest.mark.parametrize("fixture_name", WEAK_REFERENCEABLE) -def test_weakref(fixture_name, request): - """Core API classes should be weak-referenceable.""" - obj = request.getfixturevalue(fixture_name) - ref = weakref.ref(obj) - assert ref() is obj