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


gtsam
Author(s):
autogenerated on Tue Jul 4 2023 02:37:45