Adding tests to exercise corner cases involving disowning. (#2912)

* Adding test_class_sh_disowning.

* Fixing minor namespace naming inconsistency between test_class_sh_*.cpp files.

* Replacing py::overload_cast with plain cast for C++11 compatibility.

* Accommodate that the C++ order of evaluation of function arguments is unspecified.
This commit is contained in:
Ralf W. Grosse-Kunstleve 2021-03-22 12:16:29 -07:00 committed by GitHub
parent 5319ca3817
commit 08339d6331
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 152 additions and 28 deletions

View File

@ -102,6 +102,7 @@ set(PYBIND11_TEST_FILES
test_chrono.cpp
test_class.cpp
test_class_sh_basic.cpp
test_class_sh_disowning.cpp
test_class_sh_factory_constructors.cpp
test_class_sh_inheritance.cpp
test_class_sh_trampoline_shared_ptr_cpp_arg.cpp

View File

@ -0,0 +1,46 @@
#include "pybind11_tests.h"
#include <pybind11/smart_holder.h>
#include <memory>
namespace pybind11_tests {
namespace class_sh_disowning {
template <int SerNo> // Using int as a trick to easily generate a series of types.
struct Atype {
int val = 0;
Atype(int val_) : val{val_} {}
int get() const { return val * 10 + SerNo; }
};
int same_twice(std::unique_ptr<Atype<1>> at1a, std::unique_ptr<Atype<1>> at1b) {
return at1a->get() * 100 + at1b->get() * 10;
}
int mixed(std::unique_ptr<Atype<1>> at1, std::unique_ptr<Atype<2>> at2) {
return at1->get() * 200 + at2->get() * 20;
}
int overloaded(std::unique_ptr<Atype<1>> at1, int i) { return at1->get() * 30 + i; }
int overloaded(std::unique_ptr<Atype<2>> at2, int i) { return at2->get() * 40 + i; }
} // namespace class_sh_disowning
} // namespace pybind11_tests
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<1>)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<2>)
TEST_SUBMODULE(class_sh_disowning, m) {
using namespace pybind11_tests::class_sh_disowning;
py::classh<Atype<1>>(m, "Atype1").def(py::init<int>()).def("get", &Atype<1>::get);
py::classh<Atype<2>>(m, "Atype2").def(py::init<int>()).def("get", &Atype<2>::get);
m.def("same_twice", same_twice);
m.def("mixed", mixed);
m.def("overloaded", (int (*)(std::unique_ptr<Atype<1>>, int)) & overloaded);
m.def("overloaded", (int (*)(std::unique_ptr<Atype<2>>, int)) & overloaded);
}

View File

@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
from __future__ import print_function
import pytest
from pybind11_tests import class_sh_disowning as m
def test_same_twice():
while True:
obj1a = m.Atype1(57)
obj1b = m.Atype1(62)
assert m.same_twice(obj1a, obj1b) == (57 * 10 + 1) * 100 + (62 * 10 + 1) * 10
obj1c = m.Atype1(0)
with pytest.raises(ValueError):
# Disowning works for one argument, but not both.
m.same_twice(obj1c, obj1c)
with pytest.raises(ValueError):
obj1c.get()
return # Comment out for manual leak checking (use `top` command).
def test_mixed(capsys):
first_pass = True
while True:
obj1a = m.Atype1(90)
obj2a = m.Atype2(25)
assert m.mixed(obj1a, obj2a) == (90 * 10 + 1) * 200 + (25 * 10 + 2) * 20
# The C++ order of evaluation of function arguments is (unfortunately) unspecified:
# https://en.cppreference.com/w/cpp/language/eval_order
# Read on.
obj1b = m.Atype1(0)
with pytest.raises(ValueError):
# If the 1st argument is evaluated first, obj1b is disowned before the conversion for
# the already disowned obj2a fails as expected.
m.mixed(obj1b, obj2a)
obj2b = m.Atype2(0)
with pytest.raises(ValueError):
# If the 2nd argument is evaluated first, obj2b is disowned before the conversion for
# the already disowned obj1a fails as expected.
m.mixed(obj1a, obj2b)
def was_disowned(obj):
try:
obj.get()
except ValueError:
return True
return False
# Either obj1b or obj2b was disowned in the expected failed m.mixed() calls above, but not
# both.
was_disowned_results = (was_disowned(obj1b), was_disowned(obj2b))
assert was_disowned_results.count(True) == 1
if first_pass:
first_pass = False
with capsys.disabled():
print(
"\nC++ function argument %d is evaluated first."
% (was_disowned_results.index(True) + 1)
)
return # Comment out for manual leak checking (use `top` command).
def test_overloaded():
while True:
obj1 = m.Atype1(81)
obj2 = m.Atype2(60)
with pytest.raises(TypeError):
m.overloaded(obj1, "NotInt")
assert obj1.get() == 81 * 10 + 1 # Not disowned.
assert m.overloaded(obj1, 3) == (81 * 10 + 1) * 30 + 3
with pytest.raises(TypeError):
m.overloaded(obj2, "NotInt")
assert obj2.get() == 60 * 10 + 2 # Not disowned.
assert m.overloaded(obj2, 2) == (60 * 10 + 2) * 40 + 2
return # Comment out for manual leak checking (use `top` command).

