test_methods_and_attributes.py 17.5 KB
Newer Older
Dean Moldovan's avatar
Dean Moldovan committed
1
import pytest
2
3

import env  # noqa: F401
4
from pybind11_tests import ConstructorStats
5
from pybind11_tests import methods_and_attributes as m
Dean Moldovan's avatar
Dean Moldovan committed
6
7
8


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

    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

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

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

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


69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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
84

85
86
87

def test_properties():
    instance = m.TestProperties()
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

103
    with pytest.raises(AttributeError) as excinfo:
104
        dummy = instance.def_property_writeonly  # unused var
105
    assert "unreadable attribute" in str(excinfo.value)
106
107
108
109
110
111

    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
112
    assert "unreadable attribute" in str(excinfo.value)
113
114
115

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

118
119

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

125
126
    m.TestProperties.def_readwrite_static = 2
    assert m.TestProperties.def_readwrite_static == 2
127

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

    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
138
    assert "can't set attribute" in str(excinfo.value)
139

140
141
142
143
144
    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
145
    assert "unreadable attribute" in str(excinfo.value)
146
147
148

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

150
    # Static property read and write via instance
151
    instance = m.TestProperties()
152

153
154
    m.TestProperties.def_readwrite_static = 0
    assert m.TestProperties.def_readwrite_static == 0
155
156
157
    assert instance.def_readwrite_static == 0

    instance.def_readwrite_static = 2
158
    assert m.TestProperties.def_readwrite_static == 2
159
160
    assert instance.def_readwrite_static == 2

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

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

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

172
173
174
175
176
177
178
179
180
181
182
183
184
    # Only static attributes can be deleted
    del m.TestPropertiesOverride.def_readonly_static
    assert (
        hasattr(m.TestPropertiesOverride, "def_readonly_static")
        and m.TestPropertiesOverride.def_readonly_static
        is m.TestProperties.def_readonly_static
    )
    assert "def_readonly_static" not in m.TestPropertiesOverride.__dict__
    properties_override = m.TestPropertiesOverride()
    with pytest.raises(AttributeError) as excinfo:
        del properties_override.def_readonly
    assert "can't delete attribute" in str(excinfo.value)

185
186
187
188

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

189
190
191
    instance = m.TestProperties()
    assert m.TestProperties.static_cls is m.TestProperties
    assert instance.static_cls is m.TestProperties
192
193

    def check_self(self):
194
        assert self is m.TestProperties
195

196
    m.TestProperties.static_cls = check_self
197
198
    instance.static_cls = check_self

199

200
201
202
def test_metaclass_override():
    """Overriding pybind11's default metaclass changes the behavior of `static_property`"""

203
204
    assert type(m.ExampleMandA).__name__ == "pybind11_type"
    assert type(m.MetaclassOverride).__name__ == "type"
205

206
    assert m.MetaclassOverride.readonly == 1
207
208
209
210
    assert (
        type(m.MetaclassOverride.__dict__["readonly"]).__name__
        == "pybind11_static_property"
    )
211
212

    # Regular `type` replaces the property instead of calling `__set__()`
213
214
215
    m.MetaclassOverride.readonly = 2
    assert m.MetaclassOverride.readonly == 2
    assert isinstance(m.MetaclassOverride.__dict__["readonly"], int)
216
217


218
def test_no_mixed_overloads():
219
    from pybind11_tests import detailed_error_messages_enabled
220
221

    with pytest.raises(RuntimeError) as excinfo:
222
        m.ExampleMandA.add_mixed_overloads1()
223
224
225
    assert str(
        excinfo.value
    ) == "overloading a method with both static and instance methods is not supported; " + (
226
227
        "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
        if not detailed_error_messages_enabled
228
229
230
        else "error while attempting to bind static method ExampleMandA.overload_mixed1"
        "(arg0: float) -> str"
    )
231
232

    with pytest.raises(RuntimeError) as excinfo:
