test_virtual_functions.py 12.6 KB
Newer Older
Dean Moldovan's avatar
Dean Moldovan committed
1
import pytest
2

3
4
import env  # noqa: F401

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


def test_override(capture, msg):
10
    class ExtendedExampleVirt(m.ExampleVirt):
Dean Moldovan's avatar
Dean Moldovan committed
11
        def __init__(self, state):
12
            super().__init__(state + 1)
Dean Moldovan's avatar
Dean Moldovan committed
13
14
15
            self.data = "Hello world"

        def run(self, value):
16
            print("ExtendedExampleVirt::run(%i), calling parent.." % value)
17
            return super().run(value + 1)
Dean Moldovan's avatar
Dean Moldovan committed
18
19

        def run_bool(self):
20
            print("ExtendedExampleVirt::run_bool()")
Dean Moldovan's avatar
Dean Moldovan committed
21
22
            return False

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

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

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

        def get_string2(self):
            return "override2"

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

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

    ex12p = ExtendedExampleVirt(10)
    with capture:
55
        assert m.runExampleVirt(ex12p, 20) == 32
56
57
58
    assert (
        capture
        == """
Dean Moldovan's avatar
Dean Moldovan committed
59
        ExtendedExampleVirt::run(20), calling parent..
60
        Original implementation of ExampleVirt::run(state=11, value=21, str1=override1, str2=default2)
61
    """
62
    )
Dean Moldovan's avatar
Dean Moldovan committed
63
    with capture:
64
        assert m.runExampleVirtBool(ex12p) is False
Dean Moldovan's avatar
Dean Moldovan committed
65
66
    assert capture == "ExtendedExampleVirt::run_bool()"
    with capture:
67
        m.runExampleVirtVirtual(ex12p)
Dean Moldovan's avatar
Dean Moldovan committed
68
69
    assert capture == "ExtendedExampleVirt::pure_virtual(): Hello world"

70
71
    ex12p2 = ExtendedExampleVirt2(15)
    with capture:
72
        assert m.runExampleVirt(ex12p2, 50) == 68
73
74
75
    assert (
        capture
        == """
76
77
        ExtendedExampleVirt::run(50), calling parent..
        Original implementation of ExampleVirt::run(state=17, value=51, str1=override1, str2=override2)
78
    """
79
    )
80

81
    cstats = ConstructorStats.get(m.ExampleVirt)
82
83
    assert cstats.alive() == 3
    del ex12, ex12p, ex12p2
Dean Moldovan's avatar
Dean Moldovan committed
84
    assert cstats.alive() == 0
85
    assert cstats.values() == ["10", "11", "17"]
Dean Moldovan's avatar
Dean Moldovan committed
86
87
88
89
    assert cstats.copy_constructions == 0
    assert cstats.move_constructions >= 0


90
91
92
93
94
95
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).
    """
96

97
98
    class B(m.A):
        def __init__(self):
99
            super().__init__()
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

        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()
118
119
120
    assert (
        capture
        == """
121
122
123
124
125
        PyA.PyA()
        PyA.f()
        In python f()
        PyA.~PyA()
    """
126
    )
127
128
129
130
131
132
133
134
135


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.
    """
136

137
138
    class B2(m.A2):
        def __init__(self):
139
            super().__init__()
Dean Moldovan's avatar
Dean Moldovan committed
140

141
142
143
144
145
146
147
148
149
        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()
150
151
152
153
        a3 = m.A2(1)
        m.call_f(a3)
        del a3
        pytest.gc_collect()
154
155
156
    assert (
        capture
        == """
157
158
159
160
        PyA2.PyA2()
        PyA2.f()
        A2.f()
        PyA2.~PyA2()
161
162
163
164
        PyA2.PyA2()
        PyA2.f()
        A2.f()
        PyA2.~PyA2()
165
    """
166
    )
167
168
169
170
171
172
173

    # Python subclass version
    with capture:
        b2 = B2()
        m.call_f(b2)
        del b2
        pytest.gc_collect()