View File

@ -6,7 +6,7 @@
#include <string>
namespace pybind11_tests {
namespace test_class_sh_factory_constructors {
namespace class_sh_factory_constructors {
template <int> // Using int as a trick to easily generate a series of types.
struct atyp { // Short for "any type".
@ -69,25 +69,25 @@ struct with_alias {
struct with_alias_alias : with_alias {};
struct sddwaa : std::default_delete<with_alias_alias> {};
} // namespace test_class_sh_factory_constructors
} // namespace class_sh_factory_constructors
} // namespace pybind11_tests
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_valu)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_rref)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_cref)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_mref)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_cptr)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_mptr)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_shmp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_shcp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_uqmp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_uqcp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_udmp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_udcp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::with_alias)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_valu)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_rref)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_cref)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_mref)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_cptr)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_mptr)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_shmp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_shcp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_uqmp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_uqcp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_udmp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_udcp)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::with_alias)
TEST_SUBMODULE(class_sh_factory_constructors, m) {
using namespace pybind11_tests::test_class_sh_factory_constructors;
using namespace pybind11_tests::class_sh_factory_constructors;
py::classh<atyp_valu>(m, "atyp_valu")
.def(py::init(&rtrn_valu))

View File

@ -5,7 +5,7 @@
#include <memory>
namespace pybind11_tests {
namespace test_class_sh_virtual_py_cpp_mix {
namespace class_sh_virtual_py_cpp_mix {
class Base {
public:
@ -43,16 +43,15 @@ struct CppDerivedVirtualOverrider : CppDerived, py::virtual_overrider_self_life_
int get() const override { PYBIND11_OVERRIDE(int, CppDerived, get); }
};
} // namespace test_class_sh_virtual_py_cpp_mix
} // namespace class_sh_virtual_py_cpp_mix
} // namespace pybind11_tests
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_virtual_py_cpp_mix::Base)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(
pybind11_tests::test_class_sh_virtual_py_cpp_mix::CppDerivedPlain)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_virtual_py_cpp_mix::CppDerived)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_virtual_py_cpp_mix::Base)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_virtual_py_cpp_mix::CppDerivedPlain)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_virtual_py_cpp_mix::CppDerived)
TEST_SUBMODULE(class_sh_virtual_py_cpp_mix, m) {
using namespace pybind11_tests::test_class_sh_virtual_py_cpp_mix;
using namespace pybind11_tests::class_sh_virtual_py_cpp_mix;
py::classh<Base, BaseVirtualOverrider>(m, "Base").def(py::init<>()).def("get", &Base::get);

View File

@ -5,7 +5,7 @@
#include <memory>
namespace pybind11_tests {
namespace test_class_sh_with_alias {
namespace class_sh_with_alias {
template <int SerNo> // Using int as a trick to easily generate a series of types.
struct Abase {
@ -73,14 +73,14 @@ void wrap(py::module_ m, const char *py_class_name) {
m.def("AddInCppUniquePtr", AddInCppUniquePtr<SerNo>, py::arg("obj"), py::arg("other_val"));
}
} // namespace test_class_sh_with_alias
} // namespace class_sh_with_alias
} // namespace pybind11_tests
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_with_alias::Abase<0>)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_with_alias::Abase<1>)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_with_alias::Abase<0>)
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_with_alias::Abase<1>)
TEST_SUBMODULE(class_sh_with_alias, m) {
using namespace pybind11_tests::test_class_sh_with_alias;
using namespace pybind11_tests::class_sh_with_alias;
wrap<0>(m, "Abase0");
wrap<1>(m, "Abase1");
}