Blame external/pybind11/tests/test_factory_constructors.py

Packit 534379
import pytest
Packit 534379
import re
Packit 534379
Packit 534379
from pybind11_tests import factory_constructors as m
Packit 534379
from pybind11_tests.factory_constructors import tag
Packit 534379
from pybind11_tests import ConstructorStats
Packit 534379
Packit 534379
Packit 534379
def test_init_factory_basic():
Packit 534379
    """Tests py::init_factory() wrapper around various ways of returning the object"""
Packit 534379
Packit 534379
    cstats = [ConstructorStats.get(c) for c in [m.TestFactory1, m.TestFactory2, m.TestFactory3]]
Packit 534379
    cstats[0].alive()  # force gc
Packit 534379
    n_inst = ConstructorStats.detail_reg_inst()
Packit 534379
Packit 534379
    x1 = m.TestFactory1(tag.unique_ptr, 3)
Packit 534379
    assert x1.value == "3"
Packit 534379
    y1 = m.TestFactory1(tag.pointer)
Packit 534379
    assert y1.value == "(empty)"
Packit 534379
    z1 = m.TestFactory1("hi!")
Packit 534379
    assert z1.value == "hi!"
Packit 534379
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 3
Packit 534379
Packit 534379
    x2 = m.TestFactory2(tag.move)
Packit 534379
    assert x2.value == "(empty2)"
Packit 534379
    y2 = m.TestFactory2(tag.pointer, 7)
Packit 534379
    assert y2.value == "7"
Packit 534379
    z2 = m.TestFactory2(tag.unique_ptr, "hi again")
Packit 534379
    assert z2.value == "hi again"
Packit 534379
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 6
Packit 534379
Packit 534379
    x3 = m.TestFactory3(tag.shared_ptr)
Packit 534379
    assert x3.value == "(empty3)"
Packit 534379
    y3 = m.TestFactory3(tag.pointer, 42)
Packit 534379
    assert y3.value == "42"
Packit 534379
    z3 = m.TestFactory3("bye")
Packit 534379
    assert z3.value == "bye"
Packit 534379
Packit 534379
    with pytest.raises(TypeError) as excinfo:
Packit 534379
        m.TestFactory3(tag.null_ptr)
Packit 534379
    assert str(excinfo.value) == "pybind11::init(): factory function returned nullptr"
Packit 534379
Packit 534379
    assert [i.alive() for i in cstats] == [3, 3, 3]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 9
Packit 534379
Packit 534379
    del x1, y2, y3, z3
Packit 534379
    assert [i.alive() for i in cstats] == [2, 2, 1]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 5
Packit 534379
    del x2, x3, y1, z1, z2
Packit 534379
    assert [i.alive() for i in cstats] == [0, 0, 0]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst
Packit 534379
Packit 534379
    assert [i.values() for i in cstats] == [
Packit 534379
        ["3", "hi!"],
Packit 534379
        ["7", "hi again"],
Packit 534379
        ["42", "bye"]
Packit 534379
    ]
Packit 534379
    assert [i.default_constructions for i in cstats] == [1, 1, 1]
Packit 534379
Packit 534379
Packit 534379
def test_init_factory_signature(msg):
Packit 534379
    with pytest.raises(TypeError) as excinfo:
Packit 534379
        m.TestFactory1("invalid", "constructor", "arguments")
Packit 534379
    assert msg(excinfo.value) == """
Packit 534379
        __init__(): incompatible constructor arguments. The following argument types are supported:
Packit 534379
            1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int)
Packit 534379
            2. m.factory_constructors.TestFactory1(arg0: str)
Packit 534379
            3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag)
Packit 534379
            4. m.factory_constructors.TestFactory1(arg0: handle, arg1: int, arg2: handle)
Packit 534379
Packit 534379
        Invoked with: 'invalid', 'constructor', 'arguments'
Packit 534379
    """  # noqa: E501 line too long
