Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 10:17:54

0001 import re
0002 
0003 import pytest
0004 
0005 from pybind11_tests import ConstructorStats
0006 from pybind11_tests import factory_constructors as m
0007 from pybind11_tests.factory_constructors import tag
0008 
0009 
0010 def test_init_factory_basic():
0011     """Tests py::init_factory() wrapper around various ways of returning the object"""
0012 
0013     cstats = [
0014         ConstructorStats.get(c)
0015         for c in [m.TestFactory1, m.TestFactory2, m.TestFactory3]
0016     ]
0017     cstats[0].alive()  # force gc
0018     n_inst = ConstructorStats.detail_reg_inst()
0019 
0020     x1 = m.TestFactory1(tag.unique_ptr, 3)
0021     assert x1.value == "3"
0022     y1 = m.TestFactory1(tag.pointer)
0023     assert y1.value == "(empty)"
0024     z1 = m.TestFactory1("hi!")
0025     assert z1.value == "hi!"
0026 
0027     assert ConstructorStats.detail_reg_inst() == n_inst + 3
0028 
0029     x2 = m.TestFactory2(tag.move)
0030     assert x2.value == "(empty2)"
0031     y2 = m.TestFactory2(tag.pointer, 7)
0032     assert y2.value == "7"
0033     z2 = m.TestFactory2(tag.unique_ptr, "hi again")
0034     assert z2.value == "hi again"
0035 
0036     assert ConstructorStats.detail_reg_inst() == n_inst + 6
0037 
0038     x3 = m.TestFactory3(tag.shared_ptr)
0039     assert x3.value == "(empty3)"
0040     y3 = m.TestFactory3(tag.pointer, 42)
0041     assert y3.value == "42"
0042     z3 = m.TestFactory3("bye")
0043     assert z3.value == "bye"
0044 
0045     for null_ptr_kind in [tag.null_ptr, tag.null_unique_ptr, tag.null_shared_ptr]:
0046         with pytest.raises(TypeError) as excinfo:
0047             m.TestFactory3(null_ptr_kind)
0048         assert (
0049             str(excinfo.value) == "pybind11::init(): factory function returned nullptr"
0050         )
0051 
0052     assert [i.alive() for i in cstats] == [3, 3, 3]
0053     assert ConstructorStats.detail_reg_inst() == n_inst + 9
0054 
0055     del x1, y2, y3, z3
0056     assert [i.alive() for i in cstats] == [2, 2, 1]
0057     assert ConstructorStats.detail_reg_inst() == n_inst + 5
0058     del x2, x3, y1, z1, z2
0059     assert [i.alive() for i in cstats] == [0, 0, 0]
0060     assert ConstructorStats.detail_reg_inst() == n_inst
0061 
0062     assert [i.values() for i in cstats] == [
0063         ["3", "hi!"],
0064         ["7", "hi again"],
0065         ["42", "bye"],
0066     ]
0067     assert [i.default_constructions for i in cstats] == [1, 1, 1]
0068 
0069 
0070 def test_init_factory_signature(msg):
0071     with pytest.raises(TypeError) as excinfo:
0072         m.TestFactory1("invalid", "constructor", "arguments")
0073     assert (
0074         msg(excinfo.value)
0075         == """
0076         __init__(): incompatible constructor arguments. The following argument types are supported:
0077             1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int)
0078             2. m.factory_constructors.TestFactory1(arg0: str)
0079             3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag)
0080             4. m.factory_constructors.TestFactory1(arg0: handle, arg1: int, arg2: handle)
0081 
0082         Invoked with: 'invalid', 'constructor', 'arguments'
0083     """
0084     )
0085 
0086     assert (
0087         msg(m.TestFactory1.__init__.__doc__)
0088         == """
0089         __init__(*args, **kwargs)
0090         Overloaded function.
0091 
0092         1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) -> None
0093 
0094         2. __init__(self: m.factory_constructors.TestFactory1, arg0: str) -> None
0095 
0096         3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None
0097 
0098         4. __init__(self: m.factory_constructors.TestFactory1, arg0: handle, arg1: int, arg2: handle) -> None
0099     """  # noqa: E501 line too long
0100     )
0101 
0102 
0103 def test_init_factory_casting():
0104     """Tests py::init_factory() wrapper with various upcasting and downcasting returns"""
0105 
0106     cstats = [
0107         ConstructorStats.get(c)
0108         for c in [m.TestFactory3, m.TestFactory4, m.TestFactory5]
0109     ]
0110     cstats[0].alive()  # force gc
0111     n_inst = ConstructorStats.detail_reg_inst()
0112 
0113     # Construction from derived references:
0114     a = m.TestFactory3(tag.pointer, tag.TF4, 4)
0115     assert a.value == "4"
0116     b = m.TestFactory3(tag.shared_ptr, tag.TF4, 5)
0117     assert b.value == "5"
0118     c = m.TestFactory3(tag.pointer, tag.TF5, 6)
0119     assert c.value == "6"
0120     d = m.TestFactory3(tag.shared_ptr, tag.TF5, 7)
0121     assert d.value == "7"
0122 
0123     assert ConstructorStats.detail_reg_inst() == n_inst + 4
0124 
0125     # Shared a lambda with TF3:
0126     e = m.TestFactory4(tag.pointer, tag.TF4, 8)
0127     assert e.value == "8"
0128 
0129     assert ConstructorStats.detail_reg_inst() == n_inst + 5
0130     assert [i.alive() for i in cstats] == [5, 3, 2]
0131 
0132     del a
0133     assert [i.alive() for i in cstats] == [4, 2, 2]
0134     assert ConstructorStats.detail_reg_inst() == n_inst + 4
0135 
0136     del b, c, e
0137     assert [i.alive() for i in cstats] == [1, 0, 1]
0138     assert ConstructorStats.detail_reg_inst() == n_inst + 1
0139 
0140     del d
0141     assert [i.alive() for i in cstats] == [0, 0, 0]
0142     assert ConstructorStats.detail_reg_inst() == n_inst
0143 
0144     assert [i.values() for i in cstats] == [
0145         ["4", "5", "6", "7", "8"],
0146         ["4", "5", "8"],
0147         ["6", "7"],
0148     ]
0149 
0150 
0151 def test_init_factory_alias():
0152     """Tests py::init_factory() wrapper with value conversions and alias types"""
0153 
0154     cstats = [m.TestFactory6.get_cstats(), m.TestFactory6.get_alias_cstats()]
0155     cstats[0].alive()  # force gc
0156     n_inst = ConstructorStats.detail_reg_inst()
0157 
0158     a = m.TestFactory6(tag.base, 1)
0159     assert a.get() == 1
0160     assert not a.has_alias()
0161     b = m.TestFactory6(tag.alias, "hi there")
0162     assert b.get() == 8
0163     assert b.has_alias()
0164     c = m.TestFactory6(tag.alias, 3)
0165     assert c.get() == 3
0166     assert c.has_alias()
0167     d = m.TestFactory6(tag.alias, tag.pointer, 4)
0168     assert d.get() == 4
0169     assert d.has_alias()
0170     e = m.TestFactory6(tag.base, tag.pointer, 5)
0171     assert e.get() == 5
0172     assert not e.has_alias()
0173     f = m.TestFactory6(tag.base, tag.alias, tag.pointer, 6)
0174     assert f.get() == 6
0175     assert f.has_alias()
0176 
0177     assert ConstructorStats.detail_reg_inst() == n_inst + 6
0178     assert [i.alive() for i in cstats] == [6, 4]
0179 
0180     del a, b, e
0181     assert [i.alive() for i in cstats] == [3, 3]
0182     assert ConstructorStats.detail_reg_inst() == n_inst + 3
0183     del f, c, d
0184     assert [i.alive() for i in cstats] == [0, 0]
0185     assert ConstructorStats.detail_reg_inst() == n_inst
0186 
0187     class MyTest(m.TestFactory6):
0188         def __init__(self, *args):
0189             m.TestFactory6.__init__(self, *args)
0190 
0191         def get(self):
0192             return -5 + m.TestFactory6.get(self)
0193 
0194     # Return Class by value, moved into new alias:
0195     z = MyTest(tag.base, 123)
0196     assert z.get() == 118
0197     assert z.has_alias()
0198 
0199     # Return alias by value, moved into new alias:
0200     y = MyTest(tag.alias, "why hello!")
0201     assert y.get() == 5
0202     assert y.has_alias()
0203 
0204     # Return Class by pointer, moved into new alias then original destroyed:
0205     x = MyTest(tag.base, tag.pointer, 47)
0206     assert x.get() == 42
0207     assert x.has_alias()
0208 
0209     assert ConstructorStats.detail_reg_inst() == n_inst + 3
0210     assert [i.alive() for i in cstats] == [3, 3]
0211     del x, y, z
0212     assert [i.alive() for i in cstats] == [0, 0]
0213     assert ConstructorStats.detail_reg_inst() == n_inst
0214 
0215     assert [i.values() for i in cstats] == [
0216         ["1", "8", "3", "4", "5", "6", "123", "10", "47"],
0217         ["hi there", "3", "4", "6", "move", "123", "why hello!", "move", "47"],
0218     ]
0219 
0220 
0221 def test_init_factory_dual():
0222     """Tests init factory functions with dual main/alias factory functions"""
0223     from pybind11_tests.factory_constructors import TestFactory7
0224 
0225     cstats = [TestFactory7.get_cstats(), TestFactory7.get_alias_cstats()]
0226     cstats[0].alive()  # force gc
0227     n_inst = ConstructorStats.detail_reg_inst()
0228 
0229     class PythFactory7(TestFactory7):
0230         def get(self):
0231             return 100 + TestFactory7.get(self)
0232 
0233     a1 = TestFactory7(1)
0234     a2 = PythFactory7(2)
0235     assert a1.get() == 1
0236     assert a2.get() == 102
0237     assert not a1.has_alias()
0238     assert a2.has_alias()
0239 
0240     b1 = TestFactory7(tag.pointer, 3)
0241     b2 = PythFactory7(tag.pointer, 4)
0242     assert b1.get() == 3
0243     assert b2.get() == 104
0244     assert not b1.has_alias()
0245     assert b2.has_alias()
0246 
0247     c1 = TestFactory7(tag.mixed, 5)
0248     c2 = PythFactory7(tag.mixed, 6)
0249     assert c1.get() == 5
0250     assert c2.get() == 106
0251     assert not c1.has_alias()
0252     assert c2.has_alias()
0253 
0254     d1 = TestFactory7(tag.base, tag.pointer, 7)
0255     d2 = PythFactory7(tag.base, tag.pointer, 8)
0256     assert d1.get() == 7
0257     assert d2.get() == 108
0258     assert not d1.has_alias()
0259     assert d2.has_alias()
0260 
0261     # Both return an alias; the second multiplies the value by 10:
0262     e1 = TestFactory7(tag.alias, tag.pointer, 9)
0263     e2 = PythFactory7(tag.alias, tag.pointer, 10)
0264     assert e1.get() == 9
0265     assert e2.get() == 200
0266     assert e1.has_alias()
0267     assert e2.has_alias()
0268 
0269     f1 = TestFactory7(tag.shared_ptr, tag.base, 11)
0270     f2 = PythFactory7(tag.shared_ptr, tag.base, 12)
0271     assert f1.get() == 11
0272     assert f2.get() == 112
0273     assert not f1.has_alias()
0274     assert f2.has_alias()
0275 
0276     g1 = TestFactory7(tag.shared_ptr, tag.invalid_base, 13)
0277     assert g1.get() == 13
0278     assert not g1.has_alias()
0279     with pytest.raises(TypeError) as excinfo:
0280         PythFactory7(tag.shared_ptr, tag.invalid_base, 14)
0281     assert (
0282         str(excinfo.value)
0283         == "pybind11::init(): construction failed: returned holder-wrapped instance is not an "
0284         "alias instance"
0285     )
0286 
0287     assert [i.alive() for i in cstats] == [13, 7]
0288     assert ConstructorStats.detail_reg_inst() == n_inst + 13
0289 
0290     del a1, a2, b1, d1, e1, e2
0291     assert [i.alive() for i in cstats] == [7, 4]
0292     assert ConstructorStats.detail_reg_inst() == n_inst + 7
0293     del b2, c1, c2, d2, f1, f2, g1
0294     assert [i.alive() for i in cstats] == [0, 0]
0295     assert ConstructorStats.detail_reg_inst() == n_inst
0296 
0297     assert [i.values() for i in cstats] == [
0298         ["1", "2", "3", "4", "5", "6", "7", "8", "9", "100", "11", "12", "13", "14"],
0299         ["2", "4", "6", "8", "9", "100", "12"],
0300     ]
0301 
0302 
0303 def test_no_placement_new(capture):
0304     """Prior to 2.2, `py::init<...>` relied on the type supporting placement
0305     new; this tests a class without placement new support."""
0306     with capture:
0307         a = m.NoPlacementNew(123)
0308 
0309     found = re.search(r"^operator new called, returning (\d+)\n$", str(capture))
0310     assert found
0311     assert a.i == 123
0312     with capture:
0313         del a
0314         pytest.gc_collect()
0315     assert capture == "operator delete called on " + found.group(1)
0316 
0317     with capture:
0318         b = m.NoPlacementNew()
0319 
0320     found = re.search(r"^operator new called, returning (\d+)\n$", str(capture))
0321     assert found
0322     assert b.i == 100
0323     with capture:
0324         del b
0325         pytest.gc_collect()
0326     assert capture == "operator delete called on " + found.group(1)
0327 
0328 
0329 def test_multiple_inheritance():
0330     class MITest(m.TestFactory1, m.TestFactory2):
0331         def __init__(self):
0332             m.TestFactory1.__init__(self, tag.unique_ptr, 33)
0333             m.TestFactory2.__init__(self, tag.move)
0334 
0335     a = MITest()
0336     assert m.TestFactory1.value.fget(a) == "33"
0337     assert m.TestFactory2.value.fget(a) == "(empty2)"
0338 
0339 
0340 def create_and_destroy(*args):
0341     a = m.NoisyAlloc(*args)
0342     print("---")
0343     del a
0344     pytest.gc_collect()
0345 
0346 
0347 def strip_comments(s):
0348     return re.sub(r"\s+#.*", "", s)
0349 
0350 
0351 def test_reallocation_a(capture, msg):
0352     """When the constructor is overloaded, previous overloads can require a preallocated value.
0353     This test makes sure that such preallocated values only happen when they might be necessary,
0354     and that they are deallocated properly."""
0355 
0356     pytest.gc_collect()
0357 
0358     with capture:
0359         create_and_destroy(1)
0360     assert (
0361         msg(capture)
0362         == """
0363         noisy new
0364         noisy placement new
0365         NoisyAlloc(int 1)
0366         ---
0367         ~NoisyAlloc()
0368         noisy delete
0369     """
0370     )
0371 
0372 
0373 def test_reallocation_b(capture, msg):
0374     with capture:
0375         create_and_destroy(1.5)
0376     assert msg(capture) == strip_comments(
0377         """
0378         noisy new               # allocation required to attempt first overload
0379         noisy delete            # have to dealloc before considering factory init overload
0380         noisy new               # pointer factory calling "new", part 1: allocation
0381         NoisyAlloc(double 1.5)  # ... part two, invoking constructor
0382         ---
0383         ~NoisyAlloc()  # Destructor
0384         noisy delete   # operator delete
0385     """
0386     )
0387 
0388 
0389 def test_reallocation_c(capture, msg):
0390     with capture:
0391         create_and_destroy(2, 3)
0392     assert msg(capture) == strip_comments(
0393         """
0394         noisy new          # pointer factory calling "new", allocation
0395         NoisyAlloc(int 2)  # constructor
0396         ---
0397         ~NoisyAlloc()  # Destructor
0398         noisy delete   # operator delete
0399     """
0400     )
0401 
0402 
0403 def test_reallocation_d(capture, msg):
0404     with capture:
0405         create_and_destroy(2.5, 3)
0406     assert msg(capture) == strip_comments(
0407         """
0408         NoisyAlloc(double 2.5)  # construction (local func variable: operator_new not called)
0409         noisy new               # return-by-value "new" part 1: allocation
0410         ~NoisyAlloc()           # moved-away local func variable destruction
0411         ---
0412         ~NoisyAlloc()  # Destructor
0413         noisy delete   # operator delete
0414     """
0415     )
0416 
0417 
0418 def test_reallocation_e(capture, msg):
0419     with capture:
0420         create_and_destroy(3.5, 4.5)
0421     assert msg(capture) == strip_comments(
0422         """
0423         noisy new               # preallocation needed before invoking placement-new overload
0424         noisy placement new     # Placement new
0425         NoisyAlloc(double 3.5)  # construction
0426         ---
0427         ~NoisyAlloc()  # Destructor
0428         noisy delete   # operator delete
0429     """
0430     )
0431 
0432 
0433 def test_reallocation_f(capture, msg):
0434     with capture:
0435         create_and_destroy(4, 0.5)
0436     assert msg(capture) == strip_comments(
0437         """
0438         noisy new          # preallocation needed before invoking placement-new overload
0439         noisy delete       # deallocation of preallocated storage
0440         noisy new          # Factory pointer allocation
0441         NoisyAlloc(int 4)  # factory pointer construction
0442         ---
0443         ~NoisyAlloc()  # Destructor
0444         noisy delete   # operator delete
0445     """
0446     )
0447 
0448 
0449 def test_reallocation_g(capture, msg):
0450     with capture:
0451         create_and_destroy(5, "hi")
0452     assert msg(capture) == strip_comments(
0453         """
0454         noisy new            # preallocation needed before invoking first placement new
0455         noisy delete         # delete before considering new-style constructor
0456         noisy new            # preallocation for second placement new
0457         noisy placement new  # Placement new in the second placement new overload
0458         NoisyAlloc(int 5)    # construction
0459         ---
0460         ~NoisyAlloc()  # Destructor
0461         noisy delete   # operator delete
0462     """
0463     )
0464 
0465 
0466 def test_invalid_self():
0467     """Tests invocation of the pybind-registered base class with an invalid `self` argument."""
0468 
0469     class NotPybindDerived:
0470         pass
0471 
0472     # Attempts to initialize with an invalid type passed as `self`:
0473     class BrokenTF1(m.TestFactory1):
0474         def __init__(self, bad):
0475             if bad == 1:
0476                 a = m.TestFactory2(tag.pointer, 1)
0477                 m.TestFactory1.__init__(a, tag.pointer)
0478             elif bad == 2:
0479                 a = NotPybindDerived()
0480                 m.TestFactory1.__init__(a, tag.pointer)
0481 
0482     # Same as above, but for a class with an alias:
0483     class BrokenTF6(m.TestFactory6):
0484         def __init__(self, bad):
0485             if bad == 0:
0486                 m.TestFactory6.__init__()
0487             elif bad == 1:
0488                 a = m.TestFactory2(tag.pointer, 1)
0489                 m.TestFactory6.__init__(a, tag.base, 1)
0490             elif bad == 2:
0491                 a = m.TestFactory2(tag.pointer, 1)
0492                 m.TestFactory6.__init__(a, tag.alias, 1)
0493             elif bad == 3:
0494                 m.TestFactory6.__init__(
0495                     NotPybindDerived.__new__(NotPybindDerived), tag.base, 1
0496                 )
0497             elif bad == 4:
0498                 m.TestFactory6.__init__(
0499                     NotPybindDerived.__new__(NotPybindDerived), tag.alias, 1
0500                 )
0501 
0502     for arg in (1, 2):
0503         with pytest.raises(TypeError) as excinfo:
0504             BrokenTF1(arg)
0505         assert (
0506             str(excinfo.value)
0507             == "__init__(self, ...) called with invalid or missing `self` argument"
0508         )
0509 
0510     for arg in (0, 1, 2, 3, 4):
0511         with pytest.raises(TypeError) as excinfo:
0512             BrokenTF6(arg)
0513         assert (
0514             str(excinfo.value)
0515             == "__init__(self, ...) called with invalid or missing `self` argument"
0516         )