test_methods_and_attributes.py 17.7 KB
Newer Older
1
2
import sys

Dean Moldovan's avatar
Dean Moldovan committed
3
import pytest
4
5

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

9
10
11
12
13
14
15
16
17
18
NO_GETTER_MSG = (
    "unreadable attribute" if sys.version_info < (3, 11) else "object has no getter"
)
NO_SETTER_MSG = (
    "can't set attribute" if sys.version_info < (3, 11) else "object has no setter"
)
NO_DELETER_MSG = (
    "can't delete attribute" if sys.version_info < (3, 11) else "object has no deleter"
)

Dean Moldovan's avatar
Dean Moldovan committed
19
20

def test_methods_and_attributes():
21
22
    instance1 = m.ExampleMandA()
    instance2 = m.ExampleMandA(32)
Dean Moldovan's avatar
Dean Moldovan committed
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

    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

49
50
    assert instance1.overloaded() == "()"
    assert instance1.overloaded(0) == "(int)"
51
52
    assert instance1.overloaded(1, 1.0) == "(int, float)"
    assert instance1.overloaded(2.0, 2) == "(float, int)"
53
54
    assert instance1.overloaded(3, 3) == "(int, int)"
    assert instance1.overloaded(4.0, 4.0) == "(float, float)"
55
    assert instance1.overloaded_const(-3) == "(int) const"
56
57
    assert instance1.overloaded_const(5, 5.0) == "(int, float) const"
    assert instance1.overloaded_const(6.0, 6) == "(float, int) const"
58
59
    assert instance1.overloaded_const(7, 7) == "(int, int) const"
    assert instance1.overloaded_const(8.0, 8.0) == "(float, float) const"
60
    assert instance1.overloaded_float(1, 1) == "(float, float)"
61
62
63
    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)"
64

Dean Moldovan's avatar
Dean Moldovan committed
65
66
67
68
    assert instance1.value == 320
    instance1.value = 100
    assert str(instance1) == "ExampleMandA[value=100]"

69
    cstats = ConstructorStats.get(m.ExampleMandA)
Dean Moldovan's avatar
Dean Moldovan committed
70
71
72
73
74
    assert cstats.alive() == 2
    del instance1, instance2
    assert cstats.alive() == 0
    assert cstats.values() == ["32"]
    assert cstats.default_constructions == 1
75
76
    assert cstats.copy_constructions == 2
    assert cstats.move_constructions >= 2
Dean Moldovan's avatar
Dean Moldovan committed
77
78
    assert cstats.copy_assignments == 0
    assert cstats.move_assignments == 0
Dean Moldovan's avatar
Dean Moldovan committed
79
80


81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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
96

97
98
99

def test_properties():
    instance = m.TestProperties()
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

    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

115
    with pytest.raises(AttributeError) as excinfo:
116
        dummy = instance.def_property_writeonly  # unused var
117
    assert NO_GETTER_MSG in str(excinfo.value)
118
119
120
121
122
123

    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
124
    assert NO_GETTER_MSG in str(excinfo.value)
125
126
127

    with pytest.raises(AttributeError) as excinfo:
        instance.def_property_impossible = 5
128
    assert NO_SETTER_MSG in str(excinfo.value)
129

130
131

def test_static_properties():
132
    assert m.TestProperties.def_readonly_static == 1
133
    with pytest.raises(AttributeError) as excinfo:
134
        m.TestProperties.def_readonly_static = 2
135
    assert NO_SETTER_MSG in str(excinfo.value)
136

137
138
    m.TestProperties.def_readwrite_static = 2
    assert m.TestProperties.def_readwrite_static == 2
139

140
    with pytest.raises(AttributeError) as excinfo:
141
        dummy = m.TestProperties.def_writeonly_static  # unused var
142
    assert NO_GETTER_MSG in str(excinfo.value)
143
144
145
146
147
148
149

    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
150
    assert NO_SETTER_MSG in str(excinfo.value)
151

152
153
154
155
156
    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
