test_virtual_functions.py (12037:d28054ac6ec9) test_virtual_functions.py (12391:ceeca8b41e4b)
1import pytest
1import pytest
2import pybind11_tests
2
3from pybind11_tests import virtual_functions as m
3from pybind11_tests import ConstructorStats
4
5
6def test_override(capture, msg):
4from pybind11_tests import ConstructorStats
5
6
7def test_override(capture, msg):
7 from pybind11_tests import (ExampleVirt, runExampleVirt, runExampleVirtVirtual,
8 runExampleVirtBool)
9
10 class ExtendedExampleVirt(ExampleVirt):
8 class ExtendedExampleVirt(m.ExampleVirt):
11 def __init__(self, state):
12 super(ExtendedExampleVirt, self).__init__(state + 1)
13 self.data = "Hello world"
14
15 def run(self, value):
16 print('ExtendedExampleVirt::run(%i), calling parent..' % value)
17 return super(ExtendedExampleVirt, self).run(value + 1)
18

--- 9 unchanged lines hidden (view full) ---

28
29 class ExtendedExampleVirt2(ExtendedExampleVirt):
30 def __init__(self, state):
31 super(ExtendedExampleVirt2, self).__init__(state + 1)
32
33 def get_string2(self):
34 return "override2"
35
9 def __init__(self, state):
10 super(ExtendedExampleVirt, self).__init__(state + 1)
11 self.data = "Hello world"
12
13 def run(self, value):
14 print('ExtendedExampleVirt::run(%i), calling parent..' % value)
15 return super(ExtendedExampleVirt, self).run(value + 1)
16

--- 9 unchanged lines hidden (view full) ---

