test_methods_and_attributes.py 14.7 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
    assert cstats.alive() == 2
    del instance1, instance2
    assert cstats.alive() == 0
    assert cstats.values() == ["32"]
    assert cstats.default_constructions == 1
61
62
    assert cstats.copy_constructions == 2
    assert cstats.move_constructions >= 2
Dean Moldovan's avatar
Dean Moldovan committed
63
64
    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
    with pytest.raises(AttributeError) as excinfo:
        dummy = instance.def_property_writeonly  # noqa: F841 unused var
103
    assert "unreadable attribute" in str(excinfo.value)
104
105
106
107
108
109

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

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

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.value)
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
        dummy = m.TestProperties.def_writeonly_static  # noqa: F841 unused var
128
    assert "unreadable attribute" in str(excinfo.value)
129
130
131
132
133
134
135

    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.value)
137

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

    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
    with pytest.raises(AttributeError) as excinfo:
        dummy = instance.def_property_writeonly_static  # noqa: F841 unused var
161
    assert "unreadable attribute" in str(excinfo.value)
162
163
164
165

    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_bad_arg_default(msg):
325
    from pybind11_tests import debug_enabled
326
327

    with pytest.raises(RuntimeError) as excinfo:
328
        m.bad_arg_def_named()
329
    assert msg(excinfo.value) == (
330
331
        "arg(): could not convert default argument 'a: UnregisteredType' in function "
        "'should_fail' into a Python object (type not registered yet?)"
332
333
334
335
336
337
        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:
338
        m.bad_arg_def_unnamed()
339
    assert msg(excinfo.value) == (
340
341
        "arg(): could not convert default argument 'UnregisteredType' in function "
        "'should_fail' into a Python object (type not registered yet?)"
342
343
344
345
        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."
    )
346
347


348
def test_accepts_none(msg):
349
350
351
352
353
354
355
356
357
358
359
    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
360
361

    with pytest.raises(TypeError) as excinfo:
362
        m.no_none1(None)
363
364
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
365
        m.no_none2(None)
366
367
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
368
        m.no_none3(None)
369
370
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
371
        m.no_none4(None)
372
373
    assert "incompatible function arguments" in str(excinfo.value)
    with pytest.raises(TypeError) as excinfo:
374
        m.no_none5(None)
375
376
377
378
    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:
379
        assert m.ok_none1(None) == -1
380
381
    assert msg(excinfo.value) == """
        ok_none1(): incompatible function arguments. The following argument types are supported:
382
            1. (arg0: m.methods_and_attributes.NoneTester) -> int
383
384
385
386

        Invoked with: None
    """

387
    # The rest take the argument as pointer or holder, and accept None:
388
389
390
391
    assert m.ok_none2(None) == -1
    assert m.ok_none3(None) == -1
    assert m.ok_none4(None) == -1
    assert m.ok_none5(None) == -1
392
393
394
395
396


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

397
    assert str(m.StrIssue(3)) == "StrIssue[3]"
398
399

    with pytest.raises(TypeError) as excinfo:
400
        str(m.StrIssue("no", "such", "constructor"))
401
402
    assert msg(excinfo.value) == """
        __init__(): incompatible constructor arguments. The following argument types are supported:
403
404
            1. m.methods_and_attributes.StrIssue(arg0: int)
            2. m.methods_and_attributes.StrIssue()
405
406
407

        Invoked with: 'no', 'such', 'constructor'
    """
408
409
410


def test_unregistered_base_implementations():
411
    a = m.RegisteredDerived()
412
413
414
415
416
417
418
419
420
421
422
423
424
425
    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
426
427


428
429
430
431
432
433
434
435
436
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