test_methods_and_attributes.py 16.6 KB
Newer Older
Dean Moldovan's avatar
Dean Moldovan committed
1
import pytest
2
3
from pybind11_tests import methods_and_attributes as m
from pybind11_tests import ConstructorStats
Dean Moldovan's avatar
Dean Moldovan committed
4
5
6


def test_methods_and_attributes():
7
8
    instance1 = m.ExampleMandA()
    instance2 = m.ExampleMandA(32)
Dean Moldovan's avatar
Dean Moldovan committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

    instance1.add1(instance2)
    instance1.add2(instance2)
    instance1.add3(instance2)
    instance1.add4(instance2)
    instance1.add5(instance2)
    instance1.add6(32)
    instance1.add7(32)
    instance1.add8(32)
    instance1.add9(32)
    instance1.add10(32)

    assert str(instance1) == "ExampleMandA[value=320]"
    assert str(instance2) == "ExampleMandA[value=32]"
    assert str(instance1.self1()) == "ExampleMandA[value=320]"
    assert str(instance1.self2()) == "ExampleMandA[value=320]"
    assert str(instance1.self3()) == "ExampleMandA[value=320]"
    assert str(instance1.self4()) == "ExampleMandA[value=320]"
    assert str(instance1.self5()) == "ExampleMandA[value=320]"

    assert instance1.internal1() == 320
    assert instance1.internal2() == 320
    assert instance1.internal3() == 320
    assert instance1.internal4() == 320
    assert instance1.internal5() == 320

35
36
    assert instance1.overloaded() == "()"
    assert instance1.overloaded(0) == "(int)"
37
38
    assert instance1.overloaded(1, 1.0) == "(int, float)"
    assert instance1.overloaded(2.0, 2) == "(float, int)"
39
40
    assert instance1.overloaded(3,   3) == "(int, int)"
    assert instance1.overloaded(4., 4.) == "(float, float)"
41
    assert instance1.overloaded_const(-3) == "(int) const"
42
43
44
45
46
47
48
49
    assert instance1.overloaded_const(5, 5.0) == "(int, float) const"
    assert instance1.overloaded_const(6.0, 6) == "(float, int) const"
    assert instance1.overloaded_const(7,   7) == "(int, int) const"
    assert instance1.overloaded_const(8., 8.) == "(float, float) const"
    assert instance1.overloaded_float(1, 1) == "(float, float)"
    assert instance1.overloaded_float(1, 1.) == "(float, float)"
    assert instance1.overloaded_float(1., 1) == "(float, float)"
    assert instance1.overloaded_float(1., 1.) == "(float, float)"
50

Dean Moldovan's avatar
Dean Moldovan committed
51
52
53
54
    assert instance1.value == 320
    instance1.value = 100
    assert str(instance1) == "ExampleMandA[value=100]"

55
    cstats = ConstructorStats.get(m.ExampleMandA)
Dean Moldovan's avatar
Dean Moldovan committed
56
57
58
59
60
61
62
63
64
    assert cstats.alive() == 2
    del instance1, instance2
    assert cstats.alive() == 0
    assert cstats.values() == ["32"]
    assert cstats.default_constructions == 1
    assert cstats.copy_constructions == 3
    assert cstats.move_constructions >= 1
    assert cstats.copy_assignments == 0
    assert cstats.move_assignments == 0
Dean Moldovan's avatar
Dean Moldovan committed
65
66


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def test_copy_method():
    """Issue #443: calling copied methods fails in Python 3"""

    m.ExampleMandA.add2c = m.ExampleMandA.add2
    m.ExampleMandA.add2d = m.ExampleMandA.add2b
    a = m.ExampleMandA(123)
    assert a.value == 123
    a.add2(m.ExampleMandA(-100))
    assert a.value == 23
    a.add2b(m.ExampleMandA(20))
    assert a.value == 43
    a.add2c(m.ExampleMandA(6))
    assert a.value == 49
    a.add2d(m.ExampleMandA(-7))
    assert a.value == 42
82

83
84
85

def test_properties():
    instance = m.TestProperties()
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

    assert instance.def_readonly == 1
    with pytest.raises(AttributeError):
        instance.def_readonly = 2

    instance.def_readwrite = 2
    assert instance.def_readwrite == 2

    assert instance.def_property_readonly == 2
    with pytest.raises(AttributeError):
        instance.def_property_readonly = 3

    instance.def_property = 3
    assert instance.def_property == 3


def test_static_properties():
103
    assert m.TestProperties.def_readonly_static == 1
104
    with pytest.raises(AttributeError) as excinfo:
105
        m.TestProperties.def_readonly_static = 2
106
    assert "can't set attribute" in str(excinfo)
107

108
109
    m.TestProperties.def_readwrite_static = 2
    assert m.TestProperties.def_readwrite_static == 2
110