157
    assert NO_GETTER_MSG in str(excinfo.value)
158
159
160

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

162
    # Static property read and write via instance
163
    instance = m.TestProperties()
164

165
166
    m.TestProperties.def_readwrite_static = 0
    assert m.TestProperties.def_readwrite_static == 0
167
168
169
    assert instance.def_readwrite_static == 0

    instance.def_readwrite_static = 2
170
    assert m.TestProperties.def_readwrite_static == 2
171
172
    assert instance.def_readwrite_static == 2

173
174
    with pytest.raises(AttributeError) as excinfo:
        dummy = instance.def_property_writeonly_static  # noqa: F841 unused var
175
    assert NO_GETTER_MSG in str(excinfo.value)
176
177
178
179

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

180
    # It should be possible to override properties in derived classes
181
182
    assert m.TestPropertiesOverride().def_readonly == 99
    assert m.TestPropertiesOverride.def_readonly_static == 99
183

184
185
186
187
188
189
190
191
192
193
194
    # 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
195
    assert NO_DELETER_MSG in str(excinfo.value)
196

197
198
199
200

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

201
202
203
    instance = m.TestProperties()
    assert m.TestProperties.static_cls is m.TestProperties
    assert instance.static_cls is m.TestProperties
204
205

    def check_self(self):
206
        assert self is m.TestProperties
207

208
    m.TestProperties.static_cls = check_self
209
210
    instance.static_cls = check_self

211

212
213
214
def test_metaclass_override():
    """Overriding pybind11's default metaclass changes the behavior of `static_property`"""

215
216
    assert type(m.ExampleMandA).__name__ == "pybind11_type"
    assert type(m.MetaclassOverride).__name__ == "type"
217

218
    assert m.MetaclassOverride.readonly == 1
219
220
221
222
    assert (
        type(m.MetaclassOverride.__dict__["readonly"]).__name__
        == "pybind11_static_property"
    )
223
224

    # Regular `type` replaces the property instead of calling `__set__()`
225
226
227
    m.MetaclassOverride.readonly = 2
    assert m.MetaclassOverride.readonly == 2
    assert isinstance(m.MetaclassOverride.__dict__["readonly"], int)
228
229


230
def test_no_mixed_overloads():
231
    from pybind11_tests import detailed_error_messages_enabled
232
233

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

    with pytest.raises(RuntimeError) as excinfo:
245
        m.ExampleMandA.add_mixed_overloads2()
246
247
248
    assert str(
        excinfo.value
    ) == "overloading a method with both static and instance methods is not supported; " + (
249
250
        "#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
        if not detailed_error_messages_enabled
251
252
253
254
        else "error while attempting to bind instance method ExampleMandA.overload_mixed2"
        "(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: int, arg1: int)"
        " -> str"
    )
255
256


257
258
259
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
def test_property_return_value_policies(access):
    if not access.startswith("static"):
260
        obj = m.TestPropRVP()
261
    else:
262
        obj = m.TestPropRVP
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282

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

285
    instance = m.TestPropRVP()
286
287
    o = instance.rvalue
    assert o.value == 1
Wenzel Jakob's avatar
Wenzel Jakob committed
288

289
290
    os = m.TestPropRVP.static_rvalue
    assert os.value == 1
291
292


293
294
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
@pytest.mark.xfail("env.PYPY")
Dean Moldovan's avatar
Dean Moldovan committed
295
def test_dynamic_attributes():
296
    instance = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
    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'"

316
    cstats = ConstructorStats.get(m.DynamicClass)
Dean Moldovan's avatar
Dean Moldovan committed
317
318
319
320
321
    assert cstats.alive() == 1
    del instance
    assert cstats.alive() == 0

    # Derived classes should work as well
322
    class PythonDerivedDynamicClass(m.DynamicClass):
Dean Moldovan's avatar
Dean Moldovan committed
323
324
        pass

325
    for cls in m.CppDerivedDynamicClass, PythonDerivedDynamicClass:
326
327
328
        derived = cls()
        derived.foobar = 100
        assert derived.foobar == 100