Packit 534379
Packit 534379
    assert msg(m.TestFactory1.__init__.__doc__) == """
Packit 534379
        __init__(*args, **kwargs)
Packit 534379
        Overloaded function.
Packit 534379
Packit 534379
        1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) -> None
Packit 534379
Packit 534379
        2. __init__(self: m.factory_constructors.TestFactory1, arg0: str) -> None
Packit 534379
Packit 534379
        3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None
Packit 534379
Packit 534379
        4. __init__(self: m.factory_constructors.TestFactory1, arg0: handle, arg1: int, arg2: handle) -> None
Packit 534379
    """  # noqa: E501 line too long
Packit 534379
Packit 534379
Packit 534379
def test_init_factory_casting():
Packit 534379
    """Tests py::init_factory() wrapper with various upcasting and downcasting returns"""
Packit 534379
Packit 534379
    cstats = [ConstructorStats.get(c) for c in [m.TestFactory3, m.TestFactory4, m.TestFactory5]]
Packit 534379
    cstats[0].alive()  # force gc
Packit 534379
    n_inst = ConstructorStats.detail_reg_inst()
Packit 534379
Packit 534379
    # Construction from derived references:
Packit 534379
    a = m.TestFactory3(tag.pointer, tag.TF4, 4)
Packit 534379
    assert a.value == "4"
Packit 534379
    b = m.TestFactory3(tag.shared_ptr, tag.TF4, 5)
Packit 534379
    assert b.value == "5"
Packit 534379
    c = m.TestFactory3(tag.pointer, tag.TF5, 6)
Packit 534379
    assert c.value == "6"
Packit 534379
    d = m.TestFactory3(tag.shared_ptr, tag.TF5, 7)
Packit 534379
    assert d.value == "7"
Packit 534379
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 4
Packit 534379
Packit 534379
    # Shared a lambda with TF3:
Packit 534379
    e = m.TestFactory4(tag.pointer, tag.TF4, 8)
Packit 534379
    assert e.value == "8"
Packit 534379
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 5
Packit 534379
    assert [i.alive() for i in cstats] == [5, 3, 2]
Packit 534379
Packit 534379
    del a
Packit 534379
    assert [i.alive() for i in cstats] == [4, 2, 2]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 4
Packit 534379
Packit 534379
    del b, c, e
Packit 534379
    assert [i.alive() for i in cstats] == [1, 0, 1]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 1
Packit 534379
Packit 534379
    del d
Packit 534379
    assert [i.alive() for i in cstats] == [0, 0, 0]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst
Packit 534379
Packit 534379
    assert [i.values() for i in cstats] == [
Packit 534379
        ["4", "5", "6", "7", "8"],
Packit 534379
        ["4", "5", "8"],
Packit 534379
        ["6", "7"]
Packit 534379
    ]
Packit 534379
Packit 534379
Packit 534379
def test_init_factory_alias():
Packit 534379
    """Tests py::init_factory() wrapper with value conversions and alias types"""
Packit 534379
Packit 534379
    cstats = [m.TestFactory6.get_cstats(), m.TestFactory6.get_alias_cstats()]
Packit 534379
    cstats[0].alive()  # force gc
Packit 534379
    n_inst = ConstructorStats.detail_reg_inst()
Packit 534379
Packit 534379
    a = m.TestFactory6(tag.base, 1)
Packit 534379
    assert a.get() == 1
Packit 534379
    assert not a.has_alias()
Packit 534379
    b = m.TestFactory6(tag.alias, "hi there")
Packit 534379
    assert b.get() == 8
Packit 534379
    assert b.has_alias()
Packit 534379
    c = m.TestFactory6(tag.alias, 3)
Packit 534379
    assert c.get() == 3
Packit 534379
    assert c.has_alias()
Packit 534379
    d = m.TestFactory6(tag.alias, tag.pointer, 4)
Packit 534379
    assert d.get() == 4
Packit 534379
    assert d.has_alias()
Packit 534379
    e = m.TestFactory6(tag.base, tag.pointer, 5)
