test_smart_ptr.py 9.37 KB
Newer Older
1
import pytest
2
3
4

m = pytest.importorskip("pybind11_tests.smart_ptr")
from pybind11_tests import ConstructorStats  # noqa: E402
Dean Moldovan's avatar
Dean Moldovan committed
5
6
7
8


def test_smart_ptr(capture):
    # Object1
9
10
11
    for i, o in enumerate(
        [m.make_object_1(), m.make_object_2(), m.MyObject1(3)], start=1
    ):
Dean Moldovan's avatar
Dean Moldovan committed
12
13
        assert o.getRefCount() == 1
        with capture:
14
15
16
17
            m.print_object_1(o)
            m.print_object_2(o)
            m.print_object_3(o)
            m.print_object_4(o)
Dean Moldovan's avatar
Dean Moldovan committed
18
19
        assert capture == "MyObject1[{i}]\n".format(i=i) * 4

20
21
22
    for i, o in enumerate(
        [m.make_myobject1_1(), m.make_myobject1_2(), m.MyObject1(6), 7], start=4
    ):
Dean Moldovan's avatar
Dean Moldovan committed
23
24
25
        print(o)
        with capture:
            if not isinstance(o, int):
26
27
28
29
30
31
32
33
                m.print_object_1(o)
                m.print_object_2(o)
                m.print_object_3(o)
                m.print_object_4(o)
            m.print_myobject1_1(o)
            m.print_myobject1_2(o)
            m.print_myobject1_3(o)
            m.print_myobject1_4(o)
34
35
36

        times = 4 if isinstance(o, int) else 8
        assert capture == "MyObject1[{i}]\n".format(i=i) * times
Dean Moldovan's avatar
Dean Moldovan committed
37

38
    cstats = ConstructorStats.get(m.MyObject1)
Dean Moldovan's avatar
Dean Moldovan committed
39
    assert cstats.alive() == 0
40
41
42
    expected_values = ["MyObject1[{}]".format(i) for i in range(1, 7)] + [
        "MyObject1[7]"
    ] * 4
Dean Moldovan's avatar
Dean Moldovan committed
43
44
45
46
47
48
49
50
    assert cstats.values() == expected_values
    assert cstats.default_constructions == 0
    assert cstats.copy_constructions == 0
    # assert cstats.move_constructions >= 0 # Doesn't invoke any
    assert cstats.copy_assignments == 0
    assert cstats.move_assignments == 0

    # Object2
51
52
53
    for i, o in zip(
        [8, 6, 7], [m.MyObject2(8), m.make_myobject2_1(), m.make_myobject2_2()]
    ):
Dean Moldovan's avatar
Dean Moldovan committed
54
55
        print(o)
        with capture:
56
57
58
59
            m.print_myobject2_1(o)
            m.print_myobject2_2(o)
            m.print_myobject2_3(o)
            m.print_myobject2_4(o)
Dean Moldovan's avatar
Dean Moldovan committed
60
61
        assert capture == "MyObject2[{i}]\n".format(i=i) * 4

62
    cstats = ConstructorStats.get(m.MyObject2)
Dean Moldovan's avatar
Dean Moldovan committed
63
64
65
    assert cstats.alive() == 1
    o = None
    assert cstats.alive() == 0
66
    assert cstats.values() == ["MyObject2[8]", "MyObject2[6]", "MyObject2[7]"]
Dean Moldovan's avatar
Dean Moldovan committed
67
68
69
70
71
72
73
    assert cstats.default_constructions == 0
    assert cstats.copy_constructions == 0
    # assert cstats.move_constructions >= 0 # Doesn't invoke any
    assert cstats.copy_assignments == 0
    assert cstats.move_assignments == 0

    # Object3
74
75
76
    for i, o in zip(
        [9, 8, 9], [m.MyObject3(9), m.make_myobject3_1(), m.make_myobject3_2()]
    ):
Dean Moldovan's avatar
Dean Moldovan committed
77
78
        print(o)
        with capture:
79
80
81
82
            m.print_myobject3_1(o)
            m.print_myobject3_2(o)
            m.print_myobject3_3(o)
            m.print_myobject3_4(o)
Dean Moldovan's avatar
Dean Moldovan committed
83
84
        assert capture == "MyObject3[{i}]\n".format(i=i) * 4

85
    cstats = ConstructorStats.get(m.MyObject3)