26
27 class ExtendedExampleVirt2(ExtendedExampleVirt):
28 def __init__(self, state):
29 super(ExtendedExampleVirt2, self).__init__(state + 1)
30
31 def get_string2(self):
32 return "override2"
33
36 ex12 = ExampleVirt(10)
34 ex12 = m.ExampleVirt(10)
37 with capture:
35 with capture:
38 assert runExampleVirt(ex12, 20) == 30
36 assert m.runExampleVirt(ex12, 20) == 30
39 assert capture == """
40 Original implementation of ExampleVirt::run(state=10, value=20, str1=default1, str2=default2)
41 """ # noqa: E501 line too long
42
43 with pytest.raises(RuntimeError) as excinfo:
37 assert capture == """
38 Original implementation of ExampleVirt::run(state=10, value=20, str1=default1, str2=default2)
39 """ # noqa: E501 line too long
40
41 with pytest.raises(RuntimeError) as excinfo:
44 runExampleVirtVirtual(ex12)
42 m.runExampleVirtVirtual(ex12)
45 assert msg(excinfo.value) == 'Tried to call pure virtual function "ExampleVirt::pure_virtual"'
46
47 ex12p = ExtendedExampleVirt(10)
48 with capture:
43 assert msg(excinfo.value) == 'Tried to call pure virtual function "ExampleVirt::pure_virtual"'
44
45 ex12p = ExtendedExampleVirt(10)
46 with capture:
49 assert runExampleVirt(ex12p, 20) == 32
47 assert m.runExampleVirt(ex12p, 20) == 32
50 assert capture == """
51 ExtendedExampleVirt::run(20), calling parent..
52 Original implementation of ExampleVirt::run(state=11, value=21, str1=override1, str2=default2)
53 """ # noqa: E501 line too long
54 with capture:
48 assert capture == """
49 ExtendedExampleVirt::run(20), calling parent..
50 Original implementation of ExampleVirt::run(state=11, value=21, str1=override1, str2=default2)
51 """ # noqa: E501 line too long
52 with capture:
55 assert runExampleVirtBool(ex12p) is False
53 assert m.runExampleVirtBool(ex12p) is False
56 assert capture == "ExtendedExampleVirt::run_bool()"
57 with capture:
54 assert capture == "ExtendedExampleVirt::run_bool()"
55 with capture:
58 runExampleVirtVirtual(ex12p)
56 m.runExampleVirtVirtual(ex12p)
59 assert capture == "ExtendedExampleVirt::pure_virtual(): Hello world"
60
61 ex12p2 = ExtendedExampleVirt2(15)
62 with capture:
57 assert capture == "ExtendedExampleVirt::pure_virtual(): Hello world"
58
59 ex12p2 = ExtendedExampleVirt2(15)
60 with capture:
63 assert runExampleVirt(ex12p2, 50) == 68
61 assert m.runExampleVirt(ex12p2, 50) == 68
64 assert capture == """
65 ExtendedExampleVirt::run(50), calling parent..
66 Original implementation of ExampleVirt::run(state=17, value=51, str1=override1, str2=override2)
67 """ # noqa: E501 line too long
68
62 assert capture == """
63 ExtendedExampleVirt::run(50), calling parent..
64 Original implementation of ExampleVirt::run(state=17, value=51, str1=override1, str2=override2)
65 """ # noqa: E501 line too long
66
69 cstats = ConstructorStats.get(ExampleVirt)
67 cstats = ConstructorStats.get(m.ExampleVirt)
70 assert cstats.alive() == 3
71 del ex12, ex12p, ex12p2
72 assert cstats.alive() == 0
73 assert cstats.values() == ['10', '11', '17']
74 assert cstats.copy_constructions == 0
75 assert cstats.move_constructions >= 0
76
77
68 assert cstats.alive() == 3
69 del ex12, ex12p, ex12p2
70 assert cstats.alive() == 0
71 assert cstats.values() == ['10', '11', '17']
72 assert cstats.copy_constructions == 0
73 assert cstats.move_constructions >= 0
74
75
78def test_inheriting_repeat():
79 from pybind11_tests import A_Repeat, B_Repeat, C_Repeat, D_Repeat, A_Tpl, B_Tpl, C_Tpl, D_Tpl
76def test_alias_delay_initialization1(capture):
77 """`A` only initializes its trampoline class when we inherit from it
80
78
81 class AR(A_Repeat):
79 If we just create and use an A instance directly, the trampoline initialization is
80 bypassed and we only initialize an A() instead (for performance reasons).
81 """
82 class B(m.A):
83 def __init__(self):
84 super(B, self).__init__()
85
86 def f(self):
87 print("In python f()")
88
89 # C++ version
90 with capture:
91 a = m.A()
92 m.call_f(a)
93 del a
94 pytest.gc_collect()
95 assert capture == "A.f()"
96
97 # Python version
98 with capture:
99 b = B()
100 m.call_f(b)
101 del b
102 pytest.gc_collect()
103 assert capture == """
104 PyA.PyA()
105 PyA.f()
106 In python f()
107 PyA.~PyA()
108 """
109
110
111def test_alias_delay_initialization2(capture):
112 """`A2`, unlike the above, is configured to always initialize the alias
113
114 While the extra initialization and extra class layer has small virtual dispatch
115 performance penalty, it also allows us to do more things with the trampoline
116 class such as defining local variables and performing construction/destruction.
117 """
118 class B2(m.A2):
119 def __init__(self):
120 super(B2, self).__init__()
121
122 def f(self):
123 print("In python B2.f()")
124
125 # No python subclass version
126 with capture:
127 a2 = m.A2()
128 m.call_f(a2)
129 del a2
130 pytest.gc_collect()
131 a3 = m.A2(1)
132 m.call_f(a3)
133 del a3
134 pytest.gc_collect()
135 assert capture == """
136 PyA2.PyA2()
137 PyA2.f()
138 A2.f()
139 PyA2.~PyA2()
140 PyA2.PyA2()
141 PyA2.f()
142 A2.f()
143 PyA2.~PyA2()
144 """
145
146 # Python subclass version
147 with capture:
148 b2 = B2()
149 m.call_f(b2)
150 del b2
151 pytest.gc_collect()
152 assert capture == """
153 PyA2.PyA2()
154 PyA2.f()
155 In python B2.f()
156 PyA2.~PyA2()
157 """
158
159
160# PyPy: Reference count > 1 causes call with noncopyable instance
161# to fail in ncv1.print_nc()
162@pytest.unsupported_on_pypy
163@pytest.mark.skipif(not hasattr(m, "NCVirt"), reason="NCVirt test broken on ICPC")
164def test_move_support():
165 class NCVirtExt(m.NCVirt):
166 def get_noncopyable(self, a, b):
167 # Constructs and returns a new instance:
168 nc = m.NonCopyable(a * a, b * b)
169 return nc
170
171 def get_movable(self, a, b):
172 # Return a referenced copy
173 self.movable = m.Movable(a, b)
174 return self.movable
175
176 class NCVirtExt2(m.NCVirt):
177 def get_noncopyable(self, a, b):
178 # Keep a reference: this is going to throw an exception
179 self.nc = m.NonCopyable(a, b)
180 return self.nc
181
182 def get_movable(self, a, b):
183 # Return a new instance without storing it
184 return m.Movable(a, b)
185
186 ncv1 = NCVirtExt()
187 assert ncv1.print_nc(2, 3) == "36"
188 assert ncv1.print_movable(4, 5) == "9"
189 ncv2 = NCVirtExt2()
190 assert ncv2.print_movable(7, 7) == "14"
191 # Don't check the exception message here because it differs under debug/non-debug mode
192 with pytest.raises(RuntimeError):
193 ncv2.print_nc(9, 9)
194
195 nc_stats = ConstructorStats.get(m.NonCopyable)
196 mv_stats = ConstructorStats.get(m.Movable)
197 assert nc_stats.alive() == 1
198 assert mv_stats.alive() == 1
199 del ncv1, ncv2
200 assert nc_stats.alive() == 0
201 assert mv_stats.alive() == 0
202 assert nc_stats.values() == ['4', '9', '9', '9']
203 assert mv_stats.values() == ['4', '5', '7', '7']
204 assert nc_stats.copy_constructions == 0
205 assert mv_stats.copy_constructions == 1
206 assert nc_stats.move_constructions >= 0
207 assert mv_stats.move_constructions >= 0
208
209
210def test_dispatch_issue(msg):
211 """#159: virtual function dispatch has problems with similar-named functions"""
212 class PyClass1(m.DispatchIssue):
213 def dispatch(self):
214 return "Yay.."
215
216 class PyClass2(m.DispatchIssue):
217 def dispatch(self):
218 with pytest.raises(RuntimeError) as excinfo:
219 super(PyClass2, self).dispatch()
220 assert msg(excinfo.value) == 'Tried to call pure virtual function "Base::dispatch"'
221
222 p = PyClass1()
223 return m.dispatch_issue_go(p)
224
225 b = PyClass2()
226 assert m.dispatch_issue_go(b) == "Yay.."
227
228
229def test_override_ref():
230 """#392/397: overridding reference-returning functions"""
231 o = m.OverrideTest("asdf")
232
233 # Not allowed (see associated .cpp comment)
234 # i = o.str_ref()
235 # assert o.str_ref() == "asdf"
236 assert o.str_value() == "asdf"
237
238 assert o.A_value().value == "hi"
239 a = o.A_ref()
240 assert a.value == "hi"
241 a.value = "bye"
242 assert a.value == "bye"
243
244
245def test_inherited_virtuals():
246 class AR(m.A_Repeat):
82 def unlucky_number(self):
83 return 99
84
247 def unlucky_number(self):
248 return 99
249
85 class AT(A_Tpl):
250 class AT(m.A_Tpl):
86 def unlucky_number(self):
87 return 999
88
89 obj = AR()
90 assert obj.say_something(3) == "hihihi"
91 assert obj.unlucky_number() == 99
92 assert obj.say_everything() == "hi 99"
93
94 obj = AT()
95 assert obj.say_something(3) == "hihihi"
96 assert obj.unlucky_number() == 999
97 assert obj.say_everything() == "hi 999"
98
251 def unlucky_number(self):
252 return 999
253
254 obj = AR()
255 assert obj.say_something(3) == "hihihi"
256 assert obj.unlucky_number() == 99
257 assert obj.say_everything() == "hi 99"
258
259 obj = AT()
260 assert obj.say_something(3) == "hihihi"
261 assert obj.unlucky_number() == 999
262 assert obj.say_everything() == "hi 999"
263
99 for obj in [B_Repeat(), B_Tpl()]:
264 for obj in [m.B_Repeat(), m.B_Tpl()]:
100 assert obj.say_something(3) == "B says hi 3 times"
101 assert obj.unlucky_number() == 13
102 assert obj.lucky_number() == 7.0
103 assert obj.say_everything() == "B says hi 1 times 13"
104
265 assert obj.say_something(3) == "B says hi 3 times"
266 assert obj.unlucky_number() == 13
267 assert obj.lucky_number() == 7.0
268 assert obj.say_everything() == "B says hi 1 times 13"
269
105 for obj in [C_Repeat(), C_Tpl()]:
270 for obj in [m.C_Repeat(), m.C_Tpl()]:
106 assert obj.say_something(3) == "B says hi 3 times"
107 assert obj.unlucky_number() == 4444
108 assert obj.lucky_number() == 888.0
109 assert obj.say_everything() == "B says hi 1 times 4444"
110
271 assert obj.say_something(3) == "B says hi 3 times"
272 assert obj.unlucky_number() == 4444
273 assert obj.lucky_number() == 888.0
274 assert obj.say_everything() == "B says hi 1 times 4444"
275
111 class CR(C_Repeat):
276 class CR(m.C_Repeat):
112 def lucky_number(self):
277 def lucky_number(self):
113 return C_Repeat.lucky_number(self) + 1.25
278 return m.C_Repeat.lucky_number(self) + 1.25
114
115 obj = CR()
116 assert obj.say_something(3) == "B says hi 3 times"
117 assert obj.unlucky_number() == 4444
118 assert obj.lucky_number() == 889.25
119 assert obj.say_everything() == "B says hi 1 times 4444"
120
279
280 obj = CR()
281 assert obj.say_something(3) == "B says hi 3 times"
282 assert obj.unlucky_number() == 4444
283 assert obj.lucky_number() == 889.25
284 assert obj.say_everything() == "B says hi 1 times 4444"
285
121 class CT(C_Tpl):
286 class CT(m.C_Tpl):
122 pass
123
124 obj = CT()
125 assert obj.say_something(3) == "B says hi 3 times"
126 assert obj.unlucky_number() == 4444
127 assert obj.lucky_number() == 888.0
128 assert obj.say_everything() == "B says hi 1 times 4444"
129