Packit 534379
    assert e.get() == 5
Packit 534379
    assert not e.has_alias()
Packit 534379
    f = m.TestFactory6(tag.base, tag.alias, tag.pointer, 6)
Packit 534379
    assert f.get() == 6
Packit 534379
    assert f.has_alias()
Packit 534379
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 6
Packit 534379
    assert [i.alive() for i in cstats] == [6, 4]
Packit 534379
Packit 534379
    del a, b, e
Packit 534379
    assert [i.alive() for i in cstats] == [3, 3]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 3
Packit 534379
    del f, c, d
Packit 534379
    assert [i.alive() for i in cstats] == [0, 0]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst
Packit 534379
Packit 534379
    class MyTest(m.TestFactory6):
Packit 534379
        def __init__(self, *args):
Packit 534379
            m.TestFactory6.__init__(self, *args)
Packit 534379
Packit 534379
        def get(self):
Packit 534379
            return -5 + m.TestFactory6.get(self)
Packit 534379
Packit 534379
    # Return Class by value, moved into new alias:
Packit 534379
    z = MyTest(tag.base, 123)
Packit 534379
    assert z.get() == 118
Packit 534379
    assert z.has_alias()
Packit 534379
Packit 534379
    # Return alias by value, moved into new alias:
Packit 534379
    y = MyTest(tag.alias, "why hello!")
Packit 534379
    assert y.get() == 5
Packit 534379
    assert y.has_alias()
Packit 534379
Packit 534379
    # Return Class by pointer, moved into new alias then original destroyed:
Packit 534379
    x = MyTest(tag.base, tag.pointer, 47)
Packit 534379
    assert x.get() == 42
Packit 534379
    assert x.has_alias()
Packit 534379
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 3
Packit 534379
    assert [i.alive() for i in cstats] == [3, 3]
Packit 534379
    del x, y, z
Packit 534379
    assert [i.alive() for i in cstats] == [0, 0]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst
Packit 534379
Packit 534379
    assert [i.values() for i in cstats] == [
Packit 534379
        ["1", "8", "3", "4", "5", "6", "123", "10", "47"],
Packit 534379
        ["hi there", "3", "4", "6", "move", "123", "why hello!", "move", "47"]
Packit 534379
    ]
Packit 534379
Packit 534379
Packit 534379
def test_init_factory_dual():
Packit 534379
    """Tests init factory functions with dual main/alias factory functions"""
Packit 534379
    from pybind11_tests.factory_constructors import TestFactory7
Packit 534379
Packit 534379
    cstats = [TestFactory7.get_cstats(), TestFactory7.get_alias_cstats()]
Packit 534379
    cstats[0].alive()  # force gc
Packit 534379
    n_inst = ConstructorStats.detail_reg_inst()
Packit 534379
Packit 534379
    class PythFactory7(TestFactory7):
Packit 534379
        def get(self):
Packit 534379
            return 100 + TestFactory7.get(self)
Packit 534379
Packit 534379
    a1 = TestFactory7(1)
Packit 534379
    a2 = PythFactory7(2)
Packit 534379
    assert a1.get() == 1
Packit 534379
    assert a2.get() == 102
Packit 534379
    assert not a1.has_alias()
Packit 534379
    assert a2.has_alias()
Packit 534379
Packit 534379
    b1 = TestFactory7(tag.pointer, 3)
Packit 534379
    b2 = PythFactory7(tag.pointer, 4)
Packit 534379
    assert b1.get() == 3
Packit 534379
    assert b2.get() == 104
Packit 534379
    assert not b1.has_alias()
Packit 534379
    assert b2.has_alias()
Packit 534379
Packit 534379
    c1 = TestFactory7(tag.mixed, 5)
Packit 534379
    c2 = PythFactory7(tag.mixed, 6)
Packit 534379
    assert c1.get() == 5
Packit 534379
    assert c2.get() == 106
