mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-20 14:59:27 +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:
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
|
||||
Reference in New Issue
Block a user