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


gtsam
Author(s):
autogenerated on Thu Jul 4 2024 03:06:01