From d79400b96ce421d1cb617d5c67ec75ed9f243f78 Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Sun, 18 Apr 2021 19:34:53 -0400 Subject: [PATCH] Consolidate tests specific to fork for Drake --- tests/CMakeLists.txt | 6 +- tests/test_builtin_casters.cpp | 9 - tests/test_builtin_casters.py | 4 - tests/test_class.cpp | 18 -- tests/test_class.py | 37 ---- tests/test_drake_eigen.cpp | 128 ++++++++++++ tests/test_drake_eigen.py | 99 ++++++++++ ..._transfer.cpp => test_drake_smart_ptr.cpp} | 186 +++++++++++++++++- ...ip_transfer.py => test_drake_smart_ptr.py} | 144 +++++++++++++- tests/test_eigen.cpp | 75 ------- tests/test_eigen.py | 92 --------- tests/test_methods_and_attributes.py | 8 +- tests/test_multiple_inheritance.cpp | 22 --- tests/test_multiple_inheritance.py | 28 --- tests/test_smart_ptr.cpp | 121 ------------ tests/test_smart_ptr.py | 65 ------ 16 files changed, 559 insertions(+), 483 deletions(-) create mode 100644 tests/test_drake_eigen.cpp create mode 100644 tests/test_drake_eigen.py rename tests/{test_ownership_transfer.cpp => test_drake_smart_ptr.cpp} (50%) rename tests/{test_ownership_transfer.py => test_drake_smart_ptr.py} (56%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5f5617fa89..449ddf62f1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -105,6 +105,8 @@ set(PYBIND11_TEST_FILES test_copy_move.cpp test_custom_type_casters.cpp test_docstring_options.cpp + test_drake_eigen.cpp + test_drake_smart_ptr.cpp test_eigen.cpp test_enum.cpp test_eval.cpp @@ -122,7 +124,6 @@ set(PYBIND11_TEST_FILES test_numpy_vectorize.cpp test_opaque_types.cpp test_operator_overloading.cpp - test_ownership_transfer.cpp test_pickling.cpp test_pytypes.cpp test_sequences_and_iterators.cpp @@ -174,7 +175,8 @@ set(PYBIND11_CROSS_MODULE_GIL_TESTS test_gil_scoped.py) # keep it in PYBIND11_PYTEST_FILES, so that we get the "eigen is not installed" # skip message). list(FIND PYBIND11_TEST_FILES test_eigen.cpp PYBIND11_TEST_FILES_EIGEN_I) -if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) +list(FIND PYBIND11_TEST_FILES test_drake_eigen.cpp PYBIND11_TEST_FILES_EIGEN_I2) +if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1 OR PYBIND11_TEST_FILES_EIGEN_I2 GREATER -1) # Try loading via newer Eigen's Eigen3Config first (bypassing tools/FindEigen3.cmake). # Eigen 3.3.1+ exports a cmake 3.0+ target for handling dependency requirements, but also # produces a fatal error if loaded from a pre-3.0 cmake. diff --git a/tests/test_builtin_casters.cpp b/tests/test_builtin_casters.cpp index dfcbc0fc27..f4e775676d 100644 --- a/tests/test_builtin_casters.cpp +++ b/tests/test_builtin_casters.cpp @@ -268,13 +268,4 @@ TEST_SUBMODULE(builtin_casters, m) { m.def("takes_const_ptr", [](const ConstRefCasted* x) { return x->tag; }); m.def("takes_const_ref", [](const ConstRefCasted& x) { return x.tag; }); m.def("takes_const_ref_wrap", [](std::reference_wrapper x) { return x.get().tag; }); - - // For Drake issue: https://github.com/RobotLocomotion/drake/issues/9398 - m.def("test_pointer_caster", []() -> bool { - UserType a; - UserType *a_ptr = &a; - py::object o = py::cast(&a); // Rvalue - py::object o1 = py::cast(a_ptr); // Non-rvalue - return (py::cast(o) == a_ptr && py::cast(o1) == a_ptr); - }); } diff --git a/tests/test_builtin_casters.py b/tests/test_builtin_casters.py index 029f291444..fda0384333 100644 --- a/tests/test_builtin_casters.py +++ b/tests/test_builtin_casters.py @@ -535,7 +535,3 @@ def test_const_ref_caster(): assert m.takes_const_ptr(x) == 5 assert m.takes_const_ref(x) == 4 assert m.takes_const_ref_wrap(x) == 4 - - -def test_pointer_caster(): - assert m.test_pointer_caster() diff --git a/tests/test_class.cpp b/tests/test_class.cpp index ee2212327f..bd545e8c68 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -477,24 +477,6 @@ TEST_SUBMODULE(class_, m) { py::class_(gt, "OtherDuplicateNested"); py::class_(gt, "YetAnotherDuplicateNested"); }); - - // Test #1922 (drake#11424). - class ExampleVirt2 { - public: - virtual ~ExampleVirt2() = default; - virtual std::string get_name() const { return "ExampleVirt2"; } - }; - class PyExampleVirt2 : public ExampleVirt2 { - public: - std::string get_name() const override { - PYBIND11_OVERLOAD(std::string, ExampleVirt2, get_name, ); - } - }; - py::class_(m, "ExampleVirt2") - .def(py::init()) - .def("get_name", &ExampleVirt2::get_name); - m.def("example_virt2_get_name", - [](const ExampleVirt2& obj) { return obj.get_name(); }); } template class BreaksBase { public: diff --git a/tests/test_class.py b/tests/test_class.py index 69c8832f96..add33b0f33 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -475,40 +475,3 @@ class ClassScope: m.register_duplicate_nested_class_type(ClassScope) expected = 'generic_type: type "YetAnotherDuplicateNested" is already registered!' assert str(exc_info.value) == expected - - -@pytest.mark.skip( - reason="Generally reproducible in CPython, Python 3, non-debug, on Linux. " - "However, hard to pin this down for CI." -) -def test_1922(): - # Test #1922 (drake#11424). - # Define a derived class which *does not* overload the method. - # WARNING: The reproduction of this failure may be platform-specific, and - # seems to depend on the order of definition and/or the name of the classes - # defined. For example, trying to place this and the C++ code in - # `test_virtual_functions` makes `assert id_1 == id_2` below fail. - class Child1(m.ExampleVirt2): - pass - - id_1 = id(Child1) - assert m.example_virt2_get_name(m.ExampleVirt2()) == "ExampleVirt2" - assert m.example_virt2_get_name(Child1()) == "ExampleVirt2" - - # Now delete everything (and ensure it's deleted). - wref = weakref.ref(Child1) - del Child1 - pytest.gc_collect() - assert wref() is None - - # Define a derived class which *does* define an overload. - class Child2(m.ExampleVirt2): - def get_name(self): - return "Child2" - - id_2 = id(Child2) - assert id_1 == id_2 # This happens in CPython; not sure about PyPy. - assert m.example_virt2_get_name(m.ExampleVirt2()) == "ExampleVirt2" - # THIS WILL FAIL: This is using the cached `ExampleVirt2.get_name`, rather - # than re-inspect the Python dictionary. - assert m.example_virt2_get_name(Child2()) == "Child2" diff --git a/tests/test_drake_eigen.cpp b/tests/test_drake_eigen.cpp new file mode 100644 index 0000000000..f8757310e9 --- /dev/null +++ b/tests/test_drake_eigen.cpp @@ -0,0 +1,128 @@ +#include "pybind11_tests.h" +#include + +#include +#include "Eigen/src/Core/util/DisableStupidWarnings.h" + +typedef Eigen::AutoDiffScalar ADScalar; + +template +using MatrixX = Eigen::Matrix; + +typedef Eigen::Matrix VectorXADScalar; +typedef Eigen::Matrix VectorXADScalarR; +typedef Eigen::Matrix Vector5ADScalar; +typedef Eigen::Matrix Vector6ADScalarR; +PYBIND11_NUMPY_OBJECT_DTYPE(ADScalar); + +using DenseADScalarMatrixR = Eigen::Matrix; +using DenseADScalarMatrixC = Eigen::Matrix; + +VectorXADScalar& get_cm_adscalar() { + static VectorXADScalar value(1); + return value; +}; +VectorXADScalarR& get_rm_adscalar() { + static VectorXADScalarR value(1); + return value; +}; + +TEST_SUBMODULE(drake_general, m) { + m.def("double_adscalar_col", [](const VectorXADScalar &x) -> VectorXADScalar { return 2.0f * x; }); + m.def("double_adscalar_col5", [](const Vector5ADScalar &x) -> Vector5ADScalar { return 2.0f * x; }); + m.def("double_adscalar_row", [](const VectorXADScalarR &x) -> VectorXADScalarR { return 2.0f * x; }); + m.def("double_adscalar_row6", [](const Vector6ADScalarR &x) -> Vector6ADScalarR { return 2.0f * x; }); + m.def("add_rm_adscalar", [](py::EigenDRef x) { x.array() += 2; }); + m.def("add_cm_adscalar", [](py::EigenDRef x) { x.array() += 2; }); + m.def("get_cm_ref_adscalar", []() { + return py::EigenDRef(get_cm_adscalar()); + }); + m.def("get_rm_ref_adscalar", []() { + return py::EigenDRef(get_rm_adscalar()); + }); + m.def("get_cm_const_ref_adscalar", []() { return Eigen::Ref(get_cm_adscalar()); }); + m.def("get_rm_const_ref_adscalar", []() { return Eigen::Ref(get_rm_adscalar()); }); + + // Increments ADScalar Matrix, returns a copy. + m.def("incr_adscalar_matrix", [](const Eigen::Ref& m, double v) { + DenseADScalarMatrixC out = m; + out.array() += v; + return out; + }); + + // test_eigen_return_references, test_eigen_keepalive + // return value referencing/copying tests: + class ReturnTester { + Eigen::MatrixXd mat = create(); + DenseADScalarMatrixR ad_mat = create_ADScalar_mat(); + public: + ReturnTester() { print_created(this); } + ~ReturnTester() { print_destroyed(this); } + static Eigen::MatrixXd create() { return Eigen::MatrixXd::Ones(10, 10); } + static DenseADScalarMatrixR create_ADScalar_mat() { DenseADScalarMatrixR ad_mat(2, 2); + ad_mat << 1, 2, 3, 7; return ad_mat; } + static const Eigen::MatrixXd createConst() { return Eigen::MatrixXd::Ones(10, 10); } + Eigen::MatrixXd &get() { return mat; } + DenseADScalarMatrixR& get_ADScalarMat() {return ad_mat;} + Eigen::MatrixXd *getPtr() { return &mat; } + const Eigen::MatrixXd &view() { return mat; } + const Eigen::MatrixXd *viewPtr() { return &mat; } + Eigen::Ref ref() { return mat; } + Eigen::Ref refConst() { return mat; } + Eigen::Block block(int r, int c, int nrow, int ncol) { return mat.block(r, c, nrow, ncol); } + Eigen::Block blockConst(int r, int c, int nrow, int ncol) const { return mat.block(r, c, nrow, ncol); } + py::EigenDMap corners() { return py::EigenDMap(mat.data(), + py::EigenDStride(mat.outerStride() * (mat.outerSize()-1), mat.innerStride() * (mat.innerSize()-1))); } + py::EigenDMap cornersConst() const { return py::EigenDMap(mat.data(), + py::EigenDStride(mat.outerStride() * (mat.outerSize()-1), mat.innerStride() * (mat.innerSize()-1))); } + }; + using rvp = py::return_value_policy; + py::class_(m, "ReturnTester") + .def(py::init<>()) + .def_static("create", &ReturnTester::create) + .def_static("create_const", &ReturnTester::createConst) + .def("get", &ReturnTester::get, rvp::reference_internal) + .def("get_ADScalarMat", &ReturnTester::get_ADScalarMat, rvp::reference_internal) + .def("get_ptr", &ReturnTester::getPtr, rvp::reference_internal) + .def("view", &ReturnTester::view, rvp::reference_internal) + .def("view_ptr", &ReturnTester::view, rvp::reference_internal) + .def("copy_get", &ReturnTester::get) // Default rvp: copy + .def("copy_view", &ReturnTester::view) // " + .def("ref", &ReturnTester::ref) // Default for Ref is to reference + .def("ref_const", &ReturnTester::refConst) // Likewise, but const + .def("ref_safe", &ReturnTester::ref, rvp::reference_internal) + .def("ref_const_safe", &ReturnTester::refConst, rvp::reference_internal) + .def("copy_ref", &ReturnTester::ref, rvp::copy) + .def("copy_ref_const", &ReturnTester::refConst, rvp::copy) + .def("block", &ReturnTester::block) + .def("block_safe", &ReturnTester::block, rvp::reference_internal) + .def("block_const", &ReturnTester::blockConst, rvp::reference_internal) + .def("copy_block", &ReturnTester::block, rvp::copy) + .def("corners", &ReturnTester::corners, rvp::reference_internal) + .def("corners_const", &ReturnTester::cornersConst, rvp::reference_internal) + ; + + py::class_(m, "AutoDiffXd") + .def("__init__", + [](ADScalar & self, + double value, + const Eigen::VectorXd& derivatives) { + new (&self) ADScalar(value, derivatives); + }) + .def("value", [](const ADScalar & self) { + return self.value(); + }) + .def("__repr__", [](const ADScalar& self) { + return py::str("").format(self.value(), self.derivatives()); + }) + ; + + m.def("iss1105_col_obj", [](VectorXADScalar) { return true; }); + m.def("iss1105_row_obj", [](VectorXADScalarR) { return true; }); + m.def("cpp_matrix_shape", [](const MatrixX& A) { + return py::make_tuple(A.rows(), A.cols()); + }); + m.def("cpp_matrix_shape_ref", [](const Eigen::Ref>& A) { + return py::make_tuple(A.rows(), A.cols()); + }); +} diff --git a/tests/test_drake_eigen.py b/tests/test_drake_eigen.py new file mode 100644 index 0000000000..ad7bb4b776 --- /dev/null +++ b/tests/test_drake_eigen.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +import pytest + +import env # noqa: F401 + +from pybind11_tests import drake_eigen as m + + +def float_to_adscalar(arr, deriv): + arr = np.asarray(arr) + assert arr.dtype == float + new_arr = [m.AutoDiffXd(x, deriv) for x in arr.flat] + return np.array(new_arr).reshape(arr.shape) + + +def adscalar_to_float(arr): + arr = np.asarray(arr) + assert arr.dtype == object + new_arr = [x.value() for x in arr.flat] + return np.array(new_arr).reshape(arr.shape) + + +def check_array(a, b): + a, b = (np.asarray(x) for x in (a, b)) + assert a.shape == b.shape and a.dtype == b.dtype + for index, (ai, bi) in enumerate(zip(a.flat, b.flat)): + assert m.equal_to(ai, bi), index + + +def test_eigen_passing_adscalar(): + assert m.equal_to(1.0, 1.0) + assert not m.equal_to(1.0, 1.1) + assert m.equal_to(m.AutoDiffXd(0, [1.0]), m.AutoDiffXd(0, [1.0])) + assert not m.equal_to(m.AutoDiffXd(0, [1.0]), m.AutoDiffXd(0, [1.1])) + + adscalar_mat = float_to_adscalar(ref, deriv=[1.0]) + adscalar_vec_col = adscalar_mat[:, 0] + adscalar_vec_row = adscalar_mat[0, :] + + # Checking if a Python vector is getting doubled, when passed into a dynamic or fixed + # row or col vector in Eigen. + double_adscalar_mat = float_to_adscalar(2 * ref, deriv=[2.0]) + check_array(m.double_adscalar_col(adscalar_vec_col), double_adscalar_mat[:, 0]) + check_array(m.double_adscalar_col5(adscalar_vec_col), double_adscalar_mat[:, 0]) + check_array(m.double_adscalar_row(adscalar_vec_row), double_adscalar_mat[0, :]) + check_array(m.double_adscalar_row6(adscalar_vec_row), double_adscalar_mat[0, :]) + + # Adding 7 to the a dynamic matrix using reference. + incr_adscalar_mat = float_to_adscalar(ref + 7, deriv=[1.0]) + check_array(m.incr_adscalar_matrix(adscalar_mat, 7.0), incr_adscalar_mat) + # The original adscalar_mat remains unchanged in spite of passing by reference, since + # `Eigen::Ref` permits copying, and copying is the only valid operation for + # `dtype=object`. + check_array(adscalar_to_float(adscalar_mat), ref) + + # Changes in Python are not reflected in C++ when internal_reference is returned. + # These conversions should be disabled at runtime. + + def expect_ref_error(func): + with pytest.raises(RuntimeError) as excinfo: + func() + assert "dtype=object" in str(excinfo.value) + assert "reachable" not in str(excinfo.value) + + # - Return arguments. + expect_ref_error(lambda: m.get_cm_ref_adscalar()) + expect_ref_error(lambda: m.get_rm_ref_adscalar()) + expect_ref_error(lambda: m.get_cm_const_ref_adscalar()) + expect_ref_error(lambda: m.get_rm_const_ref_adscalar()) + # - - Mutable lvalues referenced via `reference_internal`. + return_tester = m.ReturnTester() + expect_ref_error(lambda: return_tester.get_ADScalarMat()) + # - Input arguments, writeable `Ref<>`s. + expect_ref_error(lambda: m.add_cm_adscalar(adscalar_vec_col)) + expect_ref_error(lambda: m.add_rm_adscalar(adscalar_vec_row)) + + # Checking Issue 1105 + assert m.iss1105_col_obj(adscalar_vec_col[:, None]) + assert m.iss1105_row_obj(adscalar_vec_row[None, :]) + + with pytest.raises(TypeError) as excinfo: + m.iss1105_row_obj(adscalar_vec_col[:, None]) + assert "incompatible function arguments" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: + m.iss1105_col_obj(adscalar_vec_row[None, :]) + assert "incompatible function arguments" in str(excinfo.value) + + +def test_eigen_obj_shape(): + # Ensure that matrices are of the same shape for dtype=object when given a 1D NumPy array. + # RobotLocomotion/drake#8620 + x_f = np.array([1, -1], dtype=float) + shape_f = m.cpp_matrix_shape(x_f) + x_ad = np.array([m.AutoDiffXd(0, []), m.AutoDiffXd(0, [])], dtype=object) + shape_ad = m.cpp_matrix_shape(x_ad) + shape_ad_ref = m.cpp_matrix_shape_ref(x_ad) + assert shape_f == shape_ad + assert shape_f == shape_ad_ref + diff --git a/tests/test_ownership_transfer.cpp b/tests/test_drake_smart_ptr.cpp similarity index 50% rename from tests/test_ownership_transfer.cpp rename to tests/test_drake_smart_ptr.cpp index 49724e04eb..3ea9511e06 100644 --- a/tests/test_ownership_transfer.cpp +++ b/tests/test_drake_smart_ptr.cpp @@ -12,6 +12,7 @@ #endif #include + #include "pybind11_tests.h" #include "object.h" @@ -31,6 +32,35 @@ enum Label : int { template class Stats {}; +struct Base1 { + Base1(int i) : i(i) { } + int foo() { return i; } + int i; +}; + +struct Base2a { + Base2a(int i) : i(i) { } + int bar() { return i; } + int i; +}; + +template > +class Container { +public: + Container(Ptr ptr) + : ptr_(std::move(ptr)) {} + T* get() const { return ptr_.get(); } + Ptr release() { return std::move(ptr_); } + + static void def(py::module &m, const std::string& name) { + py::class_(m, name.c_str()) + .def(py::init()) + .def("get", &Container::get) + .def("release", &Container::release); + } +private: + Ptr ptr_; +}; template class DefineBase { @@ -144,7 +174,56 @@ using class_shared_ = py::class_>; template using class_unique_ = py::class_; -TEST_SUBMODULE(ownership_transfer, m) { +class UniquePtrHeld { +public: + UniquePtrHeld() = delete; + UniquePtrHeld(const UniquePtrHeld&) = delete; + UniquePtrHeld(UniquePtrHeld&&) = delete; + + UniquePtrHeld(int value) + : value_(value) { + print_created(this, value); + } + ~UniquePtrHeld() { + print_destroyed(this); + } + int value() const { return value_; } +private: + int value_{}; +}; + +TEST_SUBMODULE(drake_smart_ptr, m) { + // For Drake issue: https://github.com/RobotLocomotion/drake/issues/9398 + m.def("test_pointer_caster", []() -> bool { + UserType a; + UserType *a_ptr = &a; + py::object o = py::cast(&a); // Rvalue + py::object o1 = py::cast(a_ptr); // Non-rvalue + return (py::cast(o) == a_ptr && py::cast(o1) == a_ptr); + }); + + Container::def(m, "ContainerBase1"); + + Container>::def(m, "ContainerBase2a"); + + // Test #1922 (drake#11424). + class ExampleVirt2 { + public: + virtual ~ExampleVirt2() = default; + virtual std::string get_name() const { return "ExampleVirt2"; } + }; + class PyExampleVirt2 : public ExampleVirt2 { + public: + std::string get_name() const override { + PYBIND11_OVERLOAD(std::string, ExampleVirt2, get_name, ); + } + }; + py::class_(m, "ExampleVirt2") + .def(py::init()) + .def("get_name", &ExampleVirt2::get_name); + m.def("example_virt2_get_name", + [](const ExampleVirt2& obj) { return obj.get_name(); }); + // No alias - will not have lifetime extended. class_shared_(m, "BaseBad") .def(py::init()) @@ -192,4 +271,109 @@ TEST_SUBMODULE(ownership_transfer, m) { .def("track_created", &PyInstanceStats::track_created) .def("track_destroyed", &PyInstanceStats::track_destroyed); m.def("get_instance_cstats", &get_instance_cstats); + + + py::class_(m, "UniquePtrHeld") + .def(py::init()) + .def("value", &UniquePtrHeld::value); + + class UniquePtrOther {}; + py::class_(m, "UniquePtrOther") + .def(py::init<>()); + + m.def("unique_ptr_pass_through", + [](std::unique_ptr obj) { + return obj; + }); + m.def("unique_ptr_terminal", + [](std::unique_ptr obj) { + obj.reset(); + return nullptr; + }); + + // Check traits in a concise manner. + static_assert( + py::detail::move_common>::value, + "This must be true."); + + // Guarantee API works as expected. + m.def("unique_ptr_pass_through_cast_from_py", + [](py::object obj_py) { + auto obj = + py::cast>(std::move(obj_py)); + return obj; + }); + m.def("unique_ptr_pass_through_move_from_py", + [](py::object obj_py) { + return py::move>(std::move(obj_py)); + }); + + m.def("unique_ptr_pass_through_move_to_py", + [](std::unique_ptr obj) { + return py::move(std::move(obj)); + }); + + m.def("unique_ptr_pass_through_cast_to_py", + [](std::unique_ptr obj) { + return py::cast(std::move(obj)); + }); + + class FirstT {}; + py::class_(m, "FirstT") + .def(py::init()); + class SecondT {}; + py::class_(m, "SecondT") + .def(py::init()); + + m.def("unique_ptr_overload", + [](std::unique_ptr obj, FirstT) { + py::dict out; + out["obj"] = py::cast(std::move(obj)); + out["overload"] = 1; + return out; + }); + m.def("unique_ptr_overload", + [](std::unique_ptr obj, SecondT) { + py::dict out; + out["obj"] = py::cast(std::move(obj)); + out["overload"] = 2; + return out; + }); + + // Ensure class is non-empty, so it's easier to detect double-free + // corruption. (If empty, this may be harder to see easily.) + struct SharedPtrHeld { int value = 10; }; + py::class_>(m, "SharedPtrHeld") + .def(py::init<>()); + m.def("shared_ptr_held_in_unique_ptr", + []() { + return std::unique_ptr(new SharedPtrHeld()); + }); + m.def("shared_ptr_held_func", + [](std::shared_ptr obj) { + return obj != nullptr && obj->value == 10; + }); + + // Test passing ownership of registered, but unowned, C++ instances back to + // Python. This happens when a raw pointer is passed first, and then + // ownership is transfered. + struct UniquePtrHeldContainer { + UniquePtrHeldContainer() { + value_.reset(new UniquePtrHeld(10)); + } + UniquePtrHeld* get() const { + return value_.get(); + } + using Ptr = std::unique_ptr; + Ptr reset(Ptr to) { + Ptr from = std::move(value_); + value_ = std::move(to); + return from; + } + std::unique_ptr value_; + }; + py::class_(m, "UniquePtrHeldContainer") + .def(py::init()) + .def("get", &UniquePtrHeldContainer::get, py::return_value_policy::reference_internal) + .def("reset", &UniquePtrHeldContainer::reset); } diff --git a/tests/test_ownership_transfer.py b/tests/test_drake_smart_ptr.py similarity index 56% rename from tests/test_ownership_transfer.py rename to tests/test_drake_smart_ptr.py index f48add15bd..ac536af4cd 100644 --- a/tests/test_ownership_transfer.py +++ b/tests/test_drake_smart_ptr.py @@ -1,13 +1,82 @@ # -*- coding: utf-8 -*- -from pybind11_tests import ownership_transfer as m -from pybind11_tests import ConstructorStats - -import env # noqa: F401 - import pytest import sys import weakref +import env # noqa: F401 + +from pybind11_tests import drake_smart_ptr as m +from pybind11_tests import ConstructorStats + + +def test_pointer_caster(): + assert m.test_pointer_caster() + + +@pytest.mark.skip( + reason="Generally reproducible in CPython, Python 3, non-debug, on Linux. " + "However, hard to pin this down for CI." +) +def test_1922(): + # Test #1922 (drake#11424). + # Define a derived class which *does not* overload the method. + # WARNING: The reproduction of this failure may be platform-specific, and + # seems to depend on the order of definition and/or the name of the classes + # defined. For example, trying to place this and the C++ code in + # `test_virtual_functions` makes `assert id_1 == id_2` below fail. + class Child1(m.ExampleVirt2): + pass + + id_1 = id(Child1) + assert m.example_virt2_get_name(m.ExampleVirt2()) == "ExampleVirt2" + assert m.example_virt2_get_name(Child1()) == "ExampleVirt2" + + # Now delete everything (and ensure it's deleted). + wref = weakref.ref(Child1) + del Child1 + pytest.gc_collect() + assert wref() is None + + # Define a derived class which *does* define an overload. + class Child2(m.ExampleVirt2): + def get_name(self): + return "Child2" + + id_2 = id(Child2) + assert id_1 == id_2 # This happens in CPython; not sure about PyPy. + assert m.example_virt2_get_name(m.ExampleVirt2()) == "ExampleVirt2" + # THIS WILL FAIL: This is using the cached `ExampleVirt2.get_name`, rather + # than re-inspect the Python dictionary. + assert m.example_virt2_get_name(Child2()) == "Child2" + + +@pytest.mark.skipif(env.PYPY, reason="Unsupported on PyPy") +def test_mi_ownership_constraint(): + # See below for positive tests. + + # unique_ptr + c = m.ContainerBase1(m.MIType(10, 100)) + assert c is not None + + # shared_ptr + # Should not throw an error. + obj = m.Base12a(10, 100) + assert obj.bar() == 100 + c = m.ContainerBase2a(obj) + # Use variable to avoid style violations. + assert c is not None + + # TODO(eric.cousineau): This currently causes a segfault in both + # this branch and on `master` (a303c6f). + # Figure out if there is a way to fix this? + + # assert c.get().bar() == 100 + + # # Should throw an error. + # c = m.ContainerBase2a(m.Bae12a(10, 100)) + # assert c.get().foo() == 10 + # assert c.get().bar() == 100 + def define_child(name, base_type, stats_type): # Derived instance of `DefineBase<>` in C++. @@ -160,3 +229,68 @@ def test_unique_ptr_derived_slicing(capture): assert obj.value() == 100 del obj assert cstats.alive() == 0 + + +def test_unique_ptr_arg(): + stats = ConstructorStats.get(m.UniquePtrHeld) + + pass_through_list = [ + m.unique_ptr_pass_through, + m.unique_ptr_pass_through_move_to_py, + m.unique_ptr_pass_through_cast_to_py, + # TODO(eric.cousineau): Fix these cases. + # m.unique_ptr_pass_through_cast_from_py, + # m.unique_ptr_pass_through_move_from_py, + ] + for pass_through in pass_through_list: + obj = m.UniquePtrHeld(1) + obj_ref = pass_through(obj) + assert stats.alive() == 1 + assert obj.value() == 1 + assert obj == obj_ref + del obj + del obj_ref + pytest.gc_collect() + assert stats.alive() == 0 + + obj = m.UniquePtrHeld(1) + m.unique_ptr_terminal(obj) + assert stats.alive() == 0 + + m.unique_ptr_terminal(m.UniquePtrHeld(2)) + assert stats.alive() == 0 + + assert m.unique_ptr_pass_through(None) is None + m.unique_ptr_terminal(None) + + with pytest.raises(TypeError): + m.unique_ptr_terminal(m.UniquePtrOther()) + + +def test_unique_ptr_to_shared_ptr(): + obj = m.shared_ptr_held_in_unique_ptr() + assert m.shared_ptr_held_func(obj) + + +def test_unique_ptr_overload_fail(): + obj = m.UniquePtrHeld(1) + # These overloads pass ownership back to Python. + out = m.unique_ptr_overload(obj, m.FirstT()) + assert out["obj"] is obj + assert out["overload"] == 1 + out = m.unique_ptr_overload(obj, m.SecondT()) + assert out["obj"] is obj + assert out["overload"] == 2 + + +def test_unique_ptr_held_container_from_cpp(): + def check_reset(obj_new): + c = m.UniquePtrHeldContainer() + obj = c.get() + assert obj is not None + obj_ref = c.reset(obj_new) + assert obj is obj_ref + assert c.get() is obj_new + + check_reset(obj_new=m.UniquePtrHeld(10)) + check_reset(obj_new=None) diff --git a/tests/test_eigen.cpp b/tests/test_eigen.cpp index baff69a126..b896deb7d3 100644 --- a/tests/test_eigen.cpp +++ b/tests/test_eigen.cpp @@ -17,20 +17,8 @@ #endif #include -#include -#include "Eigen/src/Core/util/DisableStupidWarnings.h" using MatrixXdR = Eigen::Matrix; -typedef Eigen::AutoDiffScalar ADScalar; - -template -using MatrixX = Eigen::Matrix; - -typedef Eigen::Matrix VectorXADScalar; -typedef Eigen::Matrix VectorXADScalarR; -typedef Eigen::Matrix Vector5ADScalar; -typedef Eigen::Matrix Vector6ADScalarR; -PYBIND11_NUMPY_OBJECT_DTYPE(ADScalar); // Sets/resets a testing reference matrix to have values of 10*r + c, where r and c are the // (1-based) row/column number. @@ -63,16 +51,6 @@ void reset_refs() { reset_ref(get_rm()); } -VectorXADScalar& get_cm_adscalar() { - static VectorXADScalar value(1); - return value; -}; -VectorXADScalarR& get_rm_adscalar() { - static VectorXADScalarR value(1); - return value; -}; - - // Returns element 2,1 from a matrix (used to test copy/nocopy) double get_elem(Eigen::Ref m) { return m(2, 1); }; @@ -100,9 +78,7 @@ TEST_SUBMODULE(eigen, m) { using FixedMatrixR = Eigen::Matrix; using FixedMatrixC = Eigen::Matrix; using DenseMatrixR = Eigen::Matrix; - using DenseADScalarMatrixR = Eigen::Matrix; using DenseMatrixC = Eigen::Matrix; - using DenseADScalarMatrixC = Eigen::Matrix; using FourRowMatrixC = Eigen::Matrix; using FourColMatrixC = Eigen::Matrix; using FourRowMatrixR = Eigen::Matrix; @@ -112,18 +88,13 @@ TEST_SUBMODULE(eigen, m) { // various tests m.def("double_col", [](const Eigen::VectorXf &x) -> Eigen::VectorXf { return 2.0f * x; }); - m.def("double_adscalar_col", [](const VectorXADScalar &x) -> VectorXADScalar { return 2.0f * x; }); - m.def("double_adscalar_col5", [](const Vector5ADScalar &x) -> Vector5ADScalar { return 2.0f * x; }); m.def("double_row", [](const Eigen::RowVectorXf &x) -> Eigen::RowVectorXf { return 2.0f * x; }); - m.def("double_adscalar_row", [](const VectorXADScalarR &x) -> VectorXADScalarR { return 2.0f * x; }); - m.def("double_adscalar_row6", [](const Vector6ADScalarR &x) -> Vector6ADScalarR { return 2.0f * x; }); m.def("double_complex", [](const Eigen::VectorXcf &x) -> Eigen::VectorXcf { return 2.0f * x; }); m.def("double_threec", [](py::EigenDRef x) { x *= 2; }); m.def("double_threer", [](py::EigenDRef x) { x *= 2; }); m.def("double_mat_cm", [](Eigen::MatrixXf x) -> Eigen::MatrixXf { return 2.0f * x; }); m.def("double_mat_rm", [](DenseMatrixR x) -> DenseMatrixR { return 2.0f * x; }); - // test_eigen_ref_to_python // Different ways of passing via Eigen::Ref; the first and second are the Eigen-recommended m.def("cholesky1", [](Eigen::Ref x) -> Eigen::MatrixXd { return x.llt().matrixL(); }); @@ -142,8 +113,6 @@ TEST_SUBMODULE(eigen, m) { // Mutators (Eigen maps into numpy variables): m.def("add_rm", add_rm); // Only takes row-contiguous m.def("add_cm", add_cm); // Only takes column-contiguous - m.def("add_rm_adscalar", [](py::EigenDRef x) { x.array() += 2; }); - m.def("add_cm_adscalar", [](py::EigenDRef x) { x.array() += 2; }); // Overloaded versions that will accept either row or column contiguous: m.def("add1", add_rm); m.def("add1", add_cm); @@ -155,17 +124,9 @@ TEST_SUBMODULE(eigen, m) { // Return mutable references (numpy maps into eigen variables) m.def("get_cm_ref", []() { return Eigen::Ref(get_cm()); }); m.def("get_rm_ref", []() { return Eigen::Ref(get_rm()); }); - m.def("get_cm_ref_adscalar", []() { - return py::EigenDRef(get_cm_adscalar()); - }); - m.def("get_rm_ref_adscalar", []() { - return py::EigenDRef(get_rm_adscalar()); - }); // The same references, but non-mutable (numpy maps into eigen variables, but is !writeable) m.def("get_cm_const_ref", []() { return Eigen::Ref(get_cm()); }); m.def("get_rm_const_ref", []() { return Eigen::Ref(get_rm()); }); - m.def("get_cm_const_ref_adscalar", []() { return Eigen::Ref(get_cm_adscalar()); }); - m.def("get_rm_const_ref_adscalar", []() { return Eigen::Ref(get_rm_adscalar()); }); m.def("reset_refs", reset_refs); // Restores get_{cm,rm}_ref to original values @@ -175,13 +136,6 @@ TEST_SUBMODULE(eigen, m) { return m; }, py::return_value_policy::reference); - // Increments ADScalar Matrix, returns a copy. - m.def("incr_adscalar_matrix", [](const Eigen::Ref& m, double v) { - DenseADScalarMatrixC out = m; - out.array() += v; - return out; - }); - // Same, but accepts a matrix of any strides m.def("incr_matrix_any", [](py::EigenDRef m, double v) { m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v); @@ -216,16 +170,12 @@ TEST_SUBMODULE(eigen, m) { // return value referencing/copying tests: class ReturnTester { Eigen::MatrixXd mat = create(); - DenseADScalarMatrixR ad_mat = create_ADScalar_mat(); public: ReturnTester() { print_created(this); } ~ReturnTester() { print_destroyed(this); } static Eigen::MatrixXd create() { return Eigen::MatrixXd::Ones(10, 10); } - static DenseADScalarMatrixR create_ADScalar_mat() { DenseADScalarMatrixR ad_mat(2, 2); - ad_mat << 1, 2, 3, 7; return ad_mat; } static const Eigen::MatrixXd createConst() { return Eigen::MatrixXd::Ones(10, 10); } Eigen::MatrixXd &get() { return mat; } - DenseADScalarMatrixR& get_ADScalarMat() {return ad_mat;} Eigen::MatrixXd *getPtr() { return &mat; } const Eigen::MatrixXd &view() { return mat; } const Eigen::MatrixXd *viewPtr() { return &mat; } @@ -244,7 +194,6 @@ TEST_SUBMODULE(eigen, m) { .def_static("create", &ReturnTester::create) .def_static("create_const", &ReturnTester::createConst) .def("get", &ReturnTester::get, rvp::reference_internal) - .def("get_ADScalarMat", &ReturnTester::get_ADScalarMat, rvp::reference_internal) .def("get_ptr", &ReturnTester::getPtr, rvp::reference_internal) .def("view", &ReturnTester::view, rvp::reference_internal) .def("view_ptr", &ReturnTester::view, rvp::reference_internal) @@ -264,21 +213,6 @@ TEST_SUBMODULE(eigen, m) { .def("corners_const", &ReturnTester::cornersConst, rvp::reference_internal) ; - py::class_(m, "AutoDiffXd") - .def("__init__", - [](ADScalar & self, - double value, - const Eigen::VectorXd& derivatives) { - new (&self) ADScalar(value, derivatives); - }) - .def("value", [](const ADScalar & self) { - return self.value(); - }) - .def("__repr__", [](const ADScalar& self) { - return py::str("").format(self.value(), self.derivatives()); - }) - ; - m.def("equal_to", [](double a, double b) { return a == b; }); // AutDiff's operator== only compares the value; we should compare the full scalar. m.def("equal_to", [](const ADScalar& a, const ADScalar& b) { @@ -375,19 +309,10 @@ TEST_SUBMODULE(eigen, m) { m.def("iss1105_col", [](Eigen::VectorXd) { return true; }); m.def("iss1105_row", [](Eigen::RowVectorXd) { return true; }); - m.def("iss1105_col_obj", [](VectorXADScalar) { return true; }); - m.def("iss1105_row_obj", [](VectorXADScalarR) { return true; }); - // Test the shape of a matrix via `type_caster`s. m.def("cpp_matrix_shape", [](const MatrixX& A) { return py::make_tuple(A.rows(), A.cols()); }); - m.def("cpp_matrix_shape", [](const MatrixX& A) { - return py::make_tuple(A.rows(), A.cols()); - }); - m.def("cpp_matrix_shape_ref", [](const Eigen::Ref>& A) { - return py::make_tuple(A.rows(), A.cols()); - }); // test_named_arguments // Make sure named arguments are working properly: diff --git a/tests/test_eigen.py b/tests/test_eigen.py index 0c336deaf0..b743939409 100644 --- a/tests/test_eigen.py +++ b/tests/test_eigen.py @@ -168,98 +168,6 @@ def test_nonunit_stride_from_python(): np.testing.assert_array_equal(counting_mat, [[0.0, 2, 2], [6, 16, 10], [6, 14, 8]]) -def float_to_adscalar(arr, deriv): - arr = np.asarray(arr) - assert arr.dtype == float - new_arr = [m.AutoDiffXd(x, deriv) for x in arr.flat] - return np.array(new_arr).reshape(arr.shape) - - -def adscalar_to_float(arr): - arr = np.asarray(arr) - assert arr.dtype == object - new_arr = [x.value() for x in arr.flat] - return np.array(new_arr).reshape(arr.shape) - - -def check_array(a, b): - a, b = (np.asarray(x) for x in (a, b)) - assert a.shape == b.shape and a.dtype == b.dtype - for index, (ai, bi) in enumerate(zip(a.flat, b.flat)): - assert m.equal_to(ai, bi), index - - -def test_eigen_passing_adscalar(): - assert m.equal_to(1.0, 1.0) - assert not m.equal_to(1.0, 1.1) - assert m.equal_to(m.AutoDiffXd(0, [1.0]), m.AutoDiffXd(0, [1.0])) - assert not m.equal_to(m.AutoDiffXd(0, [1.0]), m.AutoDiffXd(0, [1.1])) - - adscalar_mat = float_to_adscalar(ref, deriv=[1.0]) - adscalar_vec_col = adscalar_mat[:, 0] - adscalar_vec_row = adscalar_mat[0, :] - - # Checking if a Python vector is getting doubled, when passed into a dynamic or fixed - # row or col vector in Eigen. - double_adscalar_mat = float_to_adscalar(2 * ref, deriv=[2.0]) - check_array(m.double_adscalar_col(adscalar_vec_col), double_adscalar_mat[:, 0]) - check_array(m.double_adscalar_col5(adscalar_vec_col), double_adscalar_mat[:, 0]) - check_array(m.double_adscalar_row(adscalar_vec_row), double_adscalar_mat[0, :]) - check_array(m.double_adscalar_row6(adscalar_vec_row), double_adscalar_mat[0, :]) - - # Adding 7 to the a dynamic matrix using reference. - incr_adscalar_mat = float_to_adscalar(ref + 7, deriv=[1.0]) - check_array(m.incr_adscalar_matrix(adscalar_mat, 7.0), incr_adscalar_mat) - # The original adscalar_mat remains unchanged in spite of passing by reference, since - # `Eigen::Ref` permits copying, and copying is the only valid operation for - # `dtype=object`. - check_array(adscalar_to_float(adscalar_mat), ref) - - # Changes in Python are not reflected in C++ when internal_reference is returned. - # These conversions should be disabled at runtime. - - def expect_ref_error(func): - with pytest.raises(RuntimeError) as excinfo: - func() - assert "dtype=object" in str(excinfo.value) - assert "reachable" not in str(excinfo.value) - - # - Return arguments. - expect_ref_error(lambda: m.get_cm_ref_adscalar()) - expect_ref_error(lambda: m.get_rm_ref_adscalar()) - expect_ref_error(lambda: m.get_cm_const_ref_adscalar()) - expect_ref_error(lambda: m.get_rm_const_ref_adscalar()) - # - - Mutable lvalues referenced via `reference_internal`. - return_tester = m.ReturnTester() - expect_ref_error(lambda: return_tester.get_ADScalarMat()) - # - Input arguments, writeable `Ref<>`s. - expect_ref_error(lambda: m.add_cm_adscalar(adscalar_vec_col)) - expect_ref_error(lambda: m.add_rm_adscalar(adscalar_vec_row)) - - # Checking Issue 1105 - assert m.iss1105_col_obj(adscalar_vec_col[:, None]) - assert m.iss1105_row_obj(adscalar_vec_row[None, :]) - - with pytest.raises(TypeError) as excinfo: - m.iss1105_row_obj(adscalar_vec_col[:, None]) - assert "incompatible function arguments" in str(excinfo.value) - with pytest.raises(TypeError) as excinfo: - m.iss1105_col_obj(adscalar_vec_row[None, :]) - assert "incompatible function arguments" in str(excinfo.value) - - -def test_eigen_obj_shape(): - # Ensure that matrices are of the same shape for dtype=object when given a 1D NumPy array. - # RobotLocomotion/drake#8620 - x_f = np.array([1, -1], dtype=float) - shape_f = m.cpp_matrix_shape(x_f) - x_ad = np.array([m.AutoDiffXd(0, []), m.AutoDiffXd(0, [])], dtype=object) - shape_ad = m.cpp_matrix_shape(x_ad) - shape_ad_ref = m.cpp_matrix_shape_ref(x_ad) - assert shape_f == shape_ad - assert shape_f == shape_ad_ref - - def test_negative_stride_from_python(msg): """Eigen doesn't support (as of yet) negative strides. When a function takes an Eigen matrix by copy or const reference, we can pass a numpy array that has negative strides. Otherwise, an diff --git a/tests/test_methods_and_attributes.py b/tests/test_methods_and_attributes.py index 9c6bc22903..2aaf9331fd 100644 --- a/tests/test_methods_and_attributes.py +++ b/tests/test_methods_and_attributes.py @@ -8,10 +8,10 @@ def test_methods_and_attributes(): - instance1 = m.ExampleMandA() # 1 def.ctor - instance2 = m.ExampleMandA(32) # 1 ctor + instance1 = m.ExampleMandA() + instance2 = m.ExampleMandA(32) - instance1.add1(instance2) # 1 copy ctor + 1 move + instance1.add1(instance2) instance1.add2(instance2) instance1.add3(instance2) instance1.add4(instance2) @@ -24,7 +24,7 @@ def test_methods_and_attributes(): assert str(instance1) == "ExampleMandA[value=320]" assert str(instance2) == "ExampleMandA[value=32]" - assert str(instance1.self1()) == "ExampleMandA[value=320]" # 1 copy ctor + 1 move + assert str(instance1.self1()) == "ExampleMandA[value=320]" assert str(instance1.self2()) == "ExampleMandA[value=320]" assert str(instance1.self3()) == "ExampleMandA[value=320]" assert str(instance1.self4()) == "ExampleMandA[value=320]" diff --git a/tests/test_multiple_inheritance.cpp b/tests/test_multiple_inheritance.cpp index ec2d829db0..a1fa75f8a0 100644 --- a/tests/test_multiple_inheritance.cpp +++ b/tests/test_multiple_inheritance.cpp @@ -43,24 +43,6 @@ int WithStatic2::static_value2 = 2; int VanillaStaticMix1::static_value = 12; int VanillaStaticMix2::static_value = 12; -template > -class Container { -public: - Container(Ptr ptr) - : ptr_(std::move(ptr)) {} - T* get() const { return ptr_.get(); } - Ptr release() { return std::move(ptr_); } - - static void def(py::module &m, const std::string& name) { - py::class_(m, name.c_str()) - .def(py::init()) - .def("get", &Container::get) - .def("release", &Container::release); - } -private: - Ptr ptr_; -}; - TEST_SUBMODULE(multiple_inheritance, m) { // test_multiple_inheritance_mix1 @@ -95,8 +77,6 @@ TEST_SUBMODULE(multiple_inheritance, m) { py::class_(m, "MIType") .def(py::init()); - Container::def(m, "ContainerBase1"); - // test_multiple_inheritance_python_many_bases #define PYBIND11_BASEN(N) py::class_>(m, "BaseN" #N).def(py::init()).def("f" #N, [](BaseN &b) { return b.i + N; }) PYBIND11_BASEN( 1); PYBIND11_BASEN( 2); PYBIND11_BASEN( 3); PYBIND11_BASEN( 4); @@ -143,8 +123,6 @@ TEST_SUBMODULE(multiple_inheritance, m) { std::shared_ptr>(m, "Base12a", py::multiple_inheritance()) .def(py::init()); - Container>::def(m, "ContainerBase2a"); - m.def("bar_base2a", [](Base2a *b) { return b->bar(); }); m.def("bar_base2a_sharedptr", [](std::shared_ptr b) { return b->bar(); }); diff --git a/tests/test_multiple_inheritance.py b/tests/test_multiple_inheritance.py index 1d4ecd95f0..e6a7f9762b 100644 --- a/tests/test_multiple_inheritance.py +++ b/tests/test_multiple_inheritance.py @@ -359,31 +359,3 @@ def test_diamond_inheritance(): assert d is d.c0().b() assert d is d.c1().b() assert d is d.c0().c1().b().c0().b() - - -@pytest.mark.skipif(env.PYPY, reason="Unsupported on PyPy") -def test_mi_ownership_constraint(): - # See `test_ownership_transfer` for positive tests. - - # unique_ptr - c = m.ContainerBase1(m.MIType(10, 100)) - assert c is not None - - # shared_ptr - # Should not throw an error. - obj = m.Base12a(10, 100) - assert obj.bar() == 100 - c = m.ContainerBase2a(obj) - # Use variable to avoid style violations. - assert c is not None - - # TODO(eric.cousineau): This currently causes a segfault in both - # this branch and on `master` (a303c6f). - # Figure out if there is a way to fix this? - - # assert c.get().bar() == 100 - - # # Should throw an error. - # c = m.ContainerBase2a(m.Bae12a(10, 100)) - # assert c.get().foo() == 10 - # assert c.get().bar() == 100 diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index 157621932a..e0af249790 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -430,125 +430,4 @@ TEST_SUBMODULE(smart_ptr, m) { list.append(py::cast(e)); return list; }); - - class UniquePtrHeld { - public: - UniquePtrHeld() = delete; - UniquePtrHeld(const UniquePtrHeld&) = delete; - UniquePtrHeld(UniquePtrHeld&&) = delete; - - UniquePtrHeld(int value) - : value_(value) { - print_created(this, value); - } - ~UniquePtrHeld() { - print_destroyed(this); - } - int value() const { return value_; } - private: - int value_{}; - }; - py::class_(m, "UniquePtrHeld") - .def(py::init()) - .def("value", &UniquePtrHeld::value); - - class UniquePtrOther {}; - py::class_(m, "UniquePtrOther") - .def(py::init<>()); - - m.def("unique_ptr_pass_through", - [](std::unique_ptr obj) { - return obj; - }); - m.def("unique_ptr_terminal", - [](std::unique_ptr obj) { - obj.reset(); - return nullptr; - }); - - // Check traits in a concise manner. - static_assert( - py::detail::move_common>::value, - "This must be true."); - - // Guarantee API works as expected. - m.def("unique_ptr_pass_through_cast_from_py", - [](py::object obj_py) { - auto obj = - py::cast>(std::move(obj_py)); - return obj; - }); - m.def("unique_ptr_pass_through_move_from_py", - [](py::object obj_py) { - return py::move>(std::move(obj_py)); - }); - - m.def("unique_ptr_pass_through_move_to_py", - [](std::unique_ptr obj) { - return py::move(std::move(obj)); - }); - - m.def("unique_ptr_pass_through_cast_to_py", - [](std::unique_ptr obj) { - return py::cast(std::move(obj)); - }); - - class FirstT {}; - py::class_(m, "FirstT") - .def(py::init()); - class SecondT {}; - py::class_(m, "SecondT") - .def(py::init()); - - m.def("unique_ptr_overload", - [](std::unique_ptr obj, FirstT) { - py::dict out; - out["obj"] = py::cast(std::move(obj)); - out["overload"] = 1; - return out; - }); - m.def("unique_ptr_overload", - [](std::unique_ptr obj, SecondT) { - py::dict out; - out["obj"] = py::cast(std::move(obj)); - out["overload"] = 2; - return out; - }); - - // Ensure class is non-empty, so it's easier to detect double-free - // corruption. (If empty, this may be harder to see easily.) - struct SharedPtrHeld { int value = 10; }; - py::class_>(m, "SharedPtrHeld") - .def(py::init<>()); - m.def("shared_ptr_held_in_unique_ptr", - []() { - return std::unique_ptr(new SharedPtrHeld()); - }); - m.def("shared_ptr_held_func", - [](std::shared_ptr obj) { - return obj != nullptr && obj->value == 10; - }); - - // Test passing ownership of registered, but unowned, C++ instances back to - // Python. This happens when a raw pointer is passed first, and then - // ownership is transfered. - struct UniquePtrHeldContainer { - UniquePtrHeldContainer() { - value_.reset(new UniquePtrHeld(10)); - } - UniquePtrHeld* get() const { - return value_.get(); - } - using Ptr = std::unique_ptr; - Ptr reset(Ptr to) { - Ptr from = std::move(value_); - value_ = std::move(to); - return from; - } - std::unique_ptr value_; - }; - py::class_(m, "UniquePtrHeldContainer") - .def(py::init()) - .def("get", &UniquePtrHeldContainer::get, py::return_value_policy::reference_internal) - .def("reset", &UniquePtrHeldContainer::reset); } diff --git a/tests/test_smart_ptr.py b/tests/test_smart_ptr.py index ce07ecf952..85f61a3223 100644 --- a/tests/test_smart_ptr.py +++ b/tests/test_smart_ptr.py @@ -316,68 +316,3 @@ def test_shared_ptr_gc(): pytest.gc_collect() for i, v in enumerate(el.get()): assert i == v.value() - - -def test_unique_ptr_arg(): - stats = ConstructorStats.get(m.UniquePtrHeld) - - pass_through_list = [ - m.unique_ptr_pass_through, - m.unique_ptr_pass_through_move_to_py, - m.unique_ptr_pass_through_cast_to_py, - # TODO(eric.cousineau): Fix these cases. - # m.unique_ptr_pass_through_cast_from_py, - # m.unique_ptr_pass_through_move_from_py, - ] - for pass_through in pass_through_list: - obj = m.UniquePtrHeld(1) - obj_ref = pass_through(obj) - assert stats.alive() == 1 - assert obj.value() == 1 - assert obj == obj_ref - del obj - del obj_ref - pytest.gc_collect() - assert stats.alive() == 0 - - obj = m.UniquePtrHeld(1) - m.unique_ptr_terminal(obj) - assert stats.alive() == 0 - - m.unique_ptr_terminal(m.UniquePtrHeld(2)) - assert stats.alive() == 0 - - assert m.unique_ptr_pass_through(None) is None - m.unique_ptr_terminal(None) - - with pytest.raises(TypeError): - m.unique_ptr_terminal(m.UniquePtrOther()) - - -def test_unique_ptr_to_shared_ptr(): - obj = m.shared_ptr_held_in_unique_ptr() - assert m.shared_ptr_held_func(obj) - - -def test_unique_ptr_overload_fail(): - obj = m.UniquePtrHeld(1) - # These overloads pass ownership back to Python. - out = m.unique_ptr_overload(obj, m.FirstT()) - assert out["obj"] is obj - assert out["overload"] == 1 - out = m.unique_ptr_overload(obj, m.SecondT()) - assert out["obj"] is obj - assert out["overload"] == 2 - - -def test_unique_ptr_held_container_from_cpp(): - def check_reset(obj_new): - c = m.UniquePtrHeldContainer() - obj = c.get() - assert obj is not None - obj_ref = c.reset(obj_new) - assert obj is obj_ref - assert c.get() is obj_new - - check_reset(obj_new=m.UniquePtrHeld(10)) - check_reset(obj_new=None)