111
    assert m.TestProperties.def_property_readonly_static == 2
112
    with pytest.raises(AttributeError) as excinfo:
113
        m.TestProperties.def_property_readonly_static = 3
114
    assert "can't set attribute" in str(excinfo)
115

116
117
    m.TestProperties.def_property_static = 3
    assert m.TestProperties.def_property_static == 3
118

119
    # Static property read and write via instance
120
    instance = m.TestProperties()
121

122
123
    m.TestProperties.def_readwrite_static = 0
    assert m.TestProperties.def_readwrite_static == 0
124
125
126
    assert instance.def_readwrite_static == 0

    instance.def_readwrite_static = 2
127
    assert m.TestProperties.def_readwrite_static == 2
128
129
    assert instance.def_readwrite_static == 2

130
    # It should be possible to override properties in derived classes
131
132
    assert m.TestPropertiesOverride().def_readonly == 99
    assert m.TestPropertiesOverride.def_readonly_static == 99
133

134
135
136
137

def test_static_cls():
    """Static property getter and setters expect the type object as the their only argument"""

138
139
140
    instance = m.TestProperties()
    assert m.TestProperties.static_cls is m.TestProperties
    assert instance.static_cls is m.TestProperties
141
142

    def check_self(self):
143
        assert self is m.TestProperties
144

145
    m.TestProperties.static_cls = check_self
146
147
    instance.static_cls = check_self

148

149
150
151
def test_metaclass_override():
    """Overriding pybind11's default metaclass changes the behavior of `static_property`"""

152
153
    assert type(m.ExampleMandA).__name__ == "pybind11_type"
    assert type(m.MetaclassOverride).__name__ == "type"
154

155
156
    assert m.MetaclassOverride.readonly == 1
    assert type(m.MetaclassOverride.__dict__["readonly"]).__name__ == "pybind11_static_property"
157
158

    # Regular `type` replaces the property instead of calling `__set__()`
159
160
161
    m.MetaclassOverride.readonly = 2
    assert m.MetaclassOverride.readonly == 2
    assert isinstance(m.MetaclassOverride.__dict__["readonly"], int)
162
163


164
165
166
167
def test_no_mixed_overloads():
    from pybind11_tests import debug_enabled

    with pytest.raises(RuntimeError) as excinfo:
168
        m.ExampleMandA.add_mixed_overloads1()
169
170
171
172
    assert (str(excinfo.value) ==
            "overloading a method with both static and instance methods is not supported; " +
            ("compile in debug mode for more details" if not debug_enabled else
             "error while attempting to bind static method ExampleMandA.overload_mixed1"
173
             "(arg0: float) -> str")
174
175
176
            )

    with pytest.raises(RuntimeError) as excinfo:
177
        m.ExampleMandA.add_mixed_overloads2()
178
179
180
181
    assert (str(excinfo.value) ==
            "overloading a method with both static and instance methods is not supported; " +
            ("compile in debug mode for more details" if not debug_enabled else
             "error while attempting to bind instance method ExampleMandA.overload_mixed2"
182
183
             "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: int, arg1: int)"
             " -> str")
184
185
186
            )


187
188
189
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
def test_property_return_value_policies(access):
    if not access.startswith("static"):
190
        obj = m.TestPropRVP()
191
    else:
192
        obj = m.TestPropRVP
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212

    ref = getattr(obj, access + "_ref")
    assert ref.value == 1
    ref.value = 2
    assert getattr(obj, access + "_ref").value == 2
    ref.value = 1  # restore original value for static properties

    copy = getattr(obj, access + "_copy")
    assert copy.value == 1
    copy.value = 2
    assert getattr(obj, access + "_copy").value == 1

    copy = getattr(obj, access + "_func")
    assert copy.value == 1
    copy.value = 2
    assert getattr(obj, access + "_func").value == 1


def test_property_rvalue_policy():
    """When returning an rvalue, the return value policy is automatically changed from
213
    `reference(_internal)` to `move`. The following would not work otherwise."""
214

215
    instance = m.TestPropRVP()
216
217
    o = instance.rvalue
    assert o.value == 1
Wenzel Jakob's avatar
Wenzel Jakob committed
218

219
220
    os = m.TestPropRVP.static_rvalue
    assert os.value == 1
221
222


Wenzel Jakob's avatar
Wenzel Jakob committed
223
224
# https://bitbucket.org/pypy/pypy/issues/2447
@pytest.unsupported_on_pypy
Dean Moldovan's avatar
Dean Moldovan committed
225
def test_dynamic_attributes():
226
    instance = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    assert not hasattr(instance, "foo")
    assert "foo" not in dir(instance)

    # Dynamically add attribute
    instance.foo = 42
    assert hasattr(instance, "foo")
    assert instance.foo == 42
    assert "foo" in dir(instance)

    # __dict__ should be accessible and replaceable
    assert "foo" in instance.__dict__
    instance.__dict__ = {"bar": True}
    assert not hasattr(instance, "foo")
    assert hasattr(instance, "bar")

    with pytest.raises(TypeError) as excinfo:
        instance.__dict__ = []
    assert str(excinfo.value) == "__dict__ must be set to a dictionary, not a 'list'"

