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


gtsam
Author(s):
autogenerated on Sat May 8 2021 02:46:03