test_virtual_functions.py 11.2 KB
Newer Older
1
# -*- coding: utf-8 -*-
Dean Moldovan's avatar
Dean Moldovan committed
2
import pytest
3

4
5
import env  # noqa: F401

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


def test_override(capture, msg):
11
    class ExtendedExampleVirt(m.ExampleVirt):
Dean Moldovan's avatar
Dean Moldovan committed
12
13
14
15
16
17
18
19
20
21
22
23
        def __init__(self, state):
            super(ExtendedExampleVirt, self).__init__(state + 1)
            self.data = "Hello world"

        def run(self, value):
            print('ExtendedExampleVirt::run(%i), calling parent..' % value)
            return super(ExtendedExampleVirt, self).run(value + 1)

        def run_bool(self):
            print('ExtendedExampleVirt::run_bool()')
            return False

24
25
26
        def get_string1(self):
            return "override1"

Dean Moldovan's avatar
Dean Moldovan committed
27
28
29
        def pure_virtual(self):
            print('ExtendedExampleVirt::pure_virtual(): %s' % self.data)

30
31
32
33
34
35
36
    class ExtendedExampleVirt2(ExtendedExampleVirt):
        def __init__(self, state):
            super(ExtendedExampleVirt2, self).__init__(state + 1)

        def get_string2(self):
            return "override2"

37
    ex12 = m.ExampleVirt(10)
Dean Moldovan's avatar
Dean Moldovan committed
38
    with capture:
39
        assert m.runExampleVirt(ex12, 20) == 30
40
41
42
    assert capture == """
        Original implementation of ExampleVirt::run(state=10, value=20, str1=default1, str2=default2)
    """  # noqa: E501 line too long
Dean Moldovan's avatar
Dean Moldovan committed
43
44

    with pytest.raises(RuntimeError) as excinfo:
45
        m.runExampleVirtVirtual(ex12)
Dean Moldovan's avatar
Dean Moldovan committed
46
47
48
49
    assert msg(excinfo.value) == 'Tried to call pure virtual function "ExampleVirt::pure_virtual"'

    ex12p = ExtendedExampleVirt(10)
    with capture:
50
        assert m.runExampleVirt(ex12p, 20) == 32
Dean Moldovan's avatar
Dean Moldovan committed
51
52
    assert capture == """
        ExtendedExampleVirt::run(20), calling parent..
53
        Original implementation of ExampleVirt::run(state=11, value=21, str1=override1, str2=default2)
54
    """  # noqa: E501 line too long
Dean Moldovan's avatar
Dean Moldovan committed
55
    with capture:
56
        assert m.runExampleVirtBool(ex12p) is False
Dean Moldovan's avatar
Dean Moldovan committed
57
58
    assert capture == "ExtendedExampleVirt::run_bool()"
    with capture:
59
        m.runExampleVirtVirtual(ex12p)
Dean Moldovan's avatar
Dean Moldovan committed
60
61
    assert capture == "ExtendedExampleVirt::pure_virtual(): Hello world"

62
63
    ex12p2 = ExtendedExampleVirt2(15)
    with capture:
64
        assert m.runExampleVirt(ex12p2, 50) == 68
65
66
67
    assert capture == """
        ExtendedExampleVirt::run(50), calling parent..
        Original implementation of ExampleVirt::run(state=17, value=51, str1=override1, str2=override2)
68
    """  # noqa: E501 line too long
69

70
    cstats = ConstructorStats.get(m.ExampleVirt)
71
72
    assert cstats.alive() == 3
    del ex12, ex12p, ex12p2
Dean Moldovan's avatar
Dean Moldovan committed
73
    assert cstats.alive() == 0
74
    assert cstats.values() == ['10', '11', '17']
Dean Moldovan's avatar
Dean Moldovan committed
75
76
77
78
    assert cstats.copy_constructions == 0
    assert cstats.move_constructions >= 0


79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def test_alias_delay_initialization1(capture):
    """`A` only initializes its trampoline class when we inherit from it

    If we just create and use an A instance directly, the trampoline initialization is
    bypassed and we only initialize an A() instead (for performance reasons).
    """
    class B(m.A):
        def __init__(self):
            super(B, self).__init__()

        def f(self):
            print("In python f()")

    # C++ version
    with capture:
        a = m.A()
        m.call_f(a)
        del a
        pytest.gc_collect()
    assert capture == "A.f()"

    # Python version
    with capture:
        b = B()
        m.call_f(b)
        del b
        pytest.gc_collect()
    assert capture == """
        PyA.PyA()
        PyA.f()
        In python f()
        PyA.~PyA()
    """


def test_alias_delay_initialization2(capture):
    """`A2`, unlike the above, is configured to always initialize the alias

    While the extra initialization and extra class layer has small virtual dispatch
    performance penalty, it also allows us to do more things with the trampoline
    class such as defining local variables and performing construction/destruction.
    """
    class B2(m.A2):
        def __init__(self):
            super(B2, self).__init__()
