μHAL (v2.8.17)
Part of the IPbus software repository
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
test_virtual_functions.py
Go to the documentation of this file.
1# -*- coding: utf-8 -*-
2import pytest
3
4import env # noqa: F401
5
6m = pytest.importorskip("pybind11_tests.virtual_functions")
7from pybind11_tests import ConstructorStats # noqa: E402
8
9
10def test_override(capture, msg):
11 class ExtendedExampleVirt(m.ExampleVirt):
12 def __init__(self, state):
13 super(ExtendedExampleVirt, self).__init__(state + 1)
14 self.data = "Hello world"
15
16 def run(self, value):
17 print("ExtendedExampleVirt::run(%i), calling parent.." % value)
18 return super(ExtendedExampleVirt, self).run(value + 1)
19
20 def run_bool(self):
21 print("ExtendedExampleVirt::run_bool()")
22 return False
23
24 def get_string1(self):
25 return "override1"
26
27 def pure_virtual(self):
28 print("ExtendedExampleVirt::pure_virtual(): %s" % self.data)
29
30 class ExtendedExampleVirt2(ExtendedExampleVirt):
31 def __init__(self, state):
32 super(ExtendedExampleVirt2, self).__init__(state + 1)
33
34 def get_string2(self):
35 return "override2"
36
37 ex12 = m.ExampleVirt(10)
38 with capture:
39 assert m.runExampleVirt(ex12, 20) == 30
40 assert (
41 capture
42 == """
43 Original implementation of ExampleVirt::run(state=10, value=20, str1=default1, str2=default2)
44 """ # noqa: E501 line too long
45 )
46
47 with pytest.raises(RuntimeError) as excinfo:
48 m.runExampleVirtVirtual(ex12)
49 assert (
50 msg(excinfo.value)
51 == 'Tried to call pure virtual function "ExampleVirt::pure_virtual"'
52 )
53
54 ex12p = ExtendedExampleVirt(10)
55 with capture:
56 assert m.runExampleVirt(ex12p, 20) == 32
57 assert (
58 capture
59 == """
60 ExtendedExampleVirt::run(20), calling parent..
61 Original implementation of ExampleVirt::run(state=11, value=21, str1=override1, str2=default2)
62 """ # noqa: E501 line too long
63 )
64 with capture:
65 assert m.runExampleVirtBool(ex12p) is False
66 assert capture == "ExtendedExampleVirt::run_bool()"
67 with capture:
68 m.runExampleVirtVirtual(ex12p)
69 assert capture == "ExtendedExampleVirt::pure_virtual(): Hello world"
70
71 ex12p2 = ExtendedExampleVirt2(15)
72 with capture:
73 assert m.runExampleVirt(ex12p2, 50) == 68
74 assert (
75 capture
76 == """
77 ExtendedExampleVirt::run(50), calling parent..
78 Original implementation of ExampleVirt::run(state=17, value=51, str1=override1, str2=override2)
79 """ # noqa: E501 line too long
80 )
81
82 cstats = ConstructorStats.get(m.ExampleVirt)
83 assert cstats.alive() == 3
84 del ex12, ex12p, ex12p2
85 assert cstats.alive() == 0
86 assert cstats.values() == ["10", "11", "17"]
87 assert cstats.copy_constructions == 0
88 assert cstats.move_constructions >= 0
89
90
92 """`A` only initializes its trampoline class when we inherit from it
93
94 If we just create and use an A instance directly, the trampoline initialization is
95 bypassed and we only initialize an A() instead (for performance reasons).
96 """
97
98 class B(m.A):
99 def __init__(self):
100 super(B, self).__init__()
101
102 def f(self):
103 print("In python f()")
104
105 # C++ version
106 with capture:
107 a = m.A()
108 m.call_f(a)
109 del a
110 pytest.gc_collect()
111 assert capture == "A.f()"
112
113 # Python version
114 with capture:
115 b = B()
116 m.call_f(b)
117 del b
118 pytest.gc_collect()
119 assert (
120 capture
121 == """
122 PyA.PyA()
123 PyA.f()
124 In python f()
125 PyA.~PyA()
126 """
127 )
128
129
131 """`A2`, unlike the above, is configured to always initialize the alias
132
133 While the extra initialization and extra class layer has small virtual dispatch
134 performance penalty, it also allows us to do more things with the trampoline
135 class such as defining local variables and performing construction/destruction.
136 """
137
138 class B2(m.A2):
139 def __init__(self):
140 super(B2, self).__init__()
141
142 def f(self):
143 print("In python B2.f()")
144
145 # No python subclass version
146 with capture:
147 a2 = m.A2()
148 m.call_f(a2)
149 del a2
150 pytest.gc_collect()
151 a3 = m.A2(1)
152 m.call_f(a3)
153 del a3
154 pytest.gc_collect()
155 assert (
156 capture
157 == """
158 PyA2.PyA2()
159 PyA2.f()
160 A2.f()
161 PyA2.~PyA2()
162 PyA2.PyA2()
163 PyA2.f()
164 A2.f()
165 PyA2.~PyA2()
166 """
167 )
168
169 # Python subclass version
170 with capture:
171 b2 = B2()
172 m.call_f(b2)
173 del b2
174 pytest.gc_collect()
175 assert (
176 capture
177 == """
178 PyA2.PyA2()
179 PyA2.f()
180 In python B2.f()
181 PyA2.~PyA2()
182 """
183 )
184
185
186# PyPy: Reference count > 1 causes call with noncopyable instance
187# to fail in ncv1.print_nc()
188@pytest.mark.xfail("env.PYPY")
189@pytest.mark.skipif(
190 not hasattr(m, "NCVirt"), reason="NCVirt does not work on Intel/PGI/NVCC compilers"
191)
193 class NCVirtExt(m.NCVirt):
194 def get_noncopyable(self, a, b):
195 # Constructs and returns a new instance:
196 nc = m.NonCopyable(a * a, b * b)
197 return nc
198
199 def get_movable(self, a, b):
200 # Return a referenced copy
201 self.movable = m.Movable(a, b)
202 return self.movable
203
204 class NCVirtExt2(m.NCVirt):
205 def get_noncopyable(self, a, b):
206 # Keep a reference: this is going to throw an exception
207 self.nc = m.NonCopyable(a, b)
208 return self.nc
209
210 def get_movable(self, a, b):
211 # Return a new instance without storing it
212 return m.Movable(a, b)
213
214 ncv1 = NCVirtExt()
215 assert ncv1.print_nc(2, 3) == "36"
216 assert ncv1.print_movable(4, 5) == "9"
217 ncv2 = NCVirtExt2()
218 assert ncv2.print_movable(7, 7) == "14"
219 # Don't check the exception message here because it differs under debug/non-debug mode
220 with pytest.raises(RuntimeError):
221 ncv2.print_nc(9, 9)
222
223 nc_stats = ConstructorStats.get(m.NonCopyable)
224 mv_stats = ConstructorStats.get(m.Movable)
225 assert nc_stats.alive() == 1
226 assert mv_stats.alive() == 1
227 del ncv1, ncv2
228 assert nc_stats.alive() == 0
229 assert mv_stats.alive() == 0
230 assert nc_stats.values() == ["4", "9", "9", "9"]
231 assert mv_stats.values() == ["4", "5", "7", "7"]
232 assert nc_stats.copy_constructions == 0
233 assert mv_stats.copy_constructions == 1
234 assert nc_stats.move_constructions >= 0
235 assert mv_stats.move_constructions >= 0
236
237
239 """#159: virtual function dispatch has problems with similar-named functions"""
240
241 class PyClass1(m.DispatchIssue):
242 def dispatch(self):
243 return "Yay.."
244
245 class PyClass2(m.DispatchIssue):
246 def dispatch(self):
247 with pytest.raises(RuntimeError) as excinfo:
248 super(PyClass2, self).dispatch()
249 assert (
250 msg(excinfo.value)
251 == 'Tried to call pure virtual function "Base::dispatch"'
252 )
253
254 return m.dispatch_issue_go(PyClass1())
255
256 b = PyClass2()
257 assert m.dispatch_issue_go(b) == "Yay.."
258
259
261 """#3357: Recursive dispatch fails to find python function override"""
262
263 class Data(m.Data):
264 def __init__(self, value):
265 super(Data, self).__init__()
266 self.value = value
267
268 class Adder(m.Adder):
269 def __call__(self, first, second, visitor):
270 # lambda is a workaround, which adds extra frame to the
271 # current CPython thread. Removing lambda reveals the bug
272 # [https://github.com/pybind/pybind11/issues/3357]
273 (lambda: visitor(Data(first.value + second.value)))()
274
275 class StoreResultVisitor:
276 def __init__(self):
277 self.result = None
278
279 def __call__(self, data):
280 self.result = data.value
281
282 store = StoreResultVisitor()
283
284 m.add2(Data(1), Data(2), Adder(), store)
285 assert store.result == 3
286
287 # without lambda in Adder class, this function fails with
288 # RuntimeError: Tried to call pure virtual function "AdderBase::__call__"
289 m.add3(Data(1), Data(2), Data(3), Adder(), store)
290 assert store.result == 6
291
292
294 """#392/397: overriding reference-returning functions"""
295 o = m.OverrideTest("asdf")
296
297 # Not allowed (see associated .cpp comment)
298 # i = o.str_ref()
299 # assert o.str_ref() == "asdf"
300 assert o.str_value() == "asdf"
301
302 assert o.A_value().value == "hi"
303 a = o.A_ref()
304 assert a.value == "hi"
305 a.value = "bye"
306 assert a.value == "bye"
307
308
310 class AR(m.A_Repeat):
311 def unlucky_number(self):
312 return 99
313
314 class AT(m.A_Tpl):
315 def unlucky_number(self):
316 return 999
317
318 obj = AR()
319 assert obj.say_something(3) == "hihihi"
320 assert obj.unlucky_number() == 99
321 assert obj.say_everything() == "hi 99"
322
323 obj = AT()
324 assert obj.say_something(3) == "hihihi"
325 assert obj.unlucky_number() == 999
326 assert obj.say_everything() == "hi 999"
327
328 for obj in [m.B_Repeat(), m.B_Tpl()]:
329 assert obj.say_something(3) == "B says hi 3 times"
330 assert obj.unlucky_number() == 13
331 assert obj.lucky_number() == 7.0
332 assert obj.say_everything() == "B says hi 1 times 13"
333
334 for obj in [m.C_Repeat(), m.C_Tpl()]:
335 assert obj.say_something(3) == "B says hi 3 times"
336 assert obj.unlucky_number() == 4444
337 assert obj.lucky_number() == 888.0
338 assert obj.say_everything() == "B says hi 1 times 4444"
339
340 class CR(m.C_Repeat):
341 def lucky_number(self):
342 return m.C_Repeat.lucky_number(self) + 1.25
343
344 obj = CR()
345 assert obj.say_something(3) == "B says hi 3 times"
346 assert obj.unlucky_number() == 4444
347 assert obj.lucky_number() == 889.25
348 assert obj.say_everything() == "B says hi 1 times 4444"
349
350 class CT(m.C_Tpl):
351 pass
352
353 obj = CT()
354 assert obj.say_something(3) == "B says hi 3 times"
355 assert obj.unlucky_number() == 4444
356 assert obj.lucky_number() == 888.0
357 assert obj.say_everything() == "B says hi 1 times 4444"
358
359 class CCR(CR):
360 def lucky_number(self):
361 return CR.lucky_number(self) * 10
362
363 obj = CCR()
364 assert obj.say_something(3) == "B says hi 3 times"
365 assert obj.unlucky_number() == 4444
366 assert obj.lucky_number() == 8892.5
367 assert obj.say_everything() == "B says hi 1 times 4444"
368
369 class CCT(CT):
370 def lucky_number(self):
371 return CT.lucky_number(self) * 1000
372
373 obj = CCT()
374 assert obj.say_something(3) == "B says hi 3 times"
375 assert obj.unlucky_number() == 4444
376 assert obj.lucky_number() == 888000.0
377 assert obj.say_everything() == "B says hi 1 times 4444"
378
379 class DR(m.D_Repeat):
380 def unlucky_number(self):
381 return 123
382
383 def lucky_number(self):
384 return 42.0
385
386 for obj in [m.D_Repeat(), m.D_Tpl()]:
387 assert obj.say_something(3) == "B says hi 3 times"
388 assert obj.unlucky_number() == 4444
389 assert obj.lucky_number() == 888.0
390 assert obj.say_everything() == "B says hi 1 times 4444"
391
392 obj = DR()
393 assert obj.say_something(3) == "B says hi 3 times"
394 assert obj.unlucky_number() == 123
395 assert obj.lucky_number() == 42.0
396 assert obj.say_everything() == "B says hi 1 times 123"
397
398 class DT(m.D_Tpl):
399 def say_something(self, times):
400 return "DT says:" + (" quack" * times)
401
402 def unlucky_number(self):
403 return 1234
404
405 def lucky_number(self):
406 return -4.25
407
408 obj = DT()
409 assert obj.say_something(3) == "DT says: quack quack quack"
410 assert obj.unlucky_number() == 1234
411 assert obj.lucky_number() == -4.25
412 assert obj.say_everything() == "DT says: quack 1234"
413
414 class DT2(DT):
415 def say_something(self, times):
416 return "DT2: " + ("QUACK" * times)
417
418 def unlucky_number(self):
419 return -3
420
421 class BT(m.B_Tpl):
422 def say_something(self, times):
423 return "BT" * times
424
425 def unlucky_number(self):
426 return -7
427
428 def lucky_number(self):
429 return -1.375
430
431 obj = BT()
432 assert obj.say_something(3) == "BTBTBT"
433 assert obj.unlucky_number() == -7
434 assert obj.lucky_number() == -1.375
435 assert obj.say_everything() == "BT -7"
436
437
439 # Fix issue #1454 (crash when acquiring/releasing GIL on another thread in Python 2.7)
440 m.test_gil()
441 m.test_gil_from_thread()
442
443
445 def func():
446 class Test(m.test_override_cache_helper):
447 def func(self):
448 return 42
449
450 return Test()
451
452 def func2():
453 class Test(m.test_override_cache_helper):
454 pass
455
456 return Test()
457
458 for _ in range(1500):
459 assert m.test_override_cache(func()) == 42
460 assert m.test_override_cache(func2()) == 0
static ConstructorStats & get(std::type_index type)
bool hasattr(handle obj, handle name)
Definition: pytypes.h:517
def test_override(capture, msg)
def test_alias_delay_initialization1(capture)
def test_alias_delay_initialization2(capture)