246
    cstats = ConstructorStats.get(m.DynamicClass)
Dean Moldovan's avatar
Dean Moldovan committed
247
248
249
250
251
    assert cstats.alive() == 1
    del instance
    assert cstats.alive() == 0

    # Derived classes should work as well
252
    class PythonDerivedDynamicClass(m.DynamicClass):
Dean Moldovan's avatar
Dean Moldovan committed
253
254
        pass

255
    for cls in m.CppDerivedDynamicClass, PythonDerivedDynamicClass:
256
257
258
        derived = cls()
        derived.foobar = 100
        assert derived.foobar == 100
Dean Moldovan's avatar
Dean Moldovan committed
259

260
261
262
        assert cstats.alive() == 1
        del derived
        assert cstats.alive() == 0
Dean Moldovan's avatar
Dean Moldovan committed
263
264


265
266
# https://bitbucket.org/pypy/pypy/issues/2447
@pytest.unsupported_on_pypy
Dean Moldovan's avatar
Dean Moldovan committed
267
268
def test_cyclic_gc():
    # One object references itself
269
    instance = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
270
271
    instance.circular_reference = instance

272
    cstats = ConstructorStats.get(m.DynamicClass)
Dean Moldovan's avatar
Dean Moldovan committed
273
274
275
276
277
    assert cstats.alive() == 1
    del instance
    assert cstats.alive() == 0

    # Two object reference each other
278
279
    i1 = m.DynamicClass()
    i2 = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
280
281
282
283
284
285
    i1.cycle = i2
    i2.cycle = i1

    assert cstats.alive() == 2
    del i1, i2
    assert cstats.alive() == 0
286
287
288


def test_noconvert_args(msg):
289
    a = m.ArgInspector()
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
    assert msg(a.f("hi")) == """
        loading ArgInspector1 argument WITH conversion allowed.  Argument value = hi
    """
    assert msg(a.g("this is a", "this is b")) == """
        loading ArgInspector1 argument WITHOUT conversion allowed.  Argument value = this is a
        loading ArgInspector1 argument WITH conversion allowed.  Argument value = this is b
        13
        loading ArgInspector2 argument WITH conversion allowed.  Argument value = (default arg inspector 2)
    """  # noqa: E501 line too long
    assert msg(a.g("this is a", "this is b", 42)) == """
        loading ArgInspector1 argument WITHOUT conversion allowed.  Argument value = this is a
        loading ArgInspector1 argument WITH conversion allowed.  Argument value = this is b
        42
        loading ArgInspector2 argument WITH conversion allowed.  Argument value = (default arg inspector 2)
    """  # noqa: E501 line too long
    assert msg(a.g("this is a", "this is b", 42, "this is d")) == """
        loading ArgInspector1 argument WITHOUT conversion allowed.  Argument value = this is a
        loading ArgInspector1 argument WITH conversion allowed.  Argument value = this is b
        42
        loading ArgInspector2 argument WITH conversion allowed.  Argument value = this is d
    """
    assert (a.h("arg 1") ==
            "loading ArgInspector2 argument WITHOUT conversion allowed.  Argument value = arg 1")
313
    assert msg(m.arg_inspect_func("A1", "A2")) == """
314
315
316
317
        loading ArgInspector2 argument WITH conversion allowed.  Argument value = A1
        loading ArgInspector1 argument WITHOUT conversion allowed.  Argument value = A2
    """

318
319
    assert m.floats_preferred(4) == 2.0
    assert m.floats_only(4.0) == 2.0
320
    with pytest.raises(TypeError) as excinfo:
321
        m.floats_only(4)
322
323
324
325
326
327
    assert msg(excinfo.value) == """
        floats_only(): incompatible function arguments. The following argument types are supported:
            1. (f: float) -> float

        Invoked with: 4
    """
328

329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
    assert m.ints_preferred(4) == 2
    assert m.ints_preferred(True) == 0
    with pytest.raises(TypeError) as excinfo:
        m.ints_preferred(4.0)
    assert msg(excinfo.value) == """
        ints_preferred(): incompatible function arguments. The following argument types are supported:
            1. (i: int) -> int

        Invoked with: 4.0
    """  # noqa: E501 line too long

    assert m.ints_only(4) == 2
    with pytest.raises(TypeError) as excinfo:
        m.ints_only(4.0)
    assert msg(excinfo.value) == """
        ints_only(): incompatible function arguments. The following argument types are supported:
            1. (i: int) -> int

        Invoked with: 4.0
    """