Dean Moldovan's avatar
Dean Moldovan committed
124

125
126
127
128
129
130
131
132
133
        def f(self):
            print("In python B2.f()")

    # No python subclass version
    with capture:
        a2 = m.A2()
        m.call_f(a2)
        del a2
        pytest.gc_collect()
134
135
136
137
        a3 = m.A2(1)
        m.call_f(a3)
        del a3
        pytest.gc_collect()
138
139
140
141
142
    assert capture == """
        PyA2.PyA2()
        PyA2.f()
        A2.f()
        PyA2.~PyA2()
143
144
145
146
        PyA2.PyA2()
        PyA2.f()
        A2.f()
        PyA2.~PyA2()
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
    """

    # Python subclass version
    with capture:
        b2 = B2()
        m.call_f(b2)
        del b2
        pytest.gc_collect()
    assert capture == """
        PyA2.PyA2()
        PyA2.f()
        In python B2.f()
        PyA2.~PyA2()
    """


163
164
# PyPy: Reference count > 1 causes call with noncopyable instance
# to fail in ncv1.print_nc()
165
@pytest.mark.xfail("env.PYPY")
166
167
168
169
@pytest.mark.skipif(
    not hasattr(m, "NCVirt"),
    reason="NCVirt does not work on Intel/PGI/NVCC compilers"
)
170
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
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def test_move_support():
    class NCVirtExt(m.NCVirt):
        def get_noncopyable(self, a, b):
            # Constructs and returns a new instance:
            nc = m.NonCopyable(a * a, b * b)
            return nc

        def get_movable(self, a, b):
            # Return a referenced copy
            self.movable = m.Movable(a, b)
            return self.movable

    class NCVirtExt2(m.NCVirt):
        def get_noncopyable(self, a, b):
            # Keep a reference: this is going to throw an exception
            self.nc = m.NonCopyable(a, b)
            return self.nc

        def get_movable(self, a, b):
            # Return a new instance without storing it
            return m.Movable(a, b)

    ncv1 = NCVirtExt()
    assert ncv1.print_nc(2, 3) == "36"
    assert ncv1.print_movable(4, 5) == "9"
    ncv2 = NCVirtExt2()
    assert ncv2.print_movable(7, 7) == "14"
    # Don't check the exception message here because it differs under debug/non-debug mode
    with pytest.raises(RuntimeError):
        ncv2.print_nc(9, 9)

    nc_stats = ConstructorStats.get(m.NonCopyable)
    mv_stats = ConstructorStats.get(m.Movable)
    assert nc_stats.alive() == 1
    assert mv_stats.alive() == 1
    del ncv1, ncv2
    assert nc_stats.alive() == 0
    assert mv_stats.alive() == 0
    assert nc_stats.values() == ['4', '9', '9', '9']
    assert mv_stats.values() == ['4', '5', '7', '7']
    assert nc_stats.copy_constructions == 0
    assert mv_stats.copy_constructions == 1
    assert nc_stats.move_constructions >= 0
    assert mv_stats.move_constructions >= 0


def test_dispatch_issue(msg):
    """#159: virtual function dispatch has problems with similar-named functions"""
    class PyClass1(m.DispatchIssue):
        def dispatch(self):
            return "Yay.."

    class PyClass2(m.DispatchIssue):
        def dispatch(self):
            with pytest.raises(RuntimeError) as excinfo:
                super(PyClass2, self).dispatch()
            assert msg(excinfo.value) == 'Tried to call pure virtual function "Base::dispatch"'

            p = PyClass1()
            return m.dispatch_issue_go(p)

    b = PyClass2()
    assert m.dispatch_issue_go(b) == "Yay.."


def test_override_ref():
Unknown's avatar
Unknown committed
236
    """#392/397: overriding reference-returning functions"""
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
    o = m.OverrideTest("asdf")

    # Not allowed (see associated .cpp comment)
    # i = o.str_ref()
    # assert o.str_ref() == "asdf"
    assert o.str_value() == "asdf"

    assert o.A_value().value == "hi"
    a = o.A_ref()
    assert a.value == "hi"
    a.value = "bye"
    assert a.value == "bye"


def test_inherited_virtuals():
252
    class AR(m.A_Repeat):
Dean Moldovan's avatar
Dean Moldovan committed
253
254
255
        def unlucky_number(self):
            return 99

256
    class AT(m.A_Tpl):
Dean Moldovan's avatar
Dean Moldovan committed
257
258
259
        def unlucky_number(self):
            return 999

260
    obj = AR()
261
    assert obj.say_something(3) == "hihihi"