Packit 534379
    assert not c1.has_alias()
Packit 534379
    assert c2.has_alias()
Packit 534379
Packit 534379
    d1 = TestFactory7(tag.base, tag.pointer, 7)
Packit 534379
    d2 = PythFactory7(tag.base, tag.pointer, 8)
Packit 534379
    assert d1.get() == 7
Packit 534379
    assert d2.get() == 108
Packit 534379
    assert not d1.has_alias()
Packit 534379
    assert d2.has_alias()
Packit 534379
Packit 534379
    # Both return an alias; the second multiplies the value by 10:
Packit 534379
    e1 = TestFactory7(tag.alias, tag.pointer, 9)
Packit 534379
    e2 = PythFactory7(tag.alias, tag.pointer, 10)
Packit 534379
    assert e1.get() == 9
Packit 534379
    assert e2.get() == 200
Packit 534379
    assert e1.has_alias()
Packit 534379
    assert e2.has_alias()
Packit 534379
Packit 534379
    f1 = TestFactory7(tag.shared_ptr, tag.base, 11)
Packit 534379
    f2 = PythFactory7(tag.shared_ptr, tag.base, 12)
Packit 534379
    assert f1.get() == 11
Packit 534379
    assert f2.get() == 112
Packit 534379
    assert not f1.has_alias()
Packit 534379
    assert f2.has_alias()
Packit 534379
Packit 534379
    g1 = TestFactory7(tag.shared_ptr, tag.invalid_base, 13)
Packit 534379
    assert g1.get() == 13
Packit 534379
    assert not g1.has_alias()
Packit 534379
    with pytest.raises(TypeError) as excinfo:
Packit 534379
        PythFactory7(tag.shared_ptr, tag.invalid_base, 14)
Packit 534379
    assert (str(excinfo.value) ==
Packit 534379
            "pybind11::init(): construction failed: returned holder-wrapped instance is not an "
Packit 534379
            "alias instance")
Packit 534379
Packit 534379
    assert [i.alive() for i in cstats] == [13, 7]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 13
Packit 534379
Packit 534379
    del a1, a2, b1, d1, e1, e2
Packit 534379
    assert [i.alive() for i in cstats] == [7, 4]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst + 7
Packit 534379
    del b2, c1, c2, d2, f1, f2, g1
Packit 534379
    assert [i.alive() for i in cstats] == [0, 0]
Packit 534379
    assert ConstructorStats.detail_reg_inst() == n_inst
Packit 534379
Packit 534379
    assert [i.values() for i in cstats] == [
Packit 534379
        ["1", "2", "3", "4", "5", "6", "7", "8", "9", "100", "11", "12", "13", "14"],
Packit 534379
        ["2", "4", "6", "8", "9", "100", "12"]
Packit 534379
    ]
Packit 534379
Packit 534379
Packit 534379
def test_no_placement_new(capture):
Packit 534379
    """Prior to 2.2, `py::init<...>` relied on the type supporting placement
Packit 534379
    new; this tests a class without placement new support."""
Packit 534379
    with capture:
Packit 534379
        a = m.NoPlacementNew(123)
Packit 534379
Packit 534379
    found = re.search(r'^operator new called, returning (\d+)\n$', str(capture))
Packit 534379
    assert found
Packit 534379
    assert a.i == 123
Packit 534379
    with capture:
Packit 534379
        del a
Packit 534379
        pytest.gc_collect()
Packit 534379
    assert capture == "operator delete called on " + found.group(1)
Packit 534379
Packit 534379
    with capture:
Packit 534379
        b = m.NoPlacementNew()
Packit 534379
Packit 534379
    found = re.search(r'^operator new called, returning (\d+)\n$', str(capture))
Packit 534379
    assert found
Packit 534379
    assert b.i == 100
Packit 534379
    with capture:
Packit 534379
        del b
Packit 534379
        pytest.gc_collect()
Packit 534379
    assert capture == "operator delete called on " + found.group(1)