233
        m.ExampleMandA.add_mixed_overloads2()
234
235
236
    assert str(
        excinfo.value
    ) == "overloading a method with both static and instance methods is not supported; " + (
237
238
        "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
        if not detailed_error_messages_enabled
239
240
241
242
        else "error while attempting to bind instance method ExampleMandA.overload_mixed2"
        "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: int, arg1: int)"
        " -> str"
    )
243
244


245
246
247
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
def test_property_return_value_policies(access):
    if not access.startswith("static"):
248
        obj = m.TestPropRVP()
249
    else:
250
        obj = m.TestPropRVP
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270

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

273
    instance = m.TestPropRVP()
274
275
    o = instance.rvalue
    assert o.value == 1
Wenzel Jakob's avatar
Wenzel Jakob committed
276

277
278
    os = m.TestPropRVP.static_rvalue
    assert os.value == 1
279
280


281
282
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
@pytest.mark.xfail("env.PYPY")
Dean Moldovan's avatar
Dean Moldovan committed
283
def test_dynamic_attributes():
284
    instance = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
    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'"

304
    cstats = ConstructorStats.get(m.DynamicClass)
Dean Moldovan's avatar
Dean Moldovan committed
305
306
307
308
309
    assert cstats.alive() == 1
    del instance
    assert cstats.alive() == 0

    # Derived classes should work as well
310
    class PythonDerivedDynamicClass(m.DynamicClass):
Dean Moldovan's avatar
Dean Moldovan committed
311
312
        pass

313
    for cls in m.CppDerivedDynamicClass, PythonDerivedDynamicClass:
314
315
316
        derived = cls()
        derived.foobar = 100
        assert derived.foobar == 100
Dean Moldovan's avatar
Dean Moldovan committed
317

318
319
320
        assert cstats.alive() == 1
        del derived
        assert cstats.alive() == 0
Dean Moldovan's avatar
Dean Moldovan committed
321
322


323
324
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
@pytest.mark.xfail("env.PYPY")
Dean Moldovan's avatar
Dean Moldovan committed
325
326
def test_cyclic_gc():
    # One object references itself
327
    instance = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
328
329
    instance.circular_reference = instance

330
    cstats = ConstructorStats.get(m.DynamicClass)
Dean Moldovan's avatar
Dean Moldovan committed
331
332
333
334
335
    assert cstats.alive() == 1
    del instance
    assert cstats.alive() == 0

    # Two object reference each other
336
337
    i1 = m.DynamicClass()
    i2 = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
338
339
340
341
342
343
    i1.cycle = i2
    i2.cycle = i1

    assert cstats.alive() == 2
    del i1, i2
    assert cstats.alive() == 0
344
345


346
def test_bad_arg_default(msg):
347
    from pybind11_tests import detailed_error_messages_enabled
348
349

    with pytest.raises(RuntimeError) as excinfo:
350
        m.bad_arg_def_named()
351
    assert msg(excinfo.value) == (
352
353
        "arg(): could not convert default argument 'a: UnregisteredType' in function "
        "'should_fail' into a Python object (type not registered yet?)"
354
        if detailed_error_messages_enabled
355
        else "arg(): could not convert default argument into a Python object (type not registered "
356
        "yet?). #define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more information."
357
358
359
    )

    with pytest.raises(RuntimeError) as excinfo:
360
        m.bad_arg_def_unnamed()
361
    assert msg(excinfo.value) == (
362
363
        "arg(): could not convert default argument 'UnregisteredType' in function "
        "'should_fail' into a Python object (type not registered yet?)"
364
        if detailed_error_messages_enabled
365
        else "arg(): could not convert default argument into a Python object (type not registered "
366
        "yet?). #define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more information."
367
    )
368
369


370
def test_accepts_none(msg):
371
372
373
374
375
376
377
378
379
380
381
    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
382
383

    with pytest.raises(TypeError) as excinfo:
384
        m.no_none1(None)
385
386
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
387
        m.no_none2(None)
