test_methods_and_attributes.py 18 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

    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

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
    with pytest.raises(AttributeError) as excinfo:
        dummy = instance.def_property_writeonly  # noqa: F841 unused var
    assert "unreadable attribute" in str(excinfo)

    instance.def_property_writeonly = 4
    assert instance.def_property_readonly == 4

    with pytest.raises(AttributeError) as excinfo:
        dummy = instance.def_property_impossible  # noqa: F841 unused var
    assert "unreadable attribute" in str(excinfo)

    with pytest.raises(AttributeError) as excinfo:
        instance.def_property_impossible = 5
    assert "can't set attribute" in str(excinfo)

116
117

def test_static_properties():
118
    assert m.TestProperties.def_readonly_static == 1
119
    with pytest.raises(AttributeError) as excinfo:
120
        m.TestProperties.def_readonly_static = 2
121
    assert "can't set attribute" in str(excinfo)
122

123
124
    m.TestProperties.def_readwrite_static = 2
    assert m.TestProperties.def_readwrite_static == 2
125

126
    with pytest.raises(AttributeError) as excinfo:
127
128
129
130
131
132
133
134
135
        dummy = m.TestProperties.def_writeonly_static  # noqa: F841 unused var
    assert "unreadable attribute" in str(excinfo)

    m.TestProperties.def_writeonly_static = 3
    assert m.TestProperties.def_readonly_static == 3

    assert m.TestProperties.def_property_readonly_static == 3
    with pytest.raises(AttributeError) as excinfo:
        m.TestProperties.def_property_readonly_static = 99
136
    assert "can't set attribute" in str(excinfo)
137

138
139
140
141
142
143
144
145
146
    m.TestProperties.def_property_static = 4
    assert m.TestProperties.def_property_static == 4

    with pytest.raises(AttributeError) as excinfo:
        dummy = m.TestProperties.def_property_writeonly_static
    assert "unreadable attribute" in str(excinfo)

    m.TestProperties.def_property_writeonly_static = 5
    assert m.TestProperties.def_property_static == 5
147

148
    # Static property read and write via instance
149
    instance = m.TestProperties()
150

151
152
    m.TestProperties.def_readwrite_static = 0
    assert m.TestProperties.def_readwrite_static == 0
153
154
155
    assert instance.def_readwrite_static == 0

    instance.def_readwrite_static = 2
156
    assert m.TestProperties.def_readwrite_static == 2
157
158
    assert instance.def_readwrite_static == 2

159
160
161
162
163
164
165
    with pytest.raises(AttributeError) as excinfo:
        dummy = instance.def_property_writeonly_static  # noqa: F841 unused var
    assert "unreadable attribute" in str(excinfo)

    instance.def_property_writeonly_static = 4
    assert instance.def_property_static == 4

166
    # It should be possible to override properties in derived classes
167
168
    assert m.TestPropertiesOverride().def_readonly == 99
    assert m.TestPropertiesOverride.def_readonly_static == 99
169

170
171
172
173

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

174
175
176
    instance = m.TestProperties()
    assert m.TestProperties.static_cls is m.TestProperties
    assert instance.static_cls is m.TestProperties
177
178

    def check_self(self):
179
        assert self is m.TestProperties
180

181
    m.TestProperties.static_cls = check_self
182
183
    instance.static_cls = check_self

184

185
186
187
def test_metaclass_override():
    """Overriding pybind11's default metaclass changes the behavior of `static_property`"""

188
189
    assert type(m.ExampleMandA).__name__ == "pybind11_type"
    assert type(m.MetaclassOverride).__name__ == "type"
190

191
192
    assert m.MetaclassOverride.readonly == 1
    assert type(m.MetaclassOverride.__dict__["readonly"]).__name__ == "pybind11_static_property"
193
194

    # Regular `type` replaces the property instead of calling `__set__()`
195
196
197
    m.MetaclassOverride.readonly = 2
    assert m.MetaclassOverride.readonly == 2
    assert isinstance(m.MetaclassOverride.__dict__["readonly"], int)
198
199


200
201
202
203
def test_no_mixed_overloads():
    from pybind11_tests import debug_enabled

    with pytest.raises(RuntimeError) as excinfo:
204
        m.ExampleMandA.add_mixed_overloads1()
205
206
207
208
    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"
209
             "(arg0: float) -> str")
210
211
212
            )

    with pytest.raises(RuntimeError) as excinfo:
213
        m.ExampleMandA.add_mixed_overloads2()
214
215
216
217
    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"
218
219
             "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: int, arg1: int)"
             " -> str")
220
221
222
            )


223
224
225
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
def test_property_return_value_policies(access):
    if not access.startswith("static"):
226
        obj = m.TestPropRVP()
227
    else:
228
        obj = m.TestPropRVP
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248

    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
