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:
Ralf W. Grosse-Kunstleve 2024-07-05 12:52:41 -07:00
parent 470a765804
commit d9d96118e6
20 changed files with 1530 additions and 0 deletions

View 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);
}

View 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).

View 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

View 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

View 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");
}

View 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).

View 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);
});
}

View 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

View 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);
}

View 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."
)

View 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);
}

View 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).

View 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(); });
}

View 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).

View 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

View 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"

View 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

View 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

View 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"));
}

View 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