174
175
176
    assert (
        capture
        == """
177
178
179
180
181
        PyA2.PyA2()
        PyA2.f()
        In python B2.f()
        PyA2.~PyA2()
    """
182
    )
183
184


185
186
# PyPy: Reference count > 1 causes call with noncopyable instance
# to fail in ncv1.print_nc()
187
@pytest.mark.xfail("env.PYPY")
188
@pytest.mark.skipif(
189
    not hasattr(m, "NCVirt"), reason="NCVirt does not work on Intel/PGI/NVCC compilers"
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
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
229
230
    assert nc_stats.values() == ["4", "9", "9", "9"]
    assert mv_stats.values() == ["4", "5", "7", "7"]
231
232
233
234
235
236
237
238
    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"""
239

240
241
242
243
244
245
246
    class PyClass1(m.DispatchIssue):
        def dispatch(self):
            return "Yay.."

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

253
            return m.dispatch_issue_go(PyClass1())
254
255
256
257
258

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


259
260
261
262
263
def test_recursive_dispatch_issue(msg):
    """#3357: Recursive dispatch fails to find python function override"""

    class Data(m.Data):
        def __init__(self, value):
264
            super().__init__()
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
            self.value = value

    class Adder(m.Adder):
        def __call__(self, first, second, visitor):
            # lambda is a workaround, which adds extra frame to the
            # current CPython thread. Removing lambda reveals the bug
            # [https://github.com/pybind/pybind11/issues/3357]
            (lambda: visitor(Data(first.value + second.value)))()

    class StoreResultVisitor:
        def __init__(self):
            self.result = None

        def __call__(self, data):
            self.result = data.value

    store = StoreResultVisitor()

    m.add2(Data(1), Data(2), Adder(), store)
    assert store.result == 3

    # without lambda in Adder class, this function fails with
    # RuntimeError: Tried to call pure virtual function "AdderBase::__call__"
    m.add3(Data(1), Data(2), Data(3), Adder(), store)
    assert store.result == 6


292
def test_override_ref():
Unknown's avatar
Unknown committed
293
    """#392/397: overriding reference-returning functions"""
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
    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():
309
    class AR(m.A_Repeat):
Dean Moldovan's avatar
Dean Moldovan committed
310
311
312
        def unlucky_number(self):
            return 99

313
    class AT(m.A_Tpl):
Dean Moldovan's avatar
Dean Moldovan committed
314
315
316
        def unlucky_number(self):
            return 999

317
    obj = AR()
318
    assert obj.say_something(3) == "hihihi"
Dean Moldovan's avatar
Dean Moldovan committed
319
    assert obj.unlucky_number() == 99
320
    assert obj.say_everything() == "hi 99"
Dean Moldovan's avatar
Dean Moldovan committed
321

322
    obj = AT()
323
    assert obj.say_something(3) == "hihihi"
Dean Moldovan's avatar
Dean Moldovan committed
324
    assert obj.unlucky_number() == 999
325
    assert obj.say_everything() == "hi 999"
Dean Moldovan's avatar
Dean Moldovan committed
326

327
    for obj in [m.B_Repeat(), m.B_Tpl()]:
328
        assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
329
330
        assert obj.unlucky_number() == 13
        assert obj.lucky_number() == 7.0
331
        assert obj.say_everything() == "B says hi 1 times 13"
Dean Moldovan's avatar
Dean Moldovan committed
332

333
    for obj in [m.C_Repeat(), m.C_Tpl()]:
334
        assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
335
336
        assert obj.unlucky_number() == 4444
        assert obj.lucky_number() == 888.0
337
        assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
338

339
    class CR(m.C_Repeat):
Dean Moldovan's avatar
Dean Moldovan committed
340
        def lucky_number(self):
341
            return m.C_Repeat.lucky_number(self) + 1.25
Dean Moldovan's avatar
Dean Moldovan committed
342

343
    obj = CR()
344
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
345
346
    assert obj.unlucky_number() == 4444
    assert obj.lucky_number() == 889.25
347
    assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
348

349
    class CT(m.C_Tpl):
Dean Moldovan's avatar
Dean Moldovan committed
350
351
        pass

352
    obj = CT()
353
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
354
355
    assert obj.unlucky_number() == 4444
    assert obj.lucky_number() == 888.0
356
    assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
357

358
    class CCR(CR):
Dean Moldovan's avatar
Dean Moldovan committed
359
        def lucky_number(self):
360
            return CR.lucky_number(self) * 10
Dean Moldovan's avatar
Dean Moldovan committed
361

362
    obj = CCR()
363
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
364
365
    assert obj.unlucky_number() == 4444
    assert obj.lucky_number() == 8892.5
366
    assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
367

368
    class CCT(CT):
Dean Moldovan's avatar
Dean Moldovan committed
369
        def lucky_number(self):
370
            return CT.lucky_number(self) * 1000
Dean Moldovan's avatar
Dean Moldovan committed
371

372
    obj = CCT()
373
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
374
375
    assert obj.unlucky_number() == 4444
    assert obj.lucky_number() == 888000.0
376
    assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
377

378
    class DR(m.D_Repeat):
Dean Moldovan's avatar
Dean Moldovan committed
379
380
381
382
383
384
        def unlucky_number(self):
            return 123

        def lucky_number(self):
            return 42.0

385
    for obj in [m.D_Repeat(), m.D_Tpl()]:
386
        assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
387
388
        assert obj.unlucky_number() == 4444
        assert obj.lucky_number() == 888.0
389
        assert obj.say_everything() == "B says hi 1 times 4444"
Dean Moldovan's avatar
Dean Moldovan committed
390

391
    obj = DR()
392
    assert obj.say_something(3) == "B says hi 3 times"
Dean Moldovan's avatar
Dean Moldovan committed
393
394
    assert obj.unlucky_number() == 123
    assert obj.lucky_number() == 42.0
395
    assert obj.say_everything() == "B says hi 1 times 123"
Dean Moldovan's avatar
Dean Moldovan committed
396

397
    class DT(m.D_Tpl):
Dean Moldovan's avatar
Dean Moldovan committed
398
        def say_something(self, times):
399
            return "DT says:" + (" quack" * times)
Dean Moldovan's avatar
Dean Moldovan committed
400
401
402
403
404
405
406

        def unlucky_number(self):
            return 1234

        def lucky_number(self):
            return -4.25

407
408
    obj = DT()
    assert obj.say_something(3) == "DT says: quack quack quack"
Dean Moldovan's avatar
Dean Moldovan committed
409
410
    assert obj.unlucky_number() == 1234
    assert obj.lucky_number() == -4.25
411
    assert obj.say_everything() == "DT says: quack 1234"
412

413
    class DT2(DT):
414
        def say_something(self, times):
415
            return "DT2: " + ("QUACK" * times)
416
417
418
419

        def unlucky_number(self):
            return -3

420
    class BT(m.B_Tpl):
421
        def say_something(self, times):
422
423
            return "BT" * times

424
425
        def unlucky_number(self):
            return -7
426

427
428
429
        def lucky_number(self):
            return -1.375

430
431
    obj = BT()
    assert obj.say_something(3) == "BTBTBT"
432
433
    assert obj.unlucky_number() == -7
    assert obj.lucky_number() == -1.375
434
    assert obj.say_everything() == "BT -7"
435
436
437
438
439
440


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()
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459


def test_python_override():
    def func():
        class Test(m.test_override_cache_helper):
            def func(self):
                return 42

        return Test()

    def func2():
        class Test(m.test_override_cache_helper):
            pass

        return Test()

    for _ in range(1500):
        assert m.test_override_cache(func()) == 42
        assert m.test_override_cache(func2()) == 0