Dean Moldovan's avatar
Dean Moldovan committed
262
    assert obj.unlucky_number() == 99
263
    assert obj.say_everything() == "hi 99"
Dean Moldovan's avatar
Dean Moldovan committed
264

265
    obj = AT()
266
    assert obj.say_something(3) == "hihihi"
Dean Moldovan's avatar
Dean Moldovan committed
267
    assert obj.unlucky_number() == 999
268
    assert obj.say_everything() == "hi 999"
Dean Moldovan's avatar
Dean Moldovan committed
269

270
    for obj in [m.B_Repeat(), m.B_Tpl()]:
271
        assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
272
273
        assert obj.unlucky_number() == 13
        assert obj.lucky_number() == 7.0
274
        assert obj.say_everything() == "B says hi 1 times 13"
Dean Moldovan's avatar
Dean Moldovan committed
275

276
    for obj in [m.C_Repeat(), m.C_Tpl()]:
277
        assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
278
279
        assert obj.unlucky_number() == 4444
        assert obj.lucky_number() == 888.0
280
        assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
281

282
    class CR(m.C_Repeat):
Dean Moldovan's avatar
Dean Moldovan committed
283
        def lucky_number(self):
284
            return m.C_Repeat.lucky_number(self) + 1.25
Dean Moldovan's avatar
Dean Moldovan committed
285

286
    obj = CR()
287
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
288
289
    assert obj.unlucky_number() == 4444
    assert obj.lucky_number() == 889.25
290
    assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
291

292
    class CT(m.C_Tpl):
Dean Moldovan's avatar
Dean Moldovan committed
293
294
        pass

295
    obj = CT()
296
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
297
298
    assert obj.unlucky_number() == 4444
    assert obj.lucky_number() == 888.0
299
    assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
300

301
    class CCR(CR):
Dean Moldovan's avatar
Dean Moldovan committed
302
        def lucky_number(self):
303
            return CR.lucky_number(self) * 10
Dean Moldovan's avatar
Dean Moldovan committed
304

305
    obj = CCR()
306
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
307
308
    assert obj.unlucky_number() == 4444
    assert obj.lucky_number() == 8892.5
309
    assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
310

311
    class CCT(CT):
Dean Moldovan's avatar
Dean Moldovan committed
312
        def lucky_number(self):
313
            return CT.lucky_number(self) * 1000
Dean Moldovan's avatar
Dean Moldovan committed
314

315
    obj = CCT()
316
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
317
318
    assert obj.unlucky_number() == 4444
    assert obj.lucky_number() == 888000.0
319
    assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
320

321
    class DR(m.D_Repeat):
Dean Moldovan's avatar
Dean Moldovan committed
322
323
324
325
326
327
        def unlucky_number(self):
            return 123

        def lucky_number(self):
            return 42.0

328
    for obj in [m.D_Repeat(), m.D_Tpl()]:
329
        assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
330
331
        assert obj.unlucky_number() == 4444
        assert obj.lucky_number() == 888.0
332
        assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
333

334
    obj = DR()
335
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
336
337
    assert obj.unlucky_number() == 123
    assert obj.lucky_number() == 42.0
338
    assert obj.say_everything() == "B says hi 1 times 123"
Dean Moldovan's avatar
Dean Moldovan committed
339

340
    class DT(m.D_Tpl):
Dean Moldovan's avatar
Dean Moldovan committed
341
        def say_something(self, times):
342
            return "DT says:" + (' quack' * times)
Dean Moldovan's avatar
Dean Moldovan committed
343
344
345
346
347
348
349

        def unlucky_number(self):
            return 1234

        def lucky_number(self):
            return -4.25

350
351
    obj = DT()
    assert obj.say_something(3) == "DT says: quack quack quack"
Dean Moldovan's avatar
Dean Moldovan committed
352
353
    assert obj.unlucky_number() == 1234
    assert obj.lucky_number() == -4.25
354
    assert obj.say_everything() == "DT says: quack 1234"
355

356
    class DT2(DT):
357
        def say_something(self, times):
358
            return "DT2: " + ('QUACK' * times)
359
360
361
362

        def unlucky_number(self):
            return -3

363
    class BT(m.B_Tpl):
364
        def say_something(self, times):
365
366
            return "BT" * times

367
368
        def unlucky_number(self):
            return -7
369

370
371
372
        def lucky_number(self):
            return -1.375

373
374
    obj = BT()
    assert obj.say_something(3) == "BTBTBT"
375
376
    assert obj.unlucky_number() == -7
    assert obj.lucky_number() == -1.375
377
    assert obj.say_everything() == "BT -7"
378
379
380
381
382
383


def test_issue_1454():
    # Fix issue #1454 (crash when acquiring/releasing GIL on another thread in Python 2.7)
    m.test_gil()
    m.test_gil_from_thread()