Packit 534379
Packit 534379
Packit 534379
def test_multiple_inheritance():
Packit 534379
    class MITest(m.TestFactory1, m.TestFactory2):
Packit 534379
        def __init__(self):
Packit 534379
            m.TestFactory1.__init__(self, tag.unique_ptr, 33)
Packit 534379
            m.TestFactory2.__init__(self, tag.move)
Packit 534379
Packit 534379
    a = MITest()
Packit 534379
    assert m.TestFactory1.value.fget(a) == "33"
Packit 534379
    assert m.TestFactory2.value.fget(a) == "(empty2)"
Packit 534379
Packit 534379
Packit 534379
def create_and_destroy(*args):
Packit 534379
    a = m.NoisyAlloc(*args)
Packit 534379
    print("---")
Packit 534379
    del a
Packit 534379
    pytest.gc_collect()
Packit 534379
Packit 534379
Packit 534379
def strip_comments(s):
Packit 534379
    return re.sub(r'\s+#.*', '', s)
Packit 534379
Packit 534379
Packit 534379
def test_reallocations(capture, msg):
Packit 534379
    """When the constructor is overloaded, previous overloads can require a preallocated value.
Packit 534379
    This test makes sure that such preallocated values only happen when they might be necessary,
Packit 534379
    and that they are deallocated properly"""
Packit 534379
Packit 534379
    pytest.gc_collect()
Packit 534379
Packit 534379
    with capture:
Packit 534379
        create_and_destroy(1)
Packit 534379
    assert msg(capture) == """
Packit 534379
        noisy new
Packit 534379
        noisy placement new
Packit 534379
        NoisyAlloc(int 1)
Packit 534379
        ---
Packit 534379
        ~NoisyAlloc()
Packit 534379
        noisy delete
Packit 534379
    """
Packit 534379
    with capture:
Packit 534379
        create_and_destroy(1.5)
Packit 534379
    assert msg(capture) == strip_comments("""
Packit 534379
        noisy new               # allocation required to attempt first overload
Packit 534379
        noisy delete            # have to dealloc before considering factory init overload
Packit 534379
        noisy new               # pointer factory calling "new", part 1: allocation
Packit 534379
        NoisyAlloc(double 1.5)  # ... part two, invoking constructor
Packit 534379
        ---
Packit 534379
        ~NoisyAlloc()  # Destructor
Packit 534379
        noisy delete   # operator delete
Packit 534379
    """)
Packit 534379
Packit 534379
    with capture:
Packit 534379
        create_and_destroy(2, 3)
Packit 534379
    assert msg(capture) == strip_comments("""
Packit 534379
        noisy new          # pointer factory calling "new", allocation
Packit 534379
        NoisyAlloc(int 2)  # constructor
Packit 534379
        ---
Packit 534379
        ~NoisyAlloc()  # Destructor
Packit 534379
        noisy delete   # operator delete
Packit 534379
    """)
Packit 534379
Packit 534379
    with capture:
Packit 534379
        create_and_destroy(2.5, 3)
Packit 534379
    assert msg(capture) == strip_comments("""
Packit 534379
        NoisyAlloc(double 2.5)  # construction (local func variable: operator_new not called)
Packit 534379
        noisy new               # return-by-value "new" part 1: allocation
Packit 534379
        ~NoisyAlloc()           # moved-away local func variable destruction
Packit 534379
        ---
Packit 534379
        ~NoisyAlloc()  # Destructor
Packit 534379
        noisy delete   # operator delete
Packit 534379
    """)
Packit 534379
Packit 534379
    with capture:
Packit 534379
        create_and_destroy(3.5, 4.5)
Packit 534379
    assert msg(capture) == strip_comments("""
Packit 534379
        noisy new               # preallocation needed before invoking placement-new overload
Packit 534379
        noisy placement new     # Placement new
Packit 534379
        NoisyAlloc(double 3.5)  # construction
Packit 534379
        ---
Packit 534379
        ~NoisyAlloc()  # Destructor
Packit 534379
        noisy delete   # operator delete
Packit 534379
    """)
