Skip to content

Commit 08339d6

Browse files
authored
Adding tests to exercise corner cases involving disowning. (#2912)
* Adding test_class_sh_disowning. * Fixing minor namespace naming inconsistency between test_class_sh_*.cpp files. * Replacing py::overload_cast with plain cast for C++11 compatibility. * Accommodate that the C++ order of evaluation of function arguments is unspecified.
1 parent 5319ca3 commit 08339d6

6 files changed

+152
-28
lines changed

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ set(PYBIND11_TEST_FILES
102102
test_chrono.cpp
103103
test_class.cpp
104104
test_class_sh_basic.cpp
105+
test_class_sh_disowning.cpp
105106
test_class_sh_factory_constructors.cpp
106107
test_class_sh_inheritance.cpp
107108
test_class_sh_trampoline_shared_ptr_cpp_arg.cpp

tests/test_class_sh_disowning.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#include "pybind11_tests.h"
2+
3+
#include <pybind11/smart_holder.h>
4+
5+
#include <memory>
6+
7+
namespace pybind11_tests {
8+
namespace class_sh_disowning {
9+
10+
template <int SerNo> // Using int as a trick to easily generate a series of types.
11+
struct Atype {
12+
int val = 0;
13+
Atype(int val_) : val{val_} {}
14+
int get() const { return val * 10 + SerNo; }
15+
};
16+
17+
int same_twice(std::unique_ptr<Atype<1>> at1a, std::unique_ptr<Atype<1>> at1b) {
18+
return at1a->get() * 100 + at1b->get() * 10;
19+
}
20+
21+
int mixed(std::unique_ptr<Atype<1>> at1, std::unique_ptr<Atype<2>> at2) {
22+
return at1->get() * 200 + at2->get() * 20;
23+
}
24+
25+
int overloaded(std::unique_ptr<Atype<1>> at1, int i) { return at1->get() * 30 + i; }
26+
int overloaded(std::unique_ptr<Atype<2>> at2, int i) { return at2->get() * 40 + i; }
27+
28+
} // namespace class_sh_disowning
29+
} // namespace pybind11_tests
30+
31+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<1>)
32+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_disowning::Atype<2>)
33+
34+
TEST_SUBMODULE(class_sh_disowning, m) {
35+
using namespace pybind11_tests::class_sh_disowning;
36+
37+
py::classh<Atype<1>>(m, "Atype1").def(py::init<int>()).def("get", &Atype<1>::get);
38+
py::classh<Atype<2>>(m, "Atype2").def(py::init<int>()).def("get", &Atype<2>::get);
39+
40+
m.def("same_twice", same_twice);
41+
42+
m.def("mixed", mixed);
43+
44+
m.def("overloaded", (int (*)(std::unique_ptr<Atype<1>>, int)) & overloaded);
45+
m.def("overloaded", (int (*)(std::unique_ptr<Atype<2>>, int)) & overloaded);
46+
}

tests/test_class_sh_disowning.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# -*- coding: utf-8 -*-
2+
from __future__ import print_function
3+
4+
import pytest
5+
6+
from pybind11_tests import class_sh_disowning as m
7+
8+
9+
def test_same_twice():
10+
while True:
11+
obj1a = m.Atype1(57)
12+
obj1b = m.Atype1(62)
13+
assert m.same_twice(obj1a, obj1b) == (57 * 10 + 1) * 100 + (62 * 10 + 1) * 10
14+
obj1c = m.Atype1(0)
15+
with pytest.raises(ValueError):
16+
# Disowning works for one argument, but not both.
17+
m.same_twice(obj1c, obj1c)
18+
with pytest.raises(ValueError):
19+
obj1c.get()
20+
return # Comment out for manual leak checking (use `top` command).
21+
22+
23+
def test_mixed(capsys):
24+
first_pass = True
25+
while True:
26+
obj1a = m.Atype1(90)
27+
obj2a = m.Atype2(25)
28+
assert m.mixed(obj1a, obj2a) == (90 * 10 + 1) * 200 + (25 * 10 + 2) * 20
29+
30+
# The C++ order of evaluation of function arguments is (unfortunately) unspecified:
31+
# https://en.cppreference.com/w/cpp/language/eval_order
32+
# Read on.
33+
obj1b = m.Atype1(0)
34+
with pytest.raises(ValueError):
35+
# If the 1st argument is evaluated first, obj1b is disowned before the conversion for
36+
# the already disowned obj2a fails as expected.
37+
m.mixed(obj1b, obj2a)
38+
obj2b = m.Atype2(0)
39+
with pytest.raises(ValueError):
40+
# If the 2nd argument is evaluated first, obj2b is disowned before the conversion for
41+
# the already disowned obj1a fails as expected.
42+
m.mixed(obj1a, obj2b)
43+
44+
def was_disowned(obj):
45+
try:
46+
obj.get()
47+
except ValueError:
48+
return True
49+
return False
50+
51+
# Either obj1b or obj2b was disowned in the expected failed m.mixed() calls above, but not
52+
# both.
53+
was_disowned_results = (was_disowned(obj1b), was_disowned(obj2b))
54+
assert was_disowned_results.count(True) == 1
55+
if first_pass:
56+
first_pass = False
57+
with capsys.disabled():
58+
print(
59+
"\nC++ function argument %d is evaluated first."
60+
% (was_disowned_results.index(True) + 1)
61+
)
62+
63+
return # Comment out for manual leak checking (use `top` command).
64+
65+
66+
def test_overloaded():
67+
while True:
68+
obj1 = m.Atype1(81)
69+
obj2 = m.Atype2(60)
70+
with pytest.raises(TypeError):
71+
m.overloaded(obj1, "NotInt")
72+
assert obj1.get() == 81 * 10 + 1 # Not disowned.
73+
assert m.overloaded(obj1, 3) == (81 * 10 + 1) * 30 + 3
74+
with pytest.raises(TypeError):
75+
m.overloaded(obj2, "NotInt")
76+
assert obj2.get() == 60 * 10 + 2 # Not disowned.
77+
assert m.overloaded(obj2, 2) == (60 * 10 + 2) * 40 + 2
78+
return # Comment out for manual leak checking (use `top` command).

