• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# -*- coding: utf-8 -*-
2import pytest
3
4import env  # noqa: F401
5
6from pybind11_tests import methods_and_attributes as m
7from pybind11_tests import ConstructorStats
8
9
10def test_methods_and_attributes():
11    instance1 = m.ExampleMandA()
12    instance2 = m.ExampleMandA(32)
13
14    instance1.add1(instance2)
15    instance1.add2(instance2)
16    instance1.add3(instance2)
17    instance1.add4(instance2)
18    instance1.add5(instance2)
19    instance1.add6(32)
20    instance1.add7(32)
21    instance1.add8(32)
22    instance1.add9(32)
23    instance1.add10(32)
24
25    assert str(instance1) == "ExampleMandA[value=320]"
26    assert str(instance2) == "ExampleMandA[value=32]"
27    assert str(instance1.self1()) == "ExampleMandA[value=320]"
28    assert str(instance1.self2()) == "ExampleMandA[value=320]"
29    assert str(instance1.self3()) == "ExampleMandA[value=320]"
30    assert str(instance1.self4()) == "ExampleMandA[value=320]"
31    assert str(instance1.self5()) == "ExampleMandA[value=320]"
32
33    assert instance1.internal1() == 320
34    assert instance1.internal2() == 320
35    assert instance1.internal3() == 320
36    assert instance1.internal4() == 320
37    assert instance1.internal5() == 320
38
39    assert instance1.overloaded() == "()"
40    assert instance1.overloaded(0) == "(int)"
41    assert instance1.overloaded(1, 1.0) == "(int, float)"
42    assert instance1.overloaded(2.0, 2) == "(float, int)"
43    assert instance1.overloaded(3, 3) == "(int, int)"
44    assert instance1.overloaded(4.0, 4.0) == "(float, float)"
45    assert instance1.overloaded_const(-3) == "(int) const"
46    assert instance1.overloaded_const(5, 5.0) == "(int, float) const"
47    assert instance1.overloaded_const(6.0, 6) == "(float, int) const"
48    assert instance1.overloaded_const(7, 7) == "(int, int) const"
49    assert instance1.overloaded_const(8.0, 8.0) == "(float, float) const"
50    assert instance1.overloaded_float(1, 1) == "(float, float)"
51    assert instance1.overloaded_float(1, 1.0) == "(float, float)"
52    assert instance1.overloaded_float(1.0, 1) == "(float, float)"
53    assert instance1.overloaded_float(1.0, 1.0) == "(float, float)"
54
55    assert instance1.value == 320
56    instance1.value = 100
57    assert str(instance1) == "ExampleMandA[value=100]"
58
59    cstats = ConstructorStats.get(m.ExampleMandA)
60    assert cstats.alive() == 2
61    del instance1, instance2
62    assert cstats.alive() == 0
63    assert cstats.values() == ["32"]
64    assert cstats.default_constructions == 1
65    assert cstats.copy_constructions == 2
66    assert cstats.move_constructions >= 2
67    assert cstats.copy_assignments == 0
68    assert cstats.move_assignments == 0
69
70
71def test_copy_method():
72    """Issue #443: calling copied methods fails in Python 3"""
73
74    m.ExampleMandA.add2c = m.ExampleMandA.add2
75    m.ExampleMandA.add2d = m.ExampleMandA.add2b
76    a = m.ExampleMandA(123)
77    assert a.value == 123
78    a.add2(m.ExampleMandA(-100))
79    assert a.value == 23
80    a.add2b(m.ExampleMandA(20))
81    assert a.value == 43
82    a.add2c(m.ExampleMandA(6))
83    assert a.value == 49
84    a.add2d(m.ExampleMandA(-7))
85    assert a.value == 42
86
87
88def test_properties():
89    instance = m.TestProperties()
90
91    assert instance.def_readonly == 1
92    with pytest.raises(AttributeError):
93        instance.def_readonly = 2
94
95    instance.def_readwrite = 2
96    assert instance.def_readwrite == 2
97
98    assert instance.def_property_readonly == 2
99    with pytest.raises(AttributeError):
100        instance.def_property_readonly = 3
101
102    instance.def_property = 3
103    assert instance.def_property == 3
104
105    with pytest.raises(AttributeError) as excinfo:
106        dummy = instance.def_property_writeonly  # noqa: F841 unused var
107    assert "unreadable attribute" in str(excinfo.value)
108
109    instance.def_property_writeonly = 4
110    assert instance.def_property_readonly == 4
111
112    with pytest.raises(AttributeError) as excinfo:
113        dummy = instance.def_property_impossible  # noqa: F841 unused var
114    assert "unreadable attribute" in str(excinfo.value)
115
116    with pytest.raises(AttributeError) as excinfo:
117        instance.def_property_impossible = 5
118    assert "can't set attribute" in str(excinfo.value)
119
120
121def test_static_properties():
122    assert m.TestProperties.def_readonly_static == 1
123    with pytest.raises(AttributeError) as excinfo:
124        m.TestProperties.def_readonly_static = 2
125    assert "can't set attribute" in str(excinfo.value)
126
127    m.TestProperties.def_readwrite_static = 2
128    assert m.TestProperties.def_readwrite_static == 2
129
130    with pytest.raises(AttributeError) as excinfo:
131        dummy = m.TestProperties.def_writeonly_static  # noqa: F841 unused var
132    assert "unreadable attribute" in str(excinfo.value)
133
134    m.TestProperties.def_writeonly_static = 3
135    assert m.TestProperties.def_readonly_static == 3
136
137    assert m.TestProperties.def_property_readonly_static == 3
138    with pytest.raises(AttributeError) as excinfo:
139        m.TestProperties.def_property_readonly_static = 99
140    assert "can't set attribute" in str(excinfo.value)
141
142    m.TestProperties.def_property_static = 4
143    assert m.TestProperties.def_property_static == 4
144
145    with pytest.raises(AttributeError) as excinfo:
146        dummy = m.TestProperties.def_property_writeonly_static
147    assert "unreadable attribute" in str(excinfo.value)
148
149    m.TestProperties.def_property_writeonly_static = 5
150    assert m.TestProperties.def_property_static == 5
151
152    # Static property read and write via instance
153    instance = m.TestProperties()
154
155    m.TestProperties.def_readwrite_static = 0
156    assert m.TestProperties.def_readwrite_static == 0
157    assert instance.def_readwrite_static == 0
158
159    instance.def_readwrite_static = 2
160    assert m.TestProperties.def_readwrite_static == 2
161    assert instance.def_readwrite_static == 2
162
163    with pytest.raises(AttributeError) as excinfo:
164        dummy = instance.def_property_writeonly_static  # noqa: F841 unused var
165    assert "unreadable attribute" in str(excinfo.value)
166
167    instance.def_property_writeonly_static = 4
168    assert instance.def_property_static == 4
169
170    # It should be possible to override properties in derived classes
171    assert m.TestPropertiesOverride().def_readonly == 99
172    assert m.TestPropertiesOverride.def_readonly_static == 99
173
174    # Only static attributes can be deleted
175    del m.TestPropertiesOverride.def_readonly_static
176    assert (
177        hasattr(m.TestPropertiesOverride, "def_readonly_static")
178        and m.TestPropertiesOverride.def_readonly_static
179        is m.TestProperties.def_readonly_static
180    )
181    assert "def_readonly_static" not in m.TestPropertiesOverride.__dict__
182    properties_override = m.TestPropertiesOverride()
183    with pytest.raises(AttributeError) as excinfo:
184        del properties_override.def_readonly
185    assert "can't delete attribute" in str(excinfo.value)
186
187
188def test_static_cls():
189    """Static property getter and setters expect the type object as the their only argument"""
190
191    instance = m.TestProperties()
192    assert m.TestProperties.static_cls is m.TestProperties
193    assert instance.static_cls is m.TestProperties
194
195    def check_self(self):
196        assert self is m.TestProperties
197
198    m.TestProperties.static_cls = check_self
199    instance.static_cls = check_self
200
201
202def test_metaclass_override():
203    """Overriding pybind11's default metaclass changes the behavior of `static_property`"""
204
205    assert type(m.ExampleMandA).__name__ == "pybind11_type"
206    assert type(m.MetaclassOverride).__name__ == "type"
207
208    assert m.MetaclassOverride.readonly == 1
209    assert (
210        type(m.MetaclassOverride.__dict__["readonly"]).__name__
211        == "pybind11_static_property"
212    )
213
214    # Regular `type` replaces the property instead of calling `__set__()`
215    m.MetaclassOverride.readonly = 2
216    assert m.MetaclassOverride.readonly == 2
217    assert isinstance(m.MetaclassOverride.__dict__["readonly"], int)
218
219
220def test_no_mixed_overloads():
221    from pybind11_tests import debug_enabled
222
223    with pytest.raises(RuntimeError) as excinfo:
224        m.ExampleMandA.add_mixed_overloads1()
225    assert str(
226        excinfo.value
227    ) == "overloading a method with both static and instance methods is not supported; " + (
228        "compile in debug mode for more details"
229        if not debug_enabled
230        else "error while attempting to bind static method ExampleMandA.overload_mixed1"
231        "(arg0: float) -> str"
232    )
233
234    with pytest.raises(RuntimeError) as excinfo:
235        m.ExampleMandA.add_mixed_overloads2()
236    assert str(
237        excinfo.value
238    ) == "overloading a method with both static and instance methods is not supported; " + (
239        "compile in debug mode for more details"
240        if not debug_enabled
241        else "error while attempting to bind instance method ExampleMandA.overload_mixed2"
242        "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: int, arg1: int)"
243        " -> str"
244    )
245
246
247@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
248def test_property_return_value_policies(access):
249    if not access.startswith("static"):
250        obj = m.TestPropRVP()
251    else:
252        obj = m.TestPropRVP
253
254    ref = getattr(obj, access + "_ref")
255    assert ref.value == 1
256    ref.value = 2
257    assert getattr(obj, access + "_ref").value == 2
258    ref.value = 1  # restore original value for static properties
259
260    copy = getattr(obj, access + "_copy")
261    assert copy.value == 1
262    copy.value = 2
263    assert getattr(obj, access + "_copy").value == 1
264
265    copy = getattr(obj, access + "_func")
266    assert copy.value == 1
267    copy.value = 2
268    assert getattr(obj, access + "_func").value == 1
269
270
271def test_property_rvalue_policy():
272    """When returning an rvalue, the return value policy is automatically changed from
273    `reference(_internal)` to `move`. The following would not work otherwise."""
274
275    instance = m.TestPropRVP()
276    o = instance.rvalue
277    assert o.value == 1
278
279    os = m.TestPropRVP.static_rvalue
280    assert os.value == 1
281
282
283# https://foss.heptapod.net/pypy/pypy/-/issues/2447
284@pytest.mark.xfail("env.PYPY")
285def test_dynamic_attributes():
286    instance = m.DynamicClass()
287    assert not hasattr(instance, "foo")
288    assert "foo" not in dir(instance)
289
290    # Dynamically add attribute
291    instance.foo = 42
292    assert hasattr(instance, "foo")
293    assert instance.foo == 42
294    assert "foo" in dir(instance)
295
296    # __dict__ should be accessible and replaceable
297    assert "foo" in instance.__dict__
298    instance.__dict__ = {"bar": True}
299    assert not hasattr(instance, "foo")
300    assert hasattr(instance, "bar")
301
302    with pytest.raises(TypeError) as excinfo:
303        instance.__dict__ = []
304    assert str(excinfo.value) == "__dict__ must be set to a dictionary, not a 'list'"
305
306    cstats = ConstructorStats.get(m.DynamicClass)
307    assert cstats.alive() == 1
308    del instance
309    assert cstats.alive() == 0
310
311    # Derived classes should work as well
312    class PythonDerivedDynamicClass(m.DynamicClass):
313        pass
314
315    for cls in m.CppDerivedDynamicClass, PythonDerivedDynamicClass:
316        derived = cls()
317        derived.foobar = 100
318        assert derived.foobar == 100
319
320        assert cstats.alive() == 1
321        del derived
322        assert cstats.alive() == 0
323
324
325# https://foss.heptapod.net/pypy/pypy/-/issues/2447
326@pytest.mark.xfail("env.PYPY")
327def test_cyclic_gc():
328    # One object references itself
329    instance = m.DynamicClass()
330    instance.circular_reference = instance
331
332    cstats = ConstructorStats.get(m.DynamicClass)
333    assert cstats.alive() == 1
334    del instance
335    assert cstats.alive() == 0
336
337    # Two object reference each other
338    i1 = m.DynamicClass()
339    i2 = m.DynamicClass()
340    i1.cycle = i2
341    i2.cycle = i1
342
343    assert cstats.alive() == 2
344    del i1, i2
345    assert cstats.alive() == 0
346
347
348def test_bad_arg_default(msg):
349    from pybind11_tests import debug_enabled
350
351    with pytest.raises(RuntimeError) as excinfo:
352        m.bad_arg_def_named()
353    assert msg(excinfo.value) == (
354        "arg(): could not convert default argument 'a: UnregisteredType' in function "
355        "'should_fail' into a Python object (type not registered yet?)"
356        if debug_enabled
357        else "arg(): could not convert default argument into a Python object (type not registered "
358        "yet?). Compile in debug mode for more information."
359    )
360
361    with pytest.raises(RuntimeError) as excinfo:
362        m.bad_arg_def_unnamed()
363    assert msg(excinfo.value) == (
364        "arg(): could not convert default argument 'UnregisteredType' in function "
365        "'should_fail' into a Python object (type not registered yet?)"
366        if debug_enabled
367        else "arg(): could not convert default argument into a Python object (type not registered "
368        "yet?). Compile in debug mode for more information."
369    )
370
371
372def test_accepts_none(msg):
373    a = m.NoneTester()
374    assert m.no_none1(a) == 42
375    assert m.no_none2(a) == 42
376    assert m.no_none3(a) == 42
377    assert m.no_none4(a) == 42
378    assert m.no_none5(a) == 42
379    assert m.ok_none1(a) == 42
380    assert m.ok_none2(a) == 42
381    assert m.ok_none3(a) == 42
382    assert m.ok_none4(a) == 42
383    assert m.ok_none5(a) == 42
384
385    with pytest.raises(TypeError) as excinfo:
386        m.no_none1(None)
387    assert "incompatible function arguments" in str(excinfo.value)
388    with pytest.raises(TypeError) as excinfo:
389        m.no_none2(None)
390    assert "incompatible function arguments" in str(excinfo.value)
391    with pytest.raises(TypeError) as excinfo:
392        m.no_none3(None)
393    assert "incompatible function arguments" in str(excinfo.value)
394    with pytest.raises(TypeError) as excinfo:
395        m.no_none4(None)
396    assert "incompatible function arguments" in str(excinfo.value)
397    with pytest.raises(TypeError) as excinfo:
398        m.no_none5(None)
399    assert "incompatible function arguments" in str(excinfo.value)
400
401    # The first one still raises because you can't pass None as a lvalue reference arg:
402    with pytest.raises(TypeError) as excinfo:
403        assert m.ok_none1(None) == -1
404    assert (
405        msg(excinfo.value)
406        == """
407        ok_none1(): incompatible function arguments. The following argument types are supported:
408            1. (arg0: m.methods_and_attributes.NoneTester) -> int
409
410        Invoked with: None
411    """
412    )
413
414    # The rest take the argument as pointer or holder, and accept None:
415    assert m.ok_none2(None) == -1
416    assert m.ok_none3(None) == -1
417    assert m.ok_none4(None) == -1
418    assert m.ok_none5(None) == -1
419
420    with pytest.raises(TypeError) as excinfo:
421        m.no_none_kwarg(None)
422    assert "incompatible function arguments" in str(excinfo.value)
423    with pytest.raises(TypeError) as excinfo:
424        m.no_none_kwarg(a=None)
425    assert "incompatible function arguments" in str(excinfo.value)
426    with pytest.raises(TypeError) as excinfo:
427        m.no_none_kwarg_kw_only(None)
428    assert "incompatible function arguments" in str(excinfo.value)
429    with pytest.raises(TypeError) as excinfo:
430        m.no_none_kwarg_kw_only(a=None)
431    assert "incompatible function arguments" in str(excinfo.value)
432
433
434def test_str_issue(msg):
435    """#283: __str__ called on uninitialized instance when constructor arguments invalid"""
436
437    assert str(m.StrIssue(3)) == "StrIssue[3]"
438
439    with pytest.raises(TypeError) as excinfo:
440        str(m.StrIssue("no", "such", "constructor"))
441    assert (
442        msg(excinfo.value)
443        == """
444        __init__(): incompatible constructor arguments. The following argument types are supported:
445            1. m.methods_and_attributes.StrIssue(arg0: int)
446            2. m.methods_and_attributes.StrIssue()
447
448        Invoked with: 'no', 'such', 'constructor'
449    """
450    )
451
452
453def test_unregistered_base_implementations():
454    a = m.RegisteredDerived()
455    a.do_nothing()
456    assert a.rw_value == 42
457    assert a.ro_value == 1.25
458    a.rw_value += 5
459    assert a.sum() == 48.25
460    a.increase_value()
461    assert a.rw_value == 48
462    assert a.ro_value == 1.5
463    assert a.sum() == 49.5
464    assert a.rw_value_prop == 48
465    a.rw_value_prop += 1
466    assert a.rw_value_prop == 49
467    a.increase_value()
468    assert a.ro_value_prop == 1.75
469
470
471def test_ref_qualified():
472    """Tests that explicit lvalue ref-qualified methods can be called just like their
473    non ref-qualified counterparts."""
474
475    r = m.RefQualified()
476    assert r.value == 0
477    r.refQualified(17)
478    assert r.value == 17
479    assert r.constRefQualified(23) == 40
480
481
482def test_overload_ordering():
483    "Check to see if the normal overload order (first defined) and prepend overload order works"
484    assert m.overload_order("string") == 1
485    assert m.overload_order(0) == 4
486
487    # Different for Python 2 vs. 3
488    uni_name = type(u"").__name__
489
490    assert "1. overload_order(arg0: int) -> int" in m.overload_order.__doc__
491    assert (
492        "2. overload_order(arg0: {}) -> int".format(uni_name)
493        in m.overload_order.__doc__
494    )
495    assert (
496        "3. overload_order(arg0: {}) -> int".format(uni_name)
497        in m.overload_order.__doc__
498    )
499    assert "4. overload_order(arg0: int) -> int" in m.overload_order.__doc__
500
501    with pytest.raises(TypeError) as err:
502        m.overload_order(1.1)
503
504    assert "1. (arg0: int) -> int" in str(err.value)
505    assert "2. (arg0: {}) -> int".format(uni_name) in str(err.value)
506    assert "3. (arg0: {}) -> int".format(uni_name) in str(err.value)
507    assert "4. (arg0: int) -> int" in str(err.value)
508