Scarab  v2.11.1
Project 8 C++ Utility Library
test_factory_constructors.py
Go to the documentation of this file.
1 import pytest
2 import re
3 
4 from pybind11_tests import factory_constructors as m
5 from pybind11_tests.factory_constructors import tag
6 from pybind11_tests import ConstructorStats
7 
8 
10  """Tests py::init_factory() wrapper around various ways of returning the object"""
11 
12  cstats = [ConstructorStats.get(c) for c in [m.TestFactory1, m.TestFactory2, m.TestFactory3]]
13  cstats[0].alive() # force gc
14  n_inst = ConstructorStats.detail_reg_inst()
15 
16  x1 = m.TestFactory1(tag.unique_ptr, 3)
17  assert x1.value == "3"
18  y1 = m.TestFactory1(tag.pointer)
19  assert y1.value == "(empty)"
20  z1 = m.TestFactory1("hi!")
21  assert z1.value == "hi!"
22 
23  assert ConstructorStats.detail_reg_inst() == n_inst + 3
24 
25  x2 = m.TestFactory2(tag.move)
26  assert x2.value == "(empty2)"
27  y2 = m.TestFactory2(tag.pointer, 7)
28  assert y2.value == "7"
29  z2 = m.TestFactory2(tag.unique_ptr, "hi again")
30  assert z2.value == "hi again"
31 
32  assert ConstructorStats.detail_reg_inst() == n_inst + 6
33 
34  x3 = m.TestFactory3(tag.shared_ptr)
35  assert x3.value == "(empty3)"
36  y3 = m.TestFactory3(tag.pointer, 42)
37  assert y3.value == "42"
38  z3 = m.TestFactory3("bye")
39  assert z3.value == "bye"
40 
41  with pytest.raises(TypeError) as excinfo:
42  m.TestFactory3(tag.null_ptr)
43  assert str(excinfo.value) == "pybind11::init(): factory function returned nullptr"
44 
45  assert [i.alive() for i in cstats] == [3, 3, 3]
46  assert ConstructorStats.detail_reg_inst() == n_inst + 9
47 
48  del x1, y2, y3, z3
49  assert [i.alive() for i in cstats] == [2, 2, 1]
50  assert ConstructorStats.detail_reg_inst() == n_inst + 5
51  del x2, x3, y1, z1, z2
52  assert [i.alive() for i in cstats] == [0, 0, 0]
53  assert ConstructorStats.detail_reg_inst() == n_inst
54 
55  assert [i.values() for i in cstats] == [
56  ["3", "hi!"],
57  ["7", "hi again"],
58  ["42", "bye"]
59  ]
60  assert [i.default_constructions for i in cstats] == [1, 1, 1]
61 
62 
64  with pytest.raises(TypeError) as excinfo:
65  m.TestFactory1("invalid", "constructor", "arguments")
66  assert msg(excinfo.value) == """
67  __init__(): incompatible constructor arguments. The following argument types are supported:
68  1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int)
69  2. m.factory_constructors.TestFactory1(arg0: str)
70  3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag)
71  4. m.factory_constructors.TestFactory1(arg0: handle, arg1: int, arg2: handle)
72 
73  Invoked with: 'invalid', 'constructor', 'arguments'
74  """ # noqa: E501 line too long
75 
76  assert msg(m.TestFactory1.__init__.__doc__) == """
77  __init__(*args, **kwargs)
78  Overloaded function.
79 
80  1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) -> None
81 
82  2. __init__(self: m.factory_constructors.TestFactory1, arg0: str) -> None
83 
84  3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None
85 
86  4. __init__(self: m.factory_constructors.TestFactory1, arg0: handle, arg1: int, arg2: handle) -> None
87  """ # noqa: E501 line too long
88 
89 
91  """Tests py::init_factory() wrapper with various upcasting and downcasting returns"""
92 
93  cstats = [ConstructorStats.get(c) for c in [m.TestFactory3, m.TestFactory4, m.TestFactory5]]
94  cstats[0].alive() # force gc
95  n_inst = ConstructorStats.detail_reg_inst()
96 
97  # Construction from derived references:
98  a = m.TestFactory3(tag.pointer, tag.TF4, 4)
99  assert a.value == "4"
100  b = m.TestFactory3(tag.shared_ptr, tag.TF4, 5)
101  assert b.value == "5"
102  c = m.TestFactory3(tag.pointer, tag.TF5, 6)
103  assert c.value == "6"
104  d = m.TestFactory3(tag.shared_ptr, tag.TF5, 7)
105  assert d.value == "7"
106 
107  assert ConstructorStats.detail_reg_inst() == n_inst + 4
108 
109  # Shared a lambda with TF3:
110  e = m.TestFactory4(tag.pointer, tag.TF4, 8)
111  assert e.value == "8"
112 
113  assert ConstructorStats.detail_reg_inst() == n_inst + 5
114  assert [i.alive() for i in cstats] == [5, 3, 2]
115 
116  del a
117  assert [i.alive() for i in cstats] == [4, 2, 2]
118  assert ConstructorStats.detail_reg_inst() == n_inst + 4
119 
120  del b, c, e
121  assert [i.alive() for i in cstats] == [1, 0, 1]
122  assert ConstructorStats.detail_reg_inst() == n_inst + 1
123 
124  del d
125  assert [i.alive() for i in cstats] == [0, 0, 0]
126  assert ConstructorStats.detail_reg_inst() == n_inst
127 
128  assert [i.values() for i in cstats] == [
129  ["4", "5", "6", "7", "8"],
130  ["4", "5", "8"],
131  ["6", "7"]
132  ]
133 
134 
136  """Tests py::init_factory() wrapper with value conversions and alias types"""
137 
138  cstats = [m.TestFactory6.get_cstats(), m.TestFactory6.get_alias_cstats()]
139  cstats[0].alive() # force gc
140  n_inst = ConstructorStats.detail_reg_inst()
141 
142  a = m.TestFactory6(tag.base, 1)
143  assert a.get() == 1
144  assert not a.has_alias()
145  b = m.TestFactory6(tag.alias, "hi there")
146  assert b.get() == 8
147  assert b.has_alias()
148  c = m.TestFactory6(tag.alias, 3)
149  assert c.get() == 3
150  assert c.has_alias()
151  d = m.TestFactory6(tag.alias, tag.pointer, 4)
152  assert d.get() == 4
153  assert d.has_alias()
154  e = m.TestFactory6(tag.base, tag.pointer, 5)
155  assert e.get() == 5
156  assert not e.has_alias()
157  f = m.TestFactory6(tag.base, tag.alias, tag.pointer, 6)
158  assert f.get() == 6
159  assert f.has_alias()
160 
161  assert ConstructorStats.detail_reg_inst() == n_inst + 6
162  assert [i.alive() for i in cstats] == [6, 4]
163 
164  del a, b, e
165  assert [i.alive() for i in cstats] == [3, 3]
166  assert ConstructorStats.detail_reg_inst() == n_inst + 3
167  del f, c, d
168  assert [i.alive() for i in cstats] == [0, 0]
169  assert ConstructorStats.detail_reg_inst() == n_inst
170 
171  class MyTest(m.TestFactory6):
172  def __init__(self, *args):
173  m.TestFactory6.__init__(self, *args)
174 
175  def get(self):
176  return -5 + m.TestFactory6.get(self)
177 
178  # Return Class by value, moved into new alias:
179  z = MyTest(tag.base, 123)
180  assert z.get() == 118
181  assert z.has_alias()
182 
183  # Return alias by value, moved into new alias:
184  y = MyTest(tag.alias, "why hello!")
185  assert y.get() == 5
186  assert y.has_alias()
187 
188  # Return Class by pointer, moved into new alias then original destroyed:
189  x = MyTest(tag.base, tag.pointer, 47)
190  assert x.get() == 42
191  assert x.has_alias()
192 
193  assert ConstructorStats.detail_reg_inst() == n_inst + 3
194  assert [i.alive() for i in cstats] == [3, 3]
195  del x, y, z
196  assert [i.alive() for i in cstats] == [0, 0]
197  assert ConstructorStats.detail_reg_inst() == n_inst
198 
199  assert [i.values() for i in cstats] == [
200  ["1", "8", "3", "4", "5", "6", "123", "10", "47"],
201  ["hi there", "3", "4", "6", "move", "123", "why hello!", "move", "47"]
202  ]
203 
204 
206  """Tests init factory functions with dual main/alias factory functions"""
207  from pybind11_tests.factory_constructors import TestFactory7
208 
209  cstats = [TestFactory7.get_cstats(), TestFactory7.get_alias_cstats()]
210  cstats[0].alive() # force gc
211  n_inst = ConstructorStats.detail_reg_inst()
212 
213  class PythFactory7(TestFactory7):
214  def get(self):
215  return 100 + TestFactory7.get(self)
216 
217  a1 = TestFactory7(1)
218  a2 = PythFactory7(2)
219  assert a1.get() == 1
220  assert a2.get() == 102
221  assert not a1.has_alias()
222  assert a2.has_alias()
223 
224  b1 = TestFactory7(tag.pointer, 3)
225  b2 = PythFactory7(tag.pointer, 4)
226  assert b1.get() == 3
227  assert b2.get() == 104
228  assert not b1.has_alias()
229  assert b2.has_alias()
230 
231  c1 = TestFactory7(tag.mixed, 5)
232  c2 = PythFactory7(tag.mixed, 6)
233  assert c1.get() == 5
234  assert c2.get() == 106
235  assert not c1.has_alias()
236  assert c2.has_alias()
237 
238  d1 = TestFactory7(tag.base, tag.pointer, 7)
239  d2 = PythFactory7(tag.base, tag.pointer, 8)
240  assert d1.get() == 7
241  assert d2.get() == 108
242  assert not d1.has_alias()
243  assert d2.has_alias()
244 
245  # Both return an alias; the second multiplies the value by 10:
246  e1 = TestFactory7(tag.alias, tag.pointer, 9)
247  e2 = PythFactory7(tag.alias, tag.pointer, 10)
248  assert e1.get() == 9
249  assert e2.get() == 200
250  assert e1.has_alias()
251  assert e2.has_alias()
252 
253  f1 = TestFactory7(tag.shared_ptr, tag.base, 11)
254  f2 = PythFactory7(tag.shared_ptr, tag.base, 12)
255  assert f1.get() == 11
256  assert f2.get() == 112
257  assert not f1.has_alias()
258  assert f2.has_alias()
259 
260  g1 = TestFactory7(tag.shared_ptr, tag.invalid_base, 13)
261  assert g1.get() == 13
262  assert not g1.has_alias()
263  with pytest.raises(TypeError) as excinfo:
264  PythFactory7(tag.shared_ptr, tag.invalid_base, 14)
265  assert (str(excinfo.value) ==
266  "pybind11::init(): construction failed: returned holder-wrapped instance is not an "
267  "alias instance")
268 
269  assert [i.alive() for i in cstats] == [13, 7]
270  assert ConstructorStats.detail_reg_inst() == n_inst + 13
271 
272  del a1, a2, b1, d1, e1, e2
273  assert [i.alive() for i in cstats] == [7, 4]
274  assert ConstructorStats.detail_reg_inst() == n_inst + 7
275  del b2, c1, c2, d2, f1, f2, g1
276  assert [i.alive() for i in cstats] == [0, 0]
277  assert ConstructorStats.detail_reg_inst() == n_inst
278 
279  assert [i.values() for i in cstats] == [
280  ["1", "2", "3", "4", "5", "6", "7", "8", "9", "100", "11", "12", "13", "14"],
281  ["2", "4", "6", "8", "9", "100", "12"]
282  ]
283 
284 
286  """Prior to 2.2, `py::init<...>` relied on the type supporting placement
287  new; this tests a class without placement new support."""
288  with capture:
289  a = m.NoPlacementNew(123)
290 
291  found = re.search(r'^operator new called, returning (\d+)\n$', str(capture))
292  assert found
293  assert a.i == 123
294  with capture:
295  del a
296  pytest.gc_collect()
297  assert capture == "operator delete called on " + found.group(1)
298 
299  with capture:
300  b = m.NoPlacementNew()
301 
302  found = re.search(r'^operator new called, returning (\d+)\n$', str(capture))
303  assert found
304  assert b.i == 100
305  with capture:
306  del b
307  pytest.gc_collect()
308  assert capture == "operator delete called on " + found.group(1)
309 
310 
312  class MITest(m.TestFactory1, m.TestFactory2):
313  def __init__(self):
314  m.TestFactory1.__init__(self, tag.unique_ptr, 33)
315  m.TestFactory2.__init__(self, tag.move)
316 
317  a = MITest()
318  assert m.TestFactory1.value.fget(a) == "33"
319  assert m.TestFactory2.value.fget(a) == "(empty2)"
320 
321 
323  a = m.NoisyAlloc(*args)
324  print("---")
325  del a
326  pytest.gc_collect()
327 
328 
330  return re.sub(r'\s+#.*', '', s)
331 
332 
333 def test_reallocations(capture, msg):
334  """When the constructor is overloaded, previous overloads can require a preallocated value.
335  This test makes sure that such preallocated values only happen when they might be necessary,
336  and that they are deallocated properly"""
337 
338  pytest.gc_collect()
339 
340  with capture:
342  assert msg(capture) == """
343  noisy new
344  noisy placement new
345  NoisyAlloc(int 1)
346  ---
347  ~NoisyAlloc()
348  noisy delete
349  """
350  with capture:
351  create_and_destroy(1.5)
352  assert msg(capture) == strip_comments("""
353  noisy new # allocation required to attempt first overload
354  noisy delete # have to dealloc before considering factory init overload
355  noisy new # pointer factory calling "new", part 1: allocation
356  NoisyAlloc(double 1.5) # ... part two, invoking constructor
357  ---
358  ~NoisyAlloc() # Destructor
359  noisy delete # operator delete
360  """)
361 
362  with capture:
363  create_and_destroy(2, 3)
364  assert msg(capture) == strip_comments("""
365  noisy new # pointer factory calling "new", allocation
366  NoisyAlloc(int 2) # constructor
367  ---
368  ~NoisyAlloc() # Destructor
369  noisy delete # operator delete
370  """)
371 
372  with capture:
373  create_and_destroy(2.5, 3)
374  assert msg(capture) == strip_comments("""
375  NoisyAlloc(double 2.5) # construction (local func variable: operator_new not called)
376  noisy new # return-by-value "new" part 1: allocation
377  ~NoisyAlloc() # moved-away local func variable destruction
378  ---
379  ~NoisyAlloc() # Destructor
380  noisy delete # operator delete
381  """)
382 
383  with capture:
384  create_and_destroy(3.5, 4.5)
385  assert msg(capture) == strip_comments("""
386  noisy new # preallocation needed before invoking placement-new overload
387  noisy placement new # Placement new
388  NoisyAlloc(double 3.5) # construction
389  ---
390  ~NoisyAlloc() # Destructor
391  noisy delete # operator delete
392  """)
393 
394  with capture:
395  create_and_destroy(4, 0.5)
396  assert msg(capture) == strip_comments("""
397  noisy new # preallocation needed before invoking placement-new overload
398  noisy delete # deallocation of preallocated storage
399  noisy new # Factory pointer allocation
400  NoisyAlloc(int 4) # factory pointer construction
401  ---
402  ~NoisyAlloc() # Destructor
403  noisy delete # operator delete
404  """)
405 
406  with capture:
407  create_and_destroy(5, "hi")
408  assert msg(capture) == strip_comments("""
409  noisy new # preallocation needed before invoking first placement new
410  noisy delete # delete before considering new-style constructor
411  noisy new # preallocation for second placement new
412  noisy placement new # Placement new in the second placement new overload
413  NoisyAlloc(int 5) # construction
414  ---
415  ~NoisyAlloc() # Destructor
416  noisy delete # operator delete
417  """)
418 
419 
420 @pytest.unsupported_on_py2
422  """Tests invocation of the pybind-registered base class with an invalid `self` argument. You
423  can only actually do this on Python 3: Python 2 raises an exception itself if you try."""
424  class NotPybindDerived(object):
425  pass
426 
427  # Attempts to initialize with an invalid type passed as `self`:
428  class BrokenTF1(m.TestFactory1):
429  def __init__(self, bad):
430  if bad == 1:
431  a = m.TestFactory2(tag.pointer, 1)
432  m.TestFactory1.__init__(a, tag.pointer)
433  elif bad == 2:
434  a = NotPybindDerived()
435  m.TestFactory1.__init__(a, tag.pointer)
436 
437  # Same as above, but for a class with an alias:
438  class BrokenTF6(m.TestFactory6):
439  def __init__(self, bad):
440  if bad == 1:
441  a = m.TestFactory2(tag.pointer, 1)
442  m.TestFactory6.__init__(a, tag.base, 1)
443  elif bad == 2:
444  a = m.TestFactory2(tag.pointer, 1)
445  m.TestFactory6.__init__(a, tag.alias, 1)
446  elif bad == 3:
447  m.TestFactory6.__init__(NotPybindDerived.__new__(NotPybindDerived), tag.base, 1)
448  elif bad == 4:
449  m.TestFactory6.__init__(NotPybindDerived.__new__(NotPybindDerived), tag.alias, 1)
450 
451  for arg in (1, 2):
452  with pytest.raises(TypeError) as excinfo:
453  BrokenTF1(arg)
454  assert str(excinfo.value) == "__init__(self, ...) called with invalid `self` argument"
455 
456  for arg in (1, 2, 3, 4):
457  with pytest.raises(TypeError) as excinfo:
458  BrokenTF6(arg)
459  assert str(excinfo.value) == "__init__(self, ...) called with invalid `self` argument"
static ConstructorStats & get(std::type_index type)
def msg()
Definition: conftest.py:165
return os str()
void print(Args &&...args)
Definition: pybind11.h:1849