tests/test_class_sh_factory_constructors.cpp

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#include <string>
77

88
namespace pybind11_tests {
9-
namespace test_class_sh_factory_constructors {
9+
namespace class_sh_factory_constructors {
1010

1111
template <int> // Using int as a trick to easily generate a series of types.
1212
struct atyp { // Short for "any type".
@@ -69,25 +69,25 @@ struct with_alias {
6969
struct with_alias_alias : with_alias {};
7070
struct sddwaa : std::default_delete<with_alias_alias> {};
7171

72-
} // namespace test_class_sh_factory_constructors
72+
} // namespace class_sh_factory_constructors
7373
} // namespace pybind11_tests
7474

75-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_valu)
76-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_rref)
77-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_cref)
78-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_mref)
79-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_cptr)
80-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_mptr)
81-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_shmp)
82-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_shcp)
83-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_uqmp)
84-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_uqcp)
85-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_udmp)
86-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::atyp_udcp)
87-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_factory_constructors::with_alias)
75+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_valu)
76+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_rref)
77+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_cref)
78+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_mref)
79+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_cptr)
80+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_mptr)
81+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_shmp)
82+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_shcp)
83+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_uqmp)
84+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_uqcp)
85+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_udmp)
86+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::atyp_udcp)
87+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_factory_constructors::with_alias)
8888

8989
TEST_SUBMODULE(class_sh_factory_constructors, m) {
90-
using namespace pybind11_tests::test_class_sh_factory_constructors;
90+
using namespace pybind11_tests::class_sh_factory_constructors;
9191

9292
py::classh<atyp_valu>(m, "atyp_valu")
9393
.def(py::init(&rtrn_valu))

tests/test_class_sh_virtual_py_cpp_mix.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include <memory>
66

77
namespace pybind11_tests {
8-
namespace test_class_sh_virtual_py_cpp_mix {
8+
namespace class_sh_virtual_py_cpp_mix {
99

1010
class Base {
1111
public:
@@ -43,16 +43,15 @@ struct CppDerivedVirtualOverrider : CppDerived, py::virtual_overrider_self_life_
4343
int get() const override { PYBIND11_OVERRIDE(int, CppDerived, get); }
4444
};
4545

46-
} // namespace test_class_sh_virtual_py_cpp_mix
46+
} // namespace class_sh_virtual_py_cpp_mix
4747
} // namespace pybind11_tests
4848

49-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_virtual_py_cpp_mix::Base)
50-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(
51-
pybind11_tests::test_class_sh_virtual_py_cpp_mix::CppDerivedPlain)
52-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_virtual_py_cpp_mix::CppDerived)
49+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_virtual_py_cpp_mix::Base)
50+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_virtual_py_cpp_mix::CppDerivedPlain)
51+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_virtual_py_cpp_mix::CppDerived)
5352

5453
TEST_SUBMODULE(class_sh_virtual_py_cpp_mix, m) {
55-
using namespace pybind11_tests::test_class_sh_virtual_py_cpp_mix;
54+
using namespace pybind11_tests::class_sh_virtual_py_cpp_mix;
5655

5756
py::classh<Base, BaseVirtualOverrider>(m, "Base").def(py::init<>()).def("get", &Base::get);
5857

tests/test_class_sh_with_alias.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include <memory>
66

77
namespace pybind11_tests {
8-
namespace test_class_sh_with_alias {
8+
namespace class_sh_with_alias {
99

1010
template <int SerNo> // Using int as a trick to easily generate a series of types.
1111
struct Abase {
@@ -73,14 +73,14 @@ void wrap(py::module_ m, const char *py_class_name) {
7373
m.def("AddInCppUniquePtr", AddInCppUniquePtr<SerNo>, py::arg("obj"), py::arg("other_val"));
7474
}
7575

76-
} // namespace test_class_sh_with_alias
76+
} // namespace class_sh_with_alias
7777
} // namespace pybind11_tests
7878

79-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_with_alias::Abase<0>)
80-
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::test_class_sh_with_alias::Abase<1>)
79+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_with_alias::Abase<0>)
80+
PYBIND11_SMART_HOLDER_TYPE_CASTERS(pybind11_tests::class_sh_with_alias::Abase<1>)
8181

8282
TEST_SUBMODULE(class_sh_with_alias, m) {
83-
using namespace pybind11_tests::test_class_sh_with_alias;
83+
using namespace pybind11_tests::class_sh_with_alias;
8484
wrap<0>(m, "Abase0");
8585
wrap<1>(m, "Abase1");
8686
}

0 commit comments

Comments
 (0)