Dean Moldovan's avatar
Dean Moldovan committed
86
87
88
    assert cstats.alive() == 1
    o = None
    assert cstats.alive() == 0
89
    assert cstats.values() == ["MyObject3[9]", "MyObject3[8]", "MyObject3[9]"]
Dean Moldovan's avatar
Dean Moldovan committed
90
91
92
93
94
95
    assert cstats.default_constructions == 0
    assert cstats.copy_constructions == 0
    # assert cstats.move_constructions >= 0 # Doesn't invoke any
    assert cstats.copy_assignments == 0
    assert cstats.move_assignments == 0

96
97
    # Object
    cstats = ConstructorStats.get(m.Object)
Dean Moldovan's avatar
Dean Moldovan committed
98
99
100
101
102
103
104
105
    assert cstats.alive() == 0
    assert cstats.values() == []
    assert cstats.default_constructions == 10
    assert cstats.copy_constructions == 0
    # assert cstats.move_constructions >= 0 # Doesn't invoke any
    assert cstats.copy_assignments == 0
    assert cstats.move_assignments == 0

106
107
    # ref<>
    cstats = m.cstats_ref()
Dean Moldovan's avatar
Dean Moldovan committed
108
    assert cstats.alive() == 0
109
    assert cstats.values() == ["from pointer"] * 10
Dean Moldovan's avatar
Dean Moldovan committed
110
111
112
113
114
    assert cstats.default_constructions == 30
    assert cstats.copy_constructions == 12
    # assert cstats.move_constructions >= 0 # Doesn't invoke any
    assert cstats.copy_assignments == 30
    assert cstats.move_assignments == 0
115

116

117
def test_smart_ptr_refcounting():
118
    assert m.test_object1_refcounting()
119
120


121
def test_unique_nodelete():
122
    o = m.MyObject4(23)
123
    assert o.value == 23
124
    cstats = ConstructorStats.get(m.MyObject4)
125
126
    assert cstats.alive() == 1
    del o
127
128
129
    assert cstats.alive() == 1
    m.MyObject4.cleanup_all_instances()
    assert cstats.alive() == 0
130
131


132
133
134
135
136
137
def test_unique_nodelete4a():
    o = m.MyObject4a(23)
    assert o.value == 23
    cstats = ConstructorStats.get(m.MyObject4a)
    assert cstats.alive() == 1
    del o
138
139
140
    assert cstats.alive() == 1
    m.MyObject4a.cleanup_all_instances()
    assert cstats.alive() == 0
141
142
143


def test_unique_deleter():
144
    m.MyObject4a(0)
145
146
147
    o = m.MyObject4b(23)
    assert o.value == 23
    cstats4a = ConstructorStats.get(m.MyObject4a)
148
    assert cstats4a.alive() == 2
149
150
151
    cstats4b = ConstructorStats.get(m.MyObject4b)
    assert cstats4b.alive() == 1
    del o
152
    assert cstats4a.alive() == 1  # Should now only be one leftover
153
    assert cstats4b.alive() == 0  # Should be deleted
154
155
156
    m.MyObject4a.cleanup_all_instances()
    assert cstats4a.alive() == 0
    assert cstats4b.alive() == 0
157
158


159
def test_large_holder():
160
    o = m.MyObject5(5)
161
    assert o.value == 5
162
    cstats = ConstructorStats.get(m.MyObject5)
163
164
165
166
167
    assert cstats.alive() == 1
    del o
    assert cstats.alive() == 0


168
def test_shared_ptr_and_references():
169
170
    s = m.SharedPtrRef()
    stats = ConstructorStats.get(m.A)
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    assert stats.alive() == 2

    ref = s.ref  # init_holder_helper(holder_ptr=false, owned=false)
    assert stats.alive() == 2
    assert s.set_ref(ref)
    with pytest.raises(RuntimeError) as excinfo:
        assert s.set_holder(ref)
    assert "Unable to cast from non-held to held instance" in str(excinfo.value)

    copy = s.copy  # init_holder_helper(holder_ptr=false, owned=true)
    assert stats.alive() == 3
    assert s.set_ref(copy)
    assert s.set_holder(copy)

    holder_ref = s.holder_ref  # init_holder_helper(holder_ptr=true, owned=false)
    assert stats.alive() == 3
    assert s.set_ref(holder_ref)
    assert s.set_holder(holder_ref)

    holder_copy = s.holder_copy  # init_holder_helper(holder_ptr=true, owned=true)
    assert stats.alive() == 3
    assert s.set_ref(holder_copy)
    assert s.set_holder(holder_copy)

    del ref, copy, holder_ref, holder_copy, s
    assert stats.alive() == 0


