first commit

This commit is contained in:
jiangjiajun
2022-07-05 09:30:15 +00:00
parent 4df7366d62
commit 9d87046d78
781 changed files with 225888 additions and 6184 deletions

View File

@@ -0,0 +1,557 @@
# 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.4)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.21)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.21)
endif()
# Only needed for CMake < 3.5 support
include(CMakeParseArguments)
# 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_EIGEN "Download EIGEN (requires CMake 3.11+)" OFF)
option(PYBIND11_CUDA_TESTS "Enable building CUDA tests (requires CMake 3.12+)" 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
find_package(pybind11 REQUIRED CONFIG)
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_const_name
test_constants_and_functions
test_copy_move
test_custom_type_casters
test_custom_type_setup
test_docstring_options
test_eigen
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_numpy_array
test_numpy_dtypes
test_numpy_vectorize
test_opaque_types
test_operator_overloading
test_pickling
test_pytypes
test_sequences_and_iterators
test_smart_ptr
test_stl
test_stl_binders
test_tagbased_polymorphic
test_thread
test_union
test_virtual_functions)
# 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 whitelist, 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_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_gil_scoped.py" "cross_module_gil_utils")
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.cpp PYBIND11_TEST_FILES_EIGEN_I)
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
# produces a fatal error if loaded from a pre-3.0 cmake.
if(DOWNLOAD_EIGEN)
if(CMAKE_VERSION VERSION_LESS 3.11)
message(FATAL_ERROR "CMake 3.11+ required when using DOWNLOAD_EIGEN")
endif()
include(FetchContent)
FetchContent_Declare(
eigen
GIT_REPOSITORY "${PYBIND11_EIGEN_REPO}"
GIT_TAG "${PYBIND11_EIGEN_VERSION_HASH}")
FetchContent_GetProperties(eigen)
if(NOT eigen_POPULATED)
message(
STATUS
"Downloading Eigen ${PYBIND11_EIGEN_VERSION_STRING} (${PYBIND11_EIGEN_VERSION_HASH}) from ${PYBIND11_EIGEN_REPO}"
)
FetchContent_Populate(eigen)
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}")
else()
list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_EIGEN_I})
message(
STATUS "Building tests WITHOUT Eigen, use -DDOWNLOAD_EIGEN=ON on CMake 3.11+ to download")
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::boost 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)
endif()
if(PYBIND11_WERROR)
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")
if(CMAKE_CXX_STANDARD EQUAL 17) # See PR #3570
target_compile_options(${target_name} PRIVATE -Wno-conversion)
endif()
target_compile_options(
${target_name}
PRIVATE
-Werror-all
# "Inlining inhibited by limit max-size", "Inlining inhibited by limit max-total-size"
-diag-disable 11074,11076)
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()
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()
endforeach()
# Provide nice organisation in IDEs
if(NOT CMAKE_VERSION VERSION_LESS 3.8)
source_group(
TREE "${CMAKE_CURRENT_SOURCE_DIR}/../include"
PREFIX "Header Files"
FILES ${PYBIND11_HEADERS})
endif()
# 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()
# cmake 3.12 added list(transform <list> prepend
# but we can't use it yet
string(REPLACE "test_" "${CMAKE_CURRENT_SOURCE_DIR}/test_" PYBIND11_ABS_PYTEST_FILES
"${PYBIND11_PYTEST_FILES}")
set(PYBIND11_TEST_PREFIX_COMMAND
""
CACHE STRING "Put this before pytest, use for checkers and such")
# 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}
DEPENDS ${test_targets}
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
USES_TERMINAL)
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 embedding the interpreter. Provides the `cpptest` target.
add_subdirectory(test_embed)
# Test CMake build using functions and targets from subdirectory or installed location
add_subdirectory(test_cmake_build)
endif()

200
third_party/pybind11/tests/conftest.py vendored Normal file
View File

@@ -0,0 +1,200 @@
"""pytest configuration
Extends output capture as needed by pybind11: ignore constructors, optional unordered lines.
Adds docstring and exceptions message sanitizers.
"""
import contextlib
import difflib
import gc
import re
import textwrap
import pytest
# Early diagnostic for failed imports
import pybind11_tests # noqa: F401
_long_marker = re.compile(r"([0-9])L")
_hexadecimal = re.compile(r"0x[0-9a-fA-F]+")
# Avoid collecting Python3 only files
collect_ignore = []
def _strip_and_dedent(s):
"""For triple-quote strings"""
return textwrap.dedent(s.lstrip("\n").rstrip())
def _split_and_sort(s):
"""For output which does not require specific line order"""
return sorted(_strip_and_dedent(s).splitlines())
def _make_explanation(a, b):
"""Explanation for a failed assert -- the a and b arguments are List[str]"""
return ["--- actual / +++ expected"] + [
line.strip("\n") for line in difflib.ndiff(a, b)
]
class Output:
"""Basic output post-processing and comparison"""
def __init__(self, string):
self.string = string
self.explanation = []
def __str__(self):
return self.string
def __eq__(self, other):
# Ignore constructor/destructor output which is prefixed with "###"
a = [
line for line in self.string.strip().splitlines()
if not line.startswith("###")
]
b = _strip_and_dedent(other).splitlines()
if a == b:
return True
else:
self.explanation = _make_explanation(a, b)
return False
class Unordered(Output):
"""Custom comparison for output without strict line ordering"""
def __eq__(self, other):
a = _split_and_sort(self.string)
b = _split_and_sort(other)
if a == b:
return True
else:
self.explanation = _make_explanation(a, b)
return False
class Capture:
def __init__(self, capfd):
self.capfd = capfd
self.out = ""
self.err = ""
def __enter__(self):
self.capfd.readouterr()
return self
def __exit__(self, *args):
self.out, self.err = self.capfd.readouterr()
def __eq__(self, other):
a = Output(self.out)
b = other
if a == b:
return True
else:
self.explanation = a.explanation
return False
def __str__(self):
return self.out
def __contains__(self, item):
return item in self.out
@property
def unordered(self):
return Unordered(self.out)
@property
def stderr(self):
return Output(self.err)
@pytest.fixture
def capture(capsys):
"""Extended `capsys` with context manager and custom equality operators"""
return Capture(capsys)
class SanitizedString:
def __init__(self, sanitizer):
self.sanitizer = sanitizer
self.string = ""
self.explanation = []
def __call__(self, thing):
self.string = self.sanitizer(thing)
return self
def __eq__(self, other):
a = self.string
b = _strip_and_dedent(other)
if a == b:
return True
else:
self.explanation = _make_explanation(a.splitlines(),
b.splitlines())
return False
def _sanitize_general(s):
s = s.strip()
s = s.replace("pybind11_tests.", "m.")
s = _long_marker.sub(r"\1", s)
return s
def _sanitize_docstring(thing):
s = thing.__doc__
s = _sanitize_general(s)
return s
@pytest.fixture
def doc():
"""Sanitize docstrings and add custom failure explanation"""
return SanitizedString(_sanitize_docstring)
def _sanitize_message(thing):
s = str(thing)
s = _sanitize_general(s)
s = _hexadecimal.sub("0", s)
return s
@pytest.fixture
def msg():
"""Sanitize messages and add custom failure explanation"""
return SanitizedString(_sanitize_message)
# noinspection PyUnusedLocal
def pytest_assertrepr_compare(op, left, right):
"""Hook to insert custom failure explanation"""
if hasattr(left, "explanation"):
return left.explanation
@contextlib.contextmanager
def suppress(exception):
"""Suppress the desired exception"""
try:
yield
except exception:
pass
def gc_collect():
"""Run the garbage collector twice (needed when running
reference counting tests with PyPy)"""
gc.collect()
gc.collect()
def pytest_configure():
pytest.suppress = suppress
pytest.gc_collect = gc_collect

View File

@@ -0,0 +1,322 @@
#pragma once
/*
tests/constructor_stats.h -- framework for printing and tracking object
instance lifetimes in example/test code.
Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
This header provides a few useful tools for writing examples or tests that want to check and/or
display object instance lifetimes. It requires that you include this header and add the following
function calls to constructors:
class MyClass {
MyClass() { ...; print_default_created(this); }
~MyClass() { ...; print_destroyed(this); }
MyClass(const MyClass &c) { ...; print_copy_created(this); }
MyClass(MyClass &&c) { ...; print_move_created(this); }
MyClass(int a, int b) { ...; print_created(this, a, b); }
MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); }
MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); }
...
}
You can find various examples of these in several of the existing testing .cpp files. (Of course
you don't need to add any of the above constructors/operators that you don't actually have, except
for the destructor).
Each of these will print an appropriate message such as:
### MyClass @ 0x2801910 created via default constructor
### MyClass @ 0x27fa780 created 100 200
### MyClass @ 0x2801910 destroyed
### MyClass @ 0x27fa780 destroyed
You can also include extra arguments (such as the 100, 200 in the output above, coming from the
value constructor) for all of the above methods which will be included in the output.
For testing, each of these also keeps track the created instances and allows you to check how many
of the various constructors have been invoked from the Python side via code such as:
from pybind11_tests import ConstructorStats
cstats = ConstructorStats.get(MyClass)
print(cstats.alive())
print(cstats.default_constructions)
Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
collector to actually destroy objects that aren't yet referenced.
For everything except copy and move constructors and destructors, any extra values given to the
print_...() function is stored in a class-specific values list which you can retrieve and inspect
from the ConstructorStats instance `.values()` method.
In some cases, when you need to track instances of a C++ class not registered with pybind11, you
need to add a function returning the ConstructorStats for the C++ class; this can be done with:
m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>,
py::return_value_policy::reference)
Finally, you can suppress the output messages, but keep the constructor tracking (for
inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
`track_copy_created(this)`).
*/
#include "pybind11_tests.h"
#include <list>
#include <sstream>
#include <typeindex>
#include <unordered_map>
class ConstructorStats {
protected:
std::unordered_map<void *, int> _instances; // Need a map rather than set because members can
// shared address with parents
std::list<std::string> _values; // Used to track values
// (e.g. of value constructors)
public:
int default_constructions = 0;
int copy_constructions = 0;
int move_constructions = 0;
int copy_assignments = 0;
int move_assignments = 0;
void copy_created(void *inst) {
created(inst);
copy_constructions++;
}
void move_created(void *inst) {
created(inst);
move_constructions++;
}
void default_created(void *inst) {
created(inst);
default_constructions++;
}
void created(void *inst) { ++_instances[inst]; }
void destroyed(void *inst) {
if (--_instances[inst] < 0) {
throw std::runtime_error("cstats.destroyed() called with unknown "
"instance; potential double-destruction "
"or a missing cstats.created()");
}
}
static void gc() {
// Force garbage collection to ensure any pending destructors are invoked:
#if defined(PYPY_VERSION)
PyObject *globals = PyEval_GetGlobals();
PyObject *result = PyRun_String("import gc\n"
"for i in range(2):"
" gc.collect()\n",
Py_file_input,
globals,
globals);
if (result == nullptr)
throw py::error_already_set();
Py_DECREF(result);
#else
py::module_::import("gc").attr("collect")();
#endif
}
int alive() {
gc();
int total = 0;
for (const auto &p : _instances) {
if (p.second > 0) {
total += p.second;
}
}
return total;
}
void value() {} // Recursion terminator
// Takes one or more values, converts them to strings, then stores them.
template <typename T, typename... Tmore>
void value(const T &v, Tmore &&...args) {
std::ostringstream oss;
oss << v;
_values.push_back(oss.str());
value(std::forward<Tmore>(args)...);
}
// Move out stored values
py::list values() {
py::list l;
for (const auto &v : _values) {
l.append(py::cast(v));
}
_values.clear();
return l;
}
// Gets constructor stats from a C++ type index
static ConstructorStats &get(std::type_index type) {
static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
return all_cstats[type];
}
// Gets constructor stats from a C++ type
template <typename T>
static ConstructorStats &get() {
#if defined(PYPY_VERSION)
gc();
#endif
return get(typeid(T));
}
// Gets constructor stats from a Python class
static ConstructorStats &get(py::object class_) {
auto &internals = py::detail::get_internals();
const std::type_index *t1 = nullptr, *t2 = nullptr;
try {
auto *type_info
= internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
for (auto &p : internals.registered_types_cpp) {
if (p.second == type_info) {
if (t1) {
t2 = &p.first;
break;
}
t1 = &p.first;
}
}
} catch (const std::out_of_range &) {
}
if (!t1) {
throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
}
auto &cs1 = get(*t1);
// If we have both a t1 and t2 match, one is probably the trampoline class; return
// whichever has more constructions (typically one or the other will be 0)
if (t2) {
auto &cs2 = get(*t2);
int cs1_total = cs1.default_constructions + cs1.copy_constructions
+ cs1.move_constructions + (int) cs1._values.size();
int cs2_total = cs2.default_constructions + cs2.copy_constructions
+ cs2.move_constructions + (int) cs2._values.size();
if (cs2_total > cs1_total) {
return cs2;
}
}
return cs1;
}
};
// To track construction/destruction, you need to call these methods from the various
// constructors/operators. The ones that take extra values record the given values in the
// constructor stats values for later inspection.
template <class T>
void track_copy_created(T *inst) {
ConstructorStats::get<T>().copy_created(inst);
}
template <class T>
void track_move_created(T *inst) {
ConstructorStats::get<T>().move_created(inst);
}
template <class T, typename... Values>
void track_copy_assigned(T *, Values &&...values) {
auto &cst = ConstructorStats::get<T>();
cst.copy_assignments++;
cst.value(std::forward<Values>(values)...);
}
template <class T, typename... Values>
void track_move_assigned(T *, Values &&...values) {
auto &cst = ConstructorStats::get<T>();
cst.move_assignments++;
cst.value(std::forward<Values>(values)...);
}
template <class T, typename... Values>
void track_default_created(T *inst, Values &&...values) {
auto &cst = ConstructorStats::get<T>();
cst.default_created(inst);
cst.value(std::forward<Values>(values)...);
}
template <class T, typename... Values>
void track_created(T *inst, Values &&...values) {
auto &cst = ConstructorStats::get<T>();
cst.created(inst);
cst.value(std::forward<Values>(values)...);
}
template <class T, typename... Values>
void track_destroyed(T *inst) {
ConstructorStats::get<T>().destroyed(inst);
}
template <class T, typename... Values>
void track_values(T *, Values &&...values) {
ConstructorStats::get<T>().value(std::forward<Values>(values)...);
}
/// Don't cast pointers to Python, print them as strings
inline const char *format_ptrs(const char *p) { return p; }
template <typename T>
py::str format_ptrs(T *p) {
return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p));
}
template <typename T>
auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) {
return std::forward<T>(x);
}
template <class T, typename... Output>
void print_constr_details(T *inst, const std::string &action, Output &&...output) {
py::print("###",
py::type_id<T>(),
"@",
format_ptrs(inst),
action,
format_ptrs(std::forward<Output>(output))...);
}
// Verbose versions of the above:
template <class T, typename... Values>
void print_copy_created(T *inst,
Values &&...values) { // NB: this prints, but doesn't store, given values
print_constr_details(inst, "created via copy constructor", values...);
track_copy_created(inst);
}
template <class T, typename... Values>
void print_move_created(T *inst,
Values &&...values) { // NB: this prints, but doesn't store, given values
print_constr_details(inst, "created via move constructor", values...);
track_move_created(inst);
}
template <class T, typename... Values>
void print_copy_assigned(T *inst, Values &&...values) {
print_constr_details(inst, "assigned via copy assignment", values...);
track_copy_assigned(inst, values...);
}
template <class T, typename... Values>
void print_move_assigned(T *inst, Values &&...values) {
print_constr_details(inst, "assigned via move assignment", values...);
track_move_assigned(inst, values...);
}
template <class T, typename... Values>
void print_default_created(T *inst, Values &&...values) {
print_constr_details(inst, "created via default constructor", values...);
track_default_created(inst, values...);
}
template <class T, typename... Values>
void print_created(T *inst, Values &&...values) {
print_constr_details(inst, "created", values...);
track_created(inst, values...);
}
template <class T, typename... Values>
void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
print_constr_details(inst, "destroyed", values...);
track_destroyed(inst);
}
template <class T, typename... Values>
void print_values(T *inst, Values &&...values) {
print_constr_details(inst, ":", values...);
track_values(inst, values...);
}

View File

@@ -0,0 +1,45 @@
/*
tests/cross_module_gil_utils.cpp -- tools for acquiring GIL from a different module
Copyright (c) 2019 Google LLC
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/pybind11.h>
#include <cstdint>
// This file mimics a DSO that makes pybind11 calls but does not define a
// PYBIND11_MODULE. The purpose is to test that such a DSO can create a
// py::gil_scoped_acquire when the running thread is in a GIL-released state.
//
// Note that we define a Python module here for convenience, but in general
// this need not be the case. The typical scenario would be a DSO that implements
// shared logic used internally by multiple pybind11 modules.
namespace {
namespace py = pybind11;
void gil_acquire() { py::gil_scoped_acquire gil; }
constexpr char kModuleName[] = "cross_module_gil_utils";
struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT, kModuleName, nullptr, 0, nullptr, nullptr, nullptr, nullptr, nullptr};
} // namespace
extern "C" PYBIND11_EXPORT PyObject *PyInit_cross_module_gil_utils() {
PyObject *m = PyModule_Create(&moduledef);
if (m != nullptr) {
static_assert(sizeof(&gil_acquire) == sizeof(void *),
"Function pointer must have the same size as void*");
PyModule_AddObject(
m, "gil_acquire_funcaddr", PyLong_FromVoidPtr(reinterpret_cast<void *>(&gil_acquire)));
}
return m;
}

28
third_party/pybind11/tests/env.py vendored Normal file
View File

@@ -0,0 +1,28 @@
import platform
import sys
import pytest
LINUX = sys.platform.startswith("linux")
MACOS = sys.platform.startswith("darwin")
WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
CPYTHON = platform.python_implementation() == "CPython"
PYPY = platform.python_implementation() == "PyPy"
def deprecated_call():
"""
pytest.deprecated_call() seems broken in pytest<3.9.x; concretely, it
doesn't work on CPython 3.8.0 with pytest==3.3.2 on Ubuntu 18.04 (#2922).
This is a narrowed reimplementation of the following PR :(
https://github.com/pytest-dev/pytest/pull/4104
"""
# TODO: Remove this when testing requires pytest>=3.9.
pieces = pytest.__version__.split(".")
pytest_major_minor = (int(pieces[0]), int(pieces[1]))
if pytest_major_minor < (3, 9):
return pytest.warns((DeprecationWarning, PendingDeprecationWarning))
else:
return pytest.deprecated_call()

View File

@@ -0,0 +1,257 @@
import contextlib
import os
import string
import subprocess
import sys
import tarfile
import zipfile
# These tests must be run explicitly
# They require CMake 3.15+ (--install)
DIR = os.path.abspath(os.path.dirname(__file__))
MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
main_headers = {
"include/pybind11/attr.h",
"include/pybind11/buffer_info.h",
"include/pybind11/cast.h",
"include/pybind11/chrono.h",
"include/pybind11/common.h",
"include/pybind11/complex.h",
"include/pybind11/eigen.h",
"include/pybind11/embed.h",
"include/pybind11/eval.h",
"include/pybind11/functional.h",
"include/pybind11/gil.h",
"include/pybind11/iostream.h",
"include/pybind11/numpy.h",
"include/pybind11/operators.h",
"include/pybind11/options.h",
"include/pybind11/pybind11.h",
"include/pybind11/pytypes.h",
"include/pybind11/stl.h",
"include/pybind11/stl_bind.h",
}
detail_headers = {
"include/pybind11/detail/class.h",
"include/pybind11/detail/common.h",
"include/pybind11/detail/descr.h",
"include/pybind11/detail/init.h",
"include/pybind11/detail/internals.h",
"include/pybind11/detail/type_caster_base.h",
"include/pybind11/detail/typeid.h",
}
stl_headers = {"include/pybind11/stl/filesystem.h", }
cmake_files = {
"share/cmake/pybind11/FindPythonLibsNew.cmake",
"share/cmake/pybind11/pybind11Common.cmake",
"share/cmake/pybind11/pybind11Config.cmake",
"share/cmake/pybind11/pybind11ConfigVersion.cmake",
"share/cmake/pybind11/pybind11NewTools.cmake",
"share/cmake/pybind11/pybind11Targets.cmake",
"share/cmake/pybind11/pybind11Tools.cmake",
}
py_files = {
"__init__.py",
"__main__.py",
"_version.py",
"commands.py",
"py.typed",
"setup_helpers.py",
}
headers = main_headers | detail_headers | stl_headers
src_files = headers | cmake_files
all_files = src_files | py_files
sdist_files = {
"pybind11",
"pybind11/include",
"pybind11/include/pybind11",
"pybind11/include/pybind11/detail",
"pybind11/include/pybind11/stl",
"pybind11/share",
"pybind11/share/cmake",
"pybind11/share/cmake/pybind11",
"pyproject.toml",
"setup.cfg",
"setup.py",
"LICENSE",
"MANIFEST.in",
"README.rst",
"PKG-INFO",
}
local_sdist_files = {
".egg-info",
".egg-info/PKG-INFO",
".egg-info/SOURCES.txt",
".egg-info/dependency_links.txt",
".egg-info/not-zip-safe",
".egg-info/top_level.txt",
}
def test_build_sdist(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR)
out = subprocess.check_output([
sys.executable,
"-m",
"build",
"--sdist",
"--outdir",
str(tmpdir),
])
if hasattr(out, "decode"):
out = out.decode()
(sdist, ) = tmpdir.visit("*.tar.gz")
with tarfile.open(str(sdist), "r:gz") as tar:
start = tar.getnames()[0] + "/"
version = start[9:-1]
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
with contextlib.closing(
tar.extractfile(tar.getmember(start + "setup.py"))) as f:
setup_py = f.read()
with contextlib.closing(
tar.extractfile(tar.getmember(start + "pyproject.toml"))) as f:
pyproject_toml = f.read()
with contextlib.closing(
tar.extractfile(
tar.getmember(
start +
"pybind11/share/cmake/pybind11/pybind11Config.cmake"))
) as f:
contents = f.read().decode("utf8")
assert 'set(pybind11_INCLUDE_DIR "${PACKAGE_PREFIX_DIR}/include")' in contents
files = {f"pybind11/{n}" for n in all_files}
files |= sdist_files
files |= {f"pybind11{n}" for n in local_sdist_files}
files.add("pybind11.egg-info/entry_points.txt")
files.add("pybind11.egg-info/requires.txt")
assert simpler == files
with open(os.path.join(MAIN_DIR, "tools", "setup_main.py.in"), "rb") as f:
contents = (string.Template(f.read().decode()).substitute(
version=version, extra_cmd="").encode())
assert setup_py == contents
with open(os.path.join(MAIN_DIR, "tools", "pyproject.toml"), "rb") as f:
contents = f.read()
assert pyproject_toml == contents
def test_build_global_dist(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR)
monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1")
out = subprocess.check_output([
sys.executable,
"-m",
"build",
"--sdist",
"--outdir",
str(tmpdir),
])
if hasattr(out, "decode"):
out = out.decode()
(sdist, ) = tmpdir.visit("*.tar.gz")
with tarfile.open(str(sdist), "r:gz") as tar:
start = tar.getnames()[0] + "/"
version = start[16:-1]
simpler = {n.split("/", 1)[-1] for n in tar.getnames()[1:]}
with contextlib.closing(
tar.extractfile(tar.getmember(start + "setup.py"))) as f:
setup_py = f.read()
with contextlib.closing(
tar.extractfile(tar.getmember(start + "pyproject.toml"))) as f:
pyproject_toml = f.read()
files = {f"pybind11/{n}" for n in all_files}
files |= sdist_files
files |= {f"pybind11_global{n}" for n in local_sdist_files}
assert simpler == files
with open(os.path.join(MAIN_DIR, "tools", "setup_global.py.in"),
"rb") as f:
contents = (string.Template(f.read().decode()).substitute(
version=version, extra_cmd="").encode())
assert setup_py == contents
with open(os.path.join(MAIN_DIR, "tools", "pyproject.toml"), "rb") as f:
contents = f.read()
assert pyproject_toml == contents
def tests_build_wheel(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR)
subprocess.check_output(
[sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)])
(wheel, ) = tmpdir.visit("*.whl")
files = {f"pybind11/{n}" for n in all_files}
files |= {
"dist-info/LICENSE",
"dist-info/METADATA",
"dist-info/RECORD",
"dist-info/WHEEL",
"dist-info/entry_points.txt",
"dist-info/top_level.txt",
}
with zipfile.ZipFile(str(wheel)) as z:
names = z.namelist()
trimmed = {n for n in names if "dist-info" not in n}
trimmed |= {
f"dist-info/{n.split('/', 1)[-1]}"
for n in names if "dist-info" in n
}
assert files == trimmed
def tests_build_global_wheel(monkeypatch, tmpdir):
monkeypatch.chdir(MAIN_DIR)
monkeypatch.setenv("PYBIND11_GLOBAL_SDIST", "1")
subprocess.check_output(
[sys.executable, "-m", "pip", "wheel", ".", "-w", str(tmpdir)])
(wheel, ) = tmpdir.visit("*.whl")
files = {f"data/data/{n}" for n in src_files}
files |= {f"data/headers/{n[8:]}" for n in headers}
files |= {
"dist-info/LICENSE",
"dist-info/METADATA",
"dist-info/WHEEL",
"dist-info/top_level.txt",
"dist-info/RECORD",
}
with zipfile.ZipFile(str(wheel)) as z:
names = z.namelist()
beginning = names[0].split("/", 1)[0].rsplit(".", 1)[0]
trimmed = {n[len(beginning) + 1:] for n in names}
assert files == trimmed

View File

View File

@@ -0,0 +1,142 @@
import os
import subprocess
import sys
from textwrap import dedent
import pytest
DIR = os.path.abspath(os.path.dirname(__file__))
MAIN_DIR = os.path.dirname(os.path.dirname(DIR))
WIN = sys.platform.startswith("win32") or sys.platform.startswith("cygwin")
@pytest.mark.parametrize("parallel", [False, True])
@pytest.mark.parametrize("std", [11, 0])
def test_simple_setup_py(monkeypatch, tmpdir, parallel, std):
monkeypatch.chdir(tmpdir)
monkeypatch.syspath_prepend(MAIN_DIR)
(tmpdir / "setup.py").write_text(
dedent(f"""\
import sys
sys.path.append({MAIN_DIR!r})
from setuptools import setup, Extension
from pybind11.setup_helpers import build_ext, Pybind11Extension
std = {std}
ext_modules = [
Pybind11Extension(
"simple_setup",
sorted(["main.cpp"]),
cxx_std=std,
),
]
cmdclass = dict()
if std == 0:
cmdclass["build_ext"] = build_ext
parallel = {parallel}
if parallel:
from pybind11.setup_helpers import ParallelCompile
ParallelCompile().install()
setup(
name="simple_setup_package",
cmdclass=cmdclass,
ext_modules=ext_modules,
)
"""),
encoding="ascii", )
(tmpdir / "main.cpp").write_text(
dedent("""\
#include <pybind11/pybind11.h>
int f(int x) {
return x * 3;
}
PYBIND11_MODULE(simple_setup, m) {
m.def("f", &f);
}
"""),
encoding="ascii", )
out = subprocess.check_output(
[sys.executable, "setup.py", "build_ext", "--inplace"], )
if not WIN:
assert b"-g0" in out
out = subprocess.check_output(
[sys.executable, "setup.py", "build_ext", "--inplace", "--force"],
env=dict(
os.environ, CFLAGS="-g"), )
if not WIN:
assert b"-g0" not in out
# Debug helper printout, normally hidden
print(out)
for item in tmpdir.listdir():
print(item.basename)
assert (len([
f for f in tmpdir.listdir() if f.basename.startswith("simple_setup")
]) == 1)
assert len(list(tmpdir.listdir())) == 4 # two files + output + build_dir
(tmpdir / "test.py").write_text(
dedent("""\
import simple_setup
assert simple_setup.f(3) == 9
"""),
encoding="ascii", )
subprocess.check_call(
[sys.executable, "test.py"], stdout=sys.stdout, stderr=sys.stderr)
def test_intree_extensions(monkeypatch, tmpdir):
monkeypatch.syspath_prepend(MAIN_DIR)
from pybind11.setup_helpers import intree_extensions
monkeypatch.chdir(tmpdir)
root = tmpdir
root.ensure_dir()
subdir = root / "dir"
subdir.ensure_dir()
src = subdir / "ext.cpp"
src.ensure()
relpath = src.relto(tmpdir)
(ext, ) = intree_extensions([relpath])
assert ext.name == "ext"
subdir.ensure("__init__.py")
(ext, ) = intree_extensions([relpath])
assert ext.name == "dir.ext"
def test_intree_extensions_package_dir(monkeypatch, tmpdir):
monkeypatch.syspath_prepend(MAIN_DIR)
from pybind11.setup_helpers import intree_extensions
monkeypatch.chdir(tmpdir)
root = tmpdir / "src"
root.ensure_dir()
subdir = root / "dir"
subdir.ensure_dir()
src = subdir / "ext.cpp"
src.ensure()
(ext, ) = intree_extensions([src.relto(tmpdir)], package_dir={"": "src"})
assert ext.name == "dir.ext"
(ext, ) = intree_extensions(
[src.relto(tmpdir)], package_dir={"foo": "src"})
assert ext.name == "foo.dir.ext"
subdir.ensure("__init__.py")
(ext, ) = intree_extensions([src.relto(tmpdir)], package_dir={"": "src"})
assert ext.name == "dir.ext"
(ext, ) = intree_extensions(
[src.relto(tmpdir)], package_dir={"foo": "src"})
assert ext.name == "foo.dir.ext"

View File

@@ -0,0 +1,92 @@
#pragma once
#include "pybind11_tests.h"
#include <utility>
/// Simple class used to test py::local:
template <int>
class LocalBase {
public:
explicit LocalBase(int i) : i(i) {}
int i = -1;
};
/// Registered with py::module_local in both main and secondary modules:
using LocalType = LocalBase<0>;
/// Registered without py::module_local in both modules:
using NonLocalType = LocalBase<1>;
/// A second non-local type (for stl_bind tests):
using NonLocal2 = LocalBase<2>;
/// Tests within-module, different-compilation-unit local definition conflict:
using LocalExternal = LocalBase<3>;
/// Mixed: registered local first, then global
using MixedLocalGlobal = LocalBase<4>;
/// Mixed: global first, then local
using MixedGlobalLocal = LocalBase<5>;
/// Registered with py::module_local only in the secondary module:
using ExternalType1 = LocalBase<6>;
using ExternalType2 = LocalBase<7>;
using LocalVec = std::vector<LocalType>;
using LocalVec2 = std::vector<NonLocal2>;
using LocalMap = std::unordered_map<std::string, LocalType>;
using NonLocalVec = std::vector<NonLocalType>;
using NonLocalVec2 = std::vector<NonLocal2>;
using NonLocalMap = std::unordered_map<std::string, NonLocalType>;
using NonLocalMap2 = std::unordered_map<std::string, uint8_t>;
// Exception that will be caught via the module local translator.
class LocalException : public std::exception {
public:
explicit LocalException(const char *m) : message{m} {}
const char *what() const noexcept override { return message.c_str(); }
private:
std::string message = "";
};
// Exception that will be registered with register_local_exception_translator
class LocalSimpleException : public std::exception {
public:
explicit LocalSimpleException(const char *m) : message{m} {}
const char *what() const noexcept override { return message.c_str(); }
private:
std::string message = "";
};
PYBIND11_MAKE_OPAQUE(LocalVec);
PYBIND11_MAKE_OPAQUE(LocalVec2);
PYBIND11_MAKE_OPAQUE(LocalMap);
PYBIND11_MAKE_OPAQUE(NonLocalVec);
// PYBIND11_MAKE_OPAQUE(NonLocalVec2); // same type as LocalVec2
PYBIND11_MAKE_OPAQUE(NonLocalMap);
PYBIND11_MAKE_OPAQUE(NonLocalMap2);
// Simple bindings (used with the above):
template <typename T, int Adjust = 0, typename... Args>
py::class_<T> bind_local(Args &&...args) {
return py::class_<T>(std::forward<Args>(args)...).def(py::init<int>()).def("get", [](T &i) {
return i.i + Adjust;
});
};
// Simulate a foreign library base class (to match the example in the docs):
namespace pets {
class Pet {
public:
explicit Pet(std::string name) : name_(std::move(name)) {}
std::string name_;
const std::string &name() const { return name_; }
};
} // namespace pets
struct MixGL {
int i;
explicit MixGL(int i) : i{i} {}
};
struct MixGL2 {
int i;
explicit MixGL2(int i) : i{i} {}
};

205
third_party/pybind11/tests/object.h vendored Normal file
View File

@@ -0,0 +1,205 @@
#if !defined(__OBJECT_H)
# define __OBJECT_H
# include "constructor_stats.h"
# include <atomic>
/// Reference counted object base class
class Object {
public:
/// Default constructor
Object() { print_default_created(this); }
/// Copy constructor
Object(const Object &) : m_refCount(0) { print_copy_created(this); }
/// Return the current reference count
int getRefCount() const { return m_refCount; };
/// Increase the object's reference count by one
void incRef() const { ++m_refCount; }
/** \brief Decrease the reference count of
* the object and possibly deallocate it.
*
* The object will automatically be deallocated once
* the reference count reaches zero.
*/
void decRef(bool dealloc = true) const {
--m_refCount;
if (m_refCount == 0 && dealloc) {
delete this;
} else if (m_refCount < 0) {
throw std::runtime_error("Internal error: reference count < 0!");
}
}
virtual std::string toString() const = 0;
protected:
/** \brief Virtual protected deconstructor.
* (Will only be called by \ref ref)
*/
virtual ~Object() { print_destroyed(this); }
private:
mutable std::atomic<int> m_refCount{0};
};
// Tag class used to track constructions of ref objects. When we track constructors, below, we
// track and print out the actual class (e.g. ref<MyObject>), and *also* add a fake tracker for
// ref_tag. This lets us check that the total number of ref<Anything> constructors/destructors is
// correct without having to check each individual ref<Whatever> type individually.
class ref_tag {};
/**
* \brief Reference counting helper
*
* The \a ref refeference template is a simple wrapper to store a
* pointer to an object. It takes care of increasing and decreasing
* the reference count of the object. When the last reference goes
* out of scope, the associated object will be deallocated.
*
* \ingroup libcore
*/
template <typename T>
class ref {
public:
/// Create a nullptr reference
ref() : m_ptr(nullptr) {
print_default_created(this);
track_default_created((ref_tag *) this);
}
/// Construct a reference from a pointer
explicit ref(T *ptr) : m_ptr(ptr) {
if (m_ptr) {
((Object *) m_ptr)->incRef();
}
print_created(this, "from pointer", m_ptr);
track_created((ref_tag *) this, "from pointer");
}
/// Copy constructor
ref(const ref &r) : m_ptr(r.m_ptr) {
if (m_ptr) {
((Object *) m_ptr)->incRef();
}
print_copy_created(this, "with pointer", m_ptr);
track_copy_created((ref_tag *) this);
}
/// Move constructor
ref(ref &&r) noexcept : m_ptr(r.m_ptr) {
r.m_ptr = nullptr;
print_move_created(this, "with pointer", m_ptr);
track_move_created((ref_tag *) this);
}
/// Destroy this reference
~ref() {
if (m_ptr) {
((Object *) m_ptr)->decRef();
}
print_destroyed(this);
track_destroyed((ref_tag *) this);
}
/// Move another reference into the current one
ref &operator=(ref &&r) noexcept {
print_move_assigned(this, "pointer", r.m_ptr);
track_move_assigned((ref_tag *) this);
if (*this == r) {
return *this;
}
if (m_ptr) {
((Object *) m_ptr)->decRef();
}
m_ptr = r.m_ptr;
r.m_ptr = nullptr;
return *this;
}
/// Overwrite this reference with another reference
ref &operator=(const ref &r) {
if (this == &r) {
return *this;
}
print_copy_assigned(this, "pointer", r.m_ptr);
track_copy_assigned((ref_tag *) this);
if (m_ptr == r.m_ptr) {
return *this;
}
if (m_ptr) {
((Object *) m_ptr)->decRef();
}
m_ptr = r.m_ptr;
if (m_ptr) {
((Object *) m_ptr)->incRef();
}
return *this;
}
/// Overwrite this reference with a pointer to another object
ref &operator=(T *ptr) {
print_values(this, "assigned pointer");
track_values((ref_tag *) this, "assigned pointer");
if (m_ptr == ptr) {
return *this;
}
if (m_ptr) {
((Object *) m_ptr)->decRef();
}
m_ptr = ptr;
if (m_ptr) {
((Object *) m_ptr)->incRef();
}
return *this;
}
/// Compare this reference with another reference
bool operator==(const ref &r) const { return m_ptr == r.m_ptr; }
/// Compare this reference with another reference
bool operator!=(const ref &r) const { return m_ptr != r.m_ptr; }
/// Compare this reference with a pointer
bool operator==(const T *ptr) const { return m_ptr == ptr; }
/// Compare this reference with a pointer
bool operator!=(const T *ptr) const { return m_ptr != ptr; }
/// Access the object referenced by this reference
T *operator->() { return m_ptr; }
/// Access the object referenced by this reference
const T *operator->() const { return m_ptr; }
/// Return a C++ reference to the referenced object
T &operator*() { return *m_ptr; }
/// Return a const C++ reference to the referenced object
const T &operator*() const { return *m_ptr; }
/// Return a pointer to the referenced object
explicit operator T *() { return m_ptr; }
/// Return a const pointer to the referenced object
T *get_ptr() { return m_ptr; }
/// Return a pointer to the referenced object
const T *get_ptr() const { return m_ptr; }
private:
T *m_ptr;
};
#endif /* __OBJECT_H */

View File

@@ -0,0 +1,149 @@
/*
tests/pybind11_cross_module_tests.cpp -- contains tests that require multiple modules
Copyright (c) 2017 Jason Rhinelander <jason@imaginary.ca>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/stl_bind.h>
#include "local_bindings.h"
#include "pybind11_tests.h"
#include "test_exceptions.h"
#include <numeric>
#include <utility>
PYBIND11_MODULE(pybind11_cross_module_tests, m) {
m.doc() = "pybind11 cross-module test module";
// test_local_bindings.py tests:
//
// Definitions here are tested by importing both this module and the
// relevant pybind11_tests submodule from a test_whatever.py
// test_load_external
bind_local<ExternalType1>(m, "ExternalType1", py::module_local());
bind_local<ExternalType2>(m, "ExternalType2", py::module_local());
// test_exceptions.py
py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException");
m.def("raise_runtime_error", []() {
PyErr_SetString(PyExc_RuntimeError, "My runtime error");
throw py::error_already_set();
});
m.def("raise_value_error", []() {
PyErr_SetString(PyExc_ValueError, "My value error");
throw py::error_already_set();
});
m.def("throw_pybind_value_error", []() { throw py::value_error("pybind11 value error"); });
m.def("throw_pybind_type_error", []() { throw py::type_error("pybind11 type error"); });
m.def("throw_stop_iteration", []() { throw py::stop_iteration(); });
m.def("throw_local_error", []() { throw LocalException("just local"); });
m.def("throw_local_simple_error", []() { throw LocalSimpleException("external mod"); });
py::register_exception_translator([](std::exception_ptr p) {
try {
if (p) {
std::rethrow_exception(p);
}
} catch (const shared_exception &e) {
PyErr_SetString(PyExc_KeyError, e.what());
}
});
// translate the local exception into a key error but only in this module
py::register_local_exception_translator([](std::exception_ptr p) {
try {
if (p) {
std::rethrow_exception(p);
}
} catch (const LocalException &e) {
PyErr_SetString(PyExc_KeyError, e.what());
}
});
// test_local_bindings.py
// Local to both:
bind_local<LocalType, 1>(m, "LocalType", py::module_local()).def("get2", [](LocalType &t) {
return t.i + 2;
});
// Can only be called with our python type:
m.def("local_value", [](LocalType &l) { return l.i; });
// test_nonlocal_failure
// This registration will fail (global registration when LocalFail is already registered
// globally in the main test module):
m.def("register_nonlocal", [m]() { bind_local<NonLocalType, 0>(m, "NonLocalType"); });
// test_stl_bind_local
// stl_bind.h binders defaults to py::module_local if the types are local or converting:
py::bind_vector<LocalVec>(m, "LocalVec");
py::bind_map<LocalMap>(m, "LocalMap");
// test_stl_bind_global
// and global if the type (or one of the types, for the map) is global (so these will fail,
// assuming pybind11_tests is already loaded):
m.def("register_nonlocal_vec", [m]() { py::bind_vector<NonLocalVec>(m, "NonLocalVec"); });
m.def("register_nonlocal_map", [m]() { py::bind_map<NonLocalMap>(m, "NonLocalMap"); });
// The default can, however, be overridden to global using `py::module_local()` or
// `py::module_local(false)`.
// Explicitly made local:
py::bind_vector<NonLocalVec2>(m, "NonLocalVec2", py::module_local());
// Explicitly made global (and so will fail to bind):
m.def("register_nonlocal_map2",
[m]() { py::bind_map<NonLocalMap2>(m, "NonLocalMap2", py::module_local(false)); });
// test_mixed_local_global
// We try this both with the global type registered first and vice versa (the order shouldn't
// matter).
m.def("register_mixed_global_local",
[m]() { bind_local<MixedGlobalLocal, 200>(m, "MixedGlobalLocal", py::module_local()); });
m.def("register_mixed_local_global", [m]() {
bind_local<MixedLocalGlobal, 2000>(m, "MixedLocalGlobal", py::module_local(false));
});
m.def("get_mixed_gl", [](int i) { return MixedGlobalLocal(i); });
m.def("get_mixed_lg", [](int i) { return MixedLocalGlobal(i); });
// test_internal_locals_differ
m.def("local_cpp_types_addr",
[]() { return (uintptr_t) &py::detail::get_local_internals().registered_types_cpp; });
// test_stl_caster_vs_stl_bind
py::bind_vector<std::vector<int>>(m, "VectorInt");
m.def("load_vector_via_binding",
[](std::vector<int> &v) { return std::accumulate(v.begin(), v.end(), 0); });
// test_cross_module_calls
m.def("return_self", [](LocalVec *v) { return v; });
m.def("return_copy", [](const LocalVec &v) { return LocalVec(v); });
class Dog : public pets::Pet {
public:
explicit Dog(std::string name) : Pet(std::move(name)) {}
};
py::class_<pets::Pet>(m, "Pet", py::module_local()).def("name", &pets::Pet::name);
// Binding for local extending class:
py::class_<Dog, pets::Pet>(m, "Dog").def(py::init<std::string>());
m.def("pet_name", [](pets::Pet &p) { return p.name(); });
py::class_<MixGL>(m, "MixGL", py::module_local()).def(py::init<int>());
m.def("get_gl_value", [](MixGL &o) { return o.i + 100; });
py::class_<MixGL2>(m, "MixGL2", py::module_local()).def(py::init<int>());
// test_vector_bool
// We can't test both stl.h and stl_bind.h conversions of `std::vector<bool>` within
// the same module (it would be an ODR violation). Therefore `bind_vector` of `bool`
// is defined here and tested in `test_stl_binders.py`.
py::bind_vector<std::vector<bool>>(m, "VectorBool");
// test_missing_header_message
// The main module already includes stl.h, but we need to test the error message
// which appears when this header is missing.
m.def("missing_header_arg", [](const std::vector<float> &) {});
m.def("missing_header_return", []() { return std::vector<float>(); });
}

View File

@@ -0,0 +1,92 @@
/*
tests/pybind11_tests.cpp -- pybind example plugin
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
#include "constructor_stats.h"
#include <functional>
#include <list>
/*
For testing purposes, we define a static global variable here in a function that each individual
test .cpp calls with its initialization lambda. It's convenient here because we can just not
compile some test files to disable/ignore some of the test code.
It is NOT recommended as a way to use pybind11 in practice, however: the initialization order will
be essentially random, which is okay for our test scripts (there are no dependencies between the
individual pybind11 test .cpp files), but most likely not what you want when using pybind11
productively.
Instead, see the "How can I reduce the build time?" question in the "Frequently asked questions"
section of the documentation for good practice on splitting binding code over multiple files.
*/
std::list<std::function<void(py::module_ &)>> &initializers() {
static std::list<std::function<void(py::module_ &)>> inits;
return inits;
}
test_initializer::test_initializer(Initializer init) { initializers().emplace_back(init); }
test_initializer::test_initializer(const char *submodule_name, Initializer init) {
initializers().emplace_back([=](py::module_ &parent) {
auto m = parent.def_submodule(submodule_name);
init(m);
});
}
void bind_ConstructorStats(py::module_ &m) {
py::class_<ConstructorStats>(m, "ConstructorStats")
.def("alive", &ConstructorStats::alive)
.def("values", &ConstructorStats::values)
.def_readwrite("default_constructions", &ConstructorStats::default_constructions)
.def_readwrite("copy_assignments", &ConstructorStats::copy_assignments)
.def_readwrite("move_assignments", &ConstructorStats::move_assignments)
.def_readwrite("copy_constructions", &ConstructorStats::copy_constructions)
.def_readwrite("move_constructions", &ConstructorStats::move_constructions)
.def_static("get",
(ConstructorStats & (*) (py::object)) & ConstructorStats::get,
py::return_value_policy::reference_internal)
// Not exactly ConstructorStats, but related: expose the internal pybind number of
// registered instances to allow instance cleanup checks (invokes a GC first)
.def_static("detail_reg_inst", []() {
ConstructorStats::gc();
return py::detail::get_internals().registered_instances.size();
});
}
PYBIND11_MODULE(pybind11_tests, m) {
m.doc() = "pybind11 test module";
bind_ConstructorStats(m);
#if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
m.attr("detailed_error_messages_enabled") = true;
#else
m.attr("detailed_error_messages_enabled") = false;
#endif
py::class_<UserType>(m, "UserType", "A `py::class_` type for testing")
.def(py::init<>())
.def(py::init<int>())
.def("get_value", &UserType::value, "Get value using a method")
.def("set_value", &UserType::set, "Set value using a method")
.def_property("value", &UserType::value, &UserType::set, "Get/set value using a property")
.def("__repr__", [](const UserType &u) { return "UserType({})"_s.format(u.value()); });
py::class_<IncType, UserType>(m, "IncType")
.def(py::init<>())
.def(py::init<int>())
.def("__repr__", [](const IncType &u) { return "IncType({})"_s.format(u.value()); });
for (const auto &initializer : initializers()) {
initializer(m);
}
}

View File

@@ -0,0 +1,85 @@
#pragma once
#include <pybind11/eval.h>
#include <pybind11/pybind11.h>
namespace py = pybind11;
using namespace pybind11::literals;
class test_initializer {
using Initializer = void (*)(py::module_ &);
public:
explicit test_initializer(Initializer init);
test_initializer(const char *submodule_name, Initializer init);
};
#define TEST_SUBMODULE(name, variable) \
void test_submodule_##name(py::module_ &); \
test_initializer name(#name, test_submodule_##name); \
void test_submodule_##name(py::module_ &(variable))
/// Dummy type which is not exported anywhere -- something to trigger a conversion error
struct UnregisteredType {};
/// A user-defined type which is exported and can be used by any test
class UserType {
public:
UserType() = default;
explicit UserType(int i) : i(i) {}
int value() const { return i; }
void set(int set) { i = set; }
private:
int i = -1;
};
/// Like UserType, but increments `value` on copy for quick reference vs. copy tests
class IncType : public UserType {
public:
using UserType::UserType;
IncType() = default;
IncType(const IncType &other) : IncType(other.value() + 1) {}
IncType(IncType &&) = delete;
IncType &operator=(const IncType &) = delete;
IncType &operator=(IncType &&) = delete;
};
/// A simple union for basic testing
union IntFloat {
int i;
float f;
};
/// Custom cast-only type that casts to a string "rvalue" or "lvalue" depending on the cast
/// context. Used to test recursive casters (e.g. std::tuple, stl containers).
struct RValueCaster {};
PYBIND11_NAMESPACE_BEGIN(pybind11)
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
class type_caster<RValueCaster> {
public:
PYBIND11_TYPE_CASTER(RValueCaster, const_name("RValueCaster"));
static handle cast(RValueCaster &&, return_value_policy, handle) {
return py::str("rvalue").release();
}
static handle cast(const RValueCaster &, return_value_policy, handle) {
return py::str("lvalue").release();
}
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(pybind11)
template <typename F>
void ignoreOldStyleInitWarnings(F &&body) {
py::exec(R"(
message = "pybind11-bound class '.+' is using an old-style placement-new '(?:__init__|__setstate__)' which has been deprecated"
import warnings
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message=message, category=FutureWarning)
body()
)",
py::dict(py::arg("body") = py::cpp_function(body)));
}

22
third_party/pybind11/tests/pytest.ini vendored Normal file
View File

@@ -0,0 +1,22 @@
[pytest]
minversion = 3.10
norecursedirs = test_* extra_*
xfail_strict = True
addopts =
# show summary of tests
-ra
# capture only Python print and C++ py::print, but not C output (low-level Python errors)
--capture=sys
# Show local info when a failure occurs
--showlocals
log_cli_level = info
filterwarnings =
# make warnings into errors but ignore certain third-party extension issues
error
# somehow, some DeprecationWarnings do not get turned into errors
always::DeprecationWarning
# importing scipy submodules on some version of Python
ignore::ImportWarning
# bogus numpy ABI warning (see numpy/#432)
ignore:.*numpy.dtype size changed.*:RuntimeWarning
ignore:.*numpy.ufunc size changed.*:RuntimeWarning

View File

@@ -0,0 +1,9 @@
build==0.7.0
numpy==1.21.5; platform_python_implementation=="PyPy" and sys_platform=="linux" and python_version=="3.7"
numpy==1.19.3; platform_python_implementation!="PyPy" and python_version=="3.6"
numpy==1.21.5; platform_python_implementation!="PyPy" and python_version>="3.7" and python_version<"3.10"
numpy==1.22.2; platform_python_implementation!="PyPy" and python_version>="3.10" and python_version<"3.11"
pytest==7.0.0
pytest-timeout
scipy==1.5.4; platform_python_implementation!="PyPy" and python_version<"3.10"
scipy==1.8.0; platform_python_implementation!="PyPy" and python_version=="3.10"

View File

@@ -0,0 +1,25 @@
/*
tests/test_async.cpp -- __await__ support
Copyright (c) 2019 Google Inc.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
TEST_SUBMODULE(async_module, m) {
struct DoesNotSupportAsync {};
py::class_<DoesNotSupportAsync>(m, "DoesNotSupportAsync").def(py::init<>());
struct SupportsAsync {};
py::class_<SupportsAsync>(m, "SupportsAsync")
.def(py::init<>())
.def("__await__", [](const SupportsAsync &self) -> py::object {
static_cast<void>(self);
py::object loop = py::module_::import("asyncio.events").attr("get_event_loop")();
py::object f = loop.attr("create_future")();
f.attr("set_result")(5);
return f.attr("__await__")();
});
}

View File

@@ -0,0 +1,26 @@
import pytest
asyncio = pytest.importorskip("asyncio")
m = pytest.importorskip("pybind11_tests.async_module")
@pytest.fixture
def event_loop():
loop = asyncio.new_event_loop()
yield loop
loop.close()
async def get_await_result(x):
return await x
def test_await(event_loop):
assert 5 == event_loop.run_until_complete(
get_await_result(m.SupportsAsync()))
def test_await_missing(event_loop):
with pytest.raises(TypeError):
event_loop.run_until_complete(
get_await_result(m.DoesNotSupportAsync()))

View File

@@ -0,0 +1,224 @@
/*
tests/test_buffers.cpp -- supporting Pythons' buffer protocol
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/stl.h>
#include "constructor_stats.h"
#include "pybind11_tests.h"
TEST_SUBMODULE(buffers, m) {
// test_from_python / test_to_python:
class Matrix {
public:
Matrix(py::ssize_t rows, py::ssize_t cols) : m_rows(rows), m_cols(cols) {
print_created(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
m_data = new float[(size_t) (rows * cols)];
memset(m_data, 0, sizeof(float) * (size_t) (rows * cols));
}
Matrix(const Matrix &s) : m_rows(s.m_rows), m_cols(s.m_cols) {
print_copy_created(this,
std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
m_data = new float[(size_t) (m_rows * m_cols)];
memcpy(m_data, s.m_data, sizeof(float) * (size_t) (m_rows * m_cols));
}
Matrix(Matrix &&s) noexcept : m_rows(s.m_rows), m_cols(s.m_cols), m_data(s.m_data) {
print_move_created(this);
s.m_rows = 0;
s.m_cols = 0;
s.m_data = nullptr;
}
~Matrix() {
print_destroyed(this,
std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
delete[] m_data;
}
Matrix &operator=(const Matrix &s) {
if (this == &s) {
return *this;
}
print_copy_assigned(this,
std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
delete[] m_data;
m_rows = s.m_rows;
m_cols = s.m_cols;
m_data = new float[(size_t) (m_rows * m_cols)];
memcpy(m_data, s.m_data, sizeof(float) * (size_t) (m_rows * m_cols));
return *this;
}
Matrix &operator=(Matrix &&s) noexcept {
print_move_assigned(this,
std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix");
if (&s != this) {
delete[] m_data;
m_rows = s.m_rows;
m_cols = s.m_cols;
m_data = s.m_data;
s.m_rows = 0;
s.m_cols = 0;
s.m_data = nullptr;
}
return *this;
}
float operator()(py::ssize_t i, py::ssize_t j) const {
return m_data[(size_t) (i * m_cols + j)];
}
float &operator()(py::ssize_t i, py::ssize_t j) {
return m_data[(size_t) (i * m_cols + j)];
}
float *data() { return m_data; }
py::ssize_t rows() const { return m_rows; }
py::ssize_t cols() const { return m_cols; }
private:
py::ssize_t m_rows;
py::ssize_t m_cols;
float *m_data;
};
py::class_<Matrix>(m, "Matrix", py::buffer_protocol())
.def(py::init<py::ssize_t, py::ssize_t>())
/// Construct from a buffer
.def(py::init([](const py::buffer &b) {
py::buffer_info info = b.request();
if (info.format != py::format_descriptor<float>::format() || info.ndim != 2) {
throw std::runtime_error("Incompatible buffer format!");
}
auto *v = new Matrix(info.shape[0], info.shape[1]);
memcpy(v->data(), info.ptr, sizeof(float) * (size_t) (v->rows() * v->cols()));
return v;
}))
.def("rows", &Matrix::rows)
.def("cols", &Matrix::cols)
/// Bare bones interface
.def("__getitem__",
[](const Matrix &m, std::pair<py::ssize_t, py::ssize_t> i) {
if (i.first >= m.rows() || i.second >= m.cols()) {
throw py::index_error();
}
return m(i.first, i.second);
})
.def("__setitem__",
[](Matrix &m, std::pair<py::ssize_t, py::ssize_t> i, float v) {
if (i.first >= m.rows() || i.second >= m.cols()) {
throw py::index_error();
}
m(i.first, i.second) = v;
})
/// Provide buffer access
.def_buffer([](Matrix &m) -> py::buffer_info {
return py::buffer_info(
m.data(), /* Pointer to buffer */
{m.rows(), m.cols()}, /* Buffer dimensions */
{sizeof(float) * size_t(m.cols()), /* Strides (in bytes) for each index */
sizeof(float)});
});
// test_inherited_protocol
class SquareMatrix : public Matrix {
public:
explicit SquareMatrix(py::ssize_t n) : Matrix(n, n) {}
};
// Derived classes inherit the buffer protocol and the buffer access function
py::class_<SquareMatrix, Matrix>(m, "SquareMatrix").def(py::init<py::ssize_t>());
// test_pointer_to_member_fn
// Tests that passing a pointer to member to the base class works in
// the derived class.
struct Buffer {
int32_t value = 0;
py::buffer_info get_buffer_info() {
return py::buffer_info(
&value, sizeof(value), py::format_descriptor<int32_t>::format(), 1);
}
};
py::class_<Buffer>(m, "Buffer", py::buffer_protocol())
.def(py::init<>())
.def_readwrite("value", &Buffer::value)
.def_buffer(&Buffer::get_buffer_info);
class ConstBuffer {
std::unique_ptr<int32_t> value;
public:
int32_t get_value() const { return *value; }
void set_value(int32_t v) { *value = v; }
py::buffer_info get_buffer_info() const {
return py::buffer_info(
value.get(), sizeof(*value), py::format_descriptor<int32_t>::format(), 1);
}
ConstBuffer() : value(new int32_t{0}) {}
};
py::class_<ConstBuffer>(m, "ConstBuffer", py::buffer_protocol())
.def(py::init<>())
.def_property("value", &ConstBuffer::get_value, &ConstBuffer::set_value)
.def_buffer(&ConstBuffer::get_buffer_info);
struct DerivedBuffer : public Buffer {};
py::class_<DerivedBuffer>(m, "DerivedBuffer", py::buffer_protocol())
.def(py::init<>())
.def_readwrite("value", (int32_t DerivedBuffer::*) &DerivedBuffer::value)
.def_buffer(&DerivedBuffer::get_buffer_info);
struct BufferReadOnly {
const uint8_t value = 0;
explicit BufferReadOnly(uint8_t value) : value(value) {}
py::buffer_info get_buffer_info() { return py::buffer_info(&value, 1); }
};
py::class_<BufferReadOnly>(m, "BufferReadOnly", py::buffer_protocol())
.def(py::init<uint8_t>())
.def_buffer(&BufferReadOnly::get_buffer_info);
struct BufferReadOnlySelect {
uint8_t value = 0;
bool readonly = false;
py::buffer_info get_buffer_info() { return py::buffer_info(&value, 1, readonly); }
};
py::class_<BufferReadOnlySelect>(m, "BufferReadOnlySelect", py::buffer_protocol())
.def(py::init<>())
.def_readwrite("value", &BufferReadOnlySelect::value)
.def_readwrite("readonly", &BufferReadOnlySelect::readonly)
.def_buffer(&BufferReadOnlySelect::get_buffer_info);
// Expose buffer_info for testing.
py::class_<py::buffer_info>(m, "buffer_info")
.def(py::init<>())
.def_readonly("itemsize", &py::buffer_info::itemsize)
.def_readonly("size", &py::buffer_info::size)
.def_readonly("format", &py::buffer_info::format)
.def_readonly("ndim", &py::buffer_info::ndim)
.def_readonly("shape", &py::buffer_info::shape)
.def_readonly("strides", &py::buffer_info::strides)
.def_readonly("readonly", &py::buffer_info::readonly)
.def("__repr__", [](py::handle self) {
return py::str("itemsize={0.itemsize!r}, size={0.size!r}, format={0.format!r}, "
"ndim={0.ndim!r}, shape={0.shape!r}, strides={0.strides!r}, "
"readonly={0.readonly!r}")
.format(self);
});
m.def("get_buffer_info", [](const py::buffer &buffer) { return buffer.request(); });
}

View File

@@ -0,0 +1,162 @@
import ctypes
import io
import struct
import pytest
import env
from pybind11_tests import ConstructorStats
from pybind11_tests import buffers as m
np = pytest.importorskip("numpy")
def test_from_python():
with pytest.raises(RuntimeError) as excinfo:
m.Matrix(np.array([1, 2, 3])) # trying to assign a 1D array
assert str(excinfo.value) == "Incompatible buffer format!"
m3 = np.array([[1, 2, 3], [4, 5, 6]]).astype(np.float32)
m4 = m.Matrix(m3)
for i in range(m4.rows()):
for j in range(m4.cols()):
assert m3[i, j] == m4[i, j]
cstats = ConstructorStats.get(m.Matrix)
assert cstats.alive() == 1
del m3, m4
assert cstats.alive() == 0
assert cstats.values() == ["2x3 matrix"]
assert cstats.copy_constructions == 0
# assert cstats.move_constructions >= 0 # Don't invoke any
assert cstats.copy_assignments == 0
assert cstats.move_assignments == 0
# https://foss.heptapod.net/pypy/pypy/-/issues/2444
# TODO: fix on recent PyPy
@pytest.mark.xfail(
env.PYPY, reason="PyPy 7.3.7 doesn't clear this anymore", strict=False)
def test_to_python():
mat = m.Matrix(5, 4)
assert memoryview(mat).shape == (5, 4)
assert mat[2, 3] == 0
mat[2, 3] = 4.0
mat[3, 2] = 7.0
assert mat[2, 3] == 4
assert mat[3, 2] == 7
assert struct.unpack_from("f", mat, (3 * 4 + 2) * 4) == (7, )
assert struct.unpack_from("f", mat, (2 * 4 + 3) * 4) == (4, )
mat2 = np.array(mat, copy=False)
assert mat2.shape == (5, 4)
assert abs(mat2).sum() == 11
assert mat2[2, 3] == 4 and mat2[3, 2] == 7
mat2[2, 3] = 5
assert mat2[2, 3] == 5
cstats = ConstructorStats.get(m.Matrix)
assert cstats.alive() == 1
del mat
pytest.gc_collect()
assert cstats.alive() == 1
del mat2 # holds a mat reference
pytest.gc_collect()
assert cstats.alive() == 0
assert cstats.values() == ["5x4 matrix"]
assert cstats.copy_constructions == 0
# assert cstats.move_constructions >= 0 # Don't invoke any
assert cstats.copy_assignments == 0
assert cstats.move_assignments == 0
def test_inherited_protocol():
"""SquareMatrix is derived from Matrix and inherits the buffer protocol"""
matrix = m.SquareMatrix(5)
assert memoryview(matrix).shape == (5, 5)
assert np.asarray(matrix).shape == (5, 5)
def test_pointer_to_member_fn():
for cls in [m.Buffer, m.ConstBuffer, m.DerivedBuffer]:
buf = cls()
buf.value = 0x12345678
value = struct.unpack("i", bytearray(buf))[0]
assert value == 0x12345678
def test_readonly_buffer():
buf = m.BufferReadOnly(0x64)
view = memoryview(buf)
assert view[0] == 0x64
assert view.readonly
with pytest.raises(TypeError):
view[0] = 0
def test_selective_readonly_buffer():
buf = m.BufferReadOnlySelect()
memoryview(buf)[0] = 0x64
assert buf.value == 0x64
io.BytesIO(b"A").readinto(buf)
assert buf.value == ord(b"A")
buf.readonly = True
with pytest.raises(TypeError):
memoryview(buf)[0] = 0
with pytest.raises(TypeError):
io.BytesIO(b"1").readinto(buf)
def test_ctypes_array_1d():
char1d = (ctypes.c_char * 10)()
int1d = (ctypes.c_int * 15)()
long1d = (ctypes.c_long * 7)()
for carray in (char1d, int1d, long1d):
info = m.get_buffer_info(carray)
assert info.itemsize == ctypes.sizeof(carray._type_)
assert info.size == len(carray)
assert info.ndim == 1
assert info.shape == [info.size]
assert info.strides == [info.itemsize]
assert not info.readonly
def test_ctypes_array_2d():
char2d = ((ctypes.c_char * 10) * 4)()
int2d = ((ctypes.c_int * 15) * 3)()
long2d = ((ctypes.c_long * 7) * 2)()
for carray in (char2d, int2d, long2d):
info = m.get_buffer_info(carray)
assert info.itemsize == ctypes.sizeof(carray[0]._type_)
assert info.size == len(carray) * len(carray[0])
assert info.ndim == 2
assert info.shape == [len(carray), len(carray[0])]
assert info.strides == [info.itemsize * len(carray[0]), info.itemsize]
assert not info.readonly
def test_ctypes_from_buffer():
test_pystr = b"0123456789"
for pyarray in (test_pystr, bytearray(test_pystr)):
pyinfo = m.get_buffer_info(pyarray)
if pyinfo.readonly:
cbytes = (ctypes.c_char * len(pyarray)).from_buffer_copy(pyarray)
cinfo = m.get_buffer_info(cbytes)
else:
cbytes = (ctypes.c_char * len(pyarray)).from_buffer(pyarray)
cinfo = m.get_buffer_info(cbytes)
assert cinfo.size == pyinfo.size
assert cinfo.ndim == pyinfo.ndim
assert cinfo.shape == pyinfo.shape
assert cinfo.strides == pyinfo.strides
assert not cinfo.readonly

View File

@@ -0,0 +1,382 @@
/*
tests/test_builtin_casters.cpp -- Casters available without any additional headers
Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/complex.h>
#include "pybind11_tests.h"
struct ConstRefCasted {
int tag;
};
PYBIND11_NAMESPACE_BEGIN(pybind11)
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
class type_caster<ConstRefCasted> {
public:
static constexpr auto name = const_name<ConstRefCasted>();
// Input is unimportant, a new value will always be constructed based on the
// cast operator.
bool load(handle, bool) { return true; }
explicit operator ConstRefCasted &&() {
value = {1};
// NOLINTNEXTLINE(performance-move-const-arg)
return std::move(value);
}
explicit operator ConstRefCasted &() {
value = {2};
return value;
}
explicit operator ConstRefCasted *() {
value = {3};
return &value;
}
explicit operator const ConstRefCasted &() {
value = {4};
return value;
}
explicit operator const ConstRefCasted *() {
value = {5};
return &value;
}
// custom cast_op to explicitly propagate types to the conversion operators.
template <typename T_>
using cast_op_type =
/// const
conditional_t<
std::is_same<remove_reference_t<T_>, const ConstRefCasted *>::value,
const ConstRefCasted *,
conditional_t<
std::is_same<T_, const ConstRefCasted &>::value,
const ConstRefCasted &,
/// non-const
conditional_t<std::is_same<remove_reference_t<T_>, ConstRefCasted *>::value,
ConstRefCasted *,
conditional_t<std::is_same<T_, ConstRefCasted &>::value,
ConstRefCasted &,
/* else */ ConstRefCasted &&>>>>;
private:
ConstRefCasted value = {0};
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(pybind11)
TEST_SUBMODULE(builtin_casters, m) {
// test_simple_string
m.def("string_roundtrip", [](const char *s) { return s; });
// test_unicode_conversion
// Some test characters in utf16 and utf32 encodings. The last one (the 𝐀) contains a null
// byte
char32_t a32 = 0x61 /*a*/, z32 = 0x7a /*z*/, ib32 = 0x203d /*‽*/, cake32 = 0x1f382 /*🎂*/,
mathbfA32 = 0x1d400 /*𝐀*/;
char16_t b16 = 0x62 /*b*/, z16 = 0x7a, ib16 = 0x203d, cake16_1 = 0xd83c, cake16_2 = 0xdf82,
mathbfA16_1 = 0xd835, mathbfA16_2 = 0xdc00;
std::wstring wstr;
wstr.push_back(0x61); // a
wstr.push_back(0x2e18); // ⸘
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) {
wstr.push_back(mathbfA16_1);
wstr.push_back(mathbfA16_2);
} // 𝐀, utf16
else {
wstr.push_back((wchar_t) mathbfA32);
} // 𝐀, utf32
wstr.push_back(0x7a); // z
m.def("good_utf8_string", []() {
return std::string((const char *) u8"Say utf8\u203d \U0001f382 \U0001d400");
}); // Say utf8‽ 🎂 𝐀
m.def("good_utf16_string", [=]() {
return std::u16string({b16, ib16, cake16_1, cake16_2, mathbfA16_1, mathbfA16_2, z16});
}); // b‽🎂𝐀z
m.def("good_utf32_string", [=]() {
return std::u32string({a32, mathbfA32, cake32, ib32, z32});
}); // a𝐀🎂‽z
m.def("good_wchar_string", [=]() { return wstr; }); // a‽𝐀z
m.def("bad_utf8_string", []() {
return std::string("abc\xd0"
"def");
});
m.def("bad_utf16_string", [=]() { return std::u16string({b16, char16_t(0xd800), z16}); });
// Under Python 2.7, invalid unicode UTF-32 characters didn't appear to trigger
// UnicodeDecodeError
m.def("bad_utf32_string", [=]() { return std::u32string({a32, char32_t(0xd800), z32}); });
if (PYBIND11_SILENCE_MSVC_C4127(sizeof(wchar_t) == 2)) {
m.def("bad_wchar_string", [=]() {
return std::wstring({wchar_t(0x61), wchar_t(0xd800)});
});
}
m.def("u8_Z", []() -> char { return 'Z'; });
m.def("u8_eacute", []() -> char { return '\xe9'; });
m.def("u16_ibang", [=]() -> char16_t { return ib16; });
m.def("u32_mathbfA", [=]() -> char32_t { return mathbfA32; });
m.def("wchar_heart", []() -> wchar_t { return 0x2665; });
// test_single_char_arguments
m.attr("wchar_size") = py::cast(sizeof(wchar_t));
m.def("ord_char", [](char c) -> int { return static_cast<unsigned char>(c); });
m.def("ord_char_lv", [](char &c) -> int { return static_cast<unsigned char>(c); });
m.def("ord_char16", [](char16_t c) -> uint16_t { return c; });
m.def("ord_char16_lv", [](char16_t &c) -> uint16_t { return c; });
m.def("ord_char32", [](char32_t c) -> uint32_t { return c; });
m.def("ord_wchar", [](wchar_t c) -> int { return c; });
// test_bytes_to_string
m.def("strlen", [](char *s) { return strlen(s); });
m.def("string_length", [](const std::string &s) { return s.length(); });
#ifdef PYBIND11_HAS_U8STRING
m.attr("has_u8string") = true;
m.def("good_utf8_u8string", []() {
return std::u8string(u8"Say utf8\u203d \U0001f382 \U0001d400");
}); // Say utf8‽ 🎂 𝐀
m.def("bad_utf8_u8string", []() {
return std::u8string((const char8_t *) "abc\xd0"
"def");
});
m.def("u8_char8_Z", []() -> char8_t { return u8'Z'; });
// test_single_char_arguments
m.def("ord_char8", [](char8_t c) -> int { return static_cast<unsigned char>(c); });
m.def("ord_char8_lv", [](char8_t &c) -> int { return static_cast<unsigned char>(c); });
#endif
// test_string_view
#ifdef PYBIND11_HAS_STRING_VIEW
m.attr("has_string_view") = true;
m.def("string_view_print", [](std::string_view s) { py::print(s, s.size()); });
m.def("string_view16_print", [](std::u16string_view s) { py::print(s, s.size()); });
m.def("string_view32_print", [](std::u32string_view s) { py::print(s, s.size()); });
m.def("string_view_chars", [](std::string_view s) {
py::list l;
for (auto c : s) {
l.append((std::uint8_t) c);
}
return l;
});
m.def("string_view16_chars", [](std::u16string_view s) {
py::list l;
for (auto c : s) {
l.append((int) c);
}
return l;
});
m.def("string_view32_chars", [](std::u32string_view s) {
py::list l;
for (auto c : s) {
l.append((int) c);
}
return l;
});
m.def("string_view_return",
[]() { return std::string_view((const char *) u8"utf8 secret \U0001f382"); });
m.def("string_view16_return",
[]() { return std::u16string_view(u"utf16 secret \U0001f382"); });
m.def("string_view32_return",
[]() { return std::u32string_view(U"utf32 secret \U0001f382"); });
// The inner lambdas here are to also test implicit conversion
using namespace std::literals;
m.def("string_view_bytes",
[]() { return [](py::bytes b) { return b; }("abc \x80\x80 def"sv); });
m.def("string_view_str",
[]() { return [](py::str s) { return s; }("abc \342\200\275 def"sv); });
m.def("string_view_from_bytes",
[](const py::bytes &b) { return [](std::string_view s) { return s; }(b); });
m.def("string_view_memoryview", []() {
static constexpr auto val = "Have some \360\237\216\202"sv;
return py::memoryview::from_memory(val);
});
# ifdef PYBIND11_HAS_U8STRING
m.def("string_view8_print", [](std::u8string_view s) { py::print(s, s.size()); });
m.def("string_view8_chars", [](std::u8string_view s) {
py::list l;
for (auto c : s)
l.append((std::uint8_t) c);
return l;
});
m.def("string_view8_return", []() { return std::u8string_view(u8"utf8 secret \U0001f382"); });
m.def("string_view8_str", []() { return py::str{std::u8string_view{u8"abc ‽ def"}}; });
# endif
struct TypeWithBothOperatorStringAndStringView {
// NOLINTNEXTLINE(google-explicit-constructor)
operator std::string() const { return "success"; }
// NOLINTNEXTLINE(google-explicit-constructor)
operator std::string_view() const { return "failure"; }
};
m.def("bytes_from_type_with_both_operator_string_and_string_view",
[]() { return py::bytes(TypeWithBothOperatorStringAndStringView()); });
m.def("str_from_type_with_both_operator_string_and_string_view",
[]() { return py::str(TypeWithBothOperatorStringAndStringView()); });
#endif
// test_integer_casting
m.def("i32_str", [](std::int32_t v) { return std::to_string(v); });
m.def("u32_str", [](std::uint32_t v) { return std::to_string(v); });
m.def("i64_str", [](std::int64_t v) { return std::to_string(v); });
m.def("u64_str", [](std::uint64_t v) { return std::to_string(v); });
// test_int_convert
m.def("int_passthrough", [](int arg) { return arg; });
m.def(
"int_passthrough_noconvert", [](int arg) { return arg; }, py::arg{}.noconvert());
// test_tuple
m.def(
"pair_passthrough",
[](const std::pair<bool, std::string> &input) {
return std::make_pair(input.second, input.first);
},
"Return a pair in reversed order");
m.def(
"tuple_passthrough",
[](std::tuple<bool, std::string, int> input) {
return std::make_tuple(std::get<2>(input), std::get<1>(input), std::get<0>(input));
},
"Return a triple in reversed order");
m.def("empty_tuple", []() { return std::tuple<>(); });
static std::pair<RValueCaster, RValueCaster> lvpair;
static std::tuple<RValueCaster, RValueCaster, RValueCaster> lvtuple;
static std::pair<RValueCaster, std::tuple<RValueCaster, std::pair<RValueCaster, RValueCaster>>>
lvnested;
m.def("rvalue_pair", []() { return std::make_pair(RValueCaster{}, RValueCaster{}); });
m.def("lvalue_pair", []() -> const decltype(lvpair) & { return lvpair; });
m.def("rvalue_tuple",
[]() { return std::make_tuple(RValueCaster{}, RValueCaster{}, RValueCaster{}); });
m.def("lvalue_tuple", []() -> const decltype(lvtuple) & { return lvtuple; });
m.def("rvalue_nested", []() {
return std::make_pair(
RValueCaster{},
std::make_tuple(RValueCaster{}, std::make_pair(RValueCaster{}, RValueCaster{})));
});
m.def("lvalue_nested", []() -> const decltype(lvnested) & { return lvnested; });
static std::pair<int, std::string> int_string_pair{2, "items"};
m.def(
"int_string_pair", []() { return &int_string_pair; }, py::return_value_policy::reference);
// test_builtins_cast_return_none
m.def("return_none_string", []() -> std::string * { return nullptr; });
m.def("return_none_char", []() -> const char * { return nullptr; });
m.def("return_none_bool", []() -> bool * { return nullptr; });
m.def("return_none_int", []() -> int * { return nullptr; });
m.def("return_none_float", []() -> float * { return nullptr; });
m.def("return_none_pair", []() -> std::pair<int, int> * { return nullptr; });
// test_none_deferred
m.def("defer_none_cstring", [](char *) { return false; });
m.def("defer_none_cstring", [](const py::none &) { return true; });
m.def("defer_none_custom", [](UserType *) { return false; });
m.def("defer_none_custom", [](const py::none &) { return true; });
m.def("nodefer_none_void", [](void *) { return true; });
m.def("nodefer_none_void", [](const py::none &) { return false; });
// test_void_caster
m.def("load_nullptr_t", [](std::nullptr_t) {}); // not useful, but it should still compile
m.def("cast_nullptr_t", []() { return std::nullptr_t{}; });
// [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works.
// test_bool_caster
m.def("bool_passthrough", [](bool arg) { return arg; });
m.def(
"bool_passthrough_noconvert", [](bool arg) { return arg; }, py::arg{}.noconvert());
// TODO: This should be disabled and fixed in future Intel compilers
#if !defined(__INTEL_COMPILER)
// Test "bool_passthrough_noconvert" again, but using () instead of {} to construct py::arg
// When compiled with the Intel compiler, this results in segmentation faults when importing
// the module. Tested with icc (ICC) 2021.1 Beta 20200827, this should be tested again when
// a newer version of icc is available.
m.def(
"bool_passthrough_noconvert2", [](bool arg) { return arg; }, py::arg().noconvert());
#endif
// test_reference_wrapper
m.def("refwrap_builtin", [](std::reference_wrapper<int> p) { return 10 * p.get(); });
m.def("refwrap_usertype", [](std::reference_wrapper<UserType> p) { return p.get().value(); });
m.def("refwrap_usertype_const",
[](std::reference_wrapper<const UserType> p) { return p.get().value(); });
m.def("refwrap_lvalue", []() -> std::reference_wrapper<UserType> {
static UserType x(1);
return std::ref(x);
});
m.def("refwrap_lvalue_const", []() -> std::reference_wrapper<const UserType> {
static UserType x(1);
return std::cref(x);
});
// Not currently supported (std::pair caster has return-by-value cast operator);
// triggers static_assert failure.
// m.def("refwrap_pair", [](std::reference_wrapper<std::pair<int, int>>) { });
m.def(
"refwrap_list",
[](bool copy) {
static IncType x1(1), x2(2);
py::list l;
for (const auto &f : {std::ref(x1), std::ref(x2)}) {
l.append(py::cast(
f, copy ? py::return_value_policy::copy : py::return_value_policy::reference));
}
return l;
},
"copy"_a);
m.def("refwrap_iiw", [](const IncType &w) { return w.value(); });
m.def("refwrap_call_iiw", [](IncType &w, const py::function &f) {
py::list l;
l.append(f(std::ref(w)));
l.append(f(std::cref(w)));
IncType x(w.value());
l.append(f(std::ref(x)));
IncType y(w.value());
auto r3 = std::ref(y);
l.append(f(r3));
return l;
});
// test_complex
m.def("complex_cast", [](float x) { return "{}"_s.format(x); });
m.def("complex_cast",
[](std::complex<float> x) { return "({}, {})"_s.format(x.real(), x.imag()); });
// test int vs. long (Python 2)
m.def("int_cast", []() { return (int) 42; });
m.def("long_cast", []() { return (long) 42; });
m.def("longlong_cast", []() { return ULLONG_MAX; });
/// test void* cast operator
m.def("test_void_caster", []() -> bool {
void *v = (void *) 0xabcd;
py::object o = py::cast(v);
return py::cast<void *>(o) == v;
});
// Tests const/non-const propagation in cast_op.
m.def("takes", [](ConstRefCasted x) { return x.tag; });
m.def("takes_move", [](ConstRefCasted &&x) { return x.tag; });
m.def("takes_ptr", [](ConstRefCasted *x) { return x->tag; });
m.def("takes_ref", [](ConstRefCasted &x) { return x.tag; });
m.def("takes_ref_wrap", [](std::reference_wrapper<ConstRefCasted> x) { return x.get().tag; });
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<const ConstRefCasted> x) { return x.get().tag; });
}

View File

@@ -0,0 +1,512 @@
import sys
import pytest
import env
from pybind11_tests import IncType, UserType
from pybind11_tests import builtin_casters as m
def test_simple_string():
assert m.string_roundtrip("const char *") == "const char *"
def test_unicode_conversion():
"""Tests unicode conversion and error reporting."""
assert m.good_utf8_string() == "Say utf8‽ 🎂 𝐀"
assert m.good_utf16_string() == "b‽🎂𝐀z"
assert m.good_utf32_string() == "a𝐀🎂‽z"
assert m.good_wchar_string() == "a⸘𝐀z"
if hasattr(m, "has_u8string"):
assert m.good_utf8_u8string() == "Say utf8‽ 🎂 𝐀"
with pytest.raises(UnicodeDecodeError):
m.bad_utf8_string()
with pytest.raises(UnicodeDecodeError):
m.bad_utf16_string()
# These are provided only if they actually fail (they don't when 32-bit)
if hasattr(m, "bad_utf32_string"):
with pytest.raises(UnicodeDecodeError):
m.bad_utf32_string()
if hasattr(m, "bad_wchar_string"):
with pytest.raises(UnicodeDecodeError):
m.bad_wchar_string()
if hasattr(m, "has_u8string"):
with pytest.raises(UnicodeDecodeError):
m.bad_utf8_u8string()
assert m.u8_Z() == "Z"
assert m.u8_eacute() == "é"
assert m.u16_ibang() == ""
assert m.u32_mathbfA() == "𝐀"
assert m.wchar_heart() == ""
if hasattr(m, "has_u8string"):
assert m.u8_char8_Z() == "Z"
def test_single_char_arguments():
"""Tests failures for passing invalid inputs to char-accepting functions"""
def toobig_message(r):
return f"Character code point not in range({r:#x})"
toolong_message = "Expected a character, but multi-character string found"
assert m.ord_char("a") == 0x61 # simple ASCII
assert m.ord_char_lv("b") == 0x62
assert (m.ord_char("é") == 0xE9
) # requires 2 bytes in utf-8, but can be stuffed in a char
with pytest.raises(ValueError) as excinfo:
assert m.ord_char(
"Ā") == 0x100 # requires 2 bytes, doesn't fit in a char
assert str(excinfo.value) == toobig_message(0x100)
with pytest.raises(ValueError) as excinfo:
assert m.ord_char("ab")
assert str(excinfo.value) == toolong_message
assert m.ord_char16("a") == 0x61
assert m.ord_char16("é") == 0xE9
assert m.ord_char16_lv("ê") == 0xEA
assert m.ord_char16("Ā") == 0x100
assert m.ord_char16("") == 0x203D
assert m.ord_char16("") == 0x2665
assert m.ord_char16_lv("") == 0x2661
with pytest.raises(ValueError) as excinfo:
assert m.ord_char16("🎂") == 0x1F382 # requires surrogate pair
assert str(excinfo.value) == toobig_message(0x10000)
with pytest.raises(ValueError) as excinfo:
assert m.ord_char16("aa")
assert str(excinfo.value) == toolong_message
assert m.ord_char32("a") == 0x61
assert m.ord_char32("é") == 0xE9
assert m.ord_char32("Ā") == 0x100
assert m.ord_char32("") == 0x203D
assert m.ord_char32("") == 0x2665
assert m.ord_char32("🎂") == 0x1F382
with pytest.raises(ValueError) as excinfo:
assert m.ord_char32("aa")
assert str(excinfo.value) == toolong_message
assert m.ord_wchar("a") == 0x61
assert m.ord_wchar("é") == 0xE9
assert m.ord_wchar("Ā") == 0x100
assert m.ord_wchar("") == 0x203D
assert m.ord_wchar("") == 0x2665
if m.wchar_size == 2:
with pytest.raises(ValueError) as excinfo:
assert m.ord_wchar("🎂") == 0x1F382 # requires surrogate pair
assert str(excinfo.value) == toobig_message(0x10000)
else:
assert m.ord_wchar("🎂") == 0x1F382
with pytest.raises(ValueError) as excinfo:
assert m.ord_wchar("aa")
assert str(excinfo.value) == toolong_message
if hasattr(m, "has_u8string"):
assert m.ord_char8("a") == 0x61 # simple ASCII
assert m.ord_char8_lv("b") == 0x62
assert (m.ord_char8("é") == 0xE9
) # requires 2 bytes in utf-8, but can be stuffed in a char
with pytest.raises(ValueError) as excinfo:
assert m.ord_char8(
"Ā") == 0x100 # requires 2 bytes, doesn't fit in a char
assert str(excinfo.value) == toobig_message(0x100)
with pytest.raises(ValueError) as excinfo:
assert m.ord_char8("ab")
assert str(excinfo.value) == toolong_message
def test_bytes_to_string():
"""Tests the ability to pass bytes to C++ string-accepting functions. Note that this is
one-way: the only way to return bytes to Python is via the pybind11::bytes class."""
# Issue #816
assert m.strlen(b"hi") == 2
assert m.string_length(b"world") == 5
assert m.string_length("a\x00b".encode()) == 3
assert m.strlen("a\x00b".encode()) == 1 # C-string limitation
# passing in a utf8 encoded string should work
assert m.string_length("💩".encode()) == 4
def test_bytearray_to_string():
"""Tests the ability to pass bytearray to C++ string-accepting functions"""
assert m.string_length(bytearray(b"Hi")) == 2
assert m.strlen(bytearray(b"bytearray")) == 9
assert m.string_length(bytearray()) == 0
assert m.string_length(bytearray("🦜", "utf-8", "strict")) == 4
assert m.string_length(bytearray(b"\x80")) == 1
@pytest.mark.skipif(
not hasattr(m, "has_string_view"), reason="no <string_view>")
def test_string_view(capture):
"""Tests support for C++17 string_view arguments and return values"""
assert m.string_view_chars("Hi") == [72, 105]
assert m.string_view_chars("Hi 🎂") == [72, 105, 32, 0xF0, 0x9F, 0x8E, 0x82]
assert m.string_view16_chars("Hi 🎂") == [72, 105, 32, 0xD83C, 0xDF82]
assert m.string_view32_chars("Hi 🎂") == [72, 105, 32, 127874]
if hasattr(m, "has_u8string"):
assert m.string_view8_chars("Hi") == [72, 105]
assert m.string_view8_chars(
"Hi 🎂") == [72, 105, 32, 0xF0, 0x9F, 0x8E, 0x82]
assert m.string_view_return() == "utf8 secret 🎂"
assert m.string_view16_return() == "utf16 secret 🎂"
assert m.string_view32_return() == "utf32 secret 🎂"
if hasattr(m, "has_u8string"):
assert m.string_view8_return() == "utf8 secret 🎂"
with capture:
m.string_view_print("Hi")
m.string_view_print("utf8 🎂")
m.string_view16_print("utf16 🎂")
m.string_view32_print("utf32 🎂")
assert (capture == """
Hi 2
utf8 🎂 9
utf16 🎂 8
utf32 🎂 7
""")
if hasattr(m, "has_u8string"):
with capture:
m.string_view8_print("Hi")
m.string_view8_print("utf8 🎂")
assert (capture == """
Hi 2
utf8 🎂 9
""")
with capture:
m.string_view_print("Hi, ascii")
m.string_view_print("Hi, utf8 🎂")
m.string_view16_print("Hi, utf16 🎂")
m.string_view32_print("Hi, utf32 🎂")
assert (capture == """
Hi, ascii 9
Hi, utf8 🎂 13
Hi, utf16 🎂 12
Hi, utf32 🎂 11
""")
if hasattr(m, "has_u8string"):
with capture:
m.string_view8_print("Hi, ascii")
m.string_view8_print("Hi, utf8 🎂")
assert (capture == """
Hi, ascii 9
Hi, utf8 🎂 13
""")
assert m.string_view_bytes() == b"abc \x80\x80 def"
assert m.string_view_str() == "abc ‽ def"
assert m.string_view_from_bytes("abc ‽ def".encode()) == "abc ‽ def"
if hasattr(m, "has_u8string"):
assert m.string_view8_str() == "abc ‽ def"
assert m.string_view_memoryview() == "Have some 🎂".encode()
assert m.bytes_from_type_with_both_operator_string_and_string_view(
) == b"success"
assert m.str_from_type_with_both_operator_string_and_string_view(
) == "success"
def test_integer_casting():
"""Issue #929 - out-of-range integer values shouldn't be accepted"""
assert m.i32_str(-1) == "-1"
assert m.i64_str(-1) == "-1"
assert m.i32_str(2000000000) == "2000000000"
assert m.u32_str(2000000000) == "2000000000"
assert m.i64_str(-999999999999) == "-999999999999"
assert m.u64_str(999999999999) == "999999999999"
with pytest.raises(TypeError) as excinfo:
m.u32_str(-1)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.u64_str(-1)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.i32_str(-3000000000)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.i32_str(3000000000)
assert "incompatible function arguments" in str(excinfo.value)
def test_int_convert():
class Int:
def __int__(self):
return 42
class NotInt:
pass
class Float:
def __float__(self):
return 41.99999
class Index:
def __index__(self):
return 42
class IntAndIndex:
def __int__(self):
return 42
def __index__(self):
return 0
class RaisingTypeErrorOnIndex:
def __index__(self):
raise TypeError
def __int__(self):
return 42
class RaisingValueErrorOnIndex:
def __index__(self):
raise ValueError
def __int__(self):
return 42
convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert
def requires_conversion(v):
pytest.raises(TypeError, noconvert, v)
def cant_convert(v):
pytest.raises(TypeError, convert, v)
assert convert(7) == 7
assert noconvert(7) == 7
cant_convert(3.14159)
# TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
# TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7)
if (3, 8) <= sys.version_info < (3, 10) and env.CPYTHON:
with env.deprecated_call():
assert convert(Int()) == 42
else:
assert convert(Int()) == 42
requires_conversion(Int())
cant_convert(NotInt())
cant_convert(Float())
# Before Python 3.8, `PyLong_AsLong` does not pick up on `obj.__index__`,
# but pybind11 "backports" this behavior.
assert convert(Index()) == 42
assert noconvert(Index()) == 42
assert convert(IntAndIndex()) == 0 # Fishy; `int(DoubleThought)` == 42
assert noconvert(IntAndIndex()) == 0
assert convert(RaisingTypeErrorOnIndex()) == 42
requires_conversion(RaisingTypeErrorOnIndex())
assert convert(RaisingValueErrorOnIndex()) == 42
requires_conversion(RaisingValueErrorOnIndex())
def test_numpy_int_convert():
np = pytest.importorskip("numpy")
convert, noconvert = m.int_passthrough, m.int_passthrough_noconvert
def require_implicit(v):
pytest.raises(TypeError, noconvert, v)
# `np.intc` is an alias that corresponds to a C++ `int`
assert convert(np.intc(42)) == 42
assert noconvert(np.intc(42)) == 42
# The implicit conversion from np.float32 is undesirable but currently accepted.
# TODO: Avoid DeprecationWarning in `PyLong_AsLong` (and similar)
# TODO: PyPy 3.8 does not behave like CPython 3.8 here yet (7.3.7)
# https://github.com/pybind/pybind11/issues/3408
if (3, 8) <= sys.version_info < (3, 10) and env.CPYTHON:
with env.deprecated_call():
assert convert(np.float32(3.14159)) == 3
else:
assert convert(np.float32(3.14159)) == 3
require_implicit(np.float32(3.14159))
def test_tuple(doc):
"""std::pair <-> tuple & std::tuple <-> tuple"""
assert m.pair_passthrough((True, "test")) == ("test", True)
assert m.tuple_passthrough((True, "test", 5)) == (5, "test", True)
# Any sequence can be cast to a std::pair or std::tuple
assert m.pair_passthrough([True, "test"]) == ("test", True)
assert m.tuple_passthrough([True, "test", 5]) == (5, "test", True)
assert m.empty_tuple() == ()
assert (doc(m.pair_passthrough) == """
pair_passthrough(arg0: Tuple[bool, str]) -> Tuple[str, bool]
Return a pair in reversed order
""")
assert (doc(m.tuple_passthrough) == """
tuple_passthrough(arg0: Tuple[bool, str, int]) -> Tuple[int, str, bool]
Return a triple in reversed order
""")
assert m.rvalue_pair() == ("rvalue", "rvalue")
assert m.lvalue_pair() == ("lvalue", "lvalue")
assert m.rvalue_tuple() == ("rvalue", "rvalue", "rvalue")
assert m.lvalue_tuple() == ("lvalue", "lvalue", "lvalue")
assert m.rvalue_nested() == ("rvalue", ("rvalue", ("rvalue", "rvalue")))
assert m.lvalue_nested() == ("lvalue", ("lvalue", ("lvalue", "lvalue")))
assert m.int_string_pair() == (2, "items")
def test_builtins_cast_return_none():
"""Casters produced with PYBIND11_TYPE_CASTER() should convert nullptr to None"""
assert m.return_none_string() is None
assert m.return_none_char() is None
assert m.return_none_bool() is None
assert m.return_none_int() is None
assert m.return_none_float() is None
assert m.return_none_pair() is None
def test_none_deferred():
"""None passed as various argument types should defer to other overloads"""
assert not m.defer_none_cstring("abc")
assert m.defer_none_cstring(None)
assert not m.defer_none_custom(UserType())
assert m.defer_none_custom(None)
assert m.nodefer_none_void(None)
def test_void_caster():
assert m.load_nullptr_t(None) is None
assert m.cast_nullptr_t() is None
def test_reference_wrapper():
"""std::reference_wrapper for builtin and user types"""
assert m.refwrap_builtin(42) == 420
assert m.refwrap_usertype(UserType(42)) == 42
assert m.refwrap_usertype_const(UserType(42)) == 42
with pytest.raises(TypeError) as excinfo:
m.refwrap_builtin(None)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.refwrap_usertype(None)
assert "incompatible function arguments" in str(excinfo.value)
assert m.refwrap_lvalue().value == 1
assert m.refwrap_lvalue_const().value == 1
a1 = m.refwrap_list(copy=True)
a2 = m.refwrap_list(copy=True)
assert [x.value for x in a1] == [2, 3]
assert [x.value for x in a2] == [2, 3]
assert not a1[0] is a2[0] and not a1[1] is a2[1]
b1 = m.refwrap_list(copy=False)
b2 = m.refwrap_list(copy=False)
assert [x.value for x in b1] == [1, 2]
assert [x.value for x in b2] == [1, 2]
assert b1[0] is b2[0] and b1[1] is b2[1]
assert m.refwrap_iiw(IncType(5)) == 5
assert m.refwrap_call_iiw(IncType(10), m.refwrap_iiw) == [10, 10, 10, 10]
def test_complex_cast():
"""std::complex casts"""
assert m.complex_cast(1) == "1.0"
assert m.complex_cast(2j) == "(0.0, 2.0)"
def test_bool_caster():
"""Test bool caster implicit conversions."""
convert, noconvert = m.bool_passthrough, m.bool_passthrough_noconvert
def require_implicit(v):
pytest.raises(TypeError, noconvert, v)
def cant_convert(v):
pytest.raises(TypeError, convert, v)
# straight up bool
assert convert(True) is True
assert convert(False) is False
assert noconvert(True) is True
assert noconvert(False) is False
# None requires implicit conversion
require_implicit(None)
assert convert(None) is False
class A:
def __init__(self, x):
self.x = x
def __nonzero__(self):
return self.x
def __bool__(self):
return self.x
class B:
pass
# Arbitrary objects are not accepted
cant_convert(object())
cant_convert(B())
# Objects with __nonzero__ / __bool__ defined can be converted
require_implicit(A(True))
assert convert(A(True)) is True
assert convert(A(False)) is False
def test_numpy_bool():
np = pytest.importorskip("numpy")
convert, noconvert = m.bool_passthrough, m.bool_passthrough_noconvert
def cant_convert(v):
pytest.raises(TypeError, convert, v)
# np.bool_ is not considered implicit
assert convert(np.bool_(True)) is True
assert convert(np.bool_(False)) is False
assert noconvert(np.bool_(True)) is True
assert noconvert(np.bool_(False)) is False
cant_convert(np.zeros(2, dtype="int"))
def test_int_long():
assert isinstance(m.int_cast(), int)
assert isinstance(m.long_cast(), int)
assert isinstance(m.longlong_cast(), int)
def test_void_caster_2():
assert m.test_void_caster()
def test_const_ref_caster():
"""Verifies that const-ref is propagated through type_caster cast_op.
The returned ConstRefCasted type is a minimal type that is constructed to
reference the casting mode used.
"""
x = False
assert m.takes(x) == 1
assert m.takes_move(x) == 1
assert m.takes_ptr(x) == 3
assert m.takes_ref(x) == 2
assert m.takes_ref_wrap(x) == 2
assert m.takes_const_ptr(x) == 5
assert m.takes_const_ref(x) == 4
assert m.takes_const_ref_wrap(x) == 4

View File

@@ -0,0 +1,115 @@
/*
tests/test_call_policies.cpp -- keep_alive and call_guard
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
struct CustomGuard {
static bool enabled;
CustomGuard() { enabled = true; }
~CustomGuard() { enabled = false; }
static const char *report_status() { return enabled ? "guarded" : "unguarded"; }
};
bool CustomGuard::enabled = false;
struct DependentGuard {
static bool enabled;
DependentGuard() { enabled = CustomGuard::enabled; }
~DependentGuard() { enabled = false; }
static const char *report_status() { return enabled ? "guarded" : "unguarded"; }
};
bool DependentGuard::enabled = false;
TEST_SUBMODULE(call_policies, m) {
// Parent/Child are used in:
// test_keep_alive_argument, test_keep_alive_return_value, test_alive_gc_derived,
// test_alive_gc_multi_derived, test_return_none, test_keep_alive_constructor
class Child {
public:
Child() { py::print("Allocating child."); }
Child(const Child &) = default;
Child(Child &&) = default;
~Child() { py::print("Releasing child."); }
};
py::class_<Child>(m, "Child").def(py::init<>());
class Parent {
public:
Parent() { py::print("Allocating parent."); }
Parent(const Parent &parent) = default;
~Parent() { py::print("Releasing parent."); }
void addChild(Child *) {}
Child *returnChild() { return new Child(); }
Child *returnNullChild() { return nullptr; }
static Child *staticFunction(Parent *) { return new Child(); }
};
py::class_<Parent>(m, "Parent")
.def(py::init<>())
.def(py::init([](Child *) { return new Parent(); }), py::keep_alive<1, 2>())
.def("addChild", &Parent::addChild)
.def("addChildKeepAlive", &Parent::addChild, py::keep_alive<1, 2>())
.def("returnChild", &Parent::returnChild)
.def("returnChildKeepAlive", &Parent::returnChild, py::keep_alive<1, 0>())
.def("returnNullChildKeepAliveChild", &Parent::returnNullChild, py::keep_alive<1, 0>())
.def("returnNullChildKeepAliveParent", &Parent::returnNullChild, py::keep_alive<0, 1>())
.def_static("staticFunction", &Parent::staticFunction, py::keep_alive<1, 0>());
m.def(
"free_function", [](Parent *, Child *) {}, py::keep_alive<1, 2>());
m.def(
"invalid_arg_index", [] {}, py::keep_alive<0, 1>());
#if !defined(PYPY_VERSION)
// test_alive_gc
class ParentGC : public Parent {
public:
using Parent::Parent;
};
py::class_<ParentGC, Parent>(m, "ParentGC", py::dynamic_attr()).def(py::init<>());
#endif
// test_call_guard
m.def("unguarded_call", &CustomGuard::report_status);
m.def("guarded_call", &CustomGuard::report_status, py::call_guard<CustomGuard>());
m.def(
"multiple_guards_correct_order",
[]() {
return CustomGuard::report_status() + std::string(" & ")
+ DependentGuard::report_status();
},
py::call_guard<CustomGuard, DependentGuard>());
m.def(
"multiple_guards_wrong_order",
[]() {
return DependentGuard::report_status() + std::string(" & ")
+ CustomGuard::report_status();
},
py::call_guard<DependentGuard, CustomGuard>());
#if defined(WITH_THREAD) && !defined(PYPY_VERSION)
// `py::call_guard<py::gil_scoped_release>()` should work in PyPy as well,
// but it's unclear how to test it without `PyGILState_GetThisThreadState`.
auto report_gil_status = []() {
auto is_gil_held = false;
if (auto *tstate = py::detail::get_thread_state_unchecked()) {
is_gil_held = (tstate == PyGILState_GetThisThreadState());
}
return is_gil_held ? "GIL held" : "GIL released";
};
m.def("with_gil", report_gil_status);
m.def("without_gil", report_gil_status, py::call_guard<py::gil_scoped_release>());
#endif
}

View File

@@ -0,0 +1,218 @@
import pytest
import env # noqa: F401
from pybind11_tests import ConstructorStats
from pybind11_tests import call_policies as m
@pytest.mark.xfail(
"env.PYPY", reason="sometimes comes out 1 off on PyPy", strict=False)
def test_keep_alive_argument(capture):
n_inst = ConstructorStats.detail_reg_inst()
with capture:
p = m.Parent()
assert capture == "Allocating parent."
with capture:
p.addChild(m.Child())
assert ConstructorStats.detail_reg_inst() == n_inst + 1
assert (capture == """
Allocating child.
Releasing child.
""")
with capture:
del p
assert ConstructorStats.detail_reg_inst() == n_inst
assert capture == "Releasing parent."
with capture:
p = m.Parent()
assert capture == "Allocating parent."
with capture:
p.addChildKeepAlive(m.Child())
assert ConstructorStats.detail_reg_inst() == n_inst + 2
assert capture == "Allocating child."
with capture:
del p
assert ConstructorStats.detail_reg_inst() == n_inst
assert (capture == """
Releasing parent.
Releasing child.
""")
p = m.Parent()
c = m.Child()
assert ConstructorStats.detail_reg_inst() == n_inst + 2
m.free_function(p, c)
del c
assert ConstructorStats.detail_reg_inst() == n_inst + 2
del p
assert ConstructorStats.detail_reg_inst() == n_inst
with pytest.raises(RuntimeError) as excinfo:
m.invalid_arg_index()
assert str(excinfo.value) == "Could not activate keep_alive!"
def test_keep_alive_return_value(capture):
n_inst = ConstructorStats.detail_reg_inst()
with capture:
p = m.Parent()
assert capture == "Allocating parent."
with capture:
p.returnChild()
assert ConstructorStats.detail_reg_inst() == n_inst + 1
assert (capture == """
Allocating child.
Releasing child.
""")
with capture:
del p
assert ConstructorStats.detail_reg_inst() == n_inst
assert capture == "Releasing parent."
with capture:
p = m.Parent()
assert capture == "Allocating parent."
with capture:
p.returnChildKeepAlive()
assert ConstructorStats.detail_reg_inst() == n_inst + 2
assert capture == "Allocating child."
with capture:
del p
assert ConstructorStats.detail_reg_inst() == n_inst
assert (capture == """
Releasing parent.
Releasing child.
""")
p = m.Parent()
assert ConstructorStats.detail_reg_inst() == n_inst + 1
with capture:
m.Parent.staticFunction(p)
assert ConstructorStats.detail_reg_inst() == n_inst + 2
assert capture == "Allocating child."
with capture:
del p
assert ConstructorStats.detail_reg_inst() == n_inst
assert (capture == """
Releasing parent.
Releasing child.
""")
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
@pytest.mark.xfail("env.PYPY", reason="_PyObject_GetDictPtr is unimplemented")
def test_alive_gc(capture):
n_inst = ConstructorStats.detail_reg_inst()
p = m.ParentGC()
p.addChildKeepAlive(m.Child())
assert ConstructorStats.detail_reg_inst() == n_inst + 2
lst = [p]
lst.append(lst) # creates a circular reference
with capture:
del p, lst
assert ConstructorStats.detail_reg_inst() == n_inst
assert (capture == """
Releasing parent.
Releasing child.
""")
def test_alive_gc_derived(capture):
class Derived(m.Parent):
pass
n_inst = ConstructorStats.detail_reg_inst()
p = Derived()
p.addChildKeepAlive(m.Child())
assert ConstructorStats.detail_reg_inst() == n_inst + 2
lst = [p]
lst.append(lst) # creates a circular reference
with capture:
del p, lst
assert ConstructorStats.detail_reg_inst() == n_inst
assert (capture == """
Releasing parent.
Releasing child.
""")
def test_alive_gc_multi_derived(capture):
class Derived(m.Parent, m.Child):
def __init__(self):
m.Parent.__init__(self)
m.Child.__init__(self)
n_inst = ConstructorStats.detail_reg_inst()
p = Derived()
p.addChildKeepAlive(m.Child())
# +3 rather than +2 because Derived corresponds to two registered instances
assert ConstructorStats.detail_reg_inst() == n_inst + 3
lst = [p]
lst.append(lst) # creates a circular reference
with capture:
del p, lst
assert ConstructorStats.detail_reg_inst() == n_inst
assert (capture == """
Releasing parent.
Releasing child.
Releasing child.
""")
def test_return_none(capture):
n_inst = ConstructorStats.detail_reg_inst()
with capture:
p = m.Parent()
assert capture == "Allocating parent."
with capture:
p.returnNullChildKeepAliveChild()
assert ConstructorStats.detail_reg_inst() == n_inst + 1
assert capture == ""
with capture:
del p
assert ConstructorStats.detail_reg_inst() == n_inst
assert capture == "Releasing parent."
with capture:
p = m.Parent()
assert capture == "Allocating parent."
with capture:
p.returnNullChildKeepAliveParent()
assert ConstructorStats.detail_reg_inst() == n_inst + 1
assert capture == ""
with capture:
del p
assert ConstructorStats.detail_reg_inst() == n_inst
assert capture == "Releasing parent."
def test_keep_alive_constructor(capture):
n_inst = ConstructorStats.detail_reg_inst()
with capture:
p = m.Parent(m.Child())
assert ConstructorStats.detail_reg_inst() == n_inst + 2
assert (capture == """
Allocating child.
Allocating parent.
""")
with capture:
del p
assert ConstructorStats.detail_reg_inst() == n_inst
assert (capture == """
Releasing parent.
Releasing child.
""")
def test_call_guard():
assert m.unguarded_call() == "unguarded"
assert m.guarded_call() == "guarded"
assert m.multiple_guards_correct_order() == "guarded & guarded"
assert m.multiple_guards_wrong_order() == "unguarded & guarded"
if hasattr(m, "with_gil"):
assert m.with_gil() == "GIL held"
assert m.without_gil() == "GIL released"

View File

@@ -0,0 +1,243 @@
/*
tests/test_callbacks.cpp -- callbacks
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/functional.h>
#include "constructor_stats.h"
#include "pybind11_tests.h"
#include <thread>
int dummy_function(int i) { return i + 1; }
TEST_SUBMODULE(callbacks, m) {
// test_callbacks, test_function_signatures
m.def("test_callback1", [](const py::object &func) { return func(); });
m.def("test_callback2", [](const py::object &func) { return func("Hello", 'x', true, 5); });
m.def("test_callback3", [](const std::function<int(int)> &func) {
return "func(43) = " + std::to_string(func(43));
});
m.def("test_callback4",
[]() -> std::function<int(int)> { return [](int i) { return i + 1; }; });
m.def("test_callback5",
[]() { return py::cpp_function([](int i) { return i + 1; }, py::arg("number")); });
// test_keyword_args_and_generalized_unpacking
m.def("test_tuple_unpacking", [](const py::function &f) {
auto t1 = py::make_tuple(2, 3);
auto t2 = py::make_tuple(5, 6);
return f("positional", 1, *t1, 4, *t2);
});
m.def("test_dict_unpacking", [](const py::function &f) {
auto d1 = py::dict("key"_a = "value", "a"_a = 1);
auto d2 = py::dict();
auto d3 = py::dict("b"_a = 2);
return f("positional", 1, **d1, **d2, **d3);
});
m.def("test_keyword_args", [](const py::function &f) { return f("x"_a = 10, "y"_a = 20); });
m.def("test_unpacking_and_keywords1", [](const py::function &f) {
auto args = py::make_tuple(2);
auto kwargs = py::dict("d"_a = 4);
return f(1, *args, "c"_a = 3, **kwargs);
});
m.def("test_unpacking_and_keywords2", [](const py::function &f) {
auto kwargs1 = py::dict("a"_a = 1);
auto kwargs2 = py::dict("c"_a = 3, "d"_a = 4);
return f("positional",
*py::make_tuple(1),
2,
*py::make_tuple(3, 4),
5,
"key"_a = "value",
**kwargs1,
"b"_a = 2,
**kwargs2,
"e"_a = 5);
});
m.def("test_unpacking_error1", [](const py::function &f) {
auto kwargs = py::dict("x"_a = 3);
return f("x"_a = 1, "y"_a = 2, **kwargs); // duplicate ** after keyword
});
m.def("test_unpacking_error2", [](const py::function &f) {
auto kwargs = py::dict("x"_a = 3);
return f(**kwargs, "x"_a = 1); // duplicate keyword after **
});
m.def("test_arg_conversion_error1",
[](const py::function &f) { f(234, UnregisteredType(), "kw"_a = 567); });
m.def("test_arg_conversion_error2", [](const py::function &f) {
f(234, "expected_name"_a = UnregisteredType(), "kw"_a = 567);
});
// test_lambda_closure_cleanup
struct Payload {
Payload() { print_default_created(this); }
~Payload() { print_destroyed(this); }
Payload(const Payload &) { print_copy_created(this); }
Payload(Payload &&) noexcept { print_move_created(this); }
};
// Export the payload constructor statistics for testing purposes:
m.def("payload_cstats", &ConstructorStats::get<Payload>);
m.def("test_lambda_closure_cleanup", []() -> std::function<void()> {
Payload p;
// In this situation, `Func` in the implementation of
// `cpp_function::initialize` is NOT trivially destructible.
return [p]() {
/* p should be cleaned up when the returned function is garbage collected */
(void) p;
};
});
class CppCallable {
public:
CppCallable() { track_default_created(this); }
~CppCallable() { track_destroyed(this); }
CppCallable(const CppCallable &) { track_copy_created(this); }
CppCallable(CppCallable &&) noexcept { track_move_created(this); }
void operator()() {}
};
m.def("test_cpp_callable_cleanup", []() {
// Related issue: https://github.com/pybind/pybind11/issues/3228
// Related PR: https://github.com/pybind/pybind11/pull/3229
py::list alive_counts;
ConstructorStats &stat = ConstructorStats::get<CppCallable>();
alive_counts.append(stat.alive());
{
CppCallable cpp_callable;
alive_counts.append(stat.alive());
{
// In this situation, `Func` in the implementation of
// `cpp_function::initialize` IS trivially destructible,
// only `capture` is not.
py::cpp_function py_func(cpp_callable);
py::detail::silence_unused_warnings(py_func);
alive_counts.append(stat.alive());
}
alive_counts.append(stat.alive());
{
py::cpp_function py_func(std::move(cpp_callable));
py::detail::silence_unused_warnings(py_func);
alive_counts.append(stat.alive());
}
alive_counts.append(stat.alive());
}
alive_counts.append(stat.alive());
return alive_counts;
});
// test_cpp_function_roundtrip
/* Test if passing a function pointer from C++ -> Python -> C++ yields the original pointer */
m.def("dummy_function", &dummy_function);
m.def("dummy_function_overloaded", [](int i, int j) { return i + j; });
m.def("dummy_function_overloaded", &dummy_function);
m.def("dummy_function2", [](int i, int j) { return i + j; });
m.def(
"roundtrip",
[](std::function<int(int)> f, bool expect_none = false) {
if (expect_none && f) {
throw std::runtime_error("Expected None to be converted to empty std::function");
}
return f;
},
py::arg("f"),
py::arg("expect_none") = false);
m.def("test_dummy_function", [](const std::function<int(int)> &f) -> std::string {
using fn_type = int (*)(int);
const auto *result = f.target<fn_type>();
if (!result) {
auto r = f(1);
return "can't convert to function pointer: eval(1) = " + std::to_string(r);
}
if (*result == dummy_function) {
auto r = (*result)(1);
return "matches dummy_function: eval(1) = " + std::to_string(r);
}
return "argument does NOT match dummy_function. This should never happen!";
});
class AbstractBase {
public:
// [workaround(intel)] = default does not work here
// Defaulting this destructor results in linking errors with the Intel compiler
// (in Debug builds only, tested with icpc (ICC) 2021.1 Beta 20200827)
virtual ~AbstractBase() {} // NOLINT(modernize-use-equals-default)
virtual unsigned int func() = 0;
};
m.def("func_accepting_func_accepting_base",
[](const std::function<double(AbstractBase &)> &) {});
struct MovableObject {
bool valid = true;
MovableObject() = default;
MovableObject(const MovableObject &) = default;
MovableObject &operator=(const MovableObject &) = default;
MovableObject(MovableObject &&o) noexcept : valid(o.valid) { o.valid = false; }
MovableObject &operator=(MovableObject &&o) noexcept {
valid = o.valid;
o.valid = false;
return *this;
}
};
py::class_<MovableObject>(m, "MovableObject");
// test_movable_object
m.def("callback_with_movable", [](const std::function<void(MovableObject &)> &f) {
auto x = MovableObject();
f(x); // lvalue reference shouldn't move out object
return x.valid; // must still return `true`
});
// test_bound_method_callback
struct CppBoundMethodTest {};
py::class_<CppBoundMethodTest>(m, "CppBoundMethodTest")
.def(py::init<>())
.def("triple", [](CppBoundMethodTest &, int val) { return 3 * val; });
// This checks that builtin functions can be passed as callbacks
// rather than throwing RuntimeError due to trying to extract as capsule
m.def("test_sum_builtin",
[](const std::function<double(py::iterable)> &sum_builtin, const py::iterable &i) {
return sum_builtin(i);
});
// test async Python callbacks
using callback_f = std::function<void(int)>;
m.def("test_async_callback", [](const callback_f &f, const py::list &work) {
// make detached thread that calls `f` with piece of work after a little delay
auto start_f = [f](int j) {
auto invoke_f = [f, j] {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
f(j);
};
auto t = std::thread(std::move(invoke_f));
t.detach();
};
// spawn worker threads
for (auto i : work) {
start_f(py::cast<int>(i));
}
});
m.def("callback_num_times", [](const py::function &f, std::size_t num) {
for (std::size_t i = 0; i < num; i++) {
f();
}
});
}

View File

@@ -0,0 +1,200 @@
import time
from threading import Thread
import pytest
import env # noqa: F401
from pybind11_tests import callbacks as m
def test_callbacks():
from functools import partial
def func1():
return "func1"
def func2(a, b, c, d):
return "func2", a, b, c, d
def func3(a):
return f"func3({a})"
assert m.test_callback1(func1) == "func1"
assert m.test_callback2(func2) == ("func2", "Hello", "x", True, 5)
assert m.test_callback1(partial(func2, 1, 2, 3, 4)) == ("func2", 1, 2, 3, 4
)
assert m.test_callback1(partial(func3, "partial")) == "func3(partial)"
assert m.test_callback3(lambda i: i + 1) == "func(43) = 44"
f = m.test_callback4()
assert f(43) == 44
f = m.test_callback5()
assert f(number=43) == 44
def test_bound_method_callback():
# Bound Python method:
class MyClass:
def double(self, val):
return 2 * val
z = MyClass()
assert m.test_callback3(z.double) == "func(43) = 86"
z = m.CppBoundMethodTest()
assert m.test_callback3(z.triple) == "func(43) = 129"
def test_keyword_args_and_generalized_unpacking():
def f(*args, **kwargs):
return args, kwargs
assert m.test_tuple_unpacking(f) == (("positional", 1, 2, 3, 4, 5, 6), {})
assert m.test_dict_unpacking(f) == (
("positional", 1),
{
"key": "value",
"a": 1,
"b": 2
}, )
assert m.test_keyword_args(f) == ((), {"x": 10, "y": 20})
assert m.test_unpacking_and_keywords1(f) == ((1, 2), {"c": 3, "d": 4})
assert m.test_unpacking_and_keywords2(f) == (
("positional", 1, 2, 3, 4, 5),
{
"key": "value",
"a": 1,
"b": 2,
"c": 3,
"d": 4,
"e": 5
}, )
with pytest.raises(TypeError) as excinfo:
m.test_unpacking_error1(f)
assert "Got multiple values for keyword argument" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.test_unpacking_error2(f)
assert "Got multiple values for keyword argument" in str(excinfo.value)
with pytest.raises(RuntimeError) as excinfo:
m.test_arg_conversion_error1(f)
assert "Unable to convert call argument" in str(excinfo.value)
with pytest.raises(RuntimeError) as excinfo:
m.test_arg_conversion_error2(f)
assert "Unable to convert call argument" in str(excinfo.value)
def test_lambda_closure_cleanup():
m.test_lambda_closure_cleanup()
cstats = m.payload_cstats()
assert cstats.alive() == 0
assert cstats.copy_constructions == 1
assert cstats.move_constructions >= 1
def test_cpp_callable_cleanup():
alive_counts = m.test_cpp_callable_cleanup()
assert alive_counts == [0, 1, 2, 1, 2, 1, 0]
def test_cpp_function_roundtrip():
"""Test if passing a function pointer from C++ -> Python -> C++ yields the original pointer"""
assert (m.test_dummy_function(m.dummy_function) ==
"matches dummy_function: eval(1) = 2")
assert (m.test_dummy_function(m.roundtrip(m.dummy_function)) ==
"matches dummy_function: eval(1) = 2")
assert (m.test_dummy_function(m.dummy_function_overloaded) ==
"matches dummy_function: eval(1) = 2")
assert m.roundtrip(None, expect_none=True) is None
assert (m.test_dummy_function(lambda x: x + 2) ==
"can't convert to function pointer: eval(1) = 3")
with pytest.raises(TypeError) as excinfo:
m.test_dummy_function(m.dummy_function2)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.test_dummy_function(lambda x, y: x + y)
assert any(
s in str(excinfo.value)
for s in ("missing 1 required positional argument",
"takes exactly 2 arguments"))
def test_function_signatures(doc):
assert doc(m.test_callback3
) == "test_callback3(arg0: Callable[[int], int]) -> str"
assert doc(m.test_callback4) == "test_callback4() -> Callable[[int], int]"
def test_movable_object():
assert m.callback_with_movable(lambda _: None) is True
@pytest.mark.skipif(
"env.PYPY",
reason="PyPy segfaults on here. See discussion on #1413.", )
def test_python_builtins():
"""Test if python builtins like sum() can be used as callbacks"""
assert m.test_sum_builtin(sum, [1, 2, 3]) == 6
assert m.test_sum_builtin(sum, []) == 0
def test_async_callbacks():
# serves as state for async callback
class Item:
def __init__(self, value):
self.value = value
res = []
# generate stateful lambda that will store result in `res`
def gen_f():
s = Item(3)
return lambda j: res.append(s.value + j)
# do some work async
work = [1, 2, 3, 4]
m.test_async_callback(gen_f(), work)
# wait until work is done
from time import sleep
sleep(0.5)
assert sum(res) == sum(x + 3 for x in work)
def test_async_async_callbacks():
t = Thread(target=test_async_callbacks)
t.start()
t.join()
def test_callback_num_times():
# Super-simple micro-benchmarking related to PR #2919.
# Example runtimes (Intel Xeon 2.2GHz, fully optimized):
# num_millions 1, repeats 2: 0.1 secs
# num_millions 20, repeats 10: 11.5 secs
one_million = 1000000
num_millions = 1 # Try 20 for actual micro-benchmarking.
repeats = 2 # Try 10.
rates = []
for rep in range(repeats):
t0 = time.time()
m.callback_num_times(lambda: None, num_millions * one_million)
td = time.time() - t0
rate = num_millions / td if td else 0
rates.append(rate)
if not rep:
print()
print(
f"callback_num_times: {num_millions:d} million / {td:.3f} seconds = {rate:.3f} million / second"
)
if len(rates) > 1:
print("Min Mean Max")
print(
f"{min(rates):6.3f} {sum(rates) / len(rates):6.3f} {max(rates):6.3f}"
)

View File

@@ -0,0 +1,81 @@
/*
tests/test_chrono.cpp -- test conversions to/from std::chrono types
Copyright (c) 2016 Trent Houliston <trent@houliston.me> and
Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/chrono.h>
#include "pybind11_tests.h"
#include <chrono>
struct different_resolutions {
using time_point_h = std::chrono::time_point<std::chrono::system_clock, std::chrono::hours>;
using time_point_m = std::chrono::time_point<std::chrono::system_clock, std::chrono::minutes>;
using time_point_s = std::chrono::time_point<std::chrono::system_clock, std::chrono::seconds>;
using time_point_ms
= std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>;
using time_point_us
= std::chrono::time_point<std::chrono::system_clock, std::chrono::microseconds>;
time_point_h timestamp_h;
time_point_m timestamp_m;
time_point_s timestamp_s;
time_point_ms timestamp_ms;
time_point_us timestamp_us;
};
TEST_SUBMODULE(chrono, m) {
using system_time = std::chrono::system_clock::time_point;
using steady_time = std::chrono::steady_clock::time_point;
using timespan = std::chrono::duration<int64_t, std::nano>;
using timestamp = std::chrono::time_point<std::chrono::system_clock, timespan>;
// test_chrono_system_clock
// Return the current time off the wall clock
m.def("test_chrono1", []() { return std::chrono::system_clock::now(); });
// test_chrono_system_clock_roundtrip
// Round trip the passed in system clock time
m.def("test_chrono2", [](system_time t) { return t; });
// test_chrono_duration_roundtrip
// Round trip the passed in duration
m.def("test_chrono3", [](std::chrono::system_clock::duration d) { return d; });
// test_chrono_duration_subtraction_equivalence
// Difference between two passed in time_points
m.def("test_chrono4", [](system_time a, system_time b) { return a - b; });
// test_chrono_steady_clock
// Return the current time off the steady_clock
m.def("test_chrono5", []() { return std::chrono::steady_clock::now(); });
// test_chrono_steady_clock_roundtrip
// Round trip a steady clock timepoint
m.def("test_chrono6", [](steady_time t) { return t; });
// test_floating_point_duration
// Roundtrip a duration in microseconds from a float argument
m.def("test_chrono7", [](std::chrono::microseconds t) { return t; });
// Float durations (issue #719)
m.def("test_chrono_float_diff",
[](std::chrono::duration<float> a, std::chrono::duration<float> b) { return a - b; });
m.def("test_nano_timepoint",
[](timestamp start, timespan delta) -> timestamp { return start + delta; });
// Test different resolutions
py::class_<different_resolutions>(m, "different_resolutions")
.def(py::init<>())
.def_readwrite("timestamp_h", &different_resolutions::timestamp_h)
.def_readwrite("timestamp_m", &different_resolutions::timestamp_m)
.def_readwrite("timestamp_s", &different_resolutions::timestamp_s)
.def_readwrite("timestamp_ms", &different_resolutions::timestamp_ms)
.def_readwrite("timestamp_us", &different_resolutions::timestamp_us);
}

View File

@@ -0,0 +1,209 @@
import datetime
import pytest
import env # noqa: F401
from pybind11_tests import chrono as m
def test_chrono_system_clock():
# Get the time from both c++ and datetime
date0 = datetime.datetime.today()
date1 = m.test_chrono1()
date2 = datetime.datetime.today()
# The returned value should be a datetime
assert isinstance(date1, datetime.datetime)
# The numbers should vary by a very small amount (time it took to execute)
diff_python = abs(date2 - date0)
diff = abs(date1 - date2)
# There should never be a days difference
assert diff.days == 0
# Since datetime.datetime.today() calls time.time(), and on some platforms
# that has 1 second accuracy, we compare this way
assert diff.seconds <= diff_python.seconds
def test_chrono_system_clock_roundtrip():
date1 = datetime.datetime.today()
# Roundtrip the time
date2 = m.test_chrono2(date1)
# The returned value should be a datetime
assert isinstance(date2, datetime.datetime)
# They should be identical (no information lost on roundtrip)
diff = abs(date1 - date2)
assert diff == datetime.timedelta(0)
def test_chrono_system_clock_roundtrip_date():
date1 = datetime.date.today()
# Roundtrip the time
datetime2 = m.test_chrono2(date1)
date2 = datetime2.date()
time2 = datetime2.time()
# The returned value should be a datetime
assert isinstance(datetime2, datetime.datetime)
assert isinstance(date2, datetime.date)
assert isinstance(time2, datetime.time)
# They should be identical (no information lost on roundtrip)
diff = abs(date1 - date2)
assert diff.days == 0
assert diff.seconds == 0
assert diff.microseconds == 0
# Year, Month & Day should be the same after the round trip
assert date1 == date2
# There should be no time information
assert time2.hour == 0
assert time2.minute == 0
assert time2.second == 0
assert time2.microsecond == 0
SKIP_TZ_ENV_ON_WIN = pytest.mark.skipif(
"env.WIN", reason="TZ environment variable only supported on POSIX")
@pytest.mark.parametrize(
"time1",
[
datetime.datetime.today().time(),
datetime.time(0, 0, 0),
datetime.time(0, 0, 0, 1),
datetime.time(0, 28, 45, 109827),
datetime.time(0, 59, 59, 999999),
datetime.time(1, 0, 0),
datetime.time(5, 59, 59, 0),
datetime.time(5, 59, 59, 1),
], )
@pytest.mark.parametrize(
"tz",
[
None,
pytest.param(
"Europe/Brussels", marks=SKIP_TZ_ENV_ON_WIN),
pytest.param(
"Asia/Pyongyang", marks=SKIP_TZ_ENV_ON_WIN),
pytest.param(
"America/New_York", marks=SKIP_TZ_ENV_ON_WIN),
], )
def test_chrono_system_clock_roundtrip_time(time1, tz, monkeypatch):
if tz is not None:
monkeypatch.setenv("TZ", f"/usr/share/zoneinfo/{tz}")
# Roundtrip the time
datetime2 = m.test_chrono2(time1)
date2 = datetime2.date()
time2 = datetime2.time()
# The returned value should be a datetime
assert isinstance(datetime2, datetime.datetime)
assert isinstance(date2, datetime.date)
assert isinstance(time2, datetime.time)
# Hour, Minute, Second & Microsecond should be the same after the round trip
assert time1 == time2
# There should be no date information (i.e. date = python base date)
assert date2.year == 1970
assert date2.month == 1
assert date2.day == 1
def test_chrono_duration_roundtrip():
# Get the difference between two times (a timedelta)
date1 = datetime.datetime.today()
date2 = datetime.datetime.today()
diff = date2 - date1
# Make sure this is a timedelta
assert isinstance(diff, datetime.timedelta)
cpp_diff = m.test_chrono3(diff)
assert cpp_diff == diff
# Negative timedelta roundtrip
diff = datetime.timedelta(microseconds=-1)
cpp_diff = m.test_chrono3(diff)
assert cpp_diff == diff
def test_chrono_duration_subtraction_equivalence():
date1 = datetime.datetime.today()
date2 = datetime.datetime.today()
diff = date2 - date1
cpp_diff = m.test_chrono4(date2, date1)
assert cpp_diff == diff
def test_chrono_duration_subtraction_equivalence_date():
date1 = datetime.date.today()
date2 = datetime.date.today()
diff = date2 - date1
cpp_diff = m.test_chrono4(date2, date1)
assert cpp_diff == diff
def test_chrono_steady_clock():
time1 = m.test_chrono5()
assert isinstance(time1, datetime.timedelta)
def test_chrono_steady_clock_roundtrip():
time1 = datetime.timedelta(days=10, seconds=10, microseconds=100)
time2 = m.test_chrono6(time1)
assert isinstance(time2, datetime.timedelta)
# They should be identical (no information lost on roundtrip)
assert time1 == time2
def test_floating_point_duration():
# Test using a floating point number in seconds
time = m.test_chrono7(35.525123)
assert isinstance(time, datetime.timedelta)
assert time.seconds == 35
assert 525122 <= time.microseconds <= 525123
diff = m.test_chrono_float_diff(43.789012, 1.123456)
assert diff.seconds == 42
assert 665556 <= diff.microseconds <= 665557
def test_nano_timepoint():
time = datetime.datetime.now()
time1 = m.test_nano_timepoint(time, datetime.timedelta(seconds=60))
assert time1 == time + datetime.timedelta(seconds=60)
def test_chrono_different_resolutions():
resolutions = m.different_resolutions()
time = datetime.datetime.now()
resolutions.timestamp_h = time
resolutions.timestamp_m = time
resolutions.timestamp_s = time
resolutions.timestamp_ms = time
resolutions.timestamp_us = time

View File

@@ -0,0 +1,619 @@
/*
tests/test_class.cpp -- test py::class_ definitions and basic functionality
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#if defined(__INTEL_COMPILER) && __cplusplus >= 201703L
// Intel compiler requires a separate header file to support aligned new operators
// and does not set the __cpp_aligned_new feature macro.
// This header needs to be included before pybind11.
# include <aligned_new>
#endif
#include <pybind11/stl.h>
#include "constructor_stats.h"
#include "local_bindings.h"
#include "pybind11_tests.h"
#include <utility>
#if defined(_MSC_VER)
# pragma warning(disable : 4324)
// warning C4324: structure was padded due to alignment specifier
#endif
// test_brace_initialization
struct NoBraceInitialization {
explicit NoBraceInitialization(std::vector<int> v) : vec{std::move(v)} {}
template <typename T>
NoBraceInitialization(std::initializer_list<T> l) : vec(l) {}
std::vector<int> vec;
};
TEST_SUBMODULE(class_, m) {
// test_instance
struct NoConstructor {
NoConstructor() = default;
NoConstructor(const NoConstructor &) = default;
NoConstructor(NoConstructor &&) = default;
static NoConstructor *new_instance() {
auto *ptr = new NoConstructor();
print_created(ptr, "via new_instance");
return ptr;
}
~NoConstructor() { print_destroyed(this); }
};
struct NoConstructorNew {
NoConstructorNew() = default;
NoConstructorNew(const NoConstructorNew &) = default;
NoConstructorNew(NoConstructorNew &&) = default;
static NoConstructorNew *new_instance() {
auto *ptr = new NoConstructorNew();
print_created(ptr, "via new_instance");
return ptr;
}
~NoConstructorNew() { print_destroyed(this); }
};
py::class_<NoConstructor>(m, "NoConstructor")
.def_static("new_instance", &NoConstructor::new_instance, "Return an instance");
py::class_<NoConstructorNew>(m, "NoConstructorNew")
.def(py::init([](const NoConstructorNew &self) { return self; })) // Need a NOOP __init__
.def_static("__new__",
[](const py::object &) { return NoConstructorNew::new_instance(); });
// test_inheritance
class Pet {
public:
Pet(const std::string &name, const std::string &species)
: m_name(name), m_species(species) {}
std::string name() const { return m_name; }
std::string species() const { return m_species; }
private:
std::string m_name;
std::string m_species;
};
class Dog : public Pet {
public:
explicit Dog(const std::string &name) : Pet(name, "dog") {}
std::string bark() const { return "Woof!"; }
};
class Rabbit : public Pet {
public:
explicit Rabbit(const std::string &name) : Pet(name, "parrot") {}
};
class Hamster : public Pet {
public:
explicit Hamster(const std::string &name) : Pet(name, "rodent") {}
};
class Chimera : public Pet {
Chimera() : Pet("Kimmy", "chimera") {}
};
py::class_<Pet> pet_class(m, "Pet");
pet_class.def(py::init<std::string, std::string>())
.def("name", &Pet::name)
.def("species", &Pet::species);
/* One way of declaring a subclass relationship: reference parent's class_ object */
py::class_<Dog>(m, "Dog", pet_class).def(py::init<std::string>());
/* Another way of declaring a subclass relationship: reference parent's C++ type */
py::class_<Rabbit, Pet>(m, "Rabbit").def(py::init<std::string>());
/* And another: list parent in class template arguments */
py::class_<Hamster, Pet>(m, "Hamster").def(py::init<std::string>());
/* Constructors are not inherited by default */
py::class_<Chimera, Pet>(m, "Chimera");
m.def("pet_name_species",
[](const Pet &pet) { return pet.name() + " is a " + pet.species(); });
m.def("dog_bark", [](const Dog &dog) { return dog.bark(); });
// test_automatic_upcasting
struct BaseClass {
BaseClass() = default;
BaseClass(const BaseClass &) = default;
BaseClass(BaseClass &&) = default;
virtual ~BaseClass() = default;
};
struct DerivedClass1 : BaseClass {};
struct DerivedClass2 : BaseClass {};
py::class_<BaseClass>(m, "BaseClass").def(py::init<>());
py::class_<DerivedClass1>(m, "DerivedClass1").def(py::init<>());
py::class_<DerivedClass2>(m, "DerivedClass2").def(py::init<>());
m.def("return_class_1", []() -> BaseClass * { return new DerivedClass1(); });
m.def("return_class_2", []() -> BaseClass * { return new DerivedClass2(); });
m.def("return_class_n", [](int n) -> BaseClass * {
if (n == 1) {
return new DerivedClass1();
}
if (n == 2) {
return new DerivedClass2();
}
return new BaseClass();
});
m.def("return_none", []() -> BaseClass * { return nullptr; });
// test_isinstance
m.def("check_instances", [](const py::list &l) {
return py::make_tuple(py::isinstance<py::tuple>(l[0]),
py::isinstance<py::dict>(l[1]),
py::isinstance<Pet>(l[2]),
py::isinstance<Pet>(l[3]),
py::isinstance<Dog>(l[4]),
py::isinstance<Rabbit>(l[5]),
py::isinstance<UnregisteredType>(l[6]));
});
struct Invalid {};
// test_type
m.def("check_type", [](int category) {
// Currently not supported (via a fail at compile time)
// See https://github.com/pybind/pybind11/issues/2486
// if (category == 2)
// return py::type::of<int>();
if (category == 1) {
return py::type::of<DerivedClass1>();
}
return py::type::of<Invalid>();
});
m.def("get_type_of", [](py::object ob) { return py::type::of(std::move(ob)); });
m.def("get_type_classic", [](py::handle h) { return h.get_type(); });
m.def("as_type", [](const py::object &ob) { return py::type(ob); });
// test_mismatched_holder
struct MismatchBase1 {};
struct MismatchDerived1 : MismatchBase1 {};
struct MismatchBase2 {};
struct MismatchDerived2 : MismatchBase2 {};
m.def("mismatched_holder_1", []() {
auto mod = py::module_::import("__main__");
py::class_<MismatchBase1, std::shared_ptr<MismatchBase1>>(mod, "MismatchBase1");
py::class_<MismatchDerived1, MismatchBase1>(mod, "MismatchDerived1");
});
m.def("mismatched_holder_2", []() {
auto mod = py::module_::import("__main__");
py::class_<MismatchBase2>(mod, "MismatchBase2");
py::class_<MismatchDerived2, std::shared_ptr<MismatchDerived2>, MismatchBase2>(
mod, "MismatchDerived2");
});
// test_override_static
// #511: problem with inheritance + overwritten def_static
struct MyBase {
static std::unique_ptr<MyBase> make() { return std::unique_ptr<MyBase>(new MyBase()); }
};
struct MyDerived : MyBase {
static std::unique_ptr<MyDerived> make() {
return std::unique_ptr<MyDerived>(new MyDerived());
}
};
py::class_<MyBase>(m, "MyBase").def_static("make", &MyBase::make);
py::class_<MyDerived, MyBase>(m, "MyDerived")
.def_static("make", &MyDerived::make)
.def_static("make2", &MyDerived::make);
// test_implicit_conversion_life_support
struct ConvertibleFromUserType {
int i;
explicit ConvertibleFromUserType(UserType u) : i(u.value()) {}
};
py::class_<ConvertibleFromUserType>(m, "AcceptsUserType").def(py::init<UserType>());
py::implicitly_convertible<UserType, ConvertibleFromUserType>();
m.def("implicitly_convert_argument", [](const ConvertibleFromUserType &r) { return r.i; });
m.def("implicitly_convert_variable", [](const py::object &o) {
// `o` is `UserType` and `r` is a reference to a temporary created by implicit
// conversion. This is valid when called inside a bound function because the temp
// object is attached to the same life support system as the arguments.
const auto &r = o.cast<const ConvertibleFromUserType &>();
return r.i;
});
m.add_object("implicitly_convert_variable_fail", [&] {
auto f = [](PyObject *, PyObject *args) -> PyObject * {
auto o = py::reinterpret_borrow<py::tuple>(args)[0];
try { // It should fail here because there is no life support.
o.cast<const ConvertibleFromUserType &>();
} catch (const py::cast_error &e) {
return py::str(e.what()).release().ptr();
}
return py::str().release().ptr();
};
auto *def = new PyMethodDef{"f", f, METH_VARARGS, nullptr};
py::capsule def_capsule(def,
[](void *ptr) { delete reinterpret_cast<PyMethodDef *>(ptr); });
return py::reinterpret_steal<py::object>(
PyCFunction_NewEx(def, def_capsule.ptr(), m.ptr()));
}());
// test_operator_new_delete
struct HasOpNewDel {
std::uint64_t i;
static void *operator new(size_t s) {
py::print("A new", s);
return ::operator new(s);
}
static void *operator new(size_t s, void *ptr) {
py::print("A placement-new", s);
return ptr;
}
static void operator delete(void *p) {
py::print("A delete");
return ::operator delete(p);
}
};
struct HasOpNewDelSize {
std::uint32_t i;
static void *operator new(size_t s) {
py::print("B new", s);
return ::operator new(s);
}
static void *operator new(size_t s, void *ptr) {
py::print("B placement-new", s);
return ptr;
}
static void operator delete(void *p, size_t s) {
py::print("B delete", s);
return ::operator delete(p);
}
};
struct AliasedHasOpNewDelSize {
std::uint64_t i;
static void *operator new(size_t s) {
py::print("C new", s);
return ::operator new(s);
}
static void *operator new(size_t s, void *ptr) {
py::print("C placement-new", s);
return ptr;
}
static void operator delete(void *p, size_t s) {
py::print("C delete", s);
return ::operator delete(p);
}
virtual ~AliasedHasOpNewDelSize() = default;
AliasedHasOpNewDelSize() = default;
AliasedHasOpNewDelSize(const AliasedHasOpNewDelSize &) = delete;
};
struct PyAliasedHasOpNewDelSize : AliasedHasOpNewDelSize {
PyAliasedHasOpNewDelSize() = default;
explicit PyAliasedHasOpNewDelSize(int) {}
std::uint64_t j;
};
struct HasOpNewDelBoth {
std::uint32_t i[8];
static void *operator new(size_t s) {
py::print("D new", s);
return ::operator new(s);
}
static void *operator new(size_t s, void *ptr) {
py::print("D placement-new", s);
return ptr;
}
static void operator delete(void *p) {
py::print("D delete");
return ::operator delete(p);
}
static void operator delete(void *p, size_t s) {
py::print("D wrong delete", s);
return ::operator delete(p);
}
};
py::class_<HasOpNewDel>(m, "HasOpNewDel").def(py::init<>());
py::class_<HasOpNewDelSize>(m, "HasOpNewDelSize").def(py::init<>());
py::class_<HasOpNewDelBoth>(m, "HasOpNewDelBoth").def(py::init<>());
py::class_<AliasedHasOpNewDelSize, PyAliasedHasOpNewDelSize> aliased(m,
"AliasedHasOpNewDelSize");
aliased.def(py::init<>());
aliased.attr("size_noalias") = py::int_(sizeof(AliasedHasOpNewDelSize));
aliased.attr("size_alias") = py::int_(sizeof(PyAliasedHasOpNewDelSize));
// This test is actually part of test_local_bindings (test_duplicate_local), but we need a
// definition in a different compilation unit within the same module:
bind_local<LocalExternal, 17>(m, "LocalExternal", py::module_local());
// test_bind_protected_functions
class ProtectedA {
protected:
int foo() const { return value; }
private:
int value = 42;
};
class PublicistA : public ProtectedA {
public:
using ProtectedA::foo;
};
py::class_<ProtectedA>(m, "ProtectedA").def(py::init<>()).def("foo", &PublicistA::foo);
class ProtectedB {
public:
virtual ~ProtectedB() = default;
ProtectedB() = default;
ProtectedB(const ProtectedB &) = delete;
protected:
virtual int foo() const { return value; }
private:
int value = 42;
};
class TrampolineB : public ProtectedB {
public:
int foo() const override { PYBIND11_OVERRIDE(int, ProtectedB, foo, ); }
};
class PublicistB : public ProtectedB {
public:
// [workaround(intel)] = default does not work here
// Removing or defaulting this destructor results in linking errors with the Intel compiler
// (in Debug builds only, tested with icpc (ICC) 2021.1 Beta 20200827)
~PublicistB() override{}; // NOLINT(modernize-use-equals-default)
using ProtectedB::foo;
};
py::class_<ProtectedB, TrampolineB>(m, "ProtectedB")
.def(py::init<>())
.def("foo", &PublicistB::foo);
// test_brace_initialization
struct BraceInitialization {
int field1;
std::string field2;
};
py::class_<BraceInitialization>(m, "BraceInitialization")
.def(py::init<int, const std::string &>())
.def_readwrite("field1", &BraceInitialization::field1)
.def_readwrite("field2", &BraceInitialization::field2);
// We *don't* want to construct using braces when the given constructor argument maps to a
// constructor, because brace initialization could go to the wrong place (in particular when
// there is also an `initializer_list<T>`-accept constructor):
py::class_<NoBraceInitialization>(m, "NoBraceInitialization")
.def(py::init<std::vector<int>>())
.def_readonly("vec", &NoBraceInitialization::vec);
// test_reentrant_implicit_conversion_failure
// #1035: issue with runaway reentrant implicit conversion
struct BogusImplicitConversion {
BogusImplicitConversion(const BogusImplicitConversion &) = default;
};
py::class_<BogusImplicitConversion>(m, "BogusImplicitConversion")
.def(py::init<const BogusImplicitConversion &>());
py::implicitly_convertible<int, BogusImplicitConversion>();
// test_qualname
// #1166: nested class docstring doesn't show nested name
// Also related: tests that __qualname__ is set properly
struct NestBase {};
struct Nested {};
py::class_<NestBase> base(m, "NestBase");
base.def(py::init<>());
py::class_<Nested>(base, "Nested")
.def(py::init<>())
.def("fn", [](Nested &, int, NestBase &, Nested &) {})
.def(
"fa", [](Nested &, int, NestBase &, Nested &) {}, "a"_a, "b"_a, "c"_a);
base.def("g", [](NestBase &, Nested &) {});
base.def("h", []() { return NestBase(); });
// test_error_after_conversion
// The second-pass path through dispatcher() previously didn't
// remember which overload was used, and would crash trying to
// generate a useful error message
struct NotRegistered {};
struct StringWrapper {
std::string str;
};
m.def("test_error_after_conversions", [](int) {});
m.def("test_error_after_conversions",
[](const StringWrapper &) -> NotRegistered { return {}; });
py::class_<StringWrapper>(m, "StringWrapper").def(py::init<std::string>());
py::implicitly_convertible<std::string, StringWrapper>();
#if defined(PYBIND11_CPP17)
struct alignas(1024) Aligned {
std::uintptr_t ptr() const { return (uintptr_t) this; }
};
py::class_<Aligned>(m, "Aligned").def(py::init<>()).def("ptr", &Aligned::ptr);
#endif
// test_final
struct IsFinal final {};
py::class_<IsFinal>(m, "IsFinal", py::is_final());
// test_non_final_final
struct IsNonFinalFinal {};
py::class_<IsNonFinalFinal>(m, "IsNonFinalFinal", py::is_final());
// test_exception_rvalue_abort
struct PyPrintDestructor {
PyPrintDestructor() = default;
~PyPrintDestructor() { py::print("Print from destructor"); }
void throw_something() { throw std::runtime_error("error"); }
};
py::class_<PyPrintDestructor>(m, "PyPrintDestructor")
.def(py::init<>())
.def("throw_something", &PyPrintDestructor::throw_something);
// test_multiple_instances_with_same_pointer
struct SamePointer {};
static SamePointer samePointer;
py::class_<SamePointer, std::unique_ptr<SamePointer, py::nodelete>>(m, "SamePointer")
.def(py::init([]() { return &samePointer; }));
struct Empty {};
py::class_<Empty>(m, "Empty").def(py::init<>());
// test_base_and_derived_nested_scope
struct BaseWithNested {
struct Nested {};
};
struct DerivedWithNested : BaseWithNested {
struct Nested {};
};
py::class_<BaseWithNested> baseWithNested_class(m, "BaseWithNested");
py::class_<DerivedWithNested, BaseWithNested> derivedWithNested_class(m, "DerivedWithNested");
py::class_<BaseWithNested::Nested>(baseWithNested_class, "Nested")
.def_static("get_name", []() { return "BaseWithNested::Nested"; });
py::class_<DerivedWithNested::Nested>(derivedWithNested_class, "Nested")
.def_static("get_name", []() { return "DerivedWithNested::Nested"; });
// test_register_duplicate_class
struct Duplicate {};
struct OtherDuplicate {};
struct DuplicateNested {};
struct OtherDuplicateNested {};
m.def("register_duplicate_class_name", [](const py::module_ &m) {
py::class_<Duplicate>(m, "Duplicate");
py::class_<OtherDuplicate>(m, "Duplicate");
});
m.def("register_duplicate_class_type", [](const py::module_ &m) {
py::class_<OtherDuplicate>(m, "OtherDuplicate");
py::class_<OtherDuplicate>(m, "YetAnotherDuplicate");
});
m.def("register_duplicate_nested_class_name", [](const py::object &gt) {
py::class_<DuplicateNested>(gt, "DuplicateNested");
py::class_<OtherDuplicateNested>(gt, "DuplicateNested");
});
m.def("register_duplicate_nested_class_type", [](const py::object &gt) {
py::class_<OtherDuplicateNested>(gt, "OtherDuplicateNested");
py::class_<OtherDuplicateNested>(gt, "YetAnotherDuplicateNested");
});
}
template <int N>
class BreaksBase {
public:
virtual ~BreaksBase() = default;
BreaksBase() = default;
BreaksBase(const BreaksBase &) = delete;
};
template <int N>
class BreaksTramp : public BreaksBase<N> {};
// These should all compile just fine:
using DoesntBreak1 = py::class_<BreaksBase<1>, std::unique_ptr<BreaksBase<1>>, BreaksTramp<1>>;
using DoesntBreak2 = py::class_<BreaksBase<2>, BreaksTramp<2>, std::unique_ptr<BreaksBase<2>>>;
using DoesntBreak3 = py::class_<BreaksBase<3>, std::unique_ptr<BreaksBase<3>>>;
using DoesntBreak4 = py::class_<BreaksBase<4>, BreaksTramp<4>>;
using DoesntBreak5 = py::class_<BreaksBase<5>>;
using DoesntBreak6 = py::class_<BreaksBase<6>, std::shared_ptr<BreaksBase<6>>, BreaksTramp<6>>;
using DoesntBreak7 = py::class_<BreaksBase<7>, BreaksTramp<7>, std::shared_ptr<BreaksBase<7>>>;
using DoesntBreak8 = py::class_<BreaksBase<8>, std::shared_ptr<BreaksBase<8>>>;
#define CHECK_BASE(N) \
static_assert(std::is_same<typename DoesntBreak##N::type, BreaksBase<(N)>>::value, \
"DoesntBreak" #N " has wrong type!")
CHECK_BASE(1);
CHECK_BASE(2);
CHECK_BASE(3);
CHECK_BASE(4);
CHECK_BASE(5);
CHECK_BASE(6);
CHECK_BASE(7);
CHECK_BASE(8);
#define CHECK_ALIAS(N) \
static_assert( \
DoesntBreak##N::has_alias \
&& std::is_same<typename DoesntBreak##N::type_alias, BreaksTramp<(N)>>::value, \
"DoesntBreak" #N " has wrong type_alias!")
#define CHECK_NOALIAS(N) \
static_assert(!DoesntBreak##N::has_alias \
&& std::is_void<typename DoesntBreak##N::type_alias>::value, \
"DoesntBreak" #N " has type alias, but shouldn't!")
CHECK_ALIAS(1);
CHECK_ALIAS(2);
CHECK_NOALIAS(3);
CHECK_ALIAS(4);
CHECK_NOALIAS(5);
CHECK_ALIAS(6);
CHECK_ALIAS(7);
CHECK_NOALIAS(8);
#define CHECK_HOLDER(N, TYPE) \
static_assert(std::is_same<typename DoesntBreak##N::holder_type, \
std::TYPE##_ptr<BreaksBase<(N)>>>::value, \
"DoesntBreak" #N " has wrong holder_type!")
CHECK_HOLDER(1, unique);
CHECK_HOLDER(2, unique);
CHECK_HOLDER(3, unique);
CHECK_HOLDER(4, unique);
CHECK_HOLDER(5, unique);
CHECK_HOLDER(6, shared);
CHECK_HOLDER(7, shared);
CHECK_HOLDER(8, shared);
// There's no nice way to test that these fail because they fail to compile; leave them here,
// though, so that they can be manually tested by uncommenting them (and seeing that compilation
// failures occurs).
// We have to actually look into the type: the typedef alone isn't enough to instantiate the type:
#define CHECK_BROKEN(N) \
static_assert(std::is_same<typename Breaks##N::type, BreaksBase<-(N)>>::value, \
"Breaks1 has wrong type!");
#ifdef PYBIND11_NEVER_DEFINED_EVER
// Two holder classes:
typedef py::
class_<BreaksBase<-1>, std::unique_ptr<BreaksBase<-1>>, std::unique_ptr<BreaksBase<-1>>>
Breaks1;
CHECK_BROKEN(1);
// Two aliases:
typedef py::class_<BreaksBase<-2>, BreaksTramp<-2>, BreaksTramp<-2>> Breaks2;
CHECK_BROKEN(2);
// Holder + 2 aliases
typedef py::
class_<BreaksBase<-3>, std::unique_ptr<BreaksBase<-3>>, BreaksTramp<-3>, BreaksTramp<-3>>
Breaks3;
CHECK_BROKEN(3);
// Alias + 2 holders
typedef py::class_<BreaksBase<-4>,
std::unique_ptr<BreaksBase<-4>>,
BreaksTramp<-4>,
std::shared_ptr<BreaksBase<-4>>>
Breaks4;
CHECK_BROKEN(4);
// Invalid option (not a subclass or holder)
typedef py::class_<BreaksBase<-5>, BreaksTramp<-4>> Breaks5;
CHECK_BROKEN(5);
// Invalid option: multiple inheritance not supported:
template <>
struct BreaksBase<-8> : BreaksBase<-6>, BreaksBase<-7> {};
typedef py::class_<BreaksBase<-8>, BreaksBase<-6>, BreaksBase<-7>> Breaks8;
CHECK_BROKEN(8);
#endif

435
third_party/pybind11/tests/test_class.py vendored Normal file
View File

@@ -0,0 +1,435 @@
import pytest
import env # noqa: F401
from pybind11_tests import ConstructorStats, UserType
from pybind11_tests import class_ as m
def test_repr():
assert "pybind11_type" in repr(type(UserType))
assert "UserType" in repr(UserType)
def test_instance(msg):
with pytest.raises(TypeError) as excinfo:
m.NoConstructor()
assert msg(
excinfo.value) == "m.class_.NoConstructor: No constructor defined!"
instance = m.NoConstructor.new_instance()
cstats = ConstructorStats.get(m.NoConstructor)
assert cstats.alive() == 1
del instance
assert cstats.alive() == 0
def test_instance_new(msg):
instance = m.NoConstructorNew() # .__new__(m.NoConstructor.__class__)
cstats = ConstructorStats.get(m.NoConstructorNew)
assert cstats.alive() == 1
del instance
assert cstats.alive() == 0
def test_type():
assert m.check_type(1) == m.DerivedClass1
with pytest.raises(RuntimeError) as execinfo:
m.check_type(0)
assert "pybind11::detail::get_type_info: unable to find type info" in str(
execinfo.value)
assert "Invalid" in str(execinfo.value)
# Currently not supported
# See https://github.com/pybind/pybind11/issues/2486
# assert m.check_type(2) == int
def test_type_of_py():
assert m.get_type_of(1) == int
assert m.get_type_of(m.DerivedClass1()) == m.DerivedClass1
assert m.get_type_of(int) == type
def test_type_of_classic():
assert m.get_type_classic(1) == int
assert m.get_type_classic(m.DerivedClass1()) == m.DerivedClass1
assert m.get_type_classic(int) == type
def test_type_of_py_nodelete():
# If the above test deleted the class, this will segfault
assert m.get_type_of(m.DerivedClass1()) == m.DerivedClass1
def test_as_type_py():
assert m.as_type(int) == int
with pytest.raises(TypeError):
assert m.as_type(1) == int
with pytest.raises(TypeError):
assert m.as_type(m.DerivedClass1()) == m.DerivedClass1
def test_docstrings(doc):
assert doc(UserType) == "A `py::class_` type for testing"
assert UserType.__name__ == "UserType"
assert UserType.__module__ == "pybind11_tests"
assert UserType.get_value.__name__ == "get_value"
assert UserType.get_value.__module__ == "pybind11_tests"
assert (doc(UserType.get_value) == """
get_value(self: m.UserType) -> int
Get value using a method
""")
assert doc(UserType.value) == "Get/set value using a property"
assert (doc(m.NoConstructor.new_instance) == """
new_instance() -> m.class_.NoConstructor
Return an instance
""")
def test_qualname(doc):
"""Tests that a properly qualified name is set in __qualname__ and that
generated docstrings properly use it and the module name"""
assert m.NestBase.__qualname__ == "NestBase"
assert m.NestBase.Nested.__qualname__ == "NestBase.Nested"
assert (doc(m.NestBase.__init__) == """
__init__(self: m.class_.NestBase) -> None
""")
assert (doc(m.NestBase.g) == """
g(self: m.class_.NestBase, arg0: m.class_.NestBase.Nested) -> None
""")
assert (doc(m.NestBase.Nested.__init__) == """
__init__(self: m.class_.NestBase.Nested) -> None
""")
assert (doc(m.NestBase.Nested.fn) == """
fn(self: m.class_.NestBase.Nested, arg0: int, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None
""")
assert (doc(m.NestBase.Nested.fa) == """
fa(self: m.class_.NestBase.Nested, a: int, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None
""")
assert m.NestBase.__module__ == "pybind11_tests.class_"
assert m.NestBase.Nested.__module__ == "pybind11_tests.class_"
def test_inheritance(msg):
roger = m.Rabbit("Rabbit")
assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot"
assert m.pet_name_species(roger) == "Rabbit is a parrot"
polly = m.Pet("Polly", "parrot")
assert polly.name() + " is a " + polly.species() == "Polly is a parrot"
assert m.pet_name_species(polly) == "Polly is a parrot"
molly = m.Dog("Molly")
assert molly.name() + " is a " + molly.species() == "Molly is a dog"
assert m.pet_name_species(molly) == "Molly is a dog"
fred = m.Hamster("Fred")
assert fred.name() + " is a " + fred.species() == "Fred is a rodent"
assert m.dog_bark(molly) == "Woof!"
with pytest.raises(TypeError) as excinfo:
m.dog_bark(polly)
assert (msg(excinfo.value) == """
dog_bark(): incompatible function arguments. The following argument types are supported:
1. (arg0: m.class_.Dog) -> str
Invoked with: <m.class_.Pet object at 0>
""")
with pytest.raises(TypeError) as excinfo:
m.Chimera("lion", "goat")
assert "No constructor defined!" in str(excinfo.value)
def test_inheritance_init(msg):
# Single base
class Python(m.Pet):
def __init__(self):
pass
with pytest.raises(TypeError) as exc_info:
Python()
expected = "m.class_.Pet.__init__() must be called when overriding __init__"
assert msg(exc_info.value) == expected
# Multiple bases
class RabbitHamster(m.Rabbit, m.Hamster):
def __init__(self):
m.Rabbit.__init__(self, "RabbitHamster")
with pytest.raises(TypeError) as exc_info:
RabbitHamster()
expected = "m.class_.Hamster.__init__() must be called when overriding __init__"
assert msg(exc_info.value) == expected
def test_automatic_upcasting():
assert type(m.return_class_1()).__name__ == "DerivedClass1"
assert type(m.return_class_2()).__name__ == "DerivedClass2"
assert type(m.return_none()).__name__ == "NoneType"
# Repeat these a few times in a random order to ensure no invalid caching is applied
assert type(m.return_class_n(1)).__name__ == "DerivedClass1"
assert type(m.return_class_n(2)).__name__ == "DerivedClass2"
assert type(m.return_class_n(0)).__name__ == "BaseClass"
assert type(m.return_class_n(2)).__name__ == "DerivedClass2"
assert type(m.return_class_n(2)).__name__ == "DerivedClass2"
assert type(m.return_class_n(0)).__name__ == "BaseClass"
assert type(m.return_class_n(1)).__name__ == "DerivedClass1"
def test_isinstance():
objects = [tuple(), dict(), m.Pet("Polly", "parrot")] + [m.Dog("Molly")
] * 4
expected = (True, True, True, True, True, False, False)
assert m.check_instances(objects) == expected
def test_mismatched_holder():
import re
with pytest.raises(RuntimeError) as excinfo:
m.mismatched_holder_1()
assert re.match(
'generic_type: type ".*MismatchDerived1" does not have a non-default '
'holder type while its base ".*MismatchBase1" does',
str(excinfo.value), )
with pytest.raises(RuntimeError) as excinfo:
m.mismatched_holder_2()
assert re.match(
'generic_type: type ".*MismatchDerived2" has a non-default holder type '
'while its base ".*MismatchBase2" does not',
str(excinfo.value), )
def test_override_static():
"""#511: problem with inheritance + overwritten def_static"""
b = m.MyBase.make()
d1 = m.MyDerived.make2()
d2 = m.MyDerived.make()
assert isinstance(b, m.MyBase)
assert isinstance(d1, m.MyDerived)
assert isinstance(d2, m.MyDerived)
def test_implicit_conversion_life_support():
"""Ensure the lifetime of temporary objects created for implicit conversions"""
assert m.implicitly_convert_argument(UserType(5)) == 5
assert m.implicitly_convert_variable(UserType(5)) == 5
assert "outside a bound function" in m.implicitly_convert_variable_fail(
UserType(5))
def test_operator_new_delete(capture):
"""Tests that class-specific operator new/delete functions are invoked"""
class SubAliased(m.AliasedHasOpNewDelSize):
pass
with capture:
a = m.HasOpNewDel()
b = m.HasOpNewDelSize()
d = m.HasOpNewDelBoth()
assert (capture == """
A new 8
B new 4
D new 32
""")
sz_alias = str(m.AliasedHasOpNewDelSize.size_alias)
sz_noalias = str(m.AliasedHasOpNewDelSize.size_noalias)
with capture:
c = m.AliasedHasOpNewDelSize()
c2 = SubAliased()
assert capture == (
"C new " + sz_noalias + "\n" + "C new " + sz_alias + "\n")
with capture:
del a
pytest.gc_collect()
del b
pytest.gc_collect()
del d
pytest.gc_collect()
assert (capture == """
A delete
B delete 4
D delete
""")
with capture:
del c
pytest.gc_collect()
del c2
pytest.gc_collect()
assert capture == (
"C delete " + sz_noalias + "\n" + "C delete " + sz_alias + "\n")
def test_bind_protected_functions():
"""Expose protected member functions to Python using a helper class"""
a = m.ProtectedA()
assert a.foo() == 42
b = m.ProtectedB()
assert b.foo() == 42
class C(m.ProtectedB):
def __init__(self):
m.ProtectedB.__init__(self)
def foo(self):
return 0
c = C()
assert c.foo() == 0
def test_brace_initialization():
"""Tests that simple POD classes can be constructed using C++11 brace initialization"""
a = m.BraceInitialization(123, "test")
assert a.field1 == 123
assert a.field2 == "test"
# Tests that a non-simple class doesn't get brace initialization (if the
# class defines an initializer_list constructor, in particular, it would
# win over the expected constructor).
b = m.NoBraceInitialization([123, 456])
assert b.vec == [123, 456]
@pytest.mark.xfail("env.PYPY")
def test_class_refcount():
"""Instances must correctly increase/decrease the reference count of their types (#1029)"""
from sys import getrefcount
class PyDog(m.Dog):
pass
for cls in m.Dog, PyDog:
refcount_1 = getrefcount(cls)
molly = [cls("Molly") for _ in range(10)]
refcount_2 = getrefcount(cls)
del molly
pytest.gc_collect()
refcount_3 = getrefcount(cls)
assert refcount_1 == refcount_3
assert refcount_2 > refcount_1
def test_reentrant_implicit_conversion_failure(msg):
# ensure that there is no runaway reentrant implicit conversion (#1035)
with pytest.raises(TypeError) as excinfo:
m.BogusImplicitConversion(0)
assert (msg(excinfo.value) == """
__init__(): incompatible constructor arguments. The following argument types are supported:
1. m.class_.BogusImplicitConversion(arg0: m.class_.BogusImplicitConversion)
Invoked with: 0
""")
def test_error_after_conversions():
with pytest.raises(TypeError) as exc_info:
m.test_error_after_conversions("hello")
assert str(exc_info.value).startswith(
"Unable to convert function return value to a Python type!")
def test_aligned():
if hasattr(m, "Aligned"):
p = m.Aligned().ptr()
assert p % 1024 == 0
# https://foss.heptapod.net/pypy/pypy/-/issues/2742
@pytest.mark.xfail("env.PYPY")
def test_final():
with pytest.raises(TypeError) as exc_info:
class PyFinalChild(m.IsFinal):
pass
assert str(exc_info.value).endswith("is not an acceptable base type")
# https://foss.heptapod.net/pypy/pypy/-/issues/2742
@pytest.mark.xfail("env.PYPY")
def test_non_final_final():
with pytest.raises(TypeError) as exc_info:
class PyNonFinalFinalChild(m.IsNonFinalFinal):
pass
assert str(exc_info.value).endswith("is not an acceptable base type")
# https://github.com/pybind/pybind11/issues/1878
def test_exception_rvalue_abort():
with pytest.raises(RuntimeError):
m.PyPrintDestructor().throw_something()
# https://github.com/pybind/pybind11/issues/1568
def test_multiple_instances_with_same_pointer(capture):
n = 100
instances = [m.SamePointer() for _ in range(n)]
for i in range(n):
# We need to reuse the same allocated memory for with a different type,
# to ensure the bug in `deregister_instance_impl` is detected. Otherwise
# `Py_TYPE(self) == Py_TYPE(it->second)` will still succeed, even though
# the `instance` is already deleted.
instances[i] = m.Empty()
# No assert: if this does not trigger the error
# pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
# and just completes without crashing, we're good.
# https://github.com/pybind/pybind11/issues/1624
def test_base_and_derived_nested_scope():
assert issubclass(m.DerivedWithNested, m.BaseWithNested)
assert m.BaseWithNested.Nested != m.DerivedWithNested.Nested
assert m.BaseWithNested.Nested.get_name() == "BaseWithNested::Nested"
assert m.DerivedWithNested.Nested.get_name() == "DerivedWithNested::Nested"
def test_register_duplicate_class():
import types
module_scope = types.ModuleType("module_scope")
with pytest.raises(RuntimeError) as exc_info:
m.register_duplicate_class_name(module_scope)
expected = ('generic_type: cannot initialize type "Duplicate": '
"an object with that name is already defined")
assert str(exc_info.value) == expected
with pytest.raises(RuntimeError) as exc_info:
m.register_duplicate_class_type(module_scope)
expected = 'generic_type: type "YetAnotherDuplicate" is already registered!'
assert str(exc_info.value) == expected
class ClassScope:
pass
with pytest.raises(RuntimeError) as exc_info:
m.register_duplicate_nested_class_name(ClassScope)
expected = ('generic_type: cannot initialize type "DuplicateNested": '
"an object with that name is already defined")
assert str(exc_info.value) == expected
with pytest.raises(RuntimeError) as exc_info:
m.register_duplicate_nested_class_type(ClassScope)
expected = 'generic_type: type "YetAnotherDuplicateNested" is already registered!'
assert str(exc_info.value) == expected

View File

@@ -0,0 +1,84 @@
# Built-in in CMake 3.5+
include(CMakeParseArguments)
add_custom_target(test_cmake_build)
function(pybind11_add_build_test name)
cmake_parse_arguments(ARG "INSTALL" "" "" ${ARGN})
set(build_options "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}")
if(PYBIND11_FINDPYTHON)
list(APPEND build_options "-DPYBIND11_FINDPYTHON=${PYBIND11_FINDPYTHON}")
if(DEFINED Python_ROOT_DIR)
list(APPEND build_options "-DPython_ROOT_DIR=${Python_ROOT_DIR}")
endif()
list(APPEND build_options "-DPython_EXECUTABLE=${Python_EXECUTABLE}")
else()
list(APPEND build_options "-DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}")
endif()
if(DEFINED CMAKE_CXX_STANDARD)
list(APPEND build_options "-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}")
endif()
if(NOT ARG_INSTALL)
list(APPEND build_options "-Dpybind11_SOURCE_DIR=${pybind11_SOURCE_DIR}")
else()
list(APPEND build_options "-DCMAKE_PREFIX_PATH=${pybind11_BINARY_DIR}/mock_install")
endif()
add_custom_target(
test_build_${name}
${CMAKE_CTEST_COMMAND}
--build-and-test
"${CMAKE_CURRENT_SOURCE_DIR}/${name}"
"${CMAKE_CURRENT_BINARY_DIR}/${name}"
--build-config
Release
--build-noclean
--build-generator
${CMAKE_GENERATOR}
$<$<BOOL:${CMAKE_GENERATOR_PLATFORM}>:--build-generator-platform>
${CMAKE_GENERATOR_PLATFORM}
--build-makeprogram
${CMAKE_MAKE_PROGRAM}
--build-target
check_${name}
--build-options
${build_options})
if(ARG_INSTALL)
add_dependencies(test_build_${name} mock_install)
endif()
add_dependencies(test_cmake_build test_build_${name})
endfunction()
possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID)
pybind11_add_build_test(subdirectory_function)
pybind11_add_build_test(subdirectory_target)
if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy")
message(STATUS "Skipping embed test on PyPy")
else()
pybind11_add_build_test(subdirectory_embed)
endif()
if(PYBIND11_INSTALL)
add_custom_target(
mock_install ${CMAKE_COMMAND} "-DCMAKE_INSTALL_PREFIX=${pybind11_BINARY_DIR}/mock_install" -P
"${pybind11_BINARY_DIR}/cmake_install.cmake")
pybind11_add_build_test(installed_function INSTALL)
pybind11_add_build_test(installed_target INSTALL)
if(NOT ("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy"
))
pybind11_add_build_test(installed_embed INSTALL)
endif()
endif()
add_dependencies(check test_cmake_build)
add_subdirectory(subdirectory_target EXCLUDE_FROM_ALL)
add_subdirectory(subdirectory_embed EXCLUDE_FROM_ALL)

View File

@@ -0,0 +1,23 @@
#include <pybind11/embed.h>
namespace py = pybind11;
PYBIND11_EMBEDDED_MODULE(test_cmake_build, m) {
m.def("add", [](int i, int j) { return i + j; });
}
int main(int argc, char *argv[]) {
if (argc != 2) {
throw std::runtime_error("Expected test.py file as the first argument");
}
auto *test_py_file = argv[1];
py::scoped_interpreter guard{};
auto m = py::module_::import("test_cmake_build");
if (m.attr("add")(1, 2).cast<int>() != 3) {
throw std::runtime_error("embed.cpp failed");
}
py::module_::import("sys").attr("argv") = py::make_tuple("test.py", "embed.cpp");
py::eval_file(test_py_file, py::globals());
}

View File

@@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.4)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.18)
endif()
project(test_installed_embed CXX)
find_package(pybind11 CONFIG REQUIRED)
message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}")
add_executable(test_installed_embed ../embed.cpp)
target_link_libraries(test_installed_embed PRIVATE pybind11::embed)
set_target_properties(test_installed_embed PROPERTIES OUTPUT_NAME test_cmake_build)
# Do not treat includes from IMPORTED target as SYSTEM (Python headers in pybind11::embed).
# This may be needed to resolve header conflicts, e.g. between Python release and debug headers.
set_target_properties(test_installed_embed PROPERTIES NO_SYSTEM_FROM_IMPORTED ON)
add_custom_target(
check_installed_embed
$<TARGET_FILE:test_installed_embed> ${PROJECT_SOURCE_DIR}/../test.py
DEPENDS test_installed_embed)

View File

@@ -0,0 +1,39 @@
cmake_minimum_required(VERSION 3.4)
project(test_installed_module CXX)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.18)
endif()
project(test_installed_function CXX)
find_package(pybind11 CONFIG REQUIRED)
message(
STATUS "Found pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}: ${pybind11_INCLUDE_DIRS}")
pybind11_add_module(test_installed_function SHARED NO_EXTRAS ../main.cpp)
set_target_properties(test_installed_function PROPERTIES OUTPUT_NAME test_cmake_build)
if(DEFINED Python_EXECUTABLE)
set(_Python_EXECUTABLE "${Python_EXECUTABLE}")
elseif(DEFINED PYTHON_EXECUTABLE)
set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
else()
message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)")
endif()
add_custom_target(
check_installed_function
${CMAKE_COMMAND}
-E
env
PYTHONPATH=$<TARGET_FILE_DIR:test_installed_function>
${_Python_EXECUTABLE}
${PROJECT_SOURCE_DIR}/../test.py
${PROJECT_NAME}
DEPENDS test_installed_function)

View File

@@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.4)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.18)
endif()
project(test_installed_target CXX)
find_package(pybind11 CONFIG REQUIRED)
message(STATUS "Found pybind11 v${pybind11_VERSION}: ${pybind11_INCLUDE_DIRS}")
add_library(test_installed_target MODULE ../main.cpp)
target_link_libraries(test_installed_target PRIVATE pybind11::module)
set_target_properties(test_installed_target PROPERTIES OUTPUT_NAME test_cmake_build)
# Make sure result is, for example, test_installed_target.so, not libtest_installed_target.dylib
pybind11_extension(test_installed_target)
# Do not treat includes from IMPORTED target as SYSTEM (Python headers in pybind11::module).
# This may be needed to resolve header conflicts, e.g. between Python release and debug headers.
set_target_properties(test_installed_target PROPERTIES NO_SYSTEM_FROM_IMPORTED ON)
if(DEFINED Python_EXECUTABLE)
set(_Python_EXECUTABLE "${Python_EXECUTABLE}")
elseif(DEFINED PYTHON_EXECUTABLE)
set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
else()
message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)")
endif()
add_custom_target(
check_installed_target
${CMAKE_COMMAND}
-E
env
PYTHONPATH=$<TARGET_FILE_DIR:test_installed_target>
${_Python_EXECUTABLE}
${PROJECT_SOURCE_DIR}/../test.py
${PROJECT_NAME}
DEPENDS test_installed_target)

View File

@@ -0,0 +1,6 @@
#include <pybind11/pybind11.h>
namespace py = pybind11;
PYBIND11_MODULE(test_cmake_build, m) {
m.def("add", [](int i, int j) { return i + j; });
}

View File

@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.4)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.18)
endif()
project(test_subdirectory_embed CXX)
set(PYBIND11_INSTALL
ON
CACHE BOOL "")
set(PYBIND11_EXPORT_NAME test_export)
add_subdirectory("${pybind11_SOURCE_DIR}" pybind11)
# Test basic target functionality
add_executable(test_subdirectory_embed ../embed.cpp)
target_link_libraries(test_subdirectory_embed PRIVATE pybind11::embed)
set_target_properties(test_subdirectory_embed PROPERTIES OUTPUT_NAME test_cmake_build)
add_custom_target(
check_subdirectory_embed
$<TARGET_FILE:test_subdirectory_embed> "${PROJECT_SOURCE_DIR}/../test.py"
DEPENDS test_subdirectory_embed)
# Test custom export group -- PYBIND11_EXPORT_NAME
add_library(test_embed_lib ../embed.cpp)
target_link_libraries(test_embed_lib PRIVATE pybind11::embed)
install(
TARGETS test_embed_lib
EXPORT test_export
ARCHIVE DESTINATION bin
LIBRARY DESTINATION lib
RUNTIME DESTINATION lib)
install(EXPORT test_export DESTINATION lib/cmake/test_export/test_export-Targets.cmake)

View File

@@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.4)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.18)
endif()
project(test_subdirectory_function CXX)
add_subdirectory("${pybind11_SOURCE_DIR}" pybind11)
pybind11_add_module(test_subdirectory_function ../main.cpp)
set_target_properties(test_subdirectory_function PROPERTIES OUTPUT_NAME test_cmake_build)
if(DEFINED Python_EXECUTABLE)
set(_Python_EXECUTABLE "${Python_EXECUTABLE}")
elseif(DEFINED PYTHON_EXECUTABLE)
set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
else()
message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)")
endif()
add_custom_target(
check_subdirectory_function
${CMAKE_COMMAND}
-E
env
PYTHONPATH=$<TARGET_FILE_DIR:test_subdirectory_function>
${_Python_EXECUTABLE}
${PROJECT_SOURCE_DIR}/../test.py
${PROJECT_NAME}
DEPENDS test_subdirectory_function)

View File

@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.4)
# The `cmake_minimum_required(VERSION 3.4...3.18)` syntax does not work with
# some versions of VS that have a patched CMake 3.11. This forces us to emulate
# the behavior using the following workaround:
if(${CMAKE_VERSION} VERSION_LESS 3.18)
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
else()
cmake_policy(VERSION 3.18)
endif()
project(test_subdirectory_target CXX)
add_subdirectory("${pybind11_SOURCE_DIR}" pybind11)
add_library(test_subdirectory_target MODULE ../main.cpp)
set_target_properties(test_subdirectory_target PROPERTIES OUTPUT_NAME test_cmake_build)
target_link_libraries(test_subdirectory_target PRIVATE pybind11::module)
# Make sure result is, for example, test_installed_target.so, not libtest_installed_target.dylib
pybind11_extension(test_subdirectory_target)
if(DEFINED Python_EXECUTABLE)
set(_Python_EXECUTABLE "${Python_EXECUTABLE}")
elseif(DEFINED PYTHON_EXECUTABLE)
set(_Python_EXECUTABLE "${PYTHON_EXECUTABLE}")
else()
message(FATAL_ERROR "No Python executable defined (should not be possible at this stage)")
endif()
add_custom_target(
check_subdirectory_target
${CMAKE_COMMAND}
-E
env
PYTHONPATH=$<TARGET_FILE_DIR:test_subdirectory_target>
${_Python_EXECUTABLE}
${PROJECT_SOURCE_DIR}/../test.py
${PROJECT_NAME}
DEPENDS test_subdirectory_target)

View File

@@ -0,0 +1,8 @@
import sys
import test_cmake_build
assert isinstance(__file__, str) # Test this is properly set
assert test_cmake_build.add(1, 2) == 3
print(f"{sys.argv[1]} imports, runs, and adds: 1 + 2 = 3")

View File

@@ -0,0 +1,55 @@
// Copyright (c) 2021 The Pybind Development Team.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "pybind11_tests.h"
// IUT = Implementation Under Test
#define CONST_NAME_TESTS(TEST_FUNC, IUT) \
std::string TEST_FUNC(int selector) { \
switch (selector) { \
case 0: \
return IUT("").text; \
case 1: \
return IUT("A").text; \
case 2: \
return IUT("Bd").text; \
case 3: \
return IUT("Cef").text; \
case 4: \
return IUT<int>().text; /*NOLINT(bugprone-macro-parentheses)*/ \
case 5: \
return IUT<std::string>().text; /*NOLINT(bugprone-macro-parentheses)*/ \
case 6: \
return IUT<true>("T1", "T2").text; /*NOLINT(bugprone-macro-parentheses)*/ \
case 7: \
return IUT<false>("U1", "U2").text; /*NOLINT(bugprone-macro-parentheses)*/ \
case 8: \
/*NOLINTNEXTLINE(bugprone-macro-parentheses)*/ \
return IUT<true>(IUT("D1"), IUT("D2")).text; \
case 9: \
/*NOLINTNEXTLINE(bugprone-macro-parentheses)*/ \
return IUT<false>(IUT("E1"), IUT("E2")).text; \
case 10: \
return IUT("KeepAtEnd").text; \
default: \
break; \
} \
throw std::runtime_error("Invalid selector value."); \
}
CONST_NAME_TESTS(const_name_tests, py::detail::const_name)
#ifdef PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY
CONST_NAME_TESTS(underscore_tests, py::detail::_)
#endif
TEST_SUBMODULE(const_name, m) {
m.def("const_name_tests", const_name_tests);
#if defined(PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY)
m.def("underscore_tests", underscore_tests);
#else
m.attr("underscore_tests") = "PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY not defined.";
#endif
}

View File

@@ -0,0 +1,25 @@
import pytest
from pybind11_tests import const_name as m
@pytest.mark.parametrize("func", (m.const_name_tests, m.underscore_tests))
@pytest.mark.parametrize(
"selector, expected",
enumerate((
"",
"A",
"Bd",
"Cef",
"%",
"%",
"T1",
"U2",
"D1",
"E2",
"KeepAtEnd", )), )
def test_const_name(func, selector, expected):
if isinstance(func, str):
pytest.skip(func)
text = func(selector)
assert text == expected

View File

@@ -0,0 +1,159 @@
/*
tests/test_constants_and_functions.cpp -- global constants and functions, enumerations, raw
byte strings
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
enum MyEnum { EFirstEntry = 1, ESecondEntry };
std::string test_function1() { return "test_function()"; }
std::string test_function2(MyEnum k) { return "test_function(enum=" + std::to_string(k) + ")"; }
std::string test_function3(int i) { return "test_function(" + std::to_string(i) + ")"; }
py::str test_function4() { return "test_function()"; }
py::str test_function4(char *) { return "test_function(char *)"; }
py::str test_function4(int, float) { return "test_function(int, float)"; }
py::str test_function4(float, int) { return "test_function(float, int)"; }
py::bytes return_bytes() {
const char *data = "\x01\x00\x02\x00";
return std::string(data, 4);
}
std::string print_bytes(const py::bytes &bytes) {
std::string ret = "bytes[";
const auto value = static_cast<std::string>(bytes);
for (size_t i = 0; i < value.length(); ++i) {
ret += std::to_string(static_cast<int>(value[i])) + " ";
}
ret.back() = ']';
return ret;
}
// Test that we properly handle C++17 exception specifiers (which are part of the function
// signature in C++17). These should all still work before C++17, but don't affect the function
// signature.
namespace test_exc_sp {
// [workaround(intel)] Unable to use noexcept instead of noexcept(true)
// Make the f1 test basically the same as the f2 test in C++17 mode for the Intel compiler as
// it fails to compile with a plain noexcept (tested with icc (ICC) 2021.1 Beta 20200827).
#if defined(__INTEL_COMPILER) && defined(PYBIND11_CPP17)
int f1(int x) noexcept(true) { return x + 1; }
#else
int f1(int x) noexcept { return x + 1; }
#endif
int f2(int x) noexcept(true) { return x + 2; }
int f3(int x) noexcept(false) { return x + 3; }
#if defined(__GNUG__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated"
#endif
// NOLINTNEXTLINE(modernize-use-noexcept)
int f4(int x) throw() { return x + 4; } // Deprecated equivalent to noexcept(true)
#if defined(__GNUG__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic pop
#endif
struct C {
int m1(int x) noexcept { return x - 1; }
int m2(int x) const noexcept { return x - 2; }
int m3(int x) noexcept(true) { return x - 3; }
int m4(int x) const noexcept(true) { return x - 4; }
int m5(int x) noexcept(false) { return x - 5; }
int m6(int x) const noexcept(false) { return x - 6; }
#if defined(__GNUG__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wdeprecated"
#endif
// NOLINTNEXTLINE(modernize-use-noexcept)
int m7(int x) throw() { return x - 7; }
// NOLINTNEXTLINE(modernize-use-noexcept)
int m8(int x) const throw() { return x - 8; }
#if defined(__GNUG__) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic pop
#endif
};
} // namespace test_exc_sp
TEST_SUBMODULE(constants_and_functions, m) {
// test_constants
m.attr("some_constant") = py::int_(14);
// test_function_overloading
m.def("test_function", &test_function1);
m.def("test_function", &test_function2);
m.def("test_function", &test_function3);
#if defined(PYBIND11_OVERLOAD_CAST)
m.def("test_function", py::overload_cast<>(&test_function4));
m.def("test_function", py::overload_cast<char *>(&test_function4));
m.def("test_function", py::overload_cast<int, float>(&test_function4));
m.def("test_function", py::overload_cast<float, int>(&test_function4));
#else
m.def("test_function", static_cast<py::str (*)()>(&test_function4));
m.def("test_function", static_cast<py::str (*)(char *)>(&test_function4));
m.def("test_function", static_cast<py::str (*)(int, float)>(&test_function4));
m.def("test_function", static_cast<py::str (*)(float, int)>(&test_function4));
#endif
py::enum_<MyEnum>(m, "MyEnum")
.value("EFirstEntry", EFirstEntry)
.value("ESecondEntry", ESecondEntry)
.export_values();
// test_bytes
m.def("return_bytes", &return_bytes);
m.def("print_bytes", &print_bytes);
// test_exception_specifiers
using namespace test_exc_sp;
py::class_<C>(m, "C")
.def(py::init<>())
.def("m1", &C::m1)
.def("m2", &C::m2)
.def("m3", &C::m3)
.def("m4", &C::m4)
.def("m5", &C::m5)
.def("m6", &C::m6)
.def("m7", &C::m7)
.def("m8", &C::m8);
m.def("f1", f1);
m.def("f2", f2);
#if defined(__INTEL_COMPILER)
# pragma warning push
# pragma warning disable 878 // incompatible exception specifications
#endif
m.def("f3", f3);
#if defined(__INTEL_COMPILER)
# pragma warning pop
#endif
m.def("f4", f4);
// test_function_record_leaks
m.def("register_large_capture_with_invalid_arguments", [](py::module_ m) {
// This should always be enough to trigger the alternative branch
// where `sizeof(capture) > sizeof(rec->data)`
uint64_t capture[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
#if defined(__GNUC__) && __GNUC__ == 4 // CentOS7
py::detail::silence_unused_warnings(capture);
#endif
m.def(
"should_raise", [capture](int) { return capture[9] + 33; }, py::kw_only(), py::arg());
});
m.def("register_with_raising_repr", [](py::module_ m, const py::object &default_value) {
m.def(
"should_raise",
[](int, int, const py::object &) { return 42; },
"some docstring",
py::arg_v("x", 42),
py::arg_v("y", 42, "<the answer>"),
py::arg_v("z", default_value));
});
}

View File

@@ -0,0 +1,52 @@
import pytest
m = pytest.importorskip("pybind11_tests.constants_and_functions")
def test_constants():
assert m.some_constant == 14
def test_function_overloading():
assert m.test_function() == "test_function()"
assert m.test_function(7) == "test_function(7)"
assert m.test_function(m.MyEnum.EFirstEntry) == "test_function(enum=1)"
assert m.test_function(m.MyEnum.ESecondEntry) == "test_function(enum=2)"
assert m.test_function() == "test_function()"
assert m.test_function("abcd") == "test_function(char *)"
assert m.test_function(1, 1.0) == "test_function(int, float)"
assert m.test_function(1, 1.0) == "test_function(int, float)"
assert m.test_function(2.0, 2) == "test_function(float, int)"
def test_bytes():
assert m.print_bytes(m.return_bytes()) == "bytes[1 0 2 0]"
def test_exception_specifiers():
c = m.C()
assert c.m1(2) == 1
assert c.m2(3) == 1
assert c.m3(5) == 2
assert c.m4(7) == 3
assert c.m5(10) == 5
assert c.m6(14) == 8
assert c.m7(20) == 13
assert c.m8(29) == 21
assert m.f1(33) == 34
assert m.f2(53) == 55
assert m.f3(86) == 89
assert m.f4(140) == 144
def test_function_record_leaks():
class RaisingRepr:
def __repr__(self):
raise RuntimeError("Surprise!")
with pytest.raises(RuntimeError):
m.register_large_capture_with_invalid_arguments(m)
with pytest.raises(RuntimeError):
m.register_with_raising_repr(m, RaisingRepr())

View File

@@ -0,0 +1,295 @@
/*
tests/test_copy_move_policies.cpp -- 'copy' and 'move' return value policies
and related tests
Copyright (c) 2016 Ben North <ben@redfrontdoor.org>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/stl.h>
#include "constructor_stats.h"
#include "pybind11_tests.h"
template <typename derived>
struct empty {
static const derived &get_one() { return instance_; }
static derived instance_;
};
struct lacking_copy_ctor : public empty<lacking_copy_ctor> {
lacking_copy_ctor() = default;
lacking_copy_ctor(const lacking_copy_ctor &other) = delete;
};
template <>
lacking_copy_ctor empty<lacking_copy_ctor>::instance_ = {};
struct lacking_move_ctor : public empty<lacking_move_ctor> {
lacking_move_ctor() = default;
lacking_move_ctor(const lacking_move_ctor &other) = delete;
lacking_move_ctor(lacking_move_ctor &&other) = delete;
};
template <>
lacking_move_ctor empty<lacking_move_ctor>::instance_ = {};
/* Custom type caster move/copy test classes */
class MoveOnlyInt {
public:
MoveOnlyInt() { print_default_created(this); }
explicit MoveOnlyInt(int v) : value{v} { print_created(this, value); }
MoveOnlyInt(MoveOnlyInt &&m) noexcept {
print_move_created(this, m.value);
std::swap(value, m.value);
}
MoveOnlyInt &operator=(MoveOnlyInt &&m) noexcept {
print_move_assigned(this, m.value);
std::swap(value, m.value);
return *this;
}
MoveOnlyInt(const MoveOnlyInt &) = delete;
MoveOnlyInt &operator=(const MoveOnlyInt &) = delete;
~MoveOnlyInt() { print_destroyed(this); }
int value;
};
class MoveOrCopyInt {
public:
MoveOrCopyInt() { print_default_created(this); }
explicit MoveOrCopyInt(int v) : value{v} { print_created(this, value); }
MoveOrCopyInt(MoveOrCopyInt &&m) noexcept {
print_move_created(this, m.value);
std::swap(value, m.value);
}
MoveOrCopyInt &operator=(MoveOrCopyInt &&m) noexcept {
print_move_assigned(this, m.value);
std::swap(value, m.value);
return *this;
}
MoveOrCopyInt(const MoveOrCopyInt &c) {
print_copy_created(this, c.value);
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
value = c.value;
}
MoveOrCopyInt &operator=(const MoveOrCopyInt &c) {
print_copy_assigned(this, c.value);
value = c.value;
return *this;
}
~MoveOrCopyInt() { print_destroyed(this); }
int value;
};
class CopyOnlyInt {
public:
CopyOnlyInt() { print_default_created(this); }
explicit CopyOnlyInt(int v) : value{v} { print_created(this, value); }
CopyOnlyInt(const CopyOnlyInt &c) {
print_copy_created(this, c.value);
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
value = c.value;
}
CopyOnlyInt &operator=(const CopyOnlyInt &c) {
print_copy_assigned(this, c.value);
value = c.value;
return *this;
}
~CopyOnlyInt() { print_destroyed(this); }
int value;
};
PYBIND11_NAMESPACE_BEGIN(pybind11)
PYBIND11_NAMESPACE_BEGIN(detail)
template <>
struct type_caster<MoveOnlyInt> {
PYBIND11_TYPE_CASTER(MoveOnlyInt, const_name("MoveOnlyInt"));
bool load(handle src, bool) {
value = MoveOnlyInt(src.cast<int>());
return true;
}
static handle cast(const MoveOnlyInt &m, return_value_policy r, handle p) {
return pybind11::cast(m.value, r, p);
}
};
template <>
struct type_caster<MoveOrCopyInt> {
PYBIND11_TYPE_CASTER(MoveOrCopyInt, const_name("MoveOrCopyInt"));
bool load(handle src, bool) {
value = MoveOrCopyInt(src.cast<int>());
return true;
}
static handle cast(const MoveOrCopyInt &m, return_value_policy r, handle p) {
return pybind11::cast(m.value, r, p);
}
};
template <>
struct type_caster<CopyOnlyInt> {
protected:
CopyOnlyInt value;
public:
static constexpr auto name = const_name("CopyOnlyInt");
bool load(handle src, bool) {
value = CopyOnlyInt(src.cast<int>());
return true;
}
static handle cast(const CopyOnlyInt &m, return_value_policy r, handle p) {
return pybind11::cast(m.value, r, p);
}
static handle cast(const CopyOnlyInt *src, return_value_policy policy, handle parent) {
if (!src) {
return none().release();
}
return cast(*src, policy, parent);
}
explicit operator CopyOnlyInt *() { return &value; }
explicit operator CopyOnlyInt &() { return value; }
template <typename T>
using cast_op_type = pybind11::detail::cast_op_type<T>;
};
PYBIND11_NAMESPACE_END(detail)
PYBIND11_NAMESPACE_END(pybind11)
TEST_SUBMODULE(copy_move_policies, m) {
// test_lacking_copy_ctor
py::class_<lacking_copy_ctor>(m, "lacking_copy_ctor")
.def_static("get_one", &lacking_copy_ctor::get_one, py::return_value_policy::copy);
// test_lacking_move_ctor
py::class_<lacking_move_ctor>(m, "lacking_move_ctor")
.def_static("get_one", &lacking_move_ctor::get_one, py::return_value_policy::move);
// test_move_and_copy_casts
// NOLINTNEXTLINE(performance-unnecessary-value-param)
m.def("move_and_copy_casts", [](const py::object &o) {
int r = 0;
r += py::cast<MoveOrCopyInt>(o).value; /* moves */
r += py::cast<MoveOnlyInt>(o).value; /* moves */
r += py::cast<CopyOnlyInt>(o).value; /* copies */
auto m1(py::cast<MoveOrCopyInt>(o)); /* moves */
auto m2(py::cast<MoveOnlyInt>(o)); /* moves */
auto m3(py::cast<CopyOnlyInt>(o)); /* copies */
r += m1.value + m2.value + m3.value;
return r;
});
// test_move_and_copy_loads
m.def("move_only", [](MoveOnlyInt m) { return m.value; });
// Changing this breaks the existing test: needs careful review.
// NOLINTNEXTLINE(performance-unnecessary-value-param)
m.def("move_or_copy", [](MoveOrCopyInt m) { return m.value; });
// Changing this breaks the existing test: needs careful review.
// NOLINTNEXTLINE(performance-unnecessary-value-param)
m.def("copy_only", [](CopyOnlyInt m) { return m.value; });
m.def("move_pair",
[](std::pair<MoveOnlyInt, MoveOrCopyInt> p) { return p.first.value + p.second.value; });
m.def("move_tuple", [](std::tuple<MoveOnlyInt, MoveOrCopyInt, MoveOnlyInt> t) {
return std::get<0>(t).value + std::get<1>(t).value + std::get<2>(t).value;
});
m.def("copy_tuple", [](std::tuple<CopyOnlyInt, CopyOnlyInt> t) {
return std::get<0>(t).value + std::get<1>(t).value;
});
m.def("move_copy_nested",
[](std::pair<MoveOnlyInt,
std::pair<std::tuple<MoveOrCopyInt, CopyOnlyInt, std::tuple<MoveOnlyInt>>,
MoveOrCopyInt>> x) {
return x.first.value + std::get<0>(x.second.first).value
+ std::get<1>(x.second.first).value
+ std::get<0>(std::get<2>(x.second.first)).value + x.second.second.value;
});
m.def("move_and_copy_cstats", []() {
ConstructorStats::gc();
// Reset counts to 0 so that previous tests don't affect later ones:
auto &mc = ConstructorStats::get<MoveOrCopyInt>();
mc.move_assignments = mc.move_constructions = mc.copy_assignments = mc.copy_constructions
= 0;
auto &mo = ConstructorStats::get<MoveOnlyInt>();
mo.move_assignments = mo.move_constructions = mo.copy_assignments = mo.copy_constructions
= 0;
auto &co = ConstructorStats::get<CopyOnlyInt>();
co.move_assignments = co.move_constructions = co.copy_assignments = co.copy_constructions
= 0;
py::dict d;
d["MoveOrCopyInt"] = py::cast(mc, py::return_value_policy::reference);
d["MoveOnlyInt"] = py::cast(mo, py::return_value_policy::reference);
d["CopyOnlyInt"] = py::cast(co, py::return_value_policy::reference);
return d;
});
#ifdef PYBIND11_HAS_OPTIONAL
// test_move_and_copy_load_optional
m.attr("has_optional") = true;
m.def("move_optional", [](std::optional<MoveOnlyInt> o) { return o->value; });
m.def("move_or_copy_optional", [](std::optional<MoveOrCopyInt> o) { return o->value; });
m.def("copy_optional", [](std::optional<CopyOnlyInt> o) { return o->value; });
m.def("move_optional_tuple",
[](std::optional<std::tuple<MoveOrCopyInt, MoveOnlyInt, CopyOnlyInt>> x) {
return std::get<0>(*x).value + std::get<1>(*x).value + std::get<2>(*x).value;
});
#else
m.attr("has_optional") = false;
#endif
// #70 compilation issue if operator new is not public - simple body added
// but not needed on most compilers; MSVC and nvcc don't like a local
// struct not having a method defined when declared, since it can not be
// added later.
struct PrivateOpNew {
int value = 1;
private:
void *operator new(size_t bytes) {
void *ptr = std::malloc(bytes);
if (ptr) {
return ptr;
}
throw std::bad_alloc{};
}
};
py::class_<PrivateOpNew>(m, "PrivateOpNew").def_readonly("value", &PrivateOpNew::value);
m.def("private_op_new_value", []() { return PrivateOpNew(); });
m.def(
"private_op_new_reference",
[]() -> const PrivateOpNew & {
static PrivateOpNew x{};
return x;
},
py::return_value_policy::reference);
// test_move_fallback
// #389: rvp::move should fall-through to copy on non-movable objects
struct MoveIssue1 {
int v;
explicit MoveIssue1(int v) : v{v} {}
MoveIssue1(const MoveIssue1 &c) = default;
MoveIssue1(MoveIssue1 &&) = delete;
};
py::class_<MoveIssue1>(m, "MoveIssue1")
.def(py::init<int>())
.def_readwrite("value", &MoveIssue1::v);
struct MoveIssue2 {
int v;
explicit MoveIssue2(int v) : v{v} {}
MoveIssue2(MoveIssue2 &&) = default;
};
py::class_<MoveIssue2>(m, "MoveIssue2")
.def(py::init<int>())
.def_readwrite("value", &MoveIssue2::v);
// #2742: Don't expect ownership of raw pointer to `new`ed object to be transferred with
// `py::return_value_policy::move`
m.def(
"get_moveissue1",
[](int i) { return std::unique_ptr<MoveIssue1>(new MoveIssue1(i)); },
py::return_value_policy::move);
m.def(
"get_moveissue2", [](int i) { return MoveIssue2(i); }, py::return_value_policy::move);
// Make sure that cast from pytype rvalue to other pytype works
m.def("get_pytype_rvalue_castissue", [](double i) { return py::float_(i).cast<py::int_>(); });
}

View File

@@ -0,0 +1,130 @@
import pytest
from pybind11_tests import copy_move_policies as m
def test_lacking_copy_ctor():
with pytest.raises(RuntimeError) as excinfo:
m.lacking_copy_ctor.get_one()
assert "is non-copyable!" in str(excinfo.value)
def test_lacking_move_ctor():
with pytest.raises(RuntimeError) as excinfo:
m.lacking_move_ctor.get_one()
assert "is neither movable nor copyable!" in str(excinfo.value)
def test_move_and_copy_casts():
"""Cast some values in C++ via custom type casters and count the number of moves/copies."""
cstats = m.move_and_copy_cstats()
c_m, c_mc, c_c = (
cstats["MoveOnlyInt"],
cstats["MoveOrCopyInt"],
cstats["CopyOnlyInt"], )
# The type move constructions/assignments below each get incremented: the move assignment comes
# from the type_caster load; the move construction happens when extracting that via a cast or
# loading into an argument.
assert m.move_and_copy_casts(3) == 18
assert c_m.copy_assignments + c_m.copy_constructions == 0
assert c_m.move_assignments == 2
assert c_m.move_constructions >= 2
assert c_mc.alive() == 0
assert c_mc.copy_assignments + c_mc.copy_constructions == 0
assert c_mc.move_assignments == 2
assert c_mc.move_constructions >= 2
assert c_c.alive() == 0
assert c_c.copy_assignments == 2
assert c_c.copy_constructions >= 2
assert c_m.alive() + c_mc.alive() + c_c.alive() == 0
def test_move_and_copy_loads():
"""Call some functions that load arguments via custom type casters and count the number of
moves/copies."""
cstats = m.move_and_copy_cstats()
c_m, c_mc, c_c = (
cstats["MoveOnlyInt"],
cstats["MoveOrCopyInt"],
cstats["CopyOnlyInt"], )
assert m.move_only(10) == 10 # 1 move, c_m
assert m.move_or_copy(11) == 11 # 1 move, c_mc
assert m.copy_only(12) == 12 # 1 copy, c_c
assert m.move_pair((13, 14)) == 27 # 1 c_m move, 1 c_mc move
assert m.move_tuple((15, 16, 17)) == 48 # 2 c_m moves, 1 c_mc move
assert m.copy_tuple((18, 19)) == 37 # 2 c_c copies
# Direct constructions: 2 c_m moves, 2 c_mc moves, 1 c_c copy
# Extra moves/copies when moving pairs/tuples: 3 c_m, 3 c_mc, 2 c_c
assert m.move_copy_nested((1, ((2, 3, (4, )), 5))) == 15
assert c_m.copy_assignments + c_m.copy_constructions == 0
assert c_m.move_assignments == 6
assert c_m.move_constructions == 9
assert c_mc.copy_assignments + c_mc.copy_constructions == 0
assert c_mc.move_assignments == 5
assert c_mc.move_constructions == 8
assert c_c.copy_assignments == 4
assert c_c.copy_constructions == 6
assert c_m.alive() + c_mc.alive() + c_c.alive() == 0
@pytest.mark.skipif(not m.has_optional, reason="no <optional>")
def test_move_and_copy_load_optional():
"""Tests move/copy loads of std::optional arguments"""
cstats = m.move_and_copy_cstats()
c_m, c_mc, c_c = (
cstats["MoveOnlyInt"],
cstats["MoveOrCopyInt"],
cstats["CopyOnlyInt"], )
# The extra move/copy constructions below come from the std::optional move (which has to move
# its arguments):
assert m.move_optional(10) == 10 # c_m: 1 move assign, 2 move construct
assert m.move_or_copy_optional(
11) == 11 # c_mc: 1 move assign, 2 move construct
assert m.copy_optional(12) == 12 # c_c: 1 copy assign, 2 copy construct
# 1 move assign + move construct moves each of c_m, c_mc, 1 c_c copy
# +1 move/copy construct each from moving the tuple
# +1 move/copy construct each from moving the optional (which moves the tuple again)
assert m.move_optional_tuple((3, 4, 5)) == 12
assert c_m.copy_assignments + c_m.copy_constructions == 0
assert c_m.move_assignments == 2
assert c_m.move_constructions == 5
assert c_mc.copy_assignments + c_mc.copy_constructions == 0
assert c_mc.move_assignments == 2
assert c_mc.move_constructions == 5
assert c_c.copy_assignments == 2
assert c_c.copy_constructions == 5
assert c_m.alive() + c_mc.alive() + c_c.alive() == 0
def test_private_op_new():
"""An object with a private `operator new` cannot be returned by value"""
with pytest.raises(RuntimeError) as excinfo:
m.private_op_new_value()
assert "is neither movable nor copyable" in str(excinfo.value)
assert m.private_op_new_reference().value == 1
def test_move_fallback():
"""#389: rvp::move should fall-through to copy on non-movable objects"""
m1 = m.get_moveissue1(1)
assert m1.value == 1
m2 = m.get_moveissue2(2)
assert m2.value == 2
def test_pytype_rvalue_cast():
"""Make sure that cast from pytype rvalue to other pytype works"""
value = m.get_pytype_rvalue_castissue(1.0)
assert value == 1

View File

@@ -0,0 +1,209 @@
/*
tests/test_custom_type_casters.cpp -- tests type_caster<T>
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "constructor_stats.h"
#include "pybind11_tests.h"
// py::arg/py::arg_v testing: these arguments just record their argument when invoked
class ArgInspector1 {
public:
std::string arg = "(default arg inspector 1)";
};
class ArgInspector2 {
public:
std::string arg = "(default arg inspector 2)";
};
class ArgAlwaysConverts {};
namespace pybind11 {
namespace detail {
template <>
struct type_caster<ArgInspector1> {
public:
// Classic
#ifdef PYBIND11_DETAIL_UNDERSCORE_BACKWARD_COMPATIBILITY
PYBIND11_TYPE_CASTER(ArgInspector1, _("ArgInspector1"));
#else
PYBIND11_TYPE_CASTER(ArgInspector1, const_name("ArgInspector1"));
#endif
bool load(handle src, bool convert) {
value.arg = "loading ArgInspector1 argument " + std::string(convert ? "WITH" : "WITHOUT")
+ " conversion allowed. "
"Argument value = "
+ (std::string) str(src);
return true;
}
static handle cast(const ArgInspector1 &src, return_value_policy, handle) {
return str(src.arg).release();
}
};
template <>
struct type_caster<ArgInspector2> {
public:
PYBIND11_TYPE_CASTER(ArgInspector2, const_name("ArgInspector2"));
bool load(handle src, bool convert) {
value.arg = "loading ArgInspector2 argument " + std::string(convert ? "WITH" : "WITHOUT")
+ " conversion allowed. "
"Argument value = "
+ (std::string) str(src);
return true;
}
static handle cast(const ArgInspector2 &src, return_value_policy, handle) {
return str(src.arg).release();
}
};
template <>
struct type_caster<ArgAlwaysConverts> {
public:
PYBIND11_TYPE_CASTER(ArgAlwaysConverts, const_name("ArgAlwaysConverts"));
bool load(handle, bool convert) { return convert; }
static handle cast(const ArgAlwaysConverts &, return_value_policy, handle) {
return py::none().release();
}
};
} // namespace detail
} // namespace pybind11
// test_custom_caster_destruction
class DestructionTester {
public:
DestructionTester() { print_default_created(this); }
~DestructionTester() { print_destroyed(this); }
DestructionTester(const DestructionTester &) { print_copy_created(this); }
DestructionTester(DestructionTester &&) noexcept { print_move_created(this); }
DestructionTester &operator=(const DestructionTester &) {
print_copy_assigned(this);
return *this;
}
DestructionTester &operator=(DestructionTester &&) noexcept {
print_move_assigned(this);
return *this;
}
};
namespace pybind11 {
namespace detail {
template <>
struct type_caster<DestructionTester> {
PYBIND11_TYPE_CASTER(DestructionTester, const_name("DestructionTester"));
bool load(handle, bool) { return true; }
static handle cast(const DestructionTester &, return_value_policy, handle) {
return py::bool_(true).release();
}
};
} // namespace detail
} // namespace pybind11
// Define type caster outside of `pybind11::detail` and then alias it.
namespace other_lib {
struct MyType {};
// Corrupt `py` shorthand alias for surrounding context.
namespace py {}
// Corrupt unqualified relative `pybind11` namespace.
namespace pybind11 {}
// Correct alias.
namespace py_ = ::pybind11;
// Define caster. This is effectively no-op, we only ensure it compiles and we
// don't have any symbol collision when using macro mixin.
struct my_caster {
PYBIND11_TYPE_CASTER(MyType, py_::detail::const_name("MyType"));
bool load(py_::handle, bool) { return true; }
static py_::handle cast(const MyType &, py_::return_value_policy, py_::handle) {
return py_::bool_(true).release();
}
};
} // namespace other_lib
// Effectively "alias" it into correct namespace (via inheritance).
namespace pybind11 {
namespace detail {
template <>
struct type_caster<other_lib::MyType> : public other_lib::my_caster {};
} // namespace detail
} // namespace pybind11
TEST_SUBMODULE(custom_type_casters, m) {
// test_custom_type_casters
// test_noconvert_args
//
// Test converting. The ArgAlwaysConverts is just there to make the first no-conversion pass
// fail so that our call always ends up happening via the second dispatch (the one that allows
// some conversion).
class ArgInspector {
public:
ArgInspector1 f(ArgInspector1 a, ArgAlwaysConverts) { return a; }
std::string g(const ArgInspector1 &a,
const ArgInspector1 &b,
int c,
ArgInspector2 *d,
ArgAlwaysConverts) {
return a.arg + "\n" + b.arg + "\n" + std::to_string(c) + "\n" + d->arg;
}
static ArgInspector2 h(ArgInspector2 a, ArgAlwaysConverts) { return a; }
};
// [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works.
py::class_<ArgInspector>(m, "ArgInspector")
.def(py::init<>())
.def("f", &ArgInspector::f, py::arg(), py::arg() = ArgAlwaysConverts())
.def("g",
&ArgInspector::g,
"a"_a.noconvert(),
"b"_a,
"c"_a.noconvert() = 13,
"d"_a = ArgInspector2(),
py::arg() = ArgAlwaysConverts())
.def_static("h", &ArgInspector::h, py::arg{}.noconvert(), py::arg() = ArgAlwaysConverts());
m.def(
"arg_inspect_func",
[](const ArgInspector2 &a, const ArgInspector1 &b, ArgAlwaysConverts) {
return a.arg + "\n" + b.arg;
},
py::arg{}.noconvert(false),
py::arg_v(nullptr, ArgInspector1()).noconvert(true),
py::arg() = ArgAlwaysConverts());
m.def(
"floats_preferred", [](double f) { return 0.5 * f; }, "f"_a);
m.def(
"floats_only", [](double f) { return 0.5 * f; }, "f"_a.noconvert());
m.def(
"ints_preferred", [](int i) { return i / 2; }, "i"_a);
m.def(
"ints_only", [](int i) { return i / 2; }, "i"_a.noconvert());
// test_custom_caster_destruction
// Test that `take_ownership` works on types with a custom type caster when given a pointer
// default policy: don't take ownership:
m.def("custom_caster_no_destroy", []() {
static auto *dt = new DestructionTester();
return dt;
});
m.def(
"custom_caster_destroy",
[]() { return new DestructionTester(); },
py::return_value_policy::take_ownership); // Takes ownership: destroy when finished
m.def(
"custom_caster_destroy_const",
[]() -> const DestructionTester * { return new DestructionTester(); },
py::return_value_policy::take_ownership); // Likewise (const doesn't inhibit destruction)
m.def("destruction_tester_cstats",
&ConstructorStats::get<DestructionTester>,
py::return_value_policy::reference);
m.def("other_lib_type", [](other_lib::MyType x) { return x; });
}

View File

@@ -0,0 +1,96 @@
import pytest
from pybind11_tests import custom_type_casters as m
def test_noconvert_args(msg):
a = m.ArgInspector()
assert (msg(a.f("hi")) == """
loading ArgInspector1 argument WITH conversion allowed. Argument value = hi
""")
assert (msg(a.g("this is a", "this is b")) == """
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = this is a
loading ArgInspector1 argument WITH conversion allowed. Argument value = this is b
13
loading ArgInspector2 argument WITH conversion allowed. Argument value = (default arg inspector 2)
""")
assert (msg(a.g("this is a", "this is b", 42)) == """
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = this is a
loading ArgInspector1 argument WITH conversion allowed. Argument value = this is b
42
loading ArgInspector2 argument WITH conversion allowed. Argument value = (default arg inspector 2)
""")
assert (msg(a.g("this is a", "this is b", 42, "this is d")) == """
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = this is a
loading ArgInspector1 argument WITH conversion allowed. Argument value = this is b
42
loading ArgInspector2 argument WITH conversion allowed. Argument value = this is d
""")
assert (
a.h("arg 1") ==
"loading ArgInspector2 argument WITHOUT conversion allowed. Argument value = arg 1"
)
assert (msg(m.arg_inspect_func("A1", "A2")) == """
loading ArgInspector2 argument WITH conversion allowed. Argument value = A1
loading ArgInspector1 argument WITHOUT conversion allowed. Argument value = A2
""")
assert m.floats_preferred(4) == 2.0
assert m.floats_only(4.0) == 2.0
with pytest.raises(TypeError) as excinfo:
m.floats_only(4)
assert (msg(excinfo.value) == """
floats_only(): incompatible function arguments. The following argument types are supported:
1. (f: float) -> float
Invoked with: 4
""")
assert m.ints_preferred(4) == 2
assert m.ints_preferred(True) == 0
with pytest.raises(TypeError) as excinfo:
m.ints_preferred(4.0)
assert (msg(excinfo.value) == """
ints_preferred(): incompatible function arguments. The following argument types are supported:
1. (i: int) -> int
Invoked with: 4.0
""")
assert m.ints_only(4) == 2
with pytest.raises(TypeError) as excinfo:
m.ints_only(4.0)
assert (msg(excinfo.value) == """
ints_only(): incompatible function arguments. The following argument types are supported:
1. (i: int) -> int
Invoked with: 4.0
""")
def test_custom_caster_destruction():
"""Tests that returning a pointer to a type that gets converted with a custom type caster gets
destroyed when the function has py::return_value_policy::take_ownership policy applied."""
cstats = m.destruction_tester_cstats()
# This one *doesn't* have take_ownership: the pointer should be used but not destroyed:
z = m.custom_caster_no_destroy()
assert cstats.alive() == 1 and cstats.default_constructions == 1
assert z
# take_ownership applied: this constructs a new object, casts it, then destroys it:
z = m.custom_caster_destroy()
assert z
assert cstats.default_constructions == 2
# Same, but with a const pointer return (which should *not* inhibit destruction):
z = m.custom_caster_destroy_const()
assert z
assert cstats.default_constructions == 3
# Make sure we still only have the original object (from ..._no_destroy()) alive:
assert cstats.alive() == 1
def test_custom_caster_other_lib():
assert m.other_lib_type(True)

View File

@@ -0,0 +1,41 @@
/*
tests/test_custom_type_setup.cpp -- Tests `pybind11::custom_type_setup`
Copyright (c) Google LLC
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/pybind11.h>
#include "pybind11_tests.h"
namespace py = pybind11;
namespace {
struct OwnsPythonObjects {
py::object value = py::none();
};
} // namespace
TEST_SUBMODULE(custom_type_setup, m) {
py::class_<OwnsPythonObjects> cls(
m, "OwnsPythonObjects", py::custom_type_setup([](PyHeapTypeObject *heap_type) {
auto *type = &heap_type->ht_type;
type->tp_flags |= Py_TPFLAGS_HAVE_GC;
type->tp_traverse = [](PyObject *self_base, visitproc visit, void *arg) {
auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
Py_VISIT(self.value.ptr());
return 0;
};
type->tp_clear = [](PyObject *self_base) {
auto &self = py::cast<OwnsPythonObjects &>(py::handle(self_base));
self.value = py::none();
return 0;
};
}));
cls.def(py::init<>());
cls.def_readwrite("value", &OwnsPythonObjects::value);
}

View File

@@ -0,0 +1,48 @@
import gc
import weakref
import pytest
import env # noqa: F401
from pybind11_tests import custom_type_setup as m
@pytest.fixture
def gc_tester():
"""Tests that an object is garbage collected.
Assumes that any unreferenced objects are fully collected after calling
`gc.collect()`. That is true on CPython, but does not appear to reliably
hold on PyPy.
"""
weak_refs = []
def add_ref(obj):
# PyPy does not support `gc.is_tracked`.
if hasattr(gc, "is_tracked"):
assert gc.is_tracked(obj)
weak_refs.append(weakref.ref(obj))
yield add_ref
gc.collect()
for ref in weak_refs:
assert ref() is None
# PyPy does not seem to reliably garbage collect.
@pytest.mark.skipif("env.PYPY")
def test_self_cycle(gc_tester):
obj = m.OwnsPythonObjects()
obj.value = obj
gc_tester(obj)
# PyPy does not seem to reliably garbage collect.
@pytest.mark.skipif("env.PYPY")
def test_indirect_cycle(gc_tester):
obj = m.OwnsPythonObjects()
obj_list = [obj]
obj.value = obj_list
gc_tester(obj)

View File

@@ -0,0 +1,88 @@
/*
tests/test_docstring_options.cpp -- generation of docstrings and signatures
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
TEST_SUBMODULE(docstring_options, m) {
// test_docstring_options
{
py::options options;
options.disable_function_signatures();
m.def(
"test_function1", [](int, int) {}, py::arg("a"), py::arg("b"));
m.def(
"test_function2", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring");
m.def(
"test_overloaded1", [](int) {}, py::arg("i"), "Overload docstring");
m.def(
"test_overloaded1", [](double) {}, py::arg("d"));
m.def(
"test_overloaded2", [](int) {}, py::arg("i"), "overload docstring 1");
m.def(
"test_overloaded2", [](double) {}, py::arg("d"), "overload docstring 2");
m.def(
"test_overloaded3", [](int) {}, py::arg("i"));
m.def(
"test_overloaded3", [](double) {}, py::arg("d"), "Overload docstr");
options.enable_function_signatures();
m.def(
"test_function3", [](int, int) {}, py::arg("a"), py::arg("b"));
m.def(
"test_function4", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring");
options.disable_function_signatures().disable_user_defined_docstrings();
m.def(
"test_function5", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring");
{
py::options nested_options;
nested_options.enable_user_defined_docstrings();
m.def(
"test_function6",
[](int, int) {},
py::arg("a"),
py::arg("b"),
"A custom docstring");
}
}
m.def(
"test_function7", [](int, int) {}, py::arg("a"), py::arg("b"), "A custom docstring");
{
py::options options;
options.disable_user_defined_docstrings();
options.disable_function_signatures();
m.def("test_function8", []() {});
}
{
py::options options;
options.disable_user_defined_docstrings();
struct DocstringTestFoo {
int value;
void setValue(int v) { value = v; }
int getValue() const { return value; }
};
py::class_<DocstringTestFoo>(m, "DocstringTestFoo", "This is a class docstring")
.def_property("value_prop",
&DocstringTestFoo::getValue,
&DocstringTestFoo::setValue,
"This is a property docstring");
}
}

View File

@@ -0,0 +1,44 @@
from pybind11_tests import docstring_options as m
def test_docstring_options():
# options.disable_function_signatures()
assert not m.test_function1.__doc__
assert m.test_function2.__doc__ == "A custom docstring"
# docstring specified on just the first overload definition:
assert m.test_overloaded1.__doc__ == "Overload docstring"
# docstring on both overloads:
assert m.test_overloaded2.__doc__ == "overload docstring 1\noverload docstring 2"
# docstring on only second overload:
assert m.test_overloaded3.__doc__ == "Overload docstr"
# options.enable_function_signatures()
assert m.test_function3.__doc__.startswith(
"test_function3(a: int, b: int) -> None")
assert m.test_function4.__doc__.startswith(
"test_function4(a: int, b: int) -> None")
assert m.test_function4.__doc__.endswith("A custom docstring\n")
# options.disable_function_signatures()
# options.disable_user_defined_docstrings()
assert not m.test_function5.__doc__
# nested options.enable_user_defined_docstrings()
assert m.test_function6.__doc__ == "A custom docstring"
# RAII destructor
assert m.test_function7.__doc__.startswith(
"test_function7(a: int, b: int) -> None")
assert m.test_function7.__doc__.endswith("A custom docstring\n")
# when all options are disabled, no docstring (instead of an empty one) should be generated
assert m.test_function8.__doc__ is None
# Suppression of user-defined docstrings for non-function objects
assert not m.DocstringTestFoo.__doc__
assert not m.DocstringTestFoo.value_prop.__doc__

View File

@@ -0,0 +1,398 @@
/*
tests/eigen.cpp -- automatic conversion of Eigen types
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/eigen.h>
#include <pybind11/stl.h>
#include "constructor_stats.h"
#include "pybind11_tests.h"
#if defined(_MSC_VER)
# pragma warning(disable : 4996) // C4996: std::unary_negation is deprecated
#endif
#include <Eigen/Cholesky>
using MatrixXdR = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
// Sets/resets a testing reference matrix to have values of 10*r + c, where r and c are the
// (1-based) row/column number.
template <typename M>
void reset_ref(M &x) {
for (int i = 0; i < x.rows(); i++) {
for (int j = 0; j < x.cols(); j++) {
x(i, j) = 11 + 10 * i + j;
}
}
}
// Returns a static, column-major matrix
Eigen::MatrixXd &get_cm() {
static Eigen::MatrixXd *x;
if (!x) {
x = new Eigen::MatrixXd(3, 3);
reset_ref(*x);
}
return *x;
}
// Likewise, but row-major
MatrixXdR &get_rm() {
static MatrixXdR *x;
if (!x) {
x = new MatrixXdR(3, 3);
reset_ref(*x);
}
return *x;
}
// Resets the values of the static matrices returned by get_cm()/get_rm()
void reset_refs() {
reset_ref(get_cm());
reset_ref(get_rm());
}
// Returns element 2,1 from a matrix (used to test copy/nocopy)
double get_elem(const Eigen::Ref<const Eigen::MatrixXd> &m) { return m(2, 1); };
// Returns a matrix with 10*r + 100*c added to each matrix element (to help test that the matrix
// reference is referencing rows/columns correctly).
template <typename MatrixArgType>
Eigen::MatrixXd adjust_matrix(MatrixArgType m) {
Eigen::MatrixXd ret(m);
for (int c = 0; c < m.cols(); c++) {
for (int r = 0; r < m.rows(); r++) {
ret(r, c) += 10 * r + 100 * c; // NOLINT(clang-analyzer-core.uninitialized.Assign)
}
}
return ret;
}
struct CustomOperatorNew {
CustomOperatorNew() = default;
Eigen::Matrix4d a = Eigen::Matrix4d::Zero();
Eigen::Matrix4d b = Eigen::Matrix4d::Identity();
EIGEN_MAKE_ALIGNED_OPERATOR_NEW;
};
TEST_SUBMODULE(eigen, m) {
using FixedMatrixR = Eigen::Matrix<float, 5, 6, Eigen::RowMajor>;
using FixedMatrixC = Eigen::Matrix<float, 5, 6>;
using DenseMatrixR = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
using DenseMatrixC = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>;
using FourRowMatrixC = Eigen::Matrix<float, 4, Eigen::Dynamic>;
using FourColMatrixC = Eigen::Matrix<float, Eigen::Dynamic, 4>;
using FourRowMatrixR = Eigen::Matrix<float, 4, Eigen::Dynamic>;
using FourColMatrixR = Eigen::Matrix<float, Eigen::Dynamic, 4>;
using SparseMatrixR = Eigen::SparseMatrix<float, Eigen::RowMajor>;
using SparseMatrixC = Eigen::SparseMatrix<float>;
// various tests
m.def("double_col", [](const Eigen::VectorXf &x) -> Eigen::VectorXf { return 2.0f * x; });
m.def("double_row",
[](const Eigen::RowVectorXf &x) -> Eigen::RowVectorXf { return 2.0f * x; });
m.def("double_complex",
[](const Eigen::VectorXcf &x) -> Eigen::VectorXcf { return 2.0f * x; });
m.def("double_threec", [](py::EigenDRef<Eigen::Vector3f> x) { x *= 2; });
m.def("double_threer", [](py::EigenDRef<Eigen::RowVector3f> x) { x *= 2; });
m.def("double_mat_cm", [](const Eigen::MatrixXf &x) -> Eigen::MatrixXf { return 2.0f * x; });
m.def("double_mat_rm", [](const 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",
[](const Eigen::Ref<MatrixXdR> &x) -> Eigen::MatrixXd { return x.llt().matrixL(); });
m.def("cholesky2", [](const Eigen::Ref<const MatrixXdR> &x) -> Eigen::MatrixXd {
return x.llt().matrixL();
});
m.def("cholesky3",
[](const Eigen::Ref<MatrixXdR> &x) -> Eigen::MatrixXd { return x.llt().matrixL(); });
m.def("cholesky4", [](const Eigen::Ref<const MatrixXdR> &x) -> Eigen::MatrixXd {
return x.llt().matrixL();
});
// test_eigen_ref_mutators
// Mutators: these add some value to the given element using Eigen, but Eigen should be mapping
// into the numpy array data and so the result should show up there. There are three versions:
// one that works on a contiguous-row matrix (numpy's default), one for a contiguous-column
// matrix, and one for any matrix.
auto add_rm = [](Eigen::Ref<MatrixXdR> x, int r, int c, double v) { x(r, c) += v; };
auto add_cm = [](Eigen::Ref<Eigen::MatrixXd> x, int r, int c, double v) { x(r, c) += v; };
// 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
// Overloaded versions that will accept either row or column contiguous:
m.def("add1", add_rm);
m.def("add1", add_cm);
m.def("add2", add_cm);
m.def("add2", add_rm);
// This one accepts a matrix of any stride:
m.def("add_any",
[](py::EigenDRef<Eigen::MatrixXd> x, int r, int c, double v) { x(r, c) += v; });
// Return mutable references (numpy maps into eigen variables)
m.def("get_cm_ref", []() { return Eigen::Ref<Eigen::MatrixXd>(get_cm()); });
m.def("get_rm_ref", []() { return Eigen::Ref<MatrixXdR>(get_rm()); });
// The same references, but non-mutable (numpy maps into eigen variables, but is !writeable)
m.def("get_cm_const_ref", []() { return Eigen::Ref<const Eigen::MatrixXd>(get_cm()); });
m.def("get_rm_const_ref", []() { return Eigen::Ref<const MatrixXdR>(get_rm()); });
m.def("reset_refs", reset_refs); // Restores get_{cm,rm}_ref to original values
// Increments and returns ref to (same) matrix
m.def(
"incr_matrix",
[](Eigen::Ref<Eigen::MatrixXd> m, double v) {
m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v);
return m;
},
py::return_value_policy::reference);
// Same, but accepts a matrix of any strides
m.def(
"incr_matrix_any",
[](py::EigenDRef<Eigen::MatrixXd> m, double v) {
m += Eigen::MatrixXd::Constant(m.rows(), m.cols(), v);
return m;
},
py::return_value_policy::reference);
// Returns an eigen slice of even rows
m.def(
"even_rows",
[](py::EigenDRef<Eigen::MatrixXd> m) {
return py::EigenDMap<Eigen::MatrixXd>(
m.data(),
(m.rows() + 1) / 2,
m.cols(),
py::EigenDStride(m.outerStride(), 2 * m.innerStride()));
},
py::return_value_policy::reference);
// Returns an eigen slice of even columns
m.def(
"even_cols",
[](py::EigenDRef<Eigen::MatrixXd> m) {
return py::EigenDMap<Eigen::MatrixXd>(
m.data(),
m.rows(),
(m.cols() + 1) / 2,
py::EigenDStride(2 * m.outerStride(), m.innerStride()));
},
py::return_value_policy::reference);
// Returns diagonals: a vector-like object with an inner stride != 1
m.def("diagonal", [](const Eigen::Ref<const Eigen::MatrixXd> &x) { return x.diagonal(); });
m.def("diagonal_1",
[](const Eigen::Ref<const Eigen::MatrixXd> &x) { return x.diagonal<1>(); });
m.def("diagonal_n",
[](const Eigen::Ref<const Eigen::MatrixXd> &x, int index) { return x.diagonal(index); });
// Return a block of a matrix (gives non-standard strides)
m.def("block",
[](const Eigen::Ref<const Eigen::MatrixXd> &x,
int start_row,
int start_col,
int block_rows,
int block_cols) { return x.block(start_row, start_col, block_rows, block_cols); });
// test_eigen_return_references, test_eigen_keepalive
// return value referencing/copying tests:
class ReturnTester {
Eigen::MatrixXd mat = create();
public:
ReturnTester() { print_created(this); }
~ReturnTester() { print_destroyed(this); }
static Eigen::MatrixXd create() { return Eigen::MatrixXd::Ones(10, 10); }
// NOLINTNEXTLINE(readability-const-return-type)
static const Eigen::MatrixXd createConst() { return Eigen::MatrixXd::Ones(10, 10); }
Eigen::MatrixXd &get() { return mat; }
Eigen::MatrixXd *getPtr() { return &mat; }
const Eigen::MatrixXd &view() { return mat; }
const Eigen::MatrixXd *viewPtr() { return &mat; }
Eigen::Ref<Eigen::MatrixXd> ref() { return mat; }
Eigen::Ref<const Eigen::MatrixXd> refConst() { return mat; }
Eigen::Block<Eigen::MatrixXd> block(int r, int c, int nrow, int ncol) {
return mat.block(r, c, nrow, ncol);
}
Eigen::Block<const Eigen::MatrixXd> blockConst(int r, int c, int nrow, int ncol) const {
return mat.block(r, c, nrow, ncol);
}
py::EigenDMap<Eigen::Matrix2d> corners() {
return py::EigenDMap<Eigen::Matrix2d>(
mat.data(),
py::EigenDStride(mat.outerStride() * (mat.outerSize() - 1),
mat.innerStride() * (mat.innerSize() - 1)));
}
py::EigenDMap<const Eigen::Matrix2d> cornersConst() const {
return py::EigenDMap<const Eigen::Matrix2d>(
mat.data(),
py::EigenDStride(mat.outerStride() * (mat.outerSize() - 1),
mat.innerStride() * (mat.innerSize() - 1)));
}
};
using rvp = py::return_value_policy;
py::class_<ReturnTester>(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_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);
// test_special_matrix_objects
// Returns a DiagonalMatrix with diagonal (1,2,3,...)
m.def("incr_diag", [](int k) {
Eigen::DiagonalMatrix<int, Eigen::Dynamic> m(k);
for (int i = 0; i < k; i++) {
m.diagonal()[i] = i + 1;
}
return m;
});
// Returns a SelfAdjointView referencing the lower triangle of m
m.def("symmetric_lower",
[](const Eigen::MatrixXi &m) { return m.selfadjointView<Eigen::Lower>(); });
// Returns a SelfAdjointView referencing the lower triangle of m
m.def("symmetric_upper",
[](const Eigen::MatrixXi &m) { return m.selfadjointView<Eigen::Upper>(); });
// Test matrix for various functions below.
Eigen::MatrixXf mat(5, 6);
mat << 0, 3, 0, 0, 0, 11, 22, 0, 0, 0, 17, 11, 7, 5, 0, 1, 0, 11, 0, 0, 0, 0, 0, 11, 0, 0, 14,
0, 8, 11;
// test_fixed, and various other tests
m.def("fixed_r", [mat]() -> FixedMatrixR { return FixedMatrixR(mat); });
// Our Eigen does a hack which respects constness through the numpy writeable flag.
// Therefore, the const return actually affects this type despite being an rvalue.
// NOLINTNEXTLINE(readability-const-return-type)
m.def("fixed_r_const", [mat]() -> const FixedMatrixR { return FixedMatrixR(mat); });
m.def("fixed_c", [mat]() -> FixedMatrixC { return FixedMatrixC(mat); });
m.def("fixed_copy_r", [](const FixedMatrixR &m) -> FixedMatrixR { return m; });
m.def("fixed_copy_c", [](const FixedMatrixC &m) -> FixedMatrixC { return m; });
// test_mutator_descriptors
m.def("fixed_mutator_r", [](const Eigen::Ref<FixedMatrixR> &) {});
m.def("fixed_mutator_c", [](const Eigen::Ref<FixedMatrixC> &) {});
m.def("fixed_mutator_a", [](const py::EigenDRef<FixedMatrixC> &) {});
// test_dense
m.def("dense_r", [mat]() -> DenseMatrixR { return DenseMatrixR(mat); });
m.def("dense_c", [mat]() -> DenseMatrixC { return DenseMatrixC(mat); });
m.def("dense_copy_r", [](const DenseMatrixR &m) -> DenseMatrixR { return m; });
m.def("dense_copy_c", [](const DenseMatrixC &m) -> DenseMatrixC { return m; });
// test_sparse, test_sparse_signature
m.def("sparse_r", [mat]() -> SparseMatrixR {
// NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn)
return Eigen::SparseView<Eigen::MatrixXf>(mat);
});
m.def("sparse_c",
[mat]() -> SparseMatrixC { return Eigen::SparseView<Eigen::MatrixXf>(mat); });
m.def("sparse_copy_r", [](const SparseMatrixR &m) -> SparseMatrixR { return m; });
m.def("sparse_copy_c", [](const SparseMatrixC &m) -> SparseMatrixC { return m; });
// test_partially_fixed
m.def("partial_copy_four_rm_r", [](const FourRowMatrixR &m) -> FourRowMatrixR { return m; });
m.def("partial_copy_four_rm_c", [](const FourColMatrixR &m) -> FourColMatrixR { return m; });
m.def("partial_copy_four_cm_r", [](const FourRowMatrixC &m) -> FourRowMatrixC { return m; });
m.def("partial_copy_four_cm_c", [](const FourColMatrixC &m) -> FourColMatrixC { return m; });
// test_cpp_casting
// Test that we can cast a numpy object to a Eigen::MatrixXd explicitly
m.def("cpp_copy", [](py::handle m) { return m.cast<Eigen::MatrixXd>()(1, 0); });
m.def("cpp_ref_c", [](py::handle m) { return m.cast<Eigen::Ref<Eigen::MatrixXd>>()(1, 0); });
m.def("cpp_ref_r", [](py::handle m) { return m.cast<Eigen::Ref<MatrixXdR>>()(1, 0); });
m.def("cpp_ref_any",
[](py::handle m) { return m.cast<py::EigenDRef<Eigen::MatrixXd>>()(1, 0); });
// [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works.
// test_nocopy_wrapper
// Test that we can prevent copying into an argument that would normally copy: First a version
// that would allow copying (if types or strides don't match) for comparison:
m.def("get_elem", &get_elem);
// Now this alternative that calls the tells pybind to fail rather than copy:
m.def(
"get_elem_nocopy",
[](const Eigen::Ref<const Eigen::MatrixXd> &m) -> double { return get_elem(m); },
py::arg{}.noconvert());
// Also test a row-major-only no-copy const ref:
m.def(
"get_elem_rm_nocopy",
[](Eigen::Ref<const Eigen::Matrix<long, -1, -1, Eigen::RowMajor>> &m) -> long {
return m(2, 1);
},
py::arg{}.noconvert());
// test_issue738
// Issue #738: 1xN or Nx1 2D matrices were neither accepted nor properly copied with an
// incompatible stride value on the length-1 dimension--but that should be allowed (without
// requiring a copy!) because the stride value can be safely ignored on a size-1 dimension.
m.def("iss738_f1",
&adjust_matrix<const Eigen::Ref<const Eigen::MatrixXd> &>,
py::arg{}.noconvert());
m.def("iss738_f2",
&adjust_matrix<const Eigen::Ref<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>> &>,
py::arg{}.noconvert());
// test_issue1105
// Issue #1105: when converting from a numpy two-dimensional (Nx1) or (1xN) value into a dense
// eigen Vector or RowVector, the argument would fail to load because the numpy copy would
// fail: numpy won't broadcast a Nx1 into a 1-dimensional vector.
m.def("iss1105_col", [](const Eigen::VectorXd &) { return true; });
m.def("iss1105_row", [](const Eigen::RowVectorXd &) { return true; });
// test_named_arguments
// Make sure named arguments are working properly:
m.def(
"matrix_multiply",
[](const py::EigenDRef<const Eigen::MatrixXd> &A,
const py::EigenDRef<const Eigen::MatrixXd> &B) -> Eigen::MatrixXd {
if (A.cols() != B.rows()) {
throw std::domain_error("Nonconformable matrices!");
}
return A * B;
},
py::arg("A"),
py::arg("B"));
// test_custom_operator_new
py::class_<CustomOperatorNew>(m, "CustomOperatorNew")
.def(py::init<>())
.def_readonly("a", &CustomOperatorNew::a)
.def_readonly("b", &CustomOperatorNew::b);
// test_eigen_ref_life_support
// In case of a failure (the caster's temp array does not live long enough), creating
// a new array (np.ones(10)) increases the chances that the temp array will be garbage
// collected and/or that its memory will be overridden with different values.
m.def("get_elem_direct", [](const Eigen::Ref<const Eigen::VectorXd> &v) {
py::module_::import("numpy").attr("ones")(10);
return v(5);
});
m.def("get_elem_indirect", [](std::vector<Eigen::Ref<const Eigen::VectorXd>> v) {
py::module_::import("numpy").attr("ones")(10);
return v[0](5);
});
}

741
third_party/pybind11/tests/test_eigen.py vendored Normal file
View File

@@ -0,0 +1,741 @@
import pytest
from pybind11_tests import ConstructorStats
np = pytest.importorskip("numpy")
m = pytest.importorskip("pybind11_tests.eigen")
ref = np.array([
[0.0, 3, 0, 0, 0, 11],
[22, 0, 0, 0, 17, 11],
[7, 5, 0, 1, 0, 11],
[0, 0, 0, 0, 0, 11],
[0, 0, 14, 0, 8, 11],
])
def assert_equal_ref(mat):
np.testing.assert_array_equal(mat, ref)
def assert_sparse_equal_ref(sparse_mat):
assert_equal_ref(sparse_mat.toarray())
def test_fixed():
assert_equal_ref(m.fixed_c())
assert_equal_ref(m.fixed_r())
assert_equal_ref(m.fixed_copy_r(m.fixed_r()))
assert_equal_ref(m.fixed_copy_c(m.fixed_c()))
assert_equal_ref(m.fixed_copy_r(m.fixed_c()))
assert_equal_ref(m.fixed_copy_c(m.fixed_r()))
def test_dense():
assert_equal_ref(m.dense_r())
assert_equal_ref(m.dense_c())
assert_equal_ref(m.dense_copy_r(m.dense_r()))
assert_equal_ref(m.dense_copy_c(m.dense_c()))
assert_equal_ref(m.dense_copy_r(m.dense_c()))
assert_equal_ref(m.dense_copy_c(m.dense_r()))
def test_partially_fixed():
ref2 = np.array(
[[0.0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11], [12, 13, 14, 15]])
np.testing.assert_array_equal(m.partial_copy_four_rm_r(ref2), ref2)
np.testing.assert_array_equal(m.partial_copy_four_rm_c(ref2), ref2)
np.testing.assert_array_equal(
m.partial_copy_four_rm_r(ref2[:, 1]), ref2[:, [1]])
np.testing.assert_array_equal(
m.partial_copy_four_rm_c(ref2[0, :]), ref2[[0], :])
np.testing.assert_array_equal(
m.partial_copy_four_rm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)])
np.testing.assert_array_equal(
m.partial_copy_four_rm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :])
np.testing.assert_array_equal(m.partial_copy_four_cm_r(ref2), ref2)
np.testing.assert_array_equal(m.partial_copy_four_cm_c(ref2), ref2)
np.testing.assert_array_equal(
m.partial_copy_four_cm_r(ref2[:, 1]), ref2[:, [1]])
np.testing.assert_array_equal(
m.partial_copy_four_cm_c(ref2[0, :]), ref2[[0], :])
np.testing.assert_array_equal(
m.partial_copy_four_cm_r(ref2[:, (0, 2)]), ref2[:, (0, 2)])
np.testing.assert_array_equal(
m.partial_copy_four_cm_c(ref2[(3, 1, 2), :]), ref2[(3, 1, 2), :])
# TypeError should be raise for a shape mismatch
functions = [
m.partial_copy_four_rm_r,
m.partial_copy_four_rm_c,
m.partial_copy_four_cm_r,
m.partial_copy_four_cm_c,
]
matrix_with_wrong_shape = [[1, 2], [3, 4]]
for f in functions:
with pytest.raises(TypeError) as excinfo:
f(matrix_with_wrong_shape)
assert "incompatible function arguments" in str(excinfo.value)
def test_mutator_descriptors():
zr = np.arange(30, dtype="float32").reshape(5, 6) # row-major
zc = zr.reshape(6, 5).transpose() # column-major
m.fixed_mutator_r(zr)
m.fixed_mutator_c(zc)
m.fixed_mutator_a(zr)
m.fixed_mutator_a(zc)
with pytest.raises(TypeError) as excinfo:
m.fixed_mutator_r(zc)
assert (
"(arg0: numpy.ndarray[numpy.float32[5, 6],"
" flags.writeable, flags.c_contiguous]) -> None" in str(excinfo.value))
with pytest.raises(TypeError) as excinfo:
m.fixed_mutator_c(zr)
assert (
"(arg0: numpy.ndarray[numpy.float32[5, 6],"
" flags.writeable, flags.f_contiguous]) -> None" in str(excinfo.value))
with pytest.raises(TypeError) as excinfo:
m.fixed_mutator_a(np.array([[1, 2], [3, 4]], dtype="float32"))
assert "(arg0: numpy.ndarray[numpy.float32[5, 6], flags.writeable]) -> None" in str(
excinfo.value)
zr.flags.writeable = False
with pytest.raises(TypeError):
m.fixed_mutator_r(zr)
with pytest.raises(TypeError):
m.fixed_mutator_a(zr)
def test_cpp_casting():
assert m.cpp_copy(m.fixed_r()) == 22.0
assert m.cpp_copy(m.fixed_c()) == 22.0
z = np.array([[5.0, 6], [7, 8]])
assert m.cpp_copy(z) == 7.0
assert m.cpp_copy(m.get_cm_ref()) == 21.0
assert m.cpp_copy(m.get_rm_ref()) == 21.0
assert m.cpp_ref_c(m.get_cm_ref()) == 21.0
assert m.cpp_ref_r(m.get_rm_ref()) == 21.0
with pytest.raises(RuntimeError) as excinfo:
# Can't reference m.fixed_c: it contains floats, m.cpp_ref_any wants doubles
m.cpp_ref_any(m.fixed_c())
assert "Unable to cast Python instance" in str(excinfo.value)
with pytest.raises(RuntimeError) as excinfo:
# Can't reference m.fixed_r: it contains floats, m.cpp_ref_any wants doubles
m.cpp_ref_any(m.fixed_r())
assert "Unable to cast Python instance" in str(excinfo.value)
assert m.cpp_ref_any(m.ReturnTester.create()) == 1.0
assert m.cpp_ref_any(m.get_cm_ref()) == 21.0
assert m.cpp_ref_any(m.get_cm_ref()) == 21.0
def test_pass_readonly_array():
z = np.full((5, 6), 42.0)
z.flags.writeable = False
np.testing.assert_array_equal(z, m.fixed_copy_r(z))
np.testing.assert_array_equal(m.fixed_r_const(), m.fixed_r())
assert not m.fixed_r_const().flags.writeable
np.testing.assert_array_equal(
m.fixed_copy_r(m.fixed_r_const()), m.fixed_r_const())
def test_nonunit_stride_from_python():
counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3))
second_row = counting_mat[1, :]
second_col = counting_mat[:, 1]
np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row)
np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row)
np.testing.assert_array_equal(
m.double_complex(second_row), 2.0 * second_row)
np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col)
np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col)
np.testing.assert_array_equal(
m.double_complex(second_col), 2.0 * second_col)
counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3))
slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]]
for ref_mat in slices:
np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat)
np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat)
# Mutator:
m.double_threer(second_row)
m.double_threec(second_col)
np.testing.assert_array_equal(counting_mat,
[[0.0, 2, 2], [6, 16, 10], [6, 14, 8]])
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
exception will be thrown as Eigen will not be able to map the numpy array."""
counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3))
counting_mat = counting_mat[::-1, ::-1]
second_row = counting_mat[1, :]
second_col = counting_mat[:, 1]
np.testing.assert_array_equal(m.double_row(second_row), 2.0 * second_row)
np.testing.assert_array_equal(m.double_col(second_row), 2.0 * second_row)
np.testing.assert_array_equal(
m.double_complex(second_row), 2.0 * second_row)
np.testing.assert_array_equal(m.double_row(second_col), 2.0 * second_col)
np.testing.assert_array_equal(m.double_col(second_col), 2.0 * second_col)
np.testing.assert_array_equal(
m.double_complex(second_col), 2.0 * second_col)
counting_3d = np.arange(27.0, dtype=np.float32).reshape((3, 3, 3))
counting_3d = counting_3d[::-1, ::-1, ::-1]
slices = [counting_3d[0, :, :], counting_3d[:, 0, :], counting_3d[:, :, 0]]
for ref_mat in slices:
np.testing.assert_array_equal(m.double_mat_cm(ref_mat), 2.0 * ref_mat)
np.testing.assert_array_equal(m.double_mat_rm(ref_mat), 2.0 * ref_mat)
# Mutator:
with pytest.raises(TypeError) as excinfo:
m.double_threer(second_row)
assert (msg(excinfo.value) == """
double_threer(): incompatible function arguments. The following argument types are supported:
1. (arg0: numpy.ndarray[numpy.float32[1, 3], flags.writeable]) -> None
Invoked with: """ + repr(np.array(
[5.0, 4.0, 3.0], dtype="float32")))
with pytest.raises(TypeError) as excinfo:
m.double_threec(second_col)
assert (msg(excinfo.value) == """
double_threec(): incompatible function arguments. The following argument types are supported:
1. (arg0: numpy.ndarray[numpy.float32[3, 1], flags.writeable]) -> None
Invoked with: """ + repr(np.array(
[7.0, 4.0, 1.0], dtype="float32")))
def test_nonunit_stride_to_python():
assert np.all(m.diagonal(ref) == ref.diagonal())
assert np.all(m.diagonal_1(ref) == ref.diagonal(1))
for i in range(-5, 7):
assert np.all(
m.diagonal_n(ref, i) == ref.diagonal(i)), f"m.diagonal_n({i})"
assert np.all(m.block(ref, 2, 1, 3, 3) == ref[2:5, 1:4])
assert np.all(m.block(ref, 1, 4, 4, 2) == ref[1:, 4:])
assert np.all(m.block(ref, 1, 4, 3, 2) == ref[1:4, 4:])
def test_eigen_ref_to_python():
chols = [m.cholesky1, m.cholesky2, m.cholesky3, m.cholesky4]
for i, chol in enumerate(chols, start=1):
mymat = chol(np.array([[1.0, 2, 4], [2, 13, 23], [4, 23, 77]]))
assert np.all(mymat == np.array(
[[1, 0, 0], [2, 3, 0], [4, 5, 6]])), f"cholesky{i}"
def assign_both(a1, a2, r, c, v):
a1[r, c] = v
a2[r, c] = v
def array_copy_but_one(a, r, c, v):
z = np.array(a, copy=True)
z[r, c] = v
return z
def test_eigen_return_references():
"""Tests various ways of returning references and non-referencing copies"""
master = np.ones((10, 10))
a = m.ReturnTester()
a_get1 = a.get()
assert not a_get1.flags.owndata and a_get1.flags.writeable
assign_both(a_get1, master, 3, 3, 5)
a_get2 = a.get_ptr()
assert not a_get2.flags.owndata and a_get2.flags.writeable
assign_both(a_get1, master, 2, 3, 6)
a_view1 = a.view()
assert not a_view1.flags.owndata and not a_view1.flags.writeable
with pytest.raises(ValueError):
a_view1[2, 3] = 4
a_view2 = a.view_ptr()
assert not a_view2.flags.owndata and not a_view2.flags.writeable
with pytest.raises(ValueError):
a_view2[2, 3] = 4
a_copy1 = a.copy_get()
assert a_copy1.flags.owndata and a_copy1.flags.writeable
np.testing.assert_array_equal(a_copy1, master)
a_copy1[7, 7] = -44 # Shouldn't affect anything else
c1want = array_copy_but_one(master, 7, 7, -44)
a_copy2 = a.copy_view()
assert a_copy2.flags.owndata and a_copy2.flags.writeable
np.testing.assert_array_equal(a_copy2, master)
a_copy2[4, 4] = -22 # Shouldn't affect anything else
c2want = array_copy_but_one(master, 4, 4, -22)
a_ref1 = a.ref()
assert not a_ref1.flags.owndata and a_ref1.flags.writeable
assign_both(a_ref1, master, 1, 1, 15)
a_ref2 = a.ref_const()
assert not a_ref2.flags.owndata and not a_ref2.flags.writeable
with pytest.raises(ValueError):
a_ref2[5, 5] = 33
a_ref3 = a.ref_safe()
assert not a_ref3.flags.owndata and a_ref3.flags.writeable
assign_both(a_ref3, master, 0, 7, 99)
a_ref4 = a.ref_const_safe()
assert not a_ref4.flags.owndata and not a_ref4.flags.writeable
with pytest.raises(ValueError):
a_ref4[7, 0] = 987654321
a_copy3 = a.copy_ref()
assert a_copy3.flags.owndata and a_copy3.flags.writeable
np.testing.assert_array_equal(a_copy3, master)
a_copy3[8, 1] = 11
c3want = array_copy_but_one(master, 8, 1, 11)
a_copy4 = a.copy_ref_const()
assert a_copy4.flags.owndata and a_copy4.flags.writeable
np.testing.assert_array_equal(a_copy4, master)
a_copy4[8, 4] = 88
c4want = array_copy_but_one(master, 8, 4, 88)
a_block1 = a.block(3, 3, 2, 2)
assert not a_block1.flags.owndata and a_block1.flags.writeable
a_block1[0, 0] = 55
master[3, 3] = 55
a_block2 = a.block_safe(2, 2, 3, 2)
assert not a_block2.flags.owndata and a_block2.flags.writeable
a_block2[2, 1] = -123
master[4, 3] = -123
a_block3 = a.block_const(6, 7, 4, 3)
assert not a_block3.flags.owndata and not a_block3.flags.writeable
with pytest.raises(ValueError):
a_block3[2, 2] = -44444
a_copy5 = a.copy_block(2, 2, 2, 3)
assert a_copy5.flags.owndata and a_copy5.flags.writeable
np.testing.assert_array_equal(a_copy5, master[2:4, 2:5])
a_copy5[1, 1] = 777
c5want = array_copy_but_one(master[2:4, 2:5], 1, 1, 777)
a_corn1 = a.corners()
assert not a_corn1.flags.owndata and a_corn1.flags.writeable
a_corn1 *= 50
a_corn1[1, 1] = 999
master[0, 0] = 50
master[0, 9] = 50
master[9, 0] = 50
master[9, 9] = 999
a_corn2 = a.corners_const()
assert not a_corn2.flags.owndata and not a_corn2.flags.writeable
with pytest.raises(ValueError):
a_corn2[1, 0] = 51
# All of the changes made all the way along should be visible everywhere
# now (except for the copies, of course)
np.testing.assert_array_equal(a_get1, master)
np.testing.assert_array_equal(a_get2, master)
np.testing.assert_array_equal(a_view1, master)
np.testing.assert_array_equal(a_view2, master)
np.testing.assert_array_equal(a_ref1, master)
np.testing.assert_array_equal(a_ref2, master)
np.testing.assert_array_equal(a_ref3, master)
np.testing.assert_array_equal(a_ref4, master)
np.testing.assert_array_equal(a_block1, master[3:5, 3:5])
np.testing.assert_array_equal(a_block2, master[2:5, 2:4])
np.testing.assert_array_equal(a_block3, master[6:10, 7:10])
np.testing.assert_array_equal(
a_corn1, master[0::master.shape[0] - 1, 0::master.shape[1] - 1])
np.testing.assert_array_equal(
a_corn2, master[0::master.shape[0] - 1, 0::master.shape[1] - 1])
np.testing.assert_array_equal(a_copy1, c1want)
np.testing.assert_array_equal(a_copy2, c2want)
np.testing.assert_array_equal(a_copy3, c3want)
np.testing.assert_array_equal(a_copy4, c4want)
np.testing.assert_array_equal(a_copy5, c5want)
def assert_keeps_alive(cl, method, *args):
cstats = ConstructorStats.get(cl)
start_with = cstats.alive()
a = cl()
assert cstats.alive() == start_with + 1
z = method(a, *args)
assert cstats.alive() == start_with + 1
del a
# Here's the keep alive in action:
assert cstats.alive() == start_with + 1
del z
# Keep alive should have expired:
assert cstats.alive() == start_with
def test_eigen_keepalive():
a = m.ReturnTester()
cstats = ConstructorStats.get(m.ReturnTester)
assert cstats.alive() == 1
unsafe = [a.ref(), a.ref_const(), a.block(1, 2, 3, 4)]
copies = [
a.copy_get(),
a.copy_view(),
a.copy_ref(),
a.copy_ref_const(),
a.copy_block(4, 3, 2, 1),
]
del a
assert cstats.alive() == 0
del unsafe
del copies
for meth in [
m.ReturnTester.get,
m.ReturnTester.get_ptr,
m.ReturnTester.view,
m.ReturnTester.view_ptr,
m.ReturnTester.ref_safe,
m.ReturnTester.ref_const_safe,
m.ReturnTester.corners,
m.ReturnTester.corners_const,
]:
assert_keeps_alive(m.ReturnTester, meth)
for meth in [m.ReturnTester.block_safe, m.ReturnTester.block_const]:
assert_keeps_alive(m.ReturnTester, meth, 4, 3, 2, 1)
def test_eigen_ref_mutators():
"""Tests Eigen's ability to mutate numpy values"""
orig = np.array([[1.0, 2, 3], [4, 5, 6], [7, 8, 9]])
zr = np.array(orig)
zc = np.array(orig, order="F")
m.add_rm(zr, 1, 0, 100)
assert np.all(zr == np.array([[1.0, 2, 3], [104, 5, 6], [7, 8, 9]]))
m.add_cm(zc, 1, 0, 200)
assert np.all(zc == np.array([[1.0, 2, 3], [204, 5, 6], [7, 8, 9]]))
m.add_any(zr, 1, 0, 20)
assert np.all(zr == np.array([[1.0, 2, 3], [124, 5, 6], [7, 8, 9]]))
m.add_any(zc, 1, 0, 10)
assert np.all(zc == np.array([[1.0, 2, 3], [214, 5, 6], [7, 8, 9]]))
# Can't reference a col-major array with a row-major Ref, and vice versa:
with pytest.raises(TypeError):
m.add_rm(zc, 1, 0, 1)
with pytest.raises(TypeError):
m.add_cm(zr, 1, 0, 1)
# Overloads:
m.add1(zr, 1, 0, -100)
m.add2(zr, 1, 0, -20)
assert np.all(zr == orig)
m.add1(zc, 1, 0, -200)
m.add2(zc, 1, 0, -10)
assert np.all(zc == orig)
# a non-contiguous slice (this won't work on either the row- or
# column-contiguous refs, but should work for the any)
cornersr = zr[0::2, 0::2]
cornersc = zc[0::2, 0::2]
assert np.all(cornersr == np.array([[1.0, 3], [7, 9]]))
assert np.all(cornersc == np.array([[1.0, 3], [7, 9]]))
with pytest.raises(TypeError):
m.add_rm(cornersr, 0, 1, 25)
with pytest.raises(TypeError):
m.add_cm(cornersr, 0, 1, 25)
with pytest.raises(TypeError):
m.add_rm(cornersc, 0, 1, 25)
with pytest.raises(TypeError):
m.add_cm(cornersc, 0, 1, 25)
m.add_any(cornersr, 0, 1, 25)
m.add_any(cornersc, 0, 1, 44)
assert np.all(zr == np.array([[1.0, 2, 28], [4, 5, 6], [7, 8, 9]]))
assert np.all(zc == np.array([[1.0, 2, 47], [4, 5, 6], [7, 8, 9]]))
# You shouldn't be allowed to pass a non-writeable array to a mutating Eigen method:
zro = zr[0:4, 0:4]
zro.flags.writeable = False
with pytest.raises(TypeError):
m.add_rm(zro, 0, 0, 0)
with pytest.raises(TypeError):
m.add_any(zro, 0, 0, 0)
with pytest.raises(TypeError):
m.add1(zro, 0, 0, 0)
with pytest.raises(TypeError):
m.add2(zro, 0, 0, 0)
# integer array shouldn't be passable to a double-matrix-accepting mutating func:
zi = np.array([[1, 2], [3, 4]])
with pytest.raises(TypeError):
m.add_rm(zi)
def test_numpy_ref_mutators():
"""Tests numpy mutating Eigen matrices (for returned Eigen::Ref<...>s)"""
m.reset_refs() # In case another test already changed it
zc = m.get_cm_ref()
zcro = m.get_cm_const_ref()
zr = m.get_rm_ref()
zrro = m.get_rm_const_ref()
assert [zc[1, 2], zcro[1, 2], zr[1, 2], zrro[1, 2]] == [23] * 4
assert not zc.flags.owndata and zc.flags.writeable
assert not zr.flags.owndata and zr.flags.writeable
assert not zcro.flags.owndata and not zcro.flags.writeable
assert not zrro.flags.owndata and not zrro.flags.writeable
zc[1, 2] = 99
expect = np.array([[11.0, 12, 13], [21, 22, 99], [31, 32, 33]])
# We should have just changed zc, of course, but also zcro and the original eigen matrix
assert np.all(zc == expect)
assert np.all(zcro == expect)
assert np.all(m.get_cm_ref() == expect)
zr[1, 2] = 99
assert np.all(zr == expect)
assert np.all(zrro == expect)
assert np.all(m.get_rm_ref() == expect)
# Make sure the readonly ones are numpy-readonly:
with pytest.raises(ValueError):
zcro[1, 2] = 6
with pytest.raises(ValueError):
zrro[1, 2] = 6
# We should be able to explicitly copy like this (and since we're copying,
# the const should drop away)
y1 = np.array(m.get_cm_const_ref())
assert y1.flags.owndata and y1.flags.writeable
# We should get copies of the eigen data, which was modified above:
assert y1[1, 2] == 99
y1[1, 2] += 12
assert y1[1, 2] == 111
assert zc[1, 2] == 99 # Make sure we aren't referencing the original
def test_both_ref_mutators():
"""Tests a complex chain of nested eigen/numpy references"""
m.reset_refs() # In case another test already changed it
z = m.get_cm_ref() # numpy -> eigen
z[0, 2] -= 3
z2 = m.incr_matrix(z, 1) # numpy -> eigen -> numpy -> eigen
z2[1, 1] += 6
z3 = m.incr_matrix(z, 2) # (numpy -> eigen)^3
z3[2, 2] += -5
z4 = m.incr_matrix(z, 3) # (numpy -> eigen)^4
z4[1, 1] -= 1
z5 = m.incr_matrix(z, 4) # (numpy -> eigen)^5
z5[0, 0] = 0
assert np.all(z == z2)
assert np.all(z == z3)
assert np.all(z == z4)
assert np.all(z == z5)
expect = np.array([[0.0, 22, 20], [31, 37, 33], [41, 42, 38]])
assert np.all(z == expect)
y = np.array(range(100), dtype="float64").reshape(10, 10)
y2 = m.incr_matrix_any(y, 10) # np -> eigen -> np
y3 = m.incr_matrix_any(y2[0::2, 0::2],
-33) # np -> eigen -> np slice -> np -> eigen -> np
y4 = m.even_rows(y3) # numpy -> eigen slice -> (... y3)
y5 = m.even_cols(y4) # numpy -> eigen slice -> (... y4)
y6 = m.incr_matrix_any(y5, 1000) # numpy -> eigen -> (... y5)
# Apply same mutations using just numpy:
yexpect = np.array(range(100), dtype="float64").reshape(10, 10)
yexpect += 10
yexpect[0::2, 0::2] -= 33
yexpect[0::4, 0::4] += 1000
assert np.all(y6 == yexpect[0::4, 0::4])
assert np.all(y5 == yexpect[0::4, 0::4])
assert np.all(y4 == yexpect[0::4, 0::2])
assert np.all(y3 == yexpect[0::2, 0::2])
assert np.all(y2 == yexpect)
assert np.all(y == yexpect)
def test_nocopy_wrapper():
# get_elem requires a column-contiguous matrix reference, but should be
# callable with other types of matrix (via copying):
int_matrix_colmajor = np.array(
[[1, 2, 3], [4, 5, 6], [7, 8, 9]], order="F")
dbl_matrix_colmajor = np.array(
int_matrix_colmajor, dtype="double", order="F", copy=True)
int_matrix_rowmajor = np.array(int_matrix_colmajor, order="C", copy=True)
dbl_matrix_rowmajor = np.array(
int_matrix_rowmajor, dtype="double", order="C", copy=True)
# All should be callable via get_elem:
assert m.get_elem(int_matrix_colmajor) == 8
assert m.get_elem(dbl_matrix_colmajor) == 8
assert m.get_elem(int_matrix_rowmajor) == 8
assert m.get_elem(dbl_matrix_rowmajor) == 8
# All but the second should fail with m.get_elem_nocopy:
with pytest.raises(TypeError) as excinfo:
m.get_elem_nocopy(int_matrix_colmajor)
assert "get_elem_nocopy(): incompatible function arguments." in str(
excinfo.value) and ", flags.f_contiguous" in str(excinfo.value)
assert m.get_elem_nocopy(dbl_matrix_colmajor) == 8
with pytest.raises(TypeError) as excinfo:
m.get_elem_nocopy(int_matrix_rowmajor)
assert "get_elem_nocopy(): incompatible function arguments." in str(
excinfo.value) and ", flags.f_contiguous" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.get_elem_nocopy(dbl_matrix_rowmajor)
assert "get_elem_nocopy(): incompatible function arguments." in str(
excinfo.value) and ", flags.f_contiguous" in str(excinfo.value)
# For the row-major test, we take a long matrix in row-major, so only the third is allowed:
with pytest.raises(TypeError) as excinfo:
m.get_elem_rm_nocopy(int_matrix_colmajor)
assert "get_elem_rm_nocopy(): incompatible function arguments." in str(
excinfo.value) and ", flags.c_contiguous" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.get_elem_rm_nocopy(dbl_matrix_colmajor)
assert "get_elem_rm_nocopy(): incompatible function arguments." in str(
excinfo.value) and ", flags.c_contiguous" in str(excinfo.value)
assert m.get_elem_rm_nocopy(int_matrix_rowmajor) == 8
with pytest.raises(TypeError) as excinfo:
m.get_elem_rm_nocopy(dbl_matrix_rowmajor)
assert "get_elem_rm_nocopy(): incompatible function arguments." in str(
excinfo.value) and ", flags.c_contiguous" in str(excinfo.value)
def test_eigen_ref_life_support():
"""Ensure the lifetime of temporary arrays created by the `Ref` caster
The `Ref` caster sometimes creates a copy which needs to stay alive. This needs to
happen both for directs casts (just the array) or indirectly (e.g. list of arrays).
"""
a = np.full(shape=10, fill_value=8, dtype=np.int8)
assert m.get_elem_direct(a) == 8
list_of_a = [a]
assert m.get_elem_indirect(list_of_a) == 8
def test_special_matrix_objects():
assert np.all(m.incr_diag(7) == np.diag([1.0, 2, 3, 4, 5, 6, 7]))
asymm = np.array(
[[1.0, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]])
symm_lower = np.array(asymm)
symm_upper = np.array(asymm)
for i in range(4):
for j in range(i + 1, 4):
symm_lower[i, j] = symm_lower[j, i]
symm_upper[j, i] = symm_upper[i, j]
assert np.all(m.symmetric_lower(asymm) == symm_lower)
assert np.all(m.symmetric_upper(asymm) == symm_upper)
def test_dense_signature(doc):
assert (doc(m.double_col) == """
double_col(arg0: numpy.ndarray[numpy.float32[m, 1]]) -> numpy.ndarray[numpy.float32[m, 1]]
""")
assert (doc(m.double_row) == """
double_row(arg0: numpy.ndarray[numpy.float32[1, n]]) -> numpy.ndarray[numpy.float32[1, n]]
""")
assert doc(m.double_complex) == (
"""
double_complex(arg0: numpy.ndarray[numpy.complex64[m, 1]])"""
""" -> numpy.ndarray[numpy.complex64[m, 1]]
""")
assert doc(m.double_mat_rm) == ("""
double_mat_rm(arg0: numpy.ndarray[numpy.float32[m, n]])"""
""" -> numpy.ndarray[numpy.float32[m, n]]
""")
def test_named_arguments():
a = np.array([[1.0, 2], [3, 4], [5, 6]])
b = np.ones((2, 1))
assert np.all(m.matrix_multiply(a, b) == np.array([[3.0], [7], [11]]))
assert np.all(m.matrix_multiply(A=a, B=b) == np.array([[3.0], [7], [11]]))
assert np.all(m.matrix_multiply(B=b, A=a) == np.array([[3.0], [7], [11]]))
with pytest.raises(ValueError) as excinfo:
m.matrix_multiply(b, a)
assert str(excinfo.value) == "Nonconformable matrices!"
with pytest.raises(ValueError) as excinfo:
m.matrix_multiply(A=b, B=a)
assert str(excinfo.value) == "Nonconformable matrices!"
with pytest.raises(ValueError) as excinfo:
m.matrix_multiply(B=a, A=b)
assert str(excinfo.value) == "Nonconformable matrices!"
def test_sparse():
pytest.importorskip("scipy")
assert_sparse_equal_ref(m.sparse_r())
assert_sparse_equal_ref(m.sparse_c())
assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_r()))
assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_c()))
assert_sparse_equal_ref(m.sparse_copy_r(m.sparse_c()))
assert_sparse_equal_ref(m.sparse_copy_c(m.sparse_r()))
def test_sparse_signature(doc):
pytest.importorskip("scipy")
assert (doc(m.sparse_copy_r) == """
sparse_copy_r(arg0: scipy.sparse.csr_matrix[numpy.float32]) -> scipy.sparse.csr_matrix[numpy.float32]
""")
assert (doc(m.sparse_copy_c) == """
sparse_copy_c(arg0: scipy.sparse.csc_matrix[numpy.float32]) -> scipy.sparse.csc_matrix[numpy.float32]
""")
def test_issue738():
"""Ignore strides on a length-1 dimension (even if they would be incompatible length > 1)"""
assert np.all(
m.iss738_f1(np.array([[1.0, 2, 3]])) == np.array([[1.0, 102, 203]]))
assert np.all(
m.iss738_f1(np.array([[1.0], [2], [3]])) == np.array(
[[1.0], [12], [23]]))
assert np.all(
m.iss738_f2(np.array([[1.0, 2, 3]])) == np.array([[1.0, 102, 203]]))
assert np.all(
m.iss738_f2(np.array([[1.0], [2], [3]])) == np.array(
[[1.0], [12], [23]]))
def test_issue1105():
"""Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen
compile-time row vectors or column vector"""
assert m.iss1105_row(np.ones((1, 7)))
assert m.iss1105_col(np.ones((7, 1)))
# These should still fail (incompatible dimensions):
with pytest.raises(TypeError) as excinfo:
m.iss1105_row(np.ones((7, 1)))
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.iss1105_col(np.ones((1, 7)))
assert "incompatible function arguments" in str(excinfo.value)
def test_custom_operator_new():
"""Using Eigen types as member variables requires a class-specific
operator new with proper alignment"""
o = m.CustomOperatorNew()
np.testing.assert_allclose(o.a, 0.0)
np.testing.assert_allclose(o.b.diagonal(), 1.0)

View File

@@ -0,0 +1,47 @@
possibly_uninitialized(PYTHON_MODULE_EXTENSION Python_INTERPRETER_ID)
if("${PYTHON_MODULE_EXTENSION}" MATCHES "pypy" OR "${Python_INTERPRETER_ID}" STREQUAL "PyPy")
message(STATUS "Skipping embed test on PyPy")
add_custom_target(cpptest) # Dummy target on PyPy. Embedding is not supported.
set(_suppress_unused_variable_warning "${DOWNLOAD_CATCH}")
return()
endif()
find_package(Catch 2.13.5)
if(CATCH_FOUND)
message(STATUS "Building interpreter tests using Catch v${CATCH_VERSION}")
else()
message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers"
" manually or use `cmake -DDOWNLOAD_CATCH=ON` to fetch them automatically.")
return()
endif()
find_package(Threads REQUIRED)
add_executable(test_embed catch.cpp test_interpreter.cpp)
pybind11_enable_warnings(test_embed)
target_link_libraries(test_embed PRIVATE pybind11::embed Catch2::Catch2 Threads::Threads)
if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
file(COPY test_interpreter.py test_trampoline.py DESTINATION "${CMAKE_CURRENT_BINARY_DIR}")
endif()
add_custom_target(
cpptest
COMMAND "$<TARGET_FILE:test_embed>"
DEPENDS test_embed
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
pybind11_add_module(external_module THIN_LTO external_module.cpp)
set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY
"${CMAKE_CURRENT_BINARY_DIR}")
foreach(config ${CMAKE_CONFIGURATION_TYPES})
string(TOUPPER ${config} config)
set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config}
"${CMAKE_CURRENT_BINARY_DIR}")
endforeach()
add_dependencies(cpptest external_module)
add_dependencies(check cpptest)

View File

@@ -0,0 +1,27 @@
// The Catch implementation is compiled here. This is a standalone
// translation unit to avoid recompiling it for every test change.
#include <pybind11/embed.h>
#ifdef _MSC_VER
// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
# pragma warning(disable : 4996)
#endif
// Catch uses _ internally, which breaks gettext style defines
#ifdef _
# undef _
#endif
#define CATCH_CONFIG_RUNNER
#include <catch.hpp>
namespace py = pybind11;
int main(int argc, char *argv[]) {
py::scoped_interpreter guard{};
auto result = Catch::Session().run(argc, argv);
return result < 0xff ? result : 0xff;
}

View File

@@ -0,0 +1,20 @@
#include <pybind11/pybind11.h>
namespace py = pybind11;
/* Simple test module/test class to check that the referenced internals data of external pybind11
* modules aren't preserved over a finalize/initialize.
*/
PYBIND11_MODULE(external_module, m) {
class A {
public:
explicit A(int value) : v{value} {};
int v;
};
py::class_<A>(m, "A").def(py::init<int>()).def_readwrite("value", &A::v);
m.def("internals_at",
[]() { return reinterpret_cast<uintptr_t>(&py::detail::get_internals()); });
}

View File

@@ -0,0 +1,395 @@
#include <pybind11/embed.h>
#ifdef _MSC_VER
// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to
// catch 2.0.1; this should be fixed in the next catch release after 2.0.1).
# pragma warning(disable : 4996)
#endif
#include <catch.hpp>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <thread>
#include <utility>
namespace py = pybind11;
using namespace py::literals;
class Widget {
public:
explicit Widget(std::string message) : message(std::move(message)) {}
virtual ~Widget() = default;
std::string the_message() const { return message; }
virtual int the_answer() const = 0;
virtual std::string argv0() const = 0;
private:
std::string message;
};
class PyWidget final : public Widget {
using Widget::Widget;
int the_answer() const override { PYBIND11_OVERRIDE_PURE(int, Widget, the_answer); }
std::string argv0() const override { PYBIND11_OVERRIDE_PURE(std::string, Widget, argv0); }
};
class test_override_cache_helper {
public:
virtual int func() { return 0; }
test_override_cache_helper() = default;
virtual ~test_override_cache_helper() = default;
// Non-copyable
test_override_cache_helper &operator=(test_override_cache_helper const &Right) = delete;
test_override_cache_helper(test_override_cache_helper const &Copy) = delete;
};
class test_override_cache_helper_trampoline : public test_override_cache_helper {
int func() override { PYBIND11_OVERRIDE(int, test_override_cache_helper, func); }
};
PYBIND11_EMBEDDED_MODULE(widget_module, m) {
py::class_<Widget, PyWidget>(m, "Widget")
.def(py::init<std::string>())
.def_property_readonly("the_message", &Widget::the_message);
m.def("add", [](int i, int j) { return i + j; });
}
PYBIND11_EMBEDDED_MODULE(trampoline_module, m) {
py::class_<test_override_cache_helper,
test_override_cache_helper_trampoline,
std::shared_ptr<test_override_cache_helper>>(m, "test_override_cache_helper")
.def(py::init_alias<>())
.def("func", &test_override_cache_helper::func);
}
PYBIND11_EMBEDDED_MODULE(throw_exception, ) { throw std::runtime_error("C++ Error"); }
PYBIND11_EMBEDDED_MODULE(throw_error_already_set, ) {
auto d = py::dict();
d["missing"].cast<py::object>();
}
TEST_CASE("Pass classes and data between modules defined in C++ and Python") {
auto module_ = py::module_::import("test_interpreter");
REQUIRE(py::hasattr(module_, "DerivedWidget"));
auto locals = py::dict("hello"_a = "Hello, World!", "x"_a = 5, **module_.attr("__dict__"));
py::exec(R"(
widget = DerivedWidget("{} - {}".format(hello, x))
message = widget.the_message
)",
py::globals(),
locals);
REQUIRE(locals["message"].cast<std::string>() == "Hello, World! - 5");
auto py_widget = module_.attr("DerivedWidget")("The question");
auto message = py_widget.attr("the_message");
REQUIRE(message.cast<std::string>() == "The question");
const auto &cpp_widget = py_widget.cast<const Widget &>();
REQUIRE(cpp_widget.the_answer() == 42);
}
TEST_CASE("Override cache") {
auto module_ = py::module_::import("test_trampoline");
REQUIRE(py::hasattr(module_, "func"));
REQUIRE(py::hasattr(module_, "func2"));
auto locals = py::dict(**module_.attr("__dict__"));
int i = 0;
for (; i < 1500; ++i) {
std::shared_ptr<test_override_cache_helper> p_obj;
std::shared_ptr<test_override_cache_helper> p_obj2;
py::object loc_inst = locals["func"]();
p_obj = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst);
int ret = p_obj->func();
REQUIRE(ret == 42);
loc_inst = locals["func2"]();
p_obj2 = py::cast<std::shared_ptr<test_override_cache_helper>>(loc_inst);
p_obj2->func();
}
}
TEST_CASE("Import error handling") {
REQUIRE_NOTHROW(py::module_::import("widget_module"));
REQUIRE_THROWS_WITH(py::module_::import("throw_exception"), "ImportError: C++ Error");
REQUIRE_THROWS_WITH(py::module_::import("throw_error_already_set"),
Catch::Contains("ImportError: initialization failed"));
auto locals = py::dict("is_keyerror"_a = false, "message"_a = "not set");
py::exec(R"(
try:
import throw_error_already_set
except ImportError as e:
is_keyerror = type(e.__cause__) == KeyError
message = str(e.__cause__)
)",
py::globals(),
locals);
REQUIRE(locals["is_keyerror"].cast<bool>() == true);
REQUIRE(locals["message"].cast<std::string>() == "'missing'");
}
TEST_CASE("There can be only one interpreter") {
static_assert(std::is_move_constructible<py::scoped_interpreter>::value, "");
static_assert(!std::is_move_assignable<py::scoped_interpreter>::value, "");
static_assert(!std::is_copy_constructible<py::scoped_interpreter>::value, "");
static_assert(!std::is_copy_assignable<py::scoped_interpreter>::value, "");
REQUIRE_THROWS_WITH(py::initialize_interpreter(), "The interpreter is already running");
REQUIRE_THROWS_WITH(py::scoped_interpreter(), "The interpreter is already running");
py::finalize_interpreter();
REQUIRE_NOTHROW(py::scoped_interpreter());
{
auto pyi1 = py::scoped_interpreter();
auto pyi2 = std::move(pyi1);
}
py::initialize_interpreter();
}
bool has_pybind11_internals_builtin() {
auto builtins = py::handle(PyEval_GetBuiltins());
return builtins.contains(PYBIND11_INTERNALS_ID);
};
bool has_pybind11_internals_static() {
auto **&ipp = py::detail::get_internals_pp();
return (ipp != nullptr) && (*ipp != nullptr);
}
TEST_CASE("Restart the interpreter") {
// Verify pre-restart state.
REQUIRE(py::module_::import("widget_module").attr("add")(1, 2).cast<int>() == 3);
REQUIRE(has_pybind11_internals_builtin());
REQUIRE(has_pybind11_internals_static());
REQUIRE(py::module_::import("external_module").attr("A")(123).attr("value").cast<int>()
== 123);
// local and foreign module internals should point to the same internals:
REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp())
== py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>());
// Restart the interpreter.
py::finalize_interpreter();
REQUIRE(Py_IsInitialized() == 0);
py::initialize_interpreter();
REQUIRE(Py_IsInitialized() == 1);
// Internals are deleted after a restart.
REQUIRE_FALSE(has_pybind11_internals_builtin());
REQUIRE_FALSE(has_pybind11_internals_static());
pybind11::detail::get_internals();
REQUIRE(has_pybind11_internals_builtin());
REQUIRE(has_pybind11_internals_static());
REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp())
== py::module_::import("external_module").attr("internals_at")().cast<uintptr_t>());
// Make sure that an interpreter with no get_internals() created until finalize still gets the
// internals destroyed
py::finalize_interpreter();
py::initialize_interpreter();
bool ran = false;
py::module_::import("__main__").attr("internals_destroy_test")
= py::capsule(&ran, [](void *ran) {
py::detail::get_internals();
*static_cast<bool *>(ran) = true;
});
REQUIRE_FALSE(has_pybind11_internals_builtin());
REQUIRE_FALSE(has_pybind11_internals_static());
REQUIRE_FALSE(ran);
py::finalize_interpreter();
REQUIRE(ran);
py::initialize_interpreter();
REQUIRE_FALSE(has_pybind11_internals_builtin());
REQUIRE_FALSE(has_pybind11_internals_static());
// C++ modules can be reloaded.
auto cpp_module = py::module_::import("widget_module");
REQUIRE(cpp_module.attr("add")(1, 2).cast<int>() == 3);
// C++ type information is reloaded and can be used in python modules.
auto py_module = py::module_::import("test_interpreter");
auto py_widget = py_module.attr("DerivedWidget")("Hello after restart");
REQUIRE(py_widget.attr("the_message").cast<std::string>() == "Hello after restart");
}
TEST_CASE("Subinterpreter") {
// Add tags to the modules in the main interpreter and test the basics.
py::module_::import("__main__").attr("main_tag") = "main interpreter";
{
auto m = py::module_::import("widget_module");
m.attr("extension_module_tag") = "added to module in main interpreter";
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
}
REQUIRE(has_pybind11_internals_builtin());
REQUIRE(has_pybind11_internals_static());
/// Create and switch to a subinterpreter.
auto *main_tstate = PyThreadState_Get();
auto *sub_tstate = Py_NewInterpreter();
// Subinterpreters get their own copy of builtins. detail::get_internals() still
// works by returning from the static variable, i.e. all interpreters share a single
// global pybind11::internals;
REQUIRE_FALSE(has_pybind11_internals_builtin());
REQUIRE(has_pybind11_internals_static());
// Modules tags should be gone.
REQUIRE_FALSE(py::hasattr(py::module_::import("__main__"), "tag"));
{
auto m = py::module_::import("widget_module");
REQUIRE_FALSE(py::hasattr(m, "extension_module_tag"));
// Function bindings should still work.
REQUIRE(m.attr("add")(1, 2).cast<int>() == 3);
}
// Restore main interpreter.
Py_EndInterpreter(sub_tstate);
PyThreadState_Swap(main_tstate);
REQUIRE(py::hasattr(py::module_::import("__main__"), "main_tag"));
REQUIRE(py::hasattr(py::module_::import("widget_module"), "extension_module_tag"));
}
TEST_CASE("Execution frame") {
// When the interpreter is embedded, there is no execution frame, but `py::exec`
// should still function by using reasonable globals: `__main__.__dict__`.
py::exec("var = dict(number=42)");
REQUIRE(py::globals()["var"]["number"].cast<int>() == 42);
}
TEST_CASE("Threads") {
// Restart interpreter to ensure threads are not initialized
py::finalize_interpreter();
py::initialize_interpreter();
REQUIRE_FALSE(has_pybind11_internals_static());
constexpr auto num_threads = 10;
auto locals = py::dict("count"_a = 0);
{
py::gil_scoped_release gil_release{};
REQUIRE(has_pybind11_internals_static());
auto threads = std::vector<std::thread>();
for (auto i = 0; i < num_threads; ++i) {
threads.emplace_back([&]() {
py::gil_scoped_acquire gil{};
locals["count"] = locals["count"].cast<int>() + 1;
});
}
for (auto &thread : threads) {
thread.join();
}
}
REQUIRE(locals["count"].cast<int>() == num_threads);
}
// Scope exit utility https://stackoverflow.com/a/36644501/7255855
struct scope_exit {
std::function<void()> f_;
explicit scope_exit(std::function<void()> f) noexcept : f_(std::move(f)) {}
~scope_exit() {
if (f_) {
f_();
}
}
};
TEST_CASE("Reload module from file") {
// Disable generation of cached bytecode (.pyc files) for this test, otherwise
// Python might pick up an old version from the cache instead of the new versions
// of the .py files generated below
auto sys = py::module_::import("sys");
bool dont_write_bytecode = sys.attr("dont_write_bytecode").cast<bool>();
sys.attr("dont_write_bytecode") = true;
// Reset the value at scope exit
scope_exit reset_dont_write_bytecode(
[&]() { sys.attr("dont_write_bytecode") = dont_write_bytecode; });
std::string module_name = "test_module_reload";
std::string module_file = module_name + ".py";
// Create the module .py file
std::ofstream test_module(module_file);
test_module << "def test():\n";
test_module << " return 1\n";
test_module.close();
// Delete the file at scope exit
scope_exit delete_module_file([&]() { std::remove(module_file.c_str()); });
// Import the module from file
auto module_ = py::module_::import(module_name.c_str());
int result = module_.attr("test")().cast<int>();
REQUIRE(result == 1);
// Update the module .py file with a small change
test_module.open(module_file);
test_module << "def test():\n";
test_module << " return 2\n";
test_module.close();
// Reload the module
module_.reload();
result = module_.attr("test")().cast<int>();
REQUIRE(result == 2);
}
TEST_CASE("sys.argv gets initialized properly") {
py::finalize_interpreter();
{
py::scoped_interpreter default_scope;
auto module = py::module::import("test_interpreter");
auto py_widget = module.attr("DerivedWidget")("The question");
const auto &cpp_widget = py_widget.cast<const Widget &>();
REQUIRE(cpp_widget.argv0().empty());
}
{
char *argv[] = {strdup("a.out")};
py::scoped_interpreter argv_scope(true, 1, argv);
std::free(argv[0]);
auto module = py::module::import("test_interpreter");
auto py_widget = module.attr("DerivedWidget")("The question");
const auto &cpp_widget = py_widget.cast<const Widget &>();
REQUIRE(cpp_widget.argv0() == "a.out");
}
py::initialize_interpreter();
}
TEST_CASE("make_iterator can be called before then after finalizing an interpreter") {
// Reproduction of issue #2101 (https://github.com/pybind/pybind11/issues/2101)
py::finalize_interpreter();
std::vector<int> container;
{
pybind11::scoped_interpreter g;
auto iter = pybind11::make_iterator(container.begin(), container.end());
}
REQUIRE_NOTHROW([&]() {
pybind11::scoped_interpreter g;
auto iter = pybind11::make_iterator(container.begin(), container.end());
}());
py::initialize_interpreter();
}

View File

@@ -0,0 +1,14 @@
import sys
from widget_module import Widget
class DerivedWidget(Widget):
def __init__(self, message):
super().__init__(message)
def the_answer(self):
return 42
def argv0(self):
return sys.argv[0]

View File

@@ -0,0 +1,16 @@
import trampoline_module
def func():
class Test(trampoline_module.test_override_cache_helper):
def func(self):
return 42
return Test()
def func2():
class Test(trampoline_module.test_override_cache_helper):
pass
return Test()

133
third_party/pybind11/tests/test_enum.cpp vendored Normal file
View File

@@ -0,0 +1,133 @@
/*
tests/test_enums.cpp -- enumerations
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
TEST_SUBMODULE(enums, m) {
// test_unscoped_enum
enum UnscopedEnum { EOne = 1, ETwo, EThree };
py::enum_<UnscopedEnum>(m, "UnscopedEnum", py::arithmetic(), "An unscoped enumeration")
.value("EOne", EOne, "Docstring for EOne")
.value("ETwo", ETwo, "Docstring for ETwo")
.value("EThree", EThree, "Docstring for EThree")
.export_values();
// test_scoped_enum
enum class ScopedEnum { Two = 2, Three };
py::enum_<ScopedEnum>(m, "ScopedEnum", py::arithmetic())
.value("Two", ScopedEnum::Two)
.value("Three", ScopedEnum::Three);
m.def("test_scoped_enum", [](ScopedEnum z) {
return "ScopedEnum::" + std::string(z == ScopedEnum::Two ? "Two" : "Three");
});
// test_binary_operators
enum Flags { Read = 4, Write = 2, Execute = 1 };
py::enum_<Flags>(m, "Flags", py::arithmetic())
.value("Read", Flags::Read)
.value("Write", Flags::Write)
.value("Execute", Flags::Execute)
.export_values();
// test_implicit_conversion
class ClassWithUnscopedEnum {
public:
enum EMode { EFirstMode = 1, ESecondMode };
static EMode test_function(EMode mode) { return mode; }
};
py::class_<ClassWithUnscopedEnum> exenum_class(m, "ClassWithUnscopedEnum");
exenum_class.def_static("test_function", &ClassWithUnscopedEnum::test_function);
py::enum_<ClassWithUnscopedEnum::EMode>(exenum_class, "EMode")
.value("EFirstMode", ClassWithUnscopedEnum::EFirstMode)
.value("ESecondMode", ClassWithUnscopedEnum::ESecondMode)
.export_values();
// test_enum_to_int
m.def("test_enum_to_int", [](int) {});
m.def("test_enum_to_uint", [](uint32_t) {});
m.def("test_enum_to_long_long", [](long long) {});
// test_duplicate_enum_name
enum SimpleEnum { ONE, TWO, THREE };
m.def("register_bad_enum", [m]() {
py::enum_<SimpleEnum>(m, "SimpleEnum")
.value("ONE", SimpleEnum::ONE) // NOTE: all value function calls are called with the
// same first parameter value
.value("ONE", SimpleEnum::TWO)
.value("ONE", SimpleEnum::THREE)
.export_values();
});
// test_enum_scalar
enum UnscopedUCharEnum : unsigned char {};
enum class ScopedShortEnum : short {};
enum class ScopedLongEnum : long {};
enum UnscopedUInt64Enum : std::uint64_t {};
static_assert(
py::detail::all_of<
std::is_same<py::enum_<UnscopedUCharEnum>::Scalar, unsigned char>,
std::is_same<py::enum_<ScopedShortEnum>::Scalar, short>,
std::is_same<py::enum_<ScopedLongEnum>::Scalar, long>,
std::is_same<py::enum_<UnscopedUInt64Enum>::Scalar, std::uint64_t>>::value,
"Error during the deduction of enum's scalar type with normal integer underlying");
// test_enum_scalar_with_char_underlying
enum class ScopedCharEnum : char { Zero, Positive };
enum class ScopedWCharEnum : wchar_t { Zero, Positive };
enum class ScopedChar32Enum : char32_t { Zero, Positive };
enum class ScopedChar16Enum : char16_t { Zero, Positive };
// test the scalar of char type enums according to chapter 'Character types'
// from https://en.cppreference.com/w/cpp/language/types
static_assert(
py::detail::any_of<
std::is_same<py::enum_<ScopedCharEnum>::Scalar, signed char>, // e.g. gcc on x86
std::is_same<py::enum_<ScopedCharEnum>::Scalar, unsigned char> // e.g. arm linux
>::value,
"char should be cast to either signed char or unsigned char");
static_assert(sizeof(py::enum_<ScopedWCharEnum>::Scalar) == 2
|| sizeof(py::enum_<ScopedWCharEnum>::Scalar) == 4,
"wchar_t should be either 16 bits (Windows) or 32 (everywhere else)");
static_assert(
py::detail::all_of<
std::is_same<py::enum_<ScopedChar32Enum>::Scalar, std::uint_least32_t>,
std::is_same<py::enum_<ScopedChar16Enum>::Scalar, std::uint_least16_t>>::value,
"char32_t, char16_t (and char8_t)'s size, signedness, and alignment is determined");
#if defined(PYBIND11_HAS_U8STRING)
enum class ScopedChar8Enum : char8_t { Zero, Positive };
static_assert(std::is_same<py::enum_<ScopedChar8Enum>::Scalar, unsigned char>::value);
#endif
// test_char_underlying_enum
py::enum_<ScopedCharEnum>(m, "ScopedCharEnum")
.value("Zero", ScopedCharEnum::Zero)
.value("Positive", ScopedCharEnum::Positive);
py::enum_<ScopedWCharEnum>(m, "ScopedWCharEnum")
.value("Zero", ScopedWCharEnum::Zero)
.value("Positive", ScopedWCharEnum::Positive);
py::enum_<ScopedChar32Enum>(m, "ScopedChar32Enum")
.value("Zero", ScopedChar32Enum::Zero)
.value("Positive", ScopedChar32Enum::Positive);
py::enum_<ScopedChar16Enum>(m, "ScopedChar16Enum")
.value("Zero", ScopedChar16Enum::Zero)
.value("Positive", ScopedChar16Enum::Positive);
// test_bool_underlying_enum
enum class ScopedBoolEnum : bool { FALSE, TRUE };
// bool is unsigned (std::is_signed returns false) and 1-byte long, so represented with u8
static_assert(std::is_same<py::enum_<ScopedBoolEnum>::Scalar, std::uint8_t>::value, "");
py::enum_<ScopedBoolEnum>(m, "ScopedBoolEnum")
.value("FALSE", ScopedBoolEnum::FALSE)
.value("TRUE", ScopedBoolEnum::TRUE);
}

263
third_party/pybind11/tests/test_enum.py vendored Normal file
View File

@@ -0,0 +1,263 @@
import pytest
from pybind11_tests import enums as m
def test_unscoped_enum():
assert str(m.UnscopedEnum.EOne) == "UnscopedEnum.EOne"
assert str(m.UnscopedEnum.ETwo) == "UnscopedEnum.ETwo"
assert str(m.EOne) == "UnscopedEnum.EOne"
assert repr(m.UnscopedEnum.EOne) == "<UnscopedEnum.EOne: 1>"
assert repr(m.UnscopedEnum.ETwo) == "<UnscopedEnum.ETwo: 2>"
assert repr(m.EOne) == "<UnscopedEnum.EOne: 1>"
# name property
assert m.UnscopedEnum.EOne.name == "EOne"
assert m.UnscopedEnum.EOne.value == 1
assert m.UnscopedEnum.ETwo.name == "ETwo"
assert m.UnscopedEnum.ETwo.value == 2
assert m.EOne is m.UnscopedEnum.EOne
# name, value readonly
with pytest.raises(AttributeError):
m.UnscopedEnum.EOne.name = ""
with pytest.raises(AttributeError):
m.UnscopedEnum.EOne.value = 10
# name, value returns a copy
# TODO: Neither the name nor value tests actually check against aliasing.
# Use a mutable type that has reference semantics.
nonaliased_name = m.UnscopedEnum.EOne.name
nonaliased_name = "bar" # noqa: F841
assert m.UnscopedEnum.EOne.name == "EOne"
nonaliased_value = m.UnscopedEnum.EOne.value
nonaliased_value = 10 # noqa: F841
assert m.UnscopedEnum.EOne.value == 1
# __members__ property
assert m.UnscopedEnum.__members__ == {
"EOne": m.UnscopedEnum.EOne,
"ETwo": m.UnscopedEnum.ETwo,
"EThree": m.UnscopedEnum.EThree,
}
# __members__ readonly
with pytest.raises(AttributeError):
m.UnscopedEnum.__members__ = {}
# __members__ returns a copy
nonaliased_members = m.UnscopedEnum.__members__
nonaliased_members["bar"] = "baz"
assert m.UnscopedEnum.__members__ == {
"EOne": m.UnscopedEnum.EOne,
"ETwo": m.UnscopedEnum.ETwo,
"EThree": m.UnscopedEnum.EThree,
}
for docstring_line in """An unscoped enumeration
Members:
EOne : Docstring for EOne
ETwo : Docstring for ETwo
EThree : Docstring for EThree""".split("\n"):
assert docstring_line in m.UnscopedEnum.__doc__
# Unscoped enums will accept ==/!= int comparisons
y = m.UnscopedEnum.ETwo
assert y == 2
assert 2 == y
assert y != 3
assert 3 != y
# Compare with None
assert y != None # noqa: E711
assert not (y == None) # noqa: E711
# Compare with an object
assert y != object()
assert not (y == object())
# Compare with string
assert y != "2"
assert "2" != y
assert not ("2" == y)
assert not (y == "2")
with pytest.raises(TypeError):
y < object() # noqa: B015
with pytest.raises(TypeError):
y <= object() # noqa: B015
with pytest.raises(TypeError):
y > object() # noqa: B015
with pytest.raises(TypeError):
y >= object() # noqa: B015
with pytest.raises(TypeError):
y | object()
with pytest.raises(TypeError):
y & object()
with pytest.raises(TypeError):
y ^ object()
assert int(m.UnscopedEnum.ETwo) == 2
assert str(m.UnscopedEnum(2)) == "UnscopedEnum.ETwo"
# order
assert m.UnscopedEnum.EOne < m.UnscopedEnum.ETwo
assert m.UnscopedEnum.EOne < 2
assert m.UnscopedEnum.ETwo > m.UnscopedEnum.EOne
assert m.UnscopedEnum.ETwo > 1
assert m.UnscopedEnum.ETwo <= 2
assert m.UnscopedEnum.ETwo >= 2
assert m.UnscopedEnum.EOne <= m.UnscopedEnum.ETwo
assert m.UnscopedEnum.EOne <= 2
assert m.UnscopedEnum.ETwo >= m.UnscopedEnum.EOne
assert m.UnscopedEnum.ETwo >= 1
assert not (m.UnscopedEnum.ETwo < m.UnscopedEnum.EOne)
assert not (2 < m.UnscopedEnum.EOne)
# arithmetic
assert m.UnscopedEnum.EOne & m.UnscopedEnum.EThree == m.UnscopedEnum.EOne
assert m.UnscopedEnum.EOne | m.UnscopedEnum.ETwo == m.UnscopedEnum.EThree
assert m.UnscopedEnum.EOne ^ m.UnscopedEnum.EThree == m.UnscopedEnum.ETwo
def test_scoped_enum():
assert m.test_scoped_enum(m.ScopedEnum.Three) == "ScopedEnum::Three"
z = m.ScopedEnum.Two
assert m.test_scoped_enum(z) == "ScopedEnum::Two"
# Scoped enums will *NOT* accept ==/!= int comparisons (Will always return False)
assert not z == 3
assert not 3 == z
assert z != 3
assert 3 != z
# Compare with None
assert z != None # noqa: E711
assert not (z == None) # noqa: E711
# Compare with an object
assert z != object()
assert not (z == object())
# Scoped enums will *NOT* accept >, <, >= and <= int comparisons (Will throw exceptions)
with pytest.raises(TypeError):
z > 3 # noqa: B015
with pytest.raises(TypeError):
z < 3 # noqa: B015
with pytest.raises(TypeError):
z >= 3 # noqa: B015
with pytest.raises(TypeError):
z <= 3 # noqa: B015
# order
assert m.ScopedEnum.Two < m.ScopedEnum.Three
assert m.ScopedEnum.Three > m.ScopedEnum.Two
assert m.ScopedEnum.Two <= m.ScopedEnum.Three
assert m.ScopedEnum.Two <= m.ScopedEnum.Two
assert m.ScopedEnum.Two >= m.ScopedEnum.Two
assert m.ScopedEnum.Three >= m.ScopedEnum.Two
def test_implicit_conversion():
assert str(m.ClassWithUnscopedEnum.EMode.EFirstMode) == "EMode.EFirstMode"
assert str(m.ClassWithUnscopedEnum.EFirstMode) == "EMode.EFirstMode"
assert repr(
m.ClassWithUnscopedEnum.EMode.EFirstMode) == "<EMode.EFirstMode: 1>"
assert repr(m.ClassWithUnscopedEnum.EFirstMode) == "<EMode.EFirstMode: 1>"
f = m.ClassWithUnscopedEnum.test_function
first = m.ClassWithUnscopedEnum.EFirstMode
second = m.ClassWithUnscopedEnum.ESecondMode
assert f(first) == 1
assert f(first) == f(first)
assert not f(first) != f(first)
assert f(first) != f(second)
assert not f(first) == f(second)
assert f(first) == int(f(first))
assert not f(first) != int(f(first))
assert f(first) != int(f(second))
assert not f(first) == int(f(second))
# noinspection PyDictCreation
x = {f(first): 1, f(second): 2}
x[f(first)] = 3
x[f(second)] = 4
# Hashing test
assert repr(x) == "{<EMode.EFirstMode: 1>: 3, <EMode.ESecondMode: 2>: 4}"
def test_binary_operators():
assert int(m.Flags.Read) == 4
assert int(m.Flags.Write) == 2
assert int(m.Flags.Execute) == 1
assert int(m.Flags.Read | m.Flags.Write | m.Flags.Execute) == 7
assert int(m.Flags.Read | m.Flags.Write) == 6
assert int(m.Flags.Read | m.Flags.Execute) == 5
assert int(m.Flags.Write | m.Flags.Execute) == 3
assert int(m.Flags.Write | 1) == 3
assert ~m.Flags.Write == -3
state = m.Flags.Read | m.Flags.Write
assert (state & m.Flags.Read) != 0
assert (state & m.Flags.Write) != 0
assert (state & m.Flags.Execute) == 0
assert (state & 1) == 0
state2 = ~state
assert state2 == -7
assert int(state ^ state2) == -1
def test_enum_to_int():
m.test_enum_to_int(m.Flags.Read)
m.test_enum_to_int(m.ClassWithUnscopedEnum.EMode.EFirstMode)
m.test_enum_to_int(m.ScopedCharEnum.Positive)
m.test_enum_to_int(m.ScopedBoolEnum.TRUE)
m.test_enum_to_uint(m.Flags.Read)
m.test_enum_to_uint(m.ClassWithUnscopedEnum.EMode.EFirstMode)
m.test_enum_to_uint(m.ScopedCharEnum.Positive)
m.test_enum_to_uint(m.ScopedBoolEnum.TRUE)
m.test_enum_to_long_long(m.Flags.Read)
m.test_enum_to_long_long(m.ClassWithUnscopedEnum.EMode.EFirstMode)
m.test_enum_to_long_long(m.ScopedCharEnum.Positive)
m.test_enum_to_long_long(m.ScopedBoolEnum.TRUE)
def test_duplicate_enum_name():
with pytest.raises(ValueError) as excinfo:
m.register_bad_enum()
assert str(excinfo.value) == 'SimpleEnum: element "ONE" already exists!'
def test_char_underlying_enum(): # Issue #1331/PR #1334:
assert type(m.ScopedCharEnum.Positive.__int__()) is int
assert int(m.ScopedChar16Enum.Zero) == 0
assert hash(m.ScopedChar32Enum.Positive) == 1
assert type(m.ScopedCharEnum.Positive.__getstate__()) is int
assert m.ScopedWCharEnum(1) == m.ScopedWCharEnum.Positive
with pytest.raises(TypeError):
# Even if the underlying type is char, only an int can be used to construct the enum:
m.ScopedCharEnum("0")
def test_bool_underlying_enum():
assert type(m.ScopedBoolEnum.TRUE.__int__()) is int
assert int(m.ScopedBoolEnum.FALSE) == 0
assert hash(m.ScopedBoolEnum.TRUE) == 1
assert type(m.ScopedBoolEnum.TRUE.__getstate__()) is int
assert m.ScopedBoolEnum(1) == m.ScopedBoolEnum.TRUE
# Enum could construct with a bool
# (bool is a strict subclass of int, and False will be converted to 0)
assert m.ScopedBoolEnum(False) == m.ScopedBoolEnum.FALSE
def test_docstring_signatures():
for enum_type in [m.ScopedEnum, m.UnscopedEnum]:
for attr in enum_type.__dict__.values():
# Issue #2623/PR #2637: Add argument names to enum_ methods
assert "arg0" not in (attr.__doc__ or "")

118
third_party/pybind11/tests/test_eval.cpp vendored Normal file
View File

@@ -0,0 +1,118 @@
/*
tests/test_eval.cpp -- Usage of eval() and eval_file()
Copyright (c) 2016 Klemens D. Morgenstern
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/eval.h>
#include "pybind11_tests.h"
#include <utility>
TEST_SUBMODULE(eval_, m) {
// test_evals
auto global = py::dict(py::module_::import("__main__").attr("__dict__"));
m.def("test_eval_statements", [global]() {
auto local = py::dict();
local["call_test"] = py::cpp_function([&]() -> int { return 42; });
// Regular string literal
py::exec("message = 'Hello World!'\n"
"x = call_test()",
global,
local);
// Multi-line raw string literal
py::exec(R"(
if x == 42:
print(message)
else:
raise RuntimeError
)",
global,
local);
auto x = local["x"].cast<int>();
return x == 42;
});
m.def("test_eval", [global]() {
auto local = py::dict();
local["x"] = py::int_(42);
auto x = py::eval("x", global, local);
return x.cast<int>() == 42;
});
m.def("test_eval_single_statement", []() {
auto local = py::dict();
local["call_test"] = py::cpp_function([&]() -> int { return 42; });
auto result = py::eval<py::eval_single_statement>("x = call_test()", py::dict(), local);
auto x = local["x"].cast<int>();
return result.is_none() && x == 42;
});
m.def("test_eval_file", [global](py::str filename) {
auto local = py::dict();
local["y"] = py::int_(43);
int val_out = 0;
local["call_test2"] = py::cpp_function([&](int value) { val_out = value; });
auto result = py::eval_file(std::move(filename), global, local);
return val_out == 43 && result.is_none();
});
m.def("test_eval_failure", []() {
try {
py::eval("nonsense code ...");
} catch (py::error_already_set &) {
return true;
}
return false;
});
m.def("test_eval_file_failure", []() {
try {
py::eval_file("non-existing file");
} catch (std::exception &) {
return true;
}
return false;
});
// test_eval_empty_globals
m.def("eval_empty_globals", [](py::object global) {
if (global.is_none()) {
global = py::dict();
}
auto int_class = py::eval("isinstance(42, int)", global);
return global;
});
// test_eval_closure
m.def("test_eval_closure", []() {
py::dict global;
global["closure_value"] = 42;
py::dict local;
local["closure_value"] = 0;
py::exec(R"(
local_value = closure_value
def func_global():
return closure_value
def func_local():
return local_value
)",
global,
local);
return std::make_pair(global, local);
});
}

50
third_party/pybind11/tests/test_eval.py vendored Normal file
View File

@@ -0,0 +1,50 @@
import os
import pytest
import env # noqa: F401
from pybind11_tests import eval_ as m
def test_evals(capture):
with capture:
assert m.test_eval_statements()
assert capture == "Hello World!"
assert m.test_eval()
assert m.test_eval_single_statement()
assert m.test_eval_failure()
@pytest.mark.xfail("env.PYPY", raises=RuntimeError)
def test_eval_file():
filename = os.path.join(os.path.dirname(__file__), "test_eval_call.py")
assert m.test_eval_file(filename)
assert m.test_eval_file_failure()
def test_eval_empty_globals():
assert "__builtins__" in m.eval_empty_globals(None)
g = {}
assert "__builtins__" in m.eval_empty_globals(g)
assert "__builtins__" in g
def test_eval_closure():
global_, local = m.test_eval_closure()
assert global_["closure_value"] == 42
assert local["closure_value"] == 0
assert "local_value" not in global_
assert local["local_value"] == 0
assert "func_global" not in global_
assert local["func_global"]() == 42
assert "func_local" not in global_
with pytest.raises(NameError):
local["func_local"]()

View File

@@ -0,0 +1,4 @@
# This file is called from 'test_eval.py'
if "call_test2" in locals():
call_test2(y) # noqa: F821 undefined name

View File

@@ -0,0 +1,302 @@
/*
tests/test_custom-exceptions.cpp -- exception translation
Copyright (c) 2016 Pim Schellart <P.Schellart@princeton.edu>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "test_exceptions.h"
#include "local_bindings.h"
#include "pybind11_tests.h"
#include <exception>
#include <stdexcept>
#include <utility>
// A type that should be raised as an exception in Python
class MyException : public std::exception {
public:
explicit MyException(const char *m) : message{m} {}
const char *what() const noexcept override { return message.c_str(); }
private:
std::string message = "";
};
// A type that should be translated to a standard Python exception
class MyException2 : public std::exception {
public:
explicit MyException2(const char *m) : message{m} {}
const char *what() const noexcept override { return message.c_str(); }
private:
std::string message = "";
};
// A type that is not derived from std::exception (and is thus unknown)
class MyException3 {
public:
explicit MyException3(const char *m) : message{m} {}
virtual const char *what() const noexcept { return message.c_str(); }
// Rule of 5 BEGIN: to preempt compiler warnings.
MyException3(const MyException3 &) = default;
MyException3(MyException3 &&) = default;
MyException3 &operator=(const MyException3 &) = default;
MyException3 &operator=(MyException3 &&) = default;
virtual ~MyException3() = default;
// Rule of 5 END.
private:
std::string message = "";
};
// A type that should be translated to MyException
// and delegated to its exception translator
class MyException4 : public std::exception {
public:
explicit MyException4(const char *m) : message{m} {}
const char *what() const noexcept override { return message.c_str(); }
private:
std::string message = "";
};
// Like the above, but declared via the helper function
class MyException5 : public std::logic_error {
public:
explicit MyException5(const std::string &what) : std::logic_error(what) {}
};
// Inherits from MyException5
class MyException5_1 : public MyException5 {
using MyException5::MyException5;
};
// Exception that will be caught via the module local translator.
class MyException6 : public std::exception {
public:
explicit MyException6(const char *m) : message{m} {}
const char *what() const noexcept override { return message.c_str(); }
private:
std::string message = "";
};
struct PythonCallInDestructor {
explicit PythonCallInDestructor(const py::dict &d) : d(d) {}
~PythonCallInDestructor() { d["good"] = true; }
py::dict d;
};
struct PythonAlreadySetInDestructor {
explicit PythonAlreadySetInDestructor(const py::str &s) : s(s) {}
~PythonAlreadySetInDestructor() {
py::dict foo;
try {
// Assign to a py::object to force read access of nonexistent dict entry
py::object o = foo["bar"];
} catch (py::error_already_set &ex) {
ex.discard_as_unraisable(s);
}
}
py::str s;
};
TEST_SUBMODULE(exceptions, m) {
m.def("throw_std_exception",
[]() { throw std::runtime_error("This exception was intentionally thrown."); });
// make a new custom exception and use it as a translation target
static py::exception<MyException> ex(m, "MyException");
py::register_exception_translator([](std::exception_ptr p) {
try {
if (p) {
std::rethrow_exception(p);
}
} catch (const MyException &e) {
// Set MyException as the active python error
ex(e.what());
}
});
// register new translator for MyException2
// no need to store anything here because this type will
// never by visible from Python
py::register_exception_translator([](std::exception_ptr p) {
try {
if (p) {
std::rethrow_exception(p);
}
} catch (const MyException2 &e) {
// Translate this exception to a standard RuntimeError
PyErr_SetString(PyExc_RuntimeError, e.what());
}
});
// register new translator for MyException4
// which will catch it and delegate to the previously registered
// translator for MyException by throwing a new exception
py::register_exception_translator([](std::exception_ptr p) {
try {
if (p) {
std::rethrow_exception(p);
}
} catch (const MyException4 &e) {
throw MyException(e.what());
}
});
// A simple exception translation:
auto ex5 = py::register_exception<MyException5>(m, "MyException5");
// A slightly more complicated one that declares MyException5_1 as a subclass of MyException5
py::register_exception<MyException5_1>(m, "MyException5_1", ex5.ptr());
// py::register_local_exception<LocalSimpleException>(m, "LocalSimpleException")
py::register_local_exception_translator([](std::exception_ptr p) {
try {
if (p) {
std::rethrow_exception(p);
}
} catch (const MyException6 &e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
}
});
m.def("throws1", []() { throw MyException("this error should go to a custom type"); });
m.def("throws2",
[]() { throw MyException2("this error should go to a standard Python exception"); });
m.def("throws3", []() { throw MyException3("this error cannot be translated"); });
m.def("throws4", []() { throw MyException4("this error is rethrown"); });
m.def("throws5",
[]() { throw MyException5("this is a helper-defined translated exception"); });
m.def("throws5_1", []() { throw MyException5_1("MyException5 subclass"); });
m.def("throws6", []() { throw MyException6("MyException6 only handled in this module"); });
m.def("throws_logic_error", []() {
throw std::logic_error("this error should fall through to the standard handler");
});
m.def("throws_overflow_error", []() { throw std::overflow_error(""); });
m.def("throws_local_error", []() { throw LocalException("never caught"); });
m.def("throws_local_simple_error", []() { throw LocalSimpleException("this mod"); });
m.def("exception_matches", []() {
py::dict foo;
try {
// Assign to a py::object to force read access of nonexistent dict entry
py::object o = foo["bar"];
} catch (py::error_already_set &ex) {
if (!ex.matches(PyExc_KeyError)) {
throw;
}
return true;
}
return false;
});
m.def("exception_matches_base", []() {
py::dict foo;
try {
// Assign to a py::object to force read access of nonexistent dict entry
py::object o = foo["bar"];
} catch (py::error_already_set &ex) {
if (!ex.matches(PyExc_Exception)) {
throw;
}
return true;
}
return false;
});
m.def("modulenotfound_exception_matches_base", []() {
try {
// On Python >= 3.6, this raises a ModuleNotFoundError, a subclass of ImportError
py::module_::import("nonexistent");
} catch (py::error_already_set &ex) {
if (!ex.matches(PyExc_ImportError)) {
throw;
}
return true;
}
return false;
});
m.def("throw_already_set", [](bool err) {
if (err) {
PyErr_SetString(PyExc_ValueError, "foo");
}
try {
throw py::error_already_set();
} catch (const std::runtime_error &e) {
if ((err && e.what() != std::string("ValueError: foo"))
|| (!err && e.what() != std::string("Unknown internal error occurred"))) {
PyErr_Clear();
throw std::runtime_error("error message mismatch");
}
}
PyErr_Clear();
if (err) {
PyErr_SetString(PyExc_ValueError, "foo");
}
throw py::error_already_set();
});
m.def("python_call_in_destructor", [](const py::dict &d) {
bool retval = false;
try {
PythonCallInDestructor set_dict_in_destructor(d);
PyErr_SetString(PyExc_ValueError, "foo");
throw py::error_already_set();
} catch (const py::error_already_set &) {
retval = true;
}
return retval;
});
m.def("python_alreadyset_in_destructor", [](const py::str &s) {
PythonAlreadySetInDestructor alreadyset_in_destructor(s);
return true;
});
// test_nested_throws
m.def("try_catch",
[m](const py::object &exc_type, const py::function &f, const py::args &args) {
try {
f(*args);
} catch (py::error_already_set &ex) {
if (ex.matches(exc_type)) {
py::print(ex.what());
} else {
throw;
}
}
});
// Test repr that cannot be displayed
m.def("simple_bool_passthrough", [](bool x) { return x; });
m.def("throw_should_be_translated_to_key_error", []() { throw shared_exception(); });
m.def("raise_from", []() {
PyErr_SetString(PyExc_ValueError, "inner");
py::raise_from(PyExc_ValueError, "outer");
throw py::error_already_set();
});
m.def("raise_from_already_set", []() {
try {
PyErr_SetString(PyExc_ValueError, "inner");
throw py::error_already_set();
} catch (py::error_already_set &e) {
py::raise_from(e, PyExc_ValueError, "outer");
throw py::error_already_set();
}
});
m.def("throw_nested_exception", []() {
try {
throw std::runtime_error("Inner Exception");
} catch (const std::runtime_error &) {
std::throw_with_nested(std::runtime_error("Outer Exception"));
}
});
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "pybind11_tests.h"
#include <stdexcept>
// shared exceptions for cross_module_tests
class PYBIND11_EXPORT_EXCEPTION shared_exception : public pybind11::builtin_exception {
public:
using builtin_exception::builtin_exception;
explicit shared_exception() : shared_exception("") {}
void set_error() const override { PyErr_SetString(PyExc_RuntimeError, what()); }
};

View File

@@ -0,0 +1,275 @@
import sys
import pytest
import env
import pybind11_cross_module_tests as cm
from pybind11_tests import exceptions as m
def test_std_exception(msg):
with pytest.raises(RuntimeError) as excinfo:
m.throw_std_exception()
assert msg(excinfo.value) == "This exception was intentionally thrown."
def test_error_already_set(msg):
with pytest.raises(RuntimeError) as excinfo:
m.throw_already_set(False)
assert msg(excinfo.value) == "Unknown internal error occurred"
with pytest.raises(ValueError) as excinfo:
m.throw_already_set(True)
assert msg(excinfo.value) == "foo"
def test_raise_from(msg):
with pytest.raises(ValueError) as excinfo:
m.raise_from()
assert msg(excinfo.value) == "outer"
assert msg(excinfo.value.__cause__) == "inner"
def test_raise_from_already_set(msg):
with pytest.raises(ValueError) as excinfo:
m.raise_from_already_set()
assert msg(excinfo.value) == "outer"
assert msg(excinfo.value.__cause__) == "inner"
def test_cross_module_exceptions(msg):
with pytest.raises(RuntimeError) as excinfo:
cm.raise_runtime_error()
assert str(excinfo.value) == "My runtime error"
with pytest.raises(ValueError) as excinfo:
cm.raise_value_error()
assert str(excinfo.value) == "My value error"
with pytest.raises(ValueError) as excinfo:
cm.throw_pybind_value_error()
assert str(excinfo.value) == "pybind11 value error"
with pytest.raises(TypeError) as excinfo:
cm.throw_pybind_type_error()
assert str(excinfo.value) == "pybind11 type error"
with pytest.raises(StopIteration) as excinfo:
cm.throw_stop_iteration()
with pytest.raises(cm.LocalSimpleException) as excinfo:
cm.throw_local_simple_error()
assert msg(excinfo.value) == "external mod"
with pytest.raises(KeyError) as excinfo:
cm.throw_local_error()
# KeyError is a repr of the key, so it has an extra set of quotes
assert str(excinfo.value) == "'just local'"
# TODO: FIXME
@pytest.mark.xfail(
"env.PYPY and env.MACOS",
raises=RuntimeError,
reason="Expected failure with PyPy and libc++ (Issue #2847 & PR #2999)", )
def test_cross_module_exception_translator():
with pytest.raises(KeyError):
# translator registered in cross_module_tests
m.throw_should_be_translated_to_key_error()
def test_python_call_in_catch():
d = {}
assert m.python_call_in_destructor(d) is True
assert d["good"] is True
def ignore_pytest_unraisable_warning(f):
unraisable = "PytestUnraisableExceptionWarning"
if hasattr(pytest, unraisable): # Python >= 3.8 and pytest >= 6
dec = pytest.mark.filterwarnings(f"ignore::pytest.{unraisable}")
return dec(f)
else:
return f
# TODO: find out why this fails on PyPy, https://foss.heptapod.net/pypy/pypy/-/issues/3583
@pytest.mark.xfail(
env.PYPY, reason="Failure on PyPy 3.8 (7.3.7)", strict=False)
@ignore_pytest_unraisable_warning
def test_python_alreadyset_in_destructor(monkeypatch, capsys):
hooked = False
triggered = False
if hasattr(sys, "unraisablehook"): # Python 3.8+
hooked = True
# Don't take `sys.unraisablehook`, as that's overwritten by pytest
default_hook = sys.__unraisablehook__
def hook(unraisable_hook_args):
exc_type, exc_value, exc_tb, err_msg, obj = unraisable_hook_args
if obj == "already_set demo":
nonlocal triggered
triggered = True
default_hook(unraisable_hook_args)
return
# Use monkeypatch so pytest can apply and remove the patch as appropriate
monkeypatch.setattr(sys, "unraisablehook", hook)
assert m.python_alreadyset_in_destructor("already_set demo") is True
if hooked:
assert triggered is True
_, captured_stderr = capsys.readouterr()
assert captured_stderr.startswith(
"Exception ignored in: 'already_set demo'")
assert captured_stderr.rstrip().endswith("KeyError: 'bar'")
def test_exception_matches():
assert m.exception_matches()
assert m.exception_matches_base()
assert m.modulenotfound_exception_matches_base()
def test_custom(msg):
# Can we catch a MyException?
with pytest.raises(m.MyException) as excinfo:
m.throws1()
assert msg(excinfo.value) == "this error should go to a custom type"
# Can we translate to standard Python exceptions?
with pytest.raises(RuntimeError) as excinfo:
m.throws2()
assert msg(
excinfo.value) == "this error should go to a standard Python exception"
# Can we handle unknown exceptions?
with pytest.raises(RuntimeError) as excinfo:
m.throws3()
assert msg(excinfo.value) == "Caught an unknown exception!"
# Can we delegate to another handler by rethrowing?
with pytest.raises(m.MyException) as excinfo:
m.throws4()
assert msg(excinfo.value) == "this error is rethrown"
# Can we fall-through to the default handler?
with pytest.raises(RuntimeError) as excinfo:
m.throws_logic_error()
assert (msg(excinfo.value) ==
"this error should fall through to the standard handler")
# OverFlow error translation.
with pytest.raises(OverflowError) as excinfo:
m.throws_overflow_error()
# Can we handle a helper-declared exception?
with pytest.raises(m.MyException5) as excinfo:
m.throws5()
assert msg(
excinfo.value) == "this is a helper-defined translated exception"
# Exception subclassing:
with pytest.raises(m.MyException5) as excinfo:
m.throws5_1()
assert msg(excinfo.value) == "MyException5 subclass"
assert isinstance(excinfo.value, m.MyException5_1)
with pytest.raises(m.MyException5_1) as excinfo:
m.throws5_1()
assert msg(excinfo.value) == "MyException5 subclass"
with pytest.raises(m.MyException5) as excinfo:
try:
m.throws5()
except m.MyException5_1:
raise RuntimeError("Exception error: caught child from parent")
assert msg(
excinfo.value) == "this is a helper-defined translated exception"
def test_nested_throws(capture):
"""Tests nested (e.g. C++ -> Python -> C++) exception handling"""
def throw_myex():
raise m.MyException("nested error")
def throw_myex5():
raise m.MyException5("nested error 5")
# In the comments below, the exception is caught in the first step, thrown in the last step
# C++ -> Python
with capture:
m.try_catch(m.MyException5, throw_myex5)
assert str(capture).startswith("MyException5: nested error 5")
# Python -> C++ -> Python
with pytest.raises(m.MyException) as excinfo:
m.try_catch(m.MyException5, throw_myex)
assert str(excinfo.value) == "nested error"
def pycatch(exctype, f, *args):
try:
f(*args)
except m.MyException as e:
print(e)
# C++ -> Python -> C++ -> Python
with capture:
m.try_catch(
m.MyException5,
pycatch,
m.MyException,
m.try_catch,
m.MyException,
throw_myex5, )
assert str(capture).startswith("MyException5: nested error 5")
# C++ -> Python -> C++
with capture:
m.try_catch(m.MyException, pycatch, m.MyException5, m.throws4)
assert capture == "this error is rethrown"
# Python -> C++ -> Python -> C++
with pytest.raises(m.MyException5) as excinfo:
m.try_catch(m.MyException, pycatch, m.MyException, m.throws5)
assert str(
excinfo.value) == "this is a helper-defined translated exception"
def test_throw_nested_exception():
with pytest.raises(RuntimeError) as excinfo:
m.throw_nested_exception()
assert str(excinfo.value) == "Outer Exception"
assert str(excinfo.value.__cause__) == "Inner Exception"
# This can often happen if you wrap a pybind11 class in a Python wrapper
def test_invalid_repr():
class MyRepr:
def __repr__(self):
raise AttributeError("Example error")
with pytest.raises(TypeError):
m.simple_bool_passthrough(MyRepr())
def test_local_translator(msg):
"""Tests that a local translator works and that the local translator from
the cross module is not applied"""
with pytest.raises(RuntimeError) as excinfo:
m.throws6()
assert msg(excinfo.value) == "MyException6 only handled in this module"
with pytest.raises(RuntimeError) as excinfo:
m.throws_local_error()
assert not isinstance(excinfo.value, KeyError)
assert msg(excinfo.value) == "never caught"
with pytest.raises(Exception) as excinfo:
m.throws_local_simple_error()
assert not isinstance(excinfo.value, cm.LocalSimpleException)
assert msg(excinfo.value) == "this mod"

View File

@@ -0,0 +1,430 @@
/*
tests/test_factory_constructors.cpp -- tests construction from a factory function
via py::init_factory()
Copyright (c) 2017 Jason Rhinelander <jason@imaginary.ca>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "constructor_stats.h"
#include "pybind11_tests.h"
#include <cmath>
#include <new>
#include <utility>
// Classes for testing python construction via C++ factory function:
// Not publicly constructible, copyable, or movable:
class TestFactory1 {
friend class TestFactoryHelper;
TestFactory1() : value("(empty)") { print_default_created(this); }
explicit TestFactory1(int v) : value(std::to_string(v)) { print_created(this, value); }
explicit TestFactory1(std::string v) : value(std::move(v)) { print_created(this, value); }
public:
std::string value;
TestFactory1(TestFactory1 &&) = delete;
TestFactory1(const TestFactory1 &) = delete;
TestFactory1 &operator=(TestFactory1 &&) = delete;
TestFactory1 &operator=(const TestFactory1 &) = delete;
~TestFactory1() { print_destroyed(this); }
};
// Non-public construction, but moveable:
class TestFactory2 {
friend class TestFactoryHelper;
TestFactory2() : value("(empty2)") { print_default_created(this); }
explicit TestFactory2(int v) : value(std::to_string(v)) { print_created(this, value); }
explicit TestFactory2(std::string v) : value(std::move(v)) { print_created(this, value); }
public:
TestFactory2(TestFactory2 &&m) noexcept : value{std::move(m.value)} {
print_move_created(this);
}
TestFactory2 &operator=(TestFactory2 &&m) noexcept {
value = std::move(m.value);
print_move_assigned(this);
return *this;
}
std::string value;
~TestFactory2() { print_destroyed(this); }
};
// Mixed direct/factory construction:
class TestFactory3 {
protected:
friend class TestFactoryHelper;
TestFactory3() : value("(empty3)") { print_default_created(this); }
explicit TestFactory3(int v) : value(std::to_string(v)) { print_created(this, value); }
public:
explicit TestFactory3(std::string v) : value(std::move(v)) { print_created(this, value); }
TestFactory3(TestFactory3 &&m) noexcept : value{std::move(m.value)} {
print_move_created(this);
}
TestFactory3 &operator=(TestFactory3 &&m) noexcept {
value = std::move(m.value);
print_move_assigned(this);
return *this;
}
std::string value;
virtual ~TestFactory3() { print_destroyed(this); }
};
// Inheritance test
class TestFactory4 : public TestFactory3 {
public:
TestFactory4() : TestFactory3() { print_default_created(this); }
explicit TestFactory4(int v) : TestFactory3(v) { print_created(this, v); }
~TestFactory4() override { print_destroyed(this); }
};
// Another class for an invalid downcast test
class TestFactory5 : public TestFactory3 {
public:
explicit TestFactory5(int i) : TestFactory3(i) { print_created(this, i); }
~TestFactory5() override { print_destroyed(this); }
};
class TestFactory6 {
protected:
int value;
bool alias = false;
public:
explicit TestFactory6(int i) : value{i} { print_created(this, i); }
TestFactory6(TestFactory6 &&f) noexcept {
print_move_created(this);
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
value = f.value;
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
alias = f.alias;
}
TestFactory6(const TestFactory6 &f) {
print_copy_created(this);
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
value = f.value;
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
alias = f.alias;
}
virtual ~TestFactory6() { print_destroyed(this); }
virtual int get() { return value; }
bool has_alias() const { return alias; }
};
class PyTF6 : public TestFactory6 {
public:
// Special constructor that allows the factory to construct a PyTF6 from a TestFactory6 only
// when an alias is needed:
explicit PyTF6(TestFactory6 &&base) : TestFactory6(std::move(base)) {
alias = true;
print_created(this, "move", value);
}
explicit PyTF6(int i) : TestFactory6(i) {
alias = true;
print_created(this, i);
}
PyTF6(PyTF6 &&f) noexcept : TestFactory6(std::move(f)) { print_move_created(this); }
PyTF6(const PyTF6 &f) : TestFactory6(f) { print_copy_created(this); }
explicit PyTF6(std::string s) : TestFactory6((int) s.size()) {
alias = true;
print_created(this, s);
}
~PyTF6() override { print_destroyed(this); }
int get() override { PYBIND11_OVERRIDE(int, TestFactory6, get, /*no args*/); }
};
class TestFactory7 {
protected:
int value;
bool alias = false;
public:
explicit TestFactory7(int i) : value{i} { print_created(this, i); }
TestFactory7(TestFactory7 &&f) noexcept {
print_move_created(this);
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
value = f.value;
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
alias = f.alias;
}
TestFactory7(const TestFactory7 &f) {
print_copy_created(this);
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
value = f.value;
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
alias = f.alias;
}
virtual ~TestFactory7() { print_destroyed(this); }
virtual int get() { return value; }
bool has_alias() const { return alias; }
};
class PyTF7 : public TestFactory7 {
public:
explicit PyTF7(int i) : TestFactory7(i) {
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
alias = true;
print_created(this, i);
}
PyTF7(PyTF7 &&f) noexcept : TestFactory7(std::move(f)) { print_move_created(this); }
PyTF7(const PyTF7 &f) : TestFactory7(f) { print_copy_created(this); }
~PyTF7() override { print_destroyed(this); }
int get() override { PYBIND11_OVERRIDE(int, TestFactory7, get, /*no args*/); }
};
class TestFactoryHelper {
public:
// Non-movable, non-copyable type:
// Return via pointer:
static TestFactory1 *construct1() { return new TestFactory1(); }
// Holder:
static std::unique_ptr<TestFactory1> construct1(int a) {
return std::unique_ptr<TestFactory1>(new TestFactory1(a));
}
// pointer again
static TestFactory1 *construct1_string(std::string a) {
return new TestFactory1(std::move(a));
}
// Moveable type:
// pointer:
static TestFactory2 *construct2() { return new TestFactory2(); }
// holder:
static std::unique_ptr<TestFactory2> construct2(int a) {
return std::unique_ptr<TestFactory2>(new TestFactory2(a));
}
// by value moving:
static TestFactory2 construct2(std::string a) { return TestFactory2(std::move(a)); }
// shared_ptr holder type:
// pointer:
static TestFactory3 *construct3() { return new TestFactory3(); }
// holder:
static std::shared_ptr<TestFactory3> construct3(int a) {
return std::shared_ptr<TestFactory3>(new TestFactory3(a));
}
};
TEST_SUBMODULE(factory_constructors, m) {
// Define various trivial types to allow simpler overload resolution:
py::module_ m_tag = m.def_submodule("tag");
#define MAKE_TAG_TYPE(Name) \
struct Name##_tag {}; \
py::class_<Name##_tag>(m_tag, #Name "_tag").def(py::init<>()); \
m_tag.attr(#Name) = py::cast(Name##_tag{})
MAKE_TAG_TYPE(pointer);
MAKE_TAG_TYPE(unique_ptr);
MAKE_TAG_TYPE(move);
MAKE_TAG_TYPE(shared_ptr);
MAKE_TAG_TYPE(derived);
MAKE_TAG_TYPE(TF4);
MAKE_TAG_TYPE(TF5);
MAKE_TAG_TYPE(null_ptr);
MAKE_TAG_TYPE(null_unique_ptr);
MAKE_TAG_TYPE(null_shared_ptr);
MAKE_TAG_TYPE(base);
MAKE_TAG_TYPE(invalid_base);
MAKE_TAG_TYPE(alias);
MAKE_TAG_TYPE(unaliasable);
MAKE_TAG_TYPE(mixed);
// test_init_factory_basic, test_bad_type
py::class_<TestFactory1>(m, "TestFactory1")
.def(py::init([](unique_ptr_tag, int v) { return TestFactoryHelper::construct1(v); }))
.def(py::init(&TestFactoryHelper::construct1_string)) // raw function pointer
.def(py::init([](pointer_tag) { return TestFactoryHelper::construct1(); }))
.def(py::init(
[](py::handle, int v, py::handle) { return TestFactoryHelper::construct1(v); }))
.def_readwrite("value", &TestFactory1::value);
py::class_<TestFactory2>(m, "TestFactory2")
.def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct2(v); }))
.def(py::init([](unique_ptr_tag, std::string v) {
return TestFactoryHelper::construct2(std::move(v));
}))
.def(py::init([](move_tag) { return TestFactoryHelper::construct2(); }))
.def_readwrite("value", &TestFactory2::value);
// Stateful & reused:
int c = 1;
auto c4a = [c](pointer_tag, TF4_tag, int a) {
(void) c;
return new TestFactory4(a);
};
// test_init_factory_basic, test_init_factory_casting
py::class_<TestFactory3, std::shared_ptr<TestFactory3>> pyTestFactory3(m, "TestFactory3");
pyTestFactory3
.def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct3(v); }))
.def(py::init([](shared_ptr_tag) { return TestFactoryHelper::construct3(); }));
ignoreOldStyleInitWarnings([&pyTestFactory3]() {
pyTestFactory3.def("__init__", [](TestFactory3 &self, std::string v) {
new (&self) TestFactory3(std::move(v));
}); // placement-new ctor
});
pyTestFactory3
// factories returning a derived type:
.def(py::init(c4a)) // derived ptr
.def(py::init([](pointer_tag, TF5_tag, int a) { return new TestFactory5(a); }))
// derived shared ptr:
.def(py::init(
[](shared_ptr_tag, TF4_tag, int a) { return std::make_shared<TestFactory4>(a); }))
.def(py::init(
[](shared_ptr_tag, TF5_tag, int a) { return std::make_shared<TestFactory5>(a); }))
// Returns nullptr:
.def(py::init([](null_ptr_tag) { return (TestFactory3 *) nullptr; }))
.def(py::init([](null_unique_ptr_tag) { return std::unique_ptr<TestFactory3>(); }))
.def(py::init([](null_shared_ptr_tag) { return std::shared_ptr<TestFactory3>(); }))
.def_readwrite("value", &TestFactory3::value);
// test_init_factory_casting
py::class_<TestFactory4, TestFactory3, std::shared_ptr<TestFactory4>>(m, "TestFactory4")
.def(py::init(c4a)) // pointer
;
// Doesn't need to be registered, but registering makes getting ConstructorStats easier:
py::class_<TestFactory5, TestFactory3, std::shared_ptr<TestFactory5>>(m, "TestFactory5");
// test_init_factory_alias
// Alias testing
py::class_<TestFactory6, PyTF6>(m, "TestFactory6")
.def(py::init([](base_tag, int i) { return TestFactory6(i); }))
.def(py::init([](alias_tag, int i) { return PyTF6(i); }))
.def(py::init([](alias_tag, std::string s) { return PyTF6(std::move(s)); }))
.def(py::init([](alias_tag, pointer_tag, int i) { return new PyTF6(i); }))
.def(py::init([](base_tag, pointer_tag, int i) { return new TestFactory6(i); }))
.def(py::init(
[](base_tag, alias_tag, pointer_tag, int i) { return (TestFactory6 *) new PyTF6(i); }))
.def("get", &TestFactory6::get)
.def("has_alias", &TestFactory6::has_alias)
.def_static(
"get_cstats", &ConstructorStats::get<TestFactory6>, py::return_value_policy::reference)
.def_static(
"get_alias_cstats", &ConstructorStats::get<PyTF6>, py::return_value_policy::reference);
// test_init_factory_dual
// Separate alias constructor testing
py::class_<TestFactory7, PyTF7, std::shared_ptr<TestFactory7>>(m, "TestFactory7")
.def(py::init([](int i) { return TestFactory7(i); }, [](int i) { return PyTF7(i); }))
.def(py::init([](pointer_tag, int i) { return new TestFactory7(i); },
[](pointer_tag, int i) { return new PyTF7(i); }))
.def(py::init([](mixed_tag, int i) { return new TestFactory7(i); },
[](mixed_tag, int i) { return PyTF7(i); }))
.def(py::init([](mixed_tag, const std::string &s) { return TestFactory7((int) s.size()); },
[](mixed_tag, const std::string &s) { return new PyTF7((int) s.size()); }))
.def(py::init([](base_tag, pointer_tag, int i) { return new TestFactory7(i); },
[](base_tag, pointer_tag, int i) { return (TestFactory7 *) new PyTF7(i); }))
.def(py::init([](alias_tag, pointer_tag, int i) { return new PyTF7(i); },
[](alias_tag, pointer_tag, int i) { return new PyTF7(10 * i); }))
.def(py::init(
[](shared_ptr_tag, base_tag, int i) { return std::make_shared<TestFactory7>(i); },
[](shared_ptr_tag, base_tag, int i) {
auto *p = new PyTF7(i);
return std::shared_ptr<TestFactory7>(p);
}))
.def(py::init([](shared_ptr_tag,
invalid_base_tag,
int i) { return std::make_shared<TestFactory7>(i); },
[](shared_ptr_tag, invalid_base_tag, int i) {
return std::make_shared<TestFactory7>(i);
})) // <-- invalid alias factory
.def("get", &TestFactory7::get)
.def("has_alias", &TestFactory7::has_alias)
.def_static(
"get_cstats", &ConstructorStats::get<TestFactory7>, py::return_value_policy::reference)
.def_static(
"get_alias_cstats", &ConstructorStats::get<PyTF7>, py::return_value_policy::reference);
// test_placement_new_alternative
// Class with a custom new operator but *without* a placement new operator (issue #948)
class NoPlacementNew {
public:
explicit NoPlacementNew(int i) : i(i) {}
static void *operator new(std::size_t s) {
auto *p = ::operator new(s);
py::print("operator new called, returning", reinterpret_cast<uintptr_t>(p));
return p;
}
static void operator delete(void *p) {
py::print("operator delete called on", reinterpret_cast<uintptr_t>(p));
::operator delete(p);
}
int i;
};
// As of 2.2, `py::init<args>` no longer requires placement new
py::class_<NoPlacementNew>(m, "NoPlacementNew")
.def(py::init<int>())
.def(py::init([]() { return new NoPlacementNew(100); }))
.def_readwrite("i", &NoPlacementNew::i);
// test_reallocations
// Class that has verbose operator_new/operator_delete calls
struct NoisyAlloc {
NoisyAlloc(const NoisyAlloc &) = default;
explicit NoisyAlloc(int i) { py::print(py::str("NoisyAlloc(int {})").format(i)); }
explicit NoisyAlloc(double d) { py::print(py::str("NoisyAlloc(double {})").format(d)); }
~NoisyAlloc() { py::print("~NoisyAlloc()"); }
static void *operator new(size_t s) {
py::print("noisy new");
return ::operator new(s);
}
static void *operator new(size_t, void *p) {
py::print("noisy placement new");
return p;
}
static void operator delete(void *p, size_t) {
py::print("noisy delete");
::operator delete(p);
}
static void operator delete(void *, void *) { py::print("noisy placement delete"); }
};
py::class_<NoisyAlloc> pyNoisyAlloc(m, "NoisyAlloc");
// Since these overloads have the same number of arguments, the dispatcher will try each of
// them until the arguments convert. Thus we can get a pre-allocation here when passing a
// single non-integer:
ignoreOldStyleInitWarnings([&pyNoisyAlloc]() {
pyNoisyAlloc.def("__init__", [](NoisyAlloc *a, int i) {
new (a) NoisyAlloc(i);
}); // Regular constructor, runs first, requires preallocation
});
pyNoisyAlloc.def(py::init([](double d) { return new NoisyAlloc(d); }));
// The two-argument version: first the factory pointer overload.
pyNoisyAlloc.def(py::init([](int i, int) { return new NoisyAlloc(i); }));
// Return-by-value:
pyNoisyAlloc.def(py::init([](double d, int) { return NoisyAlloc(d); }));
// Old-style placement new init; requires preallocation
ignoreOldStyleInitWarnings([&pyNoisyAlloc]() {
pyNoisyAlloc.def("__init__",
[](NoisyAlloc &a, double d, double) { new (&a) NoisyAlloc(d); });
});
// Requires deallocation of previous overload preallocated value:
pyNoisyAlloc.def(py::init([](int i, double) { return new NoisyAlloc(i); }));
// Regular again: requires yet another preallocation
ignoreOldStyleInitWarnings([&pyNoisyAlloc]() {
pyNoisyAlloc.def(
"__init__", [](NoisyAlloc &a, int i, const std::string &) { new (&a) NoisyAlloc(i); });
});
// static_assert testing (the following def's should all fail with appropriate compilation
// errors):
#if 0
struct BadF1Base {};
struct BadF1 : BadF1Base {};
struct PyBadF1 : BadF1 {};
py::class_<BadF1, PyBadF1, std::shared_ptr<BadF1>> bf1(m, "BadF1");
// wrapped factory function must return a compatible pointer, holder, or value
bf1.def(py::init([]() { return 3; }));
// incompatible factory function pointer return type
bf1.def(py::init([]() { static int three = 3; return &three; }));
// incompatible factory function std::shared_ptr<T> return type: cannot convert shared_ptr<T> to holder
// (non-polymorphic base)
bf1.def(py::init([]() { return std::shared_ptr<BadF1Base>(new BadF1()); }));
#endif
}

View File

@@ -0,0 +1,504 @@
import re
import pytest
from pybind11_tests import ConstructorStats
from pybind11_tests import factory_constructors as m
from pybind11_tests.factory_constructors import tag
def test_init_factory_basic():
"""Tests py::init_factory() wrapper around various ways of returning the object"""
cstats = [
ConstructorStats.get(c)
for c in [m.TestFactory1, m.TestFactory2, m.TestFactory3]
]
cstats[0].alive() # force gc
n_inst = ConstructorStats.detail_reg_inst()
x1 = m.TestFactory1(tag.unique_ptr, 3)
assert x1.value == "3"
y1 = m.TestFactory1(tag.pointer)
assert y1.value == "(empty)"
z1 = m.TestFactory1("hi!")
assert z1.value == "hi!"
assert ConstructorStats.detail_reg_inst() == n_inst + 3
x2 = m.TestFactory2(tag.move)
assert x2.value == "(empty2)"
y2 = m.TestFactory2(tag.pointer, 7)
assert y2.value == "7"
z2 = m.TestFactory2(tag.unique_ptr, "hi again")
assert z2.value == "hi again"
assert ConstructorStats.detail_reg_inst() == n_inst + 6
x3 = m.TestFactory3(tag.shared_ptr)
assert x3.value == "(empty3)"
y3 = m.TestFactory3(tag.pointer, 42)
assert y3.value == "42"
z3 = m.TestFactory3("bye")
assert z3.value == "bye"
for null_ptr_kind in [
tag.null_ptr, tag.null_unique_ptr, tag.null_shared_ptr
]:
with pytest.raises(TypeError) as excinfo:
m.TestFactory3(null_ptr_kind)
assert (str(excinfo.value) ==
"pybind11::init(): factory function returned nullptr")
assert [i.alive() for i in cstats] == [3, 3, 3]
assert ConstructorStats.detail_reg_inst() == n_inst + 9
del x1, y2, y3, z3
assert [i.alive() for i in cstats] == [2, 2, 1]
assert ConstructorStats.detail_reg_inst() == n_inst + 5
del x2, x3, y1, z1, z2
assert [i.alive() for i in cstats] == [0, 0, 0]
assert ConstructorStats.detail_reg_inst() == n_inst
assert [i.values() for i in cstats] == [
["3", "hi!"],
["7", "hi again"],
["42", "bye"],
]
assert [i.default_constructions for i in cstats] == [1, 1, 1]
def test_init_factory_signature(msg):
with pytest.raises(TypeError) as excinfo:
m.TestFactory1("invalid", "constructor", "arguments")
assert (msg(excinfo.value) == """
__init__(): incompatible constructor arguments. The following argument types are supported:
1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int)
2. m.factory_constructors.TestFactory1(arg0: str)
3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag)
4. m.factory_constructors.TestFactory1(arg0: handle, arg1: int, arg2: handle)
Invoked with: 'invalid', 'constructor', 'arguments'
""")
assert (msg(m.TestFactory1.__init__.__doc__) == """
__init__(*args, **kwargs)
Overloaded function.
1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) -> None
2. __init__(self: m.factory_constructors.TestFactory1, arg0: str) -> None
3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None
4. __init__(self: m.factory_constructors.TestFactory1, arg0: handle, arg1: int, arg2: handle) -> None
"""
# noqa: E501 line too long
)
def test_init_factory_casting():
"""Tests py::init_factory() wrapper with various upcasting and downcasting returns"""
cstats = [
ConstructorStats.get(c)
for c in [m.TestFactory3, m.TestFactory4, m.TestFactory5]
]
cstats[0].alive() # force gc
n_inst = ConstructorStats.detail_reg_inst()
# Construction from derived references:
a = m.TestFactory3(tag.pointer, tag.TF4, 4)
assert a.value == "4"
b = m.TestFactory3(tag.shared_ptr, tag.TF4, 5)
assert b.value == "5"
c = m.TestFactory3(tag.pointer, tag.TF5, 6)
assert c.value == "6"
d = m.TestFactory3(tag.shared_ptr, tag.TF5, 7)
assert d.value == "7"
assert ConstructorStats.detail_reg_inst() == n_inst + 4
# Shared a lambda with TF3:
e = m.TestFactory4(tag.pointer, tag.TF4, 8)
assert e.value == "8"
assert ConstructorStats.detail_reg_inst() == n_inst + 5
assert [i.alive() for i in cstats] == [5, 3, 2]
del a
assert [i.alive() for i in cstats] == [4, 2, 2]
assert ConstructorStats.detail_reg_inst() == n_inst + 4
del b, c, e
assert [i.alive() for i in cstats] == [1, 0, 1]
assert ConstructorStats.detail_reg_inst() == n_inst + 1
del d
assert [i.alive() for i in cstats] == [0, 0, 0]
assert ConstructorStats.detail_reg_inst() == n_inst
assert [i.values() for i in cstats] == [
["4", "5", "6", "7", "8"],
["4", "5", "8"],
["6", "7"],
]
def test_init_factory_alias():
"""Tests py::init_factory() wrapper with value conversions and alias types"""
cstats = [m.TestFactory6.get_cstats(), m.TestFactory6.get_alias_cstats()]
cstats[0].alive() # force gc
n_inst = ConstructorStats.detail_reg_inst()
a = m.TestFactory6(tag.base, 1)
assert a.get() == 1
assert not a.has_alias()
b = m.TestFactory6(tag.alias, "hi there")
assert b.get() == 8
assert b.has_alias()
c = m.TestFactory6(tag.alias, 3)
assert c.get() == 3
assert c.has_alias()
d = m.TestFactory6(tag.alias, tag.pointer, 4)
assert d.get() == 4
assert d.has_alias()
e = m.TestFactory6(tag.base, tag.pointer, 5)
assert e.get() == 5
assert not e.has_alias()
f = m.TestFactory6(tag.base, tag.alias, tag.pointer, 6)
assert f.get() == 6
assert f.has_alias()
assert ConstructorStats.detail_reg_inst() == n_inst + 6
assert [i.alive() for i in cstats] == [6, 4]
del a, b, e
assert [i.alive() for i in cstats] == [3, 3]
assert ConstructorStats.detail_reg_inst() == n_inst + 3
del f, c, d
assert [i.alive() for i in cstats] == [0, 0]
assert ConstructorStats.detail_reg_inst() == n_inst
class MyTest(m.TestFactory6):
def __init__(self, *args):
m.TestFactory6.__init__(self, *args)
def get(self):
return -5 + m.TestFactory6.get(self)
# Return Class by value, moved into new alias:
z = MyTest(tag.base, 123)
assert z.get() == 118
assert z.has_alias()
# Return alias by value, moved into new alias:
y = MyTest(tag.alias, "why hello!")
assert y.get() == 5
assert y.has_alias()
# Return Class by pointer, moved into new alias then original destroyed:
x = MyTest(tag.base, tag.pointer, 47)
assert x.get() == 42
assert x.has_alias()
assert ConstructorStats.detail_reg_inst() == n_inst + 3
assert [i.alive() for i in cstats] == [3, 3]
del x, y, z
assert [i.alive() for i in cstats] == [0, 0]
assert ConstructorStats.detail_reg_inst() == n_inst
assert [i.values() for i in cstats] == [
["1", "8", "3", "4", "5", "6", "123", "10", "47"],
[
"hi there", "3", "4", "6", "move", "123", "why hello!", "move",
"47"
],
]
def test_init_factory_dual():
"""Tests init factory functions with dual main/alias factory functions"""
from pybind11_tests.factory_constructors import TestFactory7
cstats = [TestFactory7.get_cstats(), TestFactory7.get_alias_cstats()]
cstats[0].alive() # force gc
n_inst = ConstructorStats.detail_reg_inst()
class PythFactory7(TestFactory7):
def get(self):
return 100 + TestFactory7.get(self)
a1 = TestFactory7(1)
a2 = PythFactory7(2)
assert a1.get() == 1
assert a2.get() == 102
assert not a1.has_alias()
assert a2.has_alias()
b1 = TestFactory7(tag.pointer, 3)
b2 = PythFactory7(tag.pointer, 4)
assert b1.get() == 3
assert b2.get() == 104
assert not b1.has_alias()
assert b2.has_alias()
c1 = TestFactory7(tag.mixed, 5)
c2 = PythFactory7(tag.mixed, 6)
assert c1.get() == 5
assert c2.get() == 106
assert not c1.has_alias()
assert c2.has_alias()
d1 = TestFactory7(tag.base, tag.pointer, 7)
d2 = PythFactory7(tag.base, tag.pointer, 8)
assert d1.get() == 7
assert d2.get() == 108
assert not d1.has_alias()
assert d2.has_alias()
# Both return an alias; the second multiplies the value by 10:
e1 = TestFactory7(tag.alias, tag.pointer, 9)
e2 = PythFactory7(tag.alias, tag.pointer, 10)
assert e1.get() == 9
assert e2.get() == 200
assert e1.has_alias()
assert e2.has_alias()
f1 = TestFactory7(tag.shared_ptr, tag.base, 11)
f2 = PythFactory7(tag.shared_ptr, tag.base, 12)
assert f1.get() == 11
assert f2.get() == 112
assert not f1.has_alias()
assert f2.has_alias()
g1 = TestFactory7(tag.shared_ptr, tag.invalid_base, 13)
assert g1.get() == 13
assert not g1.has_alias()
with pytest.raises(TypeError) as excinfo:
PythFactory7(tag.shared_ptr, tag.invalid_base, 14)
assert (
str(excinfo.value) ==
"pybind11::init(): construction failed: returned holder-wrapped instance is not an "
"alias instance")
assert [i.alive() for i in cstats] == [13, 7]
assert ConstructorStats.detail_reg_inst() == n_inst + 13
del a1, a2, b1, d1, e1, e2
assert [i.alive() for i in cstats] == [7, 4]
assert ConstructorStats.detail_reg_inst() == n_inst + 7
del b2, c1, c2, d2, f1, f2, g1
assert [i.alive() for i in cstats] == [0, 0]
assert ConstructorStats.detail_reg_inst() == n_inst
assert [i.values() for i in cstats] == [
[
"1", "2", "3", "4", "5", "6", "7", "8", "9", "100", "11", "12",
"13", "14"
],
["2", "4", "6", "8", "9", "100", "12"],
]
def test_no_placement_new(capture):
"""Prior to 2.2, `py::init<...>` relied on the type supporting placement
new; this tests a class without placement new support."""
with capture:
a = m.NoPlacementNew(123)
found = re.search(r"^operator new called, returning (\d+)\n$",
str(capture))
assert found
assert a.i == 123
with capture:
del a
pytest.gc_collect()
assert capture == "operator delete called on " + found.group(1)
with capture:
b = m.NoPlacementNew()
found = re.search(r"^operator new called, returning (\d+)\n$",
str(capture))
assert found
assert b.i == 100
with capture:
del b
pytest.gc_collect()
assert capture == "operator delete called on " + found.group(1)
def test_multiple_inheritance():
class MITest(m.TestFactory1, m.TestFactory2):
def __init__(self):
m.TestFactory1.__init__(self, tag.unique_ptr, 33)
m.TestFactory2.__init__(self, tag.move)
a = MITest()
assert m.TestFactory1.value.fget(a) == "33"
assert m.TestFactory2.value.fget(a) == "(empty2)"
def create_and_destroy(*args):
a = m.NoisyAlloc(*args)
print("---")
del a
pytest.gc_collect()
def strip_comments(s):
return re.sub(r"\s+#.*", "", s)
def test_reallocation_a(capture, msg):
"""When the constructor is overloaded, previous overloads can require a preallocated value.
This test makes sure that such preallocated values only happen when they might be necessary,
and that they are deallocated properly."""
pytest.gc_collect()
with capture:
create_and_destroy(1)
assert (msg(capture) == """
noisy new
noisy placement new
NoisyAlloc(int 1)
---
~NoisyAlloc()
noisy delete
""")
def test_reallocation_b(capture, msg):
with capture:
create_and_destroy(1.5)
assert msg(capture) == strip_comments("""
noisy new # allocation required to attempt first overload
noisy delete # have to dealloc before considering factory init overload
noisy new # pointer factory calling "new", part 1: allocation
NoisyAlloc(double 1.5) # ... part two, invoking constructor
---
~NoisyAlloc() # Destructor
noisy delete # operator delete
""")
def test_reallocation_c(capture, msg):
with capture:
create_and_destroy(2, 3)
assert msg(capture) == strip_comments("""
noisy new # pointer factory calling "new", allocation
NoisyAlloc(int 2) # constructor
---
~NoisyAlloc() # Destructor
noisy delete # operator delete
""")
def test_reallocation_d(capture, msg):
with capture:
create_and_destroy(2.5, 3)
assert msg(capture) == strip_comments("""
NoisyAlloc(double 2.5) # construction (local func variable: operator_new not called)
noisy new # return-by-value "new" part 1: allocation
~NoisyAlloc() # moved-away local func variable destruction
---
~NoisyAlloc() # Destructor
noisy delete # operator delete
""")
def test_reallocation_e(capture, msg):
with capture:
create_and_destroy(3.5, 4.5)
assert msg(capture) == strip_comments("""
noisy new # preallocation needed before invoking placement-new overload
noisy placement new # Placement new
NoisyAlloc(double 3.5) # construction
---
~NoisyAlloc() # Destructor
noisy delete # operator delete
""")
def test_reallocation_f(capture, msg):
with capture:
create_and_destroy(4, 0.5)
assert msg(capture) == strip_comments("""
noisy new # preallocation needed before invoking placement-new overload
noisy delete # deallocation of preallocated storage
noisy new # Factory pointer allocation
NoisyAlloc(int 4) # factory pointer construction
---
~NoisyAlloc() # Destructor
noisy delete # operator delete
""")
def test_reallocation_g(capture, msg):
with capture:
create_and_destroy(5, "hi")
assert msg(capture) == strip_comments("""
noisy new # preallocation needed before invoking first placement new
noisy delete # delete before considering new-style constructor
noisy new # preallocation for second placement new
noisy placement new # Placement new in the second placement new overload
NoisyAlloc(int 5) # construction
---
~NoisyAlloc() # Destructor
noisy delete # operator delete
""")
def test_invalid_self():
"""Tests invocation of the pybind-registered base class with an invalid `self` argument."""
class NotPybindDerived:
pass
# Attempts to initialize with an invalid type passed as `self`:
class BrokenTF1(m.TestFactory1):
def __init__(self, bad):
if bad == 1:
a = m.TestFactory2(tag.pointer, 1)
m.TestFactory1.__init__(a, tag.pointer)
elif bad == 2:
a = NotPybindDerived()
m.TestFactory1.__init__(a, tag.pointer)
# Same as above, but for a class with an alias:
class BrokenTF6(m.TestFactory6):
def __init__(self, bad):
if bad == 0:
m.TestFactory6.__init__()
elif bad == 1:
a = m.TestFactory2(tag.pointer, 1)
m.TestFactory6.__init__(a, tag.base, 1)
elif bad == 2:
a = m.TestFactory2(tag.pointer, 1)
m.TestFactory6.__init__(a, tag.alias, 1)
elif bad == 3:
m.TestFactory6.__init__(
NotPybindDerived.__new__(NotPybindDerived), tag.base, 1)
elif bad == 4:
m.TestFactory6.__init__(
NotPybindDerived.__new__(NotPybindDerived), tag.alias, 1)
for arg in (1, 2):
with pytest.raises(TypeError) as excinfo:
BrokenTF1(arg)
assert (
str(excinfo.value) ==
"__init__(self, ...) called with invalid or missing `self` argument"
)
for arg in (0, 1, 2, 3, 4):
with pytest.raises(TypeError) as excinfo:
BrokenTF6(arg)
assert (
str(excinfo.value) ==
"__init__(self, ...) called with invalid or missing `self` argument"
)

View File

@@ -0,0 +1,47 @@
/*
tests/test_gil_scoped.cpp -- acquire and release gil
Copyright (c) 2017 Borja Zarco (Google LLC) <bzarco@google.com>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/functional.h>
#include "pybind11_tests.h"
class VirtClass {
public:
virtual ~VirtClass() = default;
VirtClass() = default;
VirtClass(const VirtClass &) = delete;
virtual void virtual_func() {}
virtual void pure_virtual_func() = 0;
};
class PyVirtClass : public VirtClass {
void virtual_func() override { PYBIND11_OVERRIDE(void, VirtClass, virtual_func, ); }
void pure_virtual_func() override {
PYBIND11_OVERRIDE_PURE(void, VirtClass, pure_virtual_func, );
}
};
TEST_SUBMODULE(gil_scoped, m) {
py::class_<VirtClass, PyVirtClass>(m, "VirtClass")
.def(py::init<>())
.def("virtual_func", &VirtClass::virtual_func)
.def("pure_virtual_func", &VirtClass::pure_virtual_func);
m.def("test_callback_py_obj", [](py::object &func) { func(); });
m.def("test_callback_std_func", [](const std::function<void()> &func) { func(); });
m.def("test_callback_virtual_func", [](VirtClass &virt) { virt.virtual_func(); });
m.def("test_callback_pure_virtual_func", [](VirtClass &virt) { virt.pure_virtual_func(); });
m.def("test_cross_module_gil", []() {
auto cm = py::module_::import("cross_module_gil_utils");
auto gil_acquire = reinterpret_cast<void (*)()>(
PyLong_AsVoidPtr(cm.attr("gil_acquire_funcaddr").ptr()));
py::gil_scoped_release gil_release;
gil_acquire();
});
}

View File

@@ -0,0 +1,93 @@
import multiprocessing
import threading
from pybind11_tests import gil_scoped as m
def _run_in_process(target, *args, **kwargs):
"""Runs target in process and returns its exitcode after 10s (None if still alive)."""
process = multiprocessing.Process(target=target, args=args, kwargs=kwargs)
process.daemon = True
try:
process.start()
# Do not need to wait much, 10s should be more than enough.
process.join(timeout=10)
return process.exitcode
finally:
if process.is_alive():
process.terminate()
def _python_to_cpp_to_python():
"""Calls different C++ functions that come back to Python."""
class ExtendedVirtClass(m.VirtClass):
def virtual_func(self):
pass
def pure_virtual_func(self):
pass
extended = ExtendedVirtClass()
m.test_callback_py_obj(lambda: None)
m.test_callback_std_func(lambda: None)
m.test_callback_virtual_func(extended)
m.test_callback_pure_virtual_func(extended)
def _python_to_cpp_to_python_from_threads(num_threads, parallel=False):
"""Calls different C++ functions that come back to Python, from Python threads."""
threads = []
for _ in range(num_threads):
thread = threading.Thread(target=_python_to_cpp_to_python)
thread.daemon = True
thread.start()
if parallel:
threads.append(thread)
else:
thread.join()
for thread in threads:
thread.join()
# TODO: FIXME, sometimes returns -11 (segfault) instead of 0 on macOS Python 3.9
def test_python_to_cpp_to_python_from_thread():
"""Makes sure there is no GIL deadlock when running in a thread.
It runs in a separate process to be able to stop and assert if it deadlocks.
"""
assert _run_in_process(_python_to_cpp_to_python_from_threads, 1) == 0
# TODO: FIXME on macOS Python 3.9
def test_python_to_cpp_to_python_from_thread_multiple_parallel():
"""Makes sure there is no GIL deadlock when running in a thread multiple times in parallel.
It runs in a separate process to be able to stop and assert if it deadlocks.
"""
assert _run_in_process(
_python_to_cpp_to_python_from_threads, 8, parallel=True) == 0
# TODO: FIXME on macOS Python 3.9
def test_python_to_cpp_to_python_from_thread_multiple_sequential():
"""Makes sure there is no GIL deadlock when running in a thread multiple times sequentially.
It runs in a separate process to be able to stop and assert if it deadlocks.
"""
assert (_run_in_process(
_python_to_cpp_to_python_from_threads, 8, parallel=False) == 0)
# TODO: FIXME on macOS Python 3.9
def test_python_to_cpp_to_python_from_process():
"""Makes sure there is no GIL deadlock when using processes.
This test is for completion, but it was never an issue.
"""
assert _run_in_process(_python_to_cpp_to_python) == 0
def test_cross_module_gil():
"""Makes sure that the GIL can be acquired by another module from a GIL-released state."""
m.test_cross_module_gil() # Should not raise a SIGSEGV

View File

@@ -0,0 +1,126 @@
/*
tests/test_iostream.cpp -- Usage of scoped_output_redirect
Copyright (c) 2017 Henry F. Schreiner
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/iostream.h>
#include "pybind11_tests.h"
#include <atomic>
#include <iostream>
#include <mutex>
#include <string>
#include <thread>
void noisy_function(const std::string &msg, bool flush) {
std::cout << msg;
if (flush) {
std::cout << std::flush;
}
}
void noisy_funct_dual(const std::string &msg, const std::string &emsg) {
std::cout << msg;
std::cerr << emsg;
}
// object to manage C++ thread
// simply repeatedly write to std::cerr until stopped
// redirect is called at some point to test the safety of scoped_estream_redirect
struct TestThread {
TestThread() : stop_{false} {
auto thread_f = [this] {
static std::mutex cout_mutex;
while (!stop_) {
{
// #HelpAppreciated: Work on iostream.h thread safety.
// Without this lock, the clang ThreadSanitizer (tsan) reliably reports a
// data race, and this test is predictably flakey on Windows.
// For more background see the discussion under
// https://github.com/pybind/pybind11/pull/2982 and
// https://github.com/pybind/pybind11/pull/2995.
const std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "x" << std::flush;
}
std::this_thread::sleep_for(std::chrono::microseconds(50));
}
};
t_ = new std::thread(std::move(thread_f));
}
~TestThread() { delete t_; }
void stop() { stop_ = true; }
void join() const {
py::gil_scoped_release gil_lock;
t_->join();
}
void sleep() {
py::gil_scoped_release gil_lock;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
std::thread *t_{nullptr};
std::atomic<bool> stop_;
};
TEST_SUBMODULE(iostream, m) {
add_ostream_redirect(m);
// test_evals
m.def("captured_output_default", [](const std::string &msg) {
py::scoped_ostream_redirect redir;
std::cout << msg << std::flush;
});
m.def("captured_output", [](const std::string &msg) {
py::scoped_ostream_redirect redir(std::cout, py::module_::import("sys").attr("stdout"));
std::cout << msg << std::flush;
});
m.def("guard_output",
&noisy_function,
py::call_guard<py::scoped_ostream_redirect>(),
py::arg("msg"),
py::arg("flush") = true);
m.def("captured_err", [](const std::string &msg) {
py::scoped_ostream_redirect redir(std::cerr, py::module_::import("sys").attr("stderr"));
std::cerr << msg << std::flush;
});
m.def("noisy_function", &noisy_function, py::arg("msg"), py::arg("flush") = true);
m.def("dual_guard",
&noisy_funct_dual,
py::call_guard<py::scoped_ostream_redirect, py::scoped_estream_redirect>(),
py::arg("msg"),
py::arg("emsg"));
m.def("raw_output", [](const std::string &msg) { std::cout << msg << std::flush; });
m.def("raw_err", [](const std::string &msg) { std::cerr << msg << std::flush; });
m.def("captured_dual", [](const std::string &msg, const std::string &emsg) {
py::scoped_ostream_redirect redirout(std::cout, py::module_::import("sys").attr("stdout"));
py::scoped_ostream_redirect redirerr(std::cerr, py::module_::import("sys").attr("stderr"));
std::cout << msg << std::flush;
std::cerr << emsg << std::flush;
});
py::class_<TestThread>(m, "TestThread")
.def(py::init<>())
.def("stop", &TestThread::stop)
.def("join", &TestThread::join)
.def("sleep", &TestThread::sleep);
}

View File

@@ -0,0 +1,295 @@
from contextlib import redirect_stderr, redirect_stdout
from io import StringIO
from pybind11_tests import iostream as m
def test_captured(capsys):
msg = "I've been redirected to Python, I hope!"
m.captured_output(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
m.captured_err(msg)
stdout, stderr = capsys.readouterr()
assert stdout == ""
assert stderr == msg
def test_captured_large_string(capsys):
# Make this bigger than the buffer used on the C++ side: 1024 chars
msg = "I've been redirected to Python, I hope!"
msg = msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_captured_utf8_2byte_offset0(capsys):
msg = "\u07FF"
msg = "" + msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_captured_utf8_2byte_offset1(capsys):
msg = "\u07FF"
msg = "1" + msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_captured_utf8_3byte_offset0(capsys):
msg = "\uFFFF"
msg = "" + msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_captured_utf8_3byte_offset1(capsys):
msg = "\uFFFF"
msg = "1" + msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_captured_utf8_3byte_offset2(capsys):
msg = "\uFFFF"
msg = "12" + msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_captured_utf8_4byte_offset0(capsys):
msg = "\U0010FFFF"
msg = "" + msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_captured_utf8_4byte_offset1(capsys):
msg = "\U0010FFFF"
msg = "1" + msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_captured_utf8_4byte_offset2(capsys):
msg = "\U0010FFFF"
msg = "12" + msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_captured_utf8_4byte_offset3(capsys):
msg = "\U0010FFFF"
msg = "123" + msg * (1024 // len(msg) + 1)
m.captured_output_default(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_guard_capture(capsys):
msg = "I've been redirected to Python, I hope!"
m.guard_output(msg)
stdout, stderr = capsys.readouterr()
assert stdout == msg
assert stderr == ""
def test_series_captured(capture):
with capture:
m.captured_output("a")
m.captured_output("b")
assert capture == "ab"
def test_flush(capfd):
msg = "(not flushed)"
msg2 = "(flushed)"
with m.ostream_redirect():
m.noisy_function(msg, flush=False)
stdout, stderr = capfd.readouterr()
assert stdout == ""
m.noisy_function(msg2, flush=True)
stdout, stderr = capfd.readouterr()
assert stdout == msg + msg2
m.noisy_function(msg, flush=False)
stdout, stderr = capfd.readouterr()
assert stdout == msg
def test_not_captured(capfd):
msg = "Something that should not show up in log"
stream = StringIO()
with redirect_stdout(stream):
m.raw_output(msg)
stdout, stderr = capfd.readouterr()
assert stdout == msg
assert stderr == ""
assert stream.getvalue() == ""
stream = StringIO()
with redirect_stdout(stream):
m.captured_output(msg)
stdout, stderr = capfd.readouterr()
assert stdout == ""
assert stderr == ""
assert stream.getvalue() == msg
def test_err(capfd):
msg = "Something that should not show up in log"
stream = StringIO()
with redirect_stderr(stream):
m.raw_err(msg)
stdout, stderr = capfd.readouterr()
assert stdout == ""
assert stderr == msg
assert stream.getvalue() == ""
stream = StringIO()
with redirect_stderr(stream):
m.captured_err(msg)
stdout, stderr = capfd.readouterr()
assert stdout == ""
assert stderr == ""
assert stream.getvalue() == msg
def test_multi_captured(capfd):
stream = StringIO()
with redirect_stdout(stream):
m.captured_output("a")
m.raw_output("b")
m.captured_output("c")
m.raw_output("d")
stdout, stderr = capfd.readouterr()
assert stdout == "bd"
assert stream.getvalue() == "ac"
def test_dual(capsys):
m.captured_dual("a", "b")
stdout, stderr = capsys.readouterr()
assert stdout == "a"
assert stderr == "b"
def test_redirect(capfd):
msg = "Should not be in log!"
stream = StringIO()
with redirect_stdout(stream):
m.raw_output(msg)
stdout, stderr = capfd.readouterr()
assert stdout == msg
assert stream.getvalue() == ""
stream = StringIO()
with redirect_stdout(stream):
with m.ostream_redirect():
m.raw_output(msg)
stdout, stderr = capfd.readouterr()
assert stdout == ""
assert stream.getvalue() == msg
stream = StringIO()
with redirect_stdout(stream):
m.raw_output(msg)
stdout, stderr = capfd.readouterr()
assert stdout == msg
assert stream.getvalue() == ""
def test_redirect_err(capfd):
msg = "StdOut"
msg2 = "StdErr"
stream = StringIO()
with redirect_stderr(stream):
with m.ostream_redirect(stdout=False):
m.raw_output(msg)
m.raw_err(msg2)
stdout, stderr = capfd.readouterr()
assert stdout == msg
assert stderr == ""
assert stream.getvalue() == msg2
def test_redirect_both(capfd):
msg = "StdOut"
msg2 = "StdErr"
stream = StringIO()
stream2 = StringIO()
with redirect_stdout(stream):
with redirect_stderr(stream2):
with m.ostream_redirect():
m.raw_output(msg)
m.raw_err(msg2)
stdout, stderr = capfd.readouterr()
assert stdout == ""
assert stderr == ""
assert stream.getvalue() == msg
assert stream2.getvalue() == msg2
def test_threading():
with m.ostream_redirect(stdout=True, stderr=False):
# start some threads
threads = []
# start some threads
for _j in range(20):
threads.append(m.TestThread())
# give the threads some time to fail
threads[0].sleep()
# stop all the threads
for t in threads:
t.stop()
for t in threads:
t.join()
# if a thread segfaults, we don't get here
assert True

View File

@@ -0,0 +1,273 @@
/*
tests/test_kwargs_and_defaults.cpp -- keyword arguments and default values
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/stl.h>
#include "constructor_stats.h"
#include "pybind11_tests.h"
#include <utility>
TEST_SUBMODULE(kwargs_and_defaults, m) {
auto kw_func
= [](int x, int y) { return "x=" + std::to_string(x) + ", y=" + std::to_string(y); };
// test_named_arguments
m.def("kw_func0", kw_func);
m.def("kw_func1", kw_func, py::arg("x"), py::arg("y"));
m.def("kw_func2", kw_func, py::arg("x") = 100, py::arg("y") = 200);
m.def(
"kw_func3", [](const char *) {}, py::arg("data") = std::string("Hello world!"));
/* A fancier default argument */
std::vector<int> list{{13, 17}};
m.def(
"kw_func4",
[](const std::vector<int> &entries) {
std::string ret = "{";
for (int i : entries) {
ret += std::to_string(i) + " ";
}
ret.back() = '}';
return ret;
},
py::arg("myList") = list);
m.def("kw_func_udl", kw_func, "x"_a, "y"_a = 300);
m.def("kw_func_udl_z", kw_func, "x"_a, "y"_a = 0);
// test_args_and_kwargs
m.def("args_function", [](py::args args) -> py::tuple { return std::move(args); });
m.def("args_kwargs_function", [](const py::args &args, const py::kwargs &kwargs) {
return py::make_tuple(args, kwargs);
});
// test_mixed_args_and_kwargs
m.def("mixed_plus_args",
[](int i, double j, const py::args &args) { return py::make_tuple(i, j, args); });
m.def("mixed_plus_kwargs",
[](int i, double j, const py::kwargs &kwargs) { return py::make_tuple(i, j, kwargs); });
auto mixed_plus_both = [](int i, double j, const py::args &args, const py::kwargs &kwargs) {
return py::make_tuple(i, j, args, kwargs);
};
m.def("mixed_plus_args_kwargs", mixed_plus_both);
m.def("mixed_plus_args_kwargs_defaults",
mixed_plus_both,
py::arg("i") = 1,
py::arg("j") = 3.14159);
m.def(
"args_kwonly",
[](int i, double j, const py::args &args, int z) { return py::make_tuple(i, j, args, z); },
"i"_a,
"j"_a,
"z"_a);
m.def(
"args_kwonly_kwargs",
[](int i, double j, const py::args &args, int z, const py::kwargs &kwargs) {
return py::make_tuple(i, j, args, z, kwargs);
},
"i"_a,
"j"_a,
py::kw_only{},
"z"_a);
m.def(
"args_kwonly_kwargs_defaults",
[](int i, double j, const py::args &args, int z, const py::kwargs &kwargs) {
return py::make_tuple(i, j, args, z, kwargs);
},
"i"_a = 1,
"j"_a = 3.14159,
"z"_a = 42);
m.def(
"args_kwonly_full_monty",
[](int h, int i, double j, const py::args &args, int z, const py::kwargs &kwargs) {
return py::make_tuple(h, i, j, args, z, kwargs);
},
py::arg() = 1,
py::arg() = 2,
py::pos_only{},
"j"_a = 3.14159,
"z"_a = 42);
// test_args_refcount
// PyPy needs a garbage collection to get the reference count values to match CPython's behaviour
#ifdef PYPY_VERSION
# define GC_IF_NEEDED ConstructorStats::gc()
#else
# define GC_IF_NEEDED
#endif
m.def("arg_refcount_h", [](py::handle h) {
GC_IF_NEEDED;
return h.ref_count();
});
m.def("arg_refcount_h", [](py::handle h, py::handle, py::handle) {
GC_IF_NEEDED;
return h.ref_count();
});
m.def("arg_refcount_o", [](const py::object &o) {
GC_IF_NEEDED;
return o.ref_count();
});
m.def("args_refcount", [](py::args a) {
GC_IF_NEEDED;
py::tuple t(a.size());
for (size_t i = 0; i < a.size(); i++) {
// Use raw Python API here to avoid an extra, intermediate incref on the tuple item:
t[i] = (int) Py_REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast<py::ssize_t>(i)));
}
return t;
});
m.def("mixed_args_refcount", [](const py::object &o, py::args a) {
GC_IF_NEEDED;
py::tuple t(a.size() + 1);
t[0] = o.ref_count();
for (size_t i = 0; i < a.size(); i++) {
// Use raw Python API here to avoid an extra, intermediate incref on the tuple item:
t[i + 1] = (int) Py_REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast<py::ssize_t>(i)));
}
return t;
});
// pybind11 won't allow these to be bound: args and kwargs, if present, must be at the end.
// Uncomment these to test that the static_assert is indeed working:
// m.def("bad_args1", [](py::args, int) {});
// m.def("bad_args2", [](py::kwargs, int) {});
// m.def("bad_args3", [](py::kwargs, py::args) {});
// m.def("bad_args4", [](py::args, int, py::kwargs) {});
// m.def("bad_args5", [](py::args, py::kwargs, int) {});
// m.def("bad_args6", [](py::args, py::args) {});
// m.def("bad_args7", [](py::kwargs, py::kwargs) {});
// test_keyword_only_args
m.def(
"kw_only_all",
[](int i, int j) { return py::make_tuple(i, j); },
py::kw_only(),
py::arg("i"),
py::arg("j"));
m.def(
"kw_only_some",
[](int i, int j, int k) { return py::make_tuple(i, j, k); },
py::arg(),
py::kw_only(),
py::arg("j"),
py::arg("k"));
m.def(
"kw_only_with_defaults",
[](int i, int j, int k, int z) { return py::make_tuple(i, j, k, z); },
py::arg() = 3,
"j"_a = 4,
py::kw_only(),
"k"_a = 5,
"z"_a);
m.def(
"kw_only_mixed",
[](int i, int j) { return py::make_tuple(i, j); },
"i"_a,
py::kw_only(),
"j"_a);
m.def(
"kw_only_plus_more",
[](int i, int j, int k, const py::kwargs &kwargs) {
return py::make_tuple(i, j, k, kwargs);
},
py::arg() /* positional */,
py::arg("j") = -1 /* both */,
py::kw_only(),
py::arg("k") /* kw-only */);
m.def("register_invalid_kw_only", [](py::module_ m) {
m.def(
"bad_kw_only",
[](int i, int j) { return py::make_tuple(i, j); },
py::kw_only(),
py::arg() /* invalid unnamed argument */,
"j"_a);
});
// test_positional_only_args
m.def(
"pos_only_all",
[](int i, int j) { return py::make_tuple(i, j); },
py::arg("i"),
py::arg("j"),
py::pos_only());
m.def(
"pos_only_mix",
[](int i, int j) { return py::make_tuple(i, j); },
py::arg("i"),
py::pos_only(),
py::arg("j"));
m.def(
"pos_kw_only_mix",
[](int i, int j, int k) { return py::make_tuple(i, j, k); },
py::arg("i"),
py::pos_only(),
py::arg("j"),
py::kw_only(),
py::arg("k"));
m.def(
"pos_only_def_mix",
[](int i, int j, int k) { return py::make_tuple(i, j, k); },
py::arg("i"),
py::arg("j") = 2,
py::pos_only(),
py::arg("k") = 3);
// These should fail to compile:
#ifdef PYBIND11_NEVER_DEFINED_EVER
// argument annotations are required when using kw_only
m.def(
"bad_kw_only1", [](int) {}, py::kw_only());
// can't specify both `py::kw_only` and a `py::args` argument
m.def(
"bad_kw_only2", [](int i, py::args) {}, py::kw_only(), "i"_a);
#endif
// test_function_signatures (along with most of the above)
struct KWClass {
void foo(int, float) {}
};
py::class_<KWClass>(m, "KWClass")
.def("foo0", &KWClass::foo)
.def("foo1", &KWClass::foo, "x"_a, "y"_a);
// Make sure a class (not an instance) can be used as a default argument.
// The return value doesn't matter, only that the module is importable.
m.def(
"class_default_argument",
[](py::object a) { return py::repr(std::move(a)); },
"a"_a = py::module_::import("decimal").attr("Decimal"));
// Initial implementation of kw_only was broken when used on a method/constructor before any
// other arguments
// https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987
struct first_arg_kw_only {};
py::class_<first_arg_kw_only>(m, "first_arg_kw_only")
.def(py::init([](int) { return first_arg_kw_only(); }),
py::kw_only(), // This being before any args was broken
py::arg("i") = 0)
.def(
"method",
[](first_arg_kw_only &, int, int) {},
py::kw_only(), // and likewise here
py::arg("i") = 1,
py::arg("j") = 2)
// Closely related: pos_only marker didn't show up properly when it was before any other
// arguments (although that is fairly useless in practice).
.def(
"pos_only",
[](first_arg_kw_only &, int, int) {},
py::pos_only{},
py::arg("i"),
py::arg("j"));
}

View File

@@ -0,0 +1,404 @@
import pytest
from pybind11_tests import kwargs_and_defaults as m
def test_function_signatures(doc):
assert doc(m.kw_func0) == "kw_func0(arg0: int, arg1: int) -> str"
assert doc(m.kw_func1) == "kw_func1(x: int, y: int) -> str"
assert doc(m.kw_func2) == "kw_func2(x: int = 100, y: int = 200) -> str"
assert doc(m.kw_func3) == "kw_func3(data: str = 'Hello world!') -> None"
assert doc(m.kw_func4) == "kw_func4(myList: List[int] = [13, 17]) -> str"
assert doc(m.kw_func_udl) == "kw_func_udl(x: int, y: int = 300) -> str"
assert doc(m.kw_func_udl_z) == "kw_func_udl_z(x: int, y: int = 0) -> str"
assert doc(m.args_function) == "args_function(*args) -> tuple"
assert (doc(m.args_kwargs_function) ==
"args_kwargs_function(*args, **kwargs) -> tuple")
assert (
doc(m.KWClass.foo0) ==
"foo0(self: m.kwargs_and_defaults.KWClass, arg0: int, arg1: float) -> None"
)
assert (
doc(m.KWClass.foo1) ==
"foo1(self: m.kwargs_and_defaults.KWClass, x: int, y: float) -> None")
def test_named_arguments(msg):
assert m.kw_func0(5, 10) == "x=5, y=10"
assert m.kw_func1(5, 10) == "x=5, y=10"
assert m.kw_func1(5, y=10) == "x=5, y=10"
assert m.kw_func1(y=10, x=5) == "x=5, y=10"
assert m.kw_func2() == "x=100, y=200"
assert m.kw_func2(5) == "x=5, y=200"
assert m.kw_func2(x=5) == "x=5, y=200"
assert m.kw_func2(y=10) == "x=100, y=10"
assert m.kw_func2(5, 10) == "x=5, y=10"
assert m.kw_func2(x=5, y=10) == "x=5, y=10"
with pytest.raises(TypeError) as excinfo:
# noinspection PyArgumentList
m.kw_func2(x=5, y=10, z=12)
assert excinfo.match(
r"(?s)^kw_func2\(\): incompatible.*Invoked with: kwargs: ((x=5|y=10|z=12)(, |$))"
+ "{3}$")
assert m.kw_func4() == "{13 17}"
assert m.kw_func4(myList=[1, 2, 3]) == "{1 2 3}"
assert m.kw_func_udl(x=5, y=10) == "x=5, y=10"
assert m.kw_func_udl_z(x=5) == "x=5, y=0"
def test_arg_and_kwargs():
args = "arg1_value", "arg2_value", 3
assert m.args_function(*args) == args
args = "a1", "a2"
kwargs = dict(arg3="a3", arg4=4)
assert m.args_kwargs_function(*args, **kwargs) == (args, kwargs)
def test_mixed_args_and_kwargs(msg):
mpa = m.mixed_plus_args
mpk = m.mixed_plus_kwargs
mpak = m.mixed_plus_args_kwargs
mpakd = m.mixed_plus_args_kwargs_defaults
assert mpa(1, 2.5, 4, 99.5, None) == (1, 2.5, (4, 99.5, None))
assert mpa(1, 2.5) == (1, 2.5, ())
with pytest.raises(TypeError) as excinfo:
assert mpa(1)
assert (msg(excinfo.value) == """
mixed_plus_args(): incompatible function arguments. The following argument types are supported:
1. (arg0: int, arg1: float, *args) -> tuple
Invoked with: 1
""")
with pytest.raises(TypeError) as excinfo:
assert mpa()
assert (msg(excinfo.value) == """
mixed_plus_args(): incompatible function arguments. The following argument types are supported:
1. (arg0: int, arg1: float, *args) -> tuple
Invoked with:
""")
assert mpk(-2, 3.5, pi=3.14159, e=2.71828) == (
-2,
3.5,
{
"e": 2.71828,
"pi": 3.14159
}, )
assert mpak(
7, 7.7, 7.77, 7.777, 7.7777, minusseven=-7) == (
7,
7.7,
(7.77, 7.777, 7.7777),
{
"minusseven": -7
}, )
assert mpakd() == (1, 3.14159, (), {})
assert mpakd(3) == (3, 3.14159, (), {})
assert mpakd(j=2.71828) == (1, 2.71828, (), {})
assert mpakd(k=42) == (1, 3.14159, (), {"k": 42})
assert mpakd(
1, 1, 2, 3, 5, 8, then=13, followedby=21) == (
1,
1,
(2, 3, 5, 8),
{
"then": 13,
"followedby": 21
}, )
# Arguments specified both positionally and via kwargs should fail:
with pytest.raises(TypeError) as excinfo:
assert mpakd(1, i=1)
assert (msg(excinfo.value) == """
mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
1. (i: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple
Invoked with: 1; kwargs: i=1
""")
with pytest.raises(TypeError) as excinfo:
assert mpakd(1, 2, j=1)
assert (msg(excinfo.value) == """
mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
1. (i: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple
Invoked with: 1, 2; kwargs: j=1
""")
# Arguments after a py::args are automatically keyword-only (pybind 2.9+)
assert m.args_kwonly(2, 2.5, z=22) == (2, 2.5, (), 22)
assert m.args_kwonly(
2, 2.5, "a", "b", "c", z=22) == (2, 2.5, ("a", "b", "c"), 22)
assert m.args_kwonly(z=22, i=4, j=16) == (4, 16, (), 22)
with pytest.raises(TypeError) as excinfo:
assert m.args_kwonly(2, 2.5, 22) # missing z= keyword
assert (msg(excinfo.value) == """
args_kwonly(): incompatible function arguments. The following argument types are supported:
1. (i: int, j: float, *args, z: int) -> tuple
Invoked with: 2, 2.5, 22
""")
assert m.args_kwonly_kwargs(
i=1, k=4, j=10, z=-1, y=9) == (
1,
10,
(),
-1,
{
"k": 4,
"y": 9
}, )
assert m.args_kwonly_kwargs(
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, z=11, y=12) == (
1,
2,
(3, 4, 5, 6, 7, 8, 9, 10),
11,
{
"y": 12
}, )
assert (
m.args_kwonly_kwargs.__doc__ ==
"args_kwonly_kwargs(i: int, j: float, *args, z: int, **kwargs) -> tuple\n"
)
assert (
m.args_kwonly_kwargs_defaults.__doc__ ==
"args_kwonly_kwargs_defaults(i: int = 1, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" # noqa: E501 line too long
)
assert m.args_kwonly_kwargs_defaults() == (1, 3.14159, (), 42, {})
assert m.args_kwonly_kwargs_defaults(2) == (2, 3.14159, (), 42, {})
assert m.args_kwonly_kwargs_defaults(z=-99) == (1, 3.14159, (), -99, {})
assert m.args_kwonly_kwargs_defaults(5, 6, 7, 8) == (5, 6, (7, 8), 42, {})
assert m.args_kwonly_kwargs_defaults(
5, 6, 7, m=8) == (5, 6, (7, ), 42, {
"m": 8
})
assert m.args_kwonly_kwargs_defaults(
5, 6, 7, m=8, z=9) == (5, 6, (7, ), 9, {
"m": 8
})
def test_keyword_only_args(msg):
assert m.kw_only_all(i=1, j=2) == (1, 2)
assert m.kw_only_all(j=1, i=2) == (2, 1)
with pytest.raises(TypeError) as excinfo:
assert m.kw_only_all(i=1) == (1, )
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
assert m.kw_only_all(1, 2) == (1, 2)
assert "incompatible function arguments" in str(excinfo.value)
assert m.kw_only_some(1, k=3, j=2) == (1, 2, 3)
assert m.kw_only_with_defaults(z=8) == (3, 4, 5, 8)
assert m.kw_only_with_defaults(2, z=8) == (2, 4, 5, 8)
assert m.kw_only_with_defaults(2, j=7, k=8, z=9) == (2, 7, 8, 9)
assert m.kw_only_with_defaults(2, 7, z=9, k=8) == (2, 7, 8, 9)
assert m.kw_only_mixed(1, j=2) == (1, 2)
assert m.kw_only_mixed(j=2, i=3) == (3, 2)
assert m.kw_only_mixed(i=2, j=3) == (2, 3)
assert m.kw_only_plus_more(4, 5, k=6, extra=7) == (4, 5, 6, {"extra": 7})
assert m.kw_only_plus_more(3, k=5, j=4, extra=6) == (3, 4, 5, {"extra": 6})
assert m.kw_only_plus_more(2, k=3, extra=4) == (2, -1, 3, {"extra": 4})
with pytest.raises(TypeError) as excinfo:
assert m.kw_only_mixed(i=1) == (1, )
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(RuntimeError) as excinfo:
m.register_invalid_kw_only(m)
assert (msg(excinfo.value) == """
arg(): cannot specify an unnamed argument after a kw_only() annotation or args() argument
""")
# https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987
x = m.first_arg_kw_only(i=1)
x.method()
x.method(i=1, j=2)
assert (
m.first_arg_kw_only.__init__.__doc__ ==
"__init__(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 0) -> None\n" # noqa: E501 line too long
)
assert (
m.first_arg_kw_only.method.__doc__ ==
"method(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, *, i: int = 1, j: int = 2) -> None\n" # noqa: E501 line too long
)
def test_positional_only_args(msg):
assert m.pos_only_all(1, 2) == (1, 2)
assert m.pos_only_all(2, 1) == (2, 1)
with pytest.raises(TypeError) as excinfo:
m.pos_only_all(i=1, j=2)
assert "incompatible function arguments" in str(excinfo.value)
assert m.pos_only_mix(1, 2) == (1, 2)
assert m.pos_only_mix(2, j=1) == (2, 1)
with pytest.raises(TypeError) as excinfo:
m.pos_only_mix(i=1, j=2)
assert "incompatible function arguments" in str(excinfo.value)
assert m.pos_kw_only_mix(1, 2, k=3) == (1, 2, 3)
assert m.pos_kw_only_mix(1, j=2, k=3) == (1, 2, 3)
with pytest.raises(TypeError) as excinfo:
m.pos_kw_only_mix(i=1, j=2, k=3)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.pos_kw_only_mix(1, 2, 3)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.pos_only_def_mix()
assert "incompatible function arguments" in str(excinfo.value)
assert m.pos_only_def_mix(1) == (1, 2, 3)
assert m.pos_only_def_mix(1, 4) == (1, 4, 3)
assert m.pos_only_def_mix(1, 4, 7) == (1, 4, 7)
assert m.pos_only_def_mix(1, 4, k=7) == (1, 4, 7)
with pytest.raises(TypeError) as excinfo:
m.pos_only_def_mix(1, j=4)
assert "incompatible function arguments" in str(excinfo.value)
# Mix it with args and kwargs:
assert (
m.args_kwonly_full_monty.__doc__ ==
"args_kwonly_full_monty(arg0: int = 1, arg1: int = 2, /, j: float = 3.14159, *args, z: int = 42, **kwargs) -> tuple\n" # noqa: E501 line too long
)
assert m.args_kwonly_full_monty() == (1, 2, 3.14159, (), 42, {})
assert m.args_kwonly_full_monty(8) == (8, 2, 3.14159, (), 42, {})
assert m.args_kwonly_full_monty(8, 9) == (8, 9, 3.14159, (), 42, {})
assert m.args_kwonly_full_monty(8, 9, 10) == (8, 9, 10.0, (), 42, {})
assert m.args_kwonly_full_monty(
3, 4, 5, 6, 7, m=8, z=9) == (
3,
4,
5.0,
(
6,
7, ),
9,
{
"m": 8
}, )
assert m.args_kwonly_full_monty(
3, 4, 5, 6, 7, m=8, z=9) == (
3,
4,
5.0,
(
6,
7, ),
9,
{
"m": 8
}, )
assert m.args_kwonly_full_monty(
5, j=7, m=8, z=9) == (5, 2, 7.0, (), 9, {
"m": 8
})
assert m.args_kwonly_full_monty(
i=5, j=7, m=8, z=9) == (
1,
2,
7.0,
(),
9,
{
"i": 5,
"m": 8
}, )
# pos_only at the beginning of the argument list was "broken" in how it was displayed (though
# this is fairly useless in practice). Related to:
# https://github.com/pybind/pybind11/pull/3402#issuecomment-963341987
assert (
m.first_arg_kw_only.pos_only.__doc__ ==
"pos_only(self: pybind11_tests.kwargs_and_defaults.first_arg_kw_only, /, i: int, j: int) -> None\n" # noqa: E501 line too long
)
def test_signatures():
assert "kw_only_all(*, i: int, j: int) -> tuple\n" == m.kw_only_all.__doc__
assert "kw_only_mixed(i: int, *, j: int) -> tuple\n" == m.kw_only_mixed.__doc__
assert "pos_only_all(i: int, j: int, /) -> tuple\n" == m.pos_only_all.__doc__
assert "pos_only_mix(i: int, /, j: int) -> tuple\n" == m.pos_only_mix.__doc__
assert ("pos_kw_only_mix(i: int, /, j: int, *, k: int) -> tuple\n" ==
m.pos_kw_only_mix.__doc__)
def test_args_refcount():
"""Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular
arguments"""
refcount = m.arg_refcount_h
myval = 54321
expected = refcount(myval)
assert m.arg_refcount_h(myval) == expected
assert m.arg_refcount_o(myval) == expected + 1
assert m.arg_refcount_h(myval) == expected
assert refcount(myval) == expected
assert m.mixed_plus_args(1, 2.0, "a", myval) == (1, 2.0, ("a", myval))
assert refcount(myval) == expected
assert m.mixed_plus_kwargs(
3, 4.0, a=1, b=myval) == (3, 4.0, {
"a": 1,
"b": myval
})
assert refcount(myval) == expected
assert m.args_function(-1, myval) == (-1, myval)
assert refcount(myval) == expected
assert m.mixed_plus_args_kwargs(
5, 6.0, myval, a=myval) == (
5,
6.0,
(myval, ),
{
"a": myval
}, )
assert refcount(myval) == expected
assert m.args_kwargs_function(
7, 8, myval, a=1, b=myval) == (
(7, 8, myval),
{
"a": 1,
"b": myval
}, )
assert refcount(myval) == expected
exp3 = refcount(myval, myval, myval)
assert m.args_refcount(myval, myval, myval) == (exp3, exp3, exp3)
assert refcount(myval) == expected
# This function takes the first arg as a `py::object` and the rest as a `py::args`. Unlike the
# previous case, when we have both positional and `py::args` we need to construct a new tuple
# for the `py::args`; in the previous case, we could simply inc_ref and pass on Python's input
# tuple without having to inc_ref the individual elements, but here we can't, hence the extra
# refs.
assert m.mixed_args_refcount(myval, myval, myval) == (exp3 + 3, exp3 + 3,
exp3 + 3)
assert m.class_default_argument() == "<class 'decimal.Decimal'>"

View File

@@ -0,0 +1,106 @@
/*
tests/test_local_bindings.cpp -- tests the py::module_local class feature which makes a class
binding local to the module in which it is defined.
Copyright (c) 2017 Jason Rhinelander <jason@imaginary.ca>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>
#include "local_bindings.h"
#include "pybind11_tests.h"
#include <numeric>
#include <utility>
TEST_SUBMODULE(local_bindings, m) {
// test_load_external
m.def("load_external1", [](ExternalType1 &e) { return e.i; });
m.def("load_external2", [](ExternalType2 &e) { return e.i; });
// test_local_bindings
// Register a class with py::module_local:
bind_local<LocalType, -1>(m, "LocalType", py::module_local()).def("get3", [](LocalType &t) {
return t.i + 3;
});
m.def("local_value", [](LocalType &l) { return l.i; });
// test_nonlocal_failure
// The main pybind11 test module is loaded first, so this registration will succeed (the second
// one, in pybind11_cross_module_tests.cpp, is designed to fail):
bind_local<NonLocalType, 0>(m, "NonLocalType")
.def(py::init<int>())
.def("get", [](LocalType &i) { return i.i; });
// test_duplicate_local
// py::module_local declarations should be visible across compilation units that get linked
// together; this tries to register a duplicate local. It depends on a definition in
// test_class.cpp and should raise a runtime error from the duplicate definition attempt. If
// test_class isn't available it *also* throws a runtime error (with "test_class not enabled"
// as value).
m.def("register_local_external", [m]() {
auto main = py::module_::import("pybind11_tests");
if (py::hasattr(main, "class_")) {
bind_local<LocalExternal, 7>(m, "LocalExternal", py::module_local());
} else {
throw std::runtime_error("test_class not enabled");
}
});
// test_stl_bind_local
// stl_bind.h binders defaults to py::module_local if the types are local or converting:
py::bind_vector<LocalVec>(m, "LocalVec");
py::bind_map<LocalMap>(m, "LocalMap");
// and global if the type (or one of the types, for the map) is global:
py::bind_vector<NonLocalVec>(m, "NonLocalVec");
py::bind_map<NonLocalMap>(m, "NonLocalMap");
// test_stl_bind_global
// They can, however, be overridden to global using `py::module_local(false)`:
bind_local<NonLocal2, 10>(m, "NonLocal2");
py::bind_vector<LocalVec2>(m, "LocalVec2", py::module_local());
py::bind_map<NonLocalMap2>(m, "NonLocalMap2", py::module_local(false));
// test_mixed_local_global
// We try this both with the global type registered first and vice versa (the order shouldn't
// matter).
m.def("register_mixed_global", [m]() {
bind_local<MixedGlobalLocal, 100>(m, "MixedGlobalLocal", py::module_local(false));
});
m.def("register_mixed_local", [m]() {
bind_local<MixedLocalGlobal, 1000>(m, "MixedLocalGlobal", py::module_local());
});
m.def("get_mixed_gl", [](int i) { return MixedGlobalLocal(i); });
m.def("get_mixed_lg", [](int i) { return MixedLocalGlobal(i); });
// test_internal_locals_differ
m.def("local_cpp_types_addr",
[]() { return (uintptr_t) &py::detail::get_local_internals().registered_types_cpp; });
// test_stl_caster_vs_stl_bind
m.def("load_vector_via_caster",
[](std::vector<int> v) { return std::accumulate(v.begin(), v.end(), 0); });
// test_cross_module_calls
m.def("return_self", [](LocalVec *v) { return v; });
m.def("return_copy", [](const LocalVec &v) { return LocalVec(v); });
class Cat : public pets::Pet {
public:
explicit Cat(std::string name) : Pet(std::move(name)) {}
};
py::class_<pets::Pet>(m, "Pet", py::module_local()).def("get_name", &pets::Pet::name);
// Binding for local extending class:
py::class_<Cat, pets::Pet>(m, "Cat").def(py::init<std::string>());
m.def("pet_name", [](pets::Pet &p) { return p.name(); });
py::class_<MixGL>(m, "MixGL").def(py::init<int>());
m.def("get_gl_value", [](MixGL &o) { return o.i + 10; });
py::class_<MixGL2>(m, "MixGL2").def(py::init<int>());
}

View File

@@ -0,0 +1,247 @@
import pytest
import env # noqa: F401
from pybind11_tests import local_bindings as m
def test_load_external():
"""Load a `py::module_local` type that's only registered in an external module"""
import pybind11_cross_module_tests as cm
assert m.load_external1(cm.ExternalType1(11)) == 11
assert m.load_external2(cm.ExternalType2(22)) == 22
with pytest.raises(TypeError) as excinfo:
assert m.load_external2(cm.ExternalType1(21)) == 21
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
assert m.load_external1(cm.ExternalType2(12)) == 12
assert "incompatible function arguments" in str(excinfo.value)
def test_local_bindings():
"""Tests that duplicate `py::module_local` class bindings work across modules"""
# Make sure we can load the second module with the conflicting (but local) definition:
import pybind11_cross_module_tests as cm
i1 = m.LocalType(5)
assert i1.get() == 4
assert i1.get3() == 8
i2 = cm.LocalType(10)
assert i2.get() == 11
assert i2.get2() == 12
assert not hasattr(i1, "get2")
assert not hasattr(i2, "get3")
# Loading within the local module
assert m.local_value(i1) == 5
assert cm.local_value(i2) == 10
# Cross-module loading works as well (on failure, the type loader looks for
# external module-local converters):
assert m.local_value(i2) == 10
assert cm.local_value(i1) == 5
def test_nonlocal_failure():
"""Tests that attempting to register a non-local type in multiple modules fails"""
import pybind11_cross_module_tests as cm
with pytest.raises(RuntimeError) as excinfo:
cm.register_nonlocal()
assert (str(excinfo.value) ==
'generic_type: type "NonLocalType" is already registered!')
def test_duplicate_local():
"""Tests expected failure when registering a class twice with py::local in the same module"""
with pytest.raises(RuntimeError) as excinfo:
m.register_local_external()
import pybind11_tests
assert str(excinfo.value) == (
'generic_type: type "LocalExternal" is already registered!'
if hasattr(pybind11_tests, "class_") else "test_class not enabled")
def test_stl_bind_local():
import pybind11_cross_module_tests as cm
v1, v2 = m.LocalVec(), cm.LocalVec()
v1.append(m.LocalType(1))
v1.append(m.LocalType(2))
v2.append(cm.LocalType(1))
v2.append(cm.LocalType(2))
# Cross module value loading:
v1.append(cm.LocalType(3))
v2.append(m.LocalType(3))
assert [i.get() for i in v1] == [0, 1, 2]
assert [i.get() for i in v2] == [2, 3, 4]
v3, v4 = m.NonLocalVec(), cm.NonLocalVec2()
v3.append(m.NonLocalType(1))
v3.append(m.NonLocalType(2))
v4.append(m.NonLocal2(3))
v4.append(m.NonLocal2(4))
assert [i.get() for i in v3] == [1, 2]
assert [i.get() for i in v4] == [13, 14]
d1, d2 = m.LocalMap(), cm.LocalMap()
d1["a"] = v1[0]
d1["b"] = v1[1]
d2["c"] = v2[0]
d2["d"] = v2[1]
assert {i: d1[i].get() for i in d1} == {"a": 0, "b": 1}
assert {i: d2[i].get() for i in d2} == {"c": 2, "d": 3}
def test_stl_bind_global():
import pybind11_cross_module_tests as cm
with pytest.raises(RuntimeError) as excinfo:
cm.register_nonlocal_map()
assert (str(excinfo.value) ==
'generic_type: type "NonLocalMap" is already registered!')
with pytest.raises(RuntimeError) as excinfo:
cm.register_nonlocal_vec()
assert (str(excinfo.value) ==
'generic_type: type "NonLocalVec" is already registered!')
with pytest.raises(RuntimeError) as excinfo:
cm.register_nonlocal_map2()
assert (str(excinfo.value) ==
'generic_type: type "NonLocalMap2" is already registered!')
def test_mixed_local_global():
"""Local types take precedence over globally registered types: a module with a `module_local`
type can be registered even if the type is already registered globally. With the module,
casting will go to the local type; outside the module casting goes to the global type."""
import pybind11_cross_module_tests as cm
m.register_mixed_global()
m.register_mixed_local()
a = []
a.append(m.MixedGlobalLocal(1))
a.append(m.MixedLocalGlobal(2))
a.append(m.get_mixed_gl(3))
a.append(m.get_mixed_lg(4))
assert [x.get() for x in a] == [101, 1002, 103, 1004]
cm.register_mixed_global_local()
cm.register_mixed_local_global()
a.append(m.MixedGlobalLocal(5))
a.append(m.MixedLocalGlobal(6))
a.append(cm.MixedGlobalLocal(7))
a.append(cm.MixedLocalGlobal(8))
a.append(m.get_mixed_gl(9))
a.append(m.get_mixed_lg(10))
a.append(cm.get_mixed_gl(11))
a.append(cm.get_mixed_lg(12))
assert [x.get() for x in a] == [
101,
1002,
103,
1004,
105,
1006,
207,
2008,
109,
1010,
211,
2012,
]
def test_internal_locals_differ():
"""Makes sure the internal local type map differs across the two modules"""
import pybind11_cross_module_tests as cm
assert m.local_cpp_types_addr() != cm.local_cpp_types_addr()
@pytest.mark.xfail("env.PYPY and sys.pypy_version_info < (7, 3, 2)")
def test_stl_caster_vs_stl_bind(msg):
"""One module uses a generic vector caster from `<pybind11/stl.h>` while the other
exports `std::vector<int>` via `py:bind_vector` and `py::module_local`"""
import pybind11_cross_module_tests as cm
v1 = cm.VectorInt([1, 2, 3])
assert m.load_vector_via_caster(v1) == 6
assert cm.load_vector_via_binding(v1) == 6
v2 = [1, 2, 3]
assert m.load_vector_via_caster(v2) == 6
with pytest.raises(TypeError) as excinfo:
cm.load_vector_via_binding(v2)
assert (msg(excinfo.value) == """
load_vector_via_binding(): incompatible function arguments. The following argument types are supported:
1. (arg0: pybind11_cross_module_tests.VectorInt) -> int
Invoked with: [1, 2, 3]
""")
def test_cross_module_calls():
import pybind11_cross_module_tests as cm
v1 = m.LocalVec()
v1.append(m.LocalType(1))
v2 = cm.LocalVec()
v2.append(cm.LocalType(2))
# Returning the self pointer should get picked up as returning an existing
# instance (even when that instance is of a foreign, non-local type).
assert m.return_self(v1) is v1
assert cm.return_self(v2) is v2
assert m.return_self(v2) is v2
assert cm.return_self(v1) is v1
assert m.LocalVec is not cm.LocalVec
# Returning a copy, on the other hand, always goes to the local type,
# regardless of where the source type came from.
assert type(m.return_copy(v1)) is m.LocalVec
assert type(m.return_copy(v2)) is m.LocalVec
assert type(cm.return_copy(v1)) is cm.LocalVec
assert type(cm.return_copy(v2)) is cm.LocalVec
# Test the example given in the documentation (which also tests inheritance casting):
mycat = m.Cat("Fluffy")
mydog = cm.Dog("Rover")
assert mycat.get_name() == "Fluffy"
assert mydog.name() == "Rover"
assert m.Cat.__base__.__name__ == "Pet"
assert cm.Dog.__base__.__name__ == "Pet"
assert m.Cat.__base__ is not cm.Dog.__base__
assert m.pet_name(mycat) == "Fluffy"
assert m.pet_name(mydog) == "Rover"
assert cm.pet_name(mycat) == "Fluffy"
assert cm.pet_name(mydog) == "Rover"
assert m.MixGL is not cm.MixGL
a = m.MixGL(1)
b = cm.MixGL(2)
assert m.get_gl_value(a) == 11
assert m.get_gl_value(b) == 12
assert cm.get_gl_value(a) == 101
assert cm.get_gl_value(b) == 102
c, d = m.MixGL2(3), cm.MixGL2(4)
with pytest.raises(TypeError) as excinfo:
m.get_gl_value(c)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.get_gl_value(d)
assert "incompatible function arguments" in str(excinfo.value)

View File

@@ -0,0 +1,459 @@
/*
tests/test_methods_and_attributes.cpp -- constructors, deconstructors, attribute access,
__str__, argument and return value conventions
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "constructor_stats.h"
#include "pybind11_tests.h"
#if !defined(PYBIND11_OVERLOAD_CAST)
template <typename... Args>
using overload_cast_ = pybind11::detail::overload_cast_impl<Args...>;
#endif
class ExampleMandA {
public:
ExampleMandA() { print_default_created(this); }
explicit ExampleMandA(int value) : value(value) { print_created(this, value); }
ExampleMandA(const ExampleMandA &e) : value(e.value) { print_copy_created(this); }
explicit ExampleMandA(std::string &&) {}
ExampleMandA(ExampleMandA &&e) noexcept : value(e.value) { print_move_created(this); }
~ExampleMandA() { print_destroyed(this); }
std::string toString() const { return "ExampleMandA[value=" + std::to_string(value) + "]"; }
void operator=(const ExampleMandA &e) {
print_copy_assigned(this);
value = e.value;
}
void operator=(ExampleMandA &&e) noexcept {
print_move_assigned(this);
value = e.value;
}
// NOLINTNEXTLINE(performance-unnecessary-value-param)
void add1(ExampleMandA other) { value += other.value; } // passing by value
void add2(ExampleMandA &other) { value += other.value; } // passing by reference
void add3(const ExampleMandA &other) { value += other.value; } // passing by const reference
void add4(ExampleMandA *other) { value += other->value; } // passing by pointer
void add5(const ExampleMandA *other) { value += other->value; } // passing by const pointer
void add6(int other) { value += other; } // passing by value
void add7(int &other) { value += other; } // passing by reference
void add8(const int &other) { value += other; } // passing by const reference
// NOLINTNEXTLINE(readability-non-const-parameter) Deliberately non-const for testing
void add9(int *other) { value += *other; } // passing by pointer
void add10(const int *other) { value += *other; } // passing by const pointer
void consume_str(std::string &&) {}
ExampleMandA self1() { return *this; } // return by value
ExampleMandA &self2() { return *this; } // return by reference
const ExampleMandA &self3() const { return *this; } // return by const reference
ExampleMandA *self4() { return this; } // return by pointer
const ExampleMandA *self5() const { return this; } // return by const pointer
int internal1() const { return value; } // return by value
int &internal2() { return value; } // return by reference
const int &internal3() const { return value; } // return by const reference
int *internal4() { return &value; } // return by pointer
const int *internal5() { return &value; } // return by const pointer
py::str overloaded() { return "()"; }
py::str overloaded(int) { return "(int)"; }
py::str overloaded(int, float) { return "(int, float)"; }
py::str overloaded(float, int) { return "(float, int)"; }
py::str overloaded(int, int) { return "(int, int)"; }
py::str overloaded(float, float) { return "(float, float)"; }
py::str overloaded(int) const { return "(int) const"; }
py::str overloaded(int, float) const { return "(int, float) const"; }
py::str overloaded(float, int) const { return "(float, int) const"; }
py::str overloaded(int, int) const { return "(int, int) const"; }
py::str overloaded(float, float) const { return "(float, float) const"; }
static py::str overloaded(float) { return "static float"; }
int value = 0;
};
struct TestProperties {
int value = 1;
static int static_value;
int get() const { return value; }
void set(int v) { value = v; }
static int static_get() { return static_value; }
static void static_set(int v) { static_value = v; }
};
int TestProperties::static_value = 1;
struct TestPropertiesOverride : TestProperties {
int value = 99;
static int static_value;
};
int TestPropertiesOverride::static_value = 99;
struct TestPropRVP {
UserType v1{1};
UserType v2{1};
static UserType sv1;
static UserType sv2;
const UserType &get1() const { return v1; }
const UserType &get2() const { return v2; }
UserType get_rvalue() const { return v2; }
void set1(int v) { v1.set(v); }
void set2(int v) { v2.set(v); }
};
UserType TestPropRVP::sv1(1);
UserType TestPropRVP::sv2(1);
// Test None-allowed py::arg argument policy
class NoneTester {
public:
int answer = 42;
};
int none1(const NoneTester &obj) { return obj.answer; }
int none2(NoneTester *obj) { return obj ? obj->answer : -1; }
int none3(std::shared_ptr<NoneTester> &obj) { return obj ? obj->answer : -1; }
int none4(std::shared_ptr<NoneTester> *obj) { return obj && *obj ? (*obj)->answer : -1; }
int none5(const std::shared_ptr<NoneTester> &obj) { return obj ? obj->answer : -1; }
// Issue #2778: implicit casting from None to object (not pointer)
class NoneCastTester {
public:
int answer = -1;
NoneCastTester() = default;
explicit NoneCastTester(int v) : answer(v) {}
};
struct StrIssue {
int val = -1;
StrIssue() = default;
explicit StrIssue(int i) : val{i} {}
};
// Issues #854, #910: incompatible function args when member function/pointer is in unregistered
// base class
class UnregisteredBase {
public:
void do_nothing() const {}
void increase_value() {
rw_value++;
ro_value += 0.25;
}
void set_int(int v) { rw_value = v; }
int get_int() const { return rw_value; }
double get_double() const { return ro_value; }
int rw_value = 42;
double ro_value = 1.25;
};
class RegisteredDerived : public UnregisteredBase {
public:
using UnregisteredBase::UnregisteredBase;
double sum() const { return rw_value + ro_value; }
};
// Test explicit lvalue ref-qualification
struct RefQualified {
int value = 0;
void refQualified(int other) & { value += other; }
int constRefQualified(int other) const & { return value + other; }
};
// Test rvalue ref param
struct RValueRefParam {
std::size_t func1(std::string &&s) { return s.size(); }
std::size_t func2(std::string &&s) const { return s.size(); }
std::size_t func3(std::string &&s) & { return s.size(); }
std::size_t func4(std::string &&s) const & { return s.size(); }
};
TEST_SUBMODULE(methods_and_attributes, m) {
// test_methods_and_attributes
py::class_<ExampleMandA> emna(m, "ExampleMandA");
emna.def(py::init<>())
.def(py::init<int>())
.def(py::init<std::string &&>())
.def(py::init<const ExampleMandA &>())
.def("add1", &ExampleMandA::add1)
.def("add2", &ExampleMandA::add2)
.def("add3", &ExampleMandA::add3)
.def("add4", &ExampleMandA::add4)
.def("add5", &ExampleMandA::add5)
.def("add6", &ExampleMandA::add6)
.def("add7", &ExampleMandA::add7)
.def("add8", &ExampleMandA::add8)
.def("add9", &ExampleMandA::add9)
.def("add10", &ExampleMandA::add10)
.def("consume_str", &ExampleMandA::consume_str)
.def("self1", &ExampleMandA::self1)
.def("self2", &ExampleMandA::self2)
.def("self3", &ExampleMandA::self3)
.def("self4", &ExampleMandA::self4)
.def("self5", &ExampleMandA::self5)
.def("internal1", &ExampleMandA::internal1)
.def("internal2", &ExampleMandA::internal2)
.def("internal3", &ExampleMandA::internal3)
.def("internal4", &ExampleMandA::internal4)
.def("internal5", &ExampleMandA::internal5)
#if defined(PYBIND11_OVERLOAD_CAST)
.def("overloaded", py::overload_cast<>(&ExampleMandA::overloaded))
.def("overloaded", py::overload_cast<int>(&ExampleMandA::overloaded))
.def("overloaded", py::overload_cast<int, float>(&ExampleMandA::overloaded))
.def("overloaded", py::overload_cast<float, int>(&ExampleMandA::overloaded))
.def("overloaded", py::overload_cast<int, int>(&ExampleMandA::overloaded))
.def("overloaded", py::overload_cast<float, float>(&ExampleMandA::overloaded))
.def("overloaded_float", py::overload_cast<float, float>(&ExampleMandA::overloaded))
.def("overloaded_const", py::overload_cast<int>(&ExampleMandA::overloaded, py::const_))
.def("overloaded_const",
py::overload_cast<int, float>(&ExampleMandA::overloaded, py::const_))
.def("overloaded_const",
py::overload_cast<float, int>(&ExampleMandA::overloaded, py::const_))
.def("overloaded_const",
py::overload_cast<int, int>(&ExampleMandA::overloaded, py::const_))
.def("overloaded_const",
py::overload_cast<float, float>(&ExampleMandA::overloaded, py::const_))
#else
// Use both the traditional static_cast method and the C++11 compatible overload_cast_
.def("overloaded", overload_cast_<>()(&ExampleMandA::overloaded))
.def("overloaded", overload_cast_<int>()(&ExampleMandA::overloaded))
.def("overloaded", overload_cast_<int, float>()(&ExampleMandA::overloaded))
.def("overloaded", static_cast<py::str (ExampleMandA::*)(float, int)>(&ExampleMandA::overloaded))
.def("overloaded", static_cast<py::str (ExampleMandA::*)(int, int)>(&ExampleMandA::overloaded))
.def("overloaded", static_cast<py::str (ExampleMandA::*)(float, float)>(&ExampleMandA::overloaded))
.def("overloaded_float", overload_cast_<float, float>()(&ExampleMandA::overloaded))
.def("overloaded_const", overload_cast_<int >()(&ExampleMandA::overloaded, py::const_))
.def("overloaded_const", overload_cast_<int, float>()(&ExampleMandA::overloaded, py::const_))
.def("overloaded_const", static_cast<py::str (ExampleMandA::*)(float, int) const>(&ExampleMandA::overloaded))
.def("overloaded_const", static_cast<py::str (ExampleMandA::*)(int, int) const>(&ExampleMandA::overloaded))
.def("overloaded_const", static_cast<py::str (ExampleMandA::*)(float, float) const>(&ExampleMandA::overloaded))
#endif
// test_no_mixed_overloads
// Raise error if trying to mix static/non-static overloads on the same name:
.def_static("add_mixed_overloads1",
[]() {
auto emna = py::reinterpret_borrow<py::class_<ExampleMandA>>(
py::module_::import("pybind11_tests.methods_and_attributes")
.attr("ExampleMandA"));
emna.def("overload_mixed1",
static_cast<py::str (ExampleMandA::*)(int, int)>(
&ExampleMandA::overloaded))
.def_static(
"overload_mixed1",
static_cast<py::str (*)(float)>(&ExampleMandA::overloaded));
})
.def_static("add_mixed_overloads2",
[]() {
auto emna = py::reinterpret_borrow<py::class_<ExampleMandA>>(
py::module_::import("pybind11_tests.methods_and_attributes")
.attr("ExampleMandA"));
emna.def_static("overload_mixed2",
static_cast<py::str (*)(float)>(&ExampleMandA::overloaded))
.def("overload_mixed2",
static_cast<py::str (ExampleMandA::*)(int, int)>(
&ExampleMandA::overloaded));
})
.def("__str__", &ExampleMandA::toString)
.def_readwrite("value", &ExampleMandA::value);
// test_copy_method
// Issue #443: can't call copied methods in Python 3
emna.attr("add2b") = emna.attr("add2");
// test_properties, test_static_properties, test_static_cls
py::class_<TestProperties>(m, "TestProperties")
.def(py::init<>())
.def_readonly("def_readonly", &TestProperties::value)
.def_readwrite("def_readwrite", &TestProperties::value)
.def_property("def_writeonly", nullptr, [](TestProperties &s, int v) { s.value = v; })
.def_property("def_property_writeonly", nullptr, &TestProperties::set)
.def_property_readonly("def_property_readonly", &TestProperties::get)
.def_property("def_property", &TestProperties::get, &TestProperties::set)
.def_property("def_property_impossible", nullptr, nullptr)
.def_readonly_static("def_readonly_static", &TestProperties::static_value)
.def_readwrite_static("def_readwrite_static", &TestProperties::static_value)
.def_property_static("def_writeonly_static",
nullptr,
[](const py::object &, int v) { TestProperties::static_value = v; })
.def_property_readonly_static(
"def_property_readonly_static",
[](const py::object &) { return TestProperties::static_get(); })
.def_property_static(
"def_property_writeonly_static",
nullptr,
[](const py::object &, int v) { return TestProperties::static_set(v); })
.def_property_static(
"def_property_static",
[](const py::object &) { return TestProperties::static_get(); },
[](const py::object &, int v) { TestProperties::static_set(v); })
.def_property_static(
"static_cls",
[](py::object cls) { return cls; },
[](const py::object &cls, const py::function &f) { f(cls); });
py::class_<TestPropertiesOverride, TestProperties>(m, "TestPropertiesOverride")
.def(py::init<>())
.def_readonly("def_readonly", &TestPropertiesOverride::value)
.def_readonly_static("def_readonly_static", &TestPropertiesOverride::static_value);
auto static_get1 = [](const py::object &) -> const UserType & { return TestPropRVP::sv1; };
auto static_get2 = [](const py::object &) -> const UserType & { return TestPropRVP::sv2; };
auto static_set1 = [](const py::object &, int v) { TestPropRVP::sv1.set(v); };
auto static_set2 = [](const py::object &, int v) { TestPropRVP::sv2.set(v); };
auto rvp_copy = py::return_value_policy::copy;
// test_property_return_value_policies
py::class_<TestPropRVP>(m, "TestPropRVP")
.def(py::init<>())
.def_property_readonly("ro_ref", &TestPropRVP::get1)
.def_property_readonly("ro_copy", &TestPropRVP::get2, rvp_copy)
.def_property_readonly("ro_func", py::cpp_function(&TestPropRVP::get2, rvp_copy))
.def_property("rw_ref", &TestPropRVP::get1, &TestPropRVP::set1)
.def_property("rw_copy", &TestPropRVP::get2, &TestPropRVP::set2, rvp_copy)
.def_property(
"rw_func", py::cpp_function(&TestPropRVP::get2, rvp_copy), &TestPropRVP::set2)
.def_property_readonly_static("static_ro_ref", static_get1)
.def_property_readonly_static("static_ro_copy", static_get2, rvp_copy)
.def_property_readonly_static("static_ro_func", py::cpp_function(static_get2, rvp_copy))
.def_property_static("static_rw_ref", static_get1, static_set1)
.def_property_static("static_rw_copy", static_get2, static_set2, rvp_copy)
.def_property_static(
"static_rw_func", py::cpp_function(static_get2, rvp_copy), static_set2)
// test_property_rvalue_policy
.def_property_readonly("rvalue", &TestPropRVP::get_rvalue)
.def_property_readonly_static("static_rvalue",
[](const py::object &) { return UserType(1); });
// test_metaclass_override
struct MetaclassOverride {};
py::class_<MetaclassOverride>(m, "MetaclassOverride", py::metaclass((PyObject *) &PyType_Type))
.def_property_readonly_static("readonly", [](const py::object &) { return 1; });
// test_overload_ordering
m.def("overload_order", [](const std::string &) { return 1; });
m.def("overload_order", [](const std::string &) { return 2; });
m.def("overload_order", [](int) { return 3; });
m.def(
"overload_order", [](int) { return 4; }, py::prepend{});
#if !defined(PYPY_VERSION)
// test_dynamic_attributes
class DynamicClass {
public:
DynamicClass() { print_default_created(this); }
DynamicClass(const DynamicClass &) = delete;
~DynamicClass() { print_destroyed(this); }
};
py::class_<DynamicClass>(m, "DynamicClass", py::dynamic_attr()).def(py::init());
class CppDerivedDynamicClass : public DynamicClass {};
py::class_<CppDerivedDynamicClass, DynamicClass>(m, "CppDerivedDynamicClass").def(py::init());
#endif
// test_bad_arg_default
// Issue/PR #648: bad arg default debugging output
#if defined(PYBIND11_DETAILED_ERROR_MESSAGES)
m.attr("detailed_error_messages_enabled") = true;
#else
m.attr("detailed_error_messages_enabled") = false;
#endif
m.def("bad_arg_def_named", [] {
auto m = py::module_::import("pybind11_tests");
m.def(
"should_fail",
[](int, UnregisteredType) {},
py::arg(),
py::arg("a") = UnregisteredType());
});
m.def("bad_arg_def_unnamed", [] {
auto m = py::module_::import("pybind11_tests");
m.def(
"should_fail",
[](int, UnregisteredType) {},
py::arg(),
py::arg() = UnregisteredType());
});
// [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works.
// test_accepts_none
py::class_<NoneTester, std::shared_ptr<NoneTester>>(m, "NoneTester").def(py::init<>());
m.def("no_none1", &none1, py::arg{}.none(false));
m.def("no_none2", &none2, py::arg{}.none(false));
m.def("no_none3", &none3, py::arg{}.none(false));
m.def("no_none4", &none4, py::arg{}.none(false));
m.def("no_none5", &none5, py::arg{}.none(false));
m.def("ok_none1", &none1);
m.def("ok_none2", &none2, py::arg{}.none(true));
m.def("ok_none3", &none3);
m.def("ok_none4", &none4, py::arg{}.none(true));
m.def("ok_none5", &none5);
m.def("no_none_kwarg", &none2, "a"_a.none(false));
m.def("no_none_kwarg_kw_only", &none2, py::kw_only(), "a"_a.none(false));
// test_casts_none
// Issue #2778: implicit casting from None to object (not pointer)
py::class_<NoneCastTester>(m, "NoneCastTester")
.def(py::init<>())
.def(py::init<int>())
.def(py::init([](py::none const &) { return NoneCastTester{}; }));
py::implicitly_convertible<py::none, NoneCastTester>();
m.def("ok_obj_or_none", [](NoneCastTester const &foo) { return foo.answer; });
// test_str_issue
// Issue #283: __str__ called on uninitialized instance when constructor arguments invalid
py::class_<StrIssue>(m, "StrIssue")
.def(py::init<int>())
.def(py::init<>())
.def("__str__",
[](const StrIssue &si) { return "StrIssue[" + std::to_string(si.val) + "]"; });
// test_unregistered_base_implementations
//
// Issues #854/910: incompatible function args when member function/pointer is in unregistered
// base class The methods and member pointers below actually resolve to members/pointers in
// UnregisteredBase; before this test/fix they would be registered via lambda with a first
// argument of an unregistered type, and thus uncallable.
py::class_<RegisteredDerived>(m, "RegisteredDerived")
.def(py::init<>())
.def("do_nothing", &RegisteredDerived::do_nothing)
.def("increase_value", &RegisteredDerived::increase_value)
.def_readwrite("rw_value", &RegisteredDerived::rw_value)
.def_readonly("ro_value", &RegisteredDerived::ro_value)
// Uncommenting the next line should trigger a static_assert:
// .def_readwrite("fails", &UserType::value)
// Uncommenting the next line should trigger a static_assert:
// .def_readonly("fails", &UserType::value)
.def_property("rw_value_prop", &RegisteredDerived::get_int, &RegisteredDerived::set_int)
.def_property_readonly("ro_value_prop", &RegisteredDerived::get_double)
// This one is in the registered class:
.def("sum", &RegisteredDerived::sum);
using Adapted
= decltype(py::method_adaptor<RegisteredDerived>(&RegisteredDerived::do_nothing));
static_assert(std::is_same<Adapted, void (RegisteredDerived::*)() const>::value, "");
// test_methods_and_attributes
py::class_<RefQualified>(m, "RefQualified")
.def(py::init<>())
.def_readonly("value", &RefQualified::value)
.def("refQualified", &RefQualified::refQualified)
.def("constRefQualified", &RefQualified::constRefQualified);
py::class_<RValueRefParam>(m, "RValueRefParam")
.def(py::init<>())
.def("func1", &RValueRefParam::func1)
.def("func2", &RValueRefParam::func2)
.def("func3", &RValueRefParam::func3)
.def("func4", &RValueRefParam::func4);
}

View File

@@ -0,0 +1,504 @@
import pytest
import env # noqa: F401
from pybind11_tests import ConstructorStats
from pybind11_tests import methods_and_attributes as m
def test_methods_and_attributes():
instance1 = m.ExampleMandA()
instance2 = m.ExampleMandA(32)
instance1.add1(instance2)
instance1.add2(instance2)
instance1.add3(instance2)
instance1.add4(instance2)
instance1.add5(instance2)
instance1.add6(32)
instance1.add7(32)
instance1.add8(32)
instance1.add9(32)
instance1.add10(32)
assert str(instance1) == "ExampleMandA[value=320]"
assert str(instance2) == "ExampleMandA[value=32]"
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]"
assert str(instance1.self5()) == "ExampleMandA[value=320]"
assert instance1.internal1() == 320
assert instance1.internal2() == 320
assert instance1.internal3() == 320
assert instance1.internal4() == 320
assert instance1.internal5() == 320
assert instance1.overloaded() == "()"
assert instance1.overloaded(0) == "(int)"
assert instance1.overloaded(1, 1.0) == "(int, float)"
assert instance1.overloaded(2.0, 2) == "(float, int)"
assert instance1.overloaded(3, 3) == "(int, int)"
assert instance1.overloaded(4.0, 4.0) == "(float, float)"
assert instance1.overloaded_const(-3) == "(int) const"
assert instance1.overloaded_const(5, 5.0) == "(int, float) const"
assert instance1.overloaded_const(6.0, 6) == "(float, int) const"
assert instance1.overloaded_const(7, 7) == "(int, int) const"
assert instance1.overloaded_const(8.0, 8.0) == "(float, float) const"
assert instance1.overloaded_float(1, 1) == "(float, float)"
assert instance1.overloaded_float(1, 1.0) == "(float, float)"
assert instance1.overloaded_float(1.0, 1) == "(float, float)"
assert instance1.overloaded_float(1.0, 1.0) == "(float, float)"
assert instance1.value == 320
instance1.value = 100
assert str(instance1) == "ExampleMandA[value=100]"
cstats = ConstructorStats.get(m.ExampleMandA)
assert cstats.alive() == 2
del instance1, instance2
assert cstats.alive() == 0
assert cstats.values() == ["32"]
assert cstats.default_constructions == 1
assert cstats.copy_constructions == 2
assert cstats.move_constructions >= 2
assert cstats.copy_assignments == 0
assert cstats.move_assignments == 0
def test_copy_method():
"""Issue #443: calling copied methods fails in Python 3"""
m.ExampleMandA.add2c = m.ExampleMandA.add2
m.ExampleMandA.add2d = m.ExampleMandA.add2b
a = m.ExampleMandA(123)
assert a.value == 123
a.add2(m.ExampleMandA(-100))
assert a.value == 23
a.add2b(m.ExampleMandA(20))
assert a.value == 43
a.add2c(m.ExampleMandA(6))
assert a.value == 49
a.add2d(m.ExampleMandA(-7))
assert a.value == 42
def test_properties():
instance = m.TestProperties()
assert instance.def_readonly == 1
with pytest.raises(AttributeError):
instance.def_readonly = 2
instance.def_readwrite = 2
assert instance.def_readwrite == 2
assert instance.def_property_readonly == 2
with pytest.raises(AttributeError):
instance.def_property_readonly = 3
instance.def_property = 3
assert instance.def_property == 3
with pytest.raises(AttributeError) as excinfo:
dummy = instance.def_property_writeonly # unused var
assert "unreadable attribute" in str(excinfo.value)
instance.def_property_writeonly = 4
assert instance.def_property_readonly == 4
with pytest.raises(AttributeError) as excinfo:
dummy = instance.def_property_impossible # noqa: F841 unused var
assert "unreadable attribute" in str(excinfo.value)
with pytest.raises(AttributeError) as excinfo:
instance.def_property_impossible = 5
assert "can't set attribute" in str(excinfo.value)
def test_static_properties():
assert m.TestProperties.def_readonly_static == 1
with pytest.raises(AttributeError) as excinfo:
m.TestProperties.def_readonly_static = 2
assert "can't set attribute" in str(excinfo.value)
m.TestProperties.def_readwrite_static = 2
assert m.TestProperties.def_readwrite_static == 2
with pytest.raises(AttributeError) as excinfo:
dummy = m.TestProperties.def_writeonly_static # unused var
assert "unreadable attribute" in str(excinfo.value)
m.TestProperties.def_writeonly_static = 3
assert m.TestProperties.def_readonly_static == 3
assert m.TestProperties.def_property_readonly_static == 3
with pytest.raises(AttributeError) as excinfo:
m.TestProperties.def_property_readonly_static = 99
assert "can't set attribute" in str(excinfo.value)
m.TestProperties.def_property_static = 4
assert m.TestProperties.def_property_static == 4
with pytest.raises(AttributeError) as excinfo:
dummy = m.TestProperties.def_property_writeonly_static
assert "unreadable attribute" in str(excinfo.value)
m.TestProperties.def_property_writeonly_static = 5
assert m.TestProperties.def_property_static == 5
# Static property read and write via instance
instance = m.TestProperties()
m.TestProperties.def_readwrite_static = 0
assert m.TestProperties.def_readwrite_static == 0
assert instance.def_readwrite_static == 0
instance.def_readwrite_static = 2
assert m.TestProperties.def_readwrite_static == 2
assert instance.def_readwrite_static == 2
with pytest.raises(AttributeError) as excinfo:
dummy = instance.def_property_writeonly_static # noqa: F841 unused var
assert "unreadable attribute" in str(excinfo.value)
instance.def_property_writeonly_static = 4
assert instance.def_property_static == 4
# It should be possible to override properties in derived classes
assert m.TestPropertiesOverride().def_readonly == 99
assert m.TestPropertiesOverride.def_readonly_static == 99
# Only static attributes can be deleted
del m.TestPropertiesOverride.def_readonly_static
assert (hasattr(m.TestPropertiesOverride, "def_readonly_static") and
m.TestPropertiesOverride.def_readonly_static is
m.TestProperties.def_readonly_static)
assert "def_readonly_static" not in m.TestPropertiesOverride.__dict__
properties_override = m.TestPropertiesOverride()
with pytest.raises(AttributeError) as excinfo:
del properties_override.def_readonly
assert "can't delete attribute" in str(excinfo.value)
def test_static_cls():
"""Static property getter and setters expect the type object as the their only argument"""
instance = m.TestProperties()
assert m.TestProperties.static_cls is m.TestProperties
assert instance.static_cls is m.TestProperties
def check_self(self):
assert self is m.TestProperties
m.TestProperties.static_cls = check_self
instance.static_cls = check_self
def test_metaclass_override():
"""Overriding pybind11's default metaclass changes the behavior of `static_property`"""
assert type(m.ExampleMandA).__name__ == "pybind11_type"
assert type(m.MetaclassOverride).__name__ == "type"
assert m.MetaclassOverride.readonly == 1
assert (type(m.MetaclassOverride.__dict__["readonly"]).__name__ ==
"pybind11_static_property")
# Regular `type` replaces the property instead of calling `__set__()`
m.MetaclassOverride.readonly = 2
assert m.MetaclassOverride.readonly == 2
assert isinstance(m.MetaclassOverride.__dict__["readonly"], int)
def test_no_mixed_overloads():
from pybind11_tests import detailed_error_messages_enabled
with pytest.raises(RuntimeError) as excinfo:
m.ExampleMandA.add_mixed_overloads1()
assert str(
excinfo.value
) == "overloading a method with both static and instance methods is not supported; " + (
"#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
if not detailed_error_messages_enabled else
"error while attempting to bind static method ExampleMandA.overload_mixed1"
"(arg0: float) -> str")
with pytest.raises(RuntimeError) as excinfo:
m.ExampleMandA.add_mixed_overloads2()
assert str(
excinfo.value
) == "overloading a method with both static and instance methods is not supported; " + (
"#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more details"
if not detailed_error_messages_enabled else
"error while attempting to bind instance method ExampleMandA.overload_mixed2"
"(self: pybind11_tests.methods_and_attributes.ExampleMandA, arg0: int, arg1: int)"
" -> str")
@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"])
def test_property_return_value_policies(access):
if not access.startswith("static"):
obj = m.TestPropRVP()
else:
obj = m.TestPropRVP
ref = getattr(obj, access + "_ref")
assert ref.value == 1
ref.value = 2
assert getattr(obj, access + "_ref").value == 2
ref.value = 1 # restore original value for static properties
copy = getattr(obj, access + "_copy")
assert copy.value == 1
copy.value = 2
assert getattr(obj, access + "_copy").value == 1
copy = getattr(obj, access + "_func")
assert copy.value == 1
copy.value = 2
assert getattr(obj, access + "_func").value == 1
def test_property_rvalue_policy():
"""When returning an rvalue, the return value policy is automatically changed from
`reference(_internal)` to `move`. The following would not work otherwise."""
instance = m.TestPropRVP()
o = instance.rvalue
assert o.value == 1
os = m.TestPropRVP.static_rvalue
assert os.value == 1
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
@pytest.mark.xfail("env.PYPY")
def test_dynamic_attributes():
instance = m.DynamicClass()
assert not hasattr(instance, "foo")
assert "foo" not in dir(instance)
# Dynamically add attribute
instance.foo = 42
assert hasattr(instance, "foo")
assert instance.foo == 42
assert "foo" in dir(instance)
# __dict__ should be accessible and replaceable
assert "foo" in instance.__dict__
instance.__dict__ = {"bar": True}
assert not hasattr(instance, "foo")
assert hasattr(instance, "bar")
with pytest.raises(TypeError) as excinfo:
instance.__dict__ = []
assert str(
excinfo.value) == "__dict__ must be set to a dictionary, not a 'list'"
cstats = ConstructorStats.get(m.DynamicClass)
assert cstats.alive() == 1
del instance
assert cstats.alive() == 0
# Derived classes should work as well
class PythonDerivedDynamicClass(m.DynamicClass):
pass
for cls in m.CppDerivedDynamicClass, PythonDerivedDynamicClass:
derived = cls()
derived.foobar = 100
assert derived.foobar == 100
assert cstats.alive() == 1
del derived
assert cstats.alive() == 0
# https://foss.heptapod.net/pypy/pypy/-/issues/2447
@pytest.mark.xfail("env.PYPY")
def test_cyclic_gc():
# One object references itself
instance = m.DynamicClass()
instance.circular_reference = instance
cstats = ConstructorStats.get(m.DynamicClass)
assert cstats.alive() == 1
del instance
assert cstats.alive() == 0
# Two object reference each other
i1 = m.DynamicClass()
i2 = m.DynamicClass()
i1.cycle = i2
i2.cycle = i1
assert cstats.alive() == 2
del i1, i2
assert cstats.alive() == 0
def test_bad_arg_default(msg):
from pybind11_tests import detailed_error_messages_enabled
with pytest.raises(RuntimeError) as excinfo:
m.bad_arg_def_named()
assert msg(excinfo.value) == (
"arg(): could not convert default argument 'a: UnregisteredType' in function "
"'should_fail' into a Python object (type not registered yet?)"
if detailed_error_messages_enabled else
"arg(): could not convert default argument into a Python object (type not registered "
"yet?). #define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more information."
)
with pytest.raises(RuntimeError) as excinfo:
m.bad_arg_def_unnamed()
assert msg(excinfo.value) == (
"arg(): could not convert default argument 'UnregisteredType' in function "
"'should_fail' into a Python object (type not registered yet?)"
if detailed_error_messages_enabled else
"arg(): could not convert default argument into a Python object (type not registered "
"yet?). #define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for more information."
)
def test_accepts_none(msg):
a = m.NoneTester()
assert m.no_none1(a) == 42
assert m.no_none2(a) == 42
assert m.no_none3(a) == 42
assert m.no_none4(a) == 42
assert m.no_none5(a) == 42
assert m.ok_none1(a) == 42
assert m.ok_none2(a) == 42
assert m.ok_none3(a) == 42
assert m.ok_none4(a) == 42
assert m.ok_none5(a) == 42
with pytest.raises(TypeError) as excinfo:
m.no_none1(None)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.no_none2(None)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.no_none3(None)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.no_none4(None)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.no_none5(None)
assert "incompatible function arguments" in str(excinfo.value)
# The first one still raises because you can't pass None as a lvalue reference arg:
with pytest.raises(TypeError) as excinfo:
assert m.ok_none1(None) == -1
assert (msg(excinfo.value) == """
ok_none1(): incompatible function arguments. The following argument types are supported:
1. (arg0: m.methods_and_attributes.NoneTester) -> int
Invoked with: None
""")
# The rest take the argument as pointer or holder, and accept None:
assert m.ok_none2(None) == -1
assert m.ok_none3(None) == -1
assert m.ok_none4(None) == -1
assert m.ok_none5(None) == -1
with pytest.raises(TypeError) as excinfo:
m.no_none_kwarg(None)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.no_none_kwarg(a=None)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.no_none_kwarg_kw_only(None)
assert "incompatible function arguments" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.no_none_kwarg_kw_only(a=None)
assert "incompatible function arguments" in str(excinfo.value)
def test_casts_none():
"""#2778: implicit casting from None to object (not pointer)"""
a = m.NoneCastTester()
assert m.ok_obj_or_none(a) == -1
a = m.NoneCastTester(4)
assert m.ok_obj_or_none(a) == 4
a = m.NoneCastTester(None)
assert m.ok_obj_or_none(a) == -1
assert m.ok_obj_or_none(None) == -1
def test_str_issue(msg):
"""#283: __str__ called on uninitialized instance when constructor arguments invalid"""
assert str(m.StrIssue(3)) == "StrIssue[3]"
with pytest.raises(TypeError) as excinfo:
str(m.StrIssue("no", "such", "constructor"))
assert (msg(excinfo.value) == """
__init__(): incompatible constructor arguments. The following argument types are supported:
1. m.methods_and_attributes.StrIssue(arg0: int)
2. m.methods_and_attributes.StrIssue()
Invoked with: 'no', 'such', 'constructor'
""")
def test_unregistered_base_implementations():
a = m.RegisteredDerived()
a.do_nothing()
assert a.rw_value == 42
assert a.ro_value == 1.25
a.rw_value += 5
assert a.sum() == 48.25
a.increase_value()
assert a.rw_value == 48
assert a.ro_value == 1.5
assert a.sum() == 49.5
assert a.rw_value_prop == 48
a.rw_value_prop += 1
assert a.rw_value_prop == 49
a.increase_value()
assert a.ro_value_prop == 1.75
def test_ref_qualified():
"""Tests that explicit lvalue ref-qualified methods can be called just like their
non ref-qualified counterparts."""
r = m.RefQualified()
assert r.value == 0
r.refQualified(17)
assert r.value == 17
assert r.constRefQualified(23) == 40
def test_overload_ordering():
"Check to see if the normal overload order (first defined) and prepend overload order works"
assert m.overload_order("string") == 1
assert m.overload_order(0) == 4
assert "1. overload_order(arg0: int) -> int" in m.overload_order.__doc__
assert "2. overload_order(arg0: str) -> int" in m.overload_order.__doc__
assert "3. overload_order(arg0: str) -> int" in m.overload_order.__doc__
assert "4. overload_order(arg0: int) -> int" in m.overload_order.__doc__
with pytest.raises(TypeError) as err:
m.overload_order(1.1)
assert "1. (arg0: int) -> int" in str(err.value)
assert "2. (arg0: str) -> int" in str(err.value)
assert "3. (arg0: str) -> int" in str(err.value)
assert "4. (arg0: int) -> int" in str(err.value)
def test_rvalue_ref_param():
r = m.RValueRefParam()
assert r.func1("123") == 3
assert r.func2("1234") == 4
assert r.func3("12345") == 5
assert r.func4("123456") == 6

View File

@@ -0,0 +1,123 @@
/*
tests/test_modules.cpp -- nested modules, importing modules, and
internal references
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "constructor_stats.h"
#include "pybind11_tests.h"
TEST_SUBMODULE(modules, m) {
// test_nested_modules
// This is intentionally "py::module" to verify it still can be used in place of "py::module_"
py::module m_sub = m.def_submodule("subsubmodule");
m_sub.def("submodule_func", []() { return "submodule_func()"; });
// test_reference_internal
class A {
public:
explicit A(int v) : v(v) { print_created(this, v); }
~A() { print_destroyed(this); }
A(const A &) { print_copy_created(this); }
A &operator=(const A &copy) {
print_copy_assigned(this);
v = copy.v;
return *this;
}
std::string toString() const { return "A[" + std::to_string(v) + "]"; }
private:
int v;
};
py::class_<A>(m_sub, "A").def(py::init<int>()).def("__repr__", &A::toString);
class B {
public:
B() { print_default_created(this); }
~B() { print_destroyed(this); }
B(const B &) { print_copy_created(this); }
B &operator=(const B &copy) {
print_copy_assigned(this);
a1 = copy.a1;
a2 = copy.a2;
return *this;
}
A &get_a1() { return a1; }
A &get_a2() { return a2; }
A a1{1};
A a2{2};
};
py::class_<B>(m_sub, "B")
.def(py::init<>())
.def("get_a1",
&B::get_a1,
"Return the internal A 1",
py::return_value_policy::reference_internal)
.def("get_a2",
&B::get_a2,
"Return the internal A 2",
py::return_value_policy::reference_internal)
.def_readwrite("a1", &B::a1) // def_readonly uses an internal
// reference return policy by default
.def_readwrite("a2", &B::a2);
// This is intentionally "py::module" to verify it still can be used in place of "py::module_"
m.attr("OD") = py::module::import("collections").attr("OrderedDict");
// test_duplicate_registration
// Registering two things with the same name
m.def("duplicate_registration", []() {
class Dupe1 {};
class Dupe2 {};
class Dupe3 {};
class DupeException {};
// Go ahead and leak, until we have a non-leaking py::module_ constructor
auto dm
= py::module_::create_extension_module("dummy", nullptr, new py::module_::module_def);
auto failures = py::list();
py::class_<Dupe1>(dm, "Dupe1");
py::class_<Dupe2>(dm, "Dupe2");
dm.def("dupe1_factory", []() { return Dupe1(); });
py::exception<DupeException>(dm, "DupeException");
try {
py::class_<Dupe1>(dm, "Dupe1");
failures.append("Dupe1 class");
} catch (std::runtime_error &) {
}
try {
dm.def("Dupe1", []() { return Dupe1(); });
failures.append("Dupe1 function");
} catch (std::runtime_error &) {
}
try {
py::class_<Dupe3>(dm, "dupe1_factory");
failures.append("dupe1_factory");
} catch (std::runtime_error &) {
}
try {
py::exception<Dupe3>(dm, "Dupe2");
failures.append("Dupe2");
} catch (std::runtime_error &) {
}
try {
dm.def("DupeException", []() { return 30; });
failures.append("DupeException1");
} catch (std::runtime_error &) {
}
try {
py::class_<DupeException>(dm, "DupeException");
failures.append("DupeException2");
} catch (std::runtime_error &) {
}
return failures;
});
}

View File

@@ -0,0 +1,89 @@
from pybind11_tests import ConstructorStats
from pybind11_tests import modules as m
from pybind11_tests.modules import subsubmodule as ms
def test_nested_modules():
import pybind11_tests
assert pybind11_tests.__name__ == "pybind11_tests"
assert pybind11_tests.modules.__name__ == "pybind11_tests.modules"
assert (pybind11_tests.modules.subsubmodule.__name__ ==
"pybind11_tests.modules.subsubmodule")
assert m.__name__ == "pybind11_tests.modules"
assert ms.__name__ == "pybind11_tests.modules.subsubmodule"
assert ms.submodule_func() == "submodule_func()"
def test_reference_internal():
b = ms.B()
assert str(b.get_a1()) == "A[1]"
assert str(b.a1) == "A[1]"
assert str(b.get_a2()) == "A[2]"
assert str(b.a2) == "A[2]"
b.a1 = ms.A(42)
b.a2 = ms.A(43)
assert str(b.get_a1()) == "A[42]"
assert str(b.a1) == "A[42]"
assert str(b.get_a2()) == "A[43]"
assert str(b.a2) == "A[43]"
astats, bstats = ConstructorStats.get(ms.A), ConstructorStats.get(ms.B)
assert astats.alive() == 2
assert bstats.alive() == 1
del b
assert astats.alive() == 0
assert bstats.alive() == 0
assert astats.values() == ["1", "2", "42", "43"]
assert bstats.values() == []
assert astats.default_constructions == 0
assert bstats.default_constructions == 1
assert astats.copy_constructions == 0
assert bstats.copy_constructions == 0
# assert astats.move_constructions >= 0 # Don't invoke any
# assert bstats.move_constructions >= 0 # Don't invoke any
assert astats.copy_assignments == 2
assert bstats.copy_assignments == 0
assert astats.move_assignments == 0
assert bstats.move_assignments == 0
def test_importing():
from collections import OrderedDict
from pybind11_tests.modules import OD
assert OD is OrderedDict
assert str(OD([(1, "a"), (2, "b")])) == "OrderedDict([(1, 'a'), (2, 'b')])"
def test_pydoc():
"""Pydoc needs to be able to provide help() for everything inside a pybind11 module"""
import pydoc
import pybind11_tests
assert pybind11_tests.__name__ == "pybind11_tests"
assert pybind11_tests.__doc__ == "pybind11 test module"
assert pydoc.text.docmodule(pybind11_tests)
def test_duplicate_registration():
"""Registering two things with the same name"""
assert m.duplicate_registration() == []
def test_builtin_key_type():
"""Test that all the keys in the builtin modules have type str.
Previous versions of pybind11 would add a unicode key in python 2.
"""
if hasattr(__builtins__, "keys"):
keys = __builtins__.keys()
else: # this is to make pypy happy since builtins is different there.
keys = __builtins__.__dict__.keys()
assert {type(k) for k in keys} == {str}

View File

@@ -0,0 +1,341 @@
/*
tests/test_multiple_inheritance.cpp -- multiple inheritance,
implicit MI casts
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "constructor_stats.h"
#include "pybind11_tests.h"
namespace {
// Many bases for testing that multiple inheritance from many classes (i.e. requiring extra
// space for holder constructed flags) works.
template <int N>
struct BaseN {
explicit BaseN(int i) : i(i) {}
int i;
};
// test_mi_static_properties
struct Vanilla {
std::string vanilla() { return "Vanilla"; };
};
struct WithStatic1 {
static std::string static_func1() { return "WithStatic1"; };
static int static_value1;
};
struct WithStatic2 {
static std::string static_func2() { return "WithStatic2"; };
static int static_value2;
};
struct VanillaStaticMix1 : Vanilla, WithStatic1, WithStatic2 {
static std::string static_func() { return "VanillaStaticMix1"; }
static int static_value;
};
struct VanillaStaticMix2 : WithStatic1, Vanilla, WithStatic2 {
static std::string static_func() { return "VanillaStaticMix2"; }
static int static_value;
};
int WithStatic1::static_value1 = 1;
int WithStatic2::static_value2 = 2;
int VanillaStaticMix1::static_value = 12;
int VanillaStaticMix2::static_value = 12;
// test_multiple_inheritance_virtbase
struct Base1a {
explicit Base1a(int i) : i(i) {}
int foo() const { return i; }
int i;
};
struct Base2a {
explicit Base2a(int i) : i(i) {}
int bar() const { return i; }
int i;
};
struct Base12a : Base1a, Base2a {
Base12a(int i, int j) : Base1a(i), Base2a(j) {}
};
// test_mi_unaligned_base
// test_mi_base_return
struct I801B1 {
int a = 1;
I801B1() = default;
I801B1(const I801B1 &) = default;
virtual ~I801B1() = default;
};
struct I801B2 {
int b = 2;
I801B2() = default;
I801B2(const I801B2 &) = default;
virtual ~I801B2() = default;
};
struct I801C : I801B1, I801B2 {};
struct I801D : I801C {}; // Indirect MI
} // namespace
TEST_SUBMODULE(multiple_inheritance, m) {
// Please do not interleave `struct` and `class` definitions with bindings code,
// but implement `struct`s and `class`es in the anonymous namespace above.
// This helps keeping the smart_holder branch in sync with master.
// test_multiple_inheritance_mix1
// test_multiple_inheritance_mix2
struct Base1 {
explicit Base1(int i) : i(i) {}
int foo() const { return i; }
int i;
};
py::class_<Base1> b1(m, "Base1");
b1.def(py::init<int>()).def("foo", &Base1::foo);
struct Base2 {
explicit Base2(int i) : i(i) {}
int bar() const { return i; }
int i;
};
py::class_<Base2> b2(m, "Base2");
b2.def(py::init<int>()).def("bar", &Base2::bar);
// test_multiple_inheritance_cpp
struct Base12 : Base1, Base2 {
Base12(int i, int j) : Base1(i), Base2(j) {}
};
struct MIType : Base12 {
MIType(int i, int j) : Base12(i, j) {}
};
py::class_<Base12, Base1, Base2>(m, "Base12");
py::class_<MIType, Base12>(m, "MIType").def(py::init<int, int>());
// test_multiple_inheritance_python_many_bases
#define PYBIND11_BASEN(N) \
py::class_<BaseN<(N)>>(m, "BaseN" #N).def(py::init<int>()).def("f" #N, [](BaseN<N> &b) { \
return b.i + (N); \
})
PYBIND11_BASEN(1);
PYBIND11_BASEN(2);
PYBIND11_BASEN(3);
PYBIND11_BASEN(4);
PYBIND11_BASEN(5);
PYBIND11_BASEN(6);
PYBIND11_BASEN(7);
PYBIND11_BASEN(8);
PYBIND11_BASEN(9);
PYBIND11_BASEN(10);
PYBIND11_BASEN(11);
PYBIND11_BASEN(12);
PYBIND11_BASEN(13);
PYBIND11_BASEN(14);
PYBIND11_BASEN(15);
PYBIND11_BASEN(16);
PYBIND11_BASEN(17);
// Uncommenting this should result in a compile time failure (MI can only be specified via
// template parameters because pybind has to know the types involved; see discussion in #742
// for details).
// struct Base12v2 : Base1, Base2 {
// Base12v2(int i, int j) : Base1(i), Base2(j) { }
// };
// py::class_<Base12v2>(m, "Base12v2", b1, b2)
// .def(py::init<int, int>());
// test_multiple_inheritance_virtbase
// Test the case where not all base classes are specified, and where pybind11 requires the
// py::multiple_inheritance flag to perform proper casting between types.
py::class_<Base1a, std::shared_ptr<Base1a>>(m, "Base1a")
.def(py::init<int>())
.def("foo", &Base1a::foo);
py::class_<Base2a, std::shared_ptr<Base2a>>(m, "Base2a")
.def(py::init<int>())
.def("bar", &Base2a::bar);
py::class_<Base12a, /* Base1 missing */ Base2a, std::shared_ptr<Base12a>>(
m, "Base12a", py::multiple_inheritance())
.def(py::init<int, int>());
m.def("bar_base2a", [](Base2a *b) { return b->bar(); });
m.def("bar_base2a_sharedptr", [](const std::shared_ptr<Base2a> &b) { return b->bar(); });
// test_mi_unaligned_base
// test_mi_base_return
// Issue #801: invalid casting to derived type with MI bases
// Unregistered classes:
struct I801B3 {
int c = 3;
virtual ~I801B3() = default;
};
struct I801E : I801B3, I801D {};
py::class_<I801B1, std::shared_ptr<I801B1>>(m, "I801B1")
.def(py::init<>())
.def_readonly("a", &I801B1::a);
py::class_<I801B2, std::shared_ptr<I801B2>>(m, "I801B2")
.def(py::init<>())
.def_readonly("b", &I801B2::b);
py::class_<I801C, I801B1, I801B2, std::shared_ptr<I801C>>(m, "I801C").def(py::init<>());
py::class_<I801D, I801C, std::shared_ptr<I801D>>(m, "I801D").def(py::init<>());
// Two separate issues here: first, we want to recognize a pointer to a base type as being a
// known instance even when the pointer value is unequal (i.e. due to a non-first
// multiple-inheritance base class):
m.def("i801b1_c", [](I801C *c) { return static_cast<I801B1 *>(c); });
m.def("i801b2_c", [](I801C *c) { return static_cast<I801B2 *>(c); });
m.def("i801b1_d", [](I801D *d) { return static_cast<I801B1 *>(d); });
m.def("i801b2_d", [](I801D *d) { return static_cast<I801B2 *>(d); });
// Second, when returned a base class pointer to a derived instance, we cannot assume that the
// pointer is `reinterpret_cast`able to the derived pointer because, like above, the base class
// pointer could be offset.
m.def("i801c_b1", []() -> I801B1 * { return new I801C(); });
m.def("i801c_b2", []() -> I801B2 * { return new I801C(); });
m.def("i801d_b1", []() -> I801B1 * { return new I801D(); });
m.def("i801d_b2", []() -> I801B2 * { return new I801D(); });
// Return a base class pointer to a pybind-registered type when the actual derived type
// isn't pybind-registered (and uses multiple-inheritance to offset the pybind base)
m.def("i801e_c", []() -> I801C * { return new I801E(); });
m.def("i801e_b2", []() -> I801B2 * { return new I801E(); });
// test_mi_static_properties
py::class_<Vanilla>(m, "Vanilla").def(py::init<>()).def("vanilla", &Vanilla::vanilla);
py::class_<WithStatic1>(m, "WithStatic1")
.def(py::init<>())
.def_static("static_func1", &WithStatic1::static_func1)
.def_readwrite_static("static_value1", &WithStatic1::static_value1);
py::class_<WithStatic2>(m, "WithStatic2")
.def(py::init<>())
.def_static("static_func2", &WithStatic2::static_func2)
.def_readwrite_static("static_value2", &WithStatic2::static_value2);
py::class_<VanillaStaticMix1, Vanilla, WithStatic1, WithStatic2>(m, "VanillaStaticMix1")
.def(py::init<>())
.def_static("static_func", &VanillaStaticMix1::static_func)
.def_readwrite_static("static_value", &VanillaStaticMix1::static_value);
py::class_<VanillaStaticMix2, WithStatic1, Vanilla, WithStatic2>(m, "VanillaStaticMix2")
.def(py::init<>())
.def_static("static_func", &VanillaStaticMix2::static_func)
.def_readwrite_static("static_value", &VanillaStaticMix2::static_value);
struct WithDict {};
struct VanillaDictMix1 : Vanilla, WithDict {};
struct VanillaDictMix2 : WithDict, Vanilla {};
py::class_<WithDict>(m, "WithDict", py::dynamic_attr()).def(py::init<>());
py::class_<VanillaDictMix1, Vanilla, WithDict>(m, "VanillaDictMix1").def(py::init<>());
py::class_<VanillaDictMix2, WithDict, Vanilla>(m, "VanillaDictMix2").def(py::init<>());
// test_diamond_inheritance
// Issue #959: segfault when constructing diamond inheritance instance
// All of these have int members so that there will be various unequal pointers involved.
struct B {
int b;
B() = default;
B(const B &) = default;
virtual ~B() = default;
};
struct C0 : public virtual B {
int c0;
};
struct C1 : public virtual B {
int c1;
};
struct D : public C0, public C1 {
int d;
};
py::class_<B>(m, "B").def("b", [](B *self) { return self; });
py::class_<C0, B>(m, "C0").def("c0", [](C0 *self) { return self; });
py::class_<C1, B>(m, "C1").def("c1", [](C1 *self) { return self; });
py::class_<D, C0, C1>(m, "D").def(py::init<>());
// test_pr3635_diamond_*
// - functions are get_{base}_{var}, return {var}
struct MVB {
MVB() = default;
MVB(const MVB &) = default;
virtual ~MVB() = default;
int b = 1;
int get_b_b() const { return b; }
};
struct MVC : virtual MVB {
int c = 2;
int get_c_b() const { return b; }
int get_c_c() const { return c; }
};
struct MVD0 : virtual MVC {
int d0 = 3;
int get_d0_b() const { return b; }
int get_d0_c() const { return c; }
int get_d0_d0() const { return d0; }
};
struct MVD1 : virtual MVC {
int d1 = 4;
int get_d1_b() const { return b; }
int get_d1_c() const { return c; }
int get_d1_d1() const { return d1; }
};
struct MVE : virtual MVD0, virtual MVD1 {
int e = 5;
int get_e_b() const { return b; }
int get_e_c() const { return c; }
int get_e_d0() const { return d0; }
int get_e_d1() const { return d1; }
int get_e_e() const { return e; }
};
struct MVF : virtual MVE {
int f = 6;
int get_f_b() const { return b; }
int get_f_c() const { return c; }
int get_f_d0() const { return d0; }
int get_f_d1() const { return d1; }
int get_f_e() const { return e; }
int get_f_f() const { return f; }
};
py::class_<MVB>(m, "MVB")
.def(py::init<>())
.def("get_b_b", &MVB::get_b_b)
.def_readwrite("b", &MVB::b);
py::class_<MVC, MVB>(m, "MVC")
.def(py::init<>())
.def("get_c_b", &MVC::get_c_b)
.def("get_c_c", &MVC::get_c_c)
.def_readwrite("c", &MVC::c);
py::class_<MVD0, MVC>(m, "MVD0")
.def(py::init<>())
.def("get_d0_b", &MVD0::get_d0_b)
.def("get_d0_c", &MVD0::get_d0_c)
.def("get_d0_d0", &MVD0::get_d0_d0)
.def_readwrite("d0", &MVD0::d0);
py::class_<MVD1, MVC>(m, "MVD1")
.def(py::init<>())
.def("get_d1_b", &MVD1::get_d1_b)
.def("get_d1_c", &MVD1::get_d1_c)
.def("get_d1_d1", &MVD1::get_d1_d1)
.def_readwrite("d1", &MVD1::d1);
py::class_<MVE, MVD0, MVD1>(m, "MVE")
.def(py::init<>())
.def("get_e_b", &MVE::get_e_b)
.def("get_e_c", &MVE::get_e_c)
.def("get_e_d0", &MVE::get_e_d0)
.def("get_e_d1", &MVE::get_e_d1)
.def("get_e_e", &MVE::get_e_e)
.def_readwrite("e", &MVE::e);
py::class_<MVF, MVE>(m, "MVF")
.def(py::init<>())
.def("get_f_b", &MVF::get_f_b)
.def("get_f_c", &MVF::get_f_c)
.def("get_f_d0", &MVF::get_f_d0)
.def("get_f_d1", &MVF::get_f_d1)
.def("get_f_e", &MVF::get_f_e)
.def("get_f_f", &MVF::get_f_f)
.def_readwrite("f", &MVF::f);
}

View File

@@ -0,0 +1,493 @@
import pytest
import env # noqa: F401
from pybind11_tests import ConstructorStats
from pybind11_tests import multiple_inheritance as m
def test_multiple_inheritance_cpp():
mt = m.MIType(3, 4)
assert mt.foo() == 3
assert mt.bar() == 4
@pytest.mark.xfail("env.PYPY")
def test_multiple_inheritance_mix1():
class Base1:
def __init__(self, i):
self.i = i
def foo(self):
return self.i
class MITypePy(Base1, m.Base2):
def __init__(self, i, j):
Base1.__init__(self, i)
m.Base2.__init__(self, j)
mt = MITypePy(3, 4)
assert mt.foo() == 3
assert mt.bar() == 4
def test_multiple_inheritance_mix2():
class Base2:
def __init__(self, i):
self.i = i
def bar(self):
return self.i
class MITypePy(m.Base1, Base2):
def __init__(self, i, j):
m.Base1.__init__(self, i)
Base2.__init__(self, j)
mt = MITypePy(3, 4)
assert mt.foo() == 3
assert mt.bar() == 4
@pytest.mark.xfail("env.PYPY")
def test_multiple_inheritance_python():
class MI1(m.Base1, m.Base2):
def __init__(self, i, j):
m.Base1.__init__(self, i)
m.Base2.__init__(self, j)
class B1:
def v(self):
return 1
class MI2(B1, m.Base1, m.Base2):
def __init__(self, i, j):
B1.__init__(self)
m.Base1.__init__(self, i)
m.Base2.__init__(self, j)
class MI3(MI2):
def __init__(self, i, j):
MI2.__init__(self, i, j)
class MI4(MI3, m.Base2):
def __init__(self, i, j):
MI3.__init__(self, i, j)
# This should be ignored (Base2 is already initialized via MI2):
m.Base2.__init__(self, i + 100)
class MI5(m.Base2, B1, m.Base1):
def __init__(self, i, j):
B1.__init__(self)
m.Base1.__init__(self, i)
m.Base2.__init__(self, j)
class MI6(m.Base2, B1):
def __init__(self, i):
m.Base2.__init__(self, i)
B1.__init__(self)
class B2(B1):
def v(self):
return 2
class B3:
def v(self):
return 3
class B4(B3, B2):
def v(self):
return 4
class MI7(B4, MI6):
def __init__(self, i):
B4.__init__(self)
MI6.__init__(self, i)
class MI8(MI6, B3):
def __init__(self, i):
MI6.__init__(self, i)
B3.__init__(self)
class MI8b(B3, MI6):
def __init__(self, i):
B3.__init__(self)
MI6.__init__(self, i)
mi1 = MI1(1, 2)
assert mi1.foo() == 1
assert mi1.bar() == 2
mi2 = MI2(3, 4)
assert mi2.v() == 1
assert mi2.foo() == 3
assert mi2.bar() == 4
mi3 = MI3(5, 6)
assert mi3.v() == 1
assert mi3.foo() == 5
assert mi3.bar() == 6
mi4 = MI4(7, 8)
assert mi4.v() == 1
assert mi4.foo() == 7
assert mi4.bar() == 8
mi5 = MI5(10, 11)
assert mi5.v() == 1
assert mi5.foo() == 10
assert mi5.bar() == 11
mi6 = MI6(12)
assert mi6.v() == 1
assert mi6.bar() == 12
mi7 = MI7(13)
assert mi7.v() == 4
assert mi7.bar() == 13
mi8 = MI8(14)
assert mi8.v() == 1
assert mi8.bar() == 14
mi8b = MI8b(15)
assert mi8b.v() == 3
assert mi8b.bar() == 15
def test_multiple_inheritance_python_many_bases():
class MIMany14(m.BaseN1, m.BaseN2, m.BaseN3, m.BaseN4):
def __init__(self):
m.BaseN1.__init__(self, 1)
m.BaseN2.__init__(self, 2)
m.BaseN3.__init__(self, 3)
m.BaseN4.__init__(self, 4)
class MIMany58(m.BaseN5, m.BaseN6, m.BaseN7, m.BaseN8):
def __init__(self):
m.BaseN5.__init__(self, 5)
m.BaseN6.__init__(self, 6)
m.BaseN7.__init__(self, 7)
m.BaseN8.__init__(self, 8)
class MIMany916(
m.BaseN9,
m.BaseN10,
m.BaseN11,
m.BaseN12,
m.BaseN13,
m.BaseN14,
m.BaseN15,
m.BaseN16, ):
def __init__(self):
m.BaseN9.__init__(self, 9)
m.BaseN10.__init__(self, 10)
m.BaseN11.__init__(self, 11)
m.BaseN12.__init__(self, 12)
m.BaseN13.__init__(self, 13)
m.BaseN14.__init__(self, 14)
m.BaseN15.__init__(self, 15)
m.BaseN16.__init__(self, 16)
class MIMany19(MIMany14, MIMany58, m.BaseN9):
def __init__(self):
MIMany14.__init__(self)
MIMany58.__init__(self)
m.BaseN9.__init__(self, 9)
class MIMany117(MIMany14, MIMany58, MIMany916, m.BaseN17):
def __init__(self):
MIMany14.__init__(self)
MIMany58.__init__(self)
MIMany916.__init__(self)
m.BaseN17.__init__(self, 17)
# Inherits from 4 registered C++ classes: can fit in one pointer on any modern arch:
a = MIMany14()
for i in range(1, 4):
assert getattr(a, "f" + str(i))() == 2 * i
# Inherits from 8: requires 1/2 pointers worth of holder flags on 32/64-bit arch:
b = MIMany916()
for i in range(9, 16):
assert getattr(b, "f" + str(i))() == 2 * i
# Inherits from 9: requires >= 2 pointers worth of holder flags
c = MIMany19()
for i in range(1, 9):
assert getattr(c, "f" + str(i))() == 2 * i
# Inherits from 17: requires >= 3 pointers worth of holder flags
d = MIMany117()
for i in range(1, 17):
assert getattr(d, "f" + str(i))() == 2 * i
def test_multiple_inheritance_virtbase():
class MITypePy(m.Base12a):
def __init__(self, i, j):
m.Base12a.__init__(self, i, j)
mt = MITypePy(3, 4)
assert mt.bar() == 4
assert m.bar_base2a(mt) == 4
assert m.bar_base2a_sharedptr(mt) == 4
def test_mi_static_properties():
"""Mixing bases with and without static properties should be possible
and the result should be independent of base definition order"""
for d in (m.VanillaStaticMix1(), m.VanillaStaticMix2()):
assert d.vanilla() == "Vanilla"
assert d.static_func1() == "WithStatic1"
assert d.static_func2() == "WithStatic2"
assert d.static_func() == d.__class__.__name__
m.WithStatic1.static_value1 = 1
m.WithStatic2.static_value2 = 2
assert d.static_value1 == 1
assert d.static_value2 == 2
assert d.static_value == 12
d.static_value1 = 0
assert d.static_value1 == 0
d.static_value2 = 0
assert d.static_value2 == 0
d.static_value = 0
assert d.static_value == 0
# Requires PyPy 6+
def test_mi_dynamic_attributes():
"""Mixing bases with and without dynamic attribute support"""
for d in (m.VanillaDictMix1(), m.VanillaDictMix2()):
d.dynamic = 1
assert d.dynamic == 1
def test_mi_unaligned_base():
"""Returning an offset (non-first MI) base class pointer should recognize the instance"""
n_inst = ConstructorStats.detail_reg_inst()
c = m.I801C()
d = m.I801D()
# + 4 below because we have the two instances, and each instance has offset base I801B2
assert ConstructorStats.detail_reg_inst() == n_inst + 4
b1c = m.i801b1_c(c)
assert b1c is c
b2c = m.i801b2_c(c)
assert b2c is c
b1d = m.i801b1_d(d)
assert b1d is d
b2d = m.i801b2_d(d)
assert b2d is d
assert ConstructorStats.detail_reg_inst(
) == n_inst + 4 # no extra instances
del c, b1c, b2c
assert ConstructorStats.detail_reg_inst() == n_inst + 2
del d, b1d, b2d
assert ConstructorStats.detail_reg_inst() == n_inst
def test_mi_base_return():
"""Tests returning an offset (non-first MI) base class pointer to a derived instance"""
n_inst = ConstructorStats.detail_reg_inst()
c1 = m.i801c_b1()
assert type(c1) is m.I801C
assert c1.a == 1
assert c1.b == 2
d1 = m.i801d_b1()
assert type(d1) is m.I801D
assert d1.a == 1
assert d1.b == 2
assert ConstructorStats.detail_reg_inst() == n_inst + 4
c2 = m.i801c_b2()
assert type(c2) is m.I801C
assert c2.a == 1
assert c2.b == 2
d2 = m.i801d_b2()
assert type(d2) is m.I801D
assert d2.a == 1
assert d2.b == 2
assert ConstructorStats.detail_reg_inst() == n_inst + 8
del c2
assert ConstructorStats.detail_reg_inst() == n_inst + 6
del c1, d1, d2
assert ConstructorStats.detail_reg_inst() == n_inst
# Returning an unregistered derived type with a registered base; we won't
# pick up the derived type, obviously, but should still work (as an object
# of whatever type was returned).
e1 = m.i801e_c()
assert type(e1) is m.I801C
assert e1.a == 1
assert e1.b == 2
e2 = m.i801e_b2()
assert type(e2) is m.I801B2
assert e2.b == 2
def test_diamond_inheritance():
"""Tests that diamond inheritance works as expected (issue #959)"""
# Issue #959: this shouldn't segfault:
d = m.D()
# Make sure all the various distinct pointers are all recognized as registered instances:
assert d is d.c0()
assert d is d.c1()
assert d is d.b()
assert d is d.c0().b()
assert d is d.c1().b()
assert d is d.c0().c1().b().c0().b()
def test_pr3635_diamond_b():
o = m.MVB()
assert o.b == 1
assert o.get_b_b() == 1
def test_pr3635_diamond_c():
o = m.MVC()
assert o.b == 1
assert o.c == 2
assert o.get_b_b() == 1
assert o.get_c_b() == 1
assert o.get_c_c() == 2
def test_pr3635_diamond_d0():
o = m.MVD0()
assert o.b == 1
assert o.c == 2
assert o.d0 == 3
assert o.get_b_b() == 1
assert o.get_c_b() == 1
assert o.get_d0_b() == 1
assert o.get_c_c() == 2
assert o.get_d0_c() == 2
assert o.get_d0_d0() == 3
def test_pr3635_diamond_d1():
o = m.MVD1()
assert o.b == 1
assert o.c == 2
assert o.d1 == 4
assert o.get_b_b() == 1
assert o.get_c_b() == 1
assert o.get_d1_b() == 1
assert o.get_c_c() == 2
assert o.get_d1_c() == 2
assert o.get_d1_d1() == 4
def test_pr3635_diamond_e():
o = m.MVE()
assert o.b == 1
assert o.c == 2
assert o.d0 == 3
assert o.d1 == 4
assert o.e == 5
assert o.get_b_b() == 1
assert o.get_c_b() == 1
assert o.get_d0_b() == 1
assert o.get_d1_b() == 1
assert o.get_e_b() == 1
assert o.get_c_c() == 2
assert o.get_d0_c() == 2
assert o.get_d1_c() == 2
assert o.get_e_c() == 2
assert o.get_d0_d0() == 3
assert o.get_e_d0() == 3
assert o.get_d1_d1() == 4
assert o.get_e_d1() == 4
assert o.get_e_e() == 5
def test_pr3635_diamond_f():
o = m.MVF()
assert o.b == 1
assert o.c == 2
assert o.d0 == 3
assert o.d1 == 4
assert o.e == 5
assert o.f == 6
assert o.get_b_b() == 1
assert o.get_c_b() == 1
assert o.get_d0_b() == 1
assert o.get_d1_b() == 1
assert o.get_e_b() == 1
assert o.get_f_b() == 1
assert o.get_c_c() == 2
assert o.get_d0_c() == 2
assert o.get_d1_c() == 2
assert o.get_e_c() == 2
assert o.get_f_c() == 2
assert o.get_d0_d0() == 3
assert o.get_e_d0() == 3
assert o.get_f_d0() == 3
assert o.get_d1_d1() == 4
assert o.get_e_d1() == 4
assert o.get_f_d1() == 4
assert o.get_e_e() == 5
assert o.get_f_e() == 5
assert o.get_f_f() == 6
def test_python_inherit_from_mi():
"""Tests extending a Python class from a single inheritor of a MI class"""
class PyMVF(m.MVF):
g = 7
def get_g_g(self):
return self.g
o = PyMVF()
assert o.b == 1
assert o.c == 2
assert o.d0 == 3
assert o.d1 == 4
assert o.e == 5
assert o.f == 6
assert o.g == 7
assert o.get_g_g() == 7

View File

@@ -0,0 +1,524 @@
/*
tests/test_numpy_array.cpp -- test core array functionality
Copyright (c) 2016 Ivan Smirnov <i.s.smirnov@gmail.com>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include "pybind11_tests.h"
#include <cstdint>
#include <utility>
// Size / dtype checks.
struct DtypeCheck {
py::dtype numpy{};
py::dtype pybind11{};
};
template <typename T>
DtypeCheck get_dtype_check(const char *name) {
py::module_ np = py::module_::import("numpy");
DtypeCheck check{};
check.numpy = np.attr("dtype")(np.attr(name));
check.pybind11 = py::dtype::of<T>();
return check;
}
std::vector<DtypeCheck> get_concrete_dtype_checks() {
return {// Normalization
get_dtype_check<std::int8_t>("int8"),
get_dtype_check<std::uint8_t>("uint8"),
get_dtype_check<std::int16_t>("int16"),
get_dtype_check<std::uint16_t>("uint16"),
get_dtype_check<std::int32_t>("int32"),
get_dtype_check<std::uint32_t>("uint32"),
get_dtype_check<std::int64_t>("int64"),
get_dtype_check<std::uint64_t>("uint64")};
}
struct DtypeSizeCheck {
std::string name{};
int size_cpp{};
int size_numpy{};
// For debugging.
py::dtype dtype{};
};
template <typename T>
DtypeSizeCheck get_dtype_size_check() {
DtypeSizeCheck check{};
check.name = py::type_id<T>();
check.size_cpp = sizeof(T);
check.dtype = py::dtype::of<T>();
check.size_numpy = check.dtype.attr("itemsize").template cast<int>();
return check;
}
std::vector<DtypeSizeCheck> get_platform_dtype_size_checks() {
return {
get_dtype_size_check<short>(),
get_dtype_size_check<unsigned short>(),
get_dtype_size_check<int>(),
get_dtype_size_check<unsigned int>(),
get_dtype_size_check<long>(),
get_dtype_size_check<unsigned long>(),
get_dtype_size_check<long long>(),
get_dtype_size_check<unsigned long long>(),
};
}
// Arrays.
using arr = py::array;
using arr_t = py::array_t<uint16_t, 0>;
static_assert(std::is_same<arr_t::value_type, uint16_t>::value, "");
template <typename... Ix>
arr data(const arr &a, Ix... index) {
return arr(a.nbytes() - a.offset_at(index...), (const uint8_t *) a.data(index...));
}
template <typename... Ix>
arr data_t(const arr_t &a, Ix... index) {
return arr(a.size() - a.index_at(index...), a.data(index...));
}
template <typename... Ix>
arr &mutate_data(arr &a, Ix... index) {
auto *ptr = (uint8_t *) a.mutable_data(index...);
for (py::ssize_t i = 0; i < a.nbytes() - a.offset_at(index...); i++) {
ptr[i] = (uint8_t) (ptr[i] * 2);
}
return a;
}
template <typename... Ix>
arr_t &mutate_data_t(arr_t &a, Ix... index) {
auto ptr = a.mutable_data(index...);
for (py::ssize_t i = 0; i < a.size() - a.index_at(index...); i++) {
ptr[i]++;
}
return a;
}
template <typename... Ix>
py::ssize_t index_at(const arr &a, Ix... idx) {
return a.index_at(idx...);
}
template <typename... Ix>
py::ssize_t index_at_t(const arr_t &a, Ix... idx) {
return a.index_at(idx...);
}
template <typename... Ix>
py::ssize_t offset_at(const arr &a, Ix... idx) {
return a.offset_at(idx...);
}
template <typename... Ix>
py::ssize_t offset_at_t(const arr_t &a, Ix... idx) {
return a.offset_at(idx...);
}
template <typename... Ix>
py::ssize_t at_t(const arr_t &a, Ix... idx) {
return a.at(idx...);
}
template <typename... Ix>
arr_t &mutate_at_t(arr_t &a, Ix... idx) {
a.mutable_at(idx...)++;
return a;
}
#define def_index_fn(name, type) \
sm.def(#name, [](type a) { return name(a); }); \
sm.def(#name, [](type a, int i) { return name(a, i); }); \
sm.def(#name, [](type a, int i, int j) { return name(a, i, j); }); \
sm.def(#name, [](type a, int i, int j, int k) { return name(a, i, j, k); });
template <typename T, typename T2>
py::handle auxiliaries(T &&r, T2 &&r2) {
if (r.ndim() != 2) {
throw std::domain_error("error: ndim != 2");
}
py::list l;
l.append(*r.data(0, 0));
l.append(*r2.mutable_data(0, 0));
l.append(r.data(0, 1) == r2.mutable_data(0, 1));
l.append(r.ndim());
l.append(r.itemsize());
l.append(r.shape(0));
l.append(r.shape(1));
l.append(r.size());
l.append(r.nbytes());
return l.release();
}
// note: declaration at local scope would create a dangling reference!
static int data_i = 42;
TEST_SUBMODULE(numpy_array, sm) {
try {
py::module_::import("numpy");
} catch (...) {
return;
}
// test_dtypes
py::class_<DtypeCheck>(sm, "DtypeCheck")
.def_readonly("numpy", &DtypeCheck::numpy)
.def_readonly("pybind11", &DtypeCheck::pybind11)
.def("__repr__", [](const DtypeCheck &self) {
return py::str("<DtypeCheck numpy={} pybind11={}>").format(self.numpy, self.pybind11);
});
sm.def("get_concrete_dtype_checks", &get_concrete_dtype_checks);
py::class_<DtypeSizeCheck>(sm, "DtypeSizeCheck")
.def_readonly("name", &DtypeSizeCheck::name)
.def_readonly("size_cpp", &DtypeSizeCheck::size_cpp)
.def_readonly("size_numpy", &DtypeSizeCheck::size_numpy)
.def("__repr__", [](const DtypeSizeCheck &self) {
return py::str("<DtypeSizeCheck name='{}' size_cpp={} size_numpy={} dtype={}>")
.format(self.name, self.size_cpp, self.size_numpy, self.dtype);
});
sm.def("get_platform_dtype_size_checks", &get_platform_dtype_size_checks);
// test_array_attributes
sm.def("ndim", [](const arr &a) { return a.ndim(); });
sm.def("shape", [](const arr &a) { return arr(a.ndim(), a.shape()); });
sm.def("shape", [](const arr &a, py::ssize_t dim) { return a.shape(dim); });
sm.def("strides", [](const arr &a) { return arr(a.ndim(), a.strides()); });
sm.def("strides", [](const arr &a, py::ssize_t dim) { return a.strides(dim); });
sm.def("writeable", [](const arr &a) { return a.writeable(); });
sm.def("size", [](const arr &a) { return a.size(); });
sm.def("itemsize", [](const arr &a) { return a.itemsize(); });
sm.def("nbytes", [](const arr &a) { return a.nbytes(); });
sm.def("owndata", [](const arr &a) { return a.owndata(); });
// test_index_offset
def_index_fn(index_at, const arr &);
def_index_fn(index_at_t, const arr_t &);
def_index_fn(offset_at, const arr &);
def_index_fn(offset_at_t, const arr_t &);
// test_data
def_index_fn(data, const arr &);
def_index_fn(data_t, const arr_t &);
// test_mutate_data, test_mutate_readonly
def_index_fn(mutate_data, arr &);
def_index_fn(mutate_data_t, arr_t &);
def_index_fn(at_t, const arr_t &);
def_index_fn(mutate_at_t, arr_t &);
// test_make_c_f_array
sm.def("make_f_array", [] { return py::array_t<float>({2, 2}, {4, 8}); });
sm.def("make_c_array", [] { return py::array_t<float>({2, 2}, {8, 4}); });
// test_empty_shaped_array
sm.def("make_empty_shaped_array", [] { return py::array(py::dtype("f"), {}, {}); });
// test numpy scalars (empty shape, ndim==0)
sm.def("scalar_int", []() { return py::array(py::dtype("i"), {}, {}, &data_i); });
// test_wrap
sm.def("wrap", [](const py::array &a) {
return py::array(a.dtype(),
{a.shape(), a.shape() + a.ndim()},
{a.strides(), a.strides() + a.ndim()},
a.data(),
a);
});
// test_numpy_view
struct ArrayClass {
int data[2] = {1, 2};
ArrayClass() { py::print("ArrayClass()"); }
~ArrayClass() { py::print("~ArrayClass()"); }
};
py::class_<ArrayClass>(sm, "ArrayClass")
.def(py::init<>())
.def("numpy_view", [](py::object &obj) {
py::print("ArrayClass::numpy_view()");
auto &a = obj.cast<ArrayClass &>();
return py::array_t<int>({2}, {4}, a.data, obj);
});
// test_cast_numpy_int64_to_uint64
sm.def("function_taking_uint64", [](uint64_t) {});
// test_isinstance
sm.def("isinstance_untyped", [](py::object yes, py::object no) {
return py::isinstance<py::array>(std::move(yes))
&& !py::isinstance<py::array>(std::move(no));
});
sm.def("isinstance_typed", [](const py::object &o) {
return py::isinstance<py::array_t<double>>(o) && !py::isinstance<py::array_t<int>>(o);
});
// test_constructors
sm.def("default_constructors", []() {
return py::dict("array"_a = py::array(),
"array_t<int32>"_a = py::array_t<std::int32_t>(),
"array_t<double>"_a = py::array_t<double>());
});
sm.def("converting_constructors", [](const py::object &o) {
return py::dict("array"_a = py::array(o),
"array_t<int32>"_a = py::array_t<std::int32_t>(o),
"array_t<double>"_a = py::array_t<double>(o));
});
// test_overload_resolution
sm.def("overloaded", [](const py::array_t<double> &) { return "double"; });
sm.def("overloaded", [](const py::array_t<float> &) { return "float"; });
sm.def("overloaded", [](const py::array_t<int> &) { return "int"; });
sm.def("overloaded", [](const py::array_t<unsigned short> &) { return "unsigned short"; });
sm.def("overloaded", [](const py::array_t<long long> &) { return "long long"; });
sm.def("overloaded",
[](const py::array_t<std::complex<double>> &) { return "double complex"; });
sm.def("overloaded", [](const py::array_t<std::complex<float>> &) { return "float complex"; });
sm.def("overloaded2",
[](const py::array_t<std::complex<double>> &) { return "double complex"; });
sm.def("overloaded2", [](const py::array_t<double> &) { return "double"; });
sm.def("overloaded2",
[](const py::array_t<std::complex<float>> &) { return "float complex"; });
sm.def("overloaded2", [](const py::array_t<float> &) { return "float"; });
// [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works.
// Only accept the exact types:
sm.def(
"overloaded3", [](const py::array_t<int> &) { return "int"; }, py::arg{}.noconvert());
sm.def(
"overloaded3",
[](const py::array_t<double> &) { return "double"; },
py::arg{}.noconvert());
// Make sure we don't do unsafe coercion (e.g. float to int) when not using forcecast, but
// rather that float gets converted via the safe (conversion to double) overload:
sm.def("overloaded4", [](const py::array_t<long long, 0> &) { return "long long"; });
sm.def("overloaded4", [](const py::array_t<double, 0> &) { return "double"; });
// But we do allow conversion to int if forcecast is enabled (but only if no overload matches
// without conversion)
sm.def("overloaded5", [](const py::array_t<unsigned int> &) { return "unsigned int"; });
sm.def("overloaded5", [](const py::array_t<double> &) { return "double"; });
// test_greedy_string_overload
// Issue 685: ndarray shouldn't go to std::string overload
sm.def("issue685", [](const std::string &) { return "string"; });
sm.def("issue685", [](const py::array &) { return "array"; });
sm.def("issue685", [](const py::object &) { return "other"; });
// test_array_unchecked_fixed_dims
sm.def(
"proxy_add2",
[](py::array_t<double> a, double v) {
auto r = a.mutable_unchecked<2>();
for (py::ssize_t i = 0; i < r.shape(0); i++) {
for (py::ssize_t j = 0; j < r.shape(1); j++) {
r(i, j) += v;
}
}
},
py::arg{}.noconvert(),
py::arg());
sm.def("proxy_init3", [](double start) {
py::array_t<double, py::array::c_style> a({3, 3, 3});
auto r = a.mutable_unchecked<3>();
for (py::ssize_t i = 0; i < r.shape(0); i++) {
for (py::ssize_t j = 0; j < r.shape(1); j++) {
for (py::ssize_t k = 0; k < r.shape(2); k++) {
r(i, j, k) = start++;
}
}
}
return a;
});
sm.def("proxy_init3F", [](double start) {
py::array_t<double, py::array::f_style> a({3, 3, 3});
auto r = a.mutable_unchecked<3>();
for (py::ssize_t k = 0; k < r.shape(2); k++) {
for (py::ssize_t j = 0; j < r.shape(1); j++) {
for (py::ssize_t i = 0; i < r.shape(0); i++) {
r(i, j, k) = start++;
}
}
}
return a;
});
sm.def("proxy_squared_L2_norm", [](const py::array_t<double> &a) {
auto r = a.unchecked<1>();
double sumsq = 0;
for (py::ssize_t i = 0; i < r.shape(0); i++) {
sumsq += r[i] * r(i); // Either notation works for a 1D array
}
return sumsq;
});
sm.def("proxy_auxiliaries2", [](py::array_t<double> a) {
auto r = a.unchecked<2>();
auto r2 = a.mutable_unchecked<2>();
return auxiliaries(r, r2);
});
sm.def("proxy_auxiliaries1_const_ref", [](py::array_t<double> a) {
const auto &r = a.unchecked<1>();
const auto &r2 = a.mutable_unchecked<1>();
return r(0) == r2(0) && r[0] == r2[0];
});
sm.def("proxy_auxiliaries2_const_ref", [](py::array_t<double> a) {
const auto &r = a.unchecked<2>();
const auto &r2 = a.mutable_unchecked<2>();
return r(0, 0) == r2(0, 0);
});
// test_array_unchecked_dyn_dims
// Same as the above, but without a compile-time dimensions specification:
sm.def(
"proxy_add2_dyn",
[](py::array_t<double> a, double v) {
auto r = a.mutable_unchecked();
if (r.ndim() != 2) {
throw std::domain_error("error: ndim != 2");
}
for (py::ssize_t i = 0; i < r.shape(0); i++) {
for (py::ssize_t j = 0; j < r.shape(1); j++) {
r(i, j) += v;
}
}
},
py::arg{}.noconvert(),
py::arg());
sm.def("proxy_init3_dyn", [](double start) {
py::array_t<double, py::array::c_style> a({3, 3, 3});
auto r = a.mutable_unchecked();
if (r.ndim() != 3) {
throw std::domain_error("error: ndim != 3");
}
for (py::ssize_t i = 0; i < r.shape(0); i++) {
for (py::ssize_t j = 0; j < r.shape(1); j++) {
for (py::ssize_t k = 0; k < r.shape(2); k++) {
r(i, j, k) = start++;
}
}
}
return a;
});
sm.def("proxy_auxiliaries2_dyn", [](py::array_t<double> a) {
return auxiliaries(a.unchecked(), a.mutable_unchecked());
});
sm.def("array_auxiliaries2", [](py::array_t<double> a) { return auxiliaries(a, a); });
// test_array_failures
// Issue #785: Uninformative "Unknown internal error" exception when constructing array from
// empty object:
sm.def("array_fail_test", []() { return py::array(py::object()); });
sm.def("array_t_fail_test", []() { return py::array_t<double>(py::object()); });
// Make sure the error from numpy is being passed through:
sm.def("array_fail_test_negative_size", []() {
int c = 0;
return py::array(-1, &c);
});
// test_initializer_list
// Issue (unnumbered; reported in #788): regression: initializer lists can be ambiguous
sm.def("array_initializer_list1", []() { return py::array_t<float>(1); });
// { 1 } also works for the above, but clang warns about it
sm.def("array_initializer_list2", []() { return py::array_t<float>({1, 2}); });
sm.def("array_initializer_list3", []() { return py::array_t<float>({1, 2, 3}); });
sm.def("array_initializer_list4", []() { return py::array_t<float>({1, 2, 3, 4}); });
// test_array_resize
// reshape array to 2D without changing size
sm.def("array_reshape2", [](py::array_t<double> a) {
const auto dim_sz = (py::ssize_t) std::sqrt(a.size());
if (dim_sz * dim_sz != a.size()) {
throw std::domain_error(
"array_reshape2: input array total size is not a squared integer");
}
a.resize({dim_sz, dim_sz});
});
// resize to 3D array with each dimension = N
sm.def("array_resize3", [](py::array_t<double> a, size_t N, bool refcheck) {
a.resize({N, N, N}, refcheck);
});
// test_array_create_and_resize
// return 2D array with Nrows = Ncols = N
sm.def("create_and_resize", [](size_t N) {
py::array_t<double> a;
a.resize({N, N});
std::fill(a.mutable_data(), a.mutable_data() + a.size(), 42.);
return a;
});
sm.def("array_view",
[](py::array_t<uint8_t> a, const std::string &dtype) { return a.view(dtype); });
sm.def("reshape_initializer_list", [](py::array_t<int> a, size_t N, size_t M, size_t O) {
return a.reshape({N, M, O});
});
sm.def("reshape_tuple", [](py::array_t<int> a, const std::vector<int> &new_shape) {
return a.reshape(new_shape);
});
sm.def("index_using_ellipsis",
[](const py::array &a) { return a[py::make_tuple(0, py::ellipsis(), 0)]; });
// test_argument_conversions
sm.def(
"accept_double", [](const py::array_t<double, 0> &) {}, py::arg("a"));
sm.def(
"accept_double_forcecast",
[](const py::array_t<double, py::array::forcecast> &) {},
py::arg("a"));
sm.def(
"accept_double_c_style",
[](const py::array_t<double, py::array::c_style> &) {},
py::arg("a"));
sm.def(
"accept_double_c_style_forcecast",
[](const py::array_t<double, py::array::forcecast | py::array::c_style> &) {},
py::arg("a"));
sm.def(
"accept_double_f_style",
[](const py::array_t<double, py::array::f_style> &) {},
py::arg("a"));
sm.def(
"accept_double_f_style_forcecast",
[](const py::array_t<double, py::array::forcecast | py::array::f_style> &) {},
py::arg("a"));
sm.def(
"accept_double_noconvert", [](const py::array_t<double, 0> &) {}, "a"_a.noconvert());
sm.def(
"accept_double_forcecast_noconvert",
[](const py::array_t<double, py::array::forcecast> &) {},
"a"_a.noconvert());
sm.def(
"accept_double_c_style_noconvert",
[](const py::array_t<double, py::array::c_style> &) {},
"a"_a.noconvert());
sm.def(
"accept_double_c_style_forcecast_noconvert",
[](const py::array_t<double, py::array::forcecast | py::array::c_style> &) {},
"a"_a.noconvert());
sm.def(
"accept_double_f_style_noconvert",
[](const py::array_t<double, py::array::f_style> &) {},
"a"_a.noconvert());
sm.def(
"accept_double_f_style_forcecast_noconvert",
[](const py::array_t<double, py::array::forcecast | py::array::f_style> &) {},
"a"_a.noconvert());
// Check that types returns correct npy format descriptor
sm.def("test_fmt_desc_float", [](const py::array_t<float> &) {});
sm.def("test_fmt_desc_double", [](const py::array_t<double> &) {});
sm.def("test_fmt_desc_const_float", [](const py::array_t<const float> &) {});
sm.def("test_fmt_desc_const_double", [](const py::array_t<const double> &) {});
}

View File

@@ -0,0 +1,589 @@
import pytest
import env # noqa: F401
from pybind11_tests import numpy_array as m
np = pytest.importorskip("numpy")
def test_dtypes():
# See issue #1328.
# - Platform-dependent sizes.
for size_check in m.get_platform_dtype_size_checks():
print(size_check)
assert size_check.size_cpp == size_check.size_numpy, size_check
# - Concrete sizes.
for check in m.get_concrete_dtype_checks():
print(check)
assert check.numpy == check.pybind11, check
if check.numpy.num != check.pybind11.num:
print(
f"NOTE: typenum mismatch for {check}: {check.numpy.num} != {check.pybind11.num}"
)
@pytest.fixture(scope="function")
def arr():
return np.array([[1, 2, 3], [4, 5, 6]], "=u2")
def test_array_attributes():
a = np.array(0, "f8")
assert m.ndim(a) == 0
assert all(m.shape(a) == [])
assert all(m.strides(a) == [])
with pytest.raises(IndexError) as excinfo:
m.shape(a, 0)
assert str(excinfo.value) == "invalid axis: 0 (ndim = 0)"
with pytest.raises(IndexError) as excinfo:
m.strides(a, 0)
assert str(excinfo.value) == "invalid axis: 0 (ndim = 0)"
assert m.writeable(a)
assert m.size(a) == 1
assert m.itemsize(a) == 8
assert m.nbytes(a) == 8
assert m.owndata(a)
a = np.array([[1, 2, 3], [4, 5, 6]], "u2").view()
a.flags.writeable = False
assert m.ndim(a) == 2
assert all(m.shape(a) == [2, 3])
assert m.shape(a, 0) == 2
assert m.shape(a, 1) == 3
assert all(m.strides(a) == [6, 2])
assert m.strides(a, 0) == 6
assert m.strides(a, 1) == 2
with pytest.raises(IndexError) as excinfo:
m.shape(a, 2)
assert str(excinfo.value) == "invalid axis: 2 (ndim = 2)"
with pytest.raises(IndexError) as excinfo:
m.strides(a, 2)
assert str(excinfo.value) == "invalid axis: 2 (ndim = 2)"
assert not m.writeable(a)
assert m.size(a) == 6
assert m.itemsize(a) == 2
assert m.nbytes(a) == 12
assert not m.owndata(a)
@pytest.mark.parametrize(
"args, ret", [([], 0), ([0], 0), ([1], 3), ([0, 1], 1), ([1, 2], 5)])
def test_index_offset(arr, args, ret):
assert m.index_at(arr, *args) == ret
assert m.index_at_t(arr, *args) == ret
assert m.offset_at(arr, *args) == ret * arr.dtype.itemsize
assert m.offset_at_t(arr, *args) == ret * arr.dtype.itemsize
def test_dim_check_fail(arr):
for func in (
m.index_at,
m.index_at_t,
m.offset_at,
m.offset_at_t,
m.data,
m.data_t,
m.mutate_data,
m.mutate_data_t, ):
with pytest.raises(IndexError) as excinfo:
func(arr, 1, 2, 3)
assert str(
excinfo.value) == "too many indices for an array: 3 (ndim = 2)"
@pytest.mark.parametrize(
"args, ret",
[
([], [1, 2, 3, 4, 5, 6]),
([1], [4, 5, 6]),
([0, 1], [2, 3, 4, 5, 6]),
([1, 2], [6]),
], )
def test_data(arr, args, ret):
from sys import byteorder
assert all(m.data_t(arr, *args) == ret)
assert all(
m.data(arr, *args)[(0 if byteorder == "little" else 1)::2] == ret)
assert all(m.data(arr, *args)[(1 if byteorder == "little" else 0)::2] == 0)
@pytest.mark.parametrize("dim", [0, 1, 3])
def test_at_fail(arr, dim):
for func in m.at_t, m.mutate_at_t:
with pytest.raises(IndexError) as excinfo:
func(arr, *([0] * dim))
assert str(
excinfo.value) == f"index dimension mismatch: {dim} (ndim = 2)"
def test_at(arr):
assert m.at_t(arr, 0, 2) == 3
assert m.at_t(arr, 1, 0) == 4
assert all(m.mutate_at_t(arr, 0, 2).ravel() == [1, 2, 4, 4, 5, 6])
assert all(m.mutate_at_t(arr, 1, 0).ravel() == [1, 2, 4, 5, 5, 6])
def test_mutate_readonly(arr):
arr.flags.writeable = False
for func, args in (
(m.mutate_data, ()),
(m.mutate_data_t, ()),
(m.mutate_at_t, (0, 0)), ):
with pytest.raises(ValueError) as excinfo:
func(arr, *args)
assert str(excinfo.value) == "array is not writeable"
def test_mutate_data(arr):
assert all(m.mutate_data(arr).ravel() == [2, 4, 6, 8, 10, 12])
assert all(m.mutate_data(arr).ravel() == [4, 8, 12, 16, 20, 24])
assert all(m.mutate_data(arr, 1).ravel() == [4, 8, 12, 32, 40, 48])
assert all(m.mutate_data(arr, 0, 1).ravel() == [4, 16, 24, 64, 80, 96])
assert all(m.mutate_data(arr, 1, 2).ravel() == [4, 16, 24, 64, 80, 192])
assert all(m.mutate_data_t(arr).ravel() == [5, 17, 25, 65, 81, 193])
assert all(m.mutate_data_t(arr).ravel() == [6, 18, 26, 66, 82, 194])
assert all(m.mutate_data_t(arr, 1).ravel() == [6, 18, 26, 67, 83, 195])
assert all(m.mutate_data_t(arr, 0, 1).ravel() == [6, 19, 27, 68, 84, 196])
assert all(m.mutate_data_t(arr, 1, 2).ravel() == [6, 19, 27, 68, 84, 197])
def test_bounds_check(arr):
for func in (
m.index_at,
m.index_at_t,
m.data,
m.data_t,
m.mutate_data,
m.mutate_data_t,
m.at_t,
m.mutate_at_t, ):
with pytest.raises(IndexError) as excinfo:
func(arr, 2, 0)
assert str(
excinfo.value) == "index 2 is out of bounds for axis 0 with size 2"
with pytest.raises(IndexError) as excinfo:
func(arr, 0, 4)
assert str(
excinfo.value) == "index 4 is out of bounds for axis 1 with size 3"
def test_make_c_f_array():
assert m.make_c_array().flags.c_contiguous
assert not m.make_c_array().flags.f_contiguous
assert m.make_f_array().flags.f_contiguous
assert not m.make_f_array().flags.c_contiguous
def test_make_empty_shaped_array():
m.make_empty_shaped_array()
# empty shape means numpy scalar, PEP 3118
assert m.scalar_int().ndim == 0
assert m.scalar_int().shape == ()
assert m.scalar_int() == 42
def test_wrap():
def assert_references(a, b, base=None):
if base is None:
base = a
assert a is not b
assert a.__array_interface__["data"][0] == b.__array_interface__[
"data"][0]
assert a.shape == b.shape
assert a.strides == b.strides
assert a.flags.c_contiguous == b.flags.c_contiguous
assert a.flags.f_contiguous == b.flags.f_contiguous
assert a.flags.writeable == b.flags.writeable
assert a.flags.aligned == b.flags.aligned
# 1.13 supported Python 3.6
if tuple(int(x) for x in np.__version__.split(".")[:2]) >= (1, 14):
assert a.flags.writebackifcopy == b.flags.writebackifcopy
else:
assert a.flags.updateifcopy == b.flags.updateifcopy
assert np.all(a == b)
assert not b.flags.owndata
assert b.base is base
if a.flags.writeable and a.ndim == 2:
a[0, 0] = 1234
assert b[0, 0] == 1234
a1 = np.array([1, 2], dtype=np.int16)
assert a1.flags.owndata and a1.base is None
a2 = m.wrap(a1)
assert_references(a1, a2)
a1 = np.array([[1, 2], [3, 4]], dtype=np.float32, order="F")
assert a1.flags.owndata and a1.base is None
a2 = m.wrap(a1)
assert_references(a1, a2)
a1 = np.array([[1, 2], [3, 4]], dtype=np.float32, order="C")
a1.flags.writeable = False
a2 = m.wrap(a1)
assert_references(a1, a2)
a1 = np.random.random((4, 4, 4))
a2 = m.wrap(a1)
assert_references(a1, a2)
a1t = a1.transpose()
a2 = m.wrap(a1t)
assert_references(a1t, a2, a1)
a1d = a1.diagonal()
a2 = m.wrap(a1d)
assert_references(a1d, a2, a1)
a1m = a1[::-1, ::-1, ::-1]
a2 = m.wrap(a1m)
assert_references(a1m, a2, a1)
def test_numpy_view(capture):
with capture:
ac = m.ArrayClass()
ac_view_1 = ac.numpy_view()
ac_view_2 = ac.numpy_view()
assert np.all(ac_view_1 == np.array([1, 2], dtype=np.int32))
del ac
pytest.gc_collect()
assert (capture == """
ArrayClass()
ArrayClass::numpy_view()
ArrayClass::numpy_view()
""")
ac_view_1[0] = 4
ac_view_1[1] = 3
assert ac_view_2[0] == 4
assert ac_view_2[1] == 3
with capture:
del ac_view_1
del ac_view_2
pytest.gc_collect()
pytest.gc_collect()
assert (capture == """
~ArrayClass()
""")
def test_cast_numpy_int64_to_uint64():
m.function_taking_uint64(123)
m.function_taking_uint64(np.uint64(123))
def test_isinstance():
assert m.isinstance_untyped(np.array([1, 2, 3]), "not an array")
assert m.isinstance_typed(np.array([1.0, 2.0, 3.0]))
def test_constructors():
defaults = m.default_constructors()
for a in defaults.values():
assert a.size == 0
assert defaults["array"].dtype == np.array([]).dtype
assert defaults["array_t<int32>"].dtype == np.int32
assert defaults["array_t<double>"].dtype == np.float64
results = m.converting_constructors([1, 2, 3])
for a in results.values():
np.testing.assert_array_equal(a, [1, 2, 3])
assert results["array"].dtype == np.int_
assert results["array_t<int32>"].dtype == np.int32
assert results["array_t<double>"].dtype == np.float64
def test_overload_resolution(msg):
# Exact overload matches:
assert m.overloaded(np.array([1], dtype="float64")) == "double"
assert m.overloaded(np.array([1], dtype="float32")) == "float"
assert m.overloaded(np.array([1], dtype="ushort")) == "unsigned short"
assert m.overloaded(np.array([1], dtype="intc")) == "int"
assert m.overloaded(np.array([1], dtype="longlong")) == "long long"
assert m.overloaded(np.array([1], dtype="complex")) == "double complex"
assert m.overloaded(np.array([1], dtype="csingle")) == "float complex"
# No exact match, should call first convertible version:
assert m.overloaded(np.array([1], dtype="uint8")) == "double"
with pytest.raises(TypeError) as excinfo:
m.overloaded("not an array")
assert (msg(excinfo.value) == """
overloaded(): incompatible function arguments. The following argument types are supported:
1. (arg0: numpy.ndarray[numpy.float64]) -> str
2. (arg0: numpy.ndarray[numpy.float32]) -> str
3. (arg0: numpy.ndarray[numpy.int32]) -> str
4. (arg0: numpy.ndarray[numpy.uint16]) -> str
5. (arg0: numpy.ndarray[numpy.int64]) -> str
6. (arg0: numpy.ndarray[numpy.complex128]) -> str
7. (arg0: numpy.ndarray[numpy.complex64]) -> str
Invoked with: 'not an array'
""")
assert m.overloaded2(np.array([1], dtype="float64")) == "double"
assert m.overloaded2(np.array([1], dtype="float32")) == "float"
assert m.overloaded2(np.array([1], dtype="complex64")) == "float complex"
assert m.overloaded2(np.array([1], dtype="complex128")) == "double complex"
assert m.overloaded2(np.array([1], dtype="float32")) == "float"
assert m.overloaded3(np.array([1], dtype="float64")) == "double"
assert m.overloaded3(np.array([1], dtype="intc")) == "int"
expected_exc = """
overloaded3(): incompatible function arguments. The following argument types are supported:
1. (arg0: numpy.ndarray[numpy.int32]) -> str
2. (arg0: numpy.ndarray[numpy.float64]) -> str
Invoked with: """
with pytest.raises(TypeError) as excinfo:
m.overloaded3(np.array([1], dtype="uintc"))
assert msg(excinfo.value) == expected_exc + repr(
np.array(
[1], dtype="uint32"))
with pytest.raises(TypeError) as excinfo:
m.overloaded3(np.array([1], dtype="float32"))
assert msg(excinfo.value) == expected_exc + repr(
np.array(
[1.0], dtype="float32"))
with pytest.raises(TypeError) as excinfo:
m.overloaded3(np.array([1], dtype="complex"))
assert msg(excinfo.value) == expected_exc + repr(np.array([1.0 + 0.0j]))
# Exact matches:
assert m.overloaded4(np.array([1], dtype="double")) == "double"
assert m.overloaded4(np.array([1], dtype="longlong")) == "long long"
# Non-exact matches requiring conversion. Since float to integer isn't a
# save conversion, it should go to the double overload, but short can go to
# either (and so should end up on the first-registered, the long long).
assert m.overloaded4(np.array([1], dtype="float32")) == "double"
assert m.overloaded4(np.array([1], dtype="short")) == "long long"
assert m.overloaded5(np.array([1], dtype="double")) == "double"
assert m.overloaded5(np.array([1], dtype="uintc")) == "unsigned int"
assert m.overloaded5(np.array([1], dtype="float32")) == "unsigned int"
def test_greedy_string_overload():
"""Tests fix for #685 - ndarray shouldn't go to std::string overload"""
assert m.issue685("abc") == "string"
assert m.issue685(np.array([97, 98, 99], dtype="b")) == "array"
assert m.issue685(123) == "other"
def test_array_unchecked_fixed_dims(msg):
z1 = np.array([[1, 2], [3, 4]], dtype="float64")
m.proxy_add2(z1, 10)
assert np.all(z1 == [[11, 12], [13, 14]])
with pytest.raises(ValueError) as excinfo:
m.proxy_add2(np.array([1.0, 2, 3]), 5.0)
assert (msg(excinfo.value) ==
"array has incorrect number of dimensions: 1; expected 2")
expect_c = np.ndarray(
shape=(3, 3, 3), buffer=np.array(range(3, 30)), dtype="int")
assert np.all(m.proxy_init3(3.0) == expect_c)
expect_f = np.transpose(expect_c)
assert np.all(m.proxy_init3F(3.0) == expect_f)
assert m.proxy_squared_L2_norm(np.array(range(6))) == 55
assert m.proxy_squared_L2_norm(np.array(range(6), dtype="float64")) == 55
assert m.proxy_auxiliaries2(z1) == [11, 11, True, 2, 8, 2, 2, 4, 32]
assert m.proxy_auxiliaries2(z1) == m.array_auxiliaries2(z1)
assert m.proxy_auxiliaries1_const_ref(z1[0, :])
assert m.proxy_auxiliaries2_const_ref(z1)
def test_array_unchecked_dyn_dims():
z1 = np.array([[1, 2], [3, 4]], dtype="float64")
m.proxy_add2_dyn(z1, 10)
assert np.all(z1 == [[11, 12], [13, 14]])
expect_c = np.ndarray(
shape=(3, 3, 3), buffer=np.array(range(3, 30)), dtype="int")
assert np.all(m.proxy_init3_dyn(3.0) == expect_c)
assert m.proxy_auxiliaries2_dyn(z1) == [11, 11, True, 2, 8, 2, 2, 4, 32]
assert m.proxy_auxiliaries2_dyn(z1) == m.array_auxiliaries2(z1)
def test_array_failure():
with pytest.raises(ValueError) as excinfo:
m.array_fail_test()
assert str(
excinfo.value) == "cannot create a pybind11::array from a nullptr"
with pytest.raises(ValueError) as excinfo:
m.array_t_fail_test()
assert str(
excinfo.value) == "cannot create a pybind11::array_t from a nullptr"
with pytest.raises(ValueError) as excinfo:
m.array_fail_test_negative_size()
assert str(excinfo.value) == "negative dimensions are not allowed"
def test_initializer_list():
assert m.array_initializer_list1().shape == (1, )
assert m.array_initializer_list2().shape == (1, 2)
assert m.array_initializer_list3().shape == (1, 2, 3)
assert m.array_initializer_list4().shape == (1, 2, 3, 4)
def test_array_resize():
a = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9], dtype="float64")
m.array_reshape2(a)
assert a.size == 9
assert np.all(a == [[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# total size change should succced with refcheck off
m.array_resize3(a, 4, False)
assert a.size == 64
# ... and fail with refcheck on
try:
m.array_resize3(a, 3, True)
except ValueError as e:
assert str(e).startswith("cannot resize an array")
# transposed array doesn't own data
b = a.transpose()
try:
m.array_resize3(b, 3, False)
except ValueError as e:
assert str(e).startswith(
"cannot resize this array: it does not own its data")
# ... but reshape should be fine
m.array_reshape2(b)
assert b.shape == (8, 8)
@pytest.mark.xfail("env.PYPY")
def test_array_create_and_resize():
a = m.create_and_resize(2)
assert a.size == 4
assert np.all(a == 42.0)
def test_array_view():
a = np.ones(100 * 4).astype("uint8")
a_float_view = m.array_view(a, "float32")
assert a_float_view.shape == (100 * 1, ) # 1 / 4 bytes = 8 / 32
a_int16_view = m.array_view(a, "int16") # 1 / 2 bytes = 16 / 32
assert a_int16_view.shape == (100 * 2, )
def test_array_view_invalid():
a = np.ones(100 * 4).astype("uint8")
with pytest.raises(TypeError):
m.array_view(a, "deadly_dtype")
def test_reshape_initializer_list():
a = np.arange(2 * 7 * 3) + 1
x = m.reshape_initializer_list(a, 2, 7, 3)
assert x.shape == (2, 7, 3)
assert list(x[1][4]) == [34, 35, 36]
with pytest.raises(ValueError) as excinfo:
m.reshape_initializer_list(a, 1, 7, 3)
assert str(
excinfo.value) == "cannot reshape array of size 42 into shape (1,7,3)"
def test_reshape_tuple():
a = np.arange(3 * 7 * 2) + 1
x = m.reshape_tuple(a, (3, 7, 2))
assert x.shape == (3, 7, 2)
assert list(x[1][4]) == [23, 24]
y = m.reshape_tuple(x, (x.size, ))
assert y.shape == (42, )
with pytest.raises(ValueError) as excinfo:
m.reshape_tuple(a, (3, 7, 1))
assert str(
excinfo.value) == "cannot reshape array of size 42 into shape (3,7,1)"
with pytest.raises(ValueError) as excinfo:
m.reshape_tuple(a, ())
assert str(
excinfo.value) == "cannot reshape array of size 42 into shape ()"
def test_index_using_ellipsis():
a = m.index_using_ellipsis(np.zeros((5, 6, 7)))
assert a.shape == (6, )
@pytest.mark.parametrize(
"test_func",
[
m.test_fmt_desc_float,
m.test_fmt_desc_double,
m.test_fmt_desc_const_float,
m.test_fmt_desc_const_double,
], )
def test_format_descriptors_for_floating_point_types(test_func):
assert "numpy.ndarray[numpy.float" in test_func.__doc__
@pytest.mark.parametrize("forcecast", [False, True])
@pytest.mark.parametrize("contiguity", [None, "C", "F"])
@pytest.mark.parametrize("noconvert", [False, True])
@pytest.mark.filterwarnings(
"ignore:Casting complex values to real discards the imaginary part:numpy.ComplexWarning"
)
def test_argument_conversions(forcecast, contiguity, noconvert):
function_name = "accept_double"
if contiguity == "C":
function_name += "_c_style"
elif contiguity == "F":
function_name += "_f_style"
if forcecast:
function_name += "_forcecast"
if noconvert:
function_name += "_noconvert"
function = getattr(m, function_name)
for dtype in [
np.dtype("float32"), np.dtype("float64"), np.dtype("complex128")
]:
for order in ["C", "F"]:
for shape in [(2, 2), (1, 3, 1, 1), (1, 1, 1), (0, )]:
if not noconvert:
# If noconvert is not passed, only complex128 needs to be truncated and
# "cannot be safely obtained". So without `forcecast`, the argument shouldn't
# be accepted.
should_raise = dtype.name == "complex128" and not forcecast
else:
# If noconvert is passed, only float64 and the matching order is accepted.
# If at most one dimension has a size greater than 1, the array is also
# trivially contiguous.
trivially_contiguous = sum(1 for d in shape if d > 1) <= 1
should_raise = dtype.name != "float64" or (
contiguity is not None and contiguity != order and
not trivially_contiguous)
array = np.zeros(shape, dtype=dtype, order=order)
if not should_raise:
function(array)
else:
with pytest.raises(
TypeError,
match="incompatible function arguments"):
function(array)
@pytest.mark.xfail("env.PYPY")
def test_dtype_refcount_leak():
from sys import getrefcount
dtype = np.dtype(np.float_)
a = np.array([1], dtype=dtype)
before = getrefcount(dtype)
m.ndim(a)
after = getrefcount(dtype)
assert after == before

View File

@@ -0,0 +1,614 @@
/*
tests/test_numpy_dtypes.cpp -- Structured and compound NumPy dtypes
Copyright (c) 2016 Ivan Smirnov
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/numpy.h>
#include "pybind11_tests.h"
#ifdef __GNUC__
# define PYBIND11_PACKED(cls) cls __attribute__((__packed__))
#else
# define PYBIND11_PACKED(cls) __pragma(pack(push, 1)) cls __pragma(pack(pop))
#endif
namespace py = pybind11;
struct SimpleStruct {
bool bool_;
uint32_t uint_;
float float_;
long double ldbl_;
};
std::ostream &operator<<(std::ostream &os, const SimpleStruct &v) {
return os << "s:" << v.bool_ << "," << v.uint_ << "," << v.float_ << "," << v.ldbl_;
}
struct SimpleStructReordered {
bool bool_;
float float_;
uint32_t uint_;
long double ldbl_;
};
PYBIND11_PACKED(struct PackedStruct {
bool bool_;
uint32_t uint_;
float float_;
long double ldbl_;
});
std::ostream &operator<<(std::ostream &os, const PackedStruct &v) {
return os << "p:" << v.bool_ << "," << v.uint_ << "," << v.float_ << "," << v.ldbl_;
}
PYBIND11_PACKED(struct NestedStruct {
SimpleStruct a;
PackedStruct b;
});
std::ostream &operator<<(std::ostream &os, const NestedStruct &v) {
return os << "n:a=" << v.a << ";b=" << v.b;
}
struct PartialStruct {
bool bool_;
uint32_t uint_;
float float_;
uint64_t dummy2;
long double ldbl_;
};
struct PartialNestedStruct {
uint64_t dummy1;
PartialStruct a;
uint64_t dummy2;
};
struct UnboundStruct {};
struct StringStruct {
char a[3];
std::array<char, 3> b;
};
struct ComplexStruct {
std::complex<float> cflt;
std::complex<double> cdbl;
};
std::ostream &operator<<(std::ostream &os, const ComplexStruct &v) {
return os << "c:" << v.cflt << "," << v.cdbl;
}
struct ArrayStruct {
char a[3][4];
int32_t b[2];
std::array<uint8_t, 3> c;
std::array<float, 2> d[4];
};
PYBIND11_PACKED(struct StructWithUglyNames {
int8_t __x__;
uint64_t __y__;
});
enum class E1 : int64_t { A = -1, B = 1 };
enum E2 : uint8_t { X = 1, Y = 2 };
PYBIND11_PACKED(struct EnumStruct {
E1 e1;
E2 e2;
});
std::ostream &operator<<(std::ostream &os, const StringStruct &v) {
os << "a='";
for (size_t i = 0; i < 3 && (v.a[i] != 0); i++) {
os << v.a[i];
}
os << "',b='";
for (size_t i = 0; i < 3 && (v.b[i] != 0); i++) {
os << v.b[i];
}
return os << "'";
}
std::ostream &operator<<(std::ostream &os, const ArrayStruct &v) {
os << "a={";
for (int i = 0; i < 3; i++) {
if (i > 0) {
os << ',';
}
os << '{';
for (int j = 0; j < 3; j++) {
os << v.a[i][j] << ',';
}
os << v.a[i][3] << '}';
}
os << "},b={" << v.b[0] << ',' << v.b[1];
os << "},c={" << int(v.c[0]) << ',' << int(v.c[1]) << ',' << int(v.c[2]);
os << "},d={";
for (int i = 0; i < 4; i++) {
if (i > 0) {
os << ',';
}
os << '{' << v.d[i][0] << ',' << v.d[i][1] << '}';
}
return os << '}';
}
std::ostream &operator<<(std::ostream &os, const EnumStruct &v) {
return os << "e1=" << (v.e1 == E1::A ? "A" : "B") << ",e2=" << (v.e2 == E2::X ? "X" : "Y");
}
template <typename T>
py::array mkarray_via_buffer(size_t n) {
return py::array(py::buffer_info(
nullptr, sizeof(T), py::format_descriptor<T>::format(), 1, {n}, {sizeof(T)}));
}
#define SET_TEST_VALS(s, i) \
do { \
(s).bool_ = (i) % 2 != 0; \
(s).uint_ = (uint32_t) (i); \
(s).float_ = (float) (i) *1.5f; \
(s).ldbl_ = (long double) (i) * -2.5L; \
} while (0)
template <typename S>
py::array_t<S, 0> create_recarray(size_t n) {
auto arr = mkarray_via_buffer<S>(n);
auto req = arr.request();
auto *ptr = static_cast<S *>(req.ptr);
for (size_t i = 0; i < n; i++) {
SET_TEST_VALS(ptr[i], i);
}
return arr;
}
template <typename S>
py::list print_recarray(py::array_t<S, 0> arr) {
const auto req = arr.request();
auto *const ptr = static_cast<S *>(req.ptr);
auto l = py::list();
for (py::ssize_t i = 0; i < req.size; i++) {
std::stringstream ss;
ss << ptr[i];
l.append(py::str(ss.str()));
}
return l;
}
py::array_t<int32_t, 0> test_array_ctors(int i) {
using arr_t = py::array_t<int32_t, 0>;
std::vector<int32_t> data{1, 2, 3, 4, 5, 6};
std::vector<py::ssize_t> shape{3, 2};
std::vector<py::ssize_t> strides{8, 4};
auto *ptr = data.data();
auto *vptr = (void *) ptr;
auto dtype = py::dtype("int32");
py::buffer_info buf_ndim1(vptr, 4, "i", 6);
py::buffer_info buf_ndim1_null(nullptr, 4, "i", 6);
py::buffer_info buf_ndim2(vptr, 4, "i", 2, shape, strides);
py::buffer_info buf_ndim2_null(nullptr, 4, "i", 2, shape, strides);
auto fill = [](py::array arr) {
auto req = arr.request();
for (int i = 0; i < 6; i++) {
((int32_t *) req.ptr)[i] = i + 1;
}
return arr;
};
switch (i) {
// shape: (3, 2)
case 10:
return arr_t(shape, strides, ptr);
case 11:
return py::array(shape, strides, ptr);
case 12:
return py::array(dtype, shape, strides, vptr);
case 13:
return arr_t(shape, ptr);
case 14:
return py::array(shape, ptr);
case 15:
return py::array(dtype, shape, vptr);
case 16:
return arr_t(buf_ndim2);
case 17:
return py::array(buf_ndim2);
// shape: (3, 2) - post-fill
case 20:
return fill(arr_t(shape, strides));
case 21:
return py::array(shape, strides, ptr); // can't have nullptr due to templated ctor
case 22:
return fill(py::array(dtype, shape, strides));
case 23:
return fill(arr_t(shape));
case 24:
return py::array(shape, ptr); // can't have nullptr due to templated ctor
case 25:
return fill(py::array(dtype, shape));
case 26:
return fill(arr_t(buf_ndim2_null));
case 27:
return fill(py::array(buf_ndim2_null));
// shape: (6, )
case 30:
return arr_t(6, ptr);
case 31:
return py::array(6, ptr);
case 32:
return py::array(dtype, 6, vptr);
case 33:
return arr_t(buf_ndim1);
case 34:
return py::array(buf_ndim1);
// shape: (6, )
case 40:
return fill(arr_t(6));
case 41:
return py::array(6, ptr); // can't have nullptr due to templated ctor
case 42:
return fill(py::array(dtype, 6));
case 43:
return fill(arr_t(buf_ndim1_null));
case 44:
return fill(py::array(buf_ndim1_null));
}
return arr_t();
}
py::list test_dtype_ctors() {
py::list list;
list.append(py::dtype("int32"));
list.append(py::dtype(std::string("float64")));
list.append(py::dtype::from_args(py::str("bool")));
py::list names, offsets, formats;
py::dict dict;
names.append(py::str("a"));
names.append(py::str("b"));
dict["names"] = names;
offsets.append(py::int_(1));
offsets.append(py::int_(10));
dict["offsets"] = offsets;
formats.append(py::dtype("int32"));
formats.append(py::dtype("float64"));
dict["formats"] = formats;
dict["itemsize"] = py::int_(20);
list.append(py::dtype::from_args(dict));
list.append(py::dtype(names, formats, offsets, 20));
list.append(py::dtype(py::buffer_info((void *) nullptr, sizeof(unsigned int), "I", 1)));
list.append(py::dtype(py::buffer_info((void *) nullptr, 0, "T{i:a:f:b:}", 1)));
list.append(py::dtype(py::detail::npy_api::NPY_DOUBLE_));
return list;
}
struct A {};
struct B {};
TEST_SUBMODULE(numpy_dtypes, m) {
try {
py::module_::import("numpy");
} catch (...) {
return;
}
// typeinfo may be registered before the dtype descriptor for scalar casts to work...
py::class_<SimpleStruct>(m, "SimpleStruct")
// Explicit construct to ensure zero-valued initialization.
.def(py::init([]() { return SimpleStruct(); }))
.def_readwrite("bool_", &SimpleStruct::bool_)
.def_readwrite("uint_", &SimpleStruct::uint_)
.def_readwrite("float_", &SimpleStruct::float_)
.def_readwrite("ldbl_", &SimpleStruct::ldbl_)
.def("astuple",
[](const SimpleStruct &self) {
return py::make_tuple(self.bool_, self.uint_, self.float_, self.ldbl_);
})
.def_static("fromtuple", [](const py::tuple &tup) {
if (py::len(tup) != 4) {
throw py::cast_error("Invalid size");
}
return SimpleStruct{tup[0].cast<bool>(),
tup[1].cast<uint32_t>(),
tup[2].cast<float>(),
tup[3].cast<long double>()};
});
PYBIND11_NUMPY_DTYPE(SimpleStruct, bool_, uint_, float_, ldbl_);
PYBIND11_NUMPY_DTYPE(SimpleStructReordered, bool_, uint_, float_, ldbl_);
PYBIND11_NUMPY_DTYPE(PackedStruct, bool_, uint_, float_, ldbl_);
PYBIND11_NUMPY_DTYPE(NestedStruct, a, b);
PYBIND11_NUMPY_DTYPE(PartialStruct, bool_, uint_, float_, ldbl_);
PYBIND11_NUMPY_DTYPE(PartialNestedStruct, a);
PYBIND11_NUMPY_DTYPE(StringStruct, a, b);
PYBIND11_NUMPY_DTYPE(ArrayStruct, a, b, c, d);
PYBIND11_NUMPY_DTYPE(EnumStruct, e1, e2);
PYBIND11_NUMPY_DTYPE(ComplexStruct, cflt, cdbl);
// ... or after
py::class_<PackedStruct>(m, "PackedStruct");
PYBIND11_NUMPY_DTYPE_EX(StructWithUglyNames, __x__, "x", __y__, "y");
#ifdef PYBIND11_NEVER_DEFINED_EVER
// If enabled, this should produce a static_assert failure telling the user that the struct
// is not a POD type
struct NotPOD {
std::string v;
NotPOD() : v("hi"){};
};
PYBIND11_NUMPY_DTYPE(NotPOD, v);
#endif
// Check that dtypes can be registered programmatically, both from
// initializer lists of field descriptors and from other containers.
py::detail::npy_format_descriptor<A>::register_dtype({});
py::detail::npy_format_descriptor<B>::register_dtype(
std::vector<py::detail::field_descriptor>{});
// test_recarray, test_scalar_conversion
m.def("create_rec_simple", &create_recarray<SimpleStruct>);
m.def("create_rec_packed", &create_recarray<PackedStruct>);
m.def("create_rec_nested", [](size_t n) { // test_signature
py::array_t<NestedStruct, 0> arr = mkarray_via_buffer<NestedStruct>(n);
auto req = arr.request();
auto *ptr = static_cast<NestedStruct *>(req.ptr);
for (size_t i = 0; i < n; i++) {
SET_TEST_VALS(ptr[i].a, i);
SET_TEST_VALS(ptr[i].b, i + 1);
}
return arr;
});
m.def("create_rec_partial", &create_recarray<PartialStruct>);
m.def("create_rec_partial_nested", [](size_t n) {
py::array_t<PartialNestedStruct, 0> arr = mkarray_via_buffer<PartialNestedStruct>(n);
auto req = arr.request();
auto *ptr = static_cast<PartialNestedStruct *>(req.ptr);
for (size_t i = 0; i < n; i++) {
SET_TEST_VALS(ptr[i].a, i);
}
return arr;
});
m.def("print_rec_simple", &print_recarray<SimpleStruct>);
m.def("print_rec_packed", &print_recarray<PackedStruct>);
m.def("print_rec_nested", &print_recarray<NestedStruct>);
// test_format_descriptors
m.def("get_format_unbound", []() { return py::format_descriptor<UnboundStruct>::format(); });
m.def("print_format_descriptors", []() {
py::list l;
for (const auto &fmt : {py::format_descriptor<SimpleStruct>::format(),
py::format_descriptor<PackedStruct>::format(),
py::format_descriptor<NestedStruct>::format(),
py::format_descriptor<PartialStruct>::format(),
py::format_descriptor<PartialNestedStruct>::format(),
py::format_descriptor<StringStruct>::format(),
py::format_descriptor<ArrayStruct>::format(),
py::format_descriptor<EnumStruct>::format(),
py::format_descriptor<ComplexStruct>::format()}) {
l.append(py::cast(fmt));
}
return l;
});
// test_dtype
std::vector<const char *> dtype_names{
"byte", "short", "intc", "int_", "longlong", "ubyte", "ushort",
"uintc", "uint", "ulonglong", "half", "single", "double", "longdouble",
"csingle", "cdouble", "clongdouble", "bool_", "datetime64", "timedelta64", "object_"};
m.def("print_dtypes", []() {
py::list l;
for (const py::handle &d : {py::dtype::of<SimpleStruct>(),
py::dtype::of<PackedStruct>(),
py::dtype::of<NestedStruct>(),
py::dtype::of<PartialStruct>(),
py::dtype::of<PartialNestedStruct>(),
py::dtype::of<StringStruct>(),
py::dtype::of<ArrayStruct>(),
py::dtype::of<EnumStruct>(),
py::dtype::of<StructWithUglyNames>(),
py::dtype::of<ComplexStruct>()}) {
l.append(py::str(d));
}
return l;
});
m.def("test_dtype_ctors", &test_dtype_ctors);
m.def("test_dtype_kind", [dtype_names]() {
py::list list;
for (const auto &dt_name : dtype_names) {
list.append(py::dtype(dt_name).kind());
}
return list;
});
m.def("test_dtype_char_", [dtype_names]() {
py::list list;
for (const auto &dt_name : dtype_names) {
list.append(py::dtype(dt_name).char_());
}
return list;
});
m.def("test_dtype_num", [dtype_names]() {
py::list list;
for (const auto &dt_name : dtype_names) {
list.append(py::dtype(dt_name).num());
}
return list;
});
m.def("test_dtype_byteorder", [dtype_names]() {
py::list list;
for (const auto &dt_name : dtype_names) {
list.append(py::dtype(dt_name).byteorder());
}
return list;
});
m.def("test_dtype_alignment", [dtype_names]() {
py::list list;
for (const auto &dt_name : dtype_names) {
list.append(py::dtype(dt_name).alignment());
}
return list;
});
m.def("test_dtype_flags", [dtype_names]() {
py::list list;
for (const auto &dt_name : dtype_names) {
list.append(py::dtype(dt_name).flags());
}
return list;
});
m.def("test_dtype_methods", []() {
py::list list;
auto dt1 = py::dtype::of<int32_t>();
auto dt2 = py::dtype::of<SimpleStruct>();
list.append(dt1);
list.append(dt2);
list.append(py::bool_(dt1.has_fields()));
list.append(py::bool_(dt2.has_fields()));
list.append(py::int_(dt1.itemsize()));
list.append(py::int_(dt2.itemsize()));
return list;
});
struct TrailingPaddingStruct {
int32_t a;
char b;
};
PYBIND11_NUMPY_DTYPE(TrailingPaddingStruct, a, b);
m.def("trailing_padding_dtype", []() { return py::dtype::of<TrailingPaddingStruct>(); });
// test_string_array
m.def("create_string_array", [](bool non_empty) {
py::array_t<StringStruct, 0> arr = mkarray_via_buffer<StringStruct>(non_empty ? 4 : 0);
if (non_empty) {
auto req = arr.request();
auto *ptr = static_cast<StringStruct *>(req.ptr);
for (py::ssize_t i = 0; i < req.size * req.itemsize; i++) {
static_cast<char *>(req.ptr)[i] = 0;
}
ptr[1].a[0] = 'a';
ptr[1].b[0] = 'a';
ptr[2].a[0] = 'a';
ptr[2].b[0] = 'a';
ptr[3].a[0] = 'a';
ptr[3].b[0] = 'a';
ptr[2].a[1] = 'b';
ptr[2].b[1] = 'b';
ptr[3].a[1] = 'b';
ptr[3].b[1] = 'b';
ptr[3].a[2] = 'c';
ptr[3].b[2] = 'c';
}
return arr;
});
m.def("print_string_array", &print_recarray<StringStruct>);
// test_array_array
m.def("create_array_array", [](size_t n) {
py::array_t<ArrayStruct, 0> arr = mkarray_via_buffer<ArrayStruct>(n);
auto *ptr = (ArrayStruct *) arr.mutable_data();
for (size_t i = 0; i < n; i++) {
for (size_t j = 0; j < 3; j++) {
for (size_t k = 0; k < 4; k++) {
ptr[i].a[j][k] = char('A' + (i * 100 + j * 10 + k) % 26);
}
}
for (size_t j = 0; j < 2; j++) {
ptr[i].b[j] = int32_t(i * 1000 + j);
}
for (size_t j = 0; j < 3; j++) {
ptr[i].c[j] = uint8_t(i * 10 + j);
}
for (size_t j = 0; j < 4; j++) {
for (size_t k = 0; k < 2; k++) {
ptr[i].d[j][k] = float(i) * 100.0f + float(j) * 10.0f + float(k);
}
}
}
return arr;
});
m.def("print_array_array", &print_recarray<ArrayStruct>);
// test_enum_array
m.def("create_enum_array", [](size_t n) {
py::array_t<EnumStruct, 0> arr = mkarray_via_buffer<EnumStruct>(n);
auto *ptr = (EnumStruct *) arr.mutable_data();
for (size_t i = 0; i < n; i++) {
ptr[i].e1 = static_cast<E1>(-1 + ((int) i % 2) * 2);
ptr[i].e2 = static_cast<E2>(1 + (i % 2));
}
return arr;
});
m.def("print_enum_array", &print_recarray<EnumStruct>);
// test_complex_array
m.def("create_complex_array", [](size_t n) {
py::array_t<ComplexStruct, 0> arr = mkarray_via_buffer<ComplexStruct>(n);
auto *ptr = (ComplexStruct *) arr.mutable_data();
for (size_t i = 0; i < n; i++) {
ptr[i].cflt.real(float(i));
ptr[i].cflt.imag(float(i) + 0.25f);
ptr[i].cdbl.real(double(i) + 0.5);
ptr[i].cdbl.imag(double(i) + 0.75);
}
return arr;
});
m.def("print_complex_array", &print_recarray<ComplexStruct>);
// test_array_constructors
m.def("test_array_ctors", &test_array_ctors);
// test_compare_buffer_info
struct CompareStruct {
bool x;
uint32_t y;
float z;
};
PYBIND11_NUMPY_DTYPE(CompareStruct, x, y, z);
m.def("compare_buffer_info", []() {
py::list list;
list.append(py::bool_(py::detail::compare_buffer_info<float>::compare(
py::buffer_info(nullptr, sizeof(float), "f", 1))));
list.append(py::bool_(py::detail::compare_buffer_info<unsigned>::compare(
py::buffer_info(nullptr, sizeof(int), "I", 1))));
list.append(py::bool_(py::detail::compare_buffer_info<long>::compare(
py::buffer_info(nullptr, sizeof(long), "l", 1))));
list.append(py::bool_(py::detail::compare_buffer_info<long>::compare(
py::buffer_info(nullptr, sizeof(long), sizeof(long) == sizeof(int) ? "i" : "q", 1))));
list.append(py::bool_(py::detail::compare_buffer_info<CompareStruct>::compare(
py::buffer_info(nullptr, sizeof(CompareStruct), "T{?:x:3xI:y:f:z:}", 1))));
return list;
});
m.def("buffer_to_dtype", [](py::buffer &buf) { return py::dtype(buf.request()); });
// test_scalar_conversion
auto f_simple = [](SimpleStruct s) { return s.uint_ * 10; };
m.def("f_simple", f_simple);
m.def("f_packed", [](PackedStruct s) { return s.uint_ * 10; });
m.def("f_nested", [](NestedStruct s) { return s.a.uint_ * 10; });
// test_vectorize
m.def("f_simple_vectorized", py::vectorize(f_simple));
auto f_simple_pass_thru = [](SimpleStruct s) { return s; };
m.def("f_simple_pass_thru_vectorized", py::vectorize(f_simple_pass_thru));
// test_register_dtype
m.def("register_dtype",
[]() { PYBIND11_NUMPY_DTYPE(SimpleStruct, bool_, uint_, float_, ldbl_); });
// test_str_leak
m.def("dtype_wrapper", [](const py::object &d) { return py::dtype::from_args(d); });
}

View File

@@ -0,0 +1,429 @@
import re
import pytest
import env # noqa: F401
from pybind11_tests import numpy_dtypes as m
np = pytest.importorskip("numpy")
@pytest.fixture(scope="module")
def simple_dtype():
ld = np.dtype("longdouble")
return np.dtype({
"names": ["bool_", "uint_", "float_", "ldbl_"],
"formats": ["?", "u4", "f4", f"f{ld.itemsize}"],
"offsets": [0, 4, 8, (16 if ld.alignment > 4 else 12)],
})
@pytest.fixture(scope="module")
def packed_dtype():
return np.dtype(
[("bool_", "?"), ("uint_", "u4"), ("float_", "f4"), ("ldbl_", "g")])
def dt_fmt():
from sys import byteorder
e = "<" if byteorder == "little" else ">"
return ("{{'names':['bool_','uint_','float_','ldbl_'],"
"'formats':['?','" + e + "u4','" + e + "f4','" + e + "f{}'],"
"'offsets':[0,4,8,{}],'itemsize':{}}}")
def simple_dtype_fmt():
ld = np.dtype("longdouble")
simple_ld_off = 12 + 4 * (ld.alignment > 4)
return dt_fmt().format(ld.itemsize, simple_ld_off,
simple_ld_off + ld.itemsize)
def packed_dtype_fmt():
from sys import byteorder
return "[('bool_','?'),('uint_','{e}u4'),('float_','{e}f4'),('ldbl_','{e}f{}')]".format(
np.dtype("longdouble").itemsize,
e="<" if byteorder == "little" else ">")
def partial_ld_offset():
return (12 + 4 * (np.dtype("uint64").alignment > 4) + 8 + 8 *
(np.dtype("longdouble").alignment > 8))
def partial_dtype_fmt():
ld = np.dtype("longdouble")
partial_ld_off = partial_ld_offset()
partial_size = partial_ld_off + ld.itemsize
partial_end_padding = partial_size % np.dtype("uint64").alignment
return dt_fmt().format(ld.itemsize, partial_ld_off,
partial_size + partial_end_padding)
def partial_nested_fmt():
ld = np.dtype("longdouble")
partial_nested_off = 8 + 8 * (ld.alignment > 8)
partial_ld_off = partial_ld_offset()
partial_size = partial_ld_off + ld.itemsize
partial_end_padding = partial_size % np.dtype("uint64").alignment
partial_nested_size = partial_nested_off * 2 + partial_size + partial_end_padding
return "{{'names':['a'],'formats':[{}],'offsets':[{}],'itemsize':{}}}".format(
partial_dtype_fmt(), partial_nested_off, partial_nested_size)
def assert_equal(actual, expected_data, expected_dtype):
np.testing.assert_equal(
actual, np.array(
expected_data, dtype=expected_dtype))
def test_format_descriptors():
with pytest.raises(RuntimeError) as excinfo:
m.get_format_unbound()
assert re.match("^NumPy type info missing for .*UnboundStruct.*$",
str(excinfo.value))
ld = np.dtype("longdouble")
ldbl_fmt = ("4x" if ld.alignment > 4 else "") + ld.char
ss_fmt = "^T{?:bool_:3xI:uint_:f:float_:" + ldbl_fmt + ":ldbl_:}"
dbl = np.dtype("double")
end_padding = ld.itemsize % np.dtype("uint64").alignment
partial_fmt = (
"^T{?:bool_:3xI:uint_:f:float_:" +
str(4 * (dbl.alignment > 4) + dbl.itemsize + 8 * (ld.alignment > 8)) +
"xg:ldbl_:" + (str(end_padding) + "x}" if end_padding > 0 else "}"))
nested_extra = str(max(8, ld.alignment))
assert m.print_format_descriptors() == [
ss_fmt,
"^T{?:bool_:I:uint_:f:float_:g:ldbl_:}",
"^T{" + ss_fmt + ":a:^T{?:bool_:I:uint_:f:float_:g:ldbl_:}:b:}",
partial_fmt,
"^T{" + nested_extra + "x" + partial_fmt + ":a:" + nested_extra + "x}",
"^T{3s:a:3s:b:}",
"^T{(3)4s:a:(2)i:b:(3)B:c:1x(4, 2)f:d:}",
"^T{q:e1:B:e2:}",
"^T{Zf:cflt:Zd:cdbl:}",
]
def test_dtype(simple_dtype):
from sys import byteorder
e = "<" if byteorder == "little" else ">"
assert [x.replace(" ", "") for x in m.print_dtypes()] == [
simple_dtype_fmt(),
packed_dtype_fmt(),
f"[('a',{simple_dtype_fmt()}),('b',{packed_dtype_fmt()})]",
partial_dtype_fmt(),
partial_nested_fmt(),
"[('a','S3'),('b','S3')]",
("{{'names':['a','b','c','d']," + "'formats':[('S4',(3,)),('" + e +
"i4',(2,)),('u1',(3,)),('" + e + "f4',(4,2))]," +
"'offsets':[0,12,20,24],'itemsize':56}}").format(e=e),
"[('e1','" + e + "i8'),('e2','u1')]",
"[('x','i1'),('y','" + e + "u8')]",
"[('cflt','" + e + "c8'),('cdbl','" + e + "c16')]",
]
d1 = np.dtype({
"names": ["a", "b"],
"formats": ["int32", "float64"],
"offsets": [1, 10],
"itemsize": 20,
})
d2 = np.dtype([("a", "i4"), ("b", "f4")])
assert m.test_dtype_ctors() == [
np.dtype("int32"),
np.dtype("float64"),
np.dtype("bool"),
d1,
d1,
np.dtype("uint32"),
d2,
np.dtype("d"),
]
assert m.test_dtype_methods() == [
np.dtype("int32"),
simple_dtype,
False,
True,
np.dtype("int32").itemsize,
simple_dtype.itemsize,
]
assert m.trailing_padding_dtype() == m.buffer_to_dtype(
np.zeros(1, m.trailing_padding_dtype()))
expected_chars = "bhilqBHILQefdgFDG?MmO"
assert m.test_dtype_kind() == list("iiiiiuuuuuffffcccbMmO")
assert m.test_dtype_char_() == list(expected_chars)
assert m.test_dtype_num() == [np.dtype(ch).num for ch in expected_chars]
assert m.test_dtype_byteorder(
) == [np.dtype(ch).byteorder for ch in expected_chars]
assert m.test_dtype_alignment(
) == [np.dtype(ch).alignment for ch in expected_chars]
assert m.test_dtype_flags(
) == [chr(np.dtype(ch).flags) for ch in expected_chars]
def test_recarray(simple_dtype, packed_dtype):
elements = [(False, 0, 0.0, -0.0), (True, 1, 1.5, -2.5), (False, 2, 3.0,
-5.0)]
for func, dtype in [
(m.create_rec_simple, simple_dtype),
(m.create_rec_packed, packed_dtype),
]:
arr = func(0)
assert arr.dtype == dtype
assert_equal(arr, [], simple_dtype)
assert_equal(arr, [], packed_dtype)
arr = func(3)
assert arr.dtype == dtype
assert_equal(arr, elements, simple_dtype)
assert_equal(arr, elements, packed_dtype)
# Show what recarray's look like in NumPy.
assert type(arr[0]) == np.void
assert type(arr[0].item()) == tuple
if dtype == simple_dtype:
assert m.print_rec_simple(arr) == [
"s:0,0,0,-0",
"s:1,1,1.5,-2.5",
"s:0,2,3,-5",
]
else:
assert m.print_rec_packed(arr) == [
"p:0,0,0,-0",
"p:1,1,1.5,-2.5",
"p:0,2,3,-5",
]
nested_dtype = np.dtype([("a", simple_dtype), ("b", packed_dtype)])
arr = m.create_rec_nested(0)
assert arr.dtype == nested_dtype
assert_equal(arr, [], nested_dtype)
arr = m.create_rec_nested(3)
assert arr.dtype == nested_dtype
assert_equal(
arr,
[
((False, 0, 0.0, -0.0), (True, 1, 1.5, -2.5)),
((True, 1, 1.5, -2.5), (False, 2, 3.0, -5.0)),
((False, 2, 3.0, -5.0), (True, 3, 4.5, -7.5)),
],
nested_dtype, )
assert m.print_rec_nested(arr) == [
"n:a=s:0,0,0,-0;b=p:1,1,1.5,-2.5",
"n:a=s:1,1,1.5,-2.5;b=p:0,2,3,-5",
"n:a=s:0,2,3,-5;b=p:1,3,4.5,-7.5",
]
arr = m.create_rec_partial(3)
assert str(arr.dtype).replace(" ", "") == partial_dtype_fmt()
partial_dtype = arr.dtype
assert "" not in arr.dtype.fields
assert partial_dtype.itemsize > simple_dtype.itemsize
assert_equal(arr, elements, simple_dtype)
assert_equal(arr, elements, packed_dtype)
arr = m.create_rec_partial_nested(3)
assert str(arr.dtype).replace(" ", "") == partial_nested_fmt()
assert "" not in arr.dtype.fields
assert "" not in arr.dtype.fields["a"][0].fields
assert arr.dtype.itemsize > partial_dtype.itemsize
np.testing.assert_equal(arr["a"], m.create_rec_partial(3))
def test_array_constructors():
data = np.arange(1, 7, dtype="int32")
for i in range(8):
np.testing.assert_array_equal(
m.test_array_ctors(10 + i), data.reshape((3, 2)))
np.testing.assert_array_equal(
m.test_array_ctors(20 + i), data.reshape((3, 2)))
for i in range(5):
np.testing.assert_array_equal(m.test_array_ctors(30 + i), data)
np.testing.assert_array_equal(m.test_array_ctors(40 + i), data)
def test_string_array():
arr = m.create_string_array(True)
assert str(arr.dtype) == "[('a', 'S3'), ('b', 'S3')]"
assert m.print_string_array(arr) == [
"a='',b=''",
"a='a',b='a'",
"a='ab',b='ab'",
"a='abc',b='abc'",
]
dtype = arr.dtype
assert arr["a"].tolist() == [b"", b"a", b"ab", b"abc"]
assert arr["b"].tolist() == [b"", b"a", b"ab", b"abc"]
arr = m.create_string_array(False)
assert dtype == arr.dtype
def test_array_array():
from sys import byteorder
e = "<" if byteorder == "little" else ">"
arr = m.create_array_array(3)
assert str(arr.dtype).replace(" ", "") == (
"{{'names':['a','b','c','d']," + "'formats':[('S4',(3,)),('" + e +
"i4',(2,)),('u1',(3,)),('{e}f4',(4,2))]," +
"'offsets':[0,12,20,24],'itemsize':56}}").format(e=e)
assert m.print_array_array(arr) == [
"a={{A,B,C,D},{K,L,M,N},{U,V,W,X}},b={0,1}," +
"c={0,1,2},d={{0,1},{10,11},{20,21},{30,31}}",
"a={{W,X,Y,Z},{G,H,I,J},{Q,R,S,T}},b={1000,1001}," +
"c={10,11,12},d={{100,101},{110,111},{120,121},{130,131}}",
"a={{S,T,U,V},{C,D,E,F},{M,N,O,P}},b={2000,2001}," +
"c={20,21,22},d={{200,201},{210,211},{220,221},{230,231}}",
]
assert arr["a"].tolist() == [
[b"ABCD", b"KLMN", b"UVWX"],
[b"WXYZ", b"GHIJ", b"QRST"],
[b"STUV", b"CDEF", b"MNOP"],
]
assert arr["b"].tolist() == [[0, 1], [1000, 1001], [2000, 2001]]
assert m.create_array_array(0).dtype == arr.dtype
def test_enum_array():
from sys import byteorder
e = "<" if byteorder == "little" else ">"
arr = m.create_enum_array(3)
dtype = arr.dtype
assert dtype == np.dtype([("e1", e + "i8"), ("e2", "u1")])
assert m.print_enum_array(arr) == ["e1=A,e2=X", "e1=B,e2=Y", "e1=A,e2=X"]
assert arr["e1"].tolist() == [-1, 1, -1]
assert arr["e2"].tolist() == [1, 2, 1]
assert m.create_enum_array(0).dtype == dtype
def test_complex_array():
from sys import byteorder
e = "<" if byteorder == "little" else ">"
arr = m.create_complex_array(3)
dtype = arr.dtype
assert dtype == np.dtype([("cflt", e + "c8"), ("cdbl", e + "c16")])
assert m.print_complex_array(arr) == [
"c:(0,0.25),(0.5,0.75)",
"c:(1,1.25),(1.5,1.75)",
"c:(2,2.25),(2.5,2.75)",
]
assert arr["cflt"].tolist() == [0.0 + 0.25j, 1.0 + 1.25j, 2.0 + 2.25j]
assert arr["cdbl"].tolist() == [0.5 + 0.75j, 1.5 + 1.75j, 2.5 + 2.75j]
assert m.create_complex_array(0).dtype == dtype
def test_signature(doc):
assert (doc(m.create_rec_nested) ==
"create_rec_nested(arg0: int) -> numpy.ndarray[NestedStruct]")
def test_scalar_conversion():
n = 3
arrays = [
m.create_rec_simple(n),
m.create_rec_packed(n),
m.create_rec_nested(n),
m.create_enum_array(n),
]
funcs = [m.f_simple, m.f_packed, m.f_nested]
for i, func in enumerate(funcs):
for j, arr in enumerate(arrays):
if i == j and i < 2:
assert [func(arr[k])
for k in range(n)] == [k * 10 for k in range(n)]
else:
with pytest.raises(TypeError) as excinfo:
func(arr[0])
assert "incompatible function arguments" in str(excinfo.value)
def test_vectorize():
n = 3
array = m.create_rec_simple(n)
values = m.f_simple_vectorized(array)
np.testing.assert_array_equal(values, [0, 10, 20])
array_2 = m.f_simple_pass_thru_vectorized(array)
np.testing.assert_array_equal(array, array_2)
def test_cls_and_dtype_conversion(simple_dtype):
s = m.SimpleStruct()
assert s.astuple() == (False, 0, 0.0, 0.0)
assert m.SimpleStruct.fromtuple(s.astuple()).astuple() == s.astuple()
s.uint_ = 2
assert m.f_simple(s) == 20
# Try as recarray of shape==(1,).
s_recarray = np.array([(False, 2, 0.0, 0.0)], dtype=simple_dtype)
# Show that this will work for vectorized case.
np.testing.assert_array_equal(m.f_simple_vectorized(s_recarray), [20])
# Show as a scalar that inherits from np.generic.
s_scalar = s_recarray[0]
assert isinstance(s_scalar, np.void)
assert m.f_simple(s_scalar) == 20
# Show that an *array* scalar (np.ndarray.shape == ()) does not convert.
# More specifically, conversion to SimpleStruct is not implicit.
s_recarray_scalar = s_recarray.reshape(())
assert isinstance(s_recarray_scalar, np.ndarray)
assert s_recarray_scalar.dtype == simple_dtype
with pytest.raises(TypeError) as excinfo:
m.f_simple(s_recarray_scalar)
assert "incompatible function arguments" in str(excinfo.value)
# Explicitly convert to m.SimpleStruct.
assert m.f_simple(m.SimpleStruct.fromtuple(s_recarray_scalar.item())) == 20
# Show that an array of dtype=object does *not* convert.
s_array_object = np.array([s])
assert s_array_object.dtype == object
with pytest.raises(TypeError) as excinfo:
m.f_simple_vectorized(s_array_object)
assert "incompatible function arguments" in str(excinfo.value)
# Explicitly convert to `np.array(..., dtype=simple_dtype)`
s_array = np.array([s.astuple()], dtype=simple_dtype)
np.testing.assert_array_equal(m.f_simple_vectorized(s_array), [20])
def test_register_dtype():
with pytest.raises(RuntimeError) as excinfo:
m.register_dtype()
assert "dtype is already registered" in str(excinfo.value)
@pytest.mark.xfail("env.PYPY")
def test_str_leak():
from sys import getrefcount
fmt = "f4"
pytest.gc_collect()
start = getrefcount(fmt)
d = m.dtype_wrapper(fmt)
assert d is np.dtype("f4")
del d
pytest.gc_collect()
assert getrefcount(fmt) == start
def test_compare_buffer_info():
assert all(m.compare_buffer_info())

View File

@@ -0,0 +1,107 @@
/*
tests/test_numpy_vectorize.cpp -- auto-vectorize functions over NumPy array
arguments
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/numpy.h>
#include "pybind11_tests.h"
#include <utility>
double my_func(int x, float y, double z) {
py::print("my_func(x:int={}, y:float={:.0f}, z:float={:.0f})"_s.format(x, y, z));
return (float) x * y * z;
}
TEST_SUBMODULE(numpy_vectorize, m) {
try {
py::module_::import("numpy");
} catch (...) {
return;
}
// test_vectorize, test_docs, test_array_collapse
// Vectorize all arguments of a function (though non-vector arguments are also allowed)
m.def("vectorized_func", py::vectorize(my_func));
// Vectorize a lambda function with a capture object (e.g. to exclude some arguments from the
// vectorization)
m.def("vectorized_func2", [](py::array_t<int> x, py::array_t<float> y, float z) {
return py::vectorize([z](int x, float y) { return my_func(x, y, z); })(std::move(x),
std::move(y));
});
// Vectorize a complex-valued function
m.def("vectorized_func3",
py::vectorize([](std::complex<double> c) { return c * std::complex<double>(2.f); }));
// test_type_selection
// NumPy function which only accepts specific data types
// A lot of these no lints could be replaced with const refs, and probably should at some
// point.
m.def("selective_func",
[](const py::array_t<int, py::array::c_style> &) { return "Int branch taken."; });
m.def("selective_func",
[](const py::array_t<float, py::array::c_style> &) { return "Float branch taken."; });
m.def("selective_func", [](const py::array_t<std::complex<float>, py::array::c_style> &) {
return "Complex float branch taken.";
});
// test_passthrough_arguments
// Passthrough test: references and non-pod types should be automatically passed through (in
// the function definition below, only `b`, `d`, and `g` are vectorized):
struct NonPODClass {
explicit NonPODClass(int v) : value{v} {}
int value;
};
py::class_<NonPODClass>(m, "NonPODClass")
.def(py::init<int>())
.def_readwrite("value", &NonPODClass::value);
m.def("vec_passthrough",
py::vectorize([](const double *a,
double b,
// Changing this broke things
// NOLINTNEXTLINE(performance-unnecessary-value-param)
py::array_t<double> c,
const int &d,
int &e,
NonPODClass f,
const double g) { return *a + b + c.at(0) + d + e + f.value + g; }));
// test_method_vectorization
struct VectorizeTestClass {
explicit VectorizeTestClass(int v) : value{v} {};
float method(int x, float y) const { return y + (float) (x + value); }
int value = 0;
};
py::class_<VectorizeTestClass> vtc(m, "VectorizeTestClass");
vtc.def(py::init<int>()).def_readwrite("value", &VectorizeTestClass::value);
// Automatic vectorizing of methods
vtc.def("method", py::vectorize(&VectorizeTestClass::method));
// test_trivial_broadcasting
// Internal optimization test for whether the input is trivially broadcastable:
py::enum_<py::detail::broadcast_trivial>(m, "trivial")
.value("f_trivial", py::detail::broadcast_trivial::f_trivial)
.value("c_trivial", py::detail::broadcast_trivial::c_trivial)
.value("non_trivial", py::detail::broadcast_trivial::non_trivial);
m.def("vectorized_is_trivial",
[](const py::array_t<int, py::array::forcecast> &arg1,
const py::array_t<float, py::array::forcecast> &arg2,
const py::array_t<double, py::array::forcecast> &arg3) {
py::ssize_t ndim = 0;
std::vector<py::ssize_t> shape;
std::array<py::buffer_info, 3> buffers{
{arg1.request(), arg2.request(), arg3.request()}};
return py::detail::broadcast(buffers, ndim, shape);
});
m.def("add_to", py::vectorize([](NonPODClass &x, int a) { x.value += a; }));
}

View File

@@ -0,0 +1,232 @@
import pytest
from pybind11_tests import numpy_vectorize as m
np = pytest.importorskip("numpy")
def test_vectorize(capture):
assert np.isclose(m.vectorized_func3(np.array(3 + 7j)), [6 + 14j])
for f in [m.vectorized_func, m.vectorized_func2]:
with capture:
assert np.isclose(f(1, 2, 3), 6)
assert capture == "my_func(x:int=1, y:float=2, z:float=3)"
with capture:
assert np.isclose(f(np.array(1), np.array(2), 3), 6)
assert capture == "my_func(x:int=1, y:float=2, z:float=3)"
with capture:
assert np.allclose(
f(np.array([1, 3]), np.array([2, 4]), 3), [6, 36])
assert (capture == """
my_func(x:int=1, y:float=2, z:float=3)
my_func(x:int=3, y:float=4, z:float=3)
""")
with capture:
a = np.array([[1, 2], [3, 4]], order="F")
b = np.array([[10, 20], [30, 40]], order="F")
c = 3
result = f(a, b, c)
assert np.allclose(result, a * b * c)
assert result.flags.f_contiguous
# All inputs are F order and full or singletons, so we the result is in col-major order:
assert (capture == """
my_func(x:int=1, y:float=10, z:float=3)
my_func(x:int=3, y:float=30, z:float=3)
my_func(x:int=2, y:float=20, z:float=3)
my_func(x:int=4, y:float=40, z:float=3)
""")
with capture:
a, b, c = (
np.array([[1, 3, 5], [7, 9, 11]]),
np.array([[2, 4, 6], [8, 10, 12]]),
3, )
assert np.allclose(f(a, b, c), a * b * c)
assert (capture == """
my_func(x:int=1, y:float=2, z:float=3)
my_func(x:int=3, y:float=4, z:float=3)
my_func(x:int=5, y:float=6, z:float=3)
my_func(x:int=7, y:float=8, z:float=3)
my_func(x:int=9, y:float=10, z:float=3)
my_func(x:int=11, y:float=12, z:float=3)
""")
with capture:
a, b, c = np.array([[1, 2, 3], [4, 5, 6]]), np.array([2, 3, 4]), 2
assert np.allclose(f(a, b, c), a * b * c)
assert (capture == """
my_func(x:int=1, y:float=2, z:float=2)
my_func(x:int=2, y:float=3, z:float=2)
my_func(x:int=3, y:float=4, z:float=2)
my_func(x:int=4, y:float=2, z:float=2)
my_func(x:int=5, y:float=3, z:float=2)
my_func(x:int=6, y:float=4, z:float=2)
""")
with capture:
a, b, c = np.array([[1, 2, 3], [4, 5, 6]]), np.array([[2], [3]]), 2
assert np.allclose(f(a, b, c), a * b * c)
assert (capture == """
my_func(x:int=1, y:float=2, z:float=2)
my_func(x:int=2, y:float=2, z:float=2)
my_func(x:int=3, y:float=2, z:float=2)
my_func(x:int=4, y:float=3, z:float=2)
my_func(x:int=5, y:float=3, z:float=2)
my_func(x:int=6, y:float=3, z:float=2)
""")
with capture:
a, b, c = (
np.array(
[[1, 2, 3], [4, 5, 6]], order="F"),
np.array([[2], [3]]),
2, )
assert np.allclose(f(a, b, c), a * b * c)
assert (capture == """
my_func(x:int=1, y:float=2, z:float=2)
my_func(x:int=2, y:float=2, z:float=2)
my_func(x:int=3, y:float=2, z:float=2)
my_func(x:int=4, y:float=3, z:float=2)
my_func(x:int=5, y:float=3, z:float=2)
my_func(x:int=6, y:float=3, z:float=2)
""")
with capture:
a, b, c = np.array([[1, 2, 3], [4, 5, 6]])[::, ::2], np.array(
[[2], [3]]), 2
assert np.allclose(f(a, b, c), a * b * c)
assert (capture == """
my_func(x:int=1, y:float=2, z:float=2)
my_func(x:int=3, y:float=2, z:float=2)
my_func(x:int=4, y:float=3, z:float=2)
my_func(x:int=6, y:float=3, z:float=2)
""")
with capture:
a, b, c = (
np.array(
[[1, 2, 3], [4, 5, 6]], order="F")[::, ::2],
np.array([[2], [3]]),
2, )
assert np.allclose(f(a, b, c), a * b * c)
assert (capture == """
my_func(x:int=1, y:float=2, z:float=2)
my_func(x:int=3, y:float=2, z:float=2)
my_func(x:int=4, y:float=3, z:float=2)
my_func(x:int=6, y:float=3, z:float=2)
""")
def test_type_selection():
assert m.selective_func(np.array(
[1], dtype=np.int32)) == "Int branch taken."
assert m.selective_func(np.array(
[1.0], dtype=np.float32)) == "Float branch taken."
assert (m.selective_func(np.array(
[1.0j], dtype=np.complex64)) == "Complex float branch taken.")
def test_docs(doc):
assert (doc(m.vectorized_func) == """
vectorized_func(arg0: numpy.ndarray[numpy.int32], arg1: numpy.ndarray[numpy.float32], arg2: numpy.ndarray[numpy.float64]) -> object
"""
# noqa: E501 line too long
)
def test_trivial_broadcasting():
trivial, vectorized_is_trivial = m.trivial, m.vectorized_is_trivial
assert vectorized_is_trivial(1, 2, 3) == trivial.c_trivial
assert vectorized_is_trivial(np.array(1), np.array(2),
3) == trivial.c_trivial
assert (vectorized_is_trivial(np.array([1, 3]), np.array([2, 4]), 3) ==
trivial.c_trivial)
assert trivial.c_trivial == vectorized_is_trivial(
np.array([[1, 3, 5], [7, 9, 11]]),
np.array([[2, 4, 6], [8, 10, 12]]), 3)
assert (vectorized_is_trivial(
np.array([[1, 2, 3], [4, 5, 6]]), np.array([2, 3, 4]), 2) ==
trivial.non_trivial)
assert (vectorized_is_trivial(
np.array([[1, 2, 3], [4, 5, 6]]), np.array([[2], [3]]), 2) ==
trivial.non_trivial)
z1 = np.array([[1, 2, 3, 4], [5, 6, 7, 8]], dtype="int32")
z2 = np.array(z1, dtype="float32")
z3 = np.array(z1, dtype="float64")
assert vectorized_is_trivial(z1, z2, z3) == trivial.c_trivial
assert vectorized_is_trivial(1, z2, z3) == trivial.c_trivial
assert vectorized_is_trivial(z1, 1, z3) == trivial.c_trivial
assert vectorized_is_trivial(z1, z2, 1) == trivial.c_trivial
assert vectorized_is_trivial(z1[::2, ::2], 1, 1) == trivial.non_trivial
assert vectorized_is_trivial(1, 1, z1[::2, ::2]) == trivial.c_trivial
assert vectorized_is_trivial(1, 1, z3[::2, ::2]) == trivial.non_trivial
assert vectorized_is_trivial(z1, 1, z3[1::4, 1::4]) == trivial.c_trivial
y1 = np.array(z1, order="F")
y2 = np.array(y1)
y3 = np.array(y1)
assert vectorized_is_trivial(y1, y2, y3) == trivial.f_trivial
assert vectorized_is_trivial(y1, 1, 1) == trivial.f_trivial
assert vectorized_is_trivial(1, y2, 1) == trivial.f_trivial
assert vectorized_is_trivial(1, 1, y3) == trivial.f_trivial
assert vectorized_is_trivial(y1, z2, 1) == trivial.non_trivial
assert vectorized_is_trivial(z1[1::4, 1::4], y2, 1) == trivial.f_trivial
assert vectorized_is_trivial(y1[1::4, 1::4], z2, 1) == trivial.c_trivial
assert m.vectorized_func(z1, z2, z3).flags.c_contiguous
assert m.vectorized_func(y1, y2, y3).flags.f_contiguous
assert m.vectorized_func(z1, 1, 1).flags.c_contiguous
assert m.vectorized_func(1, y2, 1).flags.f_contiguous
assert m.vectorized_func(z1[1::4, 1::4], y2, 1).flags.f_contiguous
assert m.vectorized_func(y1[1::4, 1::4], z2, 1).flags.c_contiguous
def test_passthrough_arguments(doc):
assert doc(m.vec_passthrough) == ("vec_passthrough(" + ", ".join([
"arg0: float",
"arg1: numpy.ndarray[numpy.float64]",
"arg2: numpy.ndarray[numpy.float64]",
"arg3: numpy.ndarray[numpy.int32]",
"arg4: int",
"arg5: m.numpy_vectorize.NonPODClass",
"arg6: numpy.ndarray[numpy.float64]",
]) + ") -> object")
b = np.array([[10, 20, 30]], dtype="float64")
c = np.array([100, 200]) # NOT a vectorized argument
d = np.array([[1000], [2000], [3000]], dtype="int")
g = np.array(
[[1000000, 2000000, 3000000]], dtype="int") # requires casting
assert np.all(
m.vec_passthrough(1, b, c, d, 10000, m.NonPODClass(100000), g) ==
np.array([
[1111111, 2111121, 3111131],
[1112111, 2112121, 3112131],
[1113111, 2113121, 3113131],
]))
def test_method_vectorization():
o = m.VectorizeTestClass(3)
x = np.array([1, 2], dtype="int")
y = np.array([[10], [20]], dtype="float32")
assert np.all(o.method(x, y) == [[14, 15], [24, 25]])
def test_array_collapse():
assert not isinstance(m.vectorized_func(1, 2, 3), np.ndarray)
assert not isinstance(m.vectorized_func(np.array(1), 2, 3), np.ndarray)
z = m.vectorized_func([1], 2, 3)
assert isinstance(z, np.ndarray)
assert z.shape == (1, )
z = m.vectorized_func(1, [[[2]]], 3)
assert isinstance(z, np.ndarray)
assert z.shape == (1, 1, 1)
def test_vectorized_noreturn():
x = m.NonPODClass(0)
assert x.value == 0
m.add_to(x, [1, 2, 3, 4])
assert x.value == 10
m.add_to(x, 1)
assert x.value == 11
m.add_to(x, [[1, 1], [2, 3]])
assert x.value == 18

View File

@@ -0,0 +1,77 @@
/*
tests/test_opaque_types.cpp -- opaque types, passing void pointers
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/stl.h>
#include "pybind11_tests.h"
#include <vector>
// IMPORTANT: Disable internal pybind11 translation mechanisms for STL data structures
//
// This also deliberately doesn't use the below StringList type alias to test
// that MAKE_OPAQUE can handle a type containing a `,`. (The `std::allocator`
// bit is just the default `std::vector` allocator).
PYBIND11_MAKE_OPAQUE(std::vector<std::string, std::allocator<std::string>>);
using StringList = std::vector<std::string, std::allocator<std::string>>;
TEST_SUBMODULE(opaque_types, m) {
// test_string_list
py::class_<StringList>(m, "StringList")
.def(py::init<>())
.def("pop_back", &StringList::pop_back)
/* There are multiple versions of push_back(), etc. Select the right ones. */
.def("push_back", (void(StringList::*)(const std::string &)) & StringList::push_back)
.def("back", (std::string & (StringList::*) ()) & StringList::back)
.def("__len__", [](const StringList &v) { return v.size(); })
.def(
"__iter__",
[](StringList &v) { return py::make_iterator(v.begin(), v.end()); },
py::keep_alive<0, 1>());
class ClassWithSTLVecProperty {
public:
StringList stringList;
};
py::class_<ClassWithSTLVecProperty>(m, "ClassWithSTLVecProperty")
.def(py::init<>())
.def_readwrite("stringList", &ClassWithSTLVecProperty::stringList);
m.def("print_opaque_list", [](const StringList &l) {
std::string ret = "Opaque list: [";
bool first = true;
for (const auto &entry : l) {
if (!first) {
ret += ", ";
}
ret += entry;
first = false;
}
return ret + "]";
});
// test_pointers
m.def("return_void_ptr", []() { return (void *) 0x1234; });
m.def("get_void_ptr_value", [](void *ptr) { return reinterpret_cast<std::intptr_t>(ptr); });
m.def("return_null_str", []() { return (char *) nullptr; });
m.def("get_null_str_value", [](char *ptr) { return reinterpret_cast<std::intptr_t>(ptr); });
m.def("return_unique_ptr", []() -> std::unique_ptr<StringList> {
auto *result = new StringList();
result->push_back("some value");
return std::unique_ptr<StringList>(result);
});
// test unions
py::class_<IntFloat>(m, "IntFloat")
.def(py::init<>())
.def_readwrite("i", &IntFloat::i)
.def_readwrite("f", &IntFloat::f);
}

View File

@@ -0,0 +1,57 @@
import pytest
from pybind11_tests import ConstructorStats, UserType
from pybind11_tests import opaque_types as m
def test_string_list():
lst = m.StringList()
lst.push_back("Element 1")
lst.push_back("Element 2")
assert m.print_opaque_list(lst) == "Opaque list: [Element 1, Element 2]"
assert lst.back() == "Element 2"
for i, k in enumerate(lst, start=1):
assert k == f"Element {i}"
lst.pop_back()
assert m.print_opaque_list(lst) == "Opaque list: [Element 1]"
cvp = m.ClassWithSTLVecProperty()
assert m.print_opaque_list(cvp.stringList) == "Opaque list: []"
cvp.stringList = lst
cvp.stringList.push_back("Element 3")
assert m.print_opaque_list(
cvp.stringList) == "Opaque list: [Element 1, Element 3]"
def test_pointers(msg):
living_before = ConstructorStats.get(UserType).alive()
assert m.get_void_ptr_value(m.return_void_ptr()) == 0x1234
assert m.get_void_ptr_value(
UserType()) # Should also work for other C++ types
assert ConstructorStats.get(UserType).alive() == living_before
with pytest.raises(TypeError) as excinfo:
m.get_void_ptr_value([1, 2, 3]) # This should not work
assert (msg(excinfo.value) == """
get_void_ptr_value(): incompatible function arguments. The following argument types are supported:
1. (arg0: capsule) -> int
Invoked with: [1, 2, 3]
""")
assert m.return_null_str() is None
assert m.get_null_str_value(m.return_null_str()) is not None
ptr = m.return_unique_ptr()
assert "StringList" in repr(ptr)
assert m.print_opaque_list(ptr) == "Opaque list: [some value]"
def test_unions():
int_float_union = m.IntFloat()
int_float_union.i = 42
assert int_float_union.i == 42
int_float_union.f = 3.0
assert int_float_union.f == 3.0

View File

@@ -0,0 +1,288 @@
/*
tests/test_operator_overloading.cpp -- operator overloading
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/operators.h>
#include <pybind11/stl.h>
#include "constructor_stats.h"
#include "pybind11_tests.h"
#include <functional>
class Vector2 {
public:
Vector2(float x, float y) : x(x), y(y) { print_created(this, toString()); }
Vector2(const Vector2 &v) : x(v.x), y(v.y) { print_copy_created(this); }
Vector2(Vector2 &&v) noexcept : x(v.x), y(v.y) {
print_move_created(this);
v.x = v.y = 0;
}
Vector2 &operator=(const Vector2 &v) {
x = v.x;
y = v.y;
print_copy_assigned(this);
return *this;
}
Vector2 &operator=(Vector2 &&v) noexcept {
x = v.x;
y = v.y;
v.x = v.y = 0;
print_move_assigned(this);
return *this;
}
~Vector2() { print_destroyed(this); }
std::string toString() const {
return "[" + std::to_string(x) + ", " + std::to_string(y) + "]";
}
Vector2 operator-() const { return Vector2(-x, -y); }
Vector2 operator+(const Vector2 &v) const { return Vector2(x + v.x, y + v.y); }
Vector2 operator-(const Vector2 &v) const { return Vector2(x - v.x, y - v.y); }
Vector2 operator-(float value) const { return Vector2(x - value, y - value); }
Vector2 operator+(float value) const { return Vector2(x + value, y + value); }
Vector2 operator*(float value) const { return Vector2(x * value, y * value); }
Vector2 operator/(float value) const { return Vector2(x / value, y / value); }
Vector2 operator*(const Vector2 &v) const { return Vector2(x * v.x, y * v.y); }
Vector2 operator/(const Vector2 &v) const { return Vector2(x / v.x, y / v.y); }
Vector2 &operator+=(const Vector2 &v) {
x += v.x;
y += v.y;
return *this;
}
Vector2 &operator-=(const Vector2 &v) {
x -= v.x;
y -= v.y;
return *this;
}
Vector2 &operator*=(float v) {
x *= v;
y *= v;
return *this;
}
Vector2 &operator/=(float v) {
x /= v;
y /= v;
return *this;
}
Vector2 &operator*=(const Vector2 &v) {
x *= v.x;
y *= v.y;
return *this;
}
Vector2 &operator/=(const Vector2 &v) {
x /= v.x;
y /= v.y;
return *this;
}
friend Vector2 operator+(float f, const Vector2 &v) { return Vector2(f + v.x, f + v.y); }
friend Vector2 operator-(float f, const Vector2 &v) { return Vector2(f - v.x, f - v.y); }
friend Vector2 operator*(float f, const Vector2 &v) { return Vector2(f * v.x, f * v.y); }
friend Vector2 operator/(float f, const Vector2 &v) { return Vector2(f / v.x, f / v.y); }
bool operator==(const Vector2 &v) const { return x == v.x && y == v.y; }
bool operator!=(const Vector2 &v) const { return x != v.x || y != v.y; }
private:
float x, y;
};
class C1 {};
class C2 {};
int operator+(const C1 &, const C1 &) { return 11; }
int operator+(const C2 &, const C2 &) { return 22; }
int operator+(const C2 &, const C1 &) { return 21; }
int operator+(const C1 &, const C2 &) { return 12; }
struct HashMe {
std::string member;
};
bool operator==(const HashMe &lhs, const HashMe &rhs) { return lhs.member == rhs.member; }
// Note: Specializing explicit within `namespace std { ... }` is done due to a
// bug in GCC<7. If you are supporting compilers later than this, consider
// specializing `using template<> struct std::hash<...>` in the global
// namespace instead, per this recommendation:
// https://en.cppreference.com/w/cpp/language/extending_std#Adding_template_specializations
namespace std {
template <>
struct hash<Vector2> {
// Not a good hash function, but easy to test
size_t operator()(const Vector2 &) { return 4; }
};
// HashMe has a hash function in C++ but no `__hash__` for Python.
template <>
struct hash<HashMe> {
std::size_t operator()(const HashMe &selector) const {
return std::hash<std::string>()(selector.member);
}
};
} // namespace std
// Not a good abs function, but easy to test.
std::string abs(const Vector2 &) { return "abs(Vector2)"; }
// MSVC & Intel warns about unknown pragmas, and warnings are errors.
#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic push
// clang 7.0.0 and Apple LLVM 10.0.1 introduce `-Wself-assign-overloaded` to
// `-Wall`, which is used here for overloading (e.g. `py::self += py::self `).
// Here, we suppress the warning using `#pragma diagnostic`.
// Taken from: https://github.com/RobotLocomotion/drake/commit/aaf84b46
// TODO(eric): This could be resolved using a function / functor (e.g. `py::self()`).
# if defined(__APPLE__) && defined(__clang__)
# if (__clang_major__ >= 10)
# pragma GCC diagnostic ignored "-Wself-assign-overloaded"
# endif
# elif defined(__clang__)
# if (__clang_major__ >= 7)
# pragma GCC diagnostic ignored "-Wself-assign-overloaded"
# endif
# endif
#endif
TEST_SUBMODULE(operators, m) {
// test_operator_overloading
py::class_<Vector2>(m, "Vector2")
.def(py::init<float, float>())
.def(py::self + py::self)
.def(py::self + float())
.def(py::self - py::self)
.def(py::self - float())
.def(py::self * float())
.def(py::self / float())
.def(py::self * py::self)
.def(py::self / py::self)
.def(py::self += py::self)
.def(py::self -= py::self)
.def(py::self *= float())
.def(py::self /= float())
.def(py::self *= py::self)
.def(py::self /= py::self)
.def(float() + py::self)
.def(float() - py::self)
.def(float() * py::self)
.def(float() / py::self)
.def(-py::self)
.def("__str__", &Vector2::toString)
.def("__repr__", &Vector2::toString)
.def(py::self == py::self)
.def(py::self != py::self)
.def(py::hash(py::self))
// N.B. See warning about usage of `py::detail::abs(py::self)` in
// `operators.h`.
.def("__abs__", [](const Vector2 &v) { return abs(v); });
m.attr("Vector") = m.attr("Vector2");
// test_operators_notimplemented
// #393: need to return NotSupported to ensure correct arithmetic operator behavior
py::class_<C1>(m, "C1").def(py::init<>()).def(py::self + py::self);
py::class_<C2>(m, "C2")
.def(py::init<>())
.def(py::self + py::self)
.def("__add__", [](const C2 &c2, const C1 &c1) { return c2 + c1; })
.def("__radd__", [](const C2 &c2, const C1 &c1) { return c1 + c2; });
// test_nested
// #328: first member in a class can't be used in operators
struct NestABase {
int value = -2;
};
py::class_<NestABase>(m, "NestABase")
.def(py::init<>())
.def_readwrite("value", &NestABase::value);
struct NestA : NestABase {
int value = 3;
NestA &operator+=(int i) {
value += i;
return *this;
}
};
py::class_<NestA>(m, "NestA")
.def(py::init<>())
.def(py::self += int())
.def(
"as_base",
[](NestA &a) -> NestABase & { return (NestABase &) a; },
py::return_value_policy::reference_internal);
m.def("get_NestA", [](const NestA &a) { return a.value; });
struct NestB {
NestA a;
int value = 4;
NestB &operator-=(int i) {
value -= i;
return *this;
}
};
py::class_<NestB>(m, "NestB")
.def(py::init<>())
.def(py::self -= int())
.def_readwrite("a", &NestB::a);
m.def("get_NestB", [](const NestB &b) { return b.value; });
struct NestC {
NestB b;
int value = 5;
NestC &operator*=(int i) {
value *= i;
return *this;
}
};
py::class_<NestC>(m, "NestC")
.def(py::init<>())
.def(py::self *= int())
.def_readwrite("b", &NestC::b);
m.def("get_NestC", [](const NestC &c) { return c.value; });
// test_overriding_eq_reset_hash
// #2191 Overriding __eq__ should set __hash__ to None
struct Comparable {
int value;
bool operator==(const Comparable &rhs) const { return value == rhs.value; }
};
struct Hashable : Comparable {
explicit Hashable(int value) : Comparable{value} {};
size_t hash() const { return static_cast<size_t>(value); }
};
struct Hashable2 : Hashable {
using Hashable::Hashable;
};
py::class_<Comparable>(m, "Comparable").def(py::init<int>()).def(py::self == py::self);
py::class_<Hashable>(m, "Hashable")
.def(py::init<int>())
.def(py::self == py::self)
.def("__hash__", &Hashable::hash);
// define __hash__ before __eq__
py::class_<Hashable2>(m, "Hashable2")
.def("__hash__", &Hashable::hash)
.def(py::init<int>())
.def(py::self == py::self);
// define __eq__ but not __hash__
py::class_<HashMe>(m, "HashMe").def(py::self == py::self);
m.def("get_unhashable_HashMe_set", []() { return std::unordered_set<HashMe>{{"one"}}; });
}
#if !defined(_MSC_VER) && !defined(__INTEL_COMPILER)
# pragma GCC diagnostic pop
#endif

View File

@@ -0,0 +1,152 @@
import pytest
from pybind11_tests import ConstructorStats
from pybind11_tests import operators as m
def test_operator_overloading():
v1 = m.Vector2(1, 2)
v2 = m.Vector(3, -1)
v3 = m.Vector2(1, 2) # Same value as v1, but different instance.
assert v1 is not v3
assert str(v1) == "[1.000000, 2.000000]"
assert str(v2) == "[3.000000, -1.000000]"
assert str(-v2) == "[-3.000000, 1.000000]"
assert str(v1 + v2) == "[4.000000, 1.000000]"
assert str(v1 - v2) == "[-2.000000, 3.000000]"
assert str(v1 - 8) == "[-7.000000, -6.000000]"
assert str(v1 + 8) == "[9.000000, 10.000000]"
assert str(v1 * 8) == "[8.000000, 16.000000]"
assert str(v1 / 8) == "[0.125000, 0.250000]"
assert str(8 - v1) == "[7.000000, 6.000000]"
assert str(8 + v1) == "[9.000000, 10.000000]"
assert str(8 * v1) == "[8.000000, 16.000000]"
assert str(8 / v1) == "[8.000000, 4.000000]"
assert str(v1 * v2) == "[3.000000, -2.000000]"
assert str(v2 / v1) == "[3.000000, -0.500000]"
assert v1 == v3
assert v1 != v2
assert hash(v1) == 4
# TODO(eric.cousineau): Make this work.
# assert abs(v1) == "abs(Vector2)"
v1 += 2 * v2
assert str(v1) == "[7.000000, 0.000000]"
v1 -= v2
assert str(v1) == "[4.000000, 1.000000]"
v1 *= 2
assert str(v1) == "[8.000000, 2.000000]"
v1 /= 16
assert str(v1) == "[0.500000, 0.125000]"
v1 *= v2
assert str(v1) == "[1.500000, -0.125000]"
v2 /= v1
assert str(v2) == "[2.000000, 8.000000]"
cstats = ConstructorStats.get(m.Vector2)
assert cstats.alive() == 3
del v1
assert cstats.alive() == 2
del v2
assert cstats.alive() == 1
del v3
assert cstats.alive() == 0
assert cstats.values() == [
"[1.000000, 2.000000]",
"[3.000000, -1.000000]",
"[1.000000, 2.000000]",
"[-3.000000, 1.000000]",
"[4.000000, 1.000000]",
"[-2.000000, 3.000000]",
"[-7.000000, -6.000000]",
"[9.000000, 10.000000]",
"[8.000000, 16.000000]",
"[0.125000, 0.250000]",
"[7.000000, 6.000000]",
"[9.000000, 10.000000]",
"[8.000000, 16.000000]",
"[8.000000, 4.000000]",
"[3.000000, -2.000000]",
"[3.000000, -0.500000]",
"[6.000000, -2.000000]",
]
assert cstats.default_constructions == 0
assert cstats.copy_constructions == 0
assert cstats.move_constructions >= 10
assert cstats.copy_assignments == 0
assert cstats.move_assignments == 0
def test_operators_notimplemented():
"""#393: need to return NotSupported to ensure correct arithmetic operator behavior"""
c1, c2 = m.C1(), m.C2()
assert c1 + c1 == 11
assert c2 + c2 == 22
assert c2 + c1 == 21
assert c1 + c2 == 12
def test_nested():
"""#328: first member in a class can't be used in operators"""
a = m.NestA()
b = m.NestB()
c = m.NestC()
a += 10
assert m.get_NestA(a) == 13
b.a += 100
assert m.get_NestA(b.a) == 103
c.b.a += 1000
assert m.get_NestA(c.b.a) == 1003
b -= 1
assert m.get_NestB(b) == 3
c.b -= 3
assert m.get_NestB(c.b) == 1
c *= 7
assert m.get_NestC(c) == 35
abase = a.as_base()
assert abase.value == -2
a.as_base().value += 44
assert abase.value == 42
assert c.b.a.as_base().value == -2
c.b.a.as_base().value += 44
assert c.b.a.as_base().value == 42
del c
pytest.gc_collect()
del a # Shouldn't delete while abase is still alive
pytest.gc_collect()
assert abase.value == 42
del abase, b
pytest.gc_collect()
def test_overriding_eq_reset_hash():
assert m.Comparable(15) is not m.Comparable(15)
assert m.Comparable(15) == m.Comparable(15)
with pytest.raises(TypeError) as excinfo:
hash(m.Comparable(15))
assert str(excinfo.value).startswith("unhashable type:")
for hashable in (m.Hashable, m.Hashable2):
assert hashable(15) is not hashable(15)
assert hashable(15) == hashable(15)
assert hash(hashable(15)) == 15
assert hash(hashable(15)) == hash(hashable(15))
def test_return_set_of_unhashable():
with pytest.raises(TypeError) as excinfo:
m.get_unhashable_HashMe_set()
assert str(excinfo.value.__cause__).startswith("unhashable type:")

View File

@@ -0,0 +1,194 @@
/*
tests/test_pickling.cpp -- pickle support
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
Copyright (c) 2021 The Pybind Development Team.
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
#include <memory>
#include <stdexcept>
#include <utility>
namespace exercise_trampoline {
struct SimpleBase {
int num = 0;
virtual ~SimpleBase() = default;
// For compatibility with old clang versions:
SimpleBase() = default;
SimpleBase(const SimpleBase &) = default;
};
struct SimpleBaseTrampoline : SimpleBase {};
struct SimpleCppDerived : SimpleBase {};
void wrap(py::module m) {
py::class_<SimpleBase, SimpleBaseTrampoline>(m, "SimpleBase")
.def(py::init<>())
.def_readwrite("num", &SimpleBase::num)
.def(py::pickle(
[](const py::object &self) {
py::dict d;
if (py::hasattr(self, "__dict__")) {
d = self.attr("__dict__");
}
return py::make_tuple(self.attr("num"), d);
},
[](const py::tuple &t) {
if (t.size() != 2) {
throw std::runtime_error("Invalid state!");
}
auto cpp_state = std::unique_ptr<SimpleBase>(new SimpleBaseTrampoline);
cpp_state->num = t[0].cast<int>();
auto py_state = t[1].cast<py::dict>();
return std::make_pair(std::move(cpp_state), py_state);
}));
m.def("make_SimpleCppDerivedAsBase",
[]() { return std::unique_ptr<SimpleBase>(new SimpleCppDerived); });
m.def("check_dynamic_cast_SimpleCppDerived", [](const SimpleBase *base_ptr) {
return dynamic_cast<const SimpleCppDerived *>(base_ptr) != nullptr;
});
}
} // namespace exercise_trampoline
TEST_SUBMODULE(pickling, m) {
m.def("simple_callable", []() { return 20220426; });
// test_roundtrip
class Pickleable {
public:
explicit Pickleable(const std::string &value) : m_value(value) {}
const std::string &value() const { return m_value; }
void setExtra1(int extra1) { m_extra1 = extra1; }
void setExtra2(int extra2) { m_extra2 = extra2; }
int extra1() const { return m_extra1; }
int extra2() const { return m_extra2; }
private:
std::string m_value;
int m_extra1 = 0;
int m_extra2 = 0;
};
class PickleableNew : public Pickleable {
public:
using Pickleable::Pickleable;
};
py::class_<Pickleable> pyPickleable(m, "Pickleable");
pyPickleable.def(py::init<std::string>())
.def("value", &Pickleable::value)
.def("extra1", &Pickleable::extra1)
.def("extra2", &Pickleable::extra2)
.def("setExtra1", &Pickleable::setExtra1)
.def("setExtra2", &Pickleable::setExtra2)
// For details on the methods below, refer to
// http://docs.python.org/3/library/pickle.html#pickling-class-instances
.def("__getstate__", [](const Pickleable &p) {
/* Return a tuple that fully encodes the state of the object */
return py::make_tuple(p.value(), p.extra1(), p.extra2());
});
ignoreOldStyleInitWarnings([&pyPickleable]() {
pyPickleable.def("__setstate__", [](Pickleable &p, const py::tuple &t) {
if (t.size() != 3) {
throw std::runtime_error("Invalid state!");
}
/* Invoke the constructor (need to use in-place version) */
new (&p) Pickleable(t[0].cast<std::string>());
/* Assign any additional state */
p.setExtra1(t[1].cast<int>());
p.setExtra2(t[2].cast<int>());
});
});
py::class_<PickleableNew, Pickleable>(m, "PickleableNew")
.def(py::init<std::string>())
.def(py::pickle(
[](const PickleableNew &p) {
return py::make_tuple(p.value(), p.extra1(), p.extra2());
},
[](const py::tuple &t) {
if (t.size() != 3) {
throw std::runtime_error("Invalid state!");
}
auto p = PickleableNew(t[0].cast<std::string>());
p.setExtra1(t[1].cast<int>());
p.setExtra2(t[2].cast<int>());
return p;
}));
#if !defined(PYPY_VERSION)
// test_roundtrip_with_dict
class PickleableWithDict {
public:
explicit PickleableWithDict(const std::string &value) : value(value) {}
std::string value;
int extra;
};
class PickleableWithDictNew : public PickleableWithDict {
public:
using PickleableWithDict::PickleableWithDict;
};
py::class_<PickleableWithDict> pyPickleableWithDict(
m, "PickleableWithDict", py::dynamic_attr());
pyPickleableWithDict.def(py::init<std::string>())
.def_readwrite("value", &PickleableWithDict::value)
.def_readwrite("extra", &PickleableWithDict::extra)
.def("__getstate__", [](const py::object &self) {
/* Also include __dict__ in state */
return py::make_tuple(self.attr("value"), self.attr("extra"), self.attr("__dict__"));
});
ignoreOldStyleInitWarnings([&pyPickleableWithDict]() {
pyPickleableWithDict.def("__setstate__", [](const py::object &self, const py::tuple &t) {
if (t.size() != 3) {
throw std::runtime_error("Invalid state!");
}
/* Cast and construct */
auto &p = self.cast<PickleableWithDict &>();
new (&p) PickleableWithDict(t[0].cast<std::string>());
/* Assign C++ state */
p.extra = t[1].cast<int>();
/* Assign Python state */
self.attr("__dict__") = t[2];
});
});
py::class_<PickleableWithDictNew, PickleableWithDict>(m, "PickleableWithDictNew")
.def(py::init<std::string>())
.def(py::pickle(
[](const py::object &self) {
return py::make_tuple(
self.attr("value"), self.attr("extra"), self.attr("__dict__"));
},
[](const py::tuple &t) {
if (t.size() != 3) {
throw std::runtime_error("Invalid state!");
}
auto cpp_state = PickleableWithDictNew(t[0].cast<std::string>());
cpp_state.extra = t[1].cast<int>();
auto py_state = t[2].cast<py::dict>();
return std::make_pair(cpp_state, py_state);
}));
#endif
exercise_trampoline::wrap(m);
}

View File

@@ -0,0 +1,95 @@
import pickle
import re
import pytest
import env
from pybind11_tests import pickling as m
def test_pickle_simple_callable():
assert m.simple_callable() == 20220426
if env.PYPY:
serialized = pickle.dumps(m.simple_callable)
deserialized = pickle.loads(serialized)
assert deserialized() == 20220426
else:
# To document broken behavior: currently it fails universally with
# all C Python versions.
with pytest.raises(TypeError) as excinfo:
pickle.dumps(m.simple_callable)
assert re.search("can.*t pickle .*PyCapsule.* object",
str(excinfo.value))
@pytest.mark.parametrize("cls_name", ["Pickleable", "PickleableNew"])
def test_roundtrip(cls_name):
cls = getattr(m, cls_name)
p = cls("test_value")
p.setExtra1(15)
p.setExtra2(48)
data = pickle.dumps(p, 2) # Must use pickle protocol >= 2
p2 = pickle.loads(data)
assert p2.value() == p.value()
assert p2.extra1() == p.extra1()
assert p2.extra2() == p.extra2()
@pytest.mark.xfail("env.PYPY")
@pytest.mark.parametrize("cls_name",
["PickleableWithDict", "PickleableWithDictNew"])
def test_roundtrip_with_dict(cls_name):
cls = getattr(m, cls_name)
p = cls("test_value")
p.extra = 15
p.dynamic = "Attribute"
data = pickle.dumps(p, pickle.HIGHEST_PROTOCOL)
p2 = pickle.loads(data)
assert p2.value == p.value
assert p2.extra == p.extra
assert p2.dynamic == p.dynamic
def test_enum_pickle():
from pybind11_tests import enums as e
data = pickle.dumps(e.EOne, 2)
assert e.EOne == pickle.loads(data)
#
# exercise_trampoline
#
class SimplePyDerived(m.SimpleBase):
pass
def test_roundtrip_simple_py_derived():
p = SimplePyDerived()
p.num = 202
p.stored_in_dict = 303
data = pickle.dumps(p, pickle.HIGHEST_PROTOCOL)
p2 = pickle.loads(data)
assert isinstance(p2, SimplePyDerived)
assert p2.num == 202
assert p2.stored_in_dict == 303
def test_roundtrip_simple_cpp_derived():
p = m.make_SimpleCppDerivedAsBase()
assert m.check_dynamic_cast_SimpleCppDerived(p)
p.num = 404
if not env.PYPY:
# To ensure that this unit test is not accidentally invalidated.
with pytest.raises(AttributeError):
# Mimics the `setstate` C++ implementation.
setattr(p, "__dict__", {}) # noqa: B010
data = pickle.dumps(p, pickle.HIGHEST_PROTOCOL)
p2 = pickle.loads(data)
assert isinstance(p2, m.SimpleBase)
assert p2.num == 404
# Issue #3062: pickleable base C++ classes can incur object slicing
# if derived typeid is not registered with pybind11
assert not m.check_dynamic_cast_SimpleCppDerived(p2)

View File

@@ -0,0 +1,615 @@
/*
tests/test_pytypes.cpp -- Python type casters
Copyright (c) 2017 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include "pybind11_tests.h"
#include <utility>
namespace external {
namespace detail {
bool check(PyObject *o) { return PyFloat_Check(o) != 0; }
PyObject *conv(PyObject *o) {
PyObject *ret = nullptr;
if (PyLong_Check(o)) {
double v = PyLong_AsDouble(o);
if (!(v == -1.0 && PyErr_Occurred())) {
ret = PyFloat_FromDouble(v);
}
} else {
PyErr_SetString(PyExc_TypeError, "Unexpected type");
}
return ret;
}
PyObject *default_constructed() { return PyFloat_FromDouble(0.0); }
} // namespace detail
class float_ : public py::object {
PYBIND11_OBJECT_CVT(float_, py::object, external::detail::check, external::detail::conv)
float_() : py::object(external::detail::default_constructed(), stolen_t{}) {}
double get_value() const { return PyFloat_AsDouble(this->ptr()); }
};
} // namespace external
TEST_SUBMODULE(pytypes, m) {
// test_bool
m.def("get_bool", [] { return py::bool_(false); });
// test_int
m.def("get_int", [] { return py::int_(0); });
// test_iterator
m.def("get_iterator", [] { return py::iterator(); });
// test_iterable
m.def("get_iterable", [] { return py::iterable(); });
// test_float
m.def("get_float", [] { return py::float_(0.0f); });
// test_list
m.def("list_no_args", []() { return py::list{}; });
m.def("list_ssize_t", []() { return py::list{(py::ssize_t) 0}; });
m.def("list_size_t", []() { return py::list{(py::size_t) 0}; });
m.def("list_insert_ssize_t", [](py::list *l) { return l->insert((py::ssize_t) 1, 83); });
m.def("list_insert_size_t", [](py::list *l) { return l->insert((py::size_t) 3, 57); });
m.def("get_list", []() {
py::list list;
list.append("value");
py::print("Entry at position 0:", list[0]);
list[0] = py::str("overwritten");
list.insert(0, "inserted-0");
list.insert(2, "inserted-2");
return list;
});
m.def("print_list", [](const py::list &list) {
int index = 0;
for (auto item : list) {
py::print("list item {}: {}"_s.format(index++, item));
}
});
// test_none
m.def("get_none", [] { return py::none(); });
m.def("print_none", [](const py::none &none) { py::print("none: {}"_s.format(none)); });
// test_set, test_frozenset
m.def("get_set", []() {
py::set set;
set.add(py::str("key1"));
set.add("key2");
set.add(std::string("key3"));
return set;
});
m.def("get_frozenset", []() {
py::set set;
set.add(py::str("key1"));
set.add("key2");
set.add(std::string("key3"));
return py::frozenset(set);
});
m.def("print_anyset", [](const py::anyset &set) {
for (auto item : set) {
py::print("key:", item);
}
});
m.def("anyset_size", [](const py::anyset &set) { return set.size(); });
m.def("anyset_empty", [](const py::anyset &set) { return set.empty(); });
m.def("anyset_contains",
[](const py::anyset &set, const py::object &key) { return set.contains(key); });
m.def("anyset_contains",
[](const py::anyset &set, const char *key) { return set.contains(key); });
m.def("set_add", [](py::set &set, const py::object &key) { set.add(key); });
m.def("set_clear", [](py::set &set) { set.clear(); });
// test_dict
m.def("get_dict", []() { return py::dict("key"_a = "value"); });
m.def("print_dict", [](const py::dict &dict) {
for (auto item : dict) {
py::print("key: {}, value={}"_s.format(item.first, item.second));
}
});
m.def("dict_keyword_constructor", []() {
auto d1 = py::dict("x"_a = 1, "y"_a = 2);
auto d2 = py::dict("z"_a = 3, **d1);
return d2;
});
m.def("dict_contains",
[](const py::dict &dict, py::object val) { return dict.contains(val); });
m.def("dict_contains",
[](const py::dict &dict, const char *val) { return dict.contains(val); });
// test_tuple
m.def("tuple_no_args", []() { return py::tuple{}; });
m.def("tuple_ssize_t", []() { return py::tuple{(py::ssize_t) 0}; });
m.def("tuple_size_t", []() { return py::tuple{(py::size_t) 0}; });
m.def("get_tuple", []() { return py::make_tuple(42, py::none(), "spam"); });
// test_simple_namespace
m.def("get_simple_namespace", []() {
auto ns = py::module_::import("types").attr("SimpleNamespace")(
"attr"_a = 42, "x"_a = "foo", "wrong"_a = 1);
py::delattr(ns, "wrong");
py::setattr(ns, "right", py::int_(2));
return ns;
});
// test_str
m.def("str_from_char_ssize_t", []() { return py::str{"red", (py::ssize_t) 3}; });
m.def("str_from_char_size_t", []() { return py::str{"blue", (py::size_t) 4}; });
m.def("str_from_string", []() { return py::str(std::string("baz")); });
m.def("str_from_bytes", []() { return py::str(py::bytes("boo", 3)); });
m.def("str_from_object", [](const py::object &obj) { return py::str(obj); });
m.def("repr_from_object", [](const py::object &obj) { return py::repr(obj); });
m.def("str_from_handle", [](py::handle h) { return py::str(h); });
m.def("str_from_string_from_str",
[](const py::str &obj) { return py::str(static_cast<std::string>(obj)); });
m.def("str_format", []() {
auto s1 = "{} + {} = {}"_s.format(1, 2, 3);
auto s2 = "{a} + {b} = {c}"_s.format("a"_a = 1, "b"_a = 2, "c"_a = 3);
return py::make_tuple(s1, s2);
});
// test_bytes
m.def("bytes_from_char_ssize_t", []() { return py::bytes{"green", (py::ssize_t) 5}; });
m.def("bytes_from_char_size_t", []() { return py::bytes{"purple", (py::size_t) 6}; });
m.def("bytes_from_string", []() { return py::bytes(std::string("foo")); });
m.def("bytes_from_str", []() { return py::bytes(py::str("bar", 3)); });
// test bytearray
m.def("bytearray_from_char_ssize_t", []() { return py::bytearray{"$%", (py::ssize_t) 2}; });
m.def("bytearray_from_char_size_t", []() { return py::bytearray{"@$!", (py::size_t) 3}; });
m.def("bytearray_from_string", []() { return py::bytearray(std::string("foo")); });
m.def("bytearray_size", []() { return py::bytearray("foo").size(); });
// test_capsule
m.def("return_capsule_with_destructor", []() {
py::print("creating capsule");
return py::capsule([]() { py::print("destructing capsule"); });
});
m.def("return_renamed_capsule_with_destructor", []() {
py::print("creating capsule");
auto cap = py::capsule([]() { py::print("destructing capsule"); });
static const char *capsule_name = "test_name1";
py::print("renaming capsule");
cap.set_name(capsule_name);
return cap;
});
m.def("return_capsule_with_destructor_2", []() {
py::print("creating capsule");
return py::capsule((void *) 1234, [](void *ptr) {
py::print("destructing capsule: {}"_s.format((size_t) ptr));
});
});
m.def("return_renamed_capsule_with_destructor_2", []() {
py::print("creating capsule");
auto cap = py::capsule((void *) 1234, [](void *ptr) {
py::print("destructing capsule: {}"_s.format((size_t) ptr));
});
static const char *capsule_name = "test_name2";
py::print("renaming capsule");
cap.set_name(capsule_name);
return cap;
});
m.def("return_capsule_with_name_and_destructor", []() {
auto capsule = py::capsule((void *) 12345, "pointer type description", [](PyObject *ptr) {
if (ptr) {
const auto *name = PyCapsule_GetName(ptr);
py::print("destructing capsule ({}, '{}')"_s.format(
(size_t) PyCapsule_GetPointer(ptr, name), name));
}
});
capsule.set_pointer((void *) 1234);
// Using get_pointer<T>()
void *contents1 = static_cast<void *>(capsule);
void *contents2 = capsule.get_pointer();
void *contents3 = capsule.get_pointer<void>();
auto result1 = reinterpret_cast<size_t>(contents1);
auto result2 = reinterpret_cast<size_t>(contents2);
auto result3 = reinterpret_cast<size_t>(contents3);
py::print(
"created capsule ({}, '{}')"_s.format(result1 & result2 & result3, capsule.name()));
return capsule;
});
// test_accessors
m.def("accessor_api", [](const py::object &o) {
auto d = py::dict();
d["basic_attr"] = o.attr("basic_attr");
auto l = py::list();
for (auto item : o.attr("begin_end")) {
l.append(item);
}
d["begin_end"] = l;
d["operator[object]"] = o.attr("d")["operator[object]"_s];
d["operator[char *]"] = o.attr("d")["operator[char *]"];
d["attr(object)"] = o.attr("sub").attr("attr_obj");
d["attr(char *)"] = o.attr("sub").attr("attr_char");
try {
o.attr("sub").attr("missing").ptr();
} catch (const py::error_already_set &) {
d["missing_attr_ptr"] = "raised"_s;
}
try {
o.attr("missing").attr("doesn't matter");
} catch (const py::error_already_set &) {
d["missing_attr_chain"] = "raised"_s;
}
d["is_none"] = o.attr("basic_attr").is_none();
d["operator()"] = o.attr("func")(1);
d["operator*"] = o.attr("func")(*o.attr("begin_end"));
// Test implicit conversion
py::list implicit_list = o.attr("begin_end");
d["implicit_list"] = implicit_list;
py::dict implicit_dict = o.attr("__dict__");
d["implicit_dict"] = implicit_dict;
return d;
});
m.def("tuple_accessor", [](const py::tuple &existing_t) {
try {
existing_t[0] = 1;
} catch (const py::error_already_set &) {
// --> Python system error
// Only new tuples (refcount == 1) are mutable
auto new_t = py::tuple(3);
for (size_t i = 0; i < new_t.size(); ++i) {
new_t[i] = i;
}
return new_t;
}
return py::tuple();
});
m.def("accessor_assignment", []() {
auto l = py::list(1);
l[0] = 0;
auto d = py::dict();
d["get"] = l[0];
auto var = l[0];
d["deferred_get"] = var;
l[0] = 1;
d["set"] = l[0];
var = 99; // this assignment should not overwrite l[0]
d["deferred_set"] = l[0];
d["var"] = var;
return d;
});
// test_constructors
m.def("default_constructors", []() {
return py::dict("bytes"_a = py::bytes(),
"bytearray"_a = py::bytearray(),
"str"_a = py::str(),
"bool"_a = py::bool_(),
"int"_a = py::int_(),
"float"_a = py::float_(),
"tuple"_a = py::tuple(),
"list"_a = py::list(),
"dict"_a = py::dict(),
"set"_a = py::set());
});
m.def("converting_constructors", [](const py::dict &d) {
return py::dict("bytes"_a = py::bytes(d["bytes"]),
"bytearray"_a = py::bytearray(d["bytearray"]),
"str"_a = py::str(d["str"]),
"bool"_a = py::bool_(d["bool"]),
"int"_a = py::int_(d["int"]),
"float"_a = py::float_(d["float"]),
"tuple"_a = py::tuple(d["tuple"]),
"list"_a = py::list(d["list"]),
"dict"_a = py::dict(d["dict"]),
"set"_a = py::set(d["set"]),
"frozenset"_a = py::frozenset(d["frozenset"]),
"memoryview"_a = py::memoryview(d["memoryview"]));
});
m.def("cast_functions", [](const py::dict &d) {
// When converting between Python types, obj.cast<T>() should be the same as T(obj)
return py::dict("bytes"_a = d["bytes"].cast<py::bytes>(),
"bytearray"_a = d["bytearray"].cast<py::bytearray>(),
"str"_a = d["str"].cast<py::str>(),
"bool"_a = d["bool"].cast<py::bool_>(),
"int"_a = d["int"].cast<py::int_>(),
"float"_a = d["float"].cast<py::float_>(),
"tuple"_a = d["tuple"].cast<py::tuple>(),
"list"_a = d["list"].cast<py::list>(),
"dict"_a = d["dict"].cast<py::dict>(),
"set"_a = d["set"].cast<py::set>(),
"frozenset"_a = d["frozenset"].cast<py::frozenset>(),
"memoryview"_a = d["memoryview"].cast<py::memoryview>());
});
m.def("convert_to_pybind11_str", [](const py::object &o) { return py::str(o); });
m.def("nonconverting_constructor",
[](const std::string &type, py::object value, bool move) -> py::object {
if (type == "bytes") {
return move ? py::bytes(std::move(value)) : py::bytes(value);
}
if (type == "none") {
return move ? py::none(std::move(value)) : py::none(value);
}
if (type == "ellipsis") {
return move ? py::ellipsis(std::move(value)) : py::ellipsis(value);
}
if (type == "type") {
return move ? py::type(std::move(value)) : py::type(value);
}
throw std::runtime_error("Invalid type");
});
m.def("get_implicit_casting", []() {
py::dict d;
d["char*_i1"] = "abc";
const char *c2 = "abc";
d["char*_i2"] = c2;
d["char*_e"] = py::cast(c2);
d["char*_p"] = py::str(c2);
d["int_i1"] = 42;
int i = 42;
d["int_i2"] = i;
i++;
d["int_e"] = py::cast(i);
i++;
d["int_p"] = py::int_(i);
d["str_i1"] = std::string("str");
std::string s2("str1");
d["str_i2"] = s2;
s2[3] = '2';
d["str_e"] = py::cast(s2);
s2[3] = '3';
d["str_p"] = py::str(s2);
py::list l(2);
l[0] = 3;
l[1] = py::cast(6);
l.append(9);
l.append(py::cast(12));
l.append(py::int_(15));
return py::dict("d"_a = d, "l"_a = l);
});
// test_print
m.def("print_function", []() {
py::print("Hello, World!");
py::print(1, 2.0, "three", true, std::string("-- multiple args"));
auto args = py::make_tuple("and", "a", "custom", "separator");
py::print("*args", *args, "sep"_a = "-");
py::print("no new line here", "end"_a = " -- ");
py::print("next print");
auto py_stderr = py::module_::import("sys").attr("stderr");
py::print("this goes to stderr", "file"_a = py_stderr);
py::print("flush", "flush"_a = true);
py::print(
"{a} + {b} = {c}"_s.format("a"_a = "py::print", "b"_a = "str.format", "c"_a = "this"));
});
m.def("print_failure", []() { py::print(42, UnregisteredType()); });
m.def("hash_function", [](py::object obj) { return py::hash(std::move(obj)); });
m.def("test_number_protocol", [](const py::object &a, const py::object &b) {
py::list l;
l.append(a.equal(b));
l.append(a.not_equal(b));
l.append(a < b);
l.append(a <= b);
l.append(a > b);
l.append(a >= b);
l.append(a + b);
l.append(a - b);
l.append(a * b);
l.append(a / b);
l.append(a | b);
l.append(a & b);
l.append(a ^ b);
l.append(a >> b);
l.append(a << b);
return l;
});
m.def("test_list_slicing", [](const py::list &a) { return a[py::slice(0, -1, 2)]; });
// See #2361
m.def("issue2361_str_implicit_copy_none", []() {
py::str is_this_none = py::none();
return is_this_none;
});
m.def("issue2361_dict_implicit_copy_none", []() {
py::dict is_this_none = py::none();
return is_this_none;
});
m.def("test_memoryview_object", [](const py::buffer &b) { return py::memoryview(b); });
m.def("test_memoryview_buffer_info",
[](const py::buffer &b) { return py::memoryview(b.request()); });
m.def("test_memoryview_from_buffer", [](bool is_unsigned) {
static const int16_t si16[] = {3, 1, 4, 1, 5};
static const uint16_t ui16[] = {2, 7, 1, 8};
if (is_unsigned) {
return py::memoryview::from_buffer(ui16, {4}, {sizeof(uint16_t)});
}
return py::memoryview::from_buffer(si16, {5}, {sizeof(int16_t)});
});
m.def("test_memoryview_from_buffer_nativeformat", []() {
static const char *format = "@i";
static const int32_t arr[] = {4, 7, 5};
return py::memoryview::from_buffer(arr, sizeof(int32_t), format, {3}, {sizeof(int32_t)});
});
m.def("test_memoryview_from_buffer_empty_shape", []() {
static const char *buf = "";
return py::memoryview::from_buffer(buf, 1, "B", {}, {});
});
m.def("test_memoryview_from_buffer_invalid_strides", []() {
static const char *buf = "\x02\x03\x04";
return py::memoryview::from_buffer(buf, 1, "B", {3}, {});
});
m.def("test_memoryview_from_buffer_nullptr", []() {
return py::memoryview::from_buffer(static_cast<void *>(nullptr), 1, "B", {}, {});
});
m.def("test_memoryview_from_memory", []() {
const char *buf = "\xff\xe1\xab\x37";
return py::memoryview::from_memory(buf, static_cast<py::ssize_t>(strlen(buf)));
});
// test_builtin_functions
m.def("get_len", [](py::handle h) { return py::len(h); });
#ifdef PYBIND11_STR_LEGACY_PERMISSIVE
m.attr("PYBIND11_STR_LEGACY_PERMISSIVE") = true;
#endif
m.def("isinstance_pybind11_bytes",
[](py::object o) { return py::isinstance<py::bytes>(std::move(o)); });
m.def("isinstance_pybind11_str",
[](py::object o) { return py::isinstance<py::str>(std::move(o)); });
m.def("pass_to_pybind11_bytes", [](py::bytes b) { return py::len(std::move(b)); });
m.def("pass_to_pybind11_str", [](py::str s) { return py::len(std::move(s)); });
m.def("pass_to_std_string", [](const std::string &s) { return s.size(); });
// test_weakref
m.def("weakref_from_handle", [](py::handle h) { return py::weakref(h); });
m.def("weakref_from_handle_and_function",
[](py::handle h, py::function f) { return py::weakref(h, std::move(f)); });
m.def("weakref_from_object", [](const py::object &o) { return py::weakref(o); });
m.def("weakref_from_object_and_function",
[](py::object o, py::function f) { return py::weakref(std::move(o), std::move(f)); });
// See PR #3263 for background (https://github.com/pybind/pybind11/pull/3263):
// pytypes.h could be changed to enforce the "most correct" user code below, by removing
// `const` from iterator `reference` using type aliases, but that will break existing
// user code.
#if (defined(__APPLE__) && defined(__clang__)) || defined(PYPY_VERSION)
// This is "most correct" and enforced on these platforms.
# define PYBIND11_AUTO_IT auto it
#else
// This works on many platforms and is (unfortunately) reflective of existing user code.
// NOLINTNEXTLINE(bugprone-macro-parentheses)
# define PYBIND11_AUTO_IT auto &it
#endif
m.def("tuple_iterator", []() {
auto tup = py::make_tuple(5, 7);
int tup_sum = 0;
for (PYBIND11_AUTO_IT : tup) {
tup_sum += it.cast<int>();
}
return tup_sum;
});
m.def("dict_iterator", []() {
py::dict dct;
dct[py::int_(3)] = 5;
dct[py::int_(7)] = 11;
int kv_sum = 0;
for (PYBIND11_AUTO_IT : dct) {
kv_sum += it.first.cast<int>() * 100 + it.second.cast<int>();
}
return kv_sum;
});
m.def("passed_iterator", [](const py::iterator &py_it) {
int elem_sum = 0;
for (PYBIND11_AUTO_IT : py_it) {
elem_sum += it.cast<int>();
}
return elem_sum;
});
#undef PYBIND11_AUTO_IT
// Tests below this line are for pybind11 IMPLEMENTATION DETAILS:
m.def("sequence_item_get_ssize_t", [](const py::object &o) {
return py::detail::accessor_policies::sequence_item::get(o, (py::ssize_t) 1);
});
m.def("sequence_item_set_ssize_t", [](const py::object &o) {
auto s = py::str{"peppa", 5};
py::detail::accessor_policies::sequence_item::set(o, (py::ssize_t) 1, s);
});
m.def("sequence_item_get_size_t", [](const py::object &o) {
return py::detail::accessor_policies::sequence_item::get(o, (py::size_t) 2);
});
m.def("sequence_item_set_size_t", [](const py::object &o) {
auto s = py::str{"george", 6};
py::detail::accessor_policies::sequence_item::set(o, (py::size_t) 2, s);
});
m.def("list_item_get_ssize_t", [](const py::object &o) {
return py::detail::accessor_policies::list_item::get(o, (py::ssize_t) 3);
});
m.def("list_item_set_ssize_t", [](const py::object &o) {
auto s = py::str{"rebecca", 7};
py::detail::accessor_policies::list_item::set(o, (py::ssize_t) 3, s);
});
m.def("list_item_get_size_t", [](const py::object &o) {
return py::detail::accessor_policies::list_item::get(o, (py::size_t) 4);
});
m.def("list_item_set_size_t", [](const py::object &o) {
auto s = py::str{"richard", 7};
py::detail::accessor_policies::list_item::set(o, (py::size_t) 4, s);
});
m.def("tuple_item_get_ssize_t", [](const py::object &o) {
return py::detail::accessor_policies::tuple_item::get(o, (py::ssize_t) 5);
});
m.def("tuple_item_set_ssize_t", []() {
auto s0 = py::str{"emely", 5};
auto s1 = py::str{"edmond", 6};
auto o = py::tuple{2};
py::detail::accessor_policies::tuple_item::set(o, (py::ssize_t) 0, s0);
py::detail::accessor_policies::tuple_item::set(o, (py::ssize_t) 1, s1);
return o;
});
m.def("tuple_item_get_size_t", [](const py::object &o) {
return py::detail::accessor_policies::tuple_item::get(o, (py::size_t) 6);
});
m.def("tuple_item_set_size_t", []() {
auto s0 = py::str{"candy", 5};
auto s1 = py::str{"cat", 3};
auto o = py::tuple{2};
py::detail::accessor_policies::tuple_item::set(o, (py::size_t) 1, s1);
py::detail::accessor_policies::tuple_item::set(o, (py::size_t) 0, s0);
return o;
});
m.def("square_float_", [](const external::float_ &x) -> double {
double v = x.get_value();
return v * v;
});
}

View File

@@ -0,0 +1,667 @@
import contextlib
import sys
import pytest
import env
from pybind11_tests import detailed_error_messages_enabled
from pybind11_tests import pytypes as m
def test_bool(doc):
assert doc(m.get_bool) == "get_bool() -> bool"
def test_int(doc):
assert doc(m.get_int) == "get_int() -> int"
def test_iterator(doc):
assert doc(m.get_iterator) == "get_iterator() -> Iterator"
def test_iterable(doc):
assert doc(m.get_iterable) == "get_iterable() -> Iterable"
def test_float(doc):
assert doc(m.get_float) == "get_float() -> float"
def test_list(capture, doc):
assert m.list_no_args() == []
assert m.list_ssize_t() == []
assert m.list_size_t() == []
lins = [1, 2]
m.list_insert_ssize_t(lins)
assert lins == [1, 83, 2]
m.list_insert_size_t(lins)
assert lins == [1, 83, 2, 57]
with capture:
lst = m.get_list()
assert lst == ["inserted-0", "overwritten", "inserted-2"]
lst.append("value2")
m.print_list(lst)
assert (capture.unordered == """
Entry at position 0: value
list item 0: inserted-0
list item 1: overwritten
list item 2: inserted-2
list item 3: value2
""")
assert doc(m.get_list) == "get_list() -> list"
assert doc(m.print_list) == "print_list(arg0: list) -> None"
def test_none(capture, doc):
assert doc(m.get_none) == "get_none() -> None"
assert doc(m.print_none) == "print_none(arg0: None) -> None"
def test_set(capture, doc):
s = m.get_set()
assert isinstance(s, set)
assert s == {"key1", "key2", "key3"}
s.add("key4")
with capture:
m.print_anyset(s)
assert (capture.unordered == """
key: key1
key: key2
key: key3
key: key4
""")
m.set_add(s, "key5")
assert m.anyset_size(s) == 5
m.set_clear(s)
assert m.anyset_empty(s)
assert not m.anyset_contains(set(), 42)
assert m.anyset_contains({42}, 42)
assert m.anyset_contains({"foo"}, "foo")
assert doc(m.get_set) == "get_set() -> set"
assert doc(m.print_anyset) == "print_anyset(arg0: anyset) -> None"
def test_frozenset(capture, doc):
s = m.get_frozenset()
assert isinstance(s, frozenset)
assert s == frozenset({"key1", "key2", "key3"})
with capture:
m.print_anyset(s)
assert (capture.unordered == """
key: key1
key: key2
key: key3
""")
assert m.anyset_size(s) == 3
assert not m.anyset_empty(s)
assert not m.anyset_contains(frozenset(), 42)
assert m.anyset_contains(frozenset({42}), 42)
assert m.anyset_contains(frozenset({"foo"}), "foo")
assert doc(m.get_frozenset) == "get_frozenset() -> frozenset"
def test_dict(capture, doc):
d = m.get_dict()
assert d == {"key": "value"}
with capture:
d["key2"] = "value2"
m.print_dict(d)
assert (capture.unordered == """
key: key, value=value
key: key2, value=value2
""")
assert not m.dict_contains({}, 42)
assert m.dict_contains({42: None}, 42)
assert m.dict_contains({"foo": None}, "foo")
assert doc(m.get_dict) == "get_dict() -> dict"
assert doc(m.print_dict) == "print_dict(arg0: dict) -> None"
assert m.dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3}
def test_tuple():
assert m.tuple_no_args() == ()
assert m.tuple_ssize_t() == ()
assert m.tuple_size_t() == ()
assert m.get_tuple() == (42, None, "spam")
def test_simple_namespace():
ns = m.get_simple_namespace()
assert ns.attr == 42
assert ns.x == "foo"
assert ns.right == 2
assert not hasattr(ns, "wrong")
def test_str(doc):
assert m.str_from_char_ssize_t().encode().decode() == "red"
assert m.str_from_char_size_t().encode().decode() == "blue"
assert m.str_from_string().encode().decode() == "baz"
assert m.str_from_bytes().encode().decode() == "boo"
assert doc(m.str_from_bytes) == "str_from_bytes() -> str"
class A:
def __str__(self):
return "this is a str"
def __repr__(self):
return "this is a repr"
assert m.str_from_object(A()) == "this is a str"
assert m.repr_from_object(A()) == "this is a repr"
assert m.str_from_handle(A()) == "this is a str"
s1, s2 = m.str_format()
assert s1 == "1 + 2 = 3"
assert s1 == s2
malformed_utf8 = b"\x80"
if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"):
assert m.str_from_object(malformed_utf8) is malformed_utf8
else:
assert m.str_from_object(malformed_utf8) == "b'\\x80'"
assert m.str_from_handle(malformed_utf8) == "b'\\x80'"
assert m.str_from_string_from_str("this is a str") == "this is a str"
ucs_surrogates_str = "\udcc3"
with pytest.raises(UnicodeEncodeError):
m.str_from_string_from_str(ucs_surrogates_str)
def test_bytes(doc):
assert m.bytes_from_char_ssize_t().decode() == "green"
assert m.bytes_from_char_size_t().decode() == "purple"
assert m.bytes_from_string().decode() == "foo"
assert m.bytes_from_str().decode() == "bar"
assert doc(m.bytes_from_str) == "bytes_from_str() -> bytes"
def test_bytearray(doc):
assert m.bytearray_from_char_ssize_t().decode() == "$%"
assert m.bytearray_from_char_size_t().decode() == "@$!"
assert m.bytearray_from_string().decode() == "foo"
assert m.bytearray_size() == len("foo")
def test_capsule(capture):
pytest.gc_collect()
with capture:
a = m.return_capsule_with_destructor()
del a
pytest.gc_collect()
assert (capture.unordered == """
creating capsule
destructing capsule
""")
with capture:
a = m.return_renamed_capsule_with_destructor()
del a
pytest.gc_collect()
assert (capture.unordered == """
creating capsule
renaming capsule
destructing capsule
""")
with capture:
a = m.return_capsule_with_destructor_2()
del a
pytest.gc_collect()
assert (capture.unordered == """
creating capsule
destructing capsule: 1234
""")
with capture:
a = m.return_renamed_capsule_with_destructor_2()
del a
pytest.gc_collect()
assert (capture.unordered == """
creating capsule
renaming capsule
destructing capsule: 1234
""")
with capture:
a = m.return_capsule_with_name_and_destructor()
del a
pytest.gc_collect()
assert (capture.unordered == """
created capsule (1234, 'pointer type description')
destructing capsule (1234, 'pointer type description')
""")
def test_accessors():
class SubTestObject:
attr_obj = 1
attr_char = 2
class TestObject:
basic_attr = 1
begin_end = [1, 2, 3]
d = {"operator[object]": 1, "operator[char *]": 2}
sub = SubTestObject()
def func(self, x, *args):
return self.basic_attr + x + sum(args)
d = m.accessor_api(TestObject())
assert d["basic_attr"] == 1
assert d["begin_end"] == [1, 2, 3]
assert d["operator[object]"] == 1
assert d["operator[char *]"] == 2
assert d["attr(object)"] == 1
assert d["attr(char *)"] == 2
assert d["missing_attr_ptr"] == "raised"
assert d["missing_attr_chain"] == "raised"
assert d["is_none"] is False
assert d["operator()"] == 2
assert d["operator*"] == 7
assert d["implicit_list"] == [1, 2, 3]
assert all(x in TestObject.__dict__ for x in d["implicit_dict"])
assert m.tuple_accessor(tuple()) == (0, 1, 2)
d = m.accessor_assignment()
assert d["get"] == 0
assert d["deferred_get"] == 0
assert d["set"] == 1
assert d["deferred_set"] == 1
assert d["var"] == 99
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]
expected = {t.__name__: t() for t in types}
assert m.default_constructors() == expected
data = {
bytes: b"41", # Currently no supported or working conversions.
bytearray: bytearray(b"41"),
str: 42,
bool: "Not empty",
int: "42",
float: "+1e3",
tuple: range(3),
list: range(3),
dict: [("two", 2), ("one", 1), ("three", 3)],
set: [4, 4, 5, 6, 6, 6],
frozenset: [4, 4, 5, 6, 6, 6],
memoryview: b"abc",
}
inputs = {k.__name__: v for k, v in data.items()}
expected = {k.__name__: k(v) for k, v in data.items()}
assert m.converting_constructors(inputs) == expected
assert m.cast_functions(inputs) == expected
# Converting constructors and cast functions should just reference rather
# than copy when no conversion is needed:
noconv1 = m.converting_constructors(expected)
for k in noconv1:
assert noconv1[k] is expected[k]
noconv2 = m.cast_functions(expected)
for k in noconv2:
assert noconv2[k] is expected[k]
def test_non_converting_constructors():
non_converting_test_cases = [
("bytes", range(10)),
("none", 42),
("ellipsis", 42),
("type", 42),
]
for t, v in non_converting_test_cases:
for move in [True, False]:
with pytest.raises(TypeError) as excinfo:
m.nonconverting_constructor(t, v, move)
expected_error = (
f"Object of type '{type(v).__name__}' is not an instance of '{t}'"
)
assert str(excinfo.value) == expected_error
def test_pybind11_str_raw_str():
# specifically to exercise pybind11::str::raw_str
cvt = m.convert_to_pybind11_str
assert cvt("Str") == "Str"
assert cvt(b"Bytes") == "b'Bytes'"
assert cvt(None) == "None"
assert cvt(False) == "False"
assert cvt(True) == "True"
assert cvt(42) == "42"
assert cvt(2**65) == "36893488147419103232"
assert cvt(-1.50) == "-1.5"
assert cvt(()) == "()"
assert cvt((18, )) == "(18,)"
assert cvt([]) == "[]"
assert cvt([28]) == "[28]"
assert cvt({}) == "{}"
assert cvt({3: 4}) == "{3: 4}"
assert cvt(set()) == "set()"
assert cvt({3, 3}) == "{3}"
valid_orig = "DZ"
valid_utf8 = valid_orig.encode("utf-8")
valid_cvt = cvt(valid_utf8)
if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"):
assert valid_cvt is valid_utf8
else:
assert type(valid_cvt) is str
assert valid_cvt == "b'\\xc7\\xb1'"
malformed_utf8 = b"\x80"
if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"):
assert cvt(malformed_utf8) is malformed_utf8
else:
malformed_cvt = cvt(malformed_utf8)
assert type(malformed_cvt) is str
assert malformed_cvt == "b'\\x80'"
def test_implicit_casting():
"""Tests implicit casting when assigning or appending to dicts and lists."""
z = m.get_implicit_casting()
assert z["d"] == {
"char*_i1": "abc",
"char*_i2": "abc",
"char*_e": "abc",
"char*_p": "abc",
"str_i1": "str",
"str_i2": "str1",
"str_e": "str2",
"str_p": "str3",
"int_i1": 42,
"int_i2": 42,
"int_e": 43,
"int_p": 44,
}
assert z["l"] == [3, 6, 9, 12, 15]
def test_print(capture):
with capture:
m.print_function()
assert (capture == """
Hello, World!
1 2.0 three True -- multiple args
*args-and-a-custom-separator
no new line here -- next print
flush
py::print + str.format = this
""")
assert capture.stderr == "this goes to stderr"
with pytest.raises(RuntimeError) as excinfo:
m.print_failure()
assert str(excinfo.value) == "Unable to convert call argument " + (
"'1' of type 'UnregisteredType' to Python object"
if detailed_error_messages_enabled else
"to Python object (#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)"
)
def test_hash():
class Hashable:
def __init__(self, value):
self.value = value
def __hash__(self):
return self.value
class Unhashable:
__hash__ = None
assert m.hash_function(Hashable(42)) == 42
with pytest.raises(TypeError):
m.hash_function(Unhashable())
def test_number_protocol():
for a, b in [(1, 1), (3, 5)]:
li = [
a == b,
a != b,
a < b,
a <= b,
a > b,
a >= b,
a + b,
a - b,
a * b,
a / b,
a | b,
a & b,
a ^ b,
a >> b,
a << b,
]
assert m.test_number_protocol(a, b) == li
def test_list_slicing():
li = list(range(100))
assert li[::2] == m.test_list_slicing(li)
def test_issue2361():
# See issue #2361
assert m.issue2361_str_implicit_copy_none() == "None"
with pytest.raises(TypeError) as excinfo:
assert m.issue2361_dict_implicit_copy_none()
assert "NoneType" in str(excinfo.value)
assert "iterable" in str(excinfo.value)
@pytest.mark.parametrize(
"method, args, fmt, expected_view",
[
(m.test_memoryview_object, (b"red", ), "B", b"red"),
(m.test_memoryview_buffer_info, (b"green", ), "B", b"green"),
(m.test_memoryview_from_buffer, (False, ), "h", [3, 1, 4, 1, 5]),
(m.test_memoryview_from_buffer, (True, ), "H", [2, 7, 1, 8]),
(m.test_memoryview_from_buffer_nativeformat, (), "@i", [4, 7, 5]),
], )
def test_memoryview(method, args, fmt, expected_view):
view = method(*args)
assert isinstance(view, memoryview)
assert view.format == fmt
assert list(view) == list(expected_view)
@pytest.mark.xfail("env.PYPY", reason="getrefcount is not available")
@pytest.mark.parametrize(
"method",
[
m.test_memoryview_object,
m.test_memoryview_buffer_info,
], )
def test_memoryview_refcount(method):
buf = b"\x0a\x0b\x0c\x0d"
ref_before = sys.getrefcount(buf)
view = method(buf)
ref_after = sys.getrefcount(buf)
assert ref_before < ref_after
assert list(view) == list(buf)
def test_memoryview_from_buffer_empty_shape():
view = m.test_memoryview_from_buffer_empty_shape()
assert isinstance(view, memoryview)
assert view.format == "B"
assert bytes(view) == b""
def test_test_memoryview_from_buffer_invalid_strides():
with pytest.raises(RuntimeError):
m.test_memoryview_from_buffer_invalid_strides()
def test_test_memoryview_from_buffer_nullptr():
with pytest.raises(ValueError):
m.test_memoryview_from_buffer_nullptr()
def test_memoryview_from_memory():
view = m.test_memoryview_from_memory()
assert isinstance(view, memoryview)
assert view.format == "B"
assert bytes(view) == b"\xff\xe1\xab\x37"
def test_builtin_functions():
assert m.get_len([i for i in range(42)]) == 42
with pytest.raises(TypeError) as exc_info:
m.get_len(i for i in range(42))
assert str(exc_info.value) in [
"object of type 'generator' has no len()",
"'generator' has no length",
] # PyPy
def test_isinstance_string_types():
assert m.isinstance_pybind11_bytes(b"")
assert not m.isinstance_pybind11_bytes("")
assert m.isinstance_pybind11_str("")
if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"):
assert m.isinstance_pybind11_str(b"")
else:
assert not m.isinstance_pybind11_str(b"")
def test_pass_bytes_or_unicode_to_string_types():
assert m.pass_to_pybind11_bytes(b"Bytes") == 5
with pytest.raises(TypeError):
m.pass_to_pybind11_bytes("Str")
if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"):
assert m.pass_to_pybind11_str(b"Bytes") == 5
else:
with pytest.raises(TypeError):
m.pass_to_pybind11_str(b"Bytes")
assert m.pass_to_pybind11_str("Str") == 3
assert m.pass_to_std_string(b"Bytes") == 5
assert m.pass_to_std_string("Str") == 3
malformed_utf8 = b"\x80"
if hasattr(m, "PYBIND11_STR_LEGACY_PERMISSIVE"):
assert m.pass_to_pybind11_str(malformed_utf8) == 1
else:
with pytest.raises(TypeError):
m.pass_to_pybind11_str(malformed_utf8)
@pytest.mark.parametrize(
"create_weakref, create_weakref_with_callback",
[
(m.weakref_from_handle, m.weakref_from_handle_and_function),
(m.weakref_from_object, m.weakref_from_object_and_function),
], )
def test_weakref(create_weakref, create_weakref_with_callback):
from weakref import getweakrefcount
# Apparently, you cannot weakly reference an object()
class WeaklyReferenced:
pass
callback_called = False
def callback(wr):
nonlocal callback_called
callback_called = True
obj = WeaklyReferenced()
assert getweakrefcount(obj) == 0
wr = create_weakref(obj)
assert getweakrefcount(obj) == 1
obj = WeaklyReferenced()
assert getweakrefcount(obj) == 0
wr = create_weakref_with_callback(obj, callback) # noqa: F841
assert getweakrefcount(obj) == 1
assert not callback_called
del obj
pytest.gc_collect()
assert callback_called
@pytest.mark.parametrize(
"create_weakref, has_callback",
[
(m.weakref_from_handle, False),
(m.weakref_from_object, False),
(m.weakref_from_handle_and_function, True),
(m.weakref_from_object_and_function, True),
], )
def test_weakref_err(create_weakref, has_callback):
class C:
__slots__ = []
def callback(_):
pass
ob = C()
# Should raise TypeError on CPython
with pytest.raises(TypeError) if not env.PYPY else contextlib.nullcontext(
):
if has_callback:
_ = create_weakref(ob, callback)
else:
_ = create_weakref(ob)
def test_cpp_iterators():
assert m.tuple_iterator() == 12
assert m.dict_iterator() == 305 + 711
assert m.passed_iterator(iter((-7, 3))) == -4
def test_implementation_details():
lst = [39, 43, 92, 49, 22, 29, 93, 98, 26, 57, 8]
tup = tuple(lst)
assert m.sequence_item_get_ssize_t(lst) == 43
assert m.sequence_item_set_ssize_t(lst) is None
assert lst[1] == "peppa"
assert m.sequence_item_get_size_t(lst) == 92
assert m.sequence_item_set_size_t(lst) is None
assert lst[2] == "george"
assert m.list_item_get_ssize_t(lst) == 49
assert m.list_item_set_ssize_t(lst) is None
assert lst[3] == "rebecca"
assert m.list_item_get_size_t(lst) == 22
assert m.list_item_set_size_t(lst) is None
assert lst[4] == "richard"
assert m.tuple_item_get_ssize_t(tup) == 29
assert m.tuple_item_set_ssize_t() == ("emely", "edmond")
assert m.tuple_item_get_size_t(tup) == 93
assert m.tuple_item_set_size_t() == ("candy", "cat")
def test_external_float_():
r1 = m.square_float_(2.0)
assert r1 == 4.0

View File

@@ -0,0 +1,562 @@
/*
tests/test_sequences_and_iterators.cpp -- supporting Pythons' sequence protocol, iterators,
etc.
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
All rights reserved. Use of this source code is governed by a
BSD-style license that can be found in the LICENSE file.
*/
#include <pybind11/operators.h>
#include <pybind11/stl.h>
#include "constructor_stats.h"
#include "pybind11_tests.h"
#include <algorithm>
#include <utility>
#include <vector>
#ifdef PYBIND11_HAS_OPTIONAL
# include <optional>
#endif // PYBIND11_HAS_OPTIONAL
template <typename T>
class NonZeroIterator {
const T *ptr_;
public:
explicit NonZeroIterator(const T *ptr) : ptr_(ptr) {}
const T &operator*() const { return *ptr_; }
NonZeroIterator &operator++() {
++ptr_;
return *this;
}
};
class NonZeroSentinel {};
template <typename A, typename B>
bool operator==(const NonZeroIterator<std::pair<A, B>> &it, const NonZeroSentinel &) {
return !(*it).first || !(*it).second;
}
/* Iterator where dereferencing returns prvalues instead of references. */
template <typename T>
class NonRefIterator {
const T *ptr_;
public:
explicit NonRefIterator(const T *ptr) : ptr_(ptr) {}
T operator*() const { return T(*ptr_); }
NonRefIterator &operator++() {
++ptr_;
return *this;
}
bool operator==(const NonRefIterator &other) const { return ptr_ == other.ptr_; }
};
class NonCopyableInt {
public:
explicit NonCopyableInt(int value) : value_(value) {}
NonCopyableInt(const NonCopyableInt &) = delete;
NonCopyableInt(NonCopyableInt &&other) noexcept : value_(other.value_) {
other.value_ = -1; // detect when an unwanted move occurs
}
NonCopyableInt &operator=(const NonCopyableInt &) = delete;
NonCopyableInt &operator=(NonCopyableInt &&other) noexcept {
value_ = other.value_;
other.value_ = -1; // detect when an unwanted move occurs
return *this;
}
int get() const { return value_; }
void set(int value) { value_ = value; }
~NonCopyableInt() = default;
private:
int value_;
};
using NonCopyableIntPair = std::pair<NonCopyableInt, NonCopyableInt>;
PYBIND11_MAKE_OPAQUE(std::vector<NonCopyableInt>);
PYBIND11_MAKE_OPAQUE(std::vector<NonCopyableIntPair>);
template <typename PythonType>
py::list test_random_access_iterator(PythonType x) {
if (x.size() < 5) {
throw py::value_error("Please provide at least 5 elements for testing.");
}
auto checks = py::list();
auto assert_equal = [&checks](py::handle a, py::handle b) {
auto result = PyObject_RichCompareBool(a.ptr(), b.ptr(), Py_EQ);
if (result == -1) {
throw py::error_already_set();
}
checks.append(result != 0);
};
auto it = x.begin();
assert_equal(x[0], *it);
assert_equal(x[0], it[0]);
assert_equal(x[1], it[1]);
assert_equal(x[1], *(++it));
assert_equal(x[1], *(it++));
assert_equal(x[2], *it);
assert_equal(x[3], *(it += 1));
assert_equal(x[2], *(--it));
assert_equal(x[2], *(it--));
assert_equal(x[1], *it);
assert_equal(x[0], *(it -= 1));
assert_equal(it->attr("real"), x[0].attr("real"));
assert_equal((it + 1)->attr("real"), x[1].attr("real"));
assert_equal(x[1], *(it + 1));
assert_equal(x[1], *(1 + it));
it += 3;
assert_equal(x[1], *(it - 2));
checks.append(static_cast<std::size_t>(x.end() - x.begin()) == x.size());
checks.append((x.begin() + static_cast<std::ptrdiff_t>(x.size())) == x.end());
checks.append(x.begin() < x.end());
return checks;
}
TEST_SUBMODULE(sequences_and_iterators, m) {
// test_sliceable
class Sliceable {
public:
explicit Sliceable(int n) : size(n) {}
int start, stop, step;
int size;
};
py::class_<Sliceable>(m, "Sliceable")
.def(py::init<int>())
.def("__getitem__", [](const Sliceable &s, const py::slice &slice) {
py::ssize_t start = 0, stop = 0, step = 0, slicelength = 0;
if (!slice.compute(s.size, &start, &stop, &step, &slicelength)) {
throw py::error_already_set();
}
int istart = static_cast<int>(start);
int istop = static_cast<int>(stop);
int istep = static_cast<int>(step);
return std::make_tuple(istart, istop, istep);
});
m.def("make_forward_slice_size_t", []() { return py::slice(0, -1, 1); });
m.def("make_reversed_slice_object",
[]() { return py::slice(py::none(), py::none(), py::int_(-1)); });
#ifdef PYBIND11_HAS_OPTIONAL
m.attr("has_optional") = true;
m.def("make_reversed_slice_size_t_optional_verbose",
[]() { return py::slice(std::nullopt, std::nullopt, -1); });
// Warning: The following spelling may still compile if optional<> is not present and give
// wrong answers. Please use with caution.
m.def("make_reversed_slice_size_t_optional", []() { return py::slice({}, {}, -1); });
#else
m.attr("has_optional") = false;
#endif
// test_sequence
class Sequence {
public:
explicit Sequence(size_t size) : m_size(size) {
print_created(this, "of size", m_size);
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
m_data = new float[size];
memset(m_data, 0, sizeof(float) * size);
}
explicit Sequence(const std::vector<float> &value) : m_size(value.size()) {
print_created(this, "of size", m_size, "from std::vector");
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
m_data = new float[m_size];
memcpy(m_data, &value[0], sizeof(float) * m_size);
}
Sequence(const Sequence &s) : m_size(s.m_size) {
print_copy_created(this);
// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
m_data = new float[m_size];
memcpy(m_data, s.m_data, sizeof(float) * m_size);
}
Sequence(Sequence &&s) noexcept : m_size(s.m_size), m_data(s.m_data) {
print_move_created(this);
s.m_size = 0;
s.m_data = nullptr;
}
~Sequence() {
print_destroyed(this);
delete[] m_data;
}
Sequence &operator=(const Sequence &s) {
if (&s != this) {
delete[] m_data;
m_size = s.m_size;
m_data = new float[m_size];
memcpy(m_data, s.m_data, sizeof(float) * m_size);
}
print_copy_assigned(this);
return *this;
}
Sequence &operator=(Sequence &&s) noexcept {
if (&s != this) {
delete[] m_data;
m_size = s.m_size;
m_data = s.m_data;
s.m_size = 0;
s.m_data = nullptr;
}
print_move_assigned(this);
return *this;
}
bool operator==(const Sequence &s) const {
if (m_size != s.size()) {
return false;
}
for (size_t i = 0; i < m_size; ++i) {
if (m_data[i] != s[i]) {
return false;
}
}
return true;
}
bool operator!=(const Sequence &s) const { return !operator==(s); }
float operator[](size_t index) const { return m_data[index]; }
float &operator[](size_t index) { return m_data[index]; }
bool contains(float v) const {
for (size_t i = 0; i < m_size; ++i) {
if (v == m_data[i]) {
return true;
}
}
return false;
}
Sequence reversed() const {
Sequence result(m_size);
for (size_t i = 0; i < m_size; ++i) {
result[m_size - i - 1] = m_data[i];
}
return result;
}
size_t size() const { return m_size; }
const float *begin() const { return m_data; }
const float *end() const { return m_data + m_size; }
private:
size_t m_size;
float *m_data;
};
py::class_<Sequence>(m, "Sequence")
.def(py::init<size_t>())
.def(py::init<const std::vector<float> &>())
/// Bare bones interface
.def("__getitem__",
[](const Sequence &s, size_t i) {
if (i >= s.size()) {
throw py::index_error();
}
return s[i];
})
.def("__setitem__",
[](Sequence &s, size_t i, float v) {
if (i >= s.size()) {
throw py::index_error();
}
s[i] = v;
})
.def("__len__", &Sequence::size)
/// Optional sequence protocol operations
.def(
"__iter__",
[](const Sequence &s) { return py::make_iterator(s.begin(), s.end()); },
py::keep_alive<0, 1>() /* Essential: keep object alive while iterator exists */)
.def("__contains__", [](const Sequence &s, float v) { return s.contains(v); })
.def("__reversed__", [](const Sequence &s) -> Sequence { return s.reversed(); })
/// Slicing protocol (optional)
.def("__getitem__",
[](const Sequence &s, const py::slice &slice) -> Sequence * {
size_t start = 0, stop = 0, step = 0, slicelength = 0;
if (!slice.compute(s.size(), &start, &stop, &step, &slicelength)) {
throw py::error_already_set();
}
auto *seq = new Sequence(slicelength);
for (size_t i = 0; i < slicelength; ++i) {
(*seq)[i] = s[start];
start += step;
}
return seq;
})
.def("__setitem__",
[](Sequence &s, const py::slice &slice, const Sequence &value) {
size_t start = 0, stop = 0, step = 0, slicelength = 0;
if (!slice.compute(s.size(), &start, &stop, &step, &slicelength)) {
throw py::error_already_set();
}
if (slicelength != value.size()) {
throw std::runtime_error(
"Left and right hand size of slice assignment have different sizes!");
}
for (size_t i = 0; i < slicelength; ++i) {
s[start] = value[i];
start += step;
}
})
/// Comparisons
.def(py::self == py::self)
.def(py::self != py::self)
// Could also define py::self + py::self for concatenation, etc.
;
// test_map_iterator
// Interface of a map-like object that isn't (directly) an unordered_map, but provides some
// basic map-like functionality.
class StringMap {
public:
StringMap() = default;
explicit StringMap(std::unordered_map<std::string, std::string> init)
: map(std::move(init)) {}
void set(const std::string &key, std::string val) { map[key] = std::move(val); }
std::string get(const std::string &key) const { return map.at(key); }
size_t size() const { return map.size(); }
private:
std::unordered_map<std::string, std::string> map;
public:
decltype(map.cbegin()) begin() const { return map.cbegin(); }
decltype(map.cend()) end() const { return map.cend(); }
};
py::class_<StringMap>(m, "StringMap")
.def(py::init<>())
.def(py::init<std::unordered_map<std::string, std::string>>())
.def("__getitem__",
[](const StringMap &map, const std::string &key) {
try {
return map.get(key);
} catch (const std::out_of_range &) {
throw py::key_error("key '" + key + "' does not exist");
}
})
.def("__setitem__", &StringMap::set)
.def("__len__", &StringMap::size)
.def(
"__iter__",
[](const StringMap &map) { return py::make_key_iterator(map.begin(), map.end()); },
py::keep_alive<0, 1>())
.def(
"items",
[](const StringMap &map) { return py::make_iterator(map.begin(), map.end()); },
py::keep_alive<0, 1>())
.def(
"values",
[](const StringMap &map) { return py::make_value_iterator(map.begin(), map.end()); },
py::keep_alive<0, 1>());
// test_generalized_iterators
class IntPairs {
public:
explicit IntPairs(std::vector<std::pair<int, int>> data) : data_(std::move(data)) {}
const std::pair<int, int> *begin() const { return data_.data(); }
// .end() only required for py::make_iterator(self) overload
const std::pair<int, int> *end() const { return data_.data() + data_.size(); }
private:
std::vector<std::pair<int, int>> data_;
};
py::class_<IntPairs>(m, "IntPairs")
.def(py::init<std::vector<std::pair<int, int>>>())
.def(
"nonzero",
[](const IntPairs &s) {
return py::make_iterator(NonZeroIterator<std::pair<int, int>>(s.begin()),
NonZeroSentinel());
},
py::keep_alive<0, 1>())
.def(
"nonzero_keys",
[](const IntPairs &s) {
return py::make_key_iterator(NonZeroIterator<std::pair<int, int>>(s.begin()),
NonZeroSentinel());
},
py::keep_alive<0, 1>())
.def(
"nonzero_values",
[](const IntPairs &s) {
return py::make_value_iterator(NonZeroIterator<std::pair<int, int>>(s.begin()),
NonZeroSentinel());
},
py::keep_alive<0, 1>())
// test iterator that returns values instead of references
.def(
"nonref",
[](const IntPairs &s) {
return py::make_iterator(NonRefIterator<std::pair<int, int>>(s.begin()),
NonRefIterator<std::pair<int, int>>(s.end()));
},
py::keep_alive<0, 1>())
.def(
"nonref_keys",
[](const IntPairs &s) {
return py::make_key_iterator(NonRefIterator<std::pair<int, int>>(s.begin()),
NonRefIterator<std::pair<int, int>>(s.end()));
},
py::keep_alive<0, 1>())
.def(
"nonref_values",
[](const IntPairs &s) {
return py::make_value_iterator(NonRefIterator<std::pair<int, int>>(s.begin()),
NonRefIterator<std::pair<int, int>>(s.end()));
},
py::keep_alive<0, 1>())
// test single-argument make_iterator
.def(
"simple_iterator",
[](IntPairs &self) { return py::make_iterator(self); },
py::keep_alive<0, 1>())
.def(
"simple_keys",
[](IntPairs &self) { return py::make_key_iterator(self); },
py::keep_alive<0, 1>())
.def(
"simple_values",
[](IntPairs &self) { return py::make_value_iterator(self); },
py::keep_alive<0, 1>())
// Test iterator with an Extra (doesn't do anything useful, so not used
// at runtime, but tests need to be able to compile with the correct
// overload. See PR #3293.
.def(
"_make_iterator_extras",
[](IntPairs &self) { return py::make_iterator(self, py::call_guard<int>()); },
py::keep_alive<0, 1>())
.def(
"_make_key_extras",
[](IntPairs &self) { return py::make_key_iterator(self, py::call_guard<int>()); },
py::keep_alive<0, 1>())
.def(
"_make_value_extras",
[](IntPairs &self) { return py::make_value_iterator(self, py::call_guard<int>()); },
py::keep_alive<0, 1>());
// test_iterator_referencing
py::class_<NonCopyableInt>(m, "NonCopyableInt")
.def(py::init<int>())
.def("set", &NonCopyableInt::set)
.def("__int__", &NonCopyableInt::get);
py::class_<std::vector<NonCopyableInt>>(m, "VectorNonCopyableInt")
.def(py::init<>())
.def("append",
[](std::vector<NonCopyableInt> &vec, int value) { vec.emplace_back(value); })
.def("__iter__", [](std::vector<NonCopyableInt> &vec) {
return py::make_iterator(vec.begin(), vec.end());
});
py::class_<std::vector<NonCopyableIntPair>>(m, "VectorNonCopyableIntPair")
.def(py::init<>())
.def("append",
[](std::vector<NonCopyableIntPair> &vec, const std::pair<int, int> &value) {
vec.emplace_back(NonCopyableInt(value.first), NonCopyableInt(value.second));
})
.def("keys",
[](std::vector<NonCopyableIntPair> &vec) {
return py::make_key_iterator(vec.begin(), vec.end());
})
.def("values", [](std::vector<NonCopyableIntPair> &vec) {
return py::make_value_iterator(vec.begin(), vec.end());
});
#if 0
// Obsolete: special data structure for exposing custom iterator types to python
// kept here for illustrative purposes because there might be some use cases which
// are not covered by the much simpler py::make_iterator
struct PySequenceIterator {
PySequenceIterator(const Sequence &seq, py::object ref) : seq(seq), ref(ref) { }
float next() {
if (index == seq.size())
throw py::stop_iteration();
return seq[index++];
}
const Sequence &seq;
py::object ref; // keep a reference
size_t index = 0;
};
py::class_<PySequenceIterator>(seq, "Iterator")
.def("__iter__", [](PySequenceIterator &it) -> PySequenceIterator& { return it; })
.def("__next__", &PySequenceIterator::next);
On the actual Sequence object, the iterator would be constructed as follows:
.def("__iter__", [](py::object s) { return PySequenceIterator(s.cast<const Sequence &>(), s); })
#endif
// test_python_iterator_in_cpp
m.def("object_to_list", [](const py::object &o) {
auto l = py::list();
for (auto item : o) {
l.append(item);
}
return l;
});
m.def("iterator_to_list", [](py::iterator it) {
auto l = py::list();
while (it != py::iterator::sentinel()) {
l.append(*it);
++it;
}
return l;
});
// test_sequence_length: check that Python sequences can be converted to py::sequence.
m.def("sequence_length", [](const py::sequence &seq) { return seq.size(); });
// Make sure that py::iterator works with std algorithms
m.def("count_none", [](const py::object &o) {
return std::count_if(o.begin(), o.end(), [](py::handle h) { return h.is_none(); });
});
m.def("find_none", [](const py::object &o) {
auto it = std::find_if(o.begin(), o.end(), [](py::handle h) { return h.is_none(); });
return it->is_none();
});
m.def("count_nonzeros", [](const py::dict &d) {
return std::count_if(d.begin(), d.end(), [](std::pair<py::handle, py::handle> p) {
return p.second.cast<int>() != 0;
});
});
m.def("tuple_iterator", &test_random_access_iterator<py::tuple>);
m.def("list_iterator", &test_random_access_iterator<py::list>);
m.def("sequence_iterator", &test_random_access_iterator<py::sequence>);
// test_iterator_passthrough
// #181: iterator passthrough did not compile
m.def("iterator_passthrough", [](py::iterator s) -> py::iterator {
return py::make_iterator(std::begin(s), std::end(s));
});
// test_iterator_rvp
// #388: Can't make iterators via make_iterator() with different r/v policies
static std::vector<int> list = {1, 2, 3};
m.def("make_iterator_1",
[]() { return py::make_iterator<py::return_value_policy::copy>(list); });
m.def("make_iterator_2",
[]() { return py::make_iterator<py::return_value_policy::automatic>(list); });
}

View File

@@ -0,0 +1,253 @@
import pytest
from pytest import approx
from pybind11_tests import ConstructorStats
from pybind11_tests import sequences_and_iterators as m
def test_slice_constructors():
assert m.make_forward_slice_size_t() == slice(0, -1, 1)
assert m.make_reversed_slice_object() == slice(None, None, -1)
@pytest.mark.skipif(not m.has_optional, reason="no <optional>")
def test_slice_constructors_explicit_optional():
assert m.make_reversed_slice_size_t_optional() == slice(None, None, -1)
assert m.make_reversed_slice_size_t_optional_verbose() == slice(None, None,
-1)
def test_generalized_iterators():
assert list(m.IntPairs([(1, 2), (3, 4), (0, 5)]).nonzero()) == [(1, 2),
(3, 4)]
assert list(m.IntPairs([(1, 2), (2, 0), (0, 3), (4, 5)]).nonzero()) == [
(1, 2)
]
assert list(m.IntPairs([(0, 3), (1, 2), (3, 4)]).nonzero()) == []
assert list(m.IntPairs([(1, 2), (3, 4), (0, 5)]).nonzero_keys()) == [1, 3]
assert list(m.IntPairs([(1, 2), (2, 0), (0, 3), (4, 5)]).nonzero_keys(
)) == [1]
assert list(m.IntPairs([(0, 3), (1, 2), (3, 4)]).nonzero_keys()) == []
assert list(m.IntPairs([(1, 2), (3, 4), (0, 5)]).nonzero_values(
)) == [2, 4]
assert list(
m.IntPairs([(1, 2), (2, 0), (0, 3), (4, 5)]).nonzero_values()) == [2]
assert list(m.IntPairs([(0, 3), (1, 2), (3, 4)]).nonzero_values()) == []
# __next__ must continue to raise StopIteration
it = m.IntPairs([(0, 0)]).nonzero()
for _ in range(3):
with pytest.raises(StopIteration):
next(it)
it = m.IntPairs([(0, 0)]).nonzero_keys()
for _ in range(3):
with pytest.raises(StopIteration):
next(it)
def test_nonref_iterators():
pairs = m.IntPairs([(1, 2), (3, 4), (0, 5)])
assert list(pairs.nonref()) == [(1, 2), (3, 4), (0, 5)]
assert list(pairs.nonref_keys()) == [1, 3, 0]
assert list(pairs.nonref_values()) == [2, 4, 5]
def test_generalized_iterators_simple():
assert list(m.IntPairs([(1, 2), (3, 4), (0, 5)]).simple_iterator()) == [
(1, 2),
(3, 4),
(0, 5),
]
assert list(m.IntPairs([(1, 2), (3, 4), (0, 5)]).simple_keys(
)) == [1, 3, 0]
assert list(m.IntPairs([(1, 2), (3, 4), (0, 5)]).simple_values(
)) == [2, 4, 5]
def test_iterator_referencing():
"""Test that iterators reference rather than copy their referents."""
vec = m.VectorNonCopyableInt()
vec.append(3)
vec.append(5)
assert [int(x) for x in vec] == [3, 5]
# Increment everything to make sure the referents can be mutated
for x in vec:
x.set(int(x) + 1)
assert [int(x) for x in vec] == [4, 6]
vec = m.VectorNonCopyableIntPair()
vec.append([3, 4])
vec.append([5, 7])
assert [int(x) for x in vec.keys()] == [3, 5]
assert [int(x) for x in vec.values()] == [4, 7]
for x in vec.keys():
x.set(int(x) + 1)
for x in vec.values():
x.set(int(x) + 10)
assert [int(x) for x in vec.keys()] == [4, 6]
assert [int(x) for x in vec.values()] == [14, 17]
def test_sliceable():
sliceable = m.Sliceable(100)
assert sliceable[::] == (0, 100, 1)
assert sliceable[10::] == (10, 100, 1)
assert sliceable[:10:] == (0, 10, 1)
assert sliceable[::10] == (0, 100, 10)
assert sliceable[-10::] == (90, 100, 1)
assert sliceable[:-10:] == (0, 90, 1)
assert sliceable[::-10] == (99, -1, -10)
assert sliceable[50:60:1] == (50, 60, 1)
assert sliceable[50:60:-1] == (50, 60, -1)
def test_sequence():
cstats = ConstructorStats.get(m.Sequence)
s = m.Sequence(5)
assert cstats.values() == ["of size", "5"]
assert "Sequence" in repr(s)
assert len(s) == 5
assert s[0] == 0 and s[3] == 0
assert 12.34 not in s
s[0], s[3] = 12.34, 56.78
assert 12.34 in s
assert s[0] == approx(12.34, rel=1e-05)
assert s[3] == approx(56.78, rel=1e-05)
rev = reversed(s)
assert cstats.values() == ["of size", "5"]
rev2 = s[::-1]
assert cstats.values() == ["of size", "5"]
it = iter(m.Sequence(0))
for _ in range(3): # __next__ must continue to raise StopIteration
with pytest.raises(StopIteration):
next(it)
assert cstats.values() == ["of size", "0"]
expected = [0, 56.78, 0, 0, 12.34]
assert rev == approx(expected, rel=1e-05)
assert rev2 == approx(expected, rel=1e-05)
assert rev == rev2
rev[0::2] = m.Sequence([2.0, 2.0, 2.0])
assert cstats.values() == ["of size", "3", "from std::vector"]
assert rev == approx([2, 56.78, 2, 0, 2], rel=1e-05)
assert cstats.alive() == 4
del it
assert cstats.alive() == 3
del s
assert cstats.alive() == 2
del rev
assert cstats.alive() == 1
del rev2
assert cstats.alive() == 0
assert cstats.values() == []
assert cstats.default_constructions == 0
assert cstats.copy_constructions == 0
assert cstats.move_constructions >= 1
assert cstats.copy_assignments == 0
assert cstats.move_assignments == 0
def test_sequence_length():
"""#2076: Exception raised by len(arg) should be propagated"""
class BadLen(RuntimeError):
pass
class SequenceLike:
def __getitem__(self, i):
return None
def __len__(self):
raise BadLen()
with pytest.raises(BadLen):
m.sequence_length(SequenceLike())
assert m.sequence_length([1, 2, 3]) == 3
assert m.sequence_length("hello") == 5
def test_map_iterator():
sm = m.StringMap({"hi": "bye", "black": "white"})
assert sm["hi"] == "bye"
assert len(sm) == 2
assert sm["black"] == "white"
with pytest.raises(KeyError):
assert sm["orange"]
sm["orange"] = "banana"
assert sm["orange"] == "banana"
expected = {"hi": "bye", "black": "white", "orange": "banana"}
for k in sm:
assert sm[k] == expected[k]
for k, v in sm.items():
assert v == expected[k]
assert list(sm.values()) == [expected[k] for k in sm]
it = iter(m.StringMap({}))
for _ in range(3): # __next__ must continue to raise StopIteration
with pytest.raises(StopIteration):
next(it)
def test_python_iterator_in_cpp():
t = (1, 2, 3)
assert m.object_to_list(t) == [1, 2, 3]
assert m.object_to_list(iter(t)) == [1, 2, 3]
assert m.iterator_to_list(iter(t)) == [1, 2, 3]
with pytest.raises(TypeError) as excinfo:
m.object_to_list(1)
assert "object is not iterable" in str(excinfo.value)
with pytest.raises(TypeError) as excinfo:
m.iterator_to_list(1)
assert "incompatible function arguments" in str(excinfo.value)
def bad_next_call():
raise RuntimeError("py::iterator::advance() should propagate errors")
with pytest.raises(RuntimeError) as excinfo:
m.iterator_to_list(iter(bad_next_call, None))
assert str(
excinfo.value) == "py::iterator::advance() should propagate errors"
lst = [1, None, 0, None]
assert m.count_none(lst) == 2
assert m.find_none(lst) is True
assert m.count_nonzeros({"a": 0, "b": 1, "c": 2}) == 2
r = range(5)
assert all(m.tuple_iterator(tuple(r)))
assert all(m.list_iterator(list(r)))
assert all(m.sequence_iterator(r))
def test_iterator_passthrough():
"""#181: iterator passthrough did not compile"""
from pybind11_tests.sequences_and_iterators import iterator_passthrough
values = [3, 5, 7, 9, 11, 13, 15]
assert list(iterator_passthrough(iter(values))) == values
def test_iterator_rvp():
"""#388: Can't make iterators via make_iterator() with different r/v policies"""
import pybind11_tests.sequences_and_iterators as m
assert list(m.make_iterator_1()) == [1, 2, 3]
assert list(m.make_iterator_2()) == [1, 2, 3]
assert not isinstance(m.make_iterator_1(), type(m.make_iterator_2()))

Some files were not shown because too many files have changed in this diff Show More