From 9d57ad457a2980bf7b78bb89082a62ab15a0e2b4 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 4 Apr 2025 11:09:18 -0700 Subject: [PATCH 1/2] Add test_cprofile_compatibility.py (currently intentionally set up to fail on all platforms). This test is unusual in that it has its isolated PYBIND11_MODULE (rather than adding a TEST_SUBMODULE). This is to ensure that unrelated tests do not somehow add to the profile stats. --- tests/CMakeLists.txt | 2 ++ tests/cprofile_compatibility_ext.cpp | 19 ++++++++++++++ tests/test_cprofile_compatibility.py | 39 ++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 tests/cprofile_compatibility_ext.cpp create mode 100644 tests/test_cprofile_compatibility.py diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 830c0374ab..51d4bee9c5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -137,6 +137,7 @@ set(PYBIND11_TEST_FILES test_constants_and_functions test_copy_move test_cpp_conduit + test_cprofile_compatibility.py test_custom_type_casters test_custom_type_setup test_docstring_options @@ -241,6 +242,7 @@ tests_extra_targets("test_exceptions.py" "cross_module_interleaved_error_already tests_extra_targets("test_gil_scoped.py" "cross_module_gil_utils") tests_extra_targets("test_cpp_conduit.py" "exo_planet_pybind11;exo_planet_c_api;home_planet_very_lonely_traveler") +tests_extra_targets("test_cprofile_compatibility.py" "cprofile_compatibility_ext") set(PYBIND11_EIGEN_REPO "https://gitlab.com/libeigen/eigen.git" diff --git a/tests/cprofile_compatibility_ext.cpp b/tests/cprofile_compatibility_ext.cpp new file mode 100644 index 0000000000..842477155c --- /dev/null +++ b/tests/cprofile_compatibility_ext.cpp @@ -0,0 +1,19 @@ +#include + +namespace pybind11_tests { +namespace cprofile_compatibility { +class CppClass {}; +} // namespace cprofile_compatibility +} // namespace pybind11_tests + +namespace py = pybind11; + +PYBIND11_MODULE(cprofile_compatibility_ext, m) { + using namespace pybind11_tests::cprofile_compatibility; + + m.def("free_func_return_secret", []() { return 102; }); + + py::class_(m, "CppClass") + .def(py::init<>()) + .def("member_func_return_secret", [](const CppClass &) { return 203; }); +} diff --git a/tests/test_cprofile_compatibility.py b/tests/test_cprofile_compatibility.py new file mode 100644 index 0000000000..ea78969165 --- /dev/null +++ b/tests/test_cprofile_compatibility.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +import cProfile as profile +import io +import pstats + +import cprofile_compatibility_ext + + +def _get_pstats(profiler): + output = io.StringIO() + stats = pstats.Stats(profiler, stream=output) + stats.sort_stats("cumtime") + stats.print_stats() + return output.getvalue() + + +class PyClass: + def py_member_func(self) -> None: + return cprofile_compatibility_ext.CppClass().member_func_return_secret() + + +def test_free_func_return_secret(): + profiler = profile.Profile() + profiler.enable() + assert cprofile_compatibility_ext.free_func_return_secret() == 102 + profiler.disable() + stats_output = _get_pstats(profiler) + assert "LETS_SEE_WHAT_WE_GET_FREE_FUNC" in stats_output + + +def test_member_func_return_secret(): + obj = PyClass() + profiler = profile.Profile() + profiler.enable() + assert obj.py_member_func() == 203 + profiler.disable() + stats_output = _get_pstats(profiler) + assert "LETS_SEE_WHAT_WE_GET_MEMBER_FUNC" in stats_output From 27bacd38f2178f89fbfb2133904b8a7ba529dfb3 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Fri, 4 Apr 2025 11:45:22 -0700 Subject: [PATCH 2/2] Replace assert so that we get the full pstats.Stats output --- tests/test_cprofile_compatibility.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_cprofile_compatibility.py b/tests/test_cprofile_compatibility.py index ea78969165..f91a3a1235 100644 --- a/tests/test_cprofile_compatibility.py +++ b/tests/test_cprofile_compatibility.py @@ -26,7 +26,9 @@ def test_free_func_return_secret(): assert cprofile_compatibility_ext.free_func_return_secret() == 102 profiler.disable() stats_output = _get_pstats(profiler) - assert "LETS_SEE_WHAT_WE_GET_FREE_FUNC" in stats_output + raise AssertionError( + f"\npstats.Stats output free_func BEGIN:\n{stats_output}\npstats.Stats output free_func END\n" + ) def test_member_func_return_secret(): @@ -36,4 +38,6 @@ def test_member_func_return_secret(): assert obj.py_member_func() == 203 profiler.disable() stats_output = _get_pstats(profiler) - assert "LETS_SEE_WHAT_WE_GET_MEMBER_FUNC" in stats_output + raise AssertionError( + f"\npstats.Stats output member_func BEGIN:\n{stats_output}\npstats.Stats output member_func END\n" + )