mirror of
https://github.com/pybind/pybind11.git
synced 2025-01-31 15:20:34 +00:00
Bring in all tests/test_class_*.cpp,py from smart_holder branch as-is that pass without any further changes.
All unit tests pass ASAN and MSAN testing with the Google-internal toolchain.
This commit is contained in:
parent
470a765804
commit
d9d96118e6
46
tests/test_class_sh_disowning.cpp
Normal file
46
tests/test_class_sh_disowning.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.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;
|
||||
explicit 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);
|
||||
}
|
76
tests/test_class_sh_disowning.py
Normal file
76
tests/test_class_sh_disowning.py
Normal file
@ -0,0 +1,76 @@
|
||||
from __future__ import annotations
|
||||
|
||||
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():
|
||||
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 is_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.
|
||||
is_disowned_results = (is_disowned(obj1b), is_disowned(obj2b))
|
||||
assert is_disowned_results.count(True) == 1
|
||||
if first_pass:
|
||||
first_pass = False
|
||||
print(
|
||||
"\nC++ function argument %d is evaluated first."
|
||||
% (is_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).
|
105
tests/test_class_sh_inheritance.cpp
Normal file
105
tests/test_class_sh_inheritance.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_inheritance {
|
||||
|
||||
template <int Id>
|
||||
struct base_template {
|
||||
base_template() : base_id(Id) {}
|
||||
virtual ~base_template() = default;
|
||||
virtual int id() const { return base_id; }
|
||||
int base_id;
|
||||
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
base_template(const base_template &) = default;
|
||||
base_template(base_template &&) noexcept = default;
|
||||
base_template &operator=(const base_template &) = default;
|
||||
base_template &operator=(base_template &&) noexcept = default;
|
||||
};
|
||||
|
||||
using base = base_template<100>;
|
||||
|
||||
struct drvd : base {
|
||||
int id() const override { return 2 * base_id; }
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
inline drvd *rtrn_mptr_drvd() { return new drvd; }
|
||||
inline base *rtrn_mptr_drvd_up_cast() { return new drvd; }
|
||||
|
||||
inline int pass_cptr_base(base const *b) { return b->id() + 11; }
|
||||
inline int pass_cptr_drvd(drvd const *d) { return d->id() + 12; }
|
||||
|
||||
inline std::shared_ptr<drvd> rtrn_shmp_drvd() { return std::make_shared<drvd>(); }
|
||||
inline std::shared_ptr<base> rtrn_shmp_drvd_up_cast() { return std::make_shared<drvd>(); }
|
||||
|
||||
inline int pass_shcp_base(const std::shared_ptr<base const>& b) { return b->id() + 21; }
|
||||
inline int pass_shcp_drvd(const std::shared_ptr<drvd const>& d) { return d->id() + 22; }
|
||||
// clang-format on
|
||||
|
||||
using base1 = base_template<110>;
|
||||
using base2 = base_template<120>;
|
||||
|
||||
// Not reusing base here because it would interfere with the single-inheritance test.
|
||||
struct drvd2 : base1, base2 {
|
||||
int id() const override { return 3 * base1::base_id + 4 * base2::base_id; }
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
inline drvd2 *rtrn_mptr_drvd2() { return new drvd2; }
|
||||
inline base1 *rtrn_mptr_drvd2_up_cast1() { return new drvd2; }
|
||||
inline base2 *rtrn_mptr_drvd2_up_cast2() { return new drvd2; }
|
||||
|
||||
inline int pass_cptr_base1(base1 const *b) { return b->id() + 21; }
|
||||
inline int pass_cptr_base2(base2 const *b) { return b->id() + 22; }
|
||||
inline int pass_cptr_drvd2(drvd2 const *d) { return d->id() + 23; }
|
||||
// clang-format on
|
||||
|
||||
} // namespace class_sh_inheritance
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::base)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::drvd)
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::base1)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::base2)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_inheritance::drvd2)
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_inheritance {
|
||||
|
||||
TEST_SUBMODULE(class_sh_inheritance, m) {
|
||||
py::classh<base>(m, "base");
|
||||
py::classh<drvd, base>(m, "drvd");
|
||||
|
||||
auto rvto = py::return_value_policy::take_ownership;
|
||||
|
||||
m.def("rtrn_mptr_drvd", rtrn_mptr_drvd, rvto);
|
||||
m.def("rtrn_mptr_drvd_up_cast", rtrn_mptr_drvd_up_cast, rvto);
|
||||
m.def("pass_cptr_base", pass_cptr_base);
|
||||
m.def("pass_cptr_drvd", pass_cptr_drvd);
|
||||
|
||||
m.def("rtrn_shmp_drvd", rtrn_shmp_drvd);
|
||||
m.def("rtrn_shmp_drvd_up_cast", rtrn_shmp_drvd_up_cast);
|
||||
m.def("pass_shcp_base", pass_shcp_base);
|
||||
m.def("pass_shcp_drvd", pass_shcp_drvd);
|
||||
|
||||
// __init__ needed for Python inheritance.
|
||||
py::classh<base1>(m, "base1").def(py::init<>());
|
||||
py::classh<base2>(m, "base2").def(py::init<>());
|
||||
py::classh<drvd2, base1, base2>(m, "drvd2");
|
||||
|
||||
m.def("rtrn_mptr_drvd2", rtrn_mptr_drvd2, rvto);
|
||||
m.def("rtrn_mptr_drvd2_up_cast1", rtrn_mptr_drvd2_up_cast1, rvto);
|
||||
m.def("rtrn_mptr_drvd2_up_cast2", rtrn_mptr_drvd2_up_cast2, rvto);
|
||||
m.def("pass_cptr_base1", pass_cptr_base1);
|
||||
m.def("pass_cptr_base2", pass_cptr_base2);
|
||||
m.def("pass_cptr_drvd2", pass_cptr_drvd2);
|
||||
}
|
||||
|
||||
} // namespace class_sh_inheritance
|
||||
} // namespace pybind11_tests
|
63
tests/test_class_sh_inheritance.py
Normal file
63
tests/test_class_sh_inheritance.py
Normal file
@ -0,0 +1,63 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pybind11_tests import class_sh_inheritance as m
|
||||
|
||||
|
||||
def test_rtrn_mptr_drvd_pass_cptr_base():
|
||||
d = m.rtrn_mptr_drvd()
|
||||
i = m.pass_cptr_base(d) # load_impl Case 2a
|
||||
assert i == 2 * 100 + 11
|
||||
|
||||
|
||||
def test_rtrn_shmp_drvd_pass_shcp_base():
|
||||
d = m.rtrn_shmp_drvd()
|
||||
i = m.pass_shcp_base(d) # load_impl Case 2a
|
||||
assert i == 2 * 100 + 21
|
||||
|
||||
|
||||
def test_rtrn_mptr_drvd_up_cast_pass_cptr_drvd():
|
||||
b = m.rtrn_mptr_drvd_up_cast()
|
||||
# the base return is down-cast immediately.
|
||||
assert b.__class__.__name__ == "drvd"
|
||||
i = m.pass_cptr_drvd(b)
|
||||
assert i == 2 * 100 + 12
|
||||
|
||||
|
||||
def test_rtrn_shmp_drvd_up_cast_pass_shcp_drvd():
|
||||
b = m.rtrn_shmp_drvd_up_cast()
|
||||
# the base return is down-cast immediately.
|
||||
assert b.__class__.__name__ == "drvd"
|
||||
i = m.pass_shcp_drvd(b)
|
||||
assert i == 2 * 100 + 22
|
||||
|
||||
|
||||
def test_rtrn_mptr_drvd2_pass_cptr_bases():
|
||||
d = m.rtrn_mptr_drvd2()
|
||||
i1 = m.pass_cptr_base1(d) # load_impl Case 2c
|
||||
assert i1 == 3 * 110 + 4 * 120 + 21
|
||||
i2 = m.pass_cptr_base2(d)
|
||||
assert i2 == 3 * 110 + 4 * 120 + 22
|
||||
|
||||
|
||||
def test_rtrn_mptr_drvd2_up_casts_pass_cptr_drvd2():
|
||||
b1 = m.rtrn_mptr_drvd2_up_cast1()
|
||||
assert b1.__class__.__name__ == "drvd2"
|
||||
i1 = m.pass_cptr_drvd2(b1)
|
||||
assert i1 == 3 * 110 + 4 * 120 + 23
|
||||
b2 = m.rtrn_mptr_drvd2_up_cast2()
|
||||
assert b2.__class__.__name__ == "drvd2"
|
||||
i2 = m.pass_cptr_drvd2(b2)
|
||||
assert i2 == 3 * 110 + 4 * 120 + 23
|
||||
|
||||
|
||||
def test_python_drvd2():
|
||||
class Drvd2(m.base1, m.base2):
|
||||
def __init__(self):
|
||||
m.base1.__init__(self)
|
||||
m.base2.__init__(self)
|
||||
|
||||
d = Drvd2()
|
||||
i1 = m.pass_cptr_base1(d) # load_impl Case 2b
|
||||
assert i1 == 110 + 21
|
||||
i2 = m.pass_cptr_base2(d)
|
||||
assert i2 == 120 + 22
|
86
tests/test_class_sh_trampoline_basic.cpp
Normal file
86
tests/test_class_sh_trampoline_basic.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_trampoline_basic {
|
||||
|
||||
template <int SerNo> // Using int as a trick to easily generate a series of types.
|
||||
struct Abase {
|
||||
int val = 0;
|
||||
virtual ~Abase() = default;
|
||||
explicit Abase(int val_) : val{val_} {}
|
||||
int Get() const { return val * 10 + 3; }
|
||||
virtual int Add(int other_val) const = 0;
|
||||
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
Abase(const Abase &) = default;
|
||||
Abase(Abase &&) noexcept = default;
|
||||
Abase &operator=(const Abase &) = default;
|
||||
Abase &operator=(Abase &&) noexcept = default;
|
||||
};
|
||||
|
||||
template <int SerNo>
|
||||
struct AbaseAlias : Abase<SerNo> {
|
||||
using Abase<SerNo>::Abase;
|
||||
|
||||
int Add(int other_val) const override {
|
||||
PYBIND11_OVERRIDE_PURE(int, /* Return type */
|
||||
Abase<SerNo>, /* Parent class */
|
||||
Add, /* Name of function in C++ (must match Python name) */
|
||||
other_val);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AbaseAlias<1> : Abase<1>, py::trampoline_self_life_support {
|
||||
using Abase<1>::Abase;
|
||||
|
||||
int Add(int other_val) const override {
|
||||
PYBIND11_OVERRIDE_PURE(int, /* Return type */
|
||||
Abase<1>, /* Parent class */
|
||||
Add, /* Name of function in C++ (must match Python name) */
|
||||
other_val);
|
||||
}
|
||||
};
|
||||
|
||||
template <int SerNo>
|
||||
int AddInCppRawPtr(const Abase<SerNo> *obj, int other_val) {
|
||||
return obj->Add(other_val) * 10 + 7;
|
||||
}
|
||||
|
||||
template <int SerNo>
|
||||
int AddInCppSharedPtr(std::shared_ptr<Abase<SerNo>> obj, int other_val) {
|
||||
return obj->Add(other_val) * 100 + 11;
|
||||
}
|
||||
|
||||
template <int SerNo>
|
||||
int AddInCppUniquePtr(std::unique_ptr<Abase<SerNo>> obj, int other_val) {
|
||||
return obj->Add(other_val) * 100 + 13;
|
||||
}
|
||||
|
||||
template <int SerNo>
|
||||
void wrap(py::module_ m, const char *py_class_name) {
|
||||
py::classh<Abase<SerNo>, AbaseAlias<SerNo>>(m, py_class_name)
|
||||
.def(py::init<int>(), py::arg("val"))
|
||||
.def("Get", &Abase<SerNo>::Get)
|
||||
.def("Add", &Abase<SerNo>::Add, py::arg("other_val"));
|
||||
|
||||
m.def("AddInCppRawPtr", AddInCppRawPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
|
||||
m.def("AddInCppSharedPtr", AddInCppSharedPtr<SerNo>, py::arg("obj"), py::arg("other_val"));
|
||||
m.def("AddInCppUniquePtr", AddInCppUniquePtr<SerNo>, py::arg("obj"), py::arg("other_val"));
|
||||
}
|
||||
|
||||
} // namespace class_sh_trampoline_basic
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_trampoline_basic::Abase<0>)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_trampoline_basic::Abase<1>)
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_basic, m) {
|
||||
using namespace pybind11_tests::class_sh_trampoline_basic;
|
||||
wrap<0>(m, "Abase0");
|
||||
wrap<1>(m, "Abase1");
|
||||
}
|
59
tests/test_class_sh_trampoline_basic.py
Normal file
59
tests/test_class_sh_trampoline_basic.py
Normal file
@ -0,0 +1,59 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_trampoline_basic as m
|
||||
|
||||
|
||||
class PyDrvd0(m.Abase0):
|
||||
def __init__(self, val):
|
||||
super().__init__(val)
|
||||
|
||||
def Add(self, other_val):
|
||||
return self.Get() * 100 + other_val
|
||||
|
||||
|
||||
class PyDrvd1(m.Abase1):
|
||||
def __init__(self, val):
|
||||
super().__init__(val)
|
||||
|
||||
def Add(self, other_val):
|
||||
return self.Get() * 200 + other_val
|
||||
|
||||
|
||||
def test_drvd0_add():
|
||||
drvd = PyDrvd0(74)
|
||||
assert drvd.Add(38) == (74 * 10 + 3) * 100 + 38
|
||||
|
||||
|
||||
def test_drvd0_add_in_cpp_raw_ptr():
|
||||
drvd = PyDrvd0(52)
|
||||
assert m.AddInCppRawPtr(drvd, 27) == ((52 * 10 + 3) * 100 + 27) * 10 + 7
|
||||
|
||||
|
||||
def test_drvd0_add_in_cpp_shared_ptr():
|
||||
while True:
|
||||
drvd = PyDrvd0(36)
|
||||
assert m.AddInCppSharedPtr(drvd, 56) == ((36 * 10 + 3) * 100 + 56) * 100 + 11
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_drvd0_add_in_cpp_unique_ptr():
|
||||
while True:
|
||||
drvd = PyDrvd0(0)
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
m.AddInCppUniquePtr(drvd, 0)
|
||||
assert (
|
||||
str(exc_info.value)
|
||||
== "Alias class (also known as trampoline) does not inherit from"
|
||||
" py::trampoline_self_life_support, therefore the ownership of this"
|
||||
" instance cannot safely be transferred to C++."
|
||||
)
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_drvd1_add_in_cpp_unique_ptr():
|
||||
while True:
|
||||
drvd = PyDrvd1(25)
|
||||
assert m.AddInCppUniquePtr(drvd, 83) == ((25 * 10 + 3) * 200 + 83) * 100 + 13
|
||||
return # Comment out for manual leak checking (use `top` command).
|
85
tests/test_class_sh_trampoline_self_life_support.cpp
Normal file
85
tests/test_class_sh_trampoline_self_life_support.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2021 The Pybind Development Team.
|
||||
// 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/smart_holder.h"
|
||||
#include "pybind11/trampoline_self_life_support.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Big5 { // Also known as "rule of five".
|
||||
std::string history;
|
||||
|
||||
explicit Big5(std::string history_start) : history{std::move(history_start)} {}
|
||||
|
||||
Big5(const Big5 &other) { history = other.history + "_CpCtor"; }
|
||||
|
||||
Big5(Big5 &&other) noexcept { history = other.history + "_MvCtor"; }
|
||||
|
||||
Big5 &operator=(const Big5 &other) {
|
||||
history = other.history + "_OpEqLv";
|
||||
return *this;
|
||||
}
|
||||
|
||||
Big5 &operator=(Big5 &&other) noexcept {
|
||||
history = other.history + "_OpEqRv";
|
||||
return *this;
|
||||
}
|
||||
|
||||
virtual ~Big5() = default;
|
||||
|
||||
protected:
|
||||
Big5() : history{"DefaultConstructor"} {}
|
||||
};
|
||||
|
||||
struct Big5Trampoline : Big5, py::trampoline_self_life_support {
|
||||
using Big5::Big5;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Big5)
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_self_life_support, m) {
|
||||
py::classh<Big5, Big5Trampoline>(m, "Big5")
|
||||
.def(py::init<std::string>())
|
||||
.def_readonly("history", &Big5::history);
|
||||
|
||||
m.def("action", [](std::unique_ptr<Big5> obj, int action_id) {
|
||||
py::object o2 = py::none();
|
||||
// This is very unusual, but needed to directly exercise the trampoline_self_life_support
|
||||
// CpCtor, MvCtor, operator= lvalue, operator= rvalue.
|
||||
auto *obj_trampoline = dynamic_cast<Big5Trampoline *>(obj.get());
|
||||
if (obj_trampoline != nullptr) {
|
||||
switch (action_id) {
|
||||
case 0: { // CpCtor
|
||||
std::unique_ptr<Big5> cp(new Big5Trampoline(*obj_trampoline));
|
||||
o2 = py::cast(std::move(cp));
|
||||
} break;
|
||||
case 1: { // MvCtor
|
||||
std::unique_ptr<Big5> mv(new Big5Trampoline(std::move(*obj_trampoline)));
|
||||
o2 = py::cast(std::move(mv));
|
||||
} break;
|
||||
case 2: { // operator= lvalue
|
||||
std::unique_ptr<Big5> lv(new Big5Trampoline);
|
||||
*lv = *obj_trampoline; // NOLINT clang-tidy cppcoreguidelines-slicing
|
||||
o2 = py::cast(std::move(lv));
|
||||
} break;
|
||||
case 3: { // operator= rvalue
|
||||
std::unique_ptr<Big5> rv(new Big5Trampoline);
|
||||
*rv = std::move(*obj_trampoline);
|
||||
o2 = py::cast(std::move(rv));
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
py::object o1 = py::cast(std::move(obj));
|
||||
return py::make_tuple(o1, o2);
|
||||
});
|
||||
}
|
38
tests/test_class_sh_trampoline_self_life_support.py
Normal file
38
tests/test_class_sh_trampoline_self_life_support.py
Normal file
@ -0,0 +1,38 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import pybind11_tests.class_sh_trampoline_self_life_support as m
|
||||
|
||||
|
||||
class PyBig5(m.Big5):
|
||||
pass
|
||||
|
||||
|
||||
def test_m_big5():
|
||||
obj = m.Big5("Seed")
|
||||
assert obj.history == "Seed"
|
||||
o1, o2 = m.action(obj, 0)
|
||||
assert o1 is not obj
|
||||
assert o1.history == "Seed"
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
_ = obj.history
|
||||
assert "Python instance was disowned" in str(excinfo.value)
|
||||
assert o2 is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("action_id", "expected_history"),
|
||||
[
|
||||
(0, "Seed_CpCtor"),
|
||||
(1, "Seed_MvCtor"),
|
||||
(2, "Seed_OpEqLv"),
|
||||
(3, "Seed_OpEqRv"),
|
||||
],
|
||||
)
|
||||
def test_py_big5(action_id, expected_history):
|
||||
obj = PyBig5("Seed")
|
||||
assert obj.history == "Seed"
|
||||
o1, o2 = m.action(obj, action_id)
|
||||
assert o1 is obj
|
||||
assert o2.history == expected_history
|
131
tests/test_class_sh_trampoline_shared_from_this.cpp
Normal file
131
tests/test_class_sh_trampoline_shared_from_this.cpp
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2021 The Pybind Development Team.
|
||||
// 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/smart_holder.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Sft : std::enable_shared_from_this<Sft> {
|
||||
std::string history;
|
||||
explicit Sft(const std::string &history_seed) : history{history_seed} {}
|
||||
virtual ~Sft() = default;
|
||||
|
||||
#if defined(__clang__)
|
||||
// "Group of 4" begin.
|
||||
// This group is not meant to be used, but will leave a trace in the
|
||||
// history in case something goes wrong.
|
||||
// However, compilers other than clang have a variety of issues. It is not
|
||||
// worth the trouble covering all platforms.
|
||||
Sft(const Sft &other) : enable_shared_from_this(other) { history = other.history + "_CpCtor"; }
|
||||
|
||||
Sft(Sft &&other) noexcept { history = other.history + "_MvCtor"; }
|
||||
|
||||
Sft &operator=(const Sft &other) {
|
||||
history = other.history + "_OpEqLv";
|
||||
return *this;
|
||||
}
|
||||
|
||||
Sft &operator=(Sft &&other) noexcept {
|
||||
history = other.history + "_OpEqRv";
|
||||
return *this;
|
||||
}
|
||||
// "Group of 4" end.
|
||||
#endif
|
||||
};
|
||||
|
||||
struct SftSharedPtrStash {
|
||||
int ser_no;
|
||||
std::vector<std::shared_ptr<Sft>> stash;
|
||||
explicit SftSharedPtrStash(int ser_no) : ser_no{ser_no} {}
|
||||
void Clear() { stash.clear(); }
|
||||
void Add(const std::shared_ptr<Sft> &obj) {
|
||||
if (!obj->history.empty()) {
|
||||
obj->history += "_Stash" + std::to_string(ser_no) + "Add";
|
||||
}
|
||||
stash.push_back(obj);
|
||||
}
|
||||
void AddSharedFromThis(Sft *obj) {
|
||||
auto sft = obj->shared_from_this();
|
||||
if (!sft->history.empty()) {
|
||||
sft->history += "_Stash" + std::to_string(ser_no) + "AddSharedFromThis";
|
||||
}
|
||||
stash.push_back(sft);
|
||||
}
|
||||
std::string history(unsigned i) {
|
||||
if (i < stash.size()) {
|
||||
return stash[i]->history;
|
||||
}
|
||||
return "OutOfRange";
|
||||
}
|
||||
long use_count(unsigned i) {
|
||||
if (i < stash.size()) {
|
||||
return stash[i].use_count();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
struct SftTrampoline : Sft, py::trampoline_self_life_support {
|
||||
using Sft::Sft;
|
||||
};
|
||||
|
||||
long use_count(const std::shared_ptr<Sft> &obj) { return obj.use_count(); }
|
||||
|
||||
long pass_shared_ptr(const std::shared_ptr<Sft> &obj) {
|
||||
auto sft = obj->shared_from_this();
|
||||
if (!sft->history.empty()) {
|
||||
sft->history += "_PassSharedPtr";
|
||||
}
|
||||
return sft.use_count();
|
||||
}
|
||||
|
||||
void pass_unique_ptr(const std::unique_ptr<Sft> &) {}
|
||||
|
||||
Sft *make_pure_cpp_sft_raw_ptr(const std::string &history_seed) { return new Sft{history_seed}; }
|
||||
|
||||
std::unique_ptr<Sft> make_pure_cpp_sft_unq_ptr(const std::string &history_seed) {
|
||||
return std::unique_ptr<Sft>(new Sft{history_seed});
|
||||
}
|
||||
|
||||
std::shared_ptr<Sft> make_pure_cpp_sft_shd_ptr(const std::string &history_seed) {
|
||||
return std::make_shared<Sft>(history_seed);
|
||||
}
|
||||
|
||||
std::shared_ptr<Sft> pass_through_shd_ptr(const std::shared_ptr<Sft> &obj) { return obj; }
|
||||
|
||||
} // namespace
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Sft)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SftSharedPtrStash)
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_shared_from_this, m) {
|
||||
py::classh<Sft, SftTrampoline>(m, "Sft")
|
||||
.def(py::init<const std::string &>())
|
||||
.def(py::init([](const std::string &history, int) {
|
||||
return std::make_shared<SftTrampoline>(history);
|
||||
}))
|
||||
.def_readonly("history", &Sft::history)
|
||||
// This leads to multiple entries in registered_instances:
|
||||
.def(py::init([](const std::shared_ptr<Sft> &existing) { return existing; }));
|
||||
|
||||
py::classh<SftSharedPtrStash>(m, "SftSharedPtrStash")
|
||||
.def(py::init<int>())
|
||||
.def("Clear", &SftSharedPtrStash::Clear)
|
||||
.def("Add", &SftSharedPtrStash::Add)
|
||||
.def("AddSharedFromThis", &SftSharedPtrStash::AddSharedFromThis)
|
||||
.def("history", &SftSharedPtrStash::history)
|
||||
.def("use_count", &SftSharedPtrStash::use_count);
|
||||
|
||||
m.def("use_count", use_count);
|
||||
m.def("pass_shared_ptr", pass_shared_ptr);
|
||||
m.def("pass_unique_ptr", pass_unique_ptr);
|
||||
m.def("make_pure_cpp_sft_raw_ptr", make_pure_cpp_sft_raw_ptr);
|
||||
m.def("make_pure_cpp_sft_unq_ptr", make_pure_cpp_sft_unq_ptr);
|
||||
m.def("make_pure_cpp_sft_shd_ptr", make_pure_cpp_sft_shd_ptr);
|
||||
m.def("pass_through_shd_ptr", pass_through_shd_ptr);
|
||||
}
|
244
tests/test_class_sh_trampoline_shared_from_this.py
Normal file
244
tests/test_class_sh_trampoline_shared_from_this.py
Normal file
@ -0,0 +1,244 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import weakref
|
||||
|
||||
import pytest
|
||||
|
||||
import env
|
||||
import pybind11_tests.class_sh_trampoline_shared_from_this as m
|
||||
|
||||
|
||||
class PySft(m.Sft):
|
||||
pass
|
||||
|
||||
|
||||
def test_release_and_shared_from_this():
|
||||
# Exercises the most direct path from building a shared_from_this-visible
|
||||
# shared_ptr to calling shared_from_this.
|
||||
obj = PySft("PySft")
|
||||
assert obj.history == "PySft"
|
||||
assert m.use_count(obj) == 1
|
||||
assert m.pass_shared_ptr(obj) == 2
|
||||
assert obj.history == "PySft_PassSharedPtr"
|
||||
assert m.use_count(obj) == 1
|
||||
assert m.pass_shared_ptr(obj) == 2
|
||||
assert obj.history == "PySft_PassSharedPtr_PassSharedPtr"
|
||||
assert m.use_count(obj) == 1
|
||||
|
||||
|
||||
def test_release_and_shared_from_this_leak():
|
||||
obj = PySft("")
|
||||
while True:
|
||||
m.pass_shared_ptr(obj)
|
||||
assert not obj.history
|
||||
assert m.use_count(obj) == 1
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_release_and_stash():
|
||||
# Exercises correct functioning of guarded_delete weak_ptr.
|
||||
obj = PySft("PySft")
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.Add(obj)
|
||||
exp_hist = "PySft_Stash1Add"
|
||||
assert obj.history == exp_hist
|
||||
assert m.use_count(obj) == 2
|
||||
assert stash1.history(0) == exp_hist
|
||||
assert stash1.use_count(0) == 1
|
||||
assert m.pass_shared_ptr(obj) == 3
|
||||
exp_hist += "_PassSharedPtr"
|
||||
assert obj.history == exp_hist
|
||||
assert m.use_count(obj) == 2
|
||||
assert stash1.history(0) == exp_hist
|
||||
assert stash1.use_count(0) == 1
|
||||
stash2 = m.SftSharedPtrStash(2)
|
||||
stash2.Add(obj)
|
||||
exp_hist += "_Stash2Add"
|
||||
assert obj.history == exp_hist
|
||||
assert m.use_count(obj) == 3
|
||||
assert stash2.history(0) == exp_hist
|
||||
assert stash2.use_count(0) == 2
|
||||
stash2.Add(obj)
|
||||
exp_hist += "_Stash2Add"
|
||||
assert obj.history == exp_hist
|
||||
assert m.use_count(obj) == 4
|
||||
assert stash1.history(0) == exp_hist
|
||||
assert stash1.use_count(0) == 3
|
||||
assert stash2.history(0) == exp_hist
|
||||
assert stash2.use_count(0) == 3
|
||||
assert stash2.history(1) == exp_hist
|
||||
assert stash2.use_count(1) == 3
|
||||
del obj
|
||||
assert stash2.history(0) == exp_hist
|
||||
assert stash2.use_count(0) == 3
|
||||
assert stash2.history(1) == exp_hist
|
||||
assert stash2.use_count(1) == 3
|
||||
stash2.Clear()
|
||||
assert stash1.history(0) == exp_hist
|
||||
assert stash1.use_count(0) == 1
|
||||
|
||||
|
||||
def test_release_and_stash_leak():
|
||||
obj = PySft("")
|
||||
while True:
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.Add(obj)
|
||||
assert not obj.history
|
||||
assert m.use_count(obj) == 2
|
||||
assert stash1.use_count(0) == 1
|
||||
stash1.Add(obj)
|
||||
assert not obj.history
|
||||
assert m.use_count(obj) == 3
|
||||
assert stash1.use_count(0) == 2
|
||||
assert stash1.use_count(1) == 2
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_release_and_stash_via_shared_from_this():
|
||||
# Exercises that the smart_holder vptr is invisible to the shared_from_this mechanism.
|
||||
obj = PySft("PySft")
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert str(exc_info.value) == "bad_weak_ptr"
|
||||
stash1.Add(obj)
|
||||
assert obj.history == "PySft_Stash1Add"
|
||||
assert stash1.use_count(0) == 1
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert obj.history == "PySft_Stash1Add_Stash1AddSharedFromThis"
|
||||
assert stash1.use_count(0) == 2
|
||||
assert stash1.use_count(1) == 2
|
||||
|
||||
|
||||
def test_release_and_stash_via_shared_from_this_leak():
|
||||
obj = PySft("")
|
||||
while True:
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert str(exc_info.value) == "bad_weak_ptr"
|
||||
stash1.Add(obj)
|
||||
assert not obj.history
|
||||
assert stash1.use_count(0) == 1
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert not obj.history
|
||||
assert stash1.use_count(0) == 2
|
||||
assert stash1.use_count(1) == 2
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_pass_released_shared_ptr_as_unique_ptr():
|
||||
# Exercises that returning a unique_ptr fails while a shared_from_this
|
||||
# visible shared_ptr exists.
|
||||
obj = PySft("PySft")
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.Add(obj) # Releases shared_ptr to C++.
|
||||
with pytest.raises(ValueError) as exc_info:
|
||||
m.pass_unique_ptr(obj)
|
||||
assert str(exc_info.value) == (
|
||||
"Python instance is currently owned by a std::shared_ptr."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"make_f",
|
||||
[
|
||||
m.make_pure_cpp_sft_raw_ptr,
|
||||
m.make_pure_cpp_sft_unq_ptr,
|
||||
m.make_pure_cpp_sft_shd_ptr,
|
||||
],
|
||||
)
|
||||
def test_pure_cpp_sft_raw_ptr(make_f):
|
||||
# Exercises void_cast_raw_ptr logic for different situations.
|
||||
obj = make_f("PureCppSft")
|
||||
assert m.pass_shared_ptr(obj) == 3
|
||||
assert obj.history == "PureCppSft_PassSharedPtr"
|
||||
obj = make_f("PureCppSft")
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.AddSharedFromThis(obj)
|
||||
assert obj.history == "PureCppSft_Stash1AddSharedFromThis"
|
||||
|
||||
|
||||
def test_multiple_registered_instances_for_same_pointee():
|
||||
obj0 = PySft("PySft")
|
||||
obj0.attachment_in_dict = "Obj0"
|
||||
assert m.pass_through_shd_ptr(obj0) is obj0
|
||||
while True:
|
||||
obj = m.Sft(obj0)
|
||||
assert obj is not obj0
|
||||
obj_pt = m.pass_through_shd_ptr(obj)
|
||||
# Unpredictable! Because registered_instances is as std::unordered_multimap.
|
||||
assert obj_pt is obj0 or obj_pt is obj
|
||||
# Multiple registered_instances for the same pointee can lead to unpredictable results:
|
||||
if obj_pt is obj0:
|
||||
assert obj_pt.attachment_in_dict == "Obj0"
|
||||
else:
|
||||
assert not hasattr(obj_pt, "attachment_in_dict")
|
||||
assert obj0.history == "PySft"
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_multiple_registered_instances_for_same_pointee_leak():
|
||||
obj0 = PySft("")
|
||||
while True:
|
||||
stash1 = m.SftSharedPtrStash(1)
|
||||
stash1.Add(m.Sft(obj0))
|
||||
assert stash1.use_count(0) == 1
|
||||
stash1.Add(m.Sft(obj0))
|
||||
assert stash1.use_count(0) == 1
|
||||
assert stash1.use_count(1) == 1
|
||||
assert not obj0.history
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_multiple_registered_instances_for_same_pointee_recursive():
|
||||
while True:
|
||||
obj0 = PySft("PySft")
|
||||
if not env.PYPY:
|
||||
obj0_wr = weakref.ref(obj0)
|
||||
obj = obj0
|
||||
# This loop creates a chain of instances linked by shared_ptrs.
|
||||
for _ in range(10):
|
||||
obj_next = m.Sft(obj)
|
||||
assert obj_next is not obj
|
||||
obj = obj_next
|
||||
del obj_next
|
||||
assert obj.history == "PySft"
|
||||
del obj0
|
||||
if not env.PYPY:
|
||||
assert obj0_wr() is not None
|
||||
del obj # This releases the chain recursively.
|
||||
if not env.PYPY:
|
||||
assert obj0_wr() is None
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
# As of 2021-07-10 the pybind11 GitHub Actions valgrind build uses Python 3.9.
|
||||
WORKAROUND_ENABLING_ROLLBACK_OF_PR3068 = env.LINUX and sys.version_info == (3, 9)
|
||||
|
||||
|
||||
def test_std_make_shared_factory():
|
||||
class PySftMakeShared(m.Sft):
|
||||
def __init__(self, history):
|
||||
super().__init__(history, 0)
|
||||
|
||||
obj = PySftMakeShared("PySftMakeShared")
|
||||
assert obj.history == "PySftMakeShared"
|
||||
if WORKAROUND_ENABLING_ROLLBACK_OF_PR3068:
|
||||
try:
|
||||
m.pass_through_shd_ptr(obj)
|
||||
except RuntimeError as e:
|
||||
str_exc_info_value = str(e)
|
||||
else:
|
||||
str_exc_info_value = "RuntimeError NOT RAISED"
|
||||
else:
|
||||
with pytest.raises(RuntimeError) as exc_info:
|
||||
m.pass_through_shd_ptr(obj)
|
||||
str_exc_info_value = str(exc_info.value)
|
||||
assert (
|
||||
str_exc_info_value
|
||||
== "smart_holder_type_casters loaded_as_shared_ptr failure: not implemented:"
|
||||
" trampoline-self-life-support for external shared_ptr to type inheriting"
|
||||
" from std::enable_shared_from_this."
|
||||
)
|
94
tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp
Normal file
94
tests/test_class_sh_trampoline_shared_ptr_cpp_arg.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2021 The Pybind Development Team.
|
||||
// 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/smart_holder.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace {
|
||||
|
||||
// For testing whether a python subclass of a C++ object dies when the
|
||||
// last python reference is lost
|
||||
struct SpBase {
|
||||
// returns true if the base virtual function is called
|
||||
virtual bool is_base_used() { return true; }
|
||||
|
||||
// returns true if there's an associated python instance
|
||||
bool has_python_instance() {
|
||||
auto *tinfo = py::detail::get_type_info(typeid(SpBase));
|
||||
return (bool) py::detail::get_object_handle(this, tinfo);
|
||||
}
|
||||
|
||||
SpBase() = default;
|
||||
SpBase(const SpBase &) = delete;
|
||||
virtual ~SpBase() = default;
|
||||
};
|
||||
|
||||
std::shared_ptr<SpBase> pass_through_shd_ptr(const std::shared_ptr<SpBase> &obj) { return obj; }
|
||||
|
||||
struct PySpBase : SpBase {
|
||||
using SpBase::SpBase;
|
||||
bool is_base_used() override { PYBIND11_OVERRIDE(bool, SpBase, is_base_used); }
|
||||
};
|
||||
|
||||
struct SpBaseTester {
|
||||
std::shared_ptr<SpBase> get_object() const { return m_obj; }
|
||||
void set_object(std::shared_ptr<SpBase> obj) { m_obj = std::move(obj); }
|
||||
bool is_base_used() { return m_obj->is_base_used(); }
|
||||
bool has_instance() { return (bool) m_obj; }
|
||||
bool has_python_instance() { return m_obj && m_obj->has_python_instance(); }
|
||||
void set_nonpython_instance() { m_obj = std::make_shared<SpBase>(); }
|
||||
std::shared_ptr<SpBase> m_obj;
|
||||
};
|
||||
|
||||
// For testing that a C++ class without an alias does not retain the python
|
||||
// portion of the object
|
||||
struct SpGoAway {};
|
||||
|
||||
struct SpGoAwayTester {
|
||||
std::shared_ptr<SpGoAway> m_obj;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpBase)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpBaseTester)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpGoAway)
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(SpGoAwayTester)
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_shared_ptr_cpp_arg, m) {
|
||||
// For testing whether a python subclass of a C++ object dies when the
|
||||
// last python reference is lost
|
||||
|
||||
py::classh<SpBase, PySpBase>(m, "SpBase")
|
||||
.def(py::init<>())
|
||||
.def(py::init([](int) { return std::make_shared<PySpBase>(); }))
|
||||
.def("is_base_used", &SpBase::is_base_used)
|
||||
.def("has_python_instance", &SpBase::has_python_instance);
|
||||
|
||||
m.def("pass_through_shd_ptr", pass_through_shd_ptr);
|
||||
m.def("pass_through_shd_ptr_release_gil",
|
||||
pass_through_shd_ptr,
|
||||
py::call_guard<py::gil_scoped_release>()); // PR #4196
|
||||
|
||||
py::classh<SpBaseTester>(m, "SpBaseTester")
|
||||
.def(py::init<>())
|
||||
.def("get_object", &SpBaseTester::get_object)
|
||||
.def("set_object", &SpBaseTester::set_object)
|
||||
.def("is_base_used", &SpBaseTester::is_base_used)
|
||||
.def("has_instance", &SpBaseTester::has_instance)
|
||||
.def("has_python_instance", &SpBaseTester::has_python_instance)
|
||||
.def("set_nonpython_instance", &SpBaseTester::set_nonpython_instance)
|
||||
.def_readwrite("obj", &SpBaseTester::m_obj);
|
||||
|
||||
// For testing that a C++ class without an alias does not retain the python
|
||||
// portion of the object
|
||||
|
||||
py::classh<SpGoAway>(m, "SpGoAway").def(py::init<>());
|
||||
|
||||
py::classh<SpGoAwayTester>(m, "SpGoAwayTester")
|
||||
.def(py::init<>())
|
||||
.def_readwrite("obj", &SpGoAwayTester::m_obj);
|
||||
}
|
148
tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py
Normal file
148
tests/test_class_sh_trampoline_shared_ptr_cpp_arg.py
Normal file
@ -0,0 +1,148 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
import pybind11_tests.class_sh_trampoline_shared_ptr_cpp_arg as m
|
||||
|
||||
|
||||
def test_shared_ptr_cpp_arg():
|
||||
import weakref
|
||||
|
||||
class PyChild(m.SpBase):
|
||||
def is_base_used(self):
|
||||
return False
|
||||
|
||||
tester = m.SpBaseTester()
|
||||
|
||||
obj = PyChild()
|
||||
objref = weakref.ref(obj)
|
||||
|
||||
# Pass the last python reference to the C++ function
|
||||
tester.set_object(obj)
|
||||
del obj
|
||||
pytest.gc_collect()
|
||||
|
||||
# python reference is still around since C++ has it now
|
||||
assert objref() is not None
|
||||
assert tester.is_base_used() is False
|
||||
assert tester.obj.is_base_used() is False
|
||||
assert tester.get_object() is objref()
|
||||
|
||||
|
||||
def test_shared_ptr_cpp_prop():
|
||||
class PyChild(m.SpBase):
|
||||
def is_base_used(self):
|
||||
return False
|
||||
|
||||
tester = m.SpBaseTester()
|
||||
|
||||
# Set the last python reference as a property of the C++ object
|
||||
tester.obj = PyChild()
|
||||
pytest.gc_collect()
|
||||
|
||||
# python reference is still around since C++ has it now
|
||||
assert tester.is_base_used() is False
|
||||
assert tester.has_python_instance() is True
|
||||
assert tester.obj.is_base_used() is False
|
||||
assert tester.obj.has_python_instance() is True
|
||||
|
||||
|
||||
def test_shared_ptr_arg_identity():
|
||||
import weakref
|
||||
|
||||
tester = m.SpBaseTester()
|
||||
|
||||
obj = m.SpBase()
|
||||
objref = weakref.ref(obj)
|
||||
|
||||
tester.set_object(obj)
|
||||
del obj
|
||||
pytest.gc_collect()
|
||||
|
||||
# SMART_HOLDER_WIP: the behavior below is DIFFERENT from PR #2839
|
||||
# python reference is gone because it is not an Alias instance
|
||||
assert objref() is None
|
||||
assert tester.has_python_instance() is False
|
||||
|
||||
|
||||
def test_shared_ptr_alias_nonpython():
|
||||
tester = m.SpBaseTester()
|
||||
|
||||
# C++ creates the object, a python instance shouldn't exist
|
||||
tester.set_nonpython_instance()
|
||||
assert tester.is_base_used() is True
|
||||
assert tester.has_instance() is True
|
||||
assert tester.has_python_instance() is False
|
||||
|
||||
# Now a python instance exists
|
||||
cobj = tester.get_object()
|
||||
assert cobj.has_python_instance()
|
||||
assert tester.has_instance() is True
|
||||
assert tester.has_python_instance() is True
|
||||
|
||||
# Now it's gone
|
||||
del cobj
|
||||
pytest.gc_collect()
|
||||
assert tester.has_instance() is True
|
||||
assert tester.has_python_instance() is False
|
||||
|
||||
# When we pass it as an arg to a new tester the python instance should
|
||||
# disappear because it wasn't created with an alias
|
||||
new_tester = m.SpBaseTester()
|
||||
|
||||
cobj = tester.get_object()
|
||||
assert cobj.has_python_instance()
|
||||
|
||||
new_tester.set_object(cobj)
|
||||
assert tester.has_python_instance() is True
|
||||
assert new_tester.has_python_instance() is True
|
||||
|
||||
del cobj
|
||||
pytest.gc_collect()
|
||||
|
||||
# Gone!
|
||||
assert tester.has_instance() is True
|
||||
assert tester.has_python_instance() is False
|
||||
assert new_tester.has_instance() is True
|
||||
assert new_tester.has_python_instance() is False
|
||||
|
||||
|
||||
def test_shared_ptr_goaway():
|
||||
import weakref
|
||||
|
||||
tester = m.SpGoAwayTester()
|
||||
|
||||
obj = m.SpGoAway()
|
||||
objref = weakref.ref(obj)
|
||||
|
||||
assert tester.obj is None
|
||||
|
||||
tester.obj = obj
|
||||
del obj
|
||||
pytest.gc_collect()
|
||||
|
||||
# python reference is no longer around
|
||||
assert objref() is None
|
||||
# C++ reference is still around
|
||||
assert tester.obj is not None
|
||||
|
||||
|
||||
def test_infinite():
|
||||
tester = m.SpBaseTester()
|
||||
while True:
|
||||
tester.set_object(m.SpBase())
|
||||
break # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"pass_through_func", [m.pass_through_shd_ptr, m.pass_through_shd_ptr_release_gil]
|
||||
)
|
||||
def test_std_make_shared_factory(pass_through_func):
|
||||
class PyChild(m.SpBase):
|
||||
def __init__(self):
|
||||
super().__init__(0)
|
||||
|
||||
obj = PyChild()
|
||||
while True:
|
||||
assert pass_through_func(obj) is obj
|
||||
break # Comment out for manual leak checking (use `top` command).
|
60
tests/test_class_sh_trampoline_unique_ptr.cpp
Normal file
60
tests/test_class_sh_trampoline_unique_ptr.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2021 The Pybind Development Team.
|
||||
// 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/smart_holder.h"
|
||||
#include "pybind11/trampoline_self_life_support.h"
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace {
|
||||
|
||||
class Class {
|
||||
public:
|
||||
virtual ~Class() = default;
|
||||
|
||||
void setVal(std::uint64_t val) { val_ = val; }
|
||||
std::uint64_t getVal() const { return val_; }
|
||||
|
||||
virtual std::unique_ptr<Class> clone() const = 0;
|
||||
virtual int foo() const = 0;
|
||||
|
||||
protected:
|
||||
Class() = default;
|
||||
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
Class(const Class &) = default;
|
||||
|
||||
private:
|
||||
std::uint64_t val_ = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(Class)
|
||||
|
||||
namespace {
|
||||
|
||||
class PyClass : public Class, public py::trampoline_self_life_support {
|
||||
public:
|
||||
std::unique_ptr<Class> clone() const override {
|
||||
PYBIND11_OVERRIDE_PURE(std::unique_ptr<Class>, Class, clone);
|
||||
}
|
||||
|
||||
int foo() const override { PYBIND11_OVERRIDE_PURE(int, Class, foo); }
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_SUBMODULE(class_sh_trampoline_unique_ptr, m) {
|
||||
py::classh<Class, PyClass>(m, "Class")
|
||||
.def(py::init<>())
|
||||
.def("set_val", &Class::setVal)
|
||||
.def("get_val", &Class::getVal)
|
||||
.def("clone", &Class::clone)
|
||||
.def("foo", &Class::foo);
|
||||
|
||||
m.def("clone", [](const Class &obj) { return obj.clone(); });
|
||||
m.def("clone_and_foo", [](const Class &obj) { return obj.clone()->foo(); });
|
||||
}
|
31
tests/test_class_sh_trampoline_unique_ptr.py
Normal file
31
tests/test_class_sh_trampoline_unique_ptr.py
Normal file
@ -0,0 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pybind11_tests.class_sh_trampoline_unique_ptr as m
|
||||
|
||||
|
||||
class MyClass(m.Class):
|
||||
def foo(self):
|
||||
return 10 + self.get_val()
|
||||
|
||||
def clone(self):
|
||||
cloned = MyClass()
|
||||
cloned.set_val(self.get_val() + 3)
|
||||
return cloned
|
||||
|
||||
|
||||
def test_m_clone():
|
||||
obj = MyClass()
|
||||
while True:
|
||||
obj.set_val(5)
|
||||
obj = m.clone(obj)
|
||||
assert obj.get_val() == 5 + 3
|
||||
assert obj.foo() == 10 + 5 + 3
|
||||
return # Comment out for manual leak checking (use `top` command).
|
||||
|
||||
|
||||
def test_m_clone_and_foo():
|
||||
obj = MyClass()
|
||||
obj.set_val(7)
|
||||
while True:
|
||||
assert m.clone_and_foo(obj) == 10 + 7 + 3
|
||||
return # Comment out for manual leak checking (use `top` command).
|
40
tests/test_class_sh_unique_ptr_custom_deleter.cpp
Normal file
40
tests/test_class_sh_unique_ptr_custom_deleter.cpp
Normal file
@ -0,0 +1,40 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_unique_ptr_custom_deleter {
|
||||
|
||||
// Reduced from a PyCLIF use case in the wild by @wangxf123456.
|
||||
class Pet {
|
||||
public:
|
||||
using Ptr = std::unique_ptr<Pet, std::function<void(Pet *)>>;
|
||||
|
||||
std::string name;
|
||||
|
||||
static Ptr New(const std::string &name) {
|
||||
return Ptr(new Pet(name), std::default_delete<Pet>());
|
||||
}
|
||||
|
||||
private:
|
||||
explicit Pet(const std::string &name) : name(name) {}
|
||||
};
|
||||
|
||||
} // namespace class_sh_unique_ptr_custom_deleter
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_unique_ptr_custom_deleter::Pet)
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_unique_ptr_custom_deleter {
|
||||
|
||||
TEST_SUBMODULE(class_sh_unique_ptr_custom_deleter, m) {
|
||||
py::classh<Pet>(m, "Pet").def_readwrite("name", &Pet::name);
|
||||
|
||||
m.def("create", &Pet::New);
|
||||
}
|
||||
|
||||
} // namespace class_sh_unique_ptr_custom_deleter
|
||||
} // namespace pybind11_tests
|
8
tests/test_class_sh_unique_ptr_custom_deleter.py
Normal file
8
tests/test_class_sh_unique_ptr_custom_deleter.py
Normal file
@ -0,0 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pybind11_tests import class_sh_unique_ptr_custom_deleter as m
|
||||
|
||||
|
||||
def test_create():
|
||||
pet = m.create("abc")
|
||||
assert pet.name == "abc"
|
60
tests/test_class_sh_unique_ptr_member.cpp
Normal file
60
tests/test_class_sh_unique_ptr_member.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_unique_ptr_member {
|
||||
|
||||
class pointee { // NOT copyable.
|
||||
public:
|
||||
pointee() = default;
|
||||
|
||||
int get_int() const { return 213; }
|
||||
|
||||
pointee(const pointee &) = delete;
|
||||
pointee(pointee &&) = delete;
|
||||
pointee &operator=(const pointee &) = delete;
|
||||
pointee &operator=(pointee &&) = delete;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<pointee> make_unique_pointee() {
|
||||
return std::unique_ptr<pointee>(new pointee);
|
||||
}
|
||||
|
||||
class ptr_owner {
|
||||
public:
|
||||
explicit ptr_owner(std::unique_ptr<pointee> ptr) : ptr_(std::move(ptr)) {}
|
||||
|
||||
bool is_owner() const { return bool(ptr_); }
|
||||
|
||||
std::unique_ptr<pointee> give_up_ownership_via_unique_ptr() { return std::move(ptr_); }
|
||||
std::shared_ptr<pointee> give_up_ownership_via_shared_ptr() { return std::move(ptr_); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<pointee> ptr_;
|
||||
};
|
||||
|
||||
} // namespace class_sh_unique_ptr_member
|
||||
} // namespace pybind11_tests
|
||||
|
||||
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_unique_ptr_member::pointee)
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_unique_ptr_member {
|
||||
|
||||
TEST_SUBMODULE(class_sh_unique_ptr_member, m) {
|
||||
py::classh<pointee>(m, "pointee").def(py::init<>()).def("get_int", &pointee::get_int);
|
||||
|
||||
m.def("make_unique_pointee", make_unique_pointee);
|
||||
|
||||
py::class_<ptr_owner>(m, "ptr_owner")
|
||||
.def(py::init<std::unique_ptr<pointee>>(), py::arg("ptr"))
|
||||
.def("is_owner", &ptr_owner::is_owner)
|
||||
.def("give_up_ownership_via_unique_ptr", &ptr_owner::give_up_ownership_via_unique_ptr)
|
||||
.def("give_up_ownership_via_shared_ptr", &ptr_owner::give_up_ownership_via_shared_ptr);
|
||||
}
|
||||
|
||||
} // namespace class_sh_unique_ptr_member
|
||||
} // namespace pybind11_tests
|
26
tests/test_class_sh_unique_ptr_member.py
Normal file
26
tests/test_class_sh_unique_ptr_member.py
Normal file
@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_unique_ptr_member as m
|
||||
|
||||
|
||||
def test_make_unique_pointee():
|
||||
obj = m.make_unique_pointee()
|
||||
assert obj.get_int() == 213
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"give_up_ownership_via",
|
||||
["give_up_ownership_via_unique_ptr", "give_up_ownership_via_shared_ptr"],
|
||||
)
|
||||
def test_pointee_and_ptr_owner(give_up_ownership_via):
|
||||
obj = m.pointee()
|
||||
assert obj.get_int() == 213
|
||||
owner = m.ptr_owner(obj)
|
||||
with pytest.raises(ValueError, match="Python instance was disowned"):
|
||||
obj.get_int()
|
||||
assert owner.is_owner()
|
||||
reclaimed = getattr(owner, give_up_ownership_via)()
|
||||
assert not owner.is_owner()
|
||||
assert reclaimed.get_int() == 213
|
64
tests/test_class_sh_virtual_py_cpp_mix.cpp
Normal file
64
tests/test_class_sh_virtual_py_cpp_mix.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#include <pybind11/smart_holder.h>
|
||||
|
||||
#include "pybind11_tests.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace pybind11_tests {
|
||||
namespace class_sh_virtual_py_cpp_mix {
|
||||
|
||||
class Base {
|
||||
public:
|
||||
virtual ~Base() = default;
|
||||
virtual int get() const { return 101; }
|
||||
|
||||
// Some compilers complain about implicitly defined versions of some of the following:
|
||||
Base() = default;
|
||||
Base(const Base &) = default;
|
||||
};
|
||||
|
||||
class CppDerivedPlain : public Base {
|
||||
public:
|
||||
int get() const override { return 202; }
|
||||
};
|
||||
|
||||
class CppDerived : public Base {
|
||||
public:
|
||||
int get() const override { return 212; }
|
||||
};
|
||||
|
||||
int get_from_cpp_plainc_ptr(const Base *b) { return b->get() + 4000; }
|
||||
|
||||
int get_from_cpp_unique_ptr(std::unique_ptr<Base> b) { return b->get() + 5000; }
|
||||
|
||||
struct BaseVirtualOverrider : Base, py::trampoline_self_life_support {
|
||||
using Base::Base;
|
||||
|
||||
int get() const override { PYBIND11_OVERRIDE(int, Base, get); }
|
||||
};
|
||||
|
||||
struct CppDerivedVirtualOverrider : CppDerived, py::trampoline_self_life_support {
|
||||
using CppDerived::CppDerived;
|
||||
|
||||
int get() const override { PYBIND11_OVERRIDE(int, CppDerived, get); }
|
||||
};
|
||||
|
||||
} // namespace class_sh_virtual_py_cpp_mix
|
||||
} // namespace pybind11_tests
|
||||
|
||||
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::class_sh_virtual_py_cpp_mix;
|
||||
|
||||
py::classh<Base, BaseVirtualOverrider>(m, "Base").def(py::init<>()).def("get", &Base::get);
|
||||
|
||||
py::classh<CppDerivedPlain, Base>(m, "CppDerivedPlain").def(py::init<>());
|
||||
|
||||
py::classh<CppDerived, Base, CppDerivedVirtualOverrider>(m, "CppDerived").def(py::init<>());
|
||||
|
||||
m.def("get_from_cpp_plainc_ptr", get_from_cpp_plainc_ptr, py::arg("b"));
|
||||
m.def("get_from_cpp_unique_ptr", get_from_cpp_unique_ptr, py::arg("b"));
|
||||
}
|
66
tests/test_class_sh_virtual_py_cpp_mix.py
Normal file
66
tests/test_class_sh_virtual_py_cpp_mix.py
Normal file
@ -0,0 +1,66 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import class_sh_virtual_py_cpp_mix as m
|
||||
|
||||
|
||||
class PyBase(m.Base): # Avoiding name PyDerived, for more systematic naming.
|
||||
def __init__(self):
|
||||
m.Base.__init__(self)
|
||||
|
||||
def get(self):
|
||||
return 323
|
||||
|
||||
|
||||
class PyCppDerived(m.CppDerived):
|
||||
def __init__(self):
|
||||
m.CppDerived.__init__(self)
|
||||
|
||||
def get(self):
|
||||
return 434
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ctor", "expected"),
|
||||
[
|
||||
(m.Base, 101),
|
||||
(PyBase, 323),
|
||||
(m.CppDerivedPlain, 202),
|
||||
(m.CppDerived, 212),
|
||||
(PyCppDerived, 434),
|
||||
],
|
||||
)
|
||||
def test_base_get(ctor, expected):
|
||||
obj = ctor()
|
||||
assert obj.get() == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ctor", "expected"),
|
||||
[
|
||||
(m.Base, 4101),
|
||||
(PyBase, 4323),
|
||||
(m.CppDerivedPlain, 4202),
|
||||
(m.CppDerived, 4212),
|
||||
(PyCppDerived, 4434),
|
||||
],
|
||||
)
|
||||
def test_get_from_cpp_plainc_ptr(ctor, expected):
|
||||
obj = ctor()
|
||||
assert m.get_from_cpp_plainc_ptr(obj) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("ctor", "expected"),
|
||||
[
|
||||
(m.Base, 5101),
|
||||
(PyBase, 5323),
|
||||
(m.CppDerivedPlain, 5202),
|
||||
(m.CppDerived, 5212),
|
||||
(PyCppDerived, 5434),
|
||||
],
|
||||
)
|
||||
def test_get_from_cpp_unique_ptr(ctor, expected):
|
||||
obj = ctor()
|
||||
assert m.get_from_cpp_unique_ptr(obj) == expected
|
Loading…
Reference in New Issue
Block a user