def test_shared_ptr_from_this_and_references():
200
201
    s = m.SharedFromThisRef()
    stats = ConstructorStats.get(m.B)
202
203
204
205
206
    assert stats.alive() == 2

    ref = s.ref  # init_holder_helper(holder_ptr=false, owned=false, bad_wp=false)
    assert stats.alive() == 2
    assert s.set_ref(ref)
207
208
209
    assert s.set_holder(
        ref
    )  # std::enable_shared_from_this can create a holder from a reference
210
211
212
213
214
215
216
217
218
219
220
221
222

    bad_wp = s.bad_wp  # init_holder_helper(holder_ptr=false, owned=false, bad_wp=true)
    assert stats.alive() == 2
    assert s.set_ref(bad_wp)
    with pytest.raises(RuntimeError) as excinfo:
        assert s.set_holder(bad_wp)
    assert "Unable to cast from non-held to held instance" in str(excinfo.value)

    copy = s.copy  # init_holder_helper(holder_ptr=false, owned=true, bad_wp=false)
    assert stats.alive() == 3
    assert s.set_ref(copy)
    assert s.set_holder(copy)

223
224
225
    holder_ref = (
        s.holder_ref
    )  # init_holder_helper(holder_ptr=true, owned=false, bad_wp=false)
226
227
228
229
    assert stats.alive() == 3
    assert s.set_ref(holder_ref)
    assert s.set_holder(holder_ref)

230
231
232
    holder_copy = (
        s.holder_copy
    )  # init_holder_helper(holder_ptr=true, owned=true, bad_wp=false)
233
234
235
236
237
238
    assert stats.alive() == 3
    assert s.set_ref(holder_copy)
    assert s.set_holder(holder_copy)

    del ref, bad_wp, copy, holder_ref, holder_copy, s
    assert stats.alive() == 0
239

240
241
    z = m.SharedFromThisVirt.get()
    y = m.SharedFromThisVirt.get()
242
243
    assert y is z

244
245

def test_move_only_holder():
246
    a = m.TypeWithMoveOnlyHolder.make()
247
    b = m.TypeWithMoveOnlyHolder.make_as_object()
248
    stats = ConstructorStats.get(m.TypeWithMoveOnlyHolder)
249
250
    assert stats.alive() == 2
    del b
251
252
253
    assert stats.alive() == 1
    del a
    assert stats.alive() == 0
254
255


256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
def test_holder_with_addressof_operator():
    # this test must not throw exception from c++
    a = m.TypeForHolderWithAddressOf.make()
    a.print_object_1()
    a.print_object_2()
    a.print_object_3()
    a.print_object_4()

    stats = ConstructorStats.get(m.TypeForHolderWithAddressOf)
    assert stats.alive() == 1

    np = m.TypeForHolderWithAddressOf.make()
    assert stats.alive() == 2
    del a
    assert stats.alive() == 1
    del np
    assert stats.alive() == 0

    b = m.TypeForHolderWithAddressOf.make()
    c = b
    assert b.get() is c.get()
    assert stats.alive() == 1

    del b
    assert stats.alive() == 1

    del c
    assert stats.alive() == 0


def test_move_only_holder_with_addressof_operator():
    a = m.TypeForMoveOnlyHolderWithAddressOf.make()
    a.print_object()

    stats = ConstructorStats.get(m.TypeForMoveOnlyHolderWithAddressOf)
    assert stats.alive() == 1

    a.value = 42
    assert a.value == 42

    del a
    assert stats.alive() == 0


300
def test_smart_ptr_from_default():
301
    instance = m.HeldByDefaultHolder()
302
    with pytest.raises(RuntimeError) as excinfo:
303
        m.HeldByDefaultHolder.load_shared_ptr(instance)
304
305
306
307
    assert (
        "Unable to load a custom holder type from a "
        "default-holder instance" in str(excinfo.value)
    )
308
309
310
311


def test_shared_ptr_gc():
    """#187: issue involving std::shared_ptr<> return value policy & garbage collection"""
312
    el = m.ElementList()
313
    for i in range(10):
314
        el.add(m.ElementA(i))
315
316
317
    pytest.gc_collect()
    for i, v in enumerate(el.get()):
        assert i == v.value()