mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-11 08:03:55 +00:00
Add Catch framework for testing embedding support and C++-side features
At this point, there is only a single test for interpreter basics. Apart from embedding itself, having a C++ test framework will also benefit the C++-side features by allowing them to be tested directly.
This commit is contained in:
parent
7f5c85c861
commit
9693a5c78f
@ -39,6 +39,7 @@ install:
|
|||||||
if ($env:CONDA -eq "27") { $env:CONDA = "" }
|
if ($env:CONDA -eq "27") { $env:CONDA = "" }
|
||||||
if ($env:PLATFORM -eq "x64") { $env:CONDA = "$env:CONDA-x64" }
|
if ($env:PLATFORM -eq "x64") { $env:CONDA = "$env:CONDA-x64" }
|
||||||
$env:PATH = "C:\Miniconda$env:CONDA\;C:\Miniconda$env:CONDA\Scripts\;$env:PATH"
|
$env:PATH = "C:\Miniconda$env:CONDA\;C:\Miniconda$env:CONDA\Scripts\;$env:PATH"
|
||||||
|
$env:PYTHONHOME = "C:\Miniconda$env:CONDA"
|
||||||
conda install -y -q pytest numpy scipy
|
conda install -y -q pytest numpy scipy
|
||||||
}
|
}
|
||||||
- ps: |
|
- ps: |
|
||||||
@ -46,8 +47,13 @@ install:
|
|||||||
7z x 3.3.3.zip -y > $null
|
7z x 3.3.3.zip -y > $null
|
||||||
$env:CMAKE_INCLUDE_PATH = "eigen-eigen-67e894c6cd8f"
|
$env:CMAKE_INCLUDE_PATH = "eigen-eigen-67e894c6cd8f"
|
||||||
build_script:
|
build_script:
|
||||||
- cmake -G "%CMAKE_GENERATOR%" -A "%CMAKE_ARCH%" -DPYBIND11_CPP_STANDARD=/std:c++%CPP% -DPYBIND11_WERROR=ON -DCMAKE_SUPPRESS_REGENERATION=1
|
- cmake -G "%CMAKE_GENERATOR%" -A "%CMAKE_ARCH%"
|
||||||
|
-DPYBIND11_CPP_STANDARD=/std:c++%CPP%
|
||||||
|
-DPYBIND11_WERROR=ON
|
||||||
|
-DDOWNLOAD_CATCH=ON
|
||||||
|
-DCMAKE_SUPPRESS_REGENERATION=1
|
||||||
- set MSBuildLogger="C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
- set MSBuildLogger="C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||||
- cmake --build . --config Release --target pytest -- /v:m /logger:%MSBuildLogger%
|
- cmake --build . --config Release --target pytest -- /v:m /logger:%MSBuildLogger%
|
||||||
|
- cmake --build . --config Release --target cpptest -- /v:m /logger:%MSBuildLogger%
|
||||||
- cmake --build . --config Release --target test_cmake_build -- /v:m /logger:%MSBuildLogger%
|
- cmake --build . --config Release --target test_cmake_build -- /v:m /logger:%MSBuildLogger%
|
||||||
on_failure: if exist "tests\test_cmake_build" type tests\test_cmake_build\*.log
|
on_failure: if exist "tests\test_cmake_build" type tests\test_cmake_build\*.log*
|
||||||
|
@ -192,8 +192,10 @@ script:
|
|||||||
-DPYBIND11_PYTHON_VERSION=$PYTHON
|
-DPYBIND11_PYTHON_VERSION=$PYTHON
|
||||||
-DPYBIND11_CPP_STANDARD=$CPP
|
-DPYBIND11_CPP_STANDARD=$CPP
|
||||||
-DPYBIND11_WERROR=${WERROR:-ON}
|
-DPYBIND11_WERROR=${WERROR:-ON}
|
||||||
|
-DDOWNLOAD_CATCH=ON
|
||||||
- $SCRIPT_RUN_PREFIX make pytest -j 2
|
- $SCRIPT_RUN_PREFIX make pytest -j 2
|
||||||
|
- $SCRIPT_RUN_PREFIX make cpptest -j 2
|
||||||
- $SCRIPT_RUN_PREFIX make test_cmake_build
|
- $SCRIPT_RUN_PREFIX make test_cmake_build
|
||||||
after_failure: cat tests/test_cmake_build/*.log
|
after_failure: cat tests/test_cmake_build/*.log*
|
||||||
after_script:
|
after_script:
|
||||||
- if [ -n "$DOCKER" ]; then docker stop "$containerid"; docker rm "$containerid"; fi
|
- if [ -n "$DOCKER" ]; then docker stop "$containerid"; docker rm "$containerid"; fi
|
||||||
|
@ -195,6 +195,9 @@ add_custom_command(TARGET pybind11_tests POST_BUILD
|
|||||||
COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/libsize.py
|
COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/tools/libsize.py
|
||||||
$<TARGET_FILE:pybind11_tests> ${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)
|
$<TARGET_FILE:pybind11_tests> ${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt)
|
||||||
|
|
||||||
|
# Test embedding the interpreter. Provides the `cpptest` target.
|
||||||
|
add_subdirectory(test_embed)
|
||||||
|
|
||||||
# Test CMake build using functions and targets from subdirectory or installed location
|
# Test CMake build using functions and targets from subdirectory or installed location
|
||||||
add_custom_target(test_cmake_build)
|
add_custom_target(test_cmake_build)
|
||||||
if(NOT CMAKE_VERSION VERSION_LESS 3.1)
|
if(NOT CMAKE_VERSION VERSION_LESS 3.1)
|
||||||
|
31
tests/test_embed/CMakeLists.txt
Normal file
31
tests/test_embed/CMakeLists.txt
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
if(${PYTHON_MODULE_EXTENSION} MATCHES "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 1.9.3)
|
||||||
|
if(NOT CATCH_FOUND)
|
||||||
|
message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers"
|
||||||
|
" manually or use `cmake -DDOWNLOAD_CATCH=1` to fetch them automatically.")
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_executable(test_embed
|
||||||
|
catch.cpp
|
||||||
|
test_interpreter.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(test_embed PRIVATE ${CATCH_INCLUDE_DIR})
|
||||||
|
pybind11_enable_warnings(test_embed)
|
||||||
|
|
||||||
|
if(NOT CMAKE_VERSION VERSION_LESS 3.0)
|
||||||
|
target_link_libraries(test_embed PRIVATE pybind11::embed)
|
||||||
|
else()
|
||||||
|
target_include_directories(test_embed PRIVATE ${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS})
|
||||||
|
target_compile_options(test_embed PRIVATE ${PYBIND11_CPP_STANDARD})
|
||||||
|
target_link_libraries(test_embed PRIVATE ${PYTHON_LIBRARIES})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_custom_target(cpptest COMMAND $<TARGET_FILE:test_embed>
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
add_dependencies(check cpptest)
|
5
tests/test_embed/catch.cpp
Normal file
5
tests/test_embed/catch.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Catch provides the `int main()` function here. This is a standalone
|
||||||
|
// translation unit to avoid recompiling it for every test change.
|
||||||
|
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include <catch.hpp>
|
64
tests/test_embed/test_interpreter.cpp
Normal file
64
tests/test_embed/test_interpreter.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#include <pybind11/pybind11.h>
|
||||||
|
#include <pybind11/eval.h>
|
||||||
|
|
||||||
|
#include <catch.hpp>
|
||||||
|
|
||||||
|
namespace py = pybind11;
|
||||||
|
using namespace py::literals;
|
||||||
|
|
||||||
|
class Widget {
|
||||||
|
public:
|
||||||
|
Widget(std::string message) : message(message) { }
|
||||||
|
virtual ~Widget() = default;
|
||||||
|
|
||||||
|
std::string the_message() const { return message; }
|
||||||
|
virtual int the_answer() const = 0;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PyWidget final : public Widget {
|
||||||
|
using Widget::Widget;
|
||||||
|
|
||||||
|
int the_answer() const override { PYBIND11_OVERLOAD_PURE(int, Widget, the_answer); }
|
||||||
|
};
|
||||||
|
|
||||||
|
PyObject *make_embedded_module() {
|
||||||
|
py::module m("widget_module");
|
||||||
|
|
||||||
|
py::class_<Widget, PyWidget>(m, "Widget")
|
||||||
|
.def(py::init<std::string>())
|
||||||
|
.def_property_readonly("the_message", &Widget::the_message);
|
||||||
|
|
||||||
|
return m.ptr();
|
||||||
|
}
|
||||||
|
|
||||||
|
py::object import_file(const std::string &module, const std::string &path, py::object globals) {
|
||||||
|
auto locals = py::dict("module_name"_a=module, "path"_a=path);
|
||||||
|
py::eval<py::eval_statements>(
|
||||||
|
"import imp\n"
|
||||||
|
"with open(path) as file:\n"
|
||||||
|
" new_module = imp.load_module(module_name, file, path, ('py', 'U', imp.PY_SOURCE))",
|
||||||
|
globals, locals
|
||||||
|
);
|
||||||
|
return locals["new_module"];
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Pass classes and data between modules defined in C++ and Python") {
|
||||||
|
PyImport_AppendInittab("widget_module", &make_embedded_module);
|
||||||
|
Py_Initialize();
|
||||||
|
{
|
||||||
|
auto globals = py::module::import("__main__").attr("__dict__");
|
||||||
|
auto module = import_file("widget", "test_interpreter.py", globals);
|
||||||
|
REQUIRE(py::hasattr(module, "DerivedWidget"));
|
||||||
|
|
||||||
|
auto py_widget = module.attr("DerivedWidget")("Hello, World!");
|
||||||
|
auto message = py_widget.attr("the_message");
|
||||||
|
REQUIRE(message.cast<std::string>() == "Hello, World!");
|
||||||
|
|
||||||
|
const auto &cpp_widget = py_widget.cast<const Widget &>();
|
||||||
|
REQUIRE(cpp_widget.the_answer() == 42);
|
||||||
|
}
|
||||||
|
Py_Finalize();
|
||||||
|
}
|
9
tests/test_embed/test_interpreter.py
Normal file
9
tests/test_embed/test_interpreter.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from widget_module import Widget
|
||||||
|
|
||||||
|
|
||||||
|
class DerivedWidget(Widget):
|
||||||
|
def __init__(self, message):
|
||||||
|
super(DerivedWidget, self).__init__(message)
|
||||||
|
|
||||||
|
def the_answer(self):
|
||||||
|
return 42
|
57
tools/FindCatch.cmake
Normal file
57
tools/FindCatch.cmake
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# - Find the Catch test framework or download it (single header)
|
||||||
|
#
|
||||||
|
# This is a quick module for internal use. It assumes that Catch is
|
||||||
|
# REQUIRED and that a minimum version is provided (not EXACT). If
|
||||||
|
# a suitable version isn't found locally, the single header file
|
||||||
|
# will be downloaded and placed in the build dir: PROJECT_BINARY_DIR.
|
||||||
|
#
|
||||||
|
# This code sets the following variables:
|
||||||
|
# CATCH_INCLUDE_DIR - path to catch.hpp
|
||||||
|
# CATCH_VERSION - version number
|
||||||
|
|
||||||
|
if(NOT Catch_FIND_VERSION)
|
||||||
|
message(FATAL_ERROR "A version number must be specified.")
|
||||||
|
elseif(Catch_FIND_REQUIRED)
|
||||||
|
message(FATAL_ERROR "This module assumes Catch is not required.")
|
||||||
|
elseif(Catch_FIND_VERSION_EXACT)
|
||||||
|
message(FATAL_ERROR "Exact version numbers are not supported, only minimum.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Extract the version number from catch.hpp
|
||||||
|
function(_get_catch_version)
|
||||||
|
file(STRINGS "${CATCH_INCLUDE_DIR}/catch.hpp" version_line REGEX "Catch v.*" LIMIT_COUNT 1)
|
||||||
|
if(version_line MATCHES "Catch v([0-9]+)\\.([0-9]+)\\.([0-9]+)")
|
||||||
|
set(CATCH_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}" PARENT_SCOPE)
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Download the single-header version of Catch
|
||||||
|
function(_download_catch version destination_dir)
|
||||||
|
message(STATUS "Downloading catch v${version}...")
|
||||||
|
set(url https://github.com/philsquared/Catch/releases/download/v${version}/catch.hpp)
|
||||||
|
file(DOWNLOAD ${url} "${destination_dir}/catch.hpp" STATUS status)
|
||||||
|
list(GET status 0 error)
|
||||||
|
if(error)
|
||||||
|
message(FATAL_ERROR "Could not download ${url}")
|
||||||
|
endif()
|
||||||
|
set(CATCH_INCLUDE_DIR "${destination_dir}" CACHE INTERNAL "")
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
# Look for catch locally
|
||||||
|
find_path(CATCH_INCLUDE_DIR NAMES catch.hpp PATH_SUFFIXES catch)
|
||||||
|
if(CATCH_INCLUDE_DIR)
|
||||||
|
_get_catch_version()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Download the header if it wasn't found or if it's outdated
|
||||||
|
if(NOT CATCH_VERSION OR CATCH_VERSION VERSION_LESS ${Catch_FIND_VERSION})
|
||||||
|
if(DOWNLOAD_CATCH)
|
||||||
|
_download_catch(${Catch_FIND_VERSION} "${PROJECT_BINARY_DIR}/catch/")
|
||||||
|
_get_catch_version()
|
||||||
|
else()
|
||||||
|
set(CATCH_FOUND FALSE)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CATCH_FOUND TRUE)
|
Loading…
Reference in New Issue
Block a user