Files
pybind11/tests/CMakeLists.txt
Xuehai Pan 0057e4945d Add per-interpreter storage for gil_safe_call_once_and_store (#5933)
* Add new argument to `gil_safe_call_once_and_store::call_once_and_store_result`

* Add per-interpreter storage for `gil_safe_call_once_and_store`

* Make `~gil_safe_call_once_and_store` a no-op

* Fix C++11 compatibility

* Improve thread-safety and add default finalizer

* Try fix thread-safety

* Try fix thread-safety

* Add a warning comment

* Simplify `PYBIND11_INTERNALS_VERSION >= 12`

* Try fix thread-safety

* Try fix thread-safety

* Revert get_pp()

* Update comments

* Move call-once storage out of internals

* Revert internal version bump

* Cleanup outdated comments

* Move atomic_bool alias into pybind11::detail namespace

The `using atomic_bool = ...` declaration was at global scope,
polluting the global namespace. Move it into pybind11::detail
to avoid potential conflicts with user code.

* Add explicit #include <unordered_map> for subinterpreter support

The subinterpreter branch uses std::unordered_map but relied on
transitive includes. Add an explicit include for robustness.

* Remove extraneous semicolon after destructor definition

Style fix: remove trailing semicolon after ~call_once_storage()
destructor body.

* Add comment explaining unused finalize parameter

Clarify why the finalize callback parameter is intentionally ignored
when subinterpreter support is disabled: the storage is process-global
and leaked to avoid destructor calls after interpreter finalization.

* Add comment explaining error_scope usage

Clarify why error_scope is used: to preserve any existing Python
error state that might be cleared or modified by dict_getitemstringref.

* Improve exception safety in get_or_create_call_once_storage_map()

Use std::unique_ptr to hold the newly allocated storage map until
the capsule is successfully created. This prevents a memory leak
if capsule creation throws an exception.

* Add timeout-minutes: 3 to cpptest workflow steps

Add a 3-minute timeout to all C++ test (cpptest) steps across all
platforms to detect hangs early. This uses GitHub Actions' built-in
timeout-minutes property which works on Linux, macOS, and Windows.

* Add progress reporter for test_with_catch Catch2 runner

Add a custom Catch2 streaming reporter that prints one line per test
case as it starts and ends, with immediate flushing to keep CI logs
current. This makes it easy to see where the embedded/interpreter
tests are spending time and to pinpoint which test case is stuck
when builds hang (e.g., free-threading issues).

The reporter:
- Prints "[ RUN      ]" when each test starts
- Prints "[       OK ]" or "[  FAILED  ]" when each test ends
- Prints the Python version once at the start via Py_GetVersion()
- Uses StreamingReporterBase for immediate output (not buffered)
- Is set as the default reporter via CATCH_CONFIG_DEFAULT_REPORTER

This approach gives visibility into all tests without changing their
behavior, turning otherwise opaque 90-minute CI timeouts into
locatable issues in the Catch output.

* clang-format auto-fix (overlooked before)

* Disable "Move Subinterpreter" test on free-threaded Python 3.14+

This test hangs in Py_EndInterpreter() when the subinterpreter is
destroyed from a different thread than it was created on.

The hang was observed:
- Intermittently on macOS with Python 3.14.0t
- Predictably on macOS, Ubuntu, and Windows with Python 3.14.1t and 3.14.2t

Root cause analysis points to an interaction between pybind11's
subinterpreter creation code and CPython's free-threaded runtime,
specifically around PyThreadState_Swap() after PyThreadState_DeleteCurrent().

See detailed analysis: https://github.com/pybind/pybind11/pull/5933

* style: pre-commit fixes

* Add test for gil_safe_call_once_and_store per-interpreter isolation

This test verifies that gil_safe_call_once_and_store provides separate
storage for each interpreter when subinterpreter support is enabled.

The test caches the interpreter ID in the main interpreter, then creates
a subinterpreter and verifies it gets its own cached value (not the main
interpreter's). Without per-interpreter storage, the subinterpreter would
incorrectly see the main interpreter's cached object.

* Add STARTING/DONE timestamps to test_with_catch output

Print UTC timestamps at the beginning and end of the test run to make
it immediately clear when tests started and whether they ran to
completion. The DONE message includes the Catch session result value.

Example output:
  [ STARTING ] 2025-12-21 03:23:20.497Z
  [ PYTHON   ] 3.14.2 ...
  [ RUN      ] Threads
  [       OK ] Threads
  [ DONE     ] 2025-12-21 03:23:20.512Z (result 0)

* Disable stdout buffering in test_with_catch

Ensure test output appears immediately in CI logs by disabling stdout
buffering. Without this, output may be lost if the process is killed
by a timeout, making it difficult to diagnose which test was hanging.

* EXPERIMENT: Re-enable hanging test to verify CI log buffering fix

This is a temporary commit to verify that the unbuffered stdout fix
makes the hanging test visible in CI logs. REVERT THIS COMMIT after
confirming the output appears.

* Revert "Disable stdout buffering in test_with_catch"

This reverts commit 0f8f32a92a.

* Use USES_TERMINAL for cpptest to show output immediately

Ninja buffers subprocess output until completion. When a test hangs,
the output is never shown, making it impossible to diagnose which test
is hanging. USES_TERMINAL gives the command direct terminal access,
bypassing ninja's buffering.

This explains why Windows CI showed test progress but Linux/macOS did
not - Windows uses MSBuild which doesn't buffer the same way.

* Fix clang-tidy performance-avoid-endl warning

Use '\n' instead of std::endl since USES_TERMINAL now handles
output buffering at the CMake level.

* Add SIGTERM handler to show when test is killed by timeout

When a test hangs and is killed by `timeout`, Catch2 marks it as failed
but the process exits before printing [ DONE ]. This made it unclear
whether the test failed normally or was terminated.

The signal handler prints a clear message when SIGTERM is received,
making timeout-related failures obvious in CI logs.

* Fix typo: atleast -> at_least

* Fix GCC warn_unused_result error for write() in signal handler

Assign the return value to a variable to satisfy GCC's warn_unused_result
attribute, then cast to void to suppress unused variable warning.

* Add USES_TERMINAL to other C++ test targets

Apply the same ninja output buffering fix to test_cross_module_rtti
and test_pure_cpp targets. Also add explanatory comments to all
USES_TERMINAL usages.

* Revert "EXPERIMENT: Re-enable hanging test to verify CI log buffering fix"

This reverts commit a3abdeea89.

* Update comment to reference PR #5940 for Move Subinterpreter fix

* Add alias `interpid_t = std::int64_t`

* Add isolation and gc test for `gil_safe_call_once_and_store`

* Add thread local cache for gil_safe_call_once_and_store

* Revert "Add thread local cache for gil_safe_call_once_and_store"

This reverts commit 5d6681956d2d326fe74c7bf80e845c8e8ddb2a7c.

* Revert changes according to code review

* Relocate multiple-interpreters tests

* Add more tests for multiple interpreters

* Remove copy constructor

* Apply suggestions from code review

* Refactor to use per-storage capsule instead

* Update comments

* Update singleton tests

* Use interpreter id type for `get_num_interpreters_seen()`

* Suppress unused variable warning

* HACKING

* Revert "HACKING"

This reverts commit 534235ea55.

* Try fix concurrency

* Test even harder

* Reorg code to avoid duplicates

* Fix unique_ptr::reset -> unique_ptr::release

* Extract reusable functions

* Fix indentation

* Appease warnings for MSVC

* Appease warnings for MSVC

* Appease warnings for MSVC

* Try fix concurrency by not using `get_num_interpreters_seen() > 1`

* Try fix tests

* Make Python path handling more robust

* Update comments and assertion messages

* Revert changes according to code review

* Disable flaky tests

* Use `@pytest.mark.xfail` rather than `pytest.skip`

* Retrigger CI

* Retrigger CI

* Revert file moves

* Refactor atomic_get_or_create_in_state_dict: improve API and fix on_fetch_ bug

Three improvements to atomic_get_or_create_in_state_dict:

1. Return std::pair<Payload*, bool> instead of just Payload*
   - The bool indicates whether storage was newly created (true) or
     already existed (false), following std::map::insert convention.
   - This fixes a bug where on_fetch_ was called even for newly created
     internals, when it should only run for fetched (existing) ones.
     (Identified by @b-pass in code review)

2. Change LeakOnInterpreterShutdown from template param to runtime arg
   - Renamed to `clear_destructor` to describe what it does locally,
     rather than embedding assumptions about why it's used.
   - Reduces template instantiations (header-only library benefits).
   - The check is in the slow path (create) anyway, so negligible cost.

3. Remove unnecessary braces around the fast-path lookup
   - The braces created a nested scope but declared no local variables
     that would benefit from scoping.

* Remove unused PYBIND11_MULTIPLE_INTERPRETERS_TEST_FILES variable

This variable was defined but never used.

---------

Co-authored-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-12-24 23:33:02 -08:00

669 lines
24 KiB
CMake

# CMakeLists.txt -- Build system for the pybind11 test suite
#
# Copyright (c) 2015 Wenzel Jakob <wenzel@inf.ethz.ch>
#
# All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
cmake_minimum_required(VERSION 3.15...4.0)
# Filter out items; print an optional message if any items filtered. This ignores extensions.
#
# Usage:
# pybind11_filter_tests(LISTNAME file1.cpp file2.cpp ... MESSAGE "")
#
macro(pybind11_filter_tests LISTNAME)
cmake_parse_arguments(ARG "" "MESSAGE" "" ${ARGN})
set(PYBIND11_FILTER_TESTS_FOUND OFF)
# Make a list of the test without any extensions, for easier filtering.
set(_TMP_ACTUAL_LIST "${${LISTNAME}};") # enforce ';' at the end to allow matching last item.
string(REGEX REPLACE "\\.[^.;]*;" ";" LIST_WITHOUT_EXTENSIONS "${_TMP_ACTUAL_LIST}")
foreach(filename IN LISTS ARG_UNPARSED_ARGUMENTS)
string(REGEX REPLACE "\\.[^.]*$" "" filename_no_ext ${filename})
# Search in the list without extensions.
list(FIND LIST_WITHOUT_EXTENSIONS ${filename_no_ext} _FILE_FOUND)
if(_FILE_FOUND GREATER -1)
list(REMOVE_AT ${LISTNAME} ${_FILE_FOUND}) # And remove from the list with extensions.
list(REMOVE_AT LIST_WITHOUT_EXTENSIONS ${_FILE_FOUND}
)# And our search list, to ensure it is in sync.
set(PYBIND11_FILTER_TESTS_FOUND ON)
endif()
endforeach()
if(PYBIND11_FILTER_TESTS_FOUND AND ARG_MESSAGE)
message(STATUS "${ARG_MESSAGE}")
endif()
endmacro()
macro(possibly_uninitialized)
foreach(VARNAME ${ARGN})
if(NOT DEFINED "${VARNAME}")
set("${VARNAME}" "")
endif()
endforeach()
endmacro()
# Function to add additional targets if any of the provided tests are found.
# Needles; Specifies the test names to look for.
# Additions; Specifies the additional test targets to add when any of the needles are found.
macro(tests_extra_targets needles additions)
# Add the index for this relation to the index extra targets map.
list(LENGTH PYBIND11_TEST_EXTRA_TARGETS PYBIND11_TEST_EXTRA_TARGETS_LEN)
list(APPEND PYBIND11_TEST_EXTRA_TARGETS ${PYBIND11_TEST_EXTRA_TARGETS_LEN})
# Add the test names to look for, and the associated test target additions.
set(PYBIND11_TEST_EXTRA_TARGETS_NEEDLES_${PYBIND11_TEST_EXTRA_TARGETS_LEN} ${needles})
set(PYBIND11_TEST_EXTRA_TARGETS_ADDITION_${PYBIND11_TEST_EXTRA_TARGETS_LEN} ${additions})
endmacro()
# New Python support
if(DEFINED Python_EXECUTABLE)
set(PYTHON_EXECUTABLE "${Python_EXECUTABLE}")
set(PYTHON_VERSION "${Python_VERSION}")
endif()
# There's no harm in including a project in a project
project(pybind11_tests CXX)
# Access FindCatch and more
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../tools")
option(PYBIND11_WERROR "Report all warnings as errors" OFF)
option(DOWNLOAD_CATCH "Download catch2 if not found" OFF)
option(DOWNLOAD_EIGEN "Download EIGEN" OFF)
option(PYBIND11_CUDA_TESTS "Enable building CUDA tests" OFF)
option(PYBIND11_TEST_SMART_HOLDER "Change the default to smart holder" OFF)
set(PYBIND11_TEST_OVERRIDE
""
CACHE STRING "Tests from ;-separated list of *.cpp files will be built instead of all tests")
set(PYBIND11_TEST_FILTER
""
CACHE STRING "Tests from ;-separated list of *.cpp files will be removed from all tests")
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
# We're being loaded directly, i.e. not via add_subdirectory, so make this
# work as its own project and load the pybind11Config to get the tools we need
if(SKBUILD)
add_subdirectory(.. pybind11_src)
else()
find_package(pybind11 REQUIRED CONFIG)
endif()
endif()
if(NOT CMAKE_BUILD_TYPE AND NOT DEFINED CMAKE_CONFIGURATION_TYPES)
message(STATUS "Setting tests build type to MinSizeRel as none was specified")
set(CMAKE_BUILD_TYPE
MinSizeRel
CACHE STRING "Choose the type of build." FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel"
"RelWithDebInfo")
endif()
if(PYBIND11_CUDA_TESTS)
enable_language(CUDA)
if(DEFINED CMAKE_CXX_STANDARD)
set(CMAKE_CUDA_STANDARD ${CMAKE_CXX_STANDARD})
endif()
set(CMAKE_CUDA_STANDARD_REQUIRED ON)
endif()
# Full set of test files (you can override these; see below, overrides ignore extension)
# Any test that has no extension is both .py and .cpp, so 'foo' will add 'foo.cpp' and 'foo.py'.
# Any test that has an extension is exclusively that and handled as such.
set(PYBIND11_TEST_FILES
test_async
test_buffers
test_builtin_casters
test_call_policies
test_callbacks
test_chrono
test_class
test_class_cross_module_use_after_one_module_dealloc
test_class_release_gil_before_calling_cpp_dtor
test_class_sh_basic
test_class_sh_disowning
test_class_sh_disowning_mi
test_class_sh_factory_constructors
test_class_sh_inheritance
test_class_sh_mi_thunks
test_class_sh_property
test_class_sh_property_non_owning
test_class_sh_shared_ptr_copy_move
test_class_sh_trampoline_basic
test_class_sh_trampoline_self_life_support
test_class_sh_trampoline_shared_from_this
test_class_sh_trampoline_shared_ptr_cpp_arg
test_class_sh_trampoline_unique_ptr
test_class_sh_unique_ptr_custom_deleter
test_class_sh_unique_ptr_member
test_class_sh_virtual_py_cpp_mix
test_const_name
test_constants_and_functions
test_copy_move
test_cpp_conduit
test_custom_type_casters
test_custom_type_setup
test_docstring_options
test_docs_advanced_cast_custom
test_eigen_matrix
test_eigen_tensor
test_enum
test_eval
test_exceptions
test_factory_constructors
test_gil_scoped
test_iostream
test_kwargs_and_defaults
test_local_bindings
test_methods_and_attributes
test_modules
test_multiple_inheritance
test_multiple_interpreters.py
test_native_enum
test_numpy_array
test_numpy_dtypes
test_numpy_scalars
test_numpy_vectorize
test_opaque_types
test_operator_overloading
test_pickling
test_potentially_slicing_weak_ptr
test_python_multiple_inheritance
test_pytypes
test_scoped_critical_section
test_sequences_and_iterators
test_smart_ptr
test_stl
test_stl_binders
test_tagbased_polymorphic
test_thread
test_type_caster_pyobject_ptr
test_type_caster_std_function_specializations
test_union
test_unnamed_namespace_a
test_unnamed_namespace_b
test_vector_unique_ptr_member
test_virtual_functions
test_warnings)
# Invoking cmake with something like:
# cmake -DPYBIND11_TEST_OVERRIDE="test_callbacks.cpp;test_pickling.cpp" ..
# lets you override the tests that get compiled and run. You can restore to all tests with:
# cmake -DPYBIND11_TEST_OVERRIDE= ..
if(PYBIND11_TEST_OVERRIDE)
# Instead of doing a direct override here, we iterate over the overrides without extension and
# match them against entries from the PYBIND11_TEST_FILES, anything that not matches goes into the filter list.
string(REGEX REPLACE "\\.[^.;]*;" ";" TEST_OVERRIDE_NO_EXT "${PYBIND11_TEST_OVERRIDE};")
string(REGEX REPLACE "\\.[^.;]*;" ";" TEST_FILES_NO_EXT "${PYBIND11_TEST_FILES};")
# This allows the override to be done with extensions, preserving backwards compatibility.
foreach(test_name ${TEST_FILES_NO_EXT})
if(NOT ${test_name} IN_LIST TEST_OVERRIDE_NO_EXT
)# If not in the allowlist, add to be filtered out.
list(APPEND PYBIND11_TEST_FILTER ${test_name})
endif()
endforeach()
endif()
# You can also filter tests:
if(PYBIND11_TEST_FILTER)
pybind11_filter_tests(PYBIND11_TEST_FILES ${PYBIND11_TEST_FILTER})
endif()
# Skip tests for CUDA check:
# /pybind11/tests/test_constants_and_functions.cpp(125):
# error: incompatible exception specifications
if(PYBIND11_CUDA_TESTS)
pybind11_filter_tests(
PYBIND11_TEST_FILES test_constants_and_functions.cpp MESSAGE
"Skipping test_constants_and_functions due to incompatible exception specifications")
endif()
# Now that the test filtering is complete, we need to split the list into the test for PYTEST
# and the list for the cpp targets.
set(PYBIND11_CPPTEST_FILES "")
set(PYBIND11_PYTEST_FILES "")
foreach(test_name ${PYBIND11_TEST_FILES})
if(test_name MATCHES "\\.py$") # Ends in .py, purely python test.
list(APPEND PYBIND11_PYTEST_FILES ${test_name})
elseif(test_name MATCHES "\\.cpp$") # Ends in .cpp, purely cpp test.
list(APPEND PYBIND11_CPPTEST_FILES ${test_name})
elseif(NOT test_name MATCHES "\\.") # No extension specified, assume both, add extension.
list(APPEND PYBIND11_PYTEST_FILES ${test_name}.py)
list(APPEND PYBIND11_CPPTEST_FILES ${test_name}.cpp)
else()
message(WARNING "Unhanded test extension in test: ${test_name}")
endif()
endforeach()
set(PYBIND11_TEST_FILES ${PYBIND11_CPPTEST_FILES})
list(SORT PYBIND11_PYTEST_FILES)
# Contains the set of test files that require pybind11_cross_module_tests to be
# built; if none of these are built (i.e. because TEST_OVERRIDE is used and
# doesn't include them) the second module doesn't get built.
tests_extra_targets(
"test_class_cross_module_use_after_one_module_dealloc.py;test_exceptions.py;test_local_bindings.py;test_stl.py;test_stl_binders.py"
"pybind11_cross_module_tests")
# And add additional targets for other tests.
tests_extra_targets("test_exceptions.py" "cross_module_interleaved_error_already_set")
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")
set(PYBIND11_EIGEN_REPO
"https://gitlab.com/libeigen/eigen.git"
CACHE STRING "Eigen repository to use for tests")
# Always use a hash for reconfigure speed and security reasons
# Include the version number for pretty printing (keep in sync)
set(PYBIND11_EIGEN_VERSION_AND_HASH
"3.4.0;929bc0e191d0927b1735b9a1ddc0e8b77e3a25ec"
CACHE STRING "Eigen version to use for tests, format: VERSION;HASH")
list(GET PYBIND11_EIGEN_VERSION_AND_HASH 0 PYBIND11_EIGEN_VERSION_STRING)
list(GET PYBIND11_EIGEN_VERSION_AND_HASH 1 PYBIND11_EIGEN_VERSION_HASH)
# Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but
# keep it in PYBIND11_PYTEST_FILES, so that we get the "eigen is not installed"
# skip message).
list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I EQUAL -1)
list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I)
endif()
if(PYBIND11_TEST_FILES_EIGEN_I 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
if(DOWNLOAD_EIGEN)
if(CMAKE_VERSION VERSION_LESS 3.18)
set(_opts)
else()
set(_opts SOURCE_SUBDIR no-cmake-build)
endif()
include(FetchContent)
FetchContent_Declare(
eigen
GIT_REPOSITORY "${PYBIND11_EIGEN_REPO}"
GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}"
${_opts})
FetchContent_MakeAvailable(eigen)
if(NOT CMAKE_VERSION VERSION_LESS 3.18)
set(EIGEN3_INCLUDE_DIR "${eigen_SOURCE_DIR}")
endif()
set(EIGEN3_INCLUDE_DIR ${eigen_SOURCE_DIR})
set(EIGEN3_FOUND TRUE)
# When getting locally, the version is not visible from a superprojet,
# so just force it.
set(EIGEN3_VERSION "${PYBIND11_EIGEN_VERSION_STRING}")
else()
find_package(Eigen3 3.2.7 QUIET CONFIG)
if(NOT EIGEN3_FOUND)
# Couldn't load via target, so fall back to allowing module mode finding, which will pick up
# tools/FindEigen3.cmake
find_package(Eigen3 3.2.7 QUIET)
endif()
endif()
if(EIGEN3_FOUND)
if(NOT TARGET Eigen3::Eigen)
add_library(Eigen3::Eigen IMPORTED INTERFACE)
set_property(TARGET Eigen3::Eigen PROPERTY INTERFACE_INCLUDE_DIRECTORIES
"${EIGEN3_INCLUDE_DIR}")
endif()
# Eigen 3.3.1+ cmake sets EIGEN3_VERSION_STRING (and hard codes the version when installed
# rather than looking it up in the cmake script); older versions, and the
# tools/FindEigen3.cmake, set EIGEN3_VERSION instead.
if(NOT EIGEN3_VERSION AND EIGEN3_VERSION_STRING)
set(EIGEN3_VERSION ${EIGEN3_VERSION_STRING})
endif()
message(STATUS "Building tests with Eigen v${EIGEN3_VERSION}")
if(NOT (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0))
tests_extra_targets("test_eigen_tensor.py" "eigen_tensor_avoid_stl_array")
endif()
else()
list(FIND PYBIND11_TEST_FILES test_eigen_matrix.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
endif()
list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
endif()
message(STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON to download")
endif()
endif()
# Some code doesn't support gcc 4
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
list(FIND PYBIND11_TEST_FILES test_eigen_tensor.cpp PYBIND11_TEST_FILES_EIGEN_I)
if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1)
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
endif()
endif()
# Optional dependency for some tests (boost::variant is only supported with version >= 1.56)
find_package(Boost 1.56)
if(Boost_FOUND)
if(NOT TARGET Boost::headers)
add_library(Boost::headers IMPORTED INTERFACE)
if(TARGET Boost::boost)
# Classic FindBoost
set_property(TARGET Boost::headers PROPERTY INTERFACE_LINK_LIBRARIES Boost::boost)
else()
# Very old FindBoost, or newer Boost than CMake in older CMakes
set_property(TARGET Boost::headers PROPERTY INTERFACE_INCLUDE_DIRECTORIES
${Boost_INCLUDE_DIRS})
endif()
endif()
endif()
# Check if we need to add -lstdc++fs or -lc++fs or nothing
if(DEFINED CMAKE_CXX_STANDARD AND CMAKE_CXX_STANDARD LESS 17)
set(STD_FS_NO_LIB_NEEDED TRUE)
elseif(MSVC)
set(STD_FS_NO_LIB_NEEDED TRUE)
else()
file(
WRITE ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
"#include <filesystem>\nint main(int /*argc*/, char ** argv) {\n std::filesystem::path p(argv[0]);\n return p.string().length();\n}"
)
try_compile(
STD_FS_NO_LIB_NEEDED ${CMAKE_CURRENT_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
COMPILE_DEFINITIONS -std=c++17)
try_compile(
STD_FS_NEEDS_STDCXXFS ${CMAKE_CURRENT_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
COMPILE_DEFINITIONS -std=c++17
LINK_LIBRARIES stdc++fs)
try_compile(
STD_FS_NEEDS_CXXFS ${CMAKE_CURRENT_BINARY_DIR}
SOURCES ${CMAKE_CURRENT_BINARY_DIR}/main.cpp
COMPILE_DEFINITIONS -std=c++17
LINK_LIBRARIES c++fs)
endif()
if(${STD_FS_NEEDS_STDCXXFS})
set(STD_FS_LIB stdc++fs)
elseif(${STD_FS_NEEDS_CXXFS})
set(STD_FS_LIB c++fs)
elseif(${STD_FS_NO_LIB_NEEDED})
set(STD_FS_LIB "")
else()
message(WARNING "Unknown C++17 compiler - not passing -lstdc++fs")
set(STD_FS_LIB "")
endif()
# Compile with compiler warnings turned on
function(pybind11_enable_warnings target_name)
if(MSVC)
target_compile_options(${target_name} PRIVATE /W4 /wd4189)
elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang)" AND NOT PYBIND11_CUDA_TESTS)
target_compile_options(
${target_name}
PRIVATE -Wall
-Wextra
-Wconversion
-Wcast-qual
-Wdeprecated
-Wundef
-Wnon-virtual-dtor)
if(DEFINED CMAKE_CXX_STANDARD AND NOT CMAKE_CXX_STANDARD VERSION_LESS 20)
target_compile_options(${target_name} PRIVATE -Wpedantic)
endif()
endif()
if(PYBIND11_WERROR)
if(CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
if(CMAKE_CXX_STANDARD EQUAL 17) # See PR #3570
target_compile_options(${target_name} PRIVATE -Wno-conversion)
endif()
# "Inlining inhibited by limit max-size", "Inlining inhibited by limit max-total-size"
target_compile_options(${target_name} PRIVATE -diag-disable 11074,11076)
endif()
if(CMAKE_VERSION VERSION_LESS "3.24")
if(MSVC)
target_compile_options(${target_name} PRIVATE /WX)
elseif(PYBIND11_CUDA_TESTS)
target_compile_options(${target_name} PRIVATE "SHELL:-Werror all-warnings")
elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang|IntelLLVM)")
target_compile_options(${target_name} PRIVATE -Werror)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Intel")
target_compile_options(${target_name} PRIVATE -Werror-all)
endif()
else()
set_target_properties(${target_name} PROPERTIES COMPILE_WARNING_AS_ERROR ON)
endif()
endif()
endfunction()
set(test_targets pybind11_tests)
# Check if any tests need extra targets by iterating through the mappings registered.
foreach(i ${PYBIND11_TEST_EXTRA_TARGETS})
foreach(needle ${PYBIND11_TEST_EXTRA_TARGETS_NEEDLES_${i}})
if(needle IN_LIST PYBIND11_PYTEST_FILES)
# Add all the additional targets to the test list. List join in newer cmake.
foreach(extra_target ${PYBIND11_TEST_EXTRA_TARGETS_ADDITION_${i}})
list(APPEND test_targets ${extra_target})
endforeach()
break() # Breaks out of the needle search, continues with the next mapping.
endif()
endforeach()
endforeach()
# Support CUDA testing by forcing the target file to compile with NVCC
if(PYBIND11_CUDA_TESTS)
set_property(SOURCE ${PYBIND11_TEST_FILES} PROPERTY LANGUAGE CUDA)
endif()
foreach(target ${test_targets})
set(test_files ${PYBIND11_TEST_FILES})
if(NOT "${target}" STREQUAL "pybind11_tests")
set(test_files "")
endif()
# Support CUDA testing by forcing the target file to compile with NVCC
if(PYBIND11_CUDA_TESTS)
set_property(SOURCE ${target}.cpp PROPERTY LANGUAGE CUDA)
endif()
# Create the binding library
pybind11_add_module(${target} THIN_LTO ${target}.cpp ${test_files} ${PYBIND11_HEADERS})
pybind11_enable_warnings(${target})
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
get_property(
suffix
TARGET ${target}
PROPERTY SUFFIX)
set(source_output "${CMAKE_CURRENT_SOURCE_DIR}/${target}${suffix}")
if(suffix AND EXISTS "${source_output}")
message(WARNING "Output file also in source directory; "
"please remove to avoid confusion: ${source_output}")
endif()
endif()
if(MSVC)
target_compile_options(${target} PRIVATE /utf-8)
endif()
if(EIGEN3_FOUND)
target_link_libraries(${target} PRIVATE Eigen3::Eigen)
target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_EIGEN)
endif()
if(Boost_FOUND)
target_link_libraries(${target} PRIVATE Boost::headers)
target_compile_definitions(${target} PRIVATE -DPYBIND11_TEST_BOOST)
endif()
if(PYBIND11_TEST_SMART_HOLDER)
target_compile_definitions(
${target}
PUBLIC -DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE
)
endif()
target_link_libraries(${target} PRIVATE ${STD_FS_LIB})
# Always write the output file directly into the 'tests' directory (even on MSVC)
if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_CURRENT_BINARY_DIR}")
if(DEFINED CMAKE_CONFIGURATION_TYPES)
foreach(config ${CMAKE_CONFIGURATION_TYPES})
string(TOUPPER ${config} config)
set_target_properties(${target} PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config}
"${CMAKE_CURRENT_BINARY_DIR}")
endforeach()
endif()
endif()
if(SKBUILD)
install(TARGETS ${target} LIBRARY DESTINATION .)
endif()
if("${target}" STREQUAL "exo_planet_c_api")
if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang|NVHPC)")
target_compile_options(${target} PRIVATE -fno-exceptions)
endif()
endif()
endforeach()
# Provide nice organisation in IDEs
source_group(
TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include"
PREFIX "Header Files"
FILES ${PYBIND11_HEADERS})
# Make sure pytest is found or produce a warning
pybind11_find_import(pytest VERSION 3.1)
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
# This is not used later in the build, so it's okay to regenerate each time.
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/pytest.ini" "${CMAKE_CURRENT_BINARY_DIR}/pytest.ini"
COPYONLY)
file(APPEND "${CMAKE_CURRENT_BINARY_DIR}/pytest.ini"
"\ntestpaths = \"${CMAKE_CURRENT_SOURCE_DIR}\"")
endif()
# Convert relative to full file names
list(TRANSFORM PYBIND11_PYTEST_FILES PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/"
OUTPUT_VARIABLE PYBIND11_ABS_PYTEST_FILES)
set(PYBIND11_TEST_PREFIX_COMMAND
""
CACHE STRING "Put this before pytest, use for checkers and such")
set(PYBIND11_PYTEST_ARGS
""
CACHE STRING "Extra arguments for pytest")
# A single command to compile and run the tests
add_custom_target(
pytest
COMMAND ${PYBIND11_TEST_PREFIX_COMMAND} ${PYTHON_EXECUTABLE} -m pytest
${PYBIND11_ABS_PYTEST_FILES} ${PYBIND11_PYTEST_ARGS}
DEPENDS ${test_targets}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
USES_TERMINAL)
if(NOT PYBIND11_CUDA_TESTS)
set(PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES
mod_per_interpreter_gil mod_shared_interpreter_gil mod_per_interpreter_gil_with_singleton)
# These modules don't get mixed with other test modules because those aren't subinterpreter safe.
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
pybind11_add_module("${mod}" THIN_LTO "${mod}.cpp")
pybind11_enable_warnings("${mod}")
endforeach()
# Put the built modules next to `pybind11_tests.so` so that the test scripts can find them.
get_target_property(pybind11_tests_output_directory pybind11_tests LIBRARY_OUTPUT_DIRECTORY)
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
set_target_properties("${mod}" PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${pybind11_tests_output_directory}")
endforeach()
if(PYBIND11_TEST_SMART_HOLDER)
foreach(mod IN LISTS PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES)
target_compile_definitions(
"${mod}"
PUBLIC
-DPYBIND11_RUN_TESTING_WITH_SMART_HOLDER_AS_DEFAULT_BUT_NEVER_USE_IN_PRODUCTION_PLEASE)
endforeach()
endif()
add_dependencies(pytest ${PYBIND11_MULTIPLE_INTERPRETERS_TEST_MODULES})
endif()
if(PYBIND11_TEST_OVERRIDE)
add_custom_command(
TARGET pytest
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E echo
"Note: not all tests run: -DPYBIND11_TEST_OVERRIDE is in effect")
endif()
# cmake-format: off
add_custom_target(
memcheck
COMMAND
PYTHONMALLOC=malloc
valgrind
--leak-check=full
--show-leak-kinds=definite,indirect
--errors-for-leak-kinds=definite,indirect
--error-exitcode=1
--read-var-info=yes
--track-origins=yes
--suppressions="${CMAKE_CURRENT_SOURCE_DIR}/valgrind-python.supp"
--suppressions="${CMAKE_CURRENT_SOURCE_DIR}/valgrind-numpy-scipy.supp"
--gen-suppressions=all
${PYTHON_EXECUTABLE} -m pytest ${PYBIND11_ABS_PYTEST_FILES}
DEPENDS ${test_targets}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
USES_TERMINAL)
# cmake-format: on
# Add a check target to run all the tests, starting with pytest (we add dependencies to this below)
add_custom_target(check DEPENDS pytest)
# The remaining tests only apply when being built as part of the pybind11 project, but not if the
# tests are being built independently.
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
return()
endif()
# Add a post-build comment to show the primary test suite .so size and, if a previous size, compare it:
add_custom_command(
TARGET pybind11_tests
POST_BUILD
COMMAND
${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/../tools/libsize.py
$<TARGET_FILE:pybind11_tests>
${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)
if(NOT PYBIND11_CUDA_TESTS)
# Test pure C++ code (not depending on Python). Provides the `test_pure_cpp` target.
add_subdirectory(pure_cpp)
# Test C++ code that depends on Python, such as embedding the interpreter. Provides the `cpptest` target.
add_subdirectory(test_with_catch)
# Test CMake build using functions and targets from subdirectory or installed location
add_subdirectory(test_cmake_build)
# Test visibility of common symbols across shared libraries
add_subdirectory(test_cross_module_rtti)
endif()