249
    `reference(_internal)` to `move`. The following would not work otherwise."""
250

251
    instance = m.TestPropRVP()
252
253
    o = instance.rvalue
    assert o.value == 1
Wenzel Jakob's avatar
Wenzel Jakob committed
254

255
256
    os = m.TestPropRVP.static_rvalue
    assert os.value == 1
257
258


Wenzel Jakob's avatar
Wenzel Jakob committed
259
260
# https://bitbucket.org/pypy/pypy/issues/2447
@pytest.unsupported_on_pypy
Dean Moldovan's avatar
Dean Moldovan committed
261
def test_dynamic_attributes():
262
    instance = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
    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'"

282
    cstats = ConstructorStats.get(m.DynamicClass)
Dean Moldovan's avatar
Dean Moldovan committed
283
284
285
286
287
    assert cstats.alive() == 1
    del instance
    assert cstats.alive() == 0

    # Derived classes should work as well
288
    class PythonDerivedDynamicClass(m.DynamicClass):
Dean Moldovan's avatar
Dean Moldovan committed
289
290
        pass

291
    for cls in m.CppDerivedDynamicClass, PythonDerivedDynamicClass:
292
293
294
        derived = cls()
        derived.foobar = 100
        assert derived.foobar == 100
Dean Moldovan's avatar
Dean Moldovan committed
295

296
297
298
        assert cstats.alive() == 1
        del derived
        assert cstats.alive() == 0
Dean Moldovan's avatar
Dean Moldovan committed
299
300


301
302
# https://bitbucket.org/pypy/pypy/issues/2447
@pytest.unsupported_on_pypy
Dean Moldovan's avatar
Dean Moldovan committed
303
304
def test_cyclic_gc():
    # One object references itself
305
    instance = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
306
307
    instance.circular_reference = instance

308
    cstats = ConstructorStats.get(m.DynamicClass)
Dean Moldovan's avatar
Dean Moldovan committed
309
310
311
312
313
    assert cstats.alive() == 1
    del instance
    assert cstats.alive() == 0

    # Two object reference each other
314
315
    i1 = m.DynamicClass()
    i2 = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
316
317
318
319
320
321
    i1.cycle = i2
    i2.cycle = i1

    assert cstats.alive() == 2
    del i1, i2
    assert cstats.alive() == 0
322
323
324


def test_noconvert_args(msg):
325
    a = m.ArgInspector()
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
    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")
349
    assert msg(m.arg_inspect_func("A1", "A2")) == """
350
351
352
353
        loading ArgInspector2 argument WITH conversion allowed.  Argument value = A1
        loading ArgInspector1 argument WITHOUT conversion allowed.  Argument value = A2
    """

354
355
    assert m.floats_preferred(4) == 2.0
    assert m.floats_only(4.0) == 2.0
356
    with pytest.raises(TypeError) as excinfo:
357
        m.floats_only(4)
358
359
360
361
362
363
    assert msg(excinfo.value) == """
        floats_only(): incompatible function arguments. The following argument types are supported:
            1. (f: float) -> float

        Invoked with: 4
    """
364

365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
    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
    """

386
387

def test_bad_arg_default(msg):
388
    from pybind11_tests import debug_enabled
389
390

    with pytest.raises(RuntimeError) as excinfo:
391
        m.bad_arg_def_named()
392
    assert msg(excinfo.value) == (
393
394
        "arg(): could not convert default argument 'a: UnregisteredType' in function "
        "'should_fail' into a Python object (type not registered yet?)"
395
396
397
398
399
400
        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:
401
        m.bad_arg_def_unnamed()
402
    assert msg(excinfo.value) == (
403
404
        "arg(): could not convert default argument 'UnregisteredType' in function "
        "'should_fail' into a Python object (type not registered yet?)"
405
406
407
408
        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."
    )
409
410


411
def test_accepts_none(msg):
412
413
414
415
416
417
418
419
420
421
422
    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
423
424

    with pytest.raises(TypeError) as excinfo:
425
        m.no_none1(None)
426
427
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
428
        m.no_none2(None)
429
430
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
431
        m.no_none3(None)
432
433
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
434
        m.no_none4(None)
435
436
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
437
        m.no_none5(None)
438
439
440
441
    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:
442
        assert m.ok_none1(None) == -1
443
444
    assert msg(excinfo.value) == """
        ok_none1(): incompatible function arguments. The following argument types are supported:
445
            1. (arg0: m.methods_and_attributes.NoneTester) -> int
446
447
448
449

        Invoked with: None
    """

450
    # The rest take the argument as pointer or holder, and accept None:
451
452
453
454
    assert m.ok_none2(None) == -1
    assert m.ok_none3(None) == -1
    assert m.ok_none4(None) == -1
    assert m.ok_none5(None) == -1
455
456
457
458
459


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

460
    assert str(m.StrIssue(3)) == "StrIssue[3]"
461
462

    with pytest.raises(TypeError) as excinfo:
463
        str(m.StrIssue("no", "such", "constructor"))
464
465
    assert msg(excinfo.value) == """
        __init__(): incompatible constructor arguments. The following argument types are supported:
466
467
            1. m.methods_and_attributes.StrIssue(arg0: int)
            2. m.methods_and_attributes.StrIssue()
468
469
470

        Invoked with: 'no', 'such', 'constructor'
    """
471
472
473


def test_unregistered_base_implementations():
474
    a = m.RegisteredDerived()
475
476
477
478
479
480
481
482
483
484
485
486
487
488
    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
489
490
491


def test_custom_caster_destruction():
492
493
    """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."""
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512

    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