| # 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*" |