Dean Moldovan's avatar
Dean Moldovan committed
329

330
331
332
        assert cstats.alive() == 1
        del derived
        assert cstats.alive() == 0
Dean Moldovan's avatar
Dean Moldovan committed
333
334


335
336
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
@pytest.mark.xfail("env.PYPY")
Dean Moldovan's avatar
Dean Moldovan committed
337
338
def test_cyclic_gc():
    # One object references itself
339
    instance = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
340
341
    instance.circular_reference = instance

342
    cstats = ConstructorStats.get(m.DynamicClass)
Dean Moldovan's avatar
Dean Moldovan committed
343
344
345
346
347
    assert cstats.alive() == 1
    del instance
    assert cstats.alive() == 0

    # Two object reference each other
348
349
    i1 = m.DynamicClass()
    i2 = m.DynamicClass()
Dean Moldovan's avatar
Dean Moldovan committed
350
351
352
353
354
355
    i1.cycle = i2
    i2.cycle = i1

    assert cstats.alive() == 2
    del i1, i2
    assert cstats.alive() == 0
356
357


358
def test_bad_arg_default(msg):
359
    from pybind11_tests import detailed_error_messages_enabled
360
361

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

    with pytest.raises(RuntimeError) as excinfo:
372
        m.bad_arg_def_unnamed()
373
    assert msg(excinfo.value) == (
374
375
        "arg(): could not convert default argument 'UnregisteredType' in function "
        "'should_fail' into a Python object (type not registered yet?)"
376
        if detailed_error_messages_enabled
377
        else "arg(): could not convert default argument into a Python object (type not registered "
378
        "yet?). #define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more information."
379
    )
380
381


382
def test_accepts_none(msg):
383
384
385
386
387
388
389
390
391
392
393
    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
394
395

    with pytest.raises(TypeError) as excinfo:
396
        m.no_none1(None)
397
398
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
399
        m.no_none2(None)
400
401
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
402
        m.no_none3(None)
403
404
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
405
        m.no_none4(None)
406
407
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
408
        m.no_none5(None)
409
410
411
412
    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:
413
        assert m.ok_none1(None) == -1
414
415
416
    assert (
        msg(excinfo.value)
        == """
417
        ok_none1(): incompatible function arguments. The following argument types are supported:
418
            1. (arg0: m.methods_and_attributes.NoneTester) -> int
419
420
421

        Invoked with: None
    """
422
    )
423

424
    # The rest take the argument as pointer or holder, and accept None:
425
426
427
428
    assert m.ok_none2(None) == -1
    assert m.ok_none3(None) == -1
    assert m.ok_none4(None) == -1
    assert m.ok_none5(None) == -1
429

430
431
432
433
434
435
436
437
438
439
440
441
442
    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)

443

444
def test_casts_none():
445
446
447
448
449
450
451
452
453
454
    """#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


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

458
    assert str(m.StrIssue(3)) == "StrIssue[3]"
459
460

    with pytest.raises(TypeError) as excinfo:
461
        str(m.StrIssue("no", "such", "constructor"))
462
463
464
    assert (
        msg(excinfo.value)
        == """
465
        __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
474


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


492
493
494
495
496
497
498
499
500
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
501
502
503


def test_overload_ordering():
504
    "Check to see if the normal overload order (first defined) and prepend overload order works"
505
506
507
508
    assert m.overload_order("string") == 1
    assert m.overload_order(0) == 4

    assert "1. overload_order(arg0: int) -> int" in m.overload_order.__doc__
509
510
    assert "2. overload_order(arg0: str) -> int" in m.overload_order.__doc__
    assert "3. overload_order(arg0: str) -> int" in m.overload_order.__doc__
511
512
513
514
515
516
    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)
517
518
    assert "2. (arg0: str) -> int" in str(err.value)
    assert "3. (arg0: str) -> int" in str(err.value)
519
    assert "4. (arg0: int) -> int" in str(err.value)
520
521
522
523
524
525
526
527


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