diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst index 5617a4cb2..87bbe2427 100644 --- a/docs/advanced/classes.rst +++ b/docs/advanced/classes.rst @@ -325,7 +325,7 @@ ensuring member initialization and (eventual) destruction. .. seealso:: - See the file :file:`tests/test_alias_initialization.cpp` for complete examples + See the file :file:`tests/test_virtual_functions.cpp` for complete examples showing both normal and forced trampoline instantiation. .. _custom_constructors: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a57896d44..bc98c7613 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -26,7 +26,6 @@ endif() # Full set of test files (you can override these; see below) set(PYBIND11_TEST_FILES - test_alias_initialization.cpp test_buffers.cpp test_builtin_casters.cpp test_call_policies.cpp @@ -40,7 +39,6 @@ set(PYBIND11_TEST_FILES test_enum.cpp test_eval.cpp test_exceptions.cpp - test_inheritance.cpp test_kwargs_and_defaults.cpp test_methods_and_attributes.cpp test_modules.cpp diff --git a/tests/test_alias_initialization.cpp b/tests/test_alias_initialization.cpp deleted file mode 100644 index 48e595695..000000000 --- a/tests/test_alias_initialization.cpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - tests/test_alias_initialization.cpp -- test cases and example of different trampoline - initialization modes - - Copyright (c) 2016 Wenzel Jakob , Jason Rhinelander - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#include "pybind11_tests.h" - -test_initializer alias_initialization([](py::module &m) { - // don't invoke Python dispatch classes by default when instantiating C++ classes that were not - // extended on the Python side - struct A { - virtual ~A() {} - virtual void f() { py::print("A.f()"); } - }; - - struct PyA : A { - PyA() { py::print("PyA.PyA()"); } - ~PyA() { py::print("PyA.~PyA()"); } - - void f() override { - py::print("PyA.f()"); - PYBIND11_OVERLOAD(void, A, f); - } - }; - - auto call_f = [](A *a) { a->f(); }; - - py::class_(m, "A") - .def(py::init<>()) - .def("f", &A::f); - - m.def("call_f", call_f); - - - // ... unless we explicitly request it, as in this example: - struct A2 { - virtual ~A2() {} - virtual void f() { py::print("A2.f()"); } - }; - - struct PyA2 : A2 { - PyA2() { py::print("PyA2.PyA2()"); } - ~PyA2() { py::print("PyA2.~PyA2()"); } - void f() override { - py::print("PyA2.f()"); - PYBIND11_OVERLOAD(void, A2, f); - } - }; - - py::class_(m, "A2") - .def(py::init_alias<>()) - .def("f", &A2::f); - - m.def("call_f", [](A2 *a2) { a2->f(); }); - -}); - diff --git a/tests/test_alias_initialization.py b/tests/test_alias_initialization.py deleted file mode 100644 index fb90cfc7b..000000000 --- a/tests/test_alias_initialization.py +++ /dev/null @@ -1,80 +0,0 @@ -import pytest - - -def test_alias_delay_initialization1(capture): - """ - A only initializes its trampoline class when we inherit from it; if we just - create and use an A instance directly, the trampoline initialization is - bypassed and we only initialize an A() instead (for performance reasons). - """ - from pybind11_tests import A, call_f - - class B(A): - def __init__(self): - super(B, self).__init__() - - def f(self): - print("In python f()") - - # C++ version - with capture: - a = A() - call_f(a) - del a - pytest.gc_collect() - assert capture == "A.f()" - - # Python version - with capture: - b = B() - call_f(b) - del b - pytest.gc_collect() - assert capture == """ - PyA.PyA() - PyA.f() - In python f() - PyA.~PyA() - """ - - -def test_alias_delay_initialization2(capture): - """A2, unlike the above, is configured to always initialize the alias; while - the extra initialization and extra class layer has small virtual dispatch - performance penalty, it also allows us to do more things with the trampoline - class such as defining local variables and performing construction/destruction. - """ - from pybind11_tests import A2, call_f - - class B2(A2): - def __init__(self): - super(B2, self).__init__() - - def f(self): - print("In python B2.f()") - - # No python subclass version - with capture: - a2 = A2() - call_f(a2) - del a2 - pytest.gc_collect() - assert capture == """ - PyA2.PyA2() - PyA2.f() - A2.f() - PyA2.~PyA2() - """ - - # Python subclass version - with capture: - b2 = B2() - call_f(b2) - del b2 - pytest.gc_collect() - assert capture == """ - PyA2.PyA2() - PyA2.f() - In python B2.f() - PyA2.~PyA2() - """ diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 1af9740e9..4e0dd6237 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -23,6 +23,133 @@ TEST_SUBMODULE(class_, m) { py::class_(m, "NoConstructor") .def_static("new_instance", &NoConstructor::new_instance, "Return an instance"); + + // test_inheritance + class Pet { + public: + Pet(const std::string &name, const std::string &species) + : m_name(name), m_species(species) {} + std::string name() const { return m_name; } + std::string species() const { return m_species; } + private: + std::string m_name; + std::string m_species; + }; + + class Dog : public Pet { + public: + Dog(const std::string &name) : Pet(name, "dog") {} + std::string bark() const { return "Woof!"; } + }; + + class Rabbit : public Pet { + public: + Rabbit(const std::string &name) : Pet(name, "parrot") {} + }; + + class Hamster : public Pet { + public: + Hamster(const std::string &name) : Pet(name, "rodent") {} + }; + + class Chimera : public Pet { + Chimera() : Pet("Kimmy", "chimera") {} + }; + + py::class_ pet_class(m, "Pet"); + pet_class + .def(py::init()) + .def("name", &Pet::name) + .def("species", &Pet::species); + + /* One way of declaring a subclass relationship: reference parent's class_ object */ + py::class_(m, "Dog", pet_class) + .def(py::init()); + + /* Another way of declaring a subclass relationship: reference parent's C++ type */ + py::class_(m, "Rabbit") + .def(py::init()); + + /* And another: list parent in class template arguments */ + py::class_(m, "Hamster") + .def(py::init()); + + /* Constructors are not inherited by default */ + py::class_(m, "Chimera"); + + m.def("pet_name_species", [](const Pet &pet) { return pet.name() + " is a " + pet.species(); }); + m.def("dog_bark", [](const Dog &dog) { return dog.bark(); }); + + // test_automatic_upcasting + struct BaseClass { virtual ~BaseClass() {} }; + struct DerivedClass1 : BaseClass { }; + struct DerivedClass2 : BaseClass { }; + + py::class_(m, "BaseClass").def(py::init<>()); + py::class_(m, "DerivedClass1").def(py::init<>()); + py::class_(m, "DerivedClass2").def(py::init<>()); + + m.def("return_class_1", []() -> BaseClass* { return new DerivedClass1(); }); + m.def("return_class_2", []() -> BaseClass* { return new DerivedClass2(); }); + m.def("return_class_n", [](int n) -> BaseClass* { + if (n == 1) return new DerivedClass1(); + if (n == 2) return new DerivedClass2(); + return new BaseClass(); + }); + m.def("return_none", []() -> BaseClass* { return nullptr; }); + + // test_isinstance + m.def("check_instances", [](py::list l) { + return py::make_tuple( + py::isinstance(l[0]), + py::isinstance(l[1]), + py::isinstance(l[2]), + py::isinstance(l[3]), + py::isinstance(l[4]), + py::isinstance(l[5]), + py::isinstance(l[6]) + ); + }); + + // test_mismatched_holder + struct MismatchBase1 { }; + struct MismatchDerived1 : MismatchBase1 { }; + + struct MismatchBase2 { }; + struct MismatchDerived2 : MismatchBase2 { }; + + m.def("mismatched_holder_1", []() { + auto mod = py::module::import("__main__"); + py::class_>(mod, "MismatchBase1"); + py::class_(mod, "MismatchDerived1"); + }); + m.def("mismatched_holder_2", []() { + auto mod = py::module::import("__main__"); + py::class_(mod, "MismatchBase2"); + py::class_, + MismatchBase2>(mod, "MismatchDerived2"); + }); + + // test_override_static + // #511: problem with inheritance + overwritten def_static + struct MyBase { + static std::unique_ptr make() { + return std::unique_ptr(new MyBase()); + } + }; + + struct MyDerived : MyBase { + static std::unique_ptr make() { + return std::unique_ptr(new MyDerived()); + } + }; + + py::class_(m, "MyBase") + .def_static("make", &MyBase::make); + + py::class_(m, "MyDerived") + .def_static("make", &MyDerived::make) + .def_static("make2", &MyDerived::make); } template class BreaksBase {}; diff --git a/tests/test_class.py b/tests/test_class.py index 6e8491374..13c778f22 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -42,3 +42,80 @@ def test_docstrings(doc): Return an instance """ + + +def test_inheritance(msg): + roger = m.Rabbit('Rabbit') + assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot" + assert m.pet_name_species(roger) == "Rabbit is a parrot" + + polly = m.Pet('Polly', 'parrot') + assert polly.name() + " is a " + polly.species() == "Polly is a parrot" + assert m.pet_name_species(polly) == "Polly is a parrot" + + molly = m.Dog('Molly') + assert molly.name() + " is a " + molly.species() == "Molly is a dog" + assert m.pet_name_species(molly) == "Molly is a dog" + + fred = m.Hamster('Fred') + assert fred.name() + " is a " + fred.species() == "Fred is a rodent" + + assert m.dog_bark(molly) == "Woof!" + + with pytest.raises(TypeError) as excinfo: + m.dog_bark(polly) + assert msg(excinfo.value) == """ + dog_bark(): incompatible function arguments. The following argument types are supported: + 1. (arg0: m.class_.Dog) -> str + + Invoked with: + """ + + with pytest.raises(TypeError) as excinfo: + m.Chimera("lion", "goat") + assert "No constructor defined!" in str(excinfo.value) + + +def test_automatic_upcasting(): + assert type(m.return_class_1()).__name__ == "DerivedClass1" + assert type(m.return_class_2()).__name__ == "DerivedClass2" + assert type(m.return_none()).__name__ == "NoneType" + # Repeat these a few times in a random order to ensure no invalid caching is applied + assert type(m.return_class_n(1)).__name__ == "DerivedClass1" + assert type(m.return_class_n(2)).__name__ == "DerivedClass2" + assert type(m.return_class_n(0)).__name__ == "BaseClass" + assert type(m.return_class_n(2)).__name__ == "DerivedClass2" + assert type(m.return_class_n(2)).__name__ == "DerivedClass2" + assert type(m.return_class_n(0)).__name__ == "BaseClass" + assert type(m.return_class_n(1)).__name__ == "DerivedClass1" + + +def test_isinstance(): + objects = [tuple(), dict(), m.Pet("Polly", "parrot")] + [m.Dog("Molly")] * 4 + expected = (True, True, True, True, True, False, False) + assert m.check_instances(objects) == expected + + +def test_mismatched_holder(): + import re + + with pytest.raises(RuntimeError) as excinfo: + m.mismatched_holder_1() + assert re.match('generic_type: type ".*MismatchDerived1" does not have a non-default ' + 'holder type while its base ".*MismatchBase1" does', str(excinfo.value)) + + with pytest.raises(RuntimeError) as excinfo: + m.mismatched_holder_2() + assert re.match('generic_type: type ".*MismatchDerived2" has a non-default holder type ' + 'while its base ".*MismatchBase2" does not', str(excinfo.value)) + + +def test_override_static(): + """#511: problem with inheritance + overwritten def_static""" + b = m.MyBase.make() + d1 = m.MyDerived.make2() + d2 = m.MyDerived.make() + + assert isinstance(b, m.MyBase) + assert isinstance(d1, m.MyDerived) + assert isinstance(d2, m.MyDerived) diff --git a/tests/test_inheritance.cpp b/tests/test_inheritance.cpp deleted file mode 100644 index fce537d22..000000000 --- a/tests/test_inheritance.cpp +++ /dev/null @@ -1,141 +0,0 @@ -/* - tests/test_inheritance.cpp -- inheritance, automatic upcasting for polymorphic types - - Copyright (c) 2016 Wenzel Jakob - - All rights reserved. Use of this source code is governed by a - BSD-style license that can be found in the LICENSE file. -*/ - -#include "pybind11_tests.h" - -class Pet { -public: - Pet(const std::string &name, const std::string &species) - : m_name(name), m_species(species) {} - std::string name() const { return m_name; } - std::string species() const { return m_species; } -private: - std::string m_name; - std::string m_species; -}; - -class Dog : public Pet { -public: - Dog(const std::string &name) : Pet(name, "dog") {} - std::string bark() const { return "Woof!"; } -}; - -class Rabbit : public Pet { -public: - Rabbit(const std::string &name) : Pet(name, "parrot") {} -}; - -class Hamster : public Pet { -public: - Hamster(const std::string &name) : Pet(name, "rodent") {} -}; - -class Chimera : public Pet { - Chimera() : Pet("Kimmy", "chimera") {} -}; - -std::string pet_name_species(const Pet &pet) { - return pet.name() + " is a " + pet.species(); -} - -std::string dog_bark(const Dog &dog) { - return dog.bark(); -} - - -struct BaseClass { virtual ~BaseClass() {} }; -struct DerivedClass1 : BaseClass { }; -struct DerivedClass2 : BaseClass { }; - -struct MismatchBase1 { }; -struct MismatchDerived1 : MismatchBase1 { }; - -struct MismatchBase2 { }; -struct MismatchDerived2 : MismatchBase2 { }; - -test_initializer inheritance([](py::module &m) { - py::class_ pet_class(m, "Pet"); - pet_class - .def(py::init()) - .def("name", &Pet::name) - .def("species", &Pet::species); - - /* One way of declaring a subclass relationship: reference parent's class_ object */ - py::class_(m, "Dog", pet_class) - .def(py::init()); - - /* Another way of declaring a subclass relationship: reference parent's C++ type */ - py::class_(m, "Rabbit") - .def(py::init()); - - /* And another: list parent in class template arguments */ - py::class_(m, "Hamster") - .def(py::init()); - - py::class_(m, "Chimera"); - - m.def("pet_name_species", pet_name_species); - m.def("dog_bark", dog_bark); - - py::class_(m, "BaseClass").def(py::init<>()); - py::class_(m, "DerivedClass1").def(py::init<>()); - py::class_(m, "DerivedClass2").def(py::init<>()); - - m.def("return_class_1", []() -> BaseClass* { return new DerivedClass1(); }); - m.def("return_class_2", []() -> BaseClass* { return new DerivedClass2(); }); - m.def("return_class_n", [](int n) -> BaseClass* { - if (n == 1) return new DerivedClass1(); - if (n == 2) return new DerivedClass2(); - return new BaseClass(); - }); - m.def("return_none", []() -> BaseClass* { return nullptr; }); - - m.def("test_isinstance", [](py::list l) { - return py::make_tuple( - py::isinstance(l[0]), - py::isinstance(l[1]), - py::isinstance(l[2]), - py::isinstance(l[3]), - py::isinstance(l[4]), - py::isinstance(l[5]), - py::isinstance(l[6]) - ); - }); - - m.def("test_mismatched_holder_type_1", []() { - auto m = py::module::import("__main__"); - py::class_>(m, "MismatchBase1"); - py::class_(m, "MismatchDerived1"); - }); - m.def("test_mismatched_holder_type_2", []() { - auto m = py::module::import("__main__"); - py::class_(m, "MismatchBase2"); - py::class_, MismatchBase2>(m, "MismatchDerived2"); - }); - - // #511: problem with inheritance + overwritten def_static - struct MyBase { - static std::unique_ptr make() { - return std::unique_ptr(new MyBase()); - } - }; - - struct MyDerived : MyBase { - static std::unique_ptr make() { - return std::unique_ptr(new MyDerived()); - } - }; - - py::class_(m, "MyBase") - .def_static("make", &MyBase::make); - - py::class_(m, "MyDerived") - .def_static("make", &MyDerived::make) - .def_static("make2", &MyDerived::make); -}); diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py deleted file mode 100644 index 40630ae2e..000000000 --- a/tests/test_inheritance.py +++ /dev/null @@ -1,91 +0,0 @@ -import pytest - - -def test_inheritance(msg): - from pybind11_tests import Pet, Dog, Rabbit, Hamster, Chimera, dog_bark, pet_name_species - - roger = Rabbit('Rabbit') - assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot" - assert pet_name_species(roger) == "Rabbit is a parrot" - - polly = Pet('Polly', 'parrot') - assert polly.name() + " is a " + polly.species() == "Polly is a parrot" - assert pet_name_species(polly) == "Polly is a parrot" - - molly = Dog('Molly') - assert molly.name() + " is a " + molly.species() == "Molly is a dog" - assert pet_name_species(molly) == "Molly is a dog" - - fred = Hamster('Fred') - assert fred.name() + " is a " + fred.species() == "Fred is a rodent" - - assert dog_bark(molly) == "Woof!" - - with pytest.raises(TypeError) as excinfo: - dog_bark(polly) - assert msg(excinfo.value) == """ - dog_bark(): incompatible function arguments. The following argument types are supported: - 1. (arg0: m.Dog) -> str - - Invoked with: - """ - - with pytest.raises(TypeError) as excinfo: - Chimera("lion", "goat") - assert "No constructor defined!" in str(excinfo.value) - - -def test_automatic_upcasting(): - from pybind11_tests import return_class_1, return_class_2, return_class_n, return_none - - assert type(return_class_1()).__name__ == "DerivedClass1" - assert type(return_class_2()).__name__ == "DerivedClass2" - assert type(return_none()).__name__ == "NoneType" - # Repeat these a few times in a random order to ensure no invalid caching - # is applied - assert type(return_class_n(1)).__name__ == "DerivedClass1" - assert type(return_class_n(2)).__name__ == "DerivedClass2" - assert type(return_class_n(0)).__name__ == "BaseClass" - assert type(return_class_n(2)).__name__ == "DerivedClass2" - assert type(return_class_n(2)).__name__ == "DerivedClass2" - assert type(return_class_n(0)).__name__ == "BaseClass" - assert type(return_class_n(1)).__name__ == "DerivedClass1" - - -def test_isinstance(): - from pybind11_tests import test_isinstance, Pet, Dog - - objects = [tuple(), dict(), Pet("Polly", "parrot")] + [Dog("Molly")] * 4 - expected = (True, True, True, True, True, False, False) - assert test_isinstance(objects) == expected - - -def test_holder(): - from pybind11_tests import test_mismatched_holder_type_1, test_mismatched_holder_type_2 - - with pytest.raises(RuntimeError) as excinfo: - test_mismatched_holder_type_1() - - assert str(excinfo.value) == ("generic_type: type \"MismatchDerived1\" does not have " - "a non-default holder type while its base " - "\"MismatchBase1\" does") - - with pytest.raises(RuntimeError) as excinfo: - test_mismatched_holder_type_2() - - assert str(excinfo.value) == ("generic_type: type \"MismatchDerived2\" has a " - "non-default holder type while its base " - "\"MismatchBase2\" does not") - - -def test_inheritance_override_def_static(): - """#511: problem with inheritance + overwritten def_static""" - from pybind11_tests import MyBase, MyDerived - - b = MyBase.make() - d1 = MyDerived.make2() - d2 = MyDerived.make() - - assert isinstance(b, MyBase) - assert isinstance(d1, MyDerived) - assert isinstance(d2, MyDerived) diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index b42f650b5..899bba666 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -322,7 +322,7 @@ struct DispatchIssue : Base { } }; -test_initializer virtual_functions([](py::module &m) { +TEST_SUBMODULE(virtual_functions, m) { py::class_(m, "ExampleVirt") .def(py::init()) /* Reference original class in function definitions */ @@ -352,6 +352,52 @@ test_initializer virtual_functions([](py::module &m) { m.def("cstats_debug", &ConstructorStats::get); initialize_inherited_virtuals(m); + // test_alias_delay_initialization1 + // don't invoke Python dispatch classes by default when instantiating C++ classes + // that were not extended on the Python side + struct A { + virtual ~A() {} + virtual void f() { py::print("A.f()"); } + }; + + struct PyA : A { + PyA() { py::print("PyA.PyA()"); } + ~PyA() { py::print("PyA.~PyA()"); } + + void f() override { + py::print("PyA.f()"); + PYBIND11_OVERLOAD(void, A, f); + } + }; + + py::class_(m, "A") + .def(py::init<>()) + .def("f", &A::f); + + m.def("call_f", [](A *a) { a->f(); }); + + // test_alias_delay_initialization2 + // ... unless we explicitly request it, as in this example: + struct A2 { + virtual ~A2() {} + virtual void f() { py::print("A2.f()"); } + }; + + struct PyA2 : A2 { + PyA2() { py::print("PyA2.PyA2()"); } + ~PyA2() { py::print("PyA2.~PyA2()"); } + void f() override { + py::print("PyA2.f()"); + PYBIND11_OVERLOAD(void, A2, f); + } + }; + + py::class_(m, "A2") + .def(py::init_alias<>()) + .def("f", &A2::f); + + m.def("call_f", [](A2 *a2) { a2->f(); }); + // #159: virtual function dispatch has problems with similar-named functions py::class_(m, "DispatchIssue") .def(py::init<>()) @@ -398,4 +444,4 @@ test_initializer virtual_functions([](py::module &m) { // .def("str_ref", &OverrideTest::str_ref) .def("A_value", &OverrideTest::A_value) .def("A_ref", &OverrideTest::A_ref); -}); +} diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py index 3ec6fc230..7d1698dab 100644 --- a/tests/test_virtual_functions.py +++ b/tests/test_virtual_functions.py @@ -1,13 +1,11 @@ import pytest -import pybind11_tests + +from pybind11_tests import virtual_functions as m from pybind11_tests import ConstructorStats def test_override(capture, msg): - from pybind11_tests import (ExampleVirt, runExampleVirt, runExampleVirtVirtual, - runExampleVirtBool) - - class ExtendedExampleVirt(ExampleVirt): + class ExtendedExampleVirt(m.ExampleVirt): def __init__(self, state): super(ExtendedExampleVirt, self).__init__(state + 1) self.data = "Hello world" @@ -33,40 +31,40 @@ def test_override(capture, msg): def get_string2(self): return "override2" - ex12 = ExampleVirt(10) + ex12 = m.ExampleVirt(10) with capture: - assert runExampleVirt(ex12, 20) == 30 + assert m.runExampleVirt(ex12, 20) == 30 assert capture == """ Original implementation of ExampleVirt::run(state=10, value=20, str1=default1, str2=default2) """ # noqa: E501 line too long with pytest.raises(RuntimeError) as excinfo: - runExampleVirtVirtual(ex12) + m.runExampleVirtVirtual(ex12) assert msg(excinfo.value) == 'Tried to call pure virtual function "ExampleVirt::pure_virtual"' ex12p = ExtendedExampleVirt(10) with capture: - assert runExampleVirt(ex12p, 20) == 32 + assert m.runExampleVirt(ex12p, 20) == 32 assert capture == """ ExtendedExampleVirt::run(20), calling parent.. Original implementation of ExampleVirt::run(state=11, value=21, str1=override1, str2=default2) """ # noqa: E501 line too long with capture: - assert runExampleVirtBool(ex12p) is False + assert m.runExampleVirtBool(ex12p) is False assert capture == "ExtendedExampleVirt::run_bool()" with capture: - runExampleVirtVirtual(ex12p) + m.runExampleVirtVirtual(ex12p) assert capture == "ExtendedExampleVirt::pure_virtual(): Hello world" ex12p2 = ExtendedExampleVirt2(15) with capture: - assert runExampleVirt(ex12p2, 50) == 68 + assert m.runExampleVirt(ex12p2, 50) == 68 assert capture == """ ExtendedExampleVirt::run(50), calling parent.. Original implementation of ExampleVirt::run(state=17, value=51, str1=override1, str2=override2) """ # noqa: E501 line too long - cstats = ConstructorStats.get(ExampleVirt) + cstats = ConstructorStats.get(m.ExampleVirt) assert cstats.alive() == 3 del ex12, ex12p, ex12p2 assert cstats.alive() == 0 @@ -75,14 +73,88 @@ def test_override(capture, msg): assert cstats.move_constructions >= 0 -def test_inheriting_repeat(): - from pybind11_tests import A_Repeat, B_Repeat, C_Repeat, D_Repeat, A_Tpl, B_Tpl, C_Tpl, D_Tpl +def test_alias_delay_initialization1(capture): + """`A` only initializes its trampoline class when we inherit from it - class AR(A_Repeat): + If we just create and use an A instance directly, the trampoline initialization is + bypassed and we only initialize an A() instead (for performance reasons). + """ + class B(m.A): + def __init__(self): + super(B, self).__init__() + + def f(self): + print("In python f()") + + # C++ version + with capture: + a = m.A() + m.call_f(a) + del a + pytest.gc_collect() + assert capture == "A.f()" + + # Python version + with capture: + b = B() + m.call_f(b) + del b + pytest.gc_collect() + assert capture == """ + PyA.PyA() + PyA.f() + In python f() + PyA.~PyA() + """ + + +def test_alias_delay_initialization2(capture): + """`A2`, unlike the above, is configured to always initialize the alias + + While the extra initialization and extra class layer has small virtual dispatch + performance penalty, it also allows us to do more things with the trampoline + class such as defining local variables and performing construction/destruction. + """ + class B2(m.A2): + def __init__(self): + super(B2, self).__init__() + + def f(self): + print("In python B2.f()") + + # No python subclass version + with capture: + a2 = m.A2() + m.call_f(a2) + del a2 + pytest.gc_collect() + assert capture == """ + PyA2.PyA2() + PyA2.f() + A2.f() + PyA2.~PyA2() + """ + + # Python subclass version + with capture: + b2 = B2() + m.call_f(b2) + del b2 + pytest.gc_collect() + assert capture == """ + PyA2.PyA2() + PyA2.f() + In python B2.f() + PyA2.~PyA2() + """ + + +def test_inheriting_repeat(): + class AR(m.A_Repeat): def unlucky_number(self): return 99 - class AT(A_Tpl): + class AT(m.A_Tpl): def unlucky_number(self): return 999 @@ -96,21 +168,21 @@ def test_inheriting_repeat(): assert obj.unlucky_number() == 999 assert obj.say_everything() == "hi 999" - for obj in [B_Repeat(), B_Tpl()]: + for obj in [m.B_Repeat(), m.B_Tpl()]: assert obj.say_something(3) == "B says hi 3 times" assert obj.unlucky_number() == 13 assert obj.lucky_number() == 7.0 assert obj.say_everything() == "B says hi 1 times 13" - for obj in [C_Repeat(), C_Tpl()]: + for obj in [m.C_Repeat(), m.C_Tpl()]: assert obj.say_something(3) == "B says hi 3 times" assert obj.unlucky_number() == 4444 assert obj.lucky_number() == 888.0 assert obj.say_everything() == "B says hi 1 times 4444" - class CR(C_Repeat): + class CR(m.C_Repeat): def lucky_number(self): - return C_Repeat.lucky_number(self) + 1.25 + return m.C_Repeat.lucky_number(self) + 1.25 obj = CR() assert obj.say_something(3) == "B says hi 3 times" @@ -118,7 +190,7 @@ def test_inheriting_repeat(): assert obj.lucky_number() == 889.25 assert obj.say_everything() == "B says hi 1 times 4444" - class CT(C_Tpl): + class CT(m.C_Tpl): pass obj = CT() @@ -147,14 +219,14 @@ def test_inheriting_repeat(): assert obj.lucky_number() == 888000.0 assert obj.say_everything() == "B says hi 1 times 4444" - class DR(D_Repeat): + class DR(m.D_Repeat): def unlucky_number(self): return 123 def lucky_number(self): return 42.0 - for obj in [D_Repeat(), D_Tpl()]: + for obj in [m.D_Repeat(), m.D_Tpl()]: assert obj.say_something(3) == "B says hi 3 times" assert obj.unlucky_number() == 4444 assert obj.lucky_number() == 888.0 @@ -166,7 +238,7 @@ def test_inheriting_repeat(): assert obj.lucky_number() == 42.0 assert obj.say_everything() == "B says hi 1 times 123" - class DT(D_Tpl): + class DT(m.D_Tpl): def say_something(self, times): return "DT says:" + (' quack' * times) @@ -189,7 +261,7 @@ def test_inheriting_repeat(): def unlucky_number(self): return -3 - class BT(B_Tpl): + class BT(m.B_Tpl): def say_something(self, times): return "BT" * times @@ -209,31 +281,28 @@ def test_inheriting_repeat(): # PyPy: Reference count > 1 causes call with noncopyable instance # to fail in ncv1.print_nc() @pytest.unsupported_on_pypy -@pytest.mark.skipif(not hasattr(pybind11_tests, 'NCVirt'), - reason="NCVirt test broken on ICPC") +@pytest.mark.skipif(not hasattr(m, "NCVirt"), reason="NCVirt test broken on ICPC") def test_move_support(): - from pybind11_tests import NCVirt, NonCopyable, Movable - - class NCVirtExt(NCVirt): + class NCVirtExt(m.NCVirt): def get_noncopyable(self, a, b): # Constructs and returns a new instance: - nc = NonCopyable(a * a, b * b) + nc = m.NonCopyable(a * a, b * b) return nc def get_movable(self, a, b): # Return a referenced copy - self.movable = Movable(a, b) + self.movable = m.Movable(a, b) return self.movable - class NCVirtExt2(NCVirt): + class NCVirtExt2(m.NCVirt): def get_noncopyable(self, a, b): # Keep a reference: this is going to throw an exception - self.nc = NonCopyable(a, b) + self.nc = m.NonCopyable(a, b) return self.nc def get_movable(self, a, b): # Return a new instance without storing it - return Movable(a, b) + return m.Movable(a, b) ncv1 = NCVirtExt() assert ncv1.print_nc(2, 3) == "36" @@ -244,8 +313,8 @@ def test_move_support(): with pytest.raises(RuntimeError): ncv2.print_nc(9, 9) - nc_stats = ConstructorStats.get(NonCopyable) - mv_stats = ConstructorStats.get(Movable) + nc_stats = ConstructorStats.get(m.NonCopyable) + mv_stats = ConstructorStats.get(m.Movable) assert nc_stats.alive() == 1 assert mv_stats.alive() == 1 del ncv1, ncv2 @@ -261,30 +330,26 @@ def test_move_support(): def test_dispatch_issue(msg): """#159: virtual function dispatch has problems with similar-named functions""" - from pybind11_tests import DispatchIssue, dispatch_issue_go - - class PyClass1(DispatchIssue): + class PyClass1(m.DispatchIssue): def dispatch(self): return "Yay.." - class PyClass2(DispatchIssue): + class PyClass2(m.DispatchIssue): def dispatch(self): with pytest.raises(RuntimeError) as excinfo: super(PyClass2, self).dispatch() assert msg(excinfo.value) == 'Tried to call pure virtual function "Base::dispatch"' p = PyClass1() - return dispatch_issue_go(p) + return m.dispatch_issue_go(p) b = PyClass2() - assert dispatch_issue_go(b) == "Yay.." + assert m.dispatch_issue_go(b) == "Yay.." def test_override_ref(): """#392/397: overridding reference-returning functions""" - from pybind11_tests import OverrideTest - - o = OverrideTest("asdf") + o = m.OverrideTest("asdf") # Not allowed (see associated .cpp comment) # i = o.str_ref()