mirror of https://github.com/pybind/pybind11
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
5.9 KiB
166 lines
5.9 KiB
# The compact 4-character naming scheme (e.g. mptr, cptr, shcp) is explained at the top of |
|
# test_class_sh_property.cpp. |
|
from __future__ import annotations |
|
|
|
import pytest |
|
|
|
import env # noqa: F401 |
|
from pybind11_tests import class_sh_property as m |
|
|
|
|
|
@pytest.mark.skipif( |
|
"env.PYPY or env.GRAALPY", reason="gc after `del field` is apparently deferred" |
|
) |
|
@pytest.mark.parametrize("m_attr", ["m_valu_readonly", "m_valu_readwrite"]) |
|
def test_valu_getter(m_attr): |
|
# Reduced from PyCLIF test: |
|
# https://github.com/google/clif/blob/c371a6d4b28d25d53a16e6d2a6d97305fb1be25a/clif/testing/python/nested_fields_test.py#L56 |
|
outer = m.Outer() |
|
field = getattr(outer, m_attr) |
|
assert field.num == -99 |
|
with pytest.raises(ValueError) as excinfo: |
|
m.DisownOuter(outer) |
|
assert str(excinfo.value) == "Cannot disown use_count != 1 (load_as_unique_ptr)." |
|
del field |
|
m.DisownOuter(outer) |
|
with pytest.raises(ValueError, match="Python instance was disowned") as excinfo: |
|
getattr(outer, m_attr) |
|
|
|
|
|
def test_valu_setter(): |
|
outer = m.Outer() |
|
assert outer.m_valu_readonly.num == -99 |
|
assert outer.m_valu_readwrite.num == -99 |
|
field = m.Field() |
|
field.num = 35 |
|
outer.m_valu_readwrite = field |
|
assert outer.m_valu_readonly.num == 35 |
|
assert outer.m_valu_readwrite.num == 35 |
|
|
|
|
|
@pytest.mark.parametrize("m_attr", ["m_shmp", "m_shcp"]) |
|
def test_shp(m_attr): |
|
m_attr_readonly = m_attr + "_readonly" |
|
m_attr_readwrite = m_attr + "_readwrite" |
|
outer = m.Outer() |
|
assert getattr(outer, m_attr_readonly) is None |
|
assert getattr(outer, m_attr_readwrite) is None |
|
field = m.Field() |
|
field.num = 43 |
|
setattr(outer, m_attr_readwrite, field) |
|
assert getattr(outer, m_attr_readonly).num == 43 |
|
assert getattr(outer, m_attr_readwrite).num == 43 |
|
getattr(outer, m_attr_readonly).num = 57 |
|
getattr(outer, m_attr_readwrite).num = 57 |
|
assert field.num == 57 |
|
del field |
|
assert getattr(outer, m_attr_readonly).num == 57 |
|
assert getattr(outer, m_attr_readwrite).num == 57 |
|
|
|
|
|
@pytest.mark.parametrize( |
|
("field_type", "num_default", "outer_type"), |
|
[ |
|
(m.ClassicField, -88, m.ClassicOuter), |
|
(m.Field, -99, m.Outer), |
|
], |
|
) |
|
@pytest.mark.parametrize("m_attr", ["m_mptr", "m_cptr"]) |
|
@pytest.mark.parametrize("r_kind", ["_readonly", "_readwrite"]) |
|
def test_ptr(field_type, num_default, outer_type, m_attr, r_kind): |
|
m_attr_r_kind = m_attr + r_kind |
|
outer = outer_type() |
|
assert getattr(outer, m_attr_r_kind) is None |
|
field = field_type() |
|
assert field.num == num_default |
|
setattr(outer, m_attr + "_readwrite", field) |
|
assert getattr(outer, m_attr_r_kind).num == num_default |
|
field.num = 76 |
|
assert getattr(outer, m_attr_r_kind).num == 76 |
|
# Change to -88 or -99 to demonstrate Undefined Behavior (dangling pointer). |
|
if num_default == 88 and m_attr == "m_mptr": |
|
del field |
|
assert getattr(outer, m_attr_r_kind).num == 76 |
|
|
|
|
|
@pytest.mark.parametrize("m_attr_readwrite", ["m_uqmp_readwrite", "m_uqcp_readwrite"]) |
|
def test_uqp(m_attr_readwrite): |
|
outer = m.Outer() |
|
assert getattr(outer, m_attr_readwrite) is None |
|
field_orig = m.Field() |
|
field_orig.num = 39 |
|
setattr(outer, m_attr_readwrite, field_orig) |
|
with pytest.raises(ValueError, match="Python instance was disowned"): |
|
_ = field_orig.num |
|
field_retr1 = getattr(outer, m_attr_readwrite) |
|
assert getattr(outer, m_attr_readwrite) is None |
|
assert field_retr1.num == 39 |
|
field_retr1.num = 93 |
|
setattr(outer, m_attr_readwrite, field_retr1) |
|
with pytest.raises(ValueError): |
|
_ = field_retr1.num |
|
field_retr2 = getattr(outer, m_attr_readwrite) |
|
assert field_retr2.num == 93 |
|
|
|
|
|
# Proof-of-concept (POC) for safe & intuitive Python access to unique_ptr members. |
|
# The C++ member unique_ptr is disowned to a temporary Python object for accessing |
|
# an attribute of the member. After the attribute was accessed, the Python object |
|
# is disowned back to the C++ member unique_ptr. |
|
# Productizing this POC is left for a future separate PR, as needed. |
|
class unique_ptr_field_proxy_poc: |
|
def __init__(self, obj, field_name): |
|
object.__setattr__(self, "__obj", obj) |
|
object.__setattr__(self, "__field_name", field_name) |
|
|
|
def __getattr__(self, *args, **kwargs): |
|
return _proxy_dereference(self, getattr, *args, **kwargs) |
|
|
|
def __setattr__(self, *args, **kwargs): |
|
return _proxy_dereference(self, setattr, *args, **kwargs) |
|
|
|
def __delattr__(self, *args, **kwargs): |
|
return _proxy_dereference(self, delattr, *args, **kwargs) |
|
|
|
|
|
def _proxy_dereference(proxy, xxxattr, *args, **kwargs): |
|
obj = object.__getattribute__(proxy, "__obj") |
|
field_name = object.__getattribute__(proxy, "__field_name") |
|
field = getattr(obj, field_name) # Disowns the C++ unique_ptr member. |
|
assert field is not None |
|
try: |
|
return xxxattr(field, *args, **kwargs) |
|
finally: |
|
setattr(obj, field_name, field) # Disowns the temporary Python object (field). |
|
|
|
|
|
@pytest.mark.parametrize("m_attr", ["m_uqmp", "m_uqcp"]) |
|
def test_unique_ptr_field_proxy_poc(m_attr): |
|
m_attr_readwrite = m_attr + "_readwrite" |
|
outer = m.Outer() |
|
field_orig = m.Field() |
|
field_orig.num = 45 |
|
setattr(outer, m_attr_readwrite, field_orig) |
|
field_proxy = unique_ptr_field_proxy_poc(outer, m_attr_readwrite) |
|
assert field_proxy.num == 45 |
|
assert field_proxy.num == 45 |
|
with pytest.raises(AttributeError): |
|
_ = field_proxy.xyz |
|
assert field_proxy.num == 45 |
|
field_proxy.num = 82 |
|
assert field_proxy.num == 82 |
|
field_proxy = unique_ptr_field_proxy_poc(outer, m_attr_readwrite) |
|
assert field_proxy.num == 82 |
|
with pytest.raises(AttributeError): |
|
del field_proxy.num |
|
assert field_proxy.num == 82 |
|
|
|
|
|
def test_readonly_char6_member(): |
|
obj = m.WithCharArrayMember() |
|
assert obj.char6_member == "Char6" |
|
|
|
|
|
def test_readonly_const_char_ptr_member(): |
|
obj = m.WithConstCharPtrMember() |
|
assert obj.const_char_ptr_member == "ConstChar*"
|
|
|