--- 12 unchanged lines hidden (view full) ---

142 return CT.lucky_number(self) * 1000
143
144 obj = CCT()
145 assert obj.say_something(3) == "B says hi 3 times"
146 assert obj.unlucky_number() == 4444
147 assert obj.lucky_number() == 888000.0
148 assert obj.say_everything() == "B says hi 1 times 4444"
149
287 pass
288
289 obj = CT()
290 assert obj.say_something(3) == "B says hi 3 times"
291 assert obj.unlucky_number() == 4444
292 assert obj.lucky_number() == 888.0
293 assert obj.say_everything() == "B says hi 1 times 4444"
294

--- 12 unchanged lines hidden (view full) ---

307 return CT.lucky_number(self) * 1000
308
309 obj = CCT()
310 assert obj.say_something(3) == "B says hi 3 times"
311 assert obj.unlucky_number() == 4444
312 assert obj.lucky_number() == 888000.0
313 assert obj.say_everything() == "B says hi 1 times 4444"
314
150 class DR(D_Repeat):
315 class DR(m.D_Repeat):
151 def unlucky_number(self):
152 return 123
153
154 def lucky_number(self):
155 return 42.0
156
316 def unlucky_number(self):
317 return 123
318
319 def lucky_number(self):
320 return 42.0
321
157 for obj in [D_Repeat(), D_Tpl()]:
322 for obj in [m.D_Repeat(), m.D_Tpl()]:
158 assert obj.say_something(3) == "B says hi 3 times"
159 assert obj.unlucky_number() == 4444
160 assert obj.lucky_number() == 888.0
161 assert obj.say_everything() == "B says hi 1 times 4444"
162
163 obj = DR()
164 assert obj.say_something(3) == "B says hi 3 times"
165 assert obj.unlucky_number() == 123
166 assert obj.lucky_number() == 42.0
167 assert obj.say_everything() == "B says hi 1 times 123"
168
323 assert obj.say_something(3) == "B says hi 3 times"
324 assert obj.unlucky_number() == 4444
325 assert obj.lucky_number() == 888.0
326 assert obj.say_everything() == "B says hi 1 times 4444"
327
328 obj = DR()
329 assert obj.say_something(3) == "B says hi 3 times"
330 assert obj.unlucky_number() == 123
331 assert obj.lucky_number() == 42.0
332 assert obj.say_everything() == "B says hi 1 times 123"
333
169 class DT(D_Tpl):
334 class DT(m.D_Tpl):
170 def say_something(self, times):
171 return "DT says:" + (' quack' * times)
172
173 def unlucky_number(self):
174 return 1234
175
176 def lucky_number(self):
177 return -4.25

