From 9f7b3f735aae417838001b338c9970710e9cee82 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Tue, 31 May 2022 12:59:19 -0700 Subject: [PATCH] addl unit tests for PR #3970 (#3977) * Add test_perf_accessors (to be merged into test_pytypes). * Python < 3.8 f-string compatibility * Use thread_local in inc_ref_counter() * Intentional breakage, brute-force way to quickly find out how many platforms reach the PYBIND11_HANDLE_REF_DEBUG code, with and without threads. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove Intentional breakage * Drop perf test, move inc_refs tests to test_pytypes * Fold in PR #3970 with `#ifdef`s * Complete test coverage for all newly added code. * Condense new unit tests via a simple local helper macro. * Remove PYBIND11_PR3970 define. See https://github.com/pybind/pybind11/pull/3977#issuecomment-1142526417 * Move static keyword first (fixes silly oversight). Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- include/pybind11/pytypes.h | 19 +++++++++++++++ tests/test_pytypes.cpp | 49 ++++++++++++++++++++++++++++++++++++++ tests/test_pytypes.py | 9 +++++++ 3 files changed, 77 insertions(+) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index c71f1b2e9..0c5690064 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -180,6 +180,10 @@ private: PYBIND11_NAMESPACE_END(detail) +#if !defined(PYBIND11_HANDLE_REF_DEBUG) && !defined(NDEBUG) +# define PYBIND11_HANDLE_REF_DEBUG +#endif + /** \rst Holds a reference to a Python object (no reference counting) @@ -209,6 +213,9 @@ public: this function automatically. Returns a reference to itself. \endrst */ const handle &inc_ref() const & { +#ifdef PYBIND11_HANDLE_REF_DEBUG + inc_ref_counter(1); +#endif Py_XINCREF(m_ptr); return *this; } @@ -244,6 +251,18 @@ public: protected: PyObject *m_ptr = nullptr; + +#ifdef PYBIND11_HANDLE_REF_DEBUG +private: + static std::size_t inc_ref_counter(std::size_t add) { + thread_local std::size_t counter = 0; + counter += add; + return counter; + } + +public: + static std::size_t inc_ref_counter() { return inc_ref_counter(0); } +#endif }; /** \rst diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 8d296f655..ef214acc3 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -297,6 +297,55 @@ TEST_SUBMODULE(pytypes, m) { return d; }); + m.def("accessor_moves", []() { // See PR #3970 + py::list return_list; +#ifdef PYBIND11_HANDLE_REF_DEBUG + py::int_ py_int_0(0); + py::int_ py_int_42(42); + py::str py_str_count("count"); + + auto tup = py::make_tuple(0); + + py::sequence seq(tup); + + py::list lst; + lst.append(0); + +# define PYBIND11_LOCAL_DEF(...) \ + { \ + std::size_t inc_refs = py::handle::inc_ref_counter(); \ + __VA_ARGS__; \ + inc_refs = py::handle::inc_ref_counter() - inc_refs; \ + return_list.append(inc_refs); \ + } + + PYBIND11_LOCAL_DEF(tup[py_int_0]) // l-value (to have a control) + PYBIND11_LOCAL_DEF(tup[py::int_(0)]) // r-value + + PYBIND11_LOCAL_DEF(tup.attr(py_str_count)) // l-value + PYBIND11_LOCAL_DEF(tup.attr(py::str("count"))) // r-value + + PYBIND11_LOCAL_DEF(seq[py_int_0]) // l-value + PYBIND11_LOCAL_DEF(seq[py::int_(0)]) // r-value + + PYBIND11_LOCAL_DEF(seq.attr(py_str_count)) // l-value + PYBIND11_LOCAL_DEF(seq.attr(py::str("count"))) // r-value + + PYBIND11_LOCAL_DEF(lst[py_int_0]) // l-value + PYBIND11_LOCAL_DEF(lst[py::int_(0)]) // r-value + + PYBIND11_LOCAL_DEF(lst.attr(py_str_count)) // l-value + PYBIND11_LOCAL_DEF(lst.attr(py::str("count"))) // r-value + + auto lst_acc = lst[py::int_(0)]; + lst_acc = py::int_(42); // Detaches lst_acc from lst. + PYBIND11_LOCAL_DEF(lst_acc = py_int_42) // l-value + PYBIND11_LOCAL_DEF(lst_acc = py::int_(42)) // r-value +# undef PYBIND11_LOCAL_DEF +#endif + return return_list; + }); + // test_constructors m.def("default_constructors", []() { return py::dict("bytes"_a = py::bytes(), diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 9afe62f42..b91a7e156 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -317,6 +317,15 @@ def test_accessors(): assert d["var"] == 99 +def test_accessor_moves(): + inc_refs = m.accessor_moves() + if inc_refs: + # To be changed in PR #3970: [1, 0, 1, 0, ...] + assert inc_refs == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + else: + pytest.skip("Not defined: PYBIND11_HANDLE_REF_DEBUG") + + def test_constructors(): """C++ default and converting constructors are equivalent to type calls in Python""" types = [bytes, bytearray, str, bool, int, float, tuple, list, dict, set]