350
351

def test_bad_arg_default(msg):
352
    from pybind11_tests import debug_enabled
353
354

    with pytest.raises(RuntimeError) as excinfo:
355
        m.bad_arg_def_named()
356
    assert msg(excinfo.value) == (
357
358
        "arg(): could not convert default argument 'a: UnregisteredType' in function "
        "'should_fail' into a Python object (type not registered yet?)"
359
360
361
362
363
364
        if debug_enabled else
        "arg(): could not convert default argument into a Python object (type not registered "
        "yet?). Compile in debug mode for more information."
    )

    with pytest.raises(RuntimeError) as excinfo:
365
        m.bad_arg_def_unnamed()
366
    assert msg(excinfo.value) == (
367
368
        "arg(): could not convert default argument 'UnregisteredType' in function "
        "'should_fail' into a Python object (type not registered yet?)"
369
370
371
372
        if debug_enabled else
        "arg(): could not convert default argument into a Python object (type not registered "
        "yet?). Compile in debug mode for more information."
    )
373
374


375
def test_accepts_none(msg):
376
377
378
379
380
381
382
383
384
385
386
    a = m.NoneTester()
    assert m.no_none1(a) == 42
    assert m.no_none2(a) == 42
    assert m.no_none3(a) == 42
    assert m.no_none4(a) == 42
    assert m.no_none5(a) == 42
    assert m.ok_none1(a) == 42
    assert m.ok_none2(a) == 42
    assert m.ok_none3(a) == 42
    assert m.ok_none4(a) == 42
    assert m.ok_none5(a) == 42
387
388

    with pytest.raises(TypeError) as excinfo:
389
        m.no_none1(None)
390
391
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
392
        m.no_none2(None)
393
394
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
395
        m.no_none3(None)
396
397
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
398
        m.no_none4(None)
399
400
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
401
        m.no_none5(None)
402
403
404
405
    assert "incompatible function arguments" in str(excinfo.value)

    # The first one still raises because you can't pass None as a lvalue reference arg:
    with pytest.raises(TypeError) as excinfo:
406
        assert m.ok_none1(None) == -1
407
408
    assert msg(excinfo.value) == """
        ok_none1(): incompatible function arguments. The following argument types are supported:
409
            1. (arg0: m.methods_and_attributes.NoneTester) -> int
410
411
412
413

        Invoked with: None
    """

414
    # The rest take the argument as pointer or holder, and accept None:
415
416
417
418
    assert m.ok_none2(None) == -1
    assert m.ok_none3(None) == -1
    assert m.ok_none4(None) == -1
    assert m.ok_none5(None) == -1
419
420
421
422
423


def test_str_issue(msg):
    """#283: __str__ called on uninitialized instance when constructor arguments invalid"""

424
    assert str(m.StrIssue(3)) == "StrIssue[3]"
425
426

    with pytest.raises(TypeError) as excinfo:
427
        str(m.StrIssue("no", "such", "constructor"))
428
429
    assert msg(excinfo.value) == """
        __init__(): incompatible constructor arguments. The following argument types are supported:
430
431
            1. m.methods_and_attributes.StrIssue(arg0: int)
            2. m.methods_and_attributes.StrIssue()
432
433
434

        Invoked with: 'no', 'such', 'constructor'
    """
435
436
437


def test_unregistered_base_implementations():
438
    a = m.RegisteredDerived()
439
440
441
442
443
444
445
446
447
448
449
450
451
452
    a.do_nothing()
    assert a.rw_value == 42
    assert a.ro_value == 1.25
    a.rw_value += 5
    assert a.sum() == 48.25
    a.increase_value()
    assert a.rw_value == 48
    assert a.ro_value == 1.5
    assert a.sum() == 49.5
    assert a.rw_value_prop == 48
    a.rw_value_prop += 1
    assert a.rw_value_prop == 49
    a.increase_value()
    assert a.ro_value_prop == 1.75
453
454
455


def test_custom_caster_destruction():
456
457
    """Tests that returning a pointer to a type that gets converted with a custom type caster gets
    destroyed when the function has py::return_value_policy::take_ownership policy applied."""
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476

    cstats = m.destruction_tester_cstats()
    # This one *doesn't* have take_ownership: the pointer should be used but not destroyed:
    z = m.custom_caster_no_destroy()
    assert cstats.alive() == 1 and cstats.default_constructions == 1
    assert z

    # take_ownership applied: this constructs a new object, casts it, then destroys it:
    z = m.custom_caster_destroy()
    assert z
    assert cstats.default_constructions == 2

    # Same, but with a const pointer return (which should *not* inhibit destruction):
    z = m.custom_caster_destroy_const()
    assert z
    assert cstats.default_constructions == 3

    # Make sure we still only have the original object (from ..._no_destroy()) alive:
    assert cstats.alive() == 1