-
Notifications
You must be signed in to change notification settings - Fork 242
Add __repr__ to core API classes and consolidate object protocol tests #1538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
/ok to test 4828262 |
|
- Add __repr__ methods to Stream, Event, Context, Buffer, ObjectCode, Kernel - Create test_object_protocols.py with comprehensive tests for: - Weak references (weakref.ref, WeakValueDict, WeakKeyDict, WeakSet) - Hash consistency, distinctness, and small value guards - Equality (reflexive, cross-type, same-type distinct objects) - Collection usage (dict, set, weak collections) - repr format validation with regex patterns - Remove test_hashable.py, test_comparable.py, test_weakref.py (consolidated)
4828262 to
197da96
Compare
|
/ok to test 197da96 |
1 similar comment
|
/ok to test 197da96 |
|
/ok to test 763005c |
|
Logging a Cursor prompt/response below. What do you think about the "middle ground" approach?
|
rwgk
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor noted:
Inconsistent __repr__ styles
Device:<Device 0 (NVIDIA GeForce...)>(angle brackets, nohandle=)Stream:Stream(handle=0x...)(constructor-style)LaunchConfig:LaunchConfig(grid=..., cluster=..., ...)(constructor-style, all attrs)
Are these design choices? Could we make the fully, or maybe more, uniform?
|
|
||
| def __repr__(self) -> str: | ||
| # Trigger lazy load to get the handle | ||
| self._lazy_load_module() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm ... this could trigger "interesting" error scenarios.
Is the lazy loading really needed? How was the handle generated (if it exists)? Didn't that trigger the import already? If the handle does not exist, maybe we should just show <not imported> or similar in the repr, because that's what the actual state is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My 2c: trigger lazy execution on __repr__ is a relatively common pattern in Python where I think this makes sense.
| """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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general hash collisions are allowed by the hash contract - only equal objects must have equal hashes, not the converse. Are you deliberately making the requirements stronger for the cuda.core types? If yes, could you please add a comment to explain?
| """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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, what if someone has 10+ GPUs and tests Device(9), this would pass even if it were returning the device ID. Also, in general legitimate hash values can be small.
I'm thinking this test isn't really needed. I think it's better to limit testing to what we're sure must be true, e.g. the hash for objects that are equal must be equal, too, but I wouldn't assert anything on the hash value itself.
| 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" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cursor suggested:
Missing test for equality of same objects created differently
The tests check that distinct objects are not equal, but don't verify that two wrapper objects pointing to the same underlying handle compare equal. For example:
stream = device.create_stream()
stream2 = Stream.from_handle(stream.handle)
assert stream == stream2 # This case isn't tested| @pytest.fixture | ||
| def sample_context_alt(sample_device_alt): | ||
| """An alternate Context object (requires multi-GPU).""" | ||
| sample_device_alt.set_current() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This calls set_current() which changes global state. The fixture doesn't restore the previous current device. Is this future-safe (what comes to mind: multiple GPUs and running tests in parallel)?
| 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})" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given we have a stream associated to the Buffer that we ultimately deallocate with, it may be nice to include that in the repr?
|
|
||
| def __repr__(self) -> str: | ||
| # Trigger lazy load to get the handle | ||
| self._lazy_load_module() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My 2c: trigger lazy execution on __repr__ is a relatively common pattern in Python where I think this makes sense.
| return as_intptr(self._h_stream) == as_intptr((<Stream>other)._h_stream) | ||
|
|
||
| def __repr__(self) -> str: | ||
| return f"Stream(handle={as_intptr(self._h_stream):#x})" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because streams are tied to a specific context, does it make sense to capture the context here as well? Similar question for other types tied to a specific context.
Summary
__repr__methods to Stream, Event, Context, Buffer, ObjectCode, and Kerneltest_object_protocols.pyconsolidating tests from removed filestest_hashable.py,test_comparable.py,test_weakref.pyChanges
__repr__implementations: All core API classes now have informative repr output (e.g.,Stream(handle=0x...),Buffer(ptr=0x..., size=64))Test Coverage