388
389
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
390
        m.no_none3(None)
391
392
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
393
        m.no_none4(None)
394
395
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
396
        m.no_none5(None)
397
398
399
400
    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:
401
        assert m.ok_none1(None) == -1
402
403
404
    assert (
        msg(excinfo.value)
        == """
405
        ok_none1(): incompatible function arguments. The following argument types are supported:
406
            1. (arg0: m.methods_and_attributes.NoneTester) -> int
407
408
409

        Invoked with: None
    """
410
    )
411

412
    # The rest take the argument as pointer or holder, and accept None:
413
414
415
416
    assert m.ok_none2(None) == -1
    assert m.ok_none3(None) == -1
    assert m.ok_none4(None) == -1
    assert m.ok_none5(None) == -1
417

418
419
420
421
422
423
424
425
426
427
428
429
430
    with pytest.raises(TypeError) as excinfo:
        m.no_none_kwarg(None)
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
        m.no_none_kwarg(a=None)
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
        m.no_none_kwarg_kw_only(None)
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
        m.no_none_kwarg_kw_only(a=None)
    assert "incompatible function arguments" in str(excinfo.value)

431

432
def test_casts_none():
433
434
435
436
437
438
439
440
441
442
    """#2778: implicit casting from None to object (not pointer)"""
    a = m.NoneCastTester()
    assert m.ok_obj_or_none(a) == -1
    a = m.NoneCastTester(4)
    assert m.ok_obj_or_none(a) == 4
    a = m.NoneCastTester(None)
    assert m.ok_obj_or_none(a) == -1
    assert m.ok_obj_or_none(None) == -1


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

446
    assert str(m.StrIssue(3)) == "StrIssue[3]"
447
448

    with pytest.raises(TypeError) as excinfo:
449
        str(m.StrIssue("no", "such", "constructor"))
450
451
452
    assert (
        msg(excinfo.value)
        == """
453
        __init__(): incompatible constructor arguments. The following argument types are supported:
454
455
            1. m.methods_and_attributes.StrIssue(arg0: int)
            2. m.methods_and_attributes.StrIssue()
456
457
458

        Invoked with: 'no', 'such', 'constructor'
    """
459
    )
460
461
462


def test_unregistered_base_implementations():
463
    a = m.RegisteredDerived()
464
465
466
467
468
469
470
471
472
473
474
475
476
477
    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
478
479


480
481
482
483
484
485
486
487
488
def test_ref_qualified():
    """Tests that explicit lvalue ref-qualified methods can be called just like their
    non ref-qualified counterparts."""

    r = m.RefQualified()
    assert r.value == 0
    r.refQualified(17)
    assert r.value == 17
    assert r.constRefQualified(23) == 40
489
490
491


def test_overload_ordering():
492
    "Check to see if the normal overload order (first defined) and prepend overload order works"
493
494
495
496
    assert m.overload_order("string") == 1
    assert m.overload_order(0) == 4

    assert "1. overload_order(arg0: int) -> int" in m.overload_order.__doc__
497
498
    assert "2. overload_order(arg0: str) -> int" in m.overload_order.__doc__
    assert "3. overload_order(arg0: str) -> int" in m.overload_order.__doc__
499
500
501
502
503
504
    assert "4. overload_order(arg0: int) -> int" in m.overload_order.__doc__

    with pytest.raises(TypeError) as err:
        m.overload_order(1.1)

    assert "1. (arg0: int) -> int" in str(err.value)
505
506
    assert "2. (arg0: str) -> int" in str(err.value)
    assert "3. (arg0: str) -> int" in str(err.value)
507
    assert "4. (arg0: int) -> int" in str(err.value)
508
509
510
511
512
513
514
515


def test_rvalue_ref_param():
    r = m.RValueRefParam()
    assert r.func1("123") == 3
    assert r.func2("1234") == 4
    assert r.func3("12345") == 5
    assert r.func4("123456") == 6