--- 6 unchanged lines hidden (view full) ---

184
185 class DT2(DT):
186 def say_something(self, times):
187 return "DT2: " + ('QUACK' * times)
188
189 def unlucky_number(self):
190 return -3
191
335 def say_something(self, times):
336 return "DT says:" + (' quack' * times)
337
338 def unlucky_number(self):
339 return 1234
340
341 def lucky_number(self):
342 return -4.25

--- 6 unchanged lines hidden (view full) ---

349
350 class DT2(DT):
351 def say_something(self, times):
352 return "DT2: " + ('QUACK' * times)
353
354 def unlucky_number(self):
355 return -3
356
192 class BT(B_Tpl):
357 class BT(m.B_Tpl):
193 def say_something(self, times):
194 return "BT" * times
195
196 def unlucky_number(self):
197 return -7
198
199 def lucky_number(self):
200 return -1.375
201
202 obj = BT()
203 assert obj.say_something(3) == "BTBTBT"
204 assert obj.unlucky_number() == -7
205 assert obj.lucky_number() == -1.375
206 assert obj.say_everything() == "BT -7"
358 def say_something(self, times):
359 return "BT" * times
360
361 def unlucky_number(self):
362 return -7
363
364 def lucky_number(self):
365 return -1.375
366
367 obj = BT()
368 assert obj.say_something(3) == "BTBTBT"
369 assert obj.unlucky_number() == -7
370 assert obj.lucky_number() == -1.375
371 assert obj.say_everything() == "BT -7"
207
208
209# PyPy: Reference count > 1 causes call with noncopyable instance
210# to fail in ncv1.print_nc()
211@pytest.unsupported_on_pypy
212@pytest.mark.skipif(not hasattr(pybind11_tests, 'NCVirt'),
213 reason="NCVirt test broken on ICPC")
214def test_move_support():
215 from pybind11_tests import NCVirt, NonCopyable, Movable
216
217 class NCVirtExt(NCVirt):
218 def get_noncopyable(self, a, b):
219 # Constructs and returns a new instance:
220 nc = NonCopyable(a * a, b * b)
221 return nc
222
223 def get_movable(self, a, b):
224 # Return a referenced copy
225 self.movable = Movable(a, b)
226 return self.movable
227
228 class NCVirtExt2(NCVirt):
229 def get_noncopyable(self, a, b):
230 # Keep a reference: this is going to throw an exception
231 self.nc = NonCopyable(a, b)
232 return self.nc
233
234 def get_movable(self, a, b):
235 # Return a new instance without storing it
236 return Movable(a, b)
237
238 ncv1 = NCVirtExt()
239 assert ncv1.print_nc(2, 3) == "36"
240 assert ncv1.print_movable(4, 5) == "9"
241 ncv2 = NCVirtExt2()
242 assert ncv2.print_movable(7, 7) == "14"
243 # Don't check the exception message here because it differs under debug/non-debug mode
244 with pytest.raises(RuntimeError):
245 ncv2.print_nc(9, 9)
246
247 nc_stats = ConstructorStats.get(NonCopyable)
248 mv_stats = ConstructorStats.get(Movable)
249 assert nc_stats.alive() == 1
250 assert mv_stats.alive() == 1
251 del ncv1, ncv2
252 assert nc_stats.alive() == 0
253 assert mv_stats.alive() == 0
254 assert nc_stats.values() == ['4', '9', '9', '9']
255 assert mv_stats.values() == ['4', '5', '7', '7']
256 assert nc_stats.copy_constructions == 0
257 assert mv_stats.copy_constructions == 1
258 assert nc_stats.move_constructions >= 0
259 assert mv_stats.move_constructions >= 0