Packit 534379
Packit 534379
    with capture:
Packit 534379
        create_and_destroy(4, 0.5)
Packit 534379
    assert msg(capture) == strip_comments("""
Packit 534379
        noisy new          # preallocation needed before invoking placement-new overload
Packit 534379
        noisy delete       # deallocation of preallocated storage
Packit 534379
        noisy new          # Factory pointer allocation
Packit 534379
        NoisyAlloc(int 4)  # factory pointer construction
Packit 534379
        ---
Packit 534379
        ~NoisyAlloc()  # Destructor
Packit 534379
        noisy delete   # operator delete
Packit 534379
    """)
Packit 534379
Packit 534379
    with capture:
Packit 534379
        create_and_destroy(5, "hi")
Packit 534379
    assert msg(capture) == strip_comments("""
Packit 534379
        noisy new            # preallocation needed before invoking first placement new
Packit 534379
        noisy delete         # delete before considering new-style constructor
Packit 534379
        noisy new            # preallocation for second placement new
Packit 534379
        noisy placement new  # Placement new in the second placement new overload
Packit 534379
        NoisyAlloc(int 5)    # construction
Packit 534379
        ---
Packit 534379
        ~NoisyAlloc()  # Destructor
Packit 534379
        noisy delete   # operator delete
Packit 534379
    """)
Packit 534379
Packit 534379
Packit 534379
@pytest.unsupported_on_py2
Packit 534379
def test_invalid_self():
Packit 534379
    """Tests invocation of the pybind-registered base class with an invalid `self` argument.  You
Packit 534379
    can only actually do this on Python 3: Python 2 raises an exception itself if you try."""
Packit 534379
    class NotPybindDerived(object):
Packit 534379
        pass
Packit 534379
Packit 534379
    # Attempts to initialize with an invalid type passed as `self`:
Packit 534379
    class BrokenTF1(m.TestFactory1):
Packit 534379
        def __init__(self, bad):
Packit 534379
            if bad == 1:
Packit 534379
                a = m.TestFactory2(tag.pointer, 1)
Packit 534379
                m.TestFactory1.__init__(a, tag.pointer)
Packit 534379
            elif bad == 2:
Packit 534379
                a = NotPybindDerived()
Packit 534379
                m.TestFactory1.__init__(a, tag.pointer)
Packit 534379
Packit 534379
    # Same as above, but for a class with an alias:
Packit 534379
    class BrokenTF6(m.TestFactory6):
Packit 534379
        def __init__(self, bad):
Packit 534379
            if bad == 1:
Packit 534379
                a = m.TestFactory2(tag.pointer, 1)
Packit 534379
                m.TestFactory6.__init__(a, tag.base, 1)
Packit 534379
            elif bad == 2:
Packit 534379
                a = m.TestFactory2(tag.pointer, 1)
Packit 534379
                m.TestFactory6.__init__(a, tag.alias, 1)
Packit 534379
            elif bad == 3:
Packit 534379
                m.TestFactory6.__init__(NotPybindDerived.__new__(NotPybindDerived), tag.base, 1)
Packit 534379
            elif bad == 4:
Packit 534379
                m.TestFactory6.__init__(NotPybindDerived.__new__(NotPybindDerived), tag.alias, 1)
Packit 534379
Packit 534379
    for arg in (1, 2):
Packit 534379
        with pytest.raises(TypeError) as excinfo:
Packit 534379
            BrokenTF1(arg)
Packit 534379
        assert str(excinfo.value) == "__init__(self, ...) called with invalid `self` argument"
Packit 534379
Packit 534379
    for arg in (1, 2, 3, 4):
Packit 534379
        with pytest.raises(TypeError) as excinfo:
Packit 534379
            BrokenTF6(arg)
Packit 534379
        assert str(excinfo.value) == "__init__(self, ...) called with invalid `self` argument"