diff options
Diffstat (limited to 'ext/pybind11/tests')
73 files changed, 8401 insertions, 0 deletions
diff --git a/ext/pybind11/tests/CMakeLists.txt b/ext/pybind11/tests/CMakeLists.txt new file mode 100644 index 000000000..27cb65291 --- /dev/null +++ b/ext/pybind11/tests/CMakeLists.txt @@ -0,0 +1,159 @@ +if(NOT CMAKE_BUILD_TYPE AND NOT 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() + +# Full set of test files (you can override these; see below) +set(PYBIND11_TEST_FILES + test_alias_initialization.cpp + test_buffers.cpp + test_callbacks.cpp + test_chrono.cpp + test_class_args.cpp + test_constants_and_functions.cpp + test_copy_move_policies.cpp + test_docstring_options.cpp + test_eigen.cpp + test_enum.cpp + test_eval.cpp + test_exceptions.cpp + test_inheritance.cpp + test_issues.cpp + test_keep_alive.cpp + test_kwargs_and_defaults.cpp + test_methods_and_attributes.cpp + test_modules.cpp + test_multiple_inheritance.cpp + test_numpy_array.cpp + test_numpy_dtypes.cpp + test_numpy_vectorize.cpp + test_opaque_types.cpp + test_operator_overloading.cpp + test_pickling.cpp + test_python_types.cpp + test_sequences_and_iterators.cpp + test_smart_ptr.cpp + test_stl_binders.cpp + test_virtual_functions.cpp +) + +# Invoking cmake with something like: +# cmake -DPYBIND11_TEST_OVERRIDE="test_issues.cpp;test_picking.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) + set(PYBIND11_TEST_FILES ${PYBIND11_TEST_OVERRIDE}) +endif() + +string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}") + +# 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) + find_package(Eigen3 QUIET) + + if(EIGEN3_FOUND) + 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") + endif() +endif() + +# Create the binding library +pybind11_add_module(pybind11_tests pybind11_tests.cpp + ${PYBIND11_TEST_FILES} ${PYBIND11_HEADERS}) + +pybind11_enable_warnings(pybind11_tests) + +if(EIGEN3_FOUND) + target_include_directories(pybind11_tests PRIVATE ${EIGEN3_INCLUDE_DIR}) + target_compile_definitions(pybind11_tests PRIVATE -DPYBIND11_TEST_EIGEN) +endif() + +set(testdir ${PROJECT_SOURCE_DIR}/tests) + +# Always write the output file directly into the 'tests' directory (even on MSVC) +if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) + set_target_properties(pybind11_tests PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${testdir}) + foreach(config ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${config} config) + set_target_properties(pybind11_tests PROPERTIES LIBRARY_OUTPUT_DIRECTORY_${config} ${testdir}) + endforeach() +endif() + +# Make sure pytest is found or produce a fatal error +if(NOT PYBIND11_PYTEST_FOUND) + execute_process(COMMAND ${PYTHON_EXECUTABLE} -m pytest --version --noconftest OUTPUT_QUIET ERROR_QUIET + RESULT_VARIABLE PYBIND11_EXEC_PYTHON_ERR) + if(PYBIND11_EXEC_PYTHON_ERR) + message(FATAL_ERROR "Running the tests requires pytest. Please install it manually (try: ${PYTHON_EXECUTABLE} -m pip install pytest)") + endif() + set(PYBIND11_PYTEST_FOUND TRUE CACHE INTERNAL "") +endif() + +# A single command to compile and run the tests +add_custom_target(pytest COMMAND ${PYTHON_EXECUTABLE} -m pytest -rws ${PYBIND11_PYTEST_FILES} + DEPENDS pybind11_tests WORKING_DIRECTORY ${testdir}) + +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() + +# test use of installation +if(PYBIND11_INSTALL) + # 2.8.12 needed for test_installed_module + # 3.0 needed for interface library for test_installed_target + # 3.1 needed for cmake -E env for testing + if(NOT CMAKE_VERSION VERSION_LESS 3.1) + add_custom_target(test_installed_target + COMMAND ${CMAKE_COMMAND} + "-DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/test_install" + -P "${PROJECT_BINARY_DIR}/cmake_install.cmake" + COMMAND ${CMAKE_CTEST_COMMAND} + --build-and-test "${CMAKE_CURRENT_SOURCE_DIR}/test_installed_target" + "${CMAKE_CURRENT_BINARY_DIR}/test_installed_target" + --build-noclean + --build-generator ${CMAKE_GENERATOR} + $<$<BOOL:${CMAKE_GENERATOR_PLATFORM}>:--build-generator-platform> ${CMAKE_GENERATOR_PLATFORM} + --build-makeprogram ${CMAKE_MAKE_PROGRAM} + --build-target check + --build-options "-DCMAKE_PREFIX_PATH=${PROJECT_BINARY_DIR}/test_install" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}" + "-DPYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}" + ) + add_custom_target(test_installed_module + COMMAND ${CMAKE_COMMAND} + "-DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/test_install" + -P "${PROJECT_BINARY_DIR}/cmake_install.cmake" + COMMAND ${CMAKE_CTEST_COMMAND} + --build-and-test "${CMAKE_CURRENT_SOURCE_DIR}/test_installed_module" + "${CMAKE_CURRENT_BINARY_DIR}/test_installed_module" + --build-noclean + --build-generator ${CMAKE_GENERATOR} + $<$<BOOL:${CMAKE_GENERATOR_PLATFORM}>:--build-generator-platform> ${CMAKE_GENERATOR_PLATFORM} + --build-makeprogram ${CMAKE_MAKE_PROGRAM} + --build-target check + --build-options "-DCMAKE_PREFIX_PATH=${PROJECT_BINARY_DIR}/test_install" + "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}" + "-DPYTHON_EXECUTABLE=${PYTHON_EXECUTABLE}" + "-DPYBIND11_CPP_STANDARD=${PYBIND11_CPP_STANDARD}" + ) + else() + add_custom_target(test_installed_target) + add_custom_target(test_installed_module) + endif() + add_custom_target(test_install) + add_dependencies(test_install test_installed_target test_installed_module) +endif() + +# And another to show the .so size and, if a previous size, compare it: +add_custom_command(TARGET pybind11_tests POST_BUILD + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/libsize.py + $<TARGET_FILE:pybind11_tests> ${CMAKE_CURRENT_BINARY_DIR}/sosize-$<TARGET_FILE_NAME:pybind11_tests>.txt) diff --git a/ext/pybind11/tests/conftest.py b/ext/pybind11/tests/conftest.py new file mode 100644 index 000000000..d4335fc6d --- /dev/null +++ b/ext/pybind11/tests/conftest.py @@ -0,0 +1,227 @@ +"""pytest configuration + +Extends output capture as needed by pybind11: ignore constructors, optional unordered lines. +Adds docstring and exceptions message sanitizers: ignore Python 2 vs 3 differences. +""" + +import pytest +import textwrap +import difflib +import re +import sys +import contextlib + +_unicode_marker = re.compile(r'u(\'[^\']*\')') +_long_marker = re.compile(r'([0-9])L') +_hexadecimal = re.compile(r'0x[0-9a-fA-F]+') + + +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(object): + """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(object): + def __init__(self, capfd): + self.capfd = capfd + self.out = "" + self.err = "" + + def __enter__(self): + self.capfd.readouterr() + return self + + def __exit__(self, *_): + 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(capfd): + """Extended `capfd` with context manager and custom equality operators""" + return Capture(capfd) + + +class SanitizedString(object): + 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 = s.replace("unicode", "str") + s = _long_marker.sub(r"\1", s) + s = _unicode_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 pytest_namespace(): + """Add import suppression and test requirements to `pytest` namespace""" + try: + import numpy as np + except ImportError: + np = None + try: + import scipy + except ImportError: + scipy = None + try: + from pybind11_tests import have_eigen + except ImportError: + have_eigen = False + + skipif = pytest.mark.skipif + return { + 'suppress': suppress, + 'requires_numpy': skipif(not np, reason="numpy is not installed"), + 'requires_scipy': skipif(not np, reason="scipy is not installed"), + 'requires_eigen_and_numpy': skipif(not have_eigen or not np, + reason="eigen and/or numpy are not installed"), + 'requires_eigen_and_scipy': skipif(not have_eigen or not scipy, + reason="eigen and/or scipy are not installed"), + } + + +def _test_import_pybind11(): + """Early diagnostic for test module initialization errors + + When there is an error during initialization, the first import will report the + real error while all subsequent imports will report nonsense. This import test + is done early (in the pytest configuration file, before any tests) in order to + avoid the noise of having all tests fail with identical error messages. + + Any possible exception is caught here and reported manually *without* the stack + trace. This further reduces noise since the trace would only show pytest internals + which are not useful for debugging pybind11 module issues. + """ + # noinspection PyBroadException + try: + import pybind11_tests # noqa: F401 imported but unused + except Exception as e: + print("Failed to import pybind11_tests from pytest:") + print(" {}: {}".format(type(e).__name__, e)) + sys.exit(1) + + +_test_import_pybind11() diff --git a/ext/pybind11/tests/constructor_stats.h b/ext/pybind11/tests/constructor_stats.h new file mode 100644 index 000000000..eb3e49cab --- /dev/null +++ b/ext/pybind11/tests/constructor_stats.h @@ -0,0 +1,249 @@ +#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 example .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 example 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 <unordered_map> +#include <list> +#include <typeindex> +#include <sstream> + +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()"); + } + + int alive() { + // Force garbage collection to ensure any pending destructors are invoked: + py::module::import("gc").attr("collect")(); + 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() { + 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(class_.ptr()); + for (auto &p : internals.registered_types_cpp) { + if (p.second == type_info) { + if (t1) { + t2 = &p.first; + break; + } + t1 = &p.first; + } + } + } + catch (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...); +} + diff --git a/ext/pybind11/tests/object.h b/ext/pybind11/tests/object.h new file mode 100644 index 000000000..753f654b2 --- /dev/null +++ b/ext/pybind11/tests/object.h @@ -0,0 +1,175 @@ +#if !defined(__OBJECT_H) +#define __OBJECT_H + +#include <atomic> +#include "constructor_stats.h" + +/// 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 + 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) : 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) { + 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) { + 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 + operator T* () { return m_ptr; } + + /// Return a const pointer to the referenced object + T* get() { return m_ptr; } + + /// Return a pointer to the referenced object + const T* get() const { return m_ptr; } +private: + T *m_ptr; +}; + +#endif /* __OBJECT_H */ diff --git a/ext/pybind11/tests/pybind11_tests.cpp b/ext/pybind11/tests/pybind11_tests.cpp new file mode 100644 index 000000000..9c593eee1 --- /dev/null +++ b/ext/pybind11/tests/pybind11_tests.cpp @@ -0,0 +1,45 @@ +/* + 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" + +std::list<std::function<void(py::module &)>> &initializers() { + static std::list<std::function<void(py::module &)>> inits; + return inits; +} + +test_initializer::test_initializer(std::function<void(py::module &)> initializer) { + initializers().push_back(std::move(initializer)); +} + +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); +} + +PYBIND11_PLUGIN(pybind11_tests) { + py::module m("pybind11_tests", "pybind example plugin"); + + bind_ConstructorStats(m); + + for (const auto &initializer : initializers()) + initializer(m); + + if (!py::hasattr(m, "have_eigen")) m.attr("have_eigen") = false; + + return m.ptr(); +} diff --git a/ext/pybind11/tests/pybind11_tests.h b/ext/pybind11/tests/pybind11_tests.h new file mode 100644 index 000000000..c11b687b2 --- /dev/null +++ b/ext/pybind11/tests/pybind11_tests.h @@ -0,0 +1,12 @@ +#pragma once +#include <pybind11/pybind11.h> +#include <functional> +#include <list> + +namespace py = pybind11; +using namespace pybind11::literals; + +class test_initializer { +public: + test_initializer(std::function<void(py::module &)> initializer); +}; diff --git a/ext/pybind11/tests/test_alias_initialization.cpp b/ext/pybind11/tests/test_alias_initialization.cpp new file mode 100644 index 000000000..48e595695 --- /dev/null +++ b/ext/pybind11/tests/test_alias_initialization.cpp @@ -0,0 +1,62 @@ +/* + tests/test_alias_initialization.cpp -- test cases and example of different trampoline + initialization modes + + Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>, 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_tests.h" + +test_initializer alias_initialization([](py::module &m) { + // don't invoke Python dispatch classes by default when instantiating C++ classes that were not + // extended on the Python side + struct A { + virtual ~A() {} + virtual void f() { py::print("A.f()"); } + }; + + struct PyA : A { + PyA() { py::print("PyA.PyA()"); } + ~PyA() { py::print("PyA.~PyA()"); } + + void f() override { + py::print("PyA.f()"); + PYBIND11_OVERLOAD(void, A, f); + } + }; + + auto call_f = [](A *a) { a->f(); }; + + py::class_<A, PyA>(m, "A") + .def(py::init<>()) + .def("f", &A::f); + + m.def("call_f", call_f); + + + // ... unless we explicitly request it, as in this example: + struct A2 { + virtual ~A2() {} + virtual void f() { py::print("A2.f()"); } + }; + + struct PyA2 : A2 { + PyA2() { py::print("PyA2.PyA2()"); } + ~PyA2() { py::print("PyA2.~PyA2()"); } + void f() override { + py::print("PyA2.f()"); + PYBIND11_OVERLOAD(void, A2, f); + } + }; + + py::class_<A2, PyA2>(m, "A2") + .def(py::init_alias<>()) + .def("f", &A2::f); + + m.def("call_f", [](A2 *a2) { a2->f(); }); + +}); + diff --git a/ext/pybind11/tests/test_alias_initialization.py b/ext/pybind11/tests/test_alias_initialization.py new file mode 100644 index 000000000..0ed9d2f79 --- /dev/null +++ b/ext/pybind11/tests/test_alias_initialization.py @@ -0,0 +1,79 @@ +import gc + + +def test_alias_delay_initialization1(capture): + """A only initializes its trampoline class when we inherit from it; if we just + create and use an A instance directly, the trampoline initialization is bypassed + and we only initialize an A() instead (for performance reasons). + """ + from pybind11_tests import A, call_f + + class B(A): + def __init__(self): + super(B, self).__init__() + + def f(self): + print("In python f()") + + # C++ version + with capture: + a = A() + call_f(a) + del a + gc.collect() + assert capture == "A.f()" + + # Python version + with capture: + b = B() + call_f(b) + del b + gc.collect() + assert capture == """ + PyA.PyA() + PyA.f() + In python f() + PyA.~PyA() + """ + + +def test_alias_delay_initialization2(capture): + """A2, unlike the above, is configured to always initialize the alias; while + the extra initialization and extra class layer has small virtual dispatch + performance penalty, it also allows us to do more things with the trampoline + class such as defining local variables and performing construction/destruction. + """ + from pybind11_tests import A2, call_f + + class B2(A2): + def __init__(self): + super(B2, self).__init__() + + def f(self): + print("In python B2.f()") + + # No python subclass version + with capture: + a2 = A2() + call_f(a2) + del a2 + gc.collect() + assert capture == """ + PyA2.PyA2() + PyA2.f() + A2.f() + PyA2.~PyA2() + """ + + # Python subclass version + with capture: + b2 = B2() + call_f(b2) + del b2 + gc.collect() + assert capture == """ + PyA2.PyA2() + PyA2.f() + In python B2.f() + PyA2.~PyA2() + """ diff --git a/ext/pybind11/tests/test_buffers.cpp b/ext/pybind11/tests/test_buffers.cpp new file mode 100644 index 000000000..c3a7a9e02 --- /dev/null +++ b/ext/pybind11/tests/test_buffers.cpp @@ -0,0 +1,117 @@ +/* + 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_tests.h" +#include "constructor_stats.h" + +class Matrix { +public: + Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) { + print_created(this, std::to_string(m_rows) + "x" + std::to_string(m_cols) + " matrix"); + m_data = new float[rows*cols]; + memset(m_data, 0, sizeof(float) * 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"); + m_data = new float[m_rows * m_cols]; + memcpy(m_data, s.m_data, sizeof(float) * m_rows * m_cols); + } + + Matrix(Matrix &&s) : 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) { + 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[m_rows * m_cols]; + memcpy(m_data, s.m_data, sizeof(float) * m_rows * m_cols); + return *this; + } + + Matrix &operator=(Matrix &&s) { + 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()(size_t i, size_t j) const { + return m_data[i*m_cols + j]; + } + + float &operator()(size_t i, size_t j) { + return m_data[i*m_cols + j]; + } + + float *data() { return m_data; } + + size_t rows() const { return m_rows; } + size_t cols() const { return m_cols; } +private: + size_t m_rows; + size_t m_cols; + float *m_data; +}; + +test_initializer buffers([](py::module &m) { + py::class_<Matrix> mtx(m, "Matrix"); + + mtx.def(py::init<size_t, size_t>()) + /// Construct from a buffer + .def("__init__", [](Matrix &v, 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!"); + new (&v) Matrix(info.shape[0], info.shape[1]); + memcpy(v.data(), info.ptr, sizeof(float) * v.rows() * v.cols()); + }) + + .def("rows", &Matrix::rows) + .def("cols", &Matrix::cols) + + /// Bare bones interface + .def("__getitem__", [](const Matrix &m, std::pair<size_t, size_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<size_t, size_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 */ + sizeof(float), /* Size of one scalar */ + py::format_descriptor<float>::format(), /* Python struct-style format descriptor */ + 2, /* Number of dimensions */ + { m.rows(), m.cols() }, /* Buffer dimensions */ + { sizeof(float) * m.rows(), /* Strides (in bytes) for each index */ + sizeof(float) } + ); + }) + ; +}); diff --git a/ext/pybind11/tests/test_buffers.py b/ext/pybind11/tests/test_buffers.py new file mode 100644 index 000000000..f0ea964d9 --- /dev/null +++ b/ext/pybind11/tests/test_buffers.py @@ -0,0 +1,57 @@ +import pytest +from pybind11_tests import Matrix, ConstructorStats + +with pytest.suppress(ImportError): + import numpy as np + + +@pytest.requires_numpy +def test_to_python(): + m = Matrix(5, 5) + + assert m[2, 3] == 0 + m[2, 3] = 4 + assert m[2, 3] == 4 + + m2 = np.array(m, copy=False) + assert m2.shape == (5, 5) + assert abs(m2).sum() == 4 + assert m2[2, 3] == 4 + m2[2, 3] = 5 + assert m2[2, 3] == 5 + + cstats = ConstructorStats.get(Matrix) + assert cstats.alive() == 1 + del m + assert cstats.alive() == 1 + del m2 # holds an m reference + assert cstats.alive() == 0 + assert cstats.values() == ["5x5 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 + + +@pytest.requires_numpy +def test_from_python(): + with pytest.raises(RuntimeError) as excinfo: + 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 = Matrix(m3) + + for i in range(m4.rows()): + for j in range(m4.cols()): + assert m3[i, j] == m4[i, j] + + cstats = ConstructorStats.get(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 diff --git a/ext/pybind11/tests/test_callbacks.cpp b/ext/pybind11/tests/test_callbacks.cpp new file mode 100644 index 000000000..31d4e39aa --- /dev/null +++ b/ext/pybind11/tests/test_callbacks.cpp @@ -0,0 +1,149 @@ +/* + 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_tests.h" +#include "constructor_stats.h" +#include <pybind11/functional.h> + + +py::object test_callback1(py::object func) { + return func(); +} + +py::tuple test_callback2(py::object func) { + return func("Hello", 'x', true, 5); +} + +std::string test_callback3(const std::function<int(int)> &func) { + return "func(43) = " + std::to_string(func(43)); +} + +std::function<int(int)> test_callback4() { + return [](int i) { return i+1; }; +} + +py::cpp_function test_callback5() { + return py::cpp_function([](int i) { return i+1; }, + py::arg("number")); +} + +int dummy_function(int i) { return i + 1; } +int dummy_function2(int i, int j) { return i + j; } +std::function<int(int)> 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; +} + +std::string test_dummy_function(const std::function<int(int)> &f) { + using fn_type = int (*)(int); + 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); + } else if (*result == dummy_function) { + auto r = (*result)(1); + return "matches dummy_function: eval(1) = " + std::to_string(r); + } else { + return "argument does NOT match dummy_function. This should never happen!"; + } +} + +struct Payload { + Payload() { + print_default_created(this); + } + ~Payload() { + print_destroyed(this); + } + Payload(const Payload &) { + print_copy_created(this); + } + Payload(Payload &&) { + print_move_created(this); + } +}; + +/// Something to trigger a conversion error +struct Unregistered {}; + +test_initializer callbacks([](py::module &m) { + m.def("test_callback1", &test_callback1); + m.def("test_callback2", &test_callback2); + m.def("test_callback3", &test_callback3); + m.def("test_callback4", &test_callback4); + m.def("test_callback5", &test_callback5); + + // Test keyword args and generalized unpacking + m.def("test_tuple_unpacking", [](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", [](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", [](py::function f) { + return f("x"_a=10, "y"_a=20); + }); + + m.def("test_unpacking_and_keywords1", [](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", [](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", [](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", [](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", [](py::function f) { + f(234, Unregistered(), "kw"_a=567); + }); + + m.def("test_arg_conversion_error2", [](py::function f) { + f(234, "expected_name"_a=Unregistered(), "kw"_a=567); + }); + + /* Test cleanup of lambda closure */ + m.def("test_cleanup", []() -> std::function<void(void)> { + Payload p; + + return [p]() { + /* p should be cleaned up when the returned function is garbage collected */ + }; + }); + + /* Test if passing a function pointer from C++ -> Python -> C++ yields the original pointer */ + m.def("dummy_function", &dummy_function); + m.def("dummy_function2", &dummy_function2); + m.def("roundtrip", &roundtrip, py::arg("f"), py::arg("expect_none")=false); + m.def("test_dummy_function", &test_dummy_function); + // Export the payload constructor statistics for testing purposes: + m.def("payload_cstats", &ConstructorStats::get<Payload>); +}); diff --git a/ext/pybind11/tests/test_callbacks.py b/ext/pybind11/tests/test_callbacks.py new file mode 100644 index 000000000..c2668aa95 --- /dev/null +++ b/ext/pybind11/tests/test_callbacks.py @@ -0,0 +1,98 @@ +import pytest + + +def test_callbacks(): + from functools import partial + from pybind11_tests import (test_callback1, test_callback2, test_callback3, + test_callback4, test_callback5) + + def func1(): + return "func1" + + def func2(a, b, c, d): + return "func2", a, b, c, d + + def func3(a): + return "func3({})".format(a) + + assert test_callback1(func1) == "func1" + assert test_callback2(func2) == ("func2", "Hello", "x", True, 5) + assert test_callback1(partial(func2, 1, 2, 3, 4)) == ("func2", 1, 2, 3, 4) + assert test_callback1(partial(func3, "partial")) == "func3(partial)" + assert test_callback3(lambda i: i + 1) == "func(43) = 44" + + f = test_callback4() + assert f(43) == 44 + f = test_callback5() + assert f(number=43) == 44 + + +def test_keyword_args_and_generalized_unpacking(): + from pybind11_tests import (test_tuple_unpacking, test_dict_unpacking, test_keyword_args, + test_unpacking_and_keywords1, test_unpacking_and_keywords2, + test_unpacking_error1, test_unpacking_error2, + test_arg_conversion_error1, test_arg_conversion_error2) + + def f(*args, **kwargs): + return args, kwargs + + assert test_tuple_unpacking(f) == (("positional", 1, 2, 3, 4, 5, 6), {}) + assert test_dict_unpacking(f) == (("positional", 1), {"key": "value", "a": 1, "b": 2}) + assert test_keyword_args(f) == ((), {"x": 10, "y": 20}) + assert test_unpacking_and_keywords1(f) == ((1, 2), {"c": 3, "d": 4}) + assert 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: + test_unpacking_error1(f) + assert "Got multiple values for keyword argument" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + test_unpacking_error2(f) + assert "Got multiple values for keyword argument" in str(excinfo.value) + + with pytest.raises(RuntimeError) as excinfo: + test_arg_conversion_error1(f) + assert "Unable to convert call argument" in str(excinfo.value) + + with pytest.raises(RuntimeError) as excinfo: + test_arg_conversion_error2(f) + assert "Unable to convert call argument" in str(excinfo.value) + + +def test_lambda_closure_cleanup(): + from pybind11_tests import test_cleanup, payload_cstats + + test_cleanup() + cstats = payload_cstats() + assert cstats.alive() == 0 + assert cstats.copy_constructions == 1 + assert cstats.move_constructions >= 1 + + +def test_cpp_function_roundtrip(): + """Test if passing a function pointer from C++ -> Python -> C++ yields the original pointer""" + from pybind11_tests import dummy_function, dummy_function2, test_dummy_function, roundtrip + + assert test_dummy_function(dummy_function) == "matches dummy_function: eval(1) = 2" + assert test_dummy_function(roundtrip(dummy_function)) == "matches dummy_function: eval(1) = 2" + assert roundtrip(None, expect_none=True) is None + assert test_dummy_function(lambda x: x + 2) == "can't convert to function pointer: eval(1) = 3" + + with pytest.raises(TypeError) as excinfo: + test_dummy_function(dummy_function2) + assert "incompatible function arguments" in str(excinfo.value) + + with pytest.raises(TypeError) as excinfo: + 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): + from pybind11_tests import test_callback3, test_callback4 + + assert doc(test_callback3) == "test_callback3(arg0: Callable[[int], int]) -> str" + assert doc(test_callback4) == "test_callback4() -> Callable[[int], int]" diff --git a/ext/pybind11/tests/test_chrono.cpp b/ext/pybind11/tests/test_chrono.cpp new file mode 100644 index 000000000..b86f57adf --- /dev/null +++ b/ext/pybind11/tests/test_chrono.cpp @@ -0,0 +1,59 @@ +/* + 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_tests.h" +#include "constructor_stats.h" +#include <pybind11/chrono.h> + +// Return the current time off the wall clock +std::chrono::system_clock::time_point test_chrono1() { + return std::chrono::system_clock::now(); +} + +// Round trip the passed in system clock time +std::chrono::system_clock::time_point test_chrono2(std::chrono::system_clock::time_point t) { + return t; +} + +// Round trip the passed in duration +std::chrono::system_clock::duration test_chrono3(std::chrono::system_clock::duration d) { + return d; +} + +// Difference between two passed in time_points +std::chrono::system_clock::duration test_chrono4(std::chrono::system_clock::time_point a, std::chrono::system_clock::time_point b) { + return a - b; +} + +// Return the current time off the steady_clock +std::chrono::steady_clock::time_point test_chrono5() { + return std::chrono::steady_clock::now(); +} + +// Round trip a steady clock timepoint +std::chrono::steady_clock::time_point test_chrono6(std::chrono::steady_clock::time_point t) { + return t; +} + +// Roundtrip a duration in microseconds from a float argument +std::chrono::microseconds test_chrono7(std::chrono::microseconds t) { + return t; +} + +test_initializer chrono([] (py::module &m) { + m.def("test_chrono1", &test_chrono1); + m.def("test_chrono2", &test_chrono2); + m.def("test_chrono3", &test_chrono3); + m.def("test_chrono4", &test_chrono4); + m.def("test_chrono5", &test_chrono5); + m.def("test_chrono6", &test_chrono6); + m.def("test_chrono7", &test_chrono7); +}); diff --git a/ext/pybind11/tests/test_chrono.py b/ext/pybind11/tests/test_chrono.py new file mode 100644 index 000000000..94ca55c76 --- /dev/null +++ b/ext/pybind11/tests/test_chrono.py @@ -0,0 +1,116 @@ + + +def test_chrono_system_clock(): + from pybind11_tests import test_chrono1 + import datetime + + # Get the time from both c++ and datetime + date1 = 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 = abs(date1 - date2) + + # There should never be a days/seconds difference + assert diff.days == 0 + assert diff.seconds == 0 + + # We test that no more than about 0.5 seconds passes here + # This makes sure that the dates created are very close to the same + # but if the testing system is incredibly overloaded this should still pass + assert diff.microseconds < 500000 + + +def test_chrono_system_clock_roundtrip(): + from pybind11_tests import test_chrono2 + import datetime + + date1 = datetime.datetime.today() + + # Roundtrip the time + date2 = 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.days == 0 + assert diff.seconds == 0 + assert diff.microseconds == 0 + + +def test_chrono_duration_roundtrip(): + from pybind11_tests import test_chrono3 + import datetime + + # 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 = test_chrono3(diff) + + assert cpp_diff.days == diff.days + assert cpp_diff.seconds == diff.seconds + assert cpp_diff.microseconds == diff.microseconds + + +def test_chrono_duration_subtraction_equivalence(): + from pybind11_tests import test_chrono4 + import datetime + + date1 = datetime.datetime.today() + date2 = datetime.datetime.today() + + diff = date2 - date1 + cpp_diff = test_chrono4(date2, date1) + + assert cpp_diff.days == diff.days + assert cpp_diff.seconds == diff.seconds + assert cpp_diff.microseconds == diff.microseconds + + +def test_chrono_steady_clock(): + from pybind11_tests import test_chrono5 + import datetime + + time1 = test_chrono5() + time2 = test_chrono5() + + assert isinstance(time1, datetime.timedelta) + assert isinstance(time2, datetime.timedelta) + + +def test_chrono_steady_clock_roundtrip(): + from pybind11_tests import test_chrono6 + import datetime + + time1 = datetime.timedelta(days=10, seconds=10, microseconds=100) + time2 = test_chrono6(time1) + + assert isinstance(time2, datetime.timedelta) + + # They should be identical (no information lost on roundtrip) + assert time1.days == time2.days + assert time1.seconds == time2.seconds + assert time1.microseconds == time2.microseconds + + +def test_floating_point_duration(): + from pybind11_tests import test_chrono7 + import datetime + + # Test using 35.525123 seconds as an example floating point number in seconds + time = test_chrono7(35.525123) + + assert isinstance(time, datetime.timedelta) + + assert time.seconds == 35 + assert 525122 <= time.microseconds <= 525123 diff --git a/ext/pybind11/tests/test_class_args.cpp b/ext/pybind11/tests/test_class_args.cpp new file mode 100644 index 000000000..e18b39db2 --- /dev/null +++ b/ext/pybind11/tests/test_class_args.cpp @@ -0,0 +1,68 @@ +/* + tests/test_class_args.cpp -- tests that various way of defining a class work + + 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" + + +template <int N> class BreaksBase {}; +template <int N> class BreaksTramp : public BreaksBase<N> {}; +// These should all compile just fine: +typedef py::class_<BreaksBase<1>, std::unique_ptr<BreaksBase<1>>, BreaksTramp<1>> DoesntBreak1; +typedef py::class_<BreaksBase<2>, BreaksTramp<2>, std::unique_ptr<BreaksBase<2>>> DoesntBreak2; +typedef py::class_<BreaksBase<3>, std::unique_ptr<BreaksBase<3>>> DoesntBreak3; +typedef py::class_<BreaksBase<4>, BreaksTramp<4>> DoesntBreak4; +typedef py::class_<BreaksBase<5>> DoesntBreak5; +typedef py::class_<BreaksBase<6>, std::shared_ptr<BreaksBase<6>>, BreaksTramp<6>> DoesntBreak6; +typedef py::class_<BreaksBase<7>, BreaksTramp<7>, std::shared_ptr<BreaksBase<7>>> DoesntBreak7; +typedef py::class_<BreaksBase<8>, std::shared_ptr<BreaksBase<8>>> DoesntBreak8; +#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!"); + +//// 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); + +test_initializer class_args([](py::module &m) { + // Just test that this compiled okay + m.def("class_args_noop", []() {}); +}); diff --git a/ext/pybind11/tests/test_class_args.py b/ext/pybind11/tests/test_class_args.py new file mode 100644 index 000000000..40cbcec9f --- /dev/null +++ b/ext/pybind11/tests/test_class_args.py @@ -0,0 +1,8 @@ + + +def test_class_args(): + """There's basically nothing to test here; just make sure the code compiled + and declared its definition + """ + from pybind11_tests import class_args_noop + class_args_noop() diff --git a/ext/pybind11/tests/test_constants_and_functions.cpp b/ext/pybind11/tests/test_constants_and_functions.cpp new file mode 100644 index 000000000..c8c0392c9 --- /dev/null +++ b/ext/pybind11/tests/test_constants_and_functions.cpp @@ -0,0 +1,66 @@ +/* + 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(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(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_initializer constants_and_functions([](py::module &m) { + m.attr("some_constant") = py::int_(14); + + 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<int, float>(&test_function4)); + m.def("test_function", py::overload_cast<float, int>(&test_function4)); +#else + 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(); + + m.def("return_bytes", &return_bytes); + m.def("print_bytes", &print_bytes); +}); diff --git a/ext/pybind11/tests/test_constants_and_functions.py b/ext/pybind11/tests/test_constants_and_functions.py new file mode 100644 index 000000000..d13d3af1b --- /dev/null +++ b/ext/pybind11/tests/test_constants_and_functions.py @@ -0,0 +1,24 @@ + + +def test_constants(): + from pybind11_tests import some_constant + + assert some_constant == 14 + + +def test_function_overloading(): + from pybind11_tests import MyEnum, test_function + + assert test_function() == "test_function()" + assert test_function(7) == "test_function(7)" + assert test_function(MyEnum.EFirstEntry) == "test_function(enum=1)" + assert test_function(MyEnum.ESecondEntry) == "test_function(enum=2)" + + assert test_function(1, 1.0) == "test_function(int, float)" + assert test_function(2.0, 2) == "test_function(float, int)" + + +def test_bytes(): + from pybind11_tests import return_bytes, print_bytes + + assert print_bytes(return_bytes()) == "bytes[1 0 2 0]" diff --git a/ext/pybind11/tests/test_copy_move_policies.cpp b/ext/pybind11/tests/test_copy_move_policies.cpp new file mode 100644 index 000000000..6f7907c1f --- /dev/null +++ b/ext/pybind11/tests/test_copy_move_policies.cpp @@ -0,0 +1,41 @@ +/* + tests/test_copy_move_policies.cpp -- 'copy' and 'move' + return value policies + + 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_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() {} + 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() {} + 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_ = {}; + +test_initializer copy_move_policies([](py::module &m) { + py::class_<lacking_copy_ctor>(m, "lacking_copy_ctor") + .def_static("get_one", &lacking_copy_ctor::get_one, + py::return_value_policy::copy); + py::class_<lacking_move_ctor>(m, "lacking_move_ctor") + .def_static("get_one", &lacking_move_ctor::get_one, + py::return_value_policy::move); +}); diff --git a/ext/pybind11/tests/test_copy_move_policies.py b/ext/pybind11/tests/test_copy_move_policies.py new file mode 100644 index 000000000..edcf38075 --- /dev/null +++ b/ext/pybind11/tests/test_copy_move_policies.py @@ -0,0 +1,15 @@ +import pytest + + +def test_lacking_copy_ctor(): + from pybind11_tests import lacking_copy_ctor + with pytest.raises(RuntimeError) as excinfo: + lacking_copy_ctor.get_one() + assert "the object is non-copyable!" in str(excinfo.value) + + +def test_lacking_move_ctor(): + from pybind11_tests import lacking_move_ctor + with pytest.raises(RuntimeError) as excinfo: + lacking_move_ctor.get_one() + assert "the object is neither movable nor copyable!" in str(excinfo.value) diff --git a/ext/pybind11/tests/test_docstring_options.cpp b/ext/pybind11/tests/test_docstring_options.cpp new file mode 100644 index 000000000..74178c272 --- /dev/null +++ b/ext/pybind11/tests/test_docstring_options.cpp @@ -0,0 +1,53 @@ +/* + 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" + +struct DocstringTestFoo { + int value; + void setValue(int v) { value = v; } + int getValue() const { return value; } +}; + +test_initializer docstring_generation([](py::module &m) { + + { + 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"); + + 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(); + + py::class_<DocstringTestFoo>(m, "DocstringTestFoo", "This is a class docstring") + .def_property("value_prop", &DocstringTestFoo::getValue, &DocstringTestFoo::setValue, "This is a property docstring") + ; + } +}); diff --git a/ext/pybind11/tests/test_docstring_options.py b/ext/pybind11/tests/test_docstring_options.py new file mode 100644 index 000000000..66ad6b89f --- /dev/null +++ b/ext/pybind11/tests/test_docstring_options.py @@ -0,0 +1,32 @@ + + +def test_docstring_options(): + from pybind11_tests import (test_function1, test_function2, test_function3, + test_function4, test_function5, test_function6, + test_function7, DocstringTestFoo) + + # options.disable_function_signatures() + assert not test_function1.__doc__ + + assert test_function2.__doc__ == "A custom docstring" + + # options.enable_function_signatures() + assert test_function3.__doc__ .startswith("test_function3(a: int, b: int) -> None") + + assert test_function4.__doc__ .startswith("test_function4(a: int, b: int) -> None") + assert test_function4.__doc__ .endswith("A custom docstring\n") + + # options.disable_function_signatures() + # options.disable_user_defined_docstrings() + assert not test_function5.__doc__ + + # nested options.enable_user_defined_docstrings() + assert test_function6.__doc__ == "A custom docstring" + + # RAII destructor + assert test_function7.__doc__ .startswith("test_function7(a: int, b: int) -> None") + assert test_function7.__doc__ .endswith("A custom docstring\n") + + # Suppression of user-defined docstrings for non-function objects + assert not DocstringTestFoo.__doc__ + assert not DocstringTestFoo.value_prop.__doc__ diff --git a/ext/pybind11/tests/test_eigen.cpp b/ext/pybind11/tests/test_eigen.cpp new file mode 100644 index 000000000..588cdceb3 --- /dev/null +++ b/ext/pybind11/tests/test_eigen.cpp @@ -0,0 +1,134 @@ +/* + 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_tests.h" +#include <pybind11/eigen.h> +#include <Eigen/Cholesky> + +Eigen::VectorXf double_col(const Eigen::VectorXf& x) +{ return 2.0f * x; } + +Eigen::RowVectorXf double_row(const Eigen::RowVectorXf& x) +{ return 2.0f * x; } + +Eigen::MatrixXf double_mat_cm(const Eigen::MatrixXf& x) +{ return 2.0f * x; } + +// Different ways of passing via Eigen::Ref; the first and second are the Eigen-recommended +Eigen::MatrixXd cholesky1(Eigen::Ref<Eigen::MatrixXd> &x) { return x.llt().matrixL(); } +Eigen::MatrixXd cholesky2(const Eigen::Ref<const Eigen::MatrixXd> &x) { return x.llt().matrixL(); } +Eigen::MatrixXd cholesky3(const Eigen::Ref<Eigen::MatrixXd> &x) { return x.llt().matrixL(); } +Eigen::MatrixXd cholesky4(Eigen::Ref<const Eigen::MatrixXd> &x) { return x.llt().matrixL(); } +Eigen::MatrixXd cholesky5(Eigen::Ref<Eigen::MatrixXd> x) { return x.llt().matrixL(); } +Eigen::MatrixXd cholesky6(Eigen::Ref<const Eigen::MatrixXd> x) { return x.llt().matrixL(); } + +typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXfRowMajor; +MatrixXfRowMajor double_mat_rm(const MatrixXfRowMajor& x) +{ return 2.0f * x; } + +test_initializer eigen([](py::module &m) { + typedef Eigen::Matrix<float, 5, 6, Eigen::RowMajor> FixedMatrixR; + typedef Eigen::Matrix<float, 5, 6> FixedMatrixC; + typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> DenseMatrixR; + typedef Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> DenseMatrixC; + typedef Eigen::SparseMatrix<float, Eigen::RowMajor> SparseMatrixR; + typedef Eigen::SparseMatrix<float> SparseMatrixC; + + m.attr("have_eigen") = true; + + // Non-symmetric matrix with zero elements + 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; + + m.def("double_col", &double_col); + m.def("double_row", &double_row); + m.def("double_mat_cm", &double_mat_cm); + m.def("double_mat_rm", &double_mat_rm); + m.def("cholesky1", &cholesky1); + m.def("cholesky2", &cholesky2); + m.def("cholesky3", &cholesky3); + m.def("cholesky4", &cholesky4); + m.def("cholesky5", &cholesky5); + m.def("cholesky6", &cholesky6); + + // 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); + }); + + // 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>(); + }); + + m.def("fixed_r", [mat]() -> FixedMatrixR { + return FixedMatrixR(mat); + }); + + m.def("fixed_c", [mat]() -> FixedMatrixC { + return FixedMatrixC(mat); + }); + + m.def("fixed_passthrough_r", [](const FixedMatrixR &m) -> FixedMatrixR { + return m; + }); + + m.def("fixed_passthrough_c", [](const FixedMatrixC &m) -> FixedMatrixC { + return m; + }); + + m.def("dense_r", [mat]() -> DenseMatrixR { + return DenseMatrixR(mat); + }); + + m.def("dense_c", [mat]() -> DenseMatrixC { + return DenseMatrixC(mat); + }); + + m.def("dense_passthrough_r", [](const DenseMatrixR &m) -> DenseMatrixR { + return m; + }); + + m.def("dense_passthrough_c", [](const DenseMatrixC &m) -> DenseMatrixC { + return m; + }); + + m.def("sparse_r", [mat]() -> SparseMatrixR { + return Eigen::SparseView<Eigen::MatrixXf>(mat); + }); + + m.def("sparse_c", [mat]() -> SparseMatrixC { + return Eigen::SparseView<Eigen::MatrixXf>(mat); + }); + + m.def("sparse_passthrough_r", [](const SparseMatrixR &m) -> SparseMatrixR { + return m; + }); + + m.def("sparse_passthrough_c", [](const SparseMatrixC &m) -> SparseMatrixC { + return m; + }); +}); diff --git a/ext/pybind11/tests/test_eigen.py b/ext/pybind11/tests/test_eigen.py new file mode 100644 index 000000000..b0092fc8b --- /dev/null +++ b/ext/pybind11/tests/test_eigen.py @@ -0,0 +1,145 @@ +import pytest + +with pytest.suppress(ImportError): + import numpy as np + + ref = np.array([[ 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.todense()) + + +@pytest.requires_eigen_and_numpy +def test_fixed(): + from pybind11_tests import fixed_r, fixed_c, fixed_passthrough_r, fixed_passthrough_c + + assert_equal_ref(fixed_c()) + assert_equal_ref(fixed_r()) + assert_equal_ref(fixed_passthrough_r(fixed_r())) + assert_equal_ref(fixed_passthrough_c(fixed_c())) + assert_equal_ref(fixed_passthrough_r(fixed_c())) + assert_equal_ref(fixed_passthrough_c(fixed_r())) + + +@pytest.requires_eigen_and_numpy +def test_dense(): + from pybind11_tests import dense_r, dense_c, dense_passthrough_r, dense_passthrough_c + + assert_equal_ref(dense_r()) + assert_equal_ref(dense_c()) + assert_equal_ref(dense_passthrough_r(dense_r())) + assert_equal_ref(dense_passthrough_c(dense_c())) + assert_equal_ref(dense_passthrough_r(dense_c())) + assert_equal_ref(dense_passthrough_c(dense_r())) + + +@pytest.requires_eigen_and_numpy +def test_nonunit_stride_from_python(): + from pybind11_tests import double_row, double_col, double_mat_cm, double_mat_rm + + counting_mat = np.arange(9.0, dtype=np.float32).reshape((3, 3)) + first_row = counting_mat[0, :] + first_col = counting_mat[:, 0] + assert np.array_equal(double_row(first_row), 2.0 * first_row) + assert np.array_equal(double_col(first_row), 2.0 * first_row) + assert np.array_equal(double_row(first_col), 2.0 * first_col) + assert np.array_equal(double_col(first_col), 2.0 * first_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 slice_idx, ref_mat in enumerate(slices): + assert np.array_equal(double_mat_cm(ref_mat), 2.0 * ref_mat) + assert np.array_equal(double_mat_rm(ref_mat), 2.0 * ref_mat) + + +@pytest.requires_eigen_and_numpy +def test_nonunit_stride_to_python(): + from pybind11_tests import diagonal, diagonal_1, diagonal_n, block + + assert np.all(diagonal(ref) == ref.diagonal()) + assert np.all(diagonal_1(ref) == ref.diagonal(1)) + for i in range(-5, 7): + assert np.all(diagonal_n(ref, i) == ref.diagonal(i)), "diagonal_n({})".format(i) + + assert np.all(block(ref, 2, 1, 3, 3) == ref[2:5, 1:4]) + assert np.all(block(ref, 1, 4, 4, 2) == ref[1:, 4:]) + assert np.all(block(ref, 1, 4, 3, 2) == ref[1:4, 4:]) + + +@pytest.requires_eigen_and_numpy +def test_eigen_ref_to_python(): + from pybind11_tests import cholesky1, cholesky2, cholesky3, cholesky4, cholesky5, cholesky6 + + chols = [cholesky1, cholesky2, cholesky3, cholesky4, cholesky5, cholesky6] + for i, chol in enumerate(chols, start=1): + mymat = chol(np.array([[1, 2, 4], [2, 13, 23], [4, 23, 77]])) + assert np.all(mymat == np.array([[1, 0, 0], [2, 3, 0], [4, 5, 6]])), "cholesky{}".format(i) + + +@pytest.requires_eigen_and_numpy +def test_special_matrix_objects(): + from pybind11_tests import incr_diag, symmetric_upper, symmetric_lower + + assert np.all(incr_diag(7) == np.diag([1, 2, 3, 4, 5, 6, 7])) + + asymm = np.array([[ 1, 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(symmetric_lower(asymm) == symm_lower) + assert np.all(symmetric_upper(asymm) == symm_upper) + + +@pytest.requires_eigen_and_numpy +def test_dense_signature(doc): + from pybind11_tests import double_col, double_row, double_mat_rm + + assert doc(double_col) == """ + double_col(arg0: numpy.ndarray[float32[m, 1]]) -> numpy.ndarray[float32[m, 1]] + """ + assert doc(double_row) == """ + double_row(arg0: numpy.ndarray[float32[1, n]]) -> numpy.ndarray[float32[1, n]] + """ + assert doc(double_mat_rm) == """ + double_mat_rm(arg0: numpy.ndarray[float32[m, n]]) -> numpy.ndarray[float32[m, n]] + """ + + +@pytest.requires_eigen_and_scipy +def test_sparse(): + from pybind11_tests import sparse_r, sparse_c, sparse_passthrough_r, sparse_passthrough_c + + assert_sparse_equal_ref(sparse_r()) + assert_sparse_equal_ref(sparse_c()) + assert_sparse_equal_ref(sparse_passthrough_r(sparse_r())) + assert_sparse_equal_ref(sparse_passthrough_c(sparse_c())) + assert_sparse_equal_ref(sparse_passthrough_r(sparse_c())) + assert_sparse_equal_ref(sparse_passthrough_c(sparse_r())) + + +@pytest.requires_eigen_and_scipy +def test_sparse_signature(doc): + from pybind11_tests import sparse_passthrough_r, sparse_passthrough_c + + assert doc(sparse_passthrough_r) == """ + sparse_passthrough_r(arg0: scipy.sparse.csr_matrix[float32]) -> scipy.sparse.csr_matrix[float32] + """ # noqa: E501 line too long + assert doc(sparse_passthrough_c) == """ + sparse_passthrough_c(arg0: scipy.sparse.csc_matrix[float32]) -> scipy.sparse.csc_matrix[float32] + """ # noqa: E501 line too long diff --git a/ext/pybind11/tests/test_enum.cpp b/ext/pybind11/tests/test_enum.cpp new file mode 100644 index 000000000..09f334cdb --- /dev/null +++ b/ext/pybind11/tests/test_enum.cpp @@ -0,0 +1,68 @@ +/* + 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" + +enum UnscopedEnum { + EOne = 1, + ETwo +}; + +enum class ScopedEnum { + Two = 2, + Three +}; + +enum Flags { + Read = 4, + Write = 2, + Execute = 1 +}; + +class ClassWithUnscopedEnum { +public: + enum EMode { + EFirstMode = 1, + ESecondMode + }; + + static EMode test_function(EMode mode) { + return mode; + } +}; + +std::string test_scoped_enum(ScopedEnum z) { + return "ScopedEnum::" + std::string(z == ScopedEnum::Two ? "Two" : "Three"); +} + +test_initializer enums([](py::module &m) { + m.def("test_scoped_enum", &test_scoped_enum); + + py::enum_<UnscopedEnum>(m, "UnscopedEnum", py::arithmetic()) + .value("EOne", EOne) + .value("ETwo", ETwo) + .export_values(); + + py::enum_<ScopedEnum>(m, "ScopedEnum", py::arithmetic()) + .value("Two", ScopedEnum::Two) + .value("Three", ScopedEnum::Three); + + py::enum_<Flags>(m, "Flags", py::arithmetic()) + .value("Read", Flags::Read) + .value("Write", Flags::Write) + .value("Execute", Flags::Execute) + .export_values(); + + 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(); +}); diff --git a/ext/pybind11/tests/test_enum.py b/ext/pybind11/tests/test_enum.py new file mode 100644 index 000000000..de5f3c6f6 --- /dev/null +++ b/ext/pybind11/tests/test_enum.py @@ -0,0 +1,108 @@ +import pytest + + +def test_unscoped_enum(): + from pybind11_tests import UnscopedEnum, EOne + + assert str(UnscopedEnum.EOne) == "UnscopedEnum.EOne" + assert str(UnscopedEnum.ETwo) == "UnscopedEnum.ETwo" + assert str(EOne) == "UnscopedEnum.EOne" + + # no TypeError exception for unscoped enum ==/!= int comparisons + y = UnscopedEnum.ETwo + assert y == 2 + assert y != 3 + + assert int(UnscopedEnum.ETwo) == 2 + assert str(UnscopedEnum(2)) == "UnscopedEnum.ETwo" + + # order + assert UnscopedEnum.EOne < UnscopedEnum.ETwo + assert UnscopedEnum.EOne < 2 + assert UnscopedEnum.ETwo > UnscopedEnum.EOne + assert UnscopedEnum.ETwo > 1 + assert UnscopedEnum.ETwo <= 2 + assert UnscopedEnum.ETwo >= 2 + assert UnscopedEnum.EOne <= UnscopedEnum.ETwo + assert UnscopedEnum.EOne <= 2 + assert UnscopedEnum.ETwo >= UnscopedEnum.EOne + assert UnscopedEnum.ETwo >= 1 + assert not (UnscopedEnum.ETwo < UnscopedEnum.EOne) + assert not (2 < UnscopedEnum.EOne) + + +def test_scoped_enum(): + from pybind11_tests import ScopedEnum, test_scoped_enum + + assert test_scoped_enum(ScopedEnum.Three) == "ScopedEnum::Three" + z = ScopedEnum.Two + assert test_scoped_enum(z) == "ScopedEnum::Two" + + # expected TypeError exceptions for scoped enum ==/!= int comparisons + with pytest.raises(TypeError): + assert z == 2 + with pytest.raises(TypeError): + assert z != 3 + + # order + assert ScopedEnum.Two < ScopedEnum.Three + assert ScopedEnum.Three > ScopedEnum.Two + assert ScopedEnum.Two <= ScopedEnum.Three + assert ScopedEnum.Two <= ScopedEnum.Two + assert ScopedEnum.Two >= ScopedEnum.Two + assert ScopedEnum.Three >= ScopedEnum.Two + + +def test_implicit_conversion(): + from pybind11_tests import ClassWithUnscopedEnum + + assert str(ClassWithUnscopedEnum.EMode.EFirstMode) == "EMode.EFirstMode" + assert str(ClassWithUnscopedEnum.EFirstMode) == "EMode.EFirstMode" + + f = ClassWithUnscopedEnum.test_function + first = ClassWithUnscopedEnum.EFirstMode + second = 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 str(x) == "{EMode.EFirstMode: 3, EMode.ESecondMode: 4}" + + +def test_binary_operators(): + from pybind11_tests import Flags + + assert int(Flags.Read) == 4 + assert int(Flags.Write) == 2 + assert int(Flags.Execute) == 1 + assert int(Flags.Read | Flags.Write | Flags.Execute) == 7 + assert int(Flags.Read | Flags.Write) == 6 + assert int(Flags.Read | Flags.Execute) == 5 + assert int(Flags.Write | Flags.Execute) == 3 + assert int(Flags.Write | 1) == 3 + + state = Flags.Read | Flags.Write + assert (state & Flags.Read) != 0 + assert (state & Flags.Write) != 0 + assert (state & Flags.Execute) == 0 + assert (state & 1) == 0 + + state2 = ~state + assert state2 == -7 + assert int(state ^ state2) == -1 diff --git a/ext/pybind11/tests/test_eval.cpp b/ext/pybind11/tests/test_eval.cpp new file mode 100644 index 000000000..ed4c226fe --- /dev/null +++ b/ext/pybind11/tests/test_eval.cpp @@ -0,0 +1,79 @@ +/* + 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" + +test_initializer eval([](py::module &m) { + 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; + }); + + auto result = py::eval<py::eval_statements>( + "print('Hello World!');\n" + "x = call_test();", + global, local + ); + auto x = local["x"].cast<int>(); + + return result == py::none() && 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 == py::none() && x == 42; + }); + + m.def("test_eval_file", [global](py::str filename) { + auto local = py::dict(); + local["y"] = py::int_(43); + + int val_out; + local["call_test2"] = py::cpp_function([&](int value) { val_out = value; }); + + auto result = py::eval_file(filename, global, local); + return val_out == 43 && result == py::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; + }); +}); diff --git a/ext/pybind11/tests/test_eval.py b/ext/pybind11/tests/test_eval.py new file mode 100644 index 000000000..8715dbadb --- /dev/null +++ b/ext/pybind11/tests/test_eval.py @@ -0,0 +1,19 @@ +import os + + +def test_evals(capture): + from pybind11_tests import (test_eval_statements, test_eval, test_eval_single_statement, + test_eval_file, test_eval_failure, test_eval_file_failure) + + with capture: + assert test_eval_statements() + assert capture == "Hello World!" + + assert test_eval() + assert test_eval_single_statement() + + filename = os.path.join(os.path.dirname(__file__), "test_eval_call.py") + assert test_eval_file(filename) + + assert test_eval_failure() + assert test_eval_file_failure() diff --git a/ext/pybind11/tests/test_eval_call.py b/ext/pybind11/tests/test_eval_call.py new file mode 100644 index 000000000..53c7e721f --- /dev/null +++ b/ext/pybind11/tests/test_eval_call.py @@ -0,0 +1,4 @@ +# This file is called from 'test_eval.py' + +if 'call_test2' in locals(): + call_test2(y) # noqa: F821 undefined name diff --git a/ext/pybind11/tests/test_exceptions.cpp b/ext/pybind11/tests/test_exceptions.cpp new file mode 100644 index 000000000..706b500f2 --- /dev/null +++ b/ext/pybind11/tests/test_exceptions.cpp @@ -0,0 +1,173 @@ +/* + 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 "pybind11_tests.h" + +// A type that should be raised as an exeption in Python +class MyException : public std::exception { +public: + explicit MyException(const char * m) : message{m} {} + virtual 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} {} + virtual 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();} +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} {} + virtual 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; +}; + +void throws1() { + throw MyException("this error should go to a custom type"); +} + +void throws2() { + throw MyException2("this error should go to a standard Python exception"); +} + +void throws3() { + throw MyException3("this error cannot be translated"); +} + +void throws4() { + throw MyException4("this error is rethrown"); +} + +void throws5() { + throw MyException5("this is a helper-defined translated exception"); +} + +void throws5_1() { + throw MyException5_1("MyException5 subclass"); +} + +void throws_logic_error() { + throw std::logic_error("this error should fall through to the standard handler"); +} + +struct PythonCallInDestructor { + PythonCallInDestructor(const py::dict &d) : d(d) {} + ~PythonCallInDestructor() { d["good"] = true; } + + py::dict d; +}; + +test_initializer custom_exceptions([](py::module &m) { + // 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()); + + m.def("throws1", &throws1); + m.def("throws2", &throws2); + m.def("throws3", &throws3); + m.def("throws4", &throws4); + m.def("throws5", &throws5); + m.def("throws5_1", &throws5_1); + m.def("throws_logic_error", &throws_logic_error); + + 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", [](py::dict d) { + try { + PythonCallInDestructor set_dict_in_destructor(d); + PyErr_SetString(PyExc_ValueError, "foo"); + throw py::error_already_set(); + } catch (const py::error_already_set&) { + return true; + } + return false; + }); +}); diff --git a/ext/pybind11/tests/test_exceptions.py b/ext/pybind11/tests/test_exceptions.py new file mode 100644 index 000000000..0025e4eb6 --- /dev/null +++ b/ext/pybind11/tests/test_exceptions.py @@ -0,0 +1,74 @@ +import pytest + + +def test_error_already_set(msg): + from pybind11_tests import throw_already_set + + with pytest.raises(RuntimeError) as excinfo: + throw_already_set(False) + assert msg(excinfo.value) == "Unknown internal error occurred" + + with pytest.raises(ValueError) as excinfo: + throw_already_set(True) + assert msg(excinfo.value) == "foo" + + +def test_python_call_in_catch(): + from pybind11_tests import python_call_in_destructor + + d = {} + assert python_call_in_destructor(d) is True + assert d["good"] is True + + +def test_custom(msg): + from pybind11_tests import (MyException, MyException5, MyException5_1, + throws1, throws2, throws3, throws4, throws5, throws5_1, + throws_logic_error) + + # Can we catch a MyException?" + with pytest.raises(MyException) as excinfo: + 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: + 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: + throws3() + assert msg(excinfo.value) == "Caught an unknown exception!" + + # Can we delegate to another handler by rethrowing? + with pytest.raises(MyException) as excinfo: + throws4() + assert msg(excinfo.value) == "this error is rethrown" + + # "Can we fall-through to the default handler?" + with pytest.raises(RuntimeError) as excinfo: + throws_logic_error() + assert msg(excinfo.value) == "this error should fall through to the standard handler" + + # Can we handle a helper-declared exception? + with pytest.raises(MyException5) as excinfo: + throws5() + assert msg(excinfo.value) == "this is a helper-defined translated exception" + + # Exception subclassing: + with pytest.raises(MyException5) as excinfo: + throws5_1() + assert msg(excinfo.value) == "MyException5 subclass" + assert isinstance(excinfo.value, MyException5_1) + + with pytest.raises(MyException5_1) as excinfo: + throws5_1() + assert msg(excinfo.value) == "MyException5 subclass" + + with pytest.raises(MyException5) as excinfo: + try: + throws5() + except MyException5_1: + raise RuntimeError("Exception error: caught child from parent") + assert msg(excinfo.value) == "this is a helper-defined translated exception" diff --git a/ext/pybind11/tests/test_inheritance.cpp b/ext/pybind11/tests/test_inheritance.cpp new file mode 100644 index 000000000..2ec0b4a7a --- /dev/null +++ b/ext/pybind11/tests/test_inheritance.cpp @@ -0,0 +1,100 @@ +/* + tests/test_inheritance.cpp -- inheritance, automatic upcasting for polymorphic 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_tests.h" + +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: + Dog(const std::string &name) : Pet(name, "dog") {} + std::string bark() const { return "Woof!"; } +}; + +class Rabbit : public Pet { +public: + Rabbit(const std::string &name) : Pet(name, "parrot") {} +}; + +class Hamster : public Pet { +public: + Hamster(const std::string &name) : Pet(name, "rodent") {} +}; + +std::string pet_name_species(const Pet &pet) { + return pet.name() + " is a " + pet.species(); +} + +std::string dog_bark(const Dog &dog) { + return dog.bark(); +} + + +struct BaseClass { virtual ~BaseClass() {} }; +struct DerivedClass1 : BaseClass { }; +struct DerivedClass2 : BaseClass { }; + +test_initializer inheritance([](py::module &m) { + 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>()); + + m.def("pet_name_species", pet_name_species); + m.def("dog_bark", dog_bark); + + 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; }); + + m.def("test_isinstance", [](py::list l) { + struct Unregistered { }; // checks missing type_info code path + + 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<Unregistered>(l[6]) + ); + }); +}); diff --git a/ext/pybind11/tests/test_inheritance.py b/ext/pybind11/tests/test_inheritance.py new file mode 100644 index 000000000..7bb52be02 --- /dev/null +++ b/ext/pybind11/tests/test_inheritance.py @@ -0,0 +1,55 @@ +import pytest + + +def test_inheritance(msg): + from pybind11_tests import Pet, Dog, Rabbit, Hamster, dog_bark, pet_name_species + + roger = Rabbit('Rabbit') + assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot" + assert pet_name_species(roger) == "Rabbit is a parrot" + + polly = Pet('Polly', 'parrot') + assert polly.name() + " is a " + polly.species() == "Polly is a parrot" + assert pet_name_species(polly) == "Polly is a parrot" + + molly = Dog('Molly') + assert molly.name() + " is a " + molly.species() == "Molly is a dog" + assert pet_name_species(molly) == "Molly is a dog" + + fred = Hamster('Fred') + assert fred.name() + " is a " + fred.species() == "Fred is a rodent" + + assert dog_bark(molly) == "Woof!" + + with pytest.raises(TypeError) as excinfo: + dog_bark(polly) + assert msg(excinfo.value) == """ + dog_bark(): incompatible function arguments. The following argument types are supported: + 1. (arg0: m.Dog) -> str + + Invoked with: <m.Pet object at 0> + """ + + +def test_automatic_upcasting(): + from pybind11_tests import return_class_1, return_class_2, return_class_n, return_none + + assert type(return_class_1()).__name__ == "DerivedClass1" + assert type(return_class_2()).__name__ == "DerivedClass2" + assert type(return_none()).__name__ == "NoneType" + # Repeat these a few times in a random order to ensure no invalid caching is applied + assert type(return_class_n(1)).__name__ == "DerivedClass1" + assert type(return_class_n(2)).__name__ == "DerivedClass2" + assert type(return_class_n(0)).__name__ == "BaseClass" + assert type(return_class_n(2)).__name__ == "DerivedClass2" + assert type(return_class_n(2)).__name__ == "DerivedClass2" + assert type(return_class_n(0)).__name__ == "BaseClass" + assert type(return_class_n(1)).__name__ == "DerivedClass1" + + +def test_isinstance(): + from pybind11_tests import test_isinstance, Pet, Dog + + objects = [tuple(), dict(), Pet("Polly", "parrot")] + [Dog("Molly")] * 4 + expected = (True, True, True, True, True, False, False) + assert test_isinstance(objects) == expected diff --git a/ext/pybind11/tests/test_installed_module/CMakeLists.txt b/ext/pybind11/tests/test_installed_module/CMakeLists.txt new file mode 100644 index 000000000..77fd49dc2 --- /dev/null +++ b/ext/pybind11/tests/test_installed_module/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 2.8.12) +project(test_installed_module CXX) + +set(CMAKE_MODULE_PATH "") + +find_package(pybind11 CONFIG REQUIRED) + +message(STATUS "Found pybind11: ${pybind11_INCLUDE_DIRS} (found version ${pybind11_VERSION})") +message(STATUS "Found Python: ${PYTHON_INCLUDE_DIRS} (found version ${PYTHON_VERSION_STRING})") + +pybind11_add_module(test_installed_module SHARED main.cpp) + +add_custom_target(check ${CMAKE_COMMAND} -E env PYTHONPATH=$<TARGET_FILE_DIR:test_installed_module> + ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test.py) diff --git a/ext/pybind11/tests/test_installed_module/main.cpp b/ext/pybind11/tests/test_installed_module/main.cpp new file mode 100644 index 000000000..a0bda4542 --- /dev/null +++ b/ext/pybind11/tests/test_installed_module/main.cpp @@ -0,0 +1,10 @@ +#include <pybind11/pybind11.h> +namespace py = pybind11; + +PYBIND11_PLUGIN(test_installed_module) { + py::module m("test_installed_module"); + + m.def("add", [](int i, int j) { return i + j; }); + + return m.ptr(); +} diff --git a/ext/pybind11/tests/test_installed_module/test.py b/ext/pybind11/tests/test_installed_module/test.py new file mode 100644 index 000000000..2f0632049 --- /dev/null +++ b/ext/pybind11/tests/test_installed_module/test.py @@ -0,0 +1,3 @@ +import test_installed_module +assert test_installed_module.add(11, 22) == 33 +print('test_installed_module imports, runs, and adds: 11 + 22 = 33') diff --git a/ext/pybind11/tests/test_installed_target/CMakeLists.txt b/ext/pybind11/tests/test_installed_target/CMakeLists.txt new file mode 100644 index 000000000..4333dc107 --- /dev/null +++ b/ext/pybind11/tests/test_installed_target/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.0) +project(test_installed_target CXX) + +set(CMAKE_MODULE_PATH "") + +find_package(pybind11 CONFIG REQUIRED) + +message(STATUS "Found pybind11: ${pybind11_INCLUDE_DIRS} (found version ${pybind11_VERSION})") +message(STATUS "Found Python: ${PYTHON_INCLUDE_DIRS} (found version ${PYTHON_VERSION_STRING})") + +add_library(test_installed_target MODULE main.cpp) + +target_link_libraries(test_installed_target PRIVATE pybind11::pybind11) + +# make sure result is, for example, test_installed_target.so, not libtest_installed_target.dylib +set_target_properties(test_installed_target PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}" + SUFFIX "${PYTHON_MODULE_EXTENSION}") + +add_custom_target(check ${CMAKE_COMMAND} -E env PYTHONPATH=$<TARGET_FILE_DIR:test_installed_target> + ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/test.py) diff --git a/ext/pybind11/tests/test_installed_target/main.cpp b/ext/pybind11/tests/test_installed_target/main.cpp new file mode 100644 index 000000000..2a84c11ce --- /dev/null +++ b/ext/pybind11/tests/test_installed_target/main.cpp @@ -0,0 +1,10 @@ +#include <pybind11/pybind11.h> +namespace py = pybind11; + +PYBIND11_PLUGIN(test_installed_target) { + py::module m("test_installed_target"); + + m.def("add", [](int i, int j) { return i + j; }); + + return m.ptr(); +} diff --git a/ext/pybind11/tests/test_installed_target/test.py b/ext/pybind11/tests/test_installed_target/test.py new file mode 100644 index 000000000..b2888a72b --- /dev/null +++ b/ext/pybind11/tests/test_installed_target/test.py @@ -0,0 +1,3 @@ +import test_installed_target +assert test_installed_target.add(1, 2) == 3 +print('test_installed_target imports, runs, and adds: 1 + 2 = 3') diff --git a/ext/pybind11/tests/test_issues.cpp b/ext/pybind11/tests/test_issues.cpp new file mode 100644 index 000000000..4c59a1b12 --- /dev/null +++ b/ext/pybind11/tests/test_issues.cpp @@ -0,0 +1,401 @@ +/* + tests/test_issues.cpp -- collection of testcases for miscellaneous issues + + 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 <pybind11/stl.h> +#include <pybind11/operators.h> +#include <pybind11/complex.h> + +#define TRACKERS(CLASS) CLASS() { print_default_created(this); } ~CLASS() { print_destroyed(this); } +struct NestABase { int value = -2; TRACKERS(NestABase) }; +struct NestA : NestABase { int value = 3; NestA& operator+=(int i) { value += i; return *this; } TRACKERS(NestA) }; +struct NestB { NestA a; int value = 4; NestB& operator-=(int i) { value -= i; return *this; } TRACKERS(NestB) }; +struct NestC { NestB b; int value = 5; NestC& operator*=(int i) { value *= i; return *this; } TRACKERS(NestC) }; + +/// #393 +class OpTest1 {}; +class OpTest2 {}; + +OpTest1 operator+(const OpTest1 &, const OpTest1 &) { + py::print("Add OpTest1 with OpTest1"); + return OpTest1(); +} +OpTest2 operator+(const OpTest2 &, const OpTest2 &) { + py::print("Add OpTest2 with OpTest2"); + return OpTest2(); +} +OpTest2 operator+(const OpTest2 &, const OpTest1 &) { + py::print("Add OpTest2 with OpTest1"); + return OpTest2(); +} + +// #461 +class Dupe1 { +public: + Dupe1(int v) : v_{v} {} + int get_value() const { return v_; } +private: + int v_; +}; +class Dupe2 {}; +class Dupe3 {}; +class DupeException : public std::runtime_error {}; + +// #478 +template <typename T> class custom_unique_ptr { +public: + custom_unique_ptr() { print_default_created(this); } + custom_unique_ptr(T *ptr) : _ptr{ptr} { print_created(this, ptr); } + custom_unique_ptr(custom_unique_ptr<T> &&move) : _ptr{move._ptr} { move._ptr = nullptr; print_move_created(this); } + custom_unique_ptr &operator=(custom_unique_ptr<T> &&move) { print_move_assigned(this); if (_ptr) destruct_ptr(); _ptr = move._ptr; move._ptr = nullptr; return *this; } + custom_unique_ptr(const custom_unique_ptr<T> &) = delete; + void operator=(const custom_unique_ptr<T> ©) = delete; + ~custom_unique_ptr() { print_destroyed(this); if (_ptr) destruct_ptr(); } +private: + T *_ptr = nullptr; + void destruct_ptr() { delete _ptr; } +}; +PYBIND11_DECLARE_HOLDER_TYPE(T, custom_unique_ptr<T>); + +/// Issue #528: templated constructor +struct TplConstrClass { + template <typename T> TplConstrClass(const T &arg) : str{arg} {} + std::string str; + bool operator==(const TplConstrClass &t) const { return t.str == str; } +}; +namespace std { +template <> struct hash<TplConstrClass> { size_t operator()(const TplConstrClass &t) const { return std::hash<std::string>()(t.str); } }; +} + + +void init_issues(py::module &m) { + py::module m2 = m.def_submodule("issues"); + +#if !defined(_MSC_VER) + // Visual Studio 2015 currently cannot compile this test + // (see the comment in type_caster_base::make_copy_constructor) + // #70 compilation issue if operator new is not public + class NonConstructible { private: void *operator new(size_t bytes) throw(); }; + py::class_<NonConstructible>(m, "Foo"); + m2.def("getstmt", []() -> NonConstructible * { return nullptr; }, + py::return_value_policy::reference); +#endif + + // #137: const char* isn't handled properly + m2.def("print_cchar", [](const char *s) { return std::string(s); }); + + // #150: char bindings broken + m2.def("print_char", [](char c) { return std::string(1, c); }); + + // #159: virtual function dispatch has problems with similar-named functions + struct Base { virtual std::string dispatch() const { + /* for some reason MSVC2015 can't compile this if the function is pure virtual */ + return {}; + }; }; + + struct DispatchIssue : Base { + virtual std::string dispatch() const { + PYBIND11_OVERLOAD_PURE(std::string, Base, dispatch, /* no arguments */); + } + }; + + py::class_<Base, DispatchIssue>(m2, "DispatchIssue") + .def(py::init<>()) + .def("dispatch", &Base::dispatch); + + m2.def("dispatch_issue_go", [](const Base * b) { return b->dispatch(); }); + + struct Placeholder { int i; Placeholder(int i) : i(i) { } }; + + py::class_<Placeholder>(m2, "Placeholder") + .def(py::init<int>()) + .def("__repr__", [](const Placeholder &p) { return "Placeholder[" + std::to_string(p.i) + "]"; }); + + // #171: Can't return reference wrappers (or STL datastructures containing them) + m2.def("return_vec_of_reference_wrapper", [](std::reference_wrapper<Placeholder> p4) { + Placeholder *p1 = new Placeholder{1}; + Placeholder *p2 = new Placeholder{2}; + Placeholder *p3 = new Placeholder{3}; + std::vector<std::reference_wrapper<Placeholder>> v; + v.push_back(std::ref(*p1)); + v.push_back(std::ref(*p2)); + v.push_back(std::ref(*p3)); + v.push_back(p4); + return v; + }); + + // #181: iterator passthrough did not compile + m2.def("iterator_passthrough", [](py::iterator s) -> py::iterator { + return py::make_iterator(std::begin(s), std::end(s)); + }); + + // #187: issue involving std::shared_ptr<> return value policy & garbage collection + struct ElementBase { virtual void foo() { } /* Force creation of virtual table */ }; + struct ElementA : ElementBase { + ElementA(int v) : v(v) { } + int value() { return v; } + int v; + }; + + struct ElementList { + void add(std::shared_ptr<ElementBase> e) { l.push_back(e); } + std::vector<std::shared_ptr<ElementBase>> l; + }; + + py::class_<ElementBase, std::shared_ptr<ElementBase>> (m2, "ElementBase"); + + py::class_<ElementA, ElementBase, std::shared_ptr<ElementA>>(m2, "ElementA") + .def(py::init<int>()) + .def("value", &ElementA::value); + + py::class_<ElementList, std::shared_ptr<ElementList>>(m2, "ElementList") + .def(py::init<>()) + .def("add", &ElementList::add) + .def("get", [](ElementList &el) { + py::list list; + for (auto &e : el.l) + list.append(py::cast(e)); + return list; + }); + + // (no id): should not be able to pass 'None' to a reference argument + m2.def("get_element", [](ElementA &el) { return el.value(); }); + + // (no id): don't cast doubles to ints + m2.def("expect_float", [](float f) { return f; }); + m2.def("expect_int", [](int i) { return i; }); + + try { + py::class_<Placeholder>(m2, "Placeholder"); + throw std::logic_error("Expected an exception!"); + } catch (std::runtime_error &) { + /* All good */ + } + + // Issue #283: __str__ called on uninitialized instance when constructor arguments invalid + class StrIssue { + public: + StrIssue(int i) : val{i} {} + StrIssue() : StrIssue(-1) {} + int value() const { return val; } + private: + int val; + }; + py::class_<StrIssue> si(m2, "StrIssue"); + si .def(py::init<int>()) + .def(py::init<>()) + .def("__str__", [](const StrIssue &si) { return "StrIssue[" + std::to_string(si.value()) + "]"; }) + ; + + // Issue #328: first member in a class can't be used in operators + py::class_<NestABase>(m2, "NestABase").def(py::init<>()).def_readwrite("value", &NestABase::value); + py::class_<NestA>(m2, "NestA").def(py::init<>()).def(py::self += int()) + .def("as_base", [](NestA &a) -> NestABase& { return (NestABase&) a; }, py::return_value_policy::reference_internal); + py::class_<NestB>(m2, "NestB").def(py::init<>()).def(py::self -= int()).def_readwrite("a", &NestB::a); + py::class_<NestC>(m2, "NestC").def(py::init<>()).def(py::self *= int()).def_readwrite("b", &NestC::b); + m2.def("get_NestA", [](const NestA &a) { return a.value; }); + m2.def("get_NestB", [](const NestB &b) { return b.value; }); + m2.def("get_NestC", [](const NestC &c) { return c.value; }); + + // Issue 389: r_v_p::move should fall-through to copy on non-movable objects + class MoveIssue1 { + public: + MoveIssue1(int v) : v{v} {} + MoveIssue1(const MoveIssue1 &c) { v = c.v; } + MoveIssue1(MoveIssue1 &&) = delete; + int v; + }; + class MoveIssue2 { + public: + MoveIssue2(int v) : v{v} {} + MoveIssue2(MoveIssue2 &&) = default; + int v; + }; + py::class_<MoveIssue1>(m2, "MoveIssue1").def(py::init<int>()).def_readwrite("value", &MoveIssue1::v); + py::class_<MoveIssue2>(m2, "MoveIssue2").def(py::init<int>()).def_readwrite("value", &MoveIssue2::v); + m2.def("get_moveissue1", [](int i) -> MoveIssue1 * { return new MoveIssue1(i); }, py::return_value_policy::move); + m2.def("get_moveissue2", [](int i) { return MoveIssue2(i); }, py::return_value_policy::move); + + // Issues 392/397: overridding reference-returning functions + class OverrideTest { + public: + struct A { std::string value = "hi"; }; + std::string v; + A a; + explicit OverrideTest(const std::string &v) : v{v} {} + virtual std::string str_value() { return v; } + virtual std::string &str_ref() { return v; } + virtual A A_value() { return a; } + virtual A &A_ref() { return a; } + }; + class PyOverrideTest : public OverrideTest { + public: + using OverrideTest::OverrideTest; + std::string str_value() override { PYBIND11_OVERLOAD(std::string, OverrideTest, str_value); } + // Not allowed (uncommenting should hit a static_assert failure): we can't get a reference + // to a python numeric value, since we only copy values in the numeric type caster: +// std::string &str_ref() override { PYBIND11_OVERLOAD(std::string &, OverrideTest, str_ref); } + // But we can work around it like this: + private: + std::string _tmp; + std::string str_ref_helper() { PYBIND11_OVERLOAD(std::string, OverrideTest, str_ref); } + public: + std::string &str_ref() override { return _tmp = str_ref_helper(); } + + A A_value() override { PYBIND11_OVERLOAD(A, OverrideTest, A_value); } + A &A_ref() override { PYBIND11_OVERLOAD(A &, OverrideTest, A_ref); } + }; + py::class_<OverrideTest::A>(m2, "OverrideTest_A") + .def_readwrite("value", &OverrideTest::A::value); + py::class_<OverrideTest, PyOverrideTest>(m2, "OverrideTest") + .def(py::init<const std::string &>()) + .def("str_value", &OverrideTest::str_value) +// .def("str_ref", &OverrideTest::str_ref) + .def("A_value", &OverrideTest::A_value) + .def("A_ref", &OverrideTest::A_ref); + + /// Issue 393: need to return NotSupported to ensure correct arithmetic operator behavior + py::class_<OpTest1>(m2, "OpTest1") + .def(py::init<>()) + .def(py::self + py::self); + + py::class_<OpTest2>(m2, "OpTest2") + .def(py::init<>()) + .def(py::self + py::self) + .def("__add__", [](const OpTest2& c2, const OpTest1& c1) { return c2 + c1; }) + .def("__radd__", [](const OpTest2& c2, const OpTest1& c1) { return c2 + c1; }); + + // Issue 388: Can't make iterators via make_iterator() with different r/v policies + static std::vector<int> list = { 1, 2, 3 }; + m2.def("make_iterator_1", []() { return py::make_iterator<py::return_value_policy::copy>(list); }); + m2.def("make_iterator_2", []() { return py::make_iterator<py::return_value_policy::automatic>(list); }); + + static std::vector<std::string> nothrows; + // Issue 461: registering two things with the same name: + py::class_<Dupe1>(m2, "Dupe1") + .def("get_value", &Dupe1::get_value) + ; + m2.def("dupe1_factory", [](int v) { return new Dupe1(v); }); + + py::class_<Dupe2>(m2, "Dupe2"); + py::exception<DupeException>(m2, "DupeException"); + + try { + m2.def("Dupe1", [](int v) { return new Dupe1(v); }); + nothrows.emplace_back("Dupe1"); + } + catch (std::runtime_error &) {} + try { + py::class_<Dupe3>(m2, "dupe1_factory"); + nothrows.emplace_back("dupe1_factory"); + } + catch (std::runtime_error &) {} + try { + py::exception<Dupe3>(m2, "Dupe2"); + nothrows.emplace_back("Dupe2"); + } + catch (std::runtime_error &) {} + try { + m2.def("DupeException", []() { return 30; }); + nothrows.emplace_back("DupeException1"); + } + catch (std::runtime_error &) {} + try { + py::class_<DupeException>(m2, "DupeException"); + nothrows.emplace_back("DupeException2"); + } + catch (std::runtime_error &) {} + m2.def("dupe_exception_failures", []() { + py::list l; + for (auto &e : nothrows) l.append(py::cast(e)); + return l; + }); + + /// Issue #471: shared pointer instance not dellocated + class SharedChild : public std::enable_shared_from_this<SharedChild> { + public: + SharedChild() { print_created(this); } + ~SharedChild() { print_destroyed(this); } + }; + + class SharedParent { + public: + SharedParent() : child(std::make_shared<SharedChild>()) { } + const SharedChild &get_child() const { return *child; } + + private: + std::shared_ptr<SharedChild> child; + }; + + py::class_<SharedChild, std::shared_ptr<SharedChild>>(m, "SharedChild"); + py::class_<SharedParent, std::shared_ptr<SharedParent>>(m, "SharedParent") + .def(py::init<>()) + .def("get_child", &SharedParent::get_child, py::return_value_policy::reference); + + /// Issue/PR #478: unique ptrs constructed and freed without destruction + class SpecialHolderObj { + public: + int val = 0; + SpecialHolderObj *ch = nullptr; + SpecialHolderObj(int v, bool make_child = true) : val{v}, ch{make_child ? new SpecialHolderObj(val+1, false) : nullptr} + { print_created(this, val); } + ~SpecialHolderObj() { delete ch; print_destroyed(this); } + SpecialHolderObj *child() { return ch; } + }; + + py::class_<SpecialHolderObj, custom_unique_ptr<SpecialHolderObj>>(m, "SpecialHolderObj") + .def(py::init<int>()) + .def("child", &SpecialHolderObj::child, pybind11::return_value_policy::reference_internal) + .def_readwrite("val", &SpecialHolderObj::val) + .def_static("holder_cstats", &ConstructorStats::get<custom_unique_ptr<SpecialHolderObj>>, + py::return_value_policy::reference); + + /// Issue #484: number conversion generates unhandled exceptions + m2.def("test_complex", [](float x) { py::print("{}"_s.format(x)); }); + m2.def("test_complex", [](std::complex<float> x) { py::print("({}, {})"_s.format(x.real(), x.imag())); }); + + /// Issue #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>(m2, "MyBase") + .def_static("make", &MyBase::make); + + py::class_<MyDerived, MyBase>(m2, "MyDerived") + .def_static("make", &MyDerived::make) + .def_static("make2", &MyDerived::make); + + py::dict d; + std::string bar = "bar"; + d["str"] = bar; + d["num"] = 3.7; + + /// Issue #528: templated constructor + m2.def("tpl_constr_vector", [](std::vector<TplConstrClass> &) {}); + m2.def("tpl_constr_map", [](std::unordered_map<TplConstrClass, TplConstrClass> &) {}); + m2.def("tpl_constr_set", [](std::unordered_set<TplConstrClass> &) {}); +#if defined(PYBIND11_HAS_OPTIONAL) + m2.def("tpl_constr_optional", [](std::optional<TplConstrClass> &) {}); +#elif defined(PYBIND11_HAS_EXP_OPTIONAL) + m2.def("tpl_constr_optional", [](std::experimental::optional<TplConstrClass> &) {}); +#endif +} + +// MSVC workaround: trying to use a lambda here crashes MSCV +test_initializer issues(&init_issues); diff --git a/ext/pybind11/tests/test_issues.py b/ext/pybind11/tests/test_issues.py new file mode 100644 index 000000000..2098ff8a3 --- /dev/null +++ b/ext/pybind11/tests/test_issues.py @@ -0,0 +1,252 @@ +import pytest +import gc +from pybind11_tests import ConstructorStats + + +def test_regressions(): + from pybind11_tests.issues import print_cchar, print_char + + # #137: const char* isn't handled properly + assert print_cchar("const char *") == "const char *" + # #150: char bindings broken + assert print_char("c") == "c" + + +def test_dispatch_issue(msg): + """#159: virtual function dispatch has problems with similar-named functions""" + from pybind11_tests.issues import DispatchIssue, dispatch_issue_go + + class PyClass1(DispatchIssue): + def dispatch(self): + return "Yay.." + + class PyClass2(DispatchIssue): + def dispatch(self): + with pytest.raises(RuntimeError) as excinfo: + super(PyClass2, self).dispatch() + assert msg(excinfo.value) == 'Tried to call pure virtual function "Base::dispatch"' + + p = PyClass1() + return dispatch_issue_go(p) + + b = PyClass2() + assert dispatch_issue_go(b) == "Yay.." + + +def test_reference_wrapper(): + """#171: Can't return reference wrappers (or STL data structures containing them)""" + from pybind11_tests.issues import Placeholder, return_vec_of_reference_wrapper + + assert str(return_vec_of_reference_wrapper(Placeholder(4))) == \ + "[Placeholder[1], Placeholder[2], Placeholder[3], Placeholder[4]]" + + +def test_iterator_passthrough(): + """#181: iterator passthrough did not compile""" + from pybind11_tests.issues import iterator_passthrough + + assert list(iterator_passthrough(iter([3, 5, 7, 9, 11, 13, 15]))) == [3, 5, 7, 9, 11, 13, 15] + + +def test_shared_ptr_gc(): + """// #187: issue involving std::shared_ptr<> return value policy & garbage collection""" + from pybind11_tests.issues import ElementList, ElementA + + el = ElementList() + for i in range(10): + el.add(ElementA(i)) + gc.collect() + for i, v in enumerate(el.get()): + assert i == v.value() + + +def test_no_id(msg): + from pybind11_tests.issues import get_element, expect_float, expect_int + + with pytest.raises(TypeError) as excinfo: + get_element(None) + assert msg(excinfo.value) == """ + get_element(): incompatible function arguments. The following argument types are supported: + 1. (arg0: m.issues.ElementA) -> int + + Invoked with: None + """ + + with pytest.raises(TypeError) as excinfo: + expect_int(5.2) + assert msg(excinfo.value) == """ + expect_int(): incompatible function arguments. The following argument types are supported: + 1. (arg0: int) -> int + + Invoked with: 5.2 + """ + assert expect_float(12) == 12 + + +def test_str_issue(msg): + """Issue #283: __str__ called on uninitialized instance when constructor arguments invalid""" + from pybind11_tests.issues import StrIssue + + assert str(StrIssue(3)) == "StrIssue[3]" + + with pytest.raises(TypeError) as excinfo: + str(StrIssue("no", "such", "constructor")) + assert msg(excinfo.value) == """ + __init__(): incompatible constructor arguments. The following argument types are supported: + 1. m.issues.StrIssue(arg0: int) + 2. m.issues.StrIssue() + + Invoked with: 'no', 'such', 'constructor' + """ + + +def test_nested(): + """ #328: first member in a class can't be used in operators""" + from pybind11_tests.issues import NestA, NestB, NestC, get_NestA, get_NestB, get_NestC + + a = NestA() + b = NestB() + c = NestC() + + a += 10 + assert get_NestA(a) == 13 + b.a += 100 + assert get_NestA(b.a) == 103 + c.b.a += 1000 + assert get_NestA(c.b.a) == 1003 + b -= 1 + assert get_NestB(b) == 3 + c.b -= 3 + assert get_NestB(c.b) == 1 + c *= 7 + assert 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 + gc.collect() + del a # Should't delete while abase is still alive + gc.collect() + + assert abase.value == 42 + del abase, b + gc.collect() + + +def test_move_fallback(): + from pybind11_tests.issues import get_moveissue1, get_moveissue2 + m2 = get_moveissue2(2) + assert m2.value == 2 + m1 = get_moveissue1(1) + assert m1.value == 1 + + +def test_override_ref(): + from pybind11_tests.issues import OverrideTest + o = OverrideTest("asdf") + + # Not allowed (see associated .cpp comment) + # i = o.str_ref() + # assert o.str_ref() == "asdf" + assert o.str_value() == "asdf" + + assert o.A_value().value == "hi" + a = o.A_ref() + assert a.value == "hi" + a.value = "bye" + assert a.value == "bye" + + +def test_operators_notimplemented(capture): + from pybind11_tests.issues import OpTest1, OpTest2 + with capture: + c1, c2 = OpTest1(), OpTest2() + c1 + c1 + c2 + c2 + c2 + c1 + c1 + c2 + assert capture == """ + Add OpTest1 with OpTest1 + Add OpTest2 with OpTest2 + Add OpTest2 with OpTest1 + Add OpTest2 with OpTest1 + """ + + +def test_iterator_rvpolicy(): + """ Issue 388: Can't make iterators via make_iterator() with different r/v policies """ + from pybind11_tests.issues import make_iterator_1 + from pybind11_tests.issues import make_iterator_2 + + assert list(make_iterator_1()) == [1, 2, 3] + assert list(make_iterator_2()) == [1, 2, 3] + assert not isinstance(make_iterator_1(), type(make_iterator_2())) + + +def test_dupe_assignment(): + """ Issue 461: overwriting a class with a function """ + from pybind11_tests.issues import dupe_exception_failures + assert dupe_exception_failures() == [] + + +def test_enable_shared_from_this_with_reference_rvp(): + """ Issue #471: shared pointer instance not dellocated """ + from pybind11_tests import SharedParent, SharedChild + + parent = SharedParent() + child = parent.get_child() + + cstats = ConstructorStats.get(SharedChild) + assert cstats.alive() == 1 + del child, parent + assert cstats.alive() == 0 + + +def test_non_destructed_holders(): + """ Issue #478: unique ptrs constructed and freed without destruction """ + from pybind11_tests import SpecialHolderObj + + a = SpecialHolderObj(123) + b = a.child() + + assert a.val == 123 + assert b.val == 124 + + cstats = SpecialHolderObj.holder_cstats() + assert cstats.alive() == 1 + del b + assert cstats.alive() == 1 + del a + assert cstats.alive() == 0 + + +def test_complex_cast(capture): + """ Issue #484: number conversion generates unhandled exceptions """ + from pybind11_tests.issues import test_complex + + with capture: + test_complex(1) + test_complex(2j) + + assert capture == """ + 1.0 + (0.0, 2.0) + """ + + +def test_inheritance_override_def_static(): + from pybind11_tests.issues import MyBase, MyDerived + + b = MyBase.make() + d1 = MyDerived.make2() + d2 = MyDerived.make() + + assert isinstance(b, MyBase) + assert isinstance(d1, MyDerived) + assert isinstance(d2, MyDerived) diff --git a/ext/pybind11/tests/test_keep_alive.cpp b/ext/pybind11/tests/test_keep_alive.cpp new file mode 100644 index 000000000..cd62a02e8 --- /dev/null +++ b/ext/pybind11/tests/test_keep_alive.cpp @@ -0,0 +1,40 @@ +/* + tests/test_keep_alive.cpp -- keep_alive modifier (pybind11's version + of Boost.Python's with_custodian_and_ward / with_custodian_and_ward_postcall) + + 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" + +class Child { +public: + Child() { py::print("Allocating child."); } + ~Child() { py::print("Releasing child."); } +}; + +class Parent { +public: + Parent() { py::print("Allocating parent."); } + ~Parent() { py::print("Releasing parent."); } + void addChild(Child *) { } + Child *returnChild() { return new Child(); } + Child *returnNullChild() { return nullptr; } +}; + +test_initializer keep_alive([](py::module &m) { + py::class_<Parent>(m, "Parent") + .def(py::init<>()) + .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>()); + + py::class_<Child>(m, "Child") + .def(py::init<>()); +}); diff --git a/ext/pybind11/tests/test_keep_alive.py b/ext/pybind11/tests/test_keep_alive.py new file mode 100644 index 000000000..0cef34658 --- /dev/null +++ b/ext/pybind11/tests/test_keep_alive.py @@ -0,0 +1,97 @@ +import gc + + +def test_keep_alive_argument(capture): + from pybind11_tests import Parent, Child + + with capture: + p = Parent() + assert capture == "Allocating parent." + with capture: + p.addChild(Child()) + gc.collect() + assert capture == """ + Allocating child. + Releasing child. + """ + with capture: + del p + gc.collect() + assert capture == "Releasing parent." + + with capture: + p = Parent() + assert capture == "Allocating parent." + with capture: + p.addChildKeepAlive(Child()) + gc.collect() + assert capture == "Allocating child." + with capture: + del p + gc.collect() + assert capture == """ + Releasing parent. + Releasing child. + """ + + +def test_keep_alive_return_value(capture): + from pybind11_tests import Parent + + with capture: + p = Parent() + assert capture == "Allocating parent." + with capture: + p.returnChild() + gc.collect() + assert capture == """ + Allocating child. + Releasing child. + """ + with capture: + del p + gc.collect() + assert capture == "Releasing parent." + + with capture: + p = Parent() + assert capture == "Allocating parent." + with capture: + p.returnChildKeepAlive() + gc.collect() + assert capture == "Allocating child." + with capture: + del p + gc.collect() + assert capture == """ + Releasing parent. + Releasing child. + """ + + +def test_return_none(capture): + from pybind11_tests import Parent + + with capture: + p = Parent() + assert capture == "Allocating parent." + with capture: + p.returnNullChildKeepAliveChild() + gc.collect() + assert capture == "" + with capture: + del p + gc.collect() + assert capture == "Releasing parent." + + with capture: + p = Parent() + assert capture == "Allocating parent." + with capture: + p.returnNullChildKeepAliveParent() + gc.collect() + assert capture == "" + with capture: + del p + gc.collect() + assert capture == "Releasing parent." diff --git a/ext/pybind11/tests/test_kwargs_and_defaults.cpp b/ext/pybind11/tests/test_kwargs_and_defaults.cpp new file mode 100644 index 000000000..24fc0cd5b --- /dev/null +++ b/ext/pybind11/tests/test_kwargs_and_defaults.cpp @@ -0,0 +1,56 @@ +/* + 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_tests.h" +#include <pybind11/stl.h> + +std::string kw_func(int x, int y) { return "x=" + std::to_string(x) + ", y=" + std::to_string(y); } + +std::string kw_func4(const std::vector<int> &entries) { + std::string ret = "{"; + for (int i : entries) + ret += std::to_string(i) + " "; + ret.back() = '}'; + return ret; +} + +py::tuple args_function(py::args args) { + return args; +} + +py::tuple args_kwargs_function(py::args args, py::kwargs kwargs) { + return py::make_tuple(args, kwargs); +} + +struct KWClass { + void foo(int, float) {} +}; + +test_initializer arg_keywords_and_defaults([](py::module &m) { + 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; + list.push_back(13); + list.push_back(17); + m.def("kw_func4", &kw_func4, py::arg("myList") = list); + + m.def("args_function", &args_function); + m.def("args_kwargs_function", &args_kwargs_function); + + 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); + + py::class_<KWClass>(m, "KWClass") + .def("foo0", &KWClass::foo) + .def("foo1", &KWClass::foo, "x"_a, "y"_a); +}); diff --git a/ext/pybind11/tests/test_kwargs_and_defaults.py b/ext/pybind11/tests/test_kwargs_and_defaults.py new file mode 100644 index 000000000..852d03c6e --- /dev/null +++ b/ext/pybind11/tests/test_kwargs_and_defaults.py @@ -0,0 +1,57 @@ +import pytest +from pybind11_tests import (kw_func0, kw_func1, kw_func2, kw_func3, kw_func4, args_function, + args_kwargs_function, kw_func_udl, kw_func_udl_z, KWClass) + + +def test_function_signatures(doc): + assert doc(kw_func0) == "kw_func0(arg0: int, arg1: int) -> str" + assert doc(kw_func1) == "kw_func1(x: int, y: int) -> str" + assert doc(kw_func2) == "kw_func2(x: int=100, y: int=200) -> str" + assert doc(kw_func3) == "kw_func3(data: str='Hello world!') -> None" + assert doc(kw_func4) == "kw_func4(myList: List[int]=[13, 17]) -> str" + assert doc(kw_func_udl) == "kw_func_udl(x: int, y: int=300) -> str" + assert doc(kw_func_udl_z) == "kw_func_udl_z(x: int, y: int=0) -> str" + assert doc(args_function) == "args_function(*args) -> tuple" + assert doc(args_kwargs_function) == "args_kwargs_function(*args, **kwargs) -> tuple" + assert doc(KWClass.foo0) == "foo0(self: m.KWClass, arg0: int, arg1: float) -> None" + assert doc(KWClass.foo1) == "foo1(self: m.KWClass, x: int, y: float) -> None" + + +def test_named_arguments(msg): + assert kw_func0(5, 10) == "x=5, y=10" + + assert kw_func1(5, 10) == "x=5, y=10" + assert kw_func1(5, y=10) == "x=5, y=10" + assert kw_func1(y=10, x=5) == "x=5, y=10" + + assert kw_func2() == "x=100, y=200" + assert kw_func2(5) == "x=5, y=200" + assert kw_func2(x=5) == "x=5, y=200" + assert kw_func2(y=10) == "x=100, y=10" + assert kw_func2(5, 10) == "x=5, y=10" + assert kw_func2(x=5, y=10) == "x=5, y=10" + + with pytest.raises(TypeError) as excinfo: + # noinspection PyArgumentList + kw_func2(x=5, y=10, z=12) + assert msg(excinfo.value) == """ + kw_func2(): incompatible function arguments. The following argument types are supported: + 1. (x: int=100, y: int=200) -> str + + Invoked with: + """ + + assert kw_func4() == "{13 17}" + assert kw_func4(myList=[1, 2, 3]) == "{1 2 3}" + + assert kw_func_udl(x=5, y=10) == "x=5, y=10" + assert kw_func_udl_z(x=5) == "x=5, y=0" + + +def test_arg_and_kwargs(): + args = 'arg1_value', 'arg2_value', 3 + assert args_function(*args) == args + + args = 'a1', 'a2' + kwargs = dict(arg3='a3', arg4=4) + assert args_kwargs_function(*args, **kwargs) == (args, kwargs) diff --git a/ext/pybind11/tests/test_methods_and_attributes.cpp b/ext/pybind11/tests/test_methods_and_attributes.cpp new file mode 100644 index 000000000..11fc90091 --- /dev/null +++ b/ext/pybind11/tests/test_methods_and_attributes.cpp @@ -0,0 +1,185 @@ +/* + 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 "pybind11_tests.h" +#include "constructor_stats.h" + +class ExampleMandA { +public: + ExampleMandA() { print_default_created(this); } + ExampleMandA(int value) : value(value) { print_created(this, value); } + ExampleMandA(const ExampleMandA &e) : value(e.value) { print_copy_created(this); } + ExampleMandA(ExampleMandA &&e) : value(e.value) { print_move_created(this); } + ~ExampleMandA() { print_destroyed(this); } + + std::string toString() { + return "ExampleMandA[value=" + std::to_string(value) + "]"; + } + + void operator=(const ExampleMandA &e) { print_copy_assigned(this); value = e.value; } + void operator=(ExampleMandA &&e) { print_move_assigned(this); value = e.value; } + + 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 + void add9(int *other) { value += *other; } // passing by pointer + void add10(const int *other) { value += *other; } // passing by const pointer + + ExampleMandA self1() { return *this; } // return by value + ExampleMandA &self2() { return *this; } // return by reference + const ExampleMandA &self3() { return *this; } // return by const reference + ExampleMandA *self4() { return this; } // return by pointer + const ExampleMandA *self5() { return this; } // return by const pointer + + int internal1() { return value; } // return by value + int &internal2() { return value; } // return by reference + const int &internal3() { 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(int, float) { return "(int, float)"; } + py::str overloaded(float, int) { return "(float, int)"; } + py::str overloaded(int, float) const { return "(int, float) const"; } + py::str overloaded(float, int) const { return "(float, int) const"; } + + 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 SimpleValue { int value = 1; }; + +struct TestPropRVP { + SimpleValue v1; + SimpleValue v2; + static SimpleValue sv1; + static SimpleValue sv2; + + const SimpleValue &get1() const { return v1; } + const SimpleValue &get2() const { return v2; } + SimpleValue get_rvalue() const { return v2; } + void set1(int v) { v1.value = v; } + void set2(int v) { v2.value = v; } +}; + +SimpleValue TestPropRVP::sv1{}; +SimpleValue TestPropRVP::sv2{}; + +class DynamicClass { +public: + DynamicClass() { print_default_created(this); } + ~DynamicClass() { print_destroyed(this); } +}; + +class CppDerivedDynamicClass : public DynamicClass { }; + +test_initializer methods_and_attributes([](py::module &m) { + py::class_<ExampleMandA>(m, "ExampleMandA") + .def(py::init<>()) + .def(py::init<int>()) + .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("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<int, float>(&ExampleMandA::overloaded)) + .def("overloaded", py::overload_cast<float, int>(&ExampleMandA::overloaded)) + .def("overloaded_const", py::overload_cast<int, float>(&ExampleMandA::overloaded, py::const_)) + .def("overloaded_const", py::overload_cast<float, int>(&ExampleMandA::overloaded, py::const_)) +#else + .def("overloaded", static_cast<py::str (ExampleMandA::*)(int, float)>(&ExampleMandA::overloaded)) + .def("overloaded", static_cast<py::str (ExampleMandA::*)(float, int)>(&ExampleMandA::overloaded)) + .def("overloaded_const", static_cast<py::str (ExampleMandA::*)(int, float) const>(&ExampleMandA::overloaded)) + .def("overloaded_const", static_cast<py::str (ExampleMandA::*)(float, int) const>(&ExampleMandA::overloaded)) +#endif + .def("__str__", &ExampleMandA::toString) + .def_readwrite("value", &ExampleMandA::value) + ; + + py::class_<TestProperties>(m, "TestProperties") + .def(py::init<>()) + .def_readonly("def_readonly", &TestProperties::value) + .def_readwrite("def_readwrite", &TestProperties::value) + .def_property_readonly("def_property_readonly", &TestProperties::get) + .def_property("def_property", &TestProperties::get, &TestProperties::set) + .def_readonly_static("def_readonly_static", &TestProperties::static_value) + .def_readwrite_static("def_readwrite_static", &TestProperties::static_value) + .def_property_readonly_static("def_property_readonly_static", + [](py::object) { return TestProperties::static_get(); }) + .def_property_static("def_property_static", + [](py::object) { return TestProperties::static_get(); }, + [](py::object, int v) { return TestProperties::static_set(v); }); + + py::class_<SimpleValue>(m, "SimpleValue") + .def_readwrite("value", &SimpleValue::value); + + auto static_get1 = [](py::object) -> const SimpleValue & { return TestPropRVP::sv1; }; + auto static_get2 = [](py::object) -> const SimpleValue & { return TestPropRVP::sv2; }; + auto static_set1 = [](py::object, int v) { TestPropRVP::sv1.value = v; }; + auto static_set2 = [](py::object, int v) { TestPropRVP::sv2.value = v; }; + auto rvp_copy = py::return_value_policy::copy; + + 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) + .def_property_readonly("rvalue", &TestPropRVP::get_rvalue) + .def_property_readonly_static("static_rvalue", [](py::object) { return SimpleValue(); }); + + py::class_<DynamicClass>(m, "DynamicClass", py::dynamic_attr()) + .def(py::init()); + + py::class_<CppDerivedDynamicClass, DynamicClass>(m, "CppDerivedDynamicClass") + .def(py::init()); +}); diff --git a/ext/pybind11/tests/test_methods_and_attributes.py b/ext/pybind11/tests/test_methods_and_attributes.py new file mode 100644 index 000000000..2b0f8d571 --- /dev/null +++ b/ext/pybind11/tests/test_methods_and_attributes.py @@ -0,0 +1,194 @@ +import pytest +from pybind11_tests import ExampleMandA, ConstructorStats + + +def test_methods_and_attributes(): + instance1 = ExampleMandA() + instance2 = 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(1, 1.0) == "(int, float)" + assert instance1.overloaded(2.0, 2) == "(float, int)" + assert instance1.overloaded_const(3, 3.0) == "(int, float) const" + assert instance1.overloaded_const(4.0, 4) == "(float, int) const" + + assert instance1.value == 320 + instance1.value = 100 + assert str(instance1) == "ExampleMandA[value=100]" + + cstats = ConstructorStats.get(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 == 3 + assert cstats.move_constructions >= 1 + assert cstats.copy_assignments == 0 + assert cstats.move_assignments == 0 + + +def test_properties(): + from pybind11_tests import TestProperties + + instance = 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 + + +def test_static_properties(): + from pybind11_tests import TestProperties as Type + + assert Type.def_readonly_static == 1 + with pytest.raises(AttributeError): + Type.def_readonly_static = 2 + + Type.def_readwrite_static = 2 + assert Type.def_readwrite_static == 2 + + assert Type.def_property_readonly_static == 2 + with pytest.raises(AttributeError): + Type.def_property_readonly_static = 3 + + Type.def_property_static = 3 + assert Type.def_property_static == 3 + + +@pytest.mark.parametrize("access", ["ro", "rw", "static_ro", "static_rw"]) +def test_property_return_value_policies(access): + from pybind11_tests import TestPropRVP + + if not access.startswith("static"): + obj = TestPropRVP() + else: + obj = 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. + """ + from pybind11_tests import TestPropRVP + + instance = TestPropRVP() + o = instance.rvalue + assert o.value == 1 + o = TestPropRVP.static_rvalue + assert o.value == 1 + + +def test_dynamic_attributes(): + from pybind11_tests import DynamicClass, CppDerivedDynamicClass + + instance = 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(DynamicClass) + assert cstats.alive() == 1 + del instance + assert cstats.alive() == 0 + + # Derived classes should work as well + class PythonDerivedDynamicClass(DynamicClass): + pass + + for cls in CppDerivedDynamicClass, PythonDerivedDynamicClass: + derived = cls() + derived.foobar = 100 + assert derived.foobar == 100 + + assert cstats.alive() == 1 + del derived + assert cstats.alive() == 0 + + +def test_cyclic_gc(): + from pybind11_tests import DynamicClass + + # One object references itself + instance = DynamicClass() + instance.circular_reference = instance + + cstats = ConstructorStats.get(DynamicClass) + assert cstats.alive() == 1 + del instance + assert cstats.alive() == 0 + + # Two object reference each other + i1 = DynamicClass() + i2 = DynamicClass() + i1.cycle = i2 + i2.cycle = i1 + + assert cstats.alive() == 2 + del i1, i2 + assert cstats.alive() == 0 diff --git a/ext/pybind11/tests/test_modules.cpp b/ext/pybind11/tests/test_modules.cpp new file mode 100644 index 000000000..50c7d8412 --- /dev/null +++ b/ext/pybind11/tests/test_modules.cpp @@ -0,0 +1,58 @@ +/* + 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 "pybind11_tests.h" +#include "constructor_stats.h" + +std::string submodule_func() { + return "submodule_func()"; +} + +class A { +public: + A(int v) : v(v) { print_created(this, v); } + ~A() { print_destroyed(this); } + A(const A&) { print_copy_created(this); } + A& operator=(const A ©) { print_copy_assigned(this); v = copy.v; return *this; } + std::string toString() { return "A[" + std::to_string(v) + "]"; } +private: + int v; +}; + +class B { +public: + B() { print_default_created(this); } + ~B() { print_destroyed(this); } + B(const B&) { print_copy_created(this); } + B& operator=(const B ©) { 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}; +}; + +test_initializer modules([](py::module &m) { + py::module m_sub = m.def_submodule("submodule"); + m_sub.def("submodule_func", &submodule_func); + + py::class_<A>(m_sub, "A") + .def(py::init<int>()) + .def("__repr__", &A::toString); + + 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); + + m.attr("OD") = py::module::import("collections").attr("OrderedDict"); +}); diff --git a/ext/pybind11/tests/test_modules.py b/ext/pybind11/tests/test_modules.py new file mode 100644 index 000000000..fe72f190a --- /dev/null +++ b/ext/pybind11/tests/test_modules.py @@ -0,0 +1,54 @@ + +def test_nested_modules(): + import pybind11_tests + from pybind11_tests.submodule import submodule_func + + assert pybind11_tests.__name__ == "pybind11_tests" + assert pybind11_tests.submodule.__name__ == "pybind11_tests.submodule" + + assert submodule_func() == "submodule_func()" + + +def test_reference_internal(): + from pybind11_tests import ConstructorStats + from pybind11_tests.submodule import A, B + + b = 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 = A(42) + b.a2 = 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(A), ConstructorStats.get(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 pybind11_tests import OD + from collections import OrderedDict + + assert OD is OrderedDict + assert str(OD([(1, 'a'), (2, 'b')])) == "OrderedDict([(1, 'a'), (2, 'b')])" diff --git a/ext/pybind11/tests/test_multiple_inheritance.cpp b/ext/pybind11/tests/test_multiple_inheritance.cpp new file mode 100644 index 000000000..3cb12b68d --- /dev/null +++ b/ext/pybind11/tests/test_multiple_inheritance.cpp @@ -0,0 +1,84 @@ +/* + 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 "pybind11_tests.h" + + +struct Base1 { + Base1(int i) : i(i) { } + int foo() { return i; } + int i; +}; + +struct Base2 { + Base2(int i) : i(i) { } + int bar() { return i; } + int i; +}; + +struct Base12 : Base1, Base2 { + Base12(int i, int j) : Base1(i), Base2(j) { } +}; + +struct MIType : Base12 { + MIType(int i, int j) : Base12(i, j) { } +}; + +test_initializer multiple_inheritance([](py::module &m) { + py::class_<Base1>(m, "Base1") + .def(py::init<int>()) + .def("foo", &Base1::foo); + + py::class_<Base2>(m, "Base2") + .def(py::init<int>()) + .def("bar", &Base2::bar); + + py::class_<Base12, Base1, Base2>(m, "Base12"); + + py::class_<MIType, Base12>(m, "MIType") + .def(py::init<int, int>()); +}); + +/* 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 */ + +struct Base1a { + Base1a(int i) : i(i) { } + int foo() { return i; } + int i; +}; + +struct Base2a { + Base2a(int i) : i(i) { } + int bar() { return i; } + int i; +}; + +struct Base12a : Base1a, Base2a { + Base12a(int i, int j) : Base1a(i), Base2a(j) { } +}; + +test_initializer multiple_inheritance_nonexplicit([](py::module &m) { + 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", [](std::shared_ptr<Base2a> b) { return b->bar(); }); +}); diff --git a/ext/pybind11/tests/test_multiple_inheritance.py b/ext/pybind11/tests/test_multiple_inheritance.py new file mode 100644 index 000000000..581cf5687 --- /dev/null +++ b/ext/pybind11/tests/test_multiple_inheritance.py @@ -0,0 +1,64 @@ + + +def test_multiple_inheritance_cpp(): + from pybind11_tests import MIType + + mt = MIType(3, 4) + + assert mt.foo() == 3 + assert mt.bar() == 4 + + +def test_multiple_inheritance_mix1(): + from pybind11_tests import Base2 + + class Base1: + def __init__(self, i): + self.i = i + + def foo(self): + return self.i + + class MITypePy(Base1, Base2): + def __init__(self, i, j): + Base1.__init__(self, i) + Base2.__init__(self, j) + + mt = MITypePy(3, 4) + + assert mt.foo() == 3 + assert mt.bar() == 4 + + +def test_multiple_inheritance_mix2(): + from pybind11_tests import Base1 + + class Base2: + def __init__(self, i): + self.i = i + + def bar(self): + return self.i + + class MITypePy(Base1, Base2): + def __init__(self, i, j): + Base1.__init__(self, i) + Base2.__init__(self, j) + + mt = MITypePy(3, 4) + + assert mt.foo() == 3 + assert mt.bar() == 4 + + +def test_multiple_inheritance_virtbase(): + from pybind11_tests import Base12a, bar_base2a, bar_base2a_sharedptr + + class MITypePy(Base12a): + def __init__(self, i, j): + Base12a.__init__(self, i, j) + + mt = MITypePy(3, 4) + assert mt.bar() == 4 + assert bar_base2a(mt) == 4 + assert bar_base2a_sharedptr(mt) == 4 diff --git a/ext/pybind11/tests/test_numpy_array.cpp b/ext/pybind11/tests/test_numpy_array.cpp new file mode 100644 index 000000000..14c4c2999 --- /dev/null +++ b/ext/pybind11/tests/test_numpy_array.cpp @@ -0,0 +1,153 @@ +/* + 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_tests.h" + +#include <pybind11/numpy.h> +#include <pybind11/stl.h> + +#include <cstdint> +#include <vector> + +using arr = py::array; +using arr_t = py::array_t<uint16_t, 0>; + +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...)); +} + +arr& mutate_data(arr& a) { + auto ptr = (uint8_t *) a.mutable_data(); + for (size_t i = 0; i < a.nbytes(); i++) + ptr[i] = (uint8_t) (ptr[i] * 2); + return a; +} + +arr_t& mutate_data_t(arr_t& a) { + auto ptr = a.mutable_data(); + for (size_t i = 0; i < a.size(); i++) + ptr[i]++; + return a; +} + +template<typename... Ix> arr& mutate_data(arr& a, Ix... index) { + auto ptr = (uint8_t *) a.mutable_data(index...); + for (size_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 (size_t i = 0; i < a.size() - a.index_at(index...); i++) + ptr[i]++; + return a; +} + +template<typename... Ix> size_t index_at(const arr& a, Ix... idx) { return a.index_at(idx...); } +template<typename... Ix> size_t index_at_t(const arr_t& a, Ix... idx) { return a.index_at(idx...); } +template<typename... Ix> size_t offset_at(const arr& a, Ix... idx) { return a.offset_at(idx...); } +template<typename... Ix> size_t offset_at_t(const arr_t& a, Ix... idx) { return a.offset_at(idx...); } +template<typename... Ix> size_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); }); + +test_initializer numpy_array([](py::module &m) { + auto sm = m.def_submodule("array"); + + 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, size_t dim) { return a.shape(dim); }); + sm.def("strides", [](const arr& a) { return arr(a.ndim(), a.strides()); }); + sm.def("strides", [](const arr& a, size_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(); }); + + def_index_fn(data, const arr&); + def_index_fn(data_t, const arr_t&); + 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&); + 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&); + + 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 }); + }); + + sm.def("wrap", [](py::array a) { + return py::array( + a.dtype(), + std::vector<size_t>(a.shape(), a.shape() + a.ndim()), + std::vector<size_t>(a.strides(), a.strides() + a.ndim()), + a.data(), + a + ); + }); + + 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()"); + ArrayClass &a = obj.cast<ArrayClass&>(); + return py::array_t<int>({2}, {4}, a.data, obj); + } + ); + + sm.def("function_taking_uint64", [](uint64_t) { }); + + sm.def("isinstance_untyped", [](py::object yes, py::object no) { + return py::isinstance<py::array>(yes) && !py::isinstance<py::array>(no); + }); + + sm.def("isinstance_typed", [](py::object o) { + return py::isinstance<py::array_t<double>>(o) && !py::isinstance<py::array_t<int>>(o); + }); + + 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", [](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) + ); + }); +}); diff --git a/ext/pybind11/tests/test_numpy_array.py b/ext/pybind11/tests/test_numpy_array.py new file mode 100644 index 000000000..1c218a10b --- /dev/null +++ b/ext/pybind11/tests/test_numpy_array.py @@ -0,0 +1,273 @@ +import pytest +import gc + +with pytest.suppress(ImportError): + import numpy as np + + +@pytest.fixture(scope='function') +def arr(): + return np.array([[1, 2, 3], [4, 5, 6]], '<u2') + + +@pytest.requires_numpy +def test_array_attributes(): + from pybind11_tests.array import ( + ndim, shape, strides, writeable, size, itemsize, nbytes, owndata + ) + + a = np.array(0, 'f8') + assert ndim(a) == 0 + assert all(shape(a) == []) + assert all(strides(a) == []) + with pytest.raises(IndexError) as excinfo: + shape(a, 0) + assert str(excinfo.value) == 'invalid axis: 0 (ndim = 0)' + with pytest.raises(IndexError) as excinfo: + strides(a, 0) + assert str(excinfo.value) == 'invalid axis: 0 (ndim = 0)' + assert writeable(a) + assert size(a) == 1 + assert itemsize(a) == 8 + assert nbytes(a) == 8 + assert owndata(a) + + a = np.array([[1, 2, 3], [4, 5, 6]], 'u2').view() + a.flags.writeable = False + assert ndim(a) == 2 + assert all(shape(a) == [2, 3]) + assert shape(a, 0) == 2 + assert shape(a, 1) == 3 + assert all(strides(a) == [6, 2]) + assert strides(a, 0) == 6 + assert strides(a, 1) == 2 + with pytest.raises(IndexError) as excinfo: + shape(a, 2) + assert str(excinfo.value) == 'invalid axis: 2 (ndim = 2)' + with pytest.raises(IndexError) as excinfo: + strides(a, 2) + assert str(excinfo.value) == 'invalid axis: 2 (ndim = 2)' + assert not writeable(a) + assert size(a) == 6 + assert itemsize(a) == 2 + assert nbytes(a) == 12 + assert not owndata(a) + + +@pytest.requires_numpy +@pytest.mark.parametrize('args, ret', [([], 0), ([0], 0), ([1], 3), ([0, 1], 1), ([1, 2], 5)]) +def test_index_offset(arr, args, ret): + from pybind11_tests.array import index_at, index_at_t, offset_at, offset_at_t + assert index_at(arr, *args) == ret + assert index_at_t(arr, *args) == ret + assert offset_at(arr, *args) == ret * arr.dtype.itemsize + assert offset_at_t(arr, *args) == ret * arr.dtype.itemsize + + +@pytest.requires_numpy +def test_dim_check_fail(arr): + from pybind11_tests.array import (index_at, index_at_t, offset_at, offset_at_t, data, data_t, + mutate_data, mutate_data_t) + for func in (index_at, index_at_t, offset_at, offset_at_t, data, data_t, + mutate_data, 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.requires_numpy +@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 pybind11_tests.array import data, data_t + assert all(data_t(arr, *args) == ret) + assert all(data(arr, *args)[::2] == ret) + assert all(data(arr, *args)[1::2] == 0) + + +@pytest.requires_numpy +def test_mutate_readonly(arr): + from pybind11_tests.array import mutate_data, mutate_data_t, mutate_at_t + arr.flags.writeable = False + for func, args in (mutate_data, ()), (mutate_data_t, ()), (mutate_at_t, (0, 0)): + with pytest.raises(RuntimeError) as excinfo: + func(arr, *args) + assert str(excinfo.value) == 'array is not writeable' + + +@pytest.requires_numpy +@pytest.mark.parametrize('dim', [0, 1, 3]) +def test_at_fail(arr, dim): + from pybind11_tests.array import at_t, mutate_at_t + for func in at_t, mutate_at_t: + with pytest.raises(IndexError) as excinfo: + func(arr, *([0] * dim)) + assert str(excinfo.value) == 'index dimension mismatch: {} (ndim = 2)'.format(dim) + + +@pytest.requires_numpy +def test_at(arr): + from pybind11_tests.array import at_t, mutate_at_t + + assert at_t(arr, 0, 2) == 3 + assert at_t(arr, 1, 0) == 4 + + assert all(mutate_at_t(arr, 0, 2).ravel() == [1, 2, 4, 4, 5, 6]) + assert all(mutate_at_t(arr, 1, 0).ravel() == [1, 2, 4, 5, 5, 6]) + + +@pytest.requires_numpy +def test_mutate_data(arr): + from pybind11_tests.array import mutate_data, mutate_data_t + + assert all(mutate_data(arr).ravel() == [2, 4, 6, 8, 10, 12]) + assert all(mutate_data(arr).ravel() == [4, 8, 12, 16, 20, 24]) + assert all(mutate_data(arr, 1).ravel() == [4, 8, 12, 32, 40, 48]) + assert all(mutate_data(arr, 0, 1).ravel() == [4, 16, 24, 64, 80, 96]) + assert all(mutate_data(arr, 1, 2).ravel() == [4, 16, 24, 64, 80, 192]) + + assert all(mutate_data_t(arr).ravel() == [5, 17, 25, 65, 81, 193]) + assert all(mutate_data_t(arr).ravel() == [6, 18, 26, 66, 82, 194]) + assert all(mutate_data_t(arr, 1).ravel() == [6, 18, 26, 67, 83, 195]) + assert all(mutate_data_t(arr, 0, 1).ravel() == [6, 19, 27, 68, 84, 196]) + assert all(mutate_data_t(arr, 1, 2).ravel() == [6, 19, 27, 68, 84, 197]) + + +@pytest.requires_numpy +def test_bounds_check(arr): + from pybind11_tests.array import (index_at, index_at_t, data, data_t, + mutate_data, mutate_data_t, at_t, mutate_at_t) + funcs = (index_at, index_at_t, data, data_t, + mutate_data, mutate_data_t, at_t, mutate_at_t) + for func in funcs: + 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' + + +@pytest.requires_numpy +def test_make_c_f_array(): + from pybind11_tests.array import ( + make_c_array, make_f_array + ) + assert make_c_array().flags.c_contiguous + assert not make_c_array().flags.f_contiguous + assert make_f_array().flags.f_contiguous + assert not make_f_array().flags.c_contiguous + + +@pytest.requires_numpy +def test_wrap(): + from pybind11_tests.array import wrap + + def assert_references(a, b): + 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 + assert a.flags.updateifcopy == b.flags.updateifcopy + assert np.all(a == b) + assert not b.flags.owndata + assert b.base is a + 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 = 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 = wrap(a1) + assert_references(a1, a2) + + a1 = np.array([[1, 2], [3, 4]], dtype=np.float32, order='C') + a1.flags.writeable = False + a2 = wrap(a1) + assert_references(a1, a2) + + a1 = np.random.random((4, 4, 4)) + a2 = wrap(a1) + assert_references(a1, a2) + + a1 = a1.transpose() + a2 = wrap(a1) + assert_references(a1, a2) + + a1 = a1.diagonal() + a2 = wrap(a1) + assert_references(a1, a2) + + +@pytest.requires_numpy +def test_numpy_view(capture): + from pybind11_tests.array import ArrayClass + with capture: + ac = 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 + 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 + gc.collect() + assert capture == """ + ~ArrayClass() + """ + + +@pytest.requires_numpy +def test_cast_numpy_int64_to_uint64(): + from pybind11_tests.array import function_taking_uint64 + function_taking_uint64(123) + function_taking_uint64(np.uint64(123)) + + +@pytest.requires_numpy +def test_isinstance(): + from pybind11_tests.array import isinstance_untyped, isinstance_typed + + assert isinstance_untyped(np.array([1, 2, 3]), "not an array") + assert isinstance_typed(np.array([1.0, 2.0, 3.0])) + + +@pytest.requires_numpy +def test_constructors(): + from pybind11_tests.array import default_constructors, converting_constructors + + defaults = 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 = 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 diff --git a/ext/pybind11/tests/test_numpy_dtypes.cpp b/ext/pybind11/tests/test_numpy_dtypes.cpp new file mode 100644 index 000000000..3894f6a30 --- /dev/null +++ b/ext/pybind11/tests/test_numpy_dtypes.cpp @@ -0,0 +1,363 @@ +/* + 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_tests.h" +#include <pybind11/numpy.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 x; + uint32_t y; + float z; +}; + +std::ostream& operator<<(std::ostream& os, const SimpleStruct& v) { + return os << "s:" << v.x << "," << v.y << "," << v.z; +} + +PYBIND11_PACKED(struct PackedStruct { + bool x; + uint32_t y; + float z; +}); + +std::ostream& operator<<(std::ostream& os, const PackedStruct& v) { + return os << "p:" << v.x << "," << v.y << "," << v.z; +} + +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 x; + uint32_t y; + float z; + uint64_t dummy2; +}; + +struct PartialNestedStruct { + uint64_t dummy1; + PartialStruct a; + uint64_t dummy2; +}; + +struct UnboundStruct { }; + +struct StringStruct { + char a[3]; + std::array<char, 3> b; +}; + +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]; i++) os << v.a[i]; + os << "',b='"; + for (size_t i = 0; i < 3 && v.b[i]; i++) os << v.b[i]; + 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) })); +} + +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++) { + ptr[i].x = i % 2 != 0; ptr[i].y = (uint32_t) i; ptr[i].z = (float) i * 1.5f; + } + return arr; +} + +std::string get_format_unbound() { + return py::format_descriptor<UnboundStruct>::format(); +} + +py::array_t<NestedStruct, 0> create_nested(size_t n) { + auto 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++) { + ptr[i].a.x = i % 2 != 0; ptr[i].a.y = (uint32_t) i; ptr[i].a.z = (float) i * 1.5f; + ptr[i].b.x = (i + 1) % 2 != 0; ptr[i].b.y = (uint32_t) (i + 1); ptr[i].b.z = (float) (i + 1) * 1.5f; + } + return arr; +} + +py::array_t<PartialNestedStruct, 0> create_partial_nested(size_t n) { + auto 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++) { + ptr[i].a.x = i % 2 != 0; ptr[i].a.y = (uint32_t) i; ptr[i].a.z = (float) i * 1.5f; + } + return arr; +} + +py::array_t<StringStruct, 0> create_string_array(bool non_empty) { + auto arr = mkarray_via_buffer<StringStruct>(non_empty ? 4 : 0); + if (non_empty) { + auto req = arr.request(); + auto ptr = static_cast<StringStruct*>(req.ptr); + for (size_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; +} + +py::array_t<EnumStruct, 0> create_enum_array(size_t n) { + auto 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; +} + +template <typename S> +py::list print_recarray(py::array_t<S, 0> arr) { + const auto req = arr.request(); + const auto ptr = static_cast<S*>(req.ptr); + auto l = py::list(); + for (size_t i = 0; i < req.size; i++) { + std::stringstream ss; + ss << ptr[i]; + l.append(py::str(ss.str())); + } + return l; +} + +py::list print_format_descriptors() { + const auto fmts = { + 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<EnumStruct>::format() + }; + auto l = py::list(); + for (const auto &fmt : fmts) { + l.append(py::cast(fmt)); + } + return l; +} + +py::list print_dtypes() { + const auto dtypes = { + py::str(py::dtype::of<SimpleStruct>()), + py::str(py::dtype::of<PackedStruct>()), + py::str(py::dtype::of<NestedStruct>()), + py::str(py::dtype::of<PartialStruct>()), + py::str(py::dtype::of<PartialNestedStruct>()), + py::str(py::dtype::of<StringStruct>()), + py::str(py::dtype::of<EnumStruct>()), + py::str(py::dtype::of<StructWithUglyNames>()) + }; + auto l = py::list(); + for (const auto &s : dtypes) { + l.append(s); + } + 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<size_t> shape { 3, 2 }; + std::vector<size_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 *) 0, sizeof(unsigned int), "I", 1))); + list.append(py::dtype(py::buffer_info((void *) 0, 0, "T{i:a:f:b:}", 1))); + return list; +} + +struct TrailingPaddingStruct { + int32_t a; + char b; +}; + +py::dtype trailing_padding_dtype() { + return py::dtype::of<TrailingPaddingStruct>(); +} + +py::dtype buffer_to_dtype(py::buffer& buf) { + return py::dtype(buf.request()); +} + +py::list 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; +} + +test_initializer numpy_dtypes([](py::module &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"); + + PYBIND11_NUMPY_DTYPE(SimpleStruct, x, y, z); + PYBIND11_NUMPY_DTYPE(PackedStruct, x, y, z); + PYBIND11_NUMPY_DTYPE(NestedStruct, a, b); + PYBIND11_NUMPY_DTYPE(PartialStruct, x, y, z); + PYBIND11_NUMPY_DTYPE(PartialNestedStruct, a); + PYBIND11_NUMPY_DTYPE(StringStruct, a, b); + PYBIND11_NUMPY_DTYPE(EnumStruct, e1, e2); + PYBIND11_NUMPY_DTYPE(TrailingPaddingStruct, a, b); + + // ... or after + py::class_<PackedStruct>(m, "PackedStruct"); + + PYBIND11_NUMPY_DTYPE_EX(StructWithUglyNames, __x__, "x", __y__, "y"); + + m.def("create_rec_simple", &create_recarray<SimpleStruct>); + m.def("create_rec_packed", &create_recarray<PackedStruct>); + m.def("create_rec_nested", &create_nested); + m.def("create_rec_partial", &create_recarray<PartialStruct>); + m.def("create_rec_partial_nested", &create_partial_nested); + m.def("print_format_descriptors", &print_format_descriptors); + m.def("print_rec_simple", &print_recarray<SimpleStruct>); + m.def("print_rec_packed", &print_recarray<PackedStruct>); + m.def("print_rec_nested", &print_recarray<NestedStruct>); + m.def("print_dtypes", &print_dtypes); + m.def("get_format_unbound", &get_format_unbound); + m.def("create_string_array", &create_string_array); + m.def("print_string_array", &print_recarray<StringStruct>); + m.def("create_enum_array", &create_enum_array); + m.def("print_enum_array", &print_recarray<EnumStruct>); + m.def("test_array_ctors", &test_array_ctors); + m.def("test_dtype_ctors", &test_dtype_ctors); + m.def("test_dtype_methods", &test_dtype_methods); + m.def("trailing_padding_dtype", &trailing_padding_dtype); + m.def("buffer_to_dtype", &buffer_to_dtype); + m.def("f_simple", [](SimpleStruct s) { return s.y * 10; }); + m.def("f_packed", [](PackedStruct s) { return s.y * 10; }); + m.def("f_nested", [](NestedStruct s) { return s.a.y * 10; }); + m.def("register_dtype", []() { PYBIND11_NUMPY_DTYPE(SimpleStruct, x, y, z); }); +}); + +#undef PYBIND11_PACKED diff --git a/ext/pybind11/tests/test_numpy_dtypes.py b/ext/pybind11/tests/test_numpy_dtypes.py new file mode 100644 index 000000000..52ebe0ede --- /dev/null +++ b/ext/pybind11/tests/test_numpy_dtypes.py @@ -0,0 +1,225 @@ +import re +import pytest + +with pytest.suppress(ImportError): + import numpy as np + + +@pytest.fixture(scope='module') +def simple_dtype(): + return np.dtype({'names': ['x', 'y', 'z'], + 'formats': ['?', 'u4', 'f4'], + 'offsets': [0, 4, 8]}) + + +@pytest.fixture(scope='module') +def packed_dtype(): + return np.dtype([('x', '?'), ('y', 'u4'), ('z', 'f4')]) + + +def assert_equal(actual, expected_data, expected_dtype): + np.testing.assert_equal(actual, np.array(expected_data, dtype=expected_dtype)) + + +@pytest.requires_numpy +def test_format_descriptors(): + from pybind11_tests import get_format_unbound, print_format_descriptors + + with pytest.raises(RuntimeError) as excinfo: + get_format_unbound() + assert re.match('^NumPy type info missing for .*UnboundStruct.*$', str(excinfo.value)) + + assert print_format_descriptors() == [ + "T{?:x:3xI:y:f:z:}", + "T{?:x:=I:y:=f:z:}", + "T{T{?:x:3xI:y:f:z:}:a:T{?:x:=I:y:=f:z:}:b:}", + "T{?:x:3xI:y:f:z:12x}", + "T{8xT{?:x:3xI:y:f:z:12x}:a:8x}", + "T{3s:a:3s:b:}", + 'T{q:e1:B:e2:}' + ] + + +@pytest.requires_numpy +def test_dtype(simple_dtype): + from pybind11_tests import (print_dtypes, test_dtype_ctors, test_dtype_methods, + trailing_padding_dtype, buffer_to_dtype) + + assert print_dtypes() == [ + "{'names':['x','y','z'], 'formats':['?','<u4','<f4'], 'offsets':[0,4,8], 'itemsize':12}", + "[('x', '?'), ('y', '<u4'), ('z', '<f4')]", + "[('a', {'names':['x','y','z'], 'formats':['?','<u4','<f4'], 'offsets':[0,4,8]," + " 'itemsize':12}), ('b', [('x', '?'), ('y', '<u4'), ('z', '<f4')])]", + "{'names':['x','y','z'], 'formats':['?','<u4','<f4'], 'offsets':[0,4,8], 'itemsize':24}", + "{'names':['a'], 'formats':[{'names':['x','y','z'], 'formats':['?','<u4','<f4']," + " 'offsets':[0,4,8], 'itemsize':24}], 'offsets':[8], 'itemsize':40}", + "[('a', 'S3'), ('b', 'S3')]", + "[('e1', '<i8'), ('e2', 'u1')]", + "[('x', 'i1'), ('y', '<u8')]" + ] + + d1 = np.dtype({'names': ['a', 'b'], 'formats': ['int32', 'float64'], + 'offsets': [1, 10], 'itemsize': 20}) + d2 = np.dtype([('a', 'i4'), ('b', 'f4')]) + assert test_dtype_ctors() == [np.dtype('int32'), np.dtype('float64'), + np.dtype('bool'), d1, d1, np.dtype('uint32'), d2] + + assert test_dtype_methods() == [np.dtype('int32'), simple_dtype, False, True, + np.dtype('int32').itemsize, simple_dtype.itemsize] + + assert trailing_padding_dtype() == buffer_to_dtype(np.zeros(1, trailing_padding_dtype())) + + +@pytest.requires_numpy +def test_recarray(simple_dtype, packed_dtype): + from pybind11_tests import (create_rec_simple, create_rec_packed, create_rec_nested, + print_rec_simple, print_rec_packed, print_rec_nested, + create_rec_partial, create_rec_partial_nested) + + elements = [(False, 0, 0.0), (True, 1, 1.5), (False, 2, 3.0)] + + for func, dtype in [(create_rec_simple, simple_dtype), (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) + + if dtype == simple_dtype: + assert print_rec_simple(arr) == [ + "s:0,0,0", + "s:1,1,1.5", + "s:0,2,3" + ] + else: + assert print_rec_packed(arr) == [ + "p:0,0,0", + "p:1,1,1.5", + "p:0,2,3" + ] + + nested_dtype = np.dtype([('a', simple_dtype), ('b', packed_dtype)]) + + arr = create_rec_nested(0) + assert arr.dtype == nested_dtype + assert_equal(arr, [], nested_dtype) + + arr = create_rec_nested(3) + assert arr.dtype == nested_dtype + assert_equal(arr, [((False, 0, 0.0), (True, 1, 1.5)), + ((True, 1, 1.5), (False, 2, 3.0)), + ((False, 2, 3.0), (True, 3, 4.5))], nested_dtype) + assert print_rec_nested(arr) == [ + "n:a=s:0,0,0;b=p:1,1,1.5", + "n:a=s:1,1,1.5;b=p:0,2,3", + "n:a=s:0,2,3;b=p:1,3,4.5" + ] + + arr = create_rec_partial(3) + assert str(arr.dtype) == \ + "{'names':['x','y','z'], 'formats':['?','<u4','<f4'], 'offsets':[0,4,8], 'itemsize':24}" + 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 = create_rec_partial_nested(3) + assert str(arr.dtype) == \ + "{'names':['a'], 'formats':[{'names':['x','y','z'], 'formats':['?','<u4','<f4']," \ + " 'offsets':[0,4,8], 'itemsize':24}], 'offsets':[8], 'itemsize':40}" + 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'], create_rec_partial(3)) + + +@pytest.requires_numpy +def test_array_constructors(): + from pybind11_tests import test_array_ctors + + data = np.arange(1, 7, dtype='int32') + for i in range(8): + np.testing.assert_array_equal(test_array_ctors(10 + i), data.reshape((3, 2))) + np.testing.assert_array_equal(test_array_ctors(20 + i), data.reshape((3, 2))) + for i in range(5): + np.testing.assert_array_equal(test_array_ctors(30 + i), data) + np.testing.assert_array_equal(test_array_ctors(40 + i), data) + + +@pytest.requires_numpy +def test_string_array(): + from pybind11_tests import create_string_array, print_string_array + + arr = create_string_array(True) + assert str(arr.dtype) == "[('a', 'S3'), ('b', 'S3')]" + assert 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 = create_string_array(False) + assert dtype == arr.dtype + + +@pytest.requires_numpy +def test_enum_array(): + from pybind11_tests import create_enum_array, print_enum_array + + arr = create_enum_array(3) + dtype = arr.dtype + assert dtype == np.dtype([('e1', '<i8'), ('e2', 'u1')]) + assert 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 create_enum_array(0).dtype == dtype + + +@pytest.requires_numpy +def test_signature(doc): + from pybind11_tests import create_rec_nested + + assert doc(create_rec_nested) == "create_rec_nested(arg0: int) -> numpy.ndarray[NestedStruct]" + + +@pytest.requires_numpy +def test_scalar_conversion(): + from pybind11_tests import (create_rec_simple, f_simple, + create_rec_packed, f_packed, + create_rec_nested, f_nested, + create_enum_array) + + n = 3 + arrays = [create_rec_simple(n), create_rec_packed(n), + create_rec_nested(n), create_enum_array(n)] + funcs = [f_simple, f_packed, 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) + + +@pytest.requires_numpy +def test_register_dtype(): + from pybind11_tests import register_dtype + + with pytest.raises(RuntimeError) as excinfo: + register_dtype() + assert 'dtype is already registered' in str(excinfo.value) diff --git a/ext/pybind11/tests/test_numpy_vectorize.cpp b/ext/pybind11/tests/test_numpy_vectorize.cpp new file mode 100644 index 000000000..6d94db2a1 --- /dev/null +++ b/ext/pybind11/tests/test_numpy_vectorize.cpp @@ -0,0 +1,41 @@ +/* + 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_tests.h" +#include <pybind11/numpy.h> + +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; +} + +std::complex<double> my_func3(std::complex<double> c) { + return c * std::complex<double>(2.f); +} + +test_initializer numpy_vectorize([](py::module &m) { + // 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); })(x, y); + } + ); + + // Vectorize a complex-valued function + m.def("vectorized_func3", py::vectorize(my_func3)); + + /// Numpy function which only accepts specific data types + m.def("selective_func", [](py::array_t<int, py::array::c_style>) { return "Int branch taken."; }); + m.def("selective_func", [](py::array_t<float, py::array::c_style>) { return "Float branch taken."; }); + m.def("selective_func", [](py::array_t<std::complex<float>, py::array::c_style>) { return "Complex float branch taken."; }); +}); diff --git a/ext/pybind11/tests/test_numpy_vectorize.py b/ext/pybind11/tests/test_numpy_vectorize.py new file mode 100644 index 000000000..718646efa --- /dev/null +++ b/ext/pybind11/tests/test_numpy_vectorize.py @@ -0,0 +1,76 @@ +import pytest + +with pytest.suppress(ImportError): + import numpy as np + + +@pytest.requires_numpy +def test_vectorize(capture): + from pybind11_tests import vectorized_func, vectorized_func2, vectorized_func3 + + assert np.isclose(vectorized_func3(np.array(3 + 7j)), [6 + 14j]) + + for f in [vectorized_func, 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, 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) + """ + + +@pytest.requires_numpy +def test_type_selection(): + from pybind11_tests import selective_func + + assert selective_func(np.array([1], dtype=np.int32)) == "Int branch taken." + assert selective_func(np.array([1.0], dtype=np.float32)) == "Float branch taken." + assert selective_func(np.array([1.0j], dtype=np.complex64)) == "Complex float branch taken." + + +@pytest.requires_numpy +def test_docs(doc): + from pybind11_tests import vectorized_func + + assert doc(vectorized_func) == """ + vectorized_func(arg0: numpy.ndarray[int], arg1: numpy.ndarray[float], arg2: numpy.ndarray[float]) -> object + """ # noqa: E501 line too long diff --git a/ext/pybind11/tests/test_opaque_types.cpp b/ext/pybind11/tests/test_opaque_types.cpp new file mode 100644 index 000000000..54f4dc7a5 --- /dev/null +++ b/ext/pybind11/tests/test_opaque_types.cpp @@ -0,0 +1,62 @@ +/* + 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_tests.h" +#include <pybind11/stl.h> +#include <vector> + +typedef std::vector<std::string> StringList; + +class ClassWithSTLVecProperty { +public: + StringList stringList; +}; + +/* IMPORTANT: Disable internal pybind11 translation mechanisms for STL data structures */ +PYBIND11_MAKE_OPAQUE(StringList); + +test_initializer opaque_types([](py::module &m) { + 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>()); + + 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 (auto entry : l) { + if (!first) + ret += ", "; + ret += entry; + first = false; + } + return ret + "]"; + }); + + 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> { + StringList *result = new StringList(); + result->push_back("some value"); + return std::unique_ptr<StringList>(result); + }); +}); diff --git a/ext/pybind11/tests/test_opaque_types.py b/ext/pybind11/tests/test_opaque_types.py new file mode 100644 index 000000000..7781943b4 --- /dev/null +++ b/ext/pybind11/tests/test_opaque_types.py @@ -0,0 +1,49 @@ +import pytest + + +def test_string_list(): + from pybind11_tests import StringList, ClassWithSTLVecProperty, print_opaque_list + + l = StringList() + l.push_back("Element 1") + l.push_back("Element 2") + assert print_opaque_list(l) == "Opaque list: [Element 1, Element 2]" + assert l.back() == "Element 2" + + for i, k in enumerate(l, start=1): + assert k == "Element {}".format(i) + l.pop_back() + assert print_opaque_list(l) == "Opaque list: [Element 1]" + + cvp = ClassWithSTLVecProperty() + assert print_opaque_list(cvp.stringList) == "Opaque list: []" + + cvp.stringList = l + cvp.stringList.push_back("Element 3") + assert print_opaque_list(cvp.stringList) == "Opaque list: [Element 1, Element 3]" + + +def test_pointers(msg): + from pybind11_tests import (return_void_ptr, get_void_ptr_value, ExampleMandA, + print_opaque_list, return_null_str, get_null_str_value, + return_unique_ptr, ConstructorStats) + + assert get_void_ptr_value(return_void_ptr()) == 0x1234 + assert get_void_ptr_value(ExampleMandA()) # Should also work for other C++ types + assert ConstructorStats.get(ExampleMandA).alive() == 0 + + with pytest.raises(TypeError) as excinfo: + 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] + """ # noqa: E501 line too long + + assert return_null_str() is None + assert get_null_str_value(return_null_str()) is not None + + ptr = return_unique_ptr() + assert "StringList" in repr(ptr) + assert print_opaque_list(ptr) == "Opaque list: [some value]" diff --git a/ext/pybind11/tests/test_operator_overloading.cpp b/ext/pybind11/tests/test_operator_overloading.cpp new file mode 100644 index 000000000..93aea8010 --- /dev/null +++ b/ext/pybind11/tests/test_operator_overloading.cpp @@ -0,0 +1,76 @@ +/* + 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_tests.h" +#include "constructor_stats.h" +#include <pybind11/operators.h> + +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) : x(v.x), y(v.y) { print_move_created(this); v.x = v.y = 0; } + ~Vector2() { print_destroyed(this); } + + std::string toString() const { + return "[" + std::to_string(x) + ", " + std::to_string(y) + "]"; + } + + void operator=(const Vector2 &v) { + print_copy_assigned(this); + x = v.x; + y = v.y; + } + + void operator=(Vector2 &&v) { + print_move_assigned(this); + x = v.x; y = v.y; v.x = v.y = 0; + } + + 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) { 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; } + + 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); } +private: + float x, y; +}; + +test_initializer operator_overloading([](py::module &m) { + 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 *= float()) + .def(py::self /= float()) + .def(float() + py::self) + .def(float() - py::self) + .def(float() * py::self) + .def(float() / py::self) + .def("__str__", &Vector2::toString) + ; + + m.attr("Vector") = m.attr("Vector2"); +}); diff --git a/ext/pybind11/tests/test_operator_overloading.py b/ext/pybind11/tests/test_operator_overloading.py new file mode 100644 index 000000000..e0d42391e --- /dev/null +++ b/ext/pybind11/tests/test_operator_overloading.py @@ -0,0 +1,41 @@ + +def test_operator_overloading(): + from pybind11_tests import Vector2, Vector, ConstructorStats + + v1 = Vector2(1, 2) + v2 = Vector(3, -1) + assert str(v1) == "[1.000000, 2.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]" + + v1 += v2 + v1 *= 2 + assert str(v1) == "[8.000000, 2.000000]" + + cstats = ConstructorStats.get(Vector2) + assert cstats.alive() == 2 + del v1 + assert cstats.alive() == 1 + del v2 + assert cstats.alive() == 0 + assert cstats.values() == ['[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]'] + 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 diff --git a/ext/pybind11/tests/test_pickling.cpp b/ext/pybind11/tests/test_pickling.cpp new file mode 100644 index 000000000..3941dc593 --- /dev/null +++ b/ext/pybind11/tests/test_pickling.cpp @@ -0,0 +1,81 @@ +/* + tests/test_pickling.cpp -- pickle support + + 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" + +class Pickleable { +public: + 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 PickleableWithDict { +public: + PickleableWithDict(const std::string &value) : value(value) { } + + std::string value; + int extra; +}; + +test_initializer pickling([](py::module &m) { + py::class_<Pickleable>(m, "Pickleable") + .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()); + }) + .def("__setstate__", [](Pickleable &p, 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_<PickleableWithDict>(m, "PickleableWithDict", py::dynamic_attr()) + .def(py::init<std::string>()) + .def_readwrite("value", &PickleableWithDict::value) + .def_readwrite("extra", &PickleableWithDict::extra) + .def("__getstate__", [](py::object self) { + /* Also include __dict__ in state */ + return py::make_tuple(self.attr("value"), self.attr("extra"), self.attr("__dict__")); + }) + .def("__setstate__", [](py::object self, py::tuple t) { + if (t.size() != 3) + throw std::runtime_error("Invalid state!"); + /* Cast and construct */ + auto& p = self.cast<PickleableWithDict&>(); + new (&p) Pickleable(t[0].cast<std::string>()); + + /* Assign C++ state */ + p.extra = t[1].cast<int>(); + + /* Assign Python state */ + self.attr("__dict__") = t[2]; + }); +}); diff --git a/ext/pybind11/tests/test_pickling.py b/ext/pybind11/tests/test_pickling.py new file mode 100644 index 000000000..5e62e1fcc --- /dev/null +++ b/ext/pybind11/tests/test_pickling.py @@ -0,0 +1,32 @@ +try: + import cPickle as pickle # Use cPickle on Python 2.7 +except ImportError: + import pickle + + +def test_roundtrip(): + from pybind11_tests import Pickleable + + p = Pickleable("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() + + +def test_roundtrip_with_dict(): + from pybind11_tests import PickleableWithDict + + p = PickleableWithDict("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 diff --git a/ext/pybind11/tests/test_python_types.cpp b/ext/pybind11/tests/test_python_types.cpp new file mode 100644 index 000000000..33c655b52 --- /dev/null +++ b/ext/pybind11/tests/test_python_types.cpp @@ -0,0 +1,430 @@ +/* + tests/test_python_types.cpp -- singleton design pattern, static functions and + variables, passing and interacting with Python 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_tests.h" +#include "constructor_stats.h" +#include <pybind11/stl.h> + +#ifdef _WIN32 +# include <io.h> +# include <fcntl.h> +#endif + +class ExamplePythonTypes { +public: + static ExamplePythonTypes *new_instance() { + auto *ptr = new ExamplePythonTypes(); + print_created(ptr, "via new_instance"); + return ptr; + } + ~ExamplePythonTypes() { print_destroyed(this); } + + /* Create and return a Python dictionary */ + py::dict get_dict() { + py::dict dict; + dict[py::str("key")] = py::str("value"); + return dict; + } + + /* Create and return a Python set */ + py::set get_set() { + py::set set; + set.add(py::str("key1")); + set.add("key2"); + set.add(std::string("key3")); + return set; + } + + /* Create and return a C++ dictionary */ + std::map<std::string, std::string> get_dict_2() { + std::map<std::string, std::string> result; + result["key"] = "value"; + return result; + } + + /* Create and return a C++ set */ + std::set<std::string> get_set_2() { + std::set<std::string> result; + result.insert("key1"); + result.insert("key2"); + return result; + } + + /* Create, manipulate, and return a Python list */ + py::list get_list() { + py::list list; + list.append("value"); + py::print("Entry at position 0:", list[0]); + list[0] = py::str("overwritten"); + return list; + } + + /* C++ STL data types are automatically casted */ + std::vector<std::wstring> get_list_2() { + std::vector<std::wstring> list; + list.push_back(L"value"); + return list; + } + + /* C++ STL data types are automatically casted */ + std::array<std::string, 2> get_array() { + return std::array<std::string, 2> {{ "array entry 1" , "array entry 2"}}; + } + + std::valarray<int> get_valarray() { + return std::valarray<int>({ 1, 4, 9 }); + } + + /* Easily iterate over a dictionary using a C++11 range-based for loop */ + void print_dict(py::dict dict) { + for (auto item : dict) + py::print("key: {}, value={}"_s.format(item.first, item.second)); + } + + /* Easily iterate over a set using a C++11 range-based for loop */ + void print_set(py::set set) { + for (auto item : set) + py::print("key:", item); + } + + /* Easily iterate over a list using a C++11 range-based for loop */ + void print_list(py::list list) { + int index = 0; + for (auto item : list) + py::print("list item {}: {}"_s.format(index++, item)); + } + + /* STL data types (such as maps) are automatically casted from Python */ + void print_dict_2(const std::map<std::string, std::string> &dict) { + for (auto item : dict) + py::print("key: {}, value={}"_s.format(item.first, item.second)); + } + + /* STL data types (such as sets) are automatically casted from Python */ + void print_set_2(const std::set<std::string> &set) { + for (auto item : set) + py::print("key:", item); + } + + /* STL data types (such as vectors) are automatically casted from Python */ + void print_list_2(std::vector<std::wstring> &list) { + int index = 0; + for (auto item : list) + py::print("list item {}: {}"_s.format(index++, item)); + } + + /* pybind automatically translates between C++11 and Python tuples */ + std::pair<std::string, bool> pair_passthrough(std::pair<bool, std::string> input) { + return std::make_pair(input.second, input.first); + } + + /* pybind automatically translates between C++11 and Python tuples */ + std::tuple<int, std::string, bool> 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)); + } + + /* STL data types (such as arrays) are automatically casted from Python */ + void print_array(std::array<std::string, 2> &array) { + int index = 0; + for (auto item : array) + py::print("array item {}: {}"_s.format(index++, item)); + } + + void print_valarray(std::valarray<int> &varray) { + int index = 0; + for (auto item : varray) + py::print("valarray item {}: {}"_s.format(index++, item)); + } + + void throw_exception() { + throw std::runtime_error("This exception was intentionally thrown."); + } + + py::bytes get_bytes_from_string() { + return (py::bytes) std::string("foo"); + } + + py::bytes get_bytes_from_str() { + return (py::bytes) py::str("bar", 3); + } + + py::str get_str_from_string() { + return (py::str) std::string("baz"); + } + + py::str get_str_from_bytes() { + return (py::str) py::bytes("boo", 3); + } + + void test_print(const py::object& obj) { + py::print(py::str(obj)); + py::print(py::repr(obj)); + } + + static int value; + static const int value2; +}; + +int ExamplePythonTypes::value = 0; +const int ExamplePythonTypes::value2 = 5; + +struct MoveOutContainer { + struct Value { int value; }; + + std::list<Value> move_list() const { return {{0}, {1}, {2}}; } +}; + + +test_initializer python_types([](py::module &m) { + /* No constructor is explicitly defined below. An exception is raised when + trying to construct it directly from Python */ + py::class_<ExamplePythonTypes>(m, "ExamplePythonTypes", "Example 2 documentation") + .def("get_dict", &ExamplePythonTypes::get_dict, "Return a Python dictionary") + .def("get_dict_2", &ExamplePythonTypes::get_dict_2, "Return a C++ dictionary") + .def("get_list", &ExamplePythonTypes::get_list, "Return a Python list") + .def("get_list_2", &ExamplePythonTypes::get_list_2, "Return a C++ list") + .def("get_set", &ExamplePythonTypes::get_set, "Return a Python set") + .def("get_set2", &ExamplePythonTypes::get_set_2, "Return a C++ set") + .def("get_array", &ExamplePythonTypes::get_array, "Return a C++ array") + .def("get_valarray", &ExamplePythonTypes::get_valarray, "Return a C++ valarray") + .def("print_dict", &ExamplePythonTypes::print_dict, "Print entries of a Python dictionary") + .def("print_dict_2", &ExamplePythonTypes::print_dict_2, "Print entries of a C++ dictionary") + .def("print_set", &ExamplePythonTypes::print_set, "Print entries of a Python set") + .def("print_set_2", &ExamplePythonTypes::print_set_2, "Print entries of a C++ set") + .def("print_list", &ExamplePythonTypes::print_list, "Print entries of a Python list") + .def("print_list_2", &ExamplePythonTypes::print_list_2, "Print entries of a C++ list") + .def("print_array", &ExamplePythonTypes::print_array, "Print entries of a C++ array") + .def("print_valarray", &ExamplePythonTypes::print_valarray, "Print entries of a C++ valarray") + .def("pair_passthrough", &ExamplePythonTypes::pair_passthrough, "Return a pair in reversed order") + .def("tuple_passthrough", &ExamplePythonTypes::tuple_passthrough, "Return a triple in reversed order") + .def("throw_exception", &ExamplePythonTypes::throw_exception, "Throw an exception") + .def("get_bytes_from_string", &ExamplePythonTypes::get_bytes_from_string, "py::bytes from std::string") + .def("get_bytes_from_str", &ExamplePythonTypes::get_bytes_from_str, "py::bytes from py::str") + .def("get_str_from_string", &ExamplePythonTypes::get_str_from_string, "py::str from std::string") + .def("get_str_from_bytes", &ExamplePythonTypes::get_str_from_bytes, "py::str from py::bytes") + .def("test_print", &ExamplePythonTypes::test_print, "test the print function") + .def_static("new_instance", &ExamplePythonTypes::new_instance, "Return an instance") + .def_readwrite_static("value", &ExamplePythonTypes::value, "Static value member") + .def_readonly_static("value2", &ExamplePythonTypes::value2, "Static value member (readonly)") + ; + + m.def("test_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("test_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); + }); + + m.def("test_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("test_accessor_api", [](py::object o) { + auto d = py::dict(); + + d["basic_attr"] = o.attr("basic_attr"); + + auto l = py::list(); + for (const 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")); + + return d; + }); + + m.def("test_tuple_accessor", [](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("test_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; + }); + + bool has_optional = false, has_exp_optional = false; +#ifdef PYBIND11_HAS_OPTIONAL + has_optional = true; + using opt_int = std::optional<int>; + m.def("double_or_zero", [](const opt_int& x) -> int { + return x.value_or(0) * 2; + }); + m.def("half_or_none", [](int x) -> opt_int { + return x ? opt_int(x / 2) : opt_int(); + }); + m.def("test_nullopt", [](opt_int x) { + return x.value_or(42); + }, py::arg_v("x", std::nullopt, "None")); +#endif + +#ifdef PYBIND11_HAS_EXP_OPTIONAL + has_exp_optional = true; + using opt_int = std::experimental::optional<int>; + m.def("double_or_zero_exp", [](const opt_int& x) -> int { + return x.value_or(0) * 2; + }); + m.def("half_or_none_exp", [](int x) -> opt_int { + return x ? opt_int(x / 2) : opt_int(); + }); + m.def("test_nullopt_exp", [](opt_int x) { + return x.value_or(42); + }, py::arg_v("x", std::experimental::nullopt, "None")); +#endif + + m.attr("has_optional") = has_optional; + m.attr("has_exp_optional") = has_exp_optional; + + m.def("test_default_constructors", []() { + return py::dict( + "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("test_converting_constructors", [](py::dict d) { + return py::dict( + "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"]), + "memoryview"_a=py::memoryview(d["memoryview"]) + ); + }); + + m.def("test_cast_functions", [](py::dict d) { + // When converting between Python types, obj.cast<T>() should be the same as T(obj) + return py::dict( + "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>(), + "memoryview"_a=d["memoryview"].cast<py::memoryview>() + ); + }); + + py::class_<MoveOutContainer::Value>(m, "MoveOutContainerValue") + .def_readonly("value", &MoveOutContainer::Value::value); + + py::class_<MoveOutContainer>(m, "MoveOutContainer") + .def(py::init<>()) + .def_property_readonly("move_list", &MoveOutContainer::move_list); + + 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 + ); + }); +}); diff --git a/ext/pybind11/tests/test_python_types.py b/ext/pybind11/tests/test_python_types.py new file mode 100644 index 000000000..9fe1ef71e --- /dev/null +++ b/ext/pybind11/tests/test_python_types.py @@ -0,0 +1,402 @@ +import pytest + +from pybind11_tests import ExamplePythonTypes, ConstructorStats, has_optional, has_exp_optional + + +def test_static(): + ExamplePythonTypes.value = 15 + assert ExamplePythonTypes.value == 15 + assert ExamplePythonTypes.value2 == 5 + + with pytest.raises(AttributeError) as excinfo: + ExamplePythonTypes.value2 = 15 + assert str(excinfo.value) == "can't set attribute" + + +def test_instance(capture): + with pytest.raises(TypeError) as excinfo: + ExamplePythonTypes() + assert str(excinfo.value) == "pybind11_tests.ExamplePythonTypes: No constructor defined!" + + instance = ExamplePythonTypes.new_instance() + + with capture: + dict_result = instance.get_dict() + dict_result['key2'] = 'value2' + instance.print_dict(dict_result) + assert capture.unordered == """ + key: key, value=value + key: key2, value=value2 + """ + with capture: + dict_result = instance.get_dict_2() + dict_result['key2'] = 'value2' + instance.print_dict_2(dict_result) + assert capture.unordered == """ + key: key, value=value + key: key2, value=value2 + """ + with capture: + set_result = instance.get_set() + set_result.add('key4') + instance.print_set(set_result) + assert capture.unordered == """ + key: key1 + key: key2 + key: key3 + key: key4 + """ + with capture: + set_result = instance.get_set2() + set_result.add('key3') + instance.print_set_2(set_result) + assert capture.unordered == """ + key: key1 + key: key2 + key: key3 + """ + with capture: + list_result = instance.get_list() + list_result.append('value2') + instance.print_list(list_result) + assert capture.unordered == """ + Entry at position 0: value + list item 0: overwritten + list item 1: value2 + """ + with capture: + list_result = instance.get_list_2() + list_result.append('value2') + instance.print_list_2(list_result) + assert capture.unordered == """ + list item 0: value + list item 1: value2 + """ + with capture: + list_result = instance.get_list_2() + list_result.append('value2') + instance.print_list_2(tuple(list_result)) + assert capture.unordered == """ + list item 0: value + list item 1: value2 + """ + array_result = instance.get_array() + assert array_result == ['array entry 1', 'array entry 2'] + with capture: + instance.print_array(array_result) + assert capture.unordered == """ + array item 0: array entry 1 + array item 1: array entry 2 + """ + varray_result = instance.get_valarray() + assert varray_result == [1, 4, 9] + with capture: + instance.print_valarray(varray_result) + assert capture.unordered == """ + valarray item 0: 1 + valarray item 1: 4 + valarray item 2: 9 + """ + with pytest.raises(RuntimeError) as excinfo: + instance.throw_exception() + assert str(excinfo.value) == "This exception was intentionally thrown." + + assert instance.pair_passthrough((True, "test")) == ("test", True) + assert instance.tuple_passthrough((True, "test", 5)) == (5, "test", True) + # Any sequence can be cast to a std::pair or std::tuple + assert instance.pair_passthrough([True, "test"]) == ("test", True) + assert instance.tuple_passthrough([True, "test", 5]) == (5, "test", True) + + assert instance.get_bytes_from_string().decode() == "foo" + assert instance.get_bytes_from_str().decode() == "bar" + assert instance.get_str_from_string().encode().decode() == "baz" + assert instance.get_str_from_bytes().encode().decode() == "boo" + + class A(object): + def __str__(self): + return "this is a str" + + def __repr__(self): + return "this is a repr" + + with capture: + instance.test_print(A()) + assert capture == """ + this is a str + this is a repr + """ + + cstats = ConstructorStats.get(ExamplePythonTypes) + assert cstats.alive() == 1 + del instance + assert cstats.alive() == 0 + + +def test_docs(doc): + assert doc(ExamplePythonTypes) == "Example 2 documentation" + assert doc(ExamplePythonTypes.get_dict) == """ + get_dict(self: m.ExamplePythonTypes) -> dict + + Return a Python dictionary + """ + assert doc(ExamplePythonTypes.get_dict_2) == """ + get_dict_2(self: m.ExamplePythonTypes) -> Dict[str, str] + + Return a C++ dictionary + """ + assert doc(ExamplePythonTypes.get_list) == """ + get_list(self: m.ExamplePythonTypes) -> list + + Return a Python list + """ + assert doc(ExamplePythonTypes.get_list_2) == """ + get_list_2(self: m.ExamplePythonTypes) -> List[str] + + Return a C++ list + """ + assert doc(ExamplePythonTypes.get_dict) == """ + get_dict(self: m.ExamplePythonTypes) -> dict + + Return a Python dictionary + """ + assert doc(ExamplePythonTypes.get_set) == """ + get_set(self: m.ExamplePythonTypes) -> set + + Return a Python set + """ + assert doc(ExamplePythonTypes.get_set2) == """ + get_set2(self: m.ExamplePythonTypes) -> Set[str] + + Return a C++ set + """ + assert doc(ExamplePythonTypes.get_array) == """ + get_array(self: m.ExamplePythonTypes) -> List[str[2]] + + Return a C++ array + """ + assert doc(ExamplePythonTypes.get_valarray) == """ + get_valarray(self: m.ExamplePythonTypes) -> List[int] + + Return a C++ valarray + """ + assert doc(ExamplePythonTypes.print_dict) == """ + print_dict(self: m.ExamplePythonTypes, arg0: dict) -> None + + Print entries of a Python dictionary + """ + assert doc(ExamplePythonTypes.print_dict_2) == """ + print_dict_2(self: m.ExamplePythonTypes, arg0: Dict[str, str]) -> None + + Print entries of a C++ dictionary + """ + assert doc(ExamplePythonTypes.print_set) == """ + print_set(self: m.ExamplePythonTypes, arg0: set) -> None + + Print entries of a Python set + """ + assert doc(ExamplePythonTypes.print_set_2) == """ + print_set_2(self: m.ExamplePythonTypes, arg0: Set[str]) -> None + + Print entries of a C++ set + """ + assert doc(ExamplePythonTypes.print_list) == """ + print_list(self: m.ExamplePythonTypes, arg0: list) -> None + + Print entries of a Python list + """ + assert doc(ExamplePythonTypes.print_list_2) == """ + print_list_2(self: m.ExamplePythonTypes, arg0: List[str]) -> None + + Print entries of a C++ list + """ + assert doc(ExamplePythonTypes.print_array) == """ + print_array(self: m.ExamplePythonTypes, arg0: List[str[2]]) -> None + + Print entries of a C++ array + """ + assert doc(ExamplePythonTypes.pair_passthrough) == """ + pair_passthrough(self: m.ExamplePythonTypes, arg0: Tuple[bool, str]) -> Tuple[str, bool] + + Return a pair in reversed order + """ + assert doc(ExamplePythonTypes.tuple_passthrough) == """ + tuple_passthrough(self: m.ExamplePythonTypes, arg0: Tuple[bool, str, int]) -> Tuple[int, str, bool] + + Return a triple in reversed order + """ # noqa: E501 line too long + assert doc(ExamplePythonTypes.throw_exception) == """ + throw_exception(self: m.ExamplePythonTypes) -> None + + Throw an exception + """ + assert doc(ExamplePythonTypes.new_instance) == """ + new_instance() -> m.ExamplePythonTypes + + Return an instance + """ + + +def test_module(): + import pybind11_tests + + assert pybind11_tests.__name__ == "pybind11_tests" + assert ExamplePythonTypes.__name__ == "ExamplePythonTypes" + assert ExamplePythonTypes.__module__ == "pybind11_tests" + assert ExamplePythonTypes.get_set.__name__ == "get_set" + assert ExamplePythonTypes.get_set.__module__ == "pybind11_tests" + + +def test_print(capture): + from pybind11_tests import test_print_function + + with capture: + test_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" + + +def test_str_api(): + from pybind11_tests import test_str_format + + s1, s2 = test_str_format() + assert s1 == "1 + 2 = 3" + assert s1 == s2 + + +def test_dict_api(): + from pybind11_tests import test_dict_keyword_constructor + + assert test_dict_keyword_constructor() == {"x": 1, "y": 2, "z": 3} + + +def test_accessors(): + from pybind11_tests import test_accessor_api, test_tuple_accessor, test_accessor_assignment + + 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 = test_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 test_tuple_accessor(tuple()) == (0, 1, 2) + + d = test_accessor_assignment() + assert d["get"] == 0 + assert d["deferred_get"] == 0 + assert d["set"] == 1 + assert d["deferred_set"] == 1 + assert d["var"] == 99 + + +@pytest.mark.skipif(not has_optional, reason='no <optional>') +def test_optional(): + from pybind11_tests import double_or_zero, half_or_none, test_nullopt + + assert double_or_zero(None) == 0 + assert double_or_zero(42) == 84 + pytest.raises(TypeError, double_or_zero, 'foo') + + assert half_or_none(0) is None + assert half_or_none(42) == 21 + pytest.raises(TypeError, half_or_none, 'foo') + + assert test_nullopt() == 42 + assert test_nullopt(None) == 42 + assert test_nullopt(42) == 42 + assert test_nullopt(43) == 43 + + +@pytest.mark.skipif(not has_exp_optional, reason='no <experimental/optional>') +def test_exp_optional(): + from pybind11_tests import double_or_zero_exp, half_or_none_exp, test_nullopt_exp + + assert double_or_zero_exp(None) == 0 + assert double_or_zero_exp(42) == 84 + pytest.raises(TypeError, double_or_zero_exp, 'foo') + + assert half_or_none_exp(0) is None + assert half_or_none_exp(42) == 21 + pytest.raises(TypeError, half_or_none_exp, 'foo') + + assert test_nullopt_exp() == 42 + assert test_nullopt_exp(None) == 42 + assert test_nullopt_exp(42) == 42 + assert test_nullopt_exp(43) == 43 + + +def test_constructors(): + """C++ default and converting constructors are equivalent to type calls in Python""" + from pybind11_tests import (test_default_constructors, test_converting_constructors, + test_cast_functions) + + types = [str, bool, int, float, tuple, list, dict, set] + expected = {t.__name__: t() for t in types} + assert test_default_constructors() == expected + + data = { + 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], + 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 test_converting_constructors(inputs) == expected + assert test_cast_functions(inputs) == expected + + +def test_move_out_container(): + """Properties use the `reference_internal` policy by default. If the underlying function + returns an rvalue, the policy is automatically changed to `move` to avoid referencing + a temporary. In case the return value is a container of user-defined types, the policy + also needs to be applied to the elements, not just the container.""" + from pybind11_tests import MoveOutContainer + + c = MoveOutContainer() + moved_out_list = c.move_list + assert [x.value for x in moved_out_list] == [0, 1, 2] + + +def test_implicit_casting(): + """Tests implicit casting when assigning or appending to dicts and lists.""" + from pybind11_tests import get_implicit_casting + + z = 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] diff --git a/ext/pybind11/tests/test_sequences_and_iterators.cpp b/ext/pybind11/tests/test_sequences_and_iterators.cpp new file mode 100644 index 000000000..323b4bf00 --- /dev/null +++ b/ext/pybind11/tests/test_sequences_and_iterators.cpp @@ -0,0 +1,275 @@ +/* + 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_tests.h" +#include "constructor_stats.h" +#include <pybind11/operators.h> +#include <pybind11/stl.h> + +class Sequence { +public: + Sequence(size_t size) : m_size(size) { + print_created(this, "of size", m_size); + m_data = new float[size]; + memset(m_data, 0, sizeof(float) * size); + } + + Sequence(const std::vector<float> &value) : m_size(value.size()) { + print_created(this, "of size", m_size, "from std::vector"); + 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); + m_data = new float[m_size]; + memcpy(m_data, s.m_data, sizeof(float)*m_size); + } + + Sequence(Sequence &&s) : 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) { + 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; +}; + +class IntPairs { +public: + IntPairs(std::vector<std::pair<int, int>> data) : data_(std::move(data)) {} + const std::pair<int, int>* begin() const { return data_.data(); } + +private: + std::vector<std::pair<int, int>> data_; +}; + +// 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; + StringMap(std::unordered_map<std::string, std::string> init) + : map(std::move(init)) {} + + void set(std::string key, std::string val) { + map[key] = val; + } + + std::string get(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(); } +}; + +template<typename T> +class NonZeroIterator { + const T* ptr_; +public: + 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; +} + +test_initializer sequences_and_iterators([](py::module &m) { + + py::class_<Sequence> seq(m, "Sequence"); + + seq.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, py::slice slice) -> Sequence* { + size_t start, stop, step, slicelength; + if (!slice.compute(s.size(), &start, &stop, &step, &slicelength)) + throw py::error_already_set(); + Sequence *seq = new Sequence(slicelength); + for (size_t i=0; i<slicelength; ++i) { + (*seq)[i] = s[start]; start += step; + } + return seq; + }) + .def("__setitem__", [](Sequence &s, py::slice slice, const Sequence &value) { + size_t start, stop, step, slicelength; + 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. + + py::class_<StringMap> map(m, "StringMap"); + + map .def(py::init<>()) + .def(py::init<std::unordered_map<std::string, std::string>>()) + .def("__getitem__", [](const StringMap &map, 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>()) + ; + + 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>()); + + +#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 +}); diff --git a/ext/pybind11/tests/test_sequences_and_iterators.py b/ext/pybind11/tests/test_sequences_and_iterators.py new file mode 100644 index 000000000..76b9f43f6 --- /dev/null +++ b/ext/pybind11/tests/test_sequences_and_iterators.py @@ -0,0 +1,90 @@ +import pytest + + +def isclose(a, b, rel_tol=1e-05, abs_tol=0.0): + """Like math.isclose() from Python 3.5""" + return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) + + +def allclose(a_list, b_list, rel_tol=1e-05, abs_tol=0.0): + return all(isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol) for a, b in zip(a_list, b_list)) + + +def test_generalized_iterators(): + from pybind11_tests import IntPairs + + assert list(IntPairs([(1, 2), (3, 4), (0, 5)]).nonzero()) == [(1, 2), (3, 4)] + assert list(IntPairs([(1, 2), (2, 0), (0, 3), (4, 5)]).nonzero()) == [(1, 2)] + assert list(IntPairs([(0, 3), (1, 2), (3, 4)]).nonzero()) == [] + + assert list(IntPairs([(1, 2), (3, 4), (0, 5)]).nonzero_keys()) == [1, 3] + assert list(IntPairs([(1, 2), (2, 0), (0, 3), (4, 5)]).nonzero_keys()) == [1] + assert list(IntPairs([(0, 3), (1, 2), (3, 4)]).nonzero_keys()) == [] + + +def test_sequence(): + from pybind11_tests import Sequence, ConstructorStats + + cstats = ConstructorStats.get(Sequence) + + s = 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 isclose(s[0], 12.34) and isclose(s[3], 56.78) + + rev = reversed(s) + assert cstats.values() == ['of size', '5'] + + rev2 = s[::-1] + assert cstats.values() == ['of size', '5'] + + expected = [0, 56.78, 0, 0, 12.34] + assert allclose(rev, expected) + assert allclose(rev2, expected) + assert rev == rev2 + + rev[0::2] = Sequence([2.0, 2.0, 2.0]) + assert cstats.values() == ['of size', '3', 'from std::vector'] + + assert allclose(rev, [2, 56.78, 2, 0, 2]) + + 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_map_iterator(): + from pybind11_tests import StringMap + + m = StringMap({'hi': 'bye', 'black': 'white'}) + assert m['hi'] == 'bye' + assert len(m) == 2 + assert m['black'] == 'white' + + with pytest.raises(KeyError): + assert m['orange'] + m['orange'] = 'banana' + assert m['orange'] == 'banana' + + expected = {'hi': 'bye', 'black': 'white', 'orange': 'banana'} + for k in m: + assert m[k] == expected[k] + for k, v in m.items(): + assert v == expected[k] diff --git a/ext/pybind11/tests/test_smart_ptr.cpp b/ext/pybind11/tests/test_smart_ptr.cpp new file mode 100644 index 000000000..07c3cb066 --- /dev/null +++ b/ext/pybind11/tests/test_smart_ptr.cpp @@ -0,0 +1,224 @@ +/* + tests/test_smart_ptr.cpp -- binding classes with custom reference counting, + implicit conversions between 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_tests.h" +#include "object.h" + +/// Custom object with builtin reference counting (see 'object.h' for the implementation) +class MyObject1 : public Object { +public: + MyObject1(int value) : value(value) { + print_created(this, toString()); + } + + std::string toString() const { + return "MyObject1[" + std::to_string(value) + "]"; + } + +protected: + virtual ~MyObject1() { + print_destroyed(this); + } + +private: + int value; +}; + +/// Object managed by a std::shared_ptr<> +class MyObject2 { +public: + MyObject2(int value) : value(value) { + print_created(this, toString()); + } + + std::string toString() const { + return "MyObject2[" + std::to_string(value) + "]"; + } + + virtual ~MyObject2() { + print_destroyed(this); + } + +private: + int value; +}; + +/// Object managed by a std::shared_ptr<>, additionally derives from std::enable_shared_from_this<> +class MyObject3 : public std::enable_shared_from_this<MyObject3> { +public: + MyObject3(int value) : value(value) { + print_created(this, toString()); + } + + std::string toString() const { + return "MyObject3[" + std::to_string(value) + "]"; + } + + virtual ~MyObject3() { + print_destroyed(this); + } + +private: + int value; +}; + +class MyObject4 { +public: + MyObject4(int value) : value{value} { + print_created(this); + } + int value; +private: + ~MyObject4() { + print_destroyed(this); + } +}; + +/// Make pybind aware of the ref-counted wrapper type (s) +PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>); // Required for custom holder type +PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>); // Not required any more for std::shared_ptr, + // but it should compile without error + +Object *make_object_1() { return new MyObject1(1); } +ref<Object> make_object_2() { return new MyObject1(2); } + +MyObject1 *make_myobject1_1() { return new MyObject1(4); } +ref<MyObject1> make_myobject1_2() { return new MyObject1(5); } + +MyObject2 *make_myobject2_1() { return new MyObject2(6); } +std::shared_ptr<MyObject2> make_myobject2_2() { return std::make_shared<MyObject2>(7); } + +MyObject3 *make_myobject3_1() { return new MyObject3(8); } +std::shared_ptr<MyObject3> make_myobject3_2() { return std::make_shared<MyObject3>(9); } + +void print_object_1(const Object *obj) { py::print(obj->toString()); } +void print_object_2(ref<Object> obj) { py::print(obj->toString()); } +void print_object_3(const ref<Object> &obj) { py::print(obj->toString()); } +void print_object_4(const ref<Object> *obj) { py::print((*obj)->toString()); } + +void print_myobject1_1(const MyObject1 *obj) { py::print(obj->toString()); } +void print_myobject1_2(ref<MyObject1> obj) { py::print(obj->toString()); } +void print_myobject1_3(const ref<MyObject1> &obj) { py::print(obj->toString()); } +void print_myobject1_4(const ref<MyObject1> *obj) { py::print((*obj)->toString()); } + +void print_myobject2_1(const MyObject2 *obj) { py::print(obj->toString()); } +void print_myobject2_2(std::shared_ptr<MyObject2> obj) { py::print(obj->toString()); } +void print_myobject2_3(const std::shared_ptr<MyObject2> &obj) { py::print(obj->toString()); } +void print_myobject2_4(const std::shared_ptr<MyObject2> *obj) { py::print((*obj)->toString()); } + +void print_myobject3_1(const MyObject3 *obj) { py::print(obj->toString()); } +void print_myobject3_2(std::shared_ptr<MyObject3> obj) { py::print(obj->toString()); } +void print_myobject3_3(const std::shared_ptr<MyObject3> &obj) { py::print(obj->toString()); } +void print_myobject3_4(const std::shared_ptr<MyObject3> *obj) { py::print((*obj)->toString()); } + +test_initializer smart_ptr([](py::module &m) { + py::class_<Object, ref<Object>> obj(m, "Object"); + obj.def("getRefCount", &Object::getRefCount); + + py::class_<MyObject1, ref<MyObject1>>(m, "MyObject1", obj) + .def(py::init<int>()); + + m.def("make_object_1", &make_object_1); + m.def("make_object_2", &make_object_2); + m.def("make_myobject1_1", &make_myobject1_1); + m.def("make_myobject1_2", &make_myobject1_2); + m.def("print_object_1", &print_object_1); + m.def("print_object_2", &print_object_2); + m.def("print_object_3", &print_object_3); + m.def("print_object_4", &print_object_4); + m.def("print_myobject1_1", &print_myobject1_1); + m.def("print_myobject1_2", &print_myobject1_2); + m.def("print_myobject1_3", &print_myobject1_3); + m.def("print_myobject1_4", &print_myobject1_4); + + py::class_<MyObject2, std::shared_ptr<MyObject2>>(m, "MyObject2") + .def(py::init<int>()); + m.def("make_myobject2_1", &make_myobject2_1); + m.def("make_myobject2_2", &make_myobject2_2); + m.def("print_myobject2_1", &print_myobject2_1); + m.def("print_myobject2_2", &print_myobject2_2); + m.def("print_myobject2_3", &print_myobject2_3); + m.def("print_myobject2_4", &print_myobject2_4); + + py::class_<MyObject3, std::shared_ptr<MyObject3>>(m, "MyObject3") + .def(py::init<int>()); + m.def("make_myobject3_1", &make_myobject3_1); + m.def("make_myobject3_2", &make_myobject3_2); + m.def("print_myobject3_1", &print_myobject3_1); + m.def("print_myobject3_2", &print_myobject3_2); + m.def("print_myobject3_3", &print_myobject3_3); + m.def("print_myobject3_4", &print_myobject3_4); + + py::class_<MyObject4, std::unique_ptr<MyObject4, py::nodelete>>(m, "MyObject4") + .def(py::init<int>()) + .def_readwrite("value", &MyObject4::value); + + py::implicitly_convertible<py::int_, MyObject1>(); + + // Expose constructor stats for the ref type + m.def("cstats_ref", &ConstructorStats::get<ref_tag>); +}); + +struct SharedPtrRef { + struct A { + A() { print_created(this); } + A(const A &) { print_copy_created(this); } + A(A &&) { print_move_created(this); } + ~A() { print_destroyed(this); } + }; + + A value = {}; + std::shared_ptr<A> shared = std::make_shared<A>(); +}; + +struct SharedFromThisRef { + struct B : std::enable_shared_from_this<B> { + B() { print_created(this); } + B(const B &) : std::enable_shared_from_this<B>() { print_copy_created(this); } + B(B &&) : std::enable_shared_from_this<B>() { print_move_created(this); } + ~B() { print_destroyed(this); } + }; + + B value = {}; + std::shared_ptr<B> shared = std::make_shared<B>(); +}; + +test_initializer smart_ptr_and_references([](py::module &pm) { + auto m = pm.def_submodule("smart_ptr"); + + using A = SharedPtrRef::A; + py::class_<A, std::shared_ptr<A>>(m, "A"); + + py::class_<SharedPtrRef>(m, "SharedPtrRef") + .def(py::init<>()) + .def_readonly("ref", &SharedPtrRef::value) + .def_property_readonly("copy", [](const SharedPtrRef &s) { return s.value; }, + py::return_value_policy::copy) + .def_readonly("holder_ref", &SharedPtrRef::shared) + .def_property_readonly("holder_copy", [](const SharedPtrRef &s) { return s.shared; }, + py::return_value_policy::copy) + .def("set_ref", [](SharedPtrRef &, const A &) { return true; }) + .def("set_holder", [](SharedPtrRef &, std::shared_ptr<A>) { return true; }); + + using B = SharedFromThisRef::B; + py::class_<B, std::shared_ptr<B>>(m, "B"); + + py::class_<SharedFromThisRef>(m, "SharedFromThisRef") + .def(py::init<>()) + .def_readonly("bad_wp", &SharedFromThisRef::value) + .def_property_readonly("ref", [](const SharedFromThisRef &s) -> const B & { return *s.shared; }) + .def_property_readonly("copy", [](const SharedFromThisRef &s) { return s.value; }, + py::return_value_policy::copy) + .def_readonly("holder_ref", &SharedFromThisRef::shared) + .def_property_readonly("holder_copy", [](const SharedFromThisRef &s) { return s.shared; }, + py::return_value_policy::copy) + .def("set_ref", [](SharedFromThisRef &, const B &) { return true; }) + .def("set_holder", [](SharedFromThisRef &, std::shared_ptr<B>) { return true; }); +}); diff --git a/ext/pybind11/tests/test_smart_ptr.py b/ext/pybind11/tests/test_smart_ptr.py new file mode 100644 index 000000000..3a33e1761 --- /dev/null +++ b/ext/pybind11/tests/test_smart_ptr.py @@ -0,0 +1,198 @@ +import pytest +from pybind11_tests import ConstructorStats + + +def test_smart_ptr(capture): + # Object1 + from pybind11_tests import (MyObject1, make_object_1, make_object_2, + print_object_1, print_object_2, print_object_3, print_object_4) + + for i, o in enumerate([make_object_1(), make_object_2(), MyObject1(3)], start=1): + assert o.getRefCount() == 1 + with capture: + print_object_1(o) + print_object_2(o) + print_object_3(o) + print_object_4(o) + assert capture == "MyObject1[{i}]\n".format(i=i) * 4 + + from pybind11_tests import (make_myobject1_1, make_myobject1_2, + print_myobject1_1, print_myobject1_2, + print_myobject1_3, print_myobject1_4) + + for i, o in enumerate([make_myobject1_1(), make_myobject1_2(), MyObject1(6), 7], start=4): + print(o) + with capture: + if not isinstance(o, int): + print_object_1(o) + print_object_2(o) + print_object_3(o) + print_object_4(o) + print_myobject1_1(o) + print_myobject1_2(o) + print_myobject1_3(o) + print_myobject1_4(o) + assert capture == "MyObject1[{i}]\n".format(i=i) * (4 if isinstance(o, int) else 8) + + cstats = ConstructorStats.get(MyObject1) + assert cstats.alive() == 0 + expected_values = ['MyObject1[{}]'.format(i) for i in range(1, 7)] + ['MyObject1[7]'] * 4 + assert cstats.values() == expected_values + assert cstats.default_constructions == 0 + assert cstats.copy_constructions == 0 + # assert cstats.move_constructions >= 0 # Doesn't invoke any + assert cstats.copy_assignments == 0 + assert cstats.move_assignments == 0 + + # Object2 + from pybind11_tests import (MyObject2, make_myobject2_1, make_myobject2_2, + make_myobject3_1, make_myobject3_2, + print_myobject2_1, print_myobject2_2, + print_myobject2_3, print_myobject2_4) + + for i, o in zip([8, 6, 7], [MyObject2(8), make_myobject2_1(), make_myobject2_2()]): + print(o) + with capture: + print_myobject2_1(o) + print_myobject2_2(o) + print_myobject2_3(o) + print_myobject2_4(o) + assert capture == "MyObject2[{i}]\n".format(i=i) * 4 + + cstats = ConstructorStats.get(MyObject2) + assert cstats.alive() == 1 + o = None + assert cstats.alive() == 0 + assert cstats.values() == ['MyObject2[8]', 'MyObject2[6]', 'MyObject2[7]'] + assert cstats.default_constructions == 0 + assert cstats.copy_constructions == 0 + # assert cstats.move_constructions >= 0 # Doesn't invoke any + assert cstats.copy_assignments == 0 + assert cstats.move_assignments == 0 + + # Object3 + from pybind11_tests import (MyObject3, print_myobject3_1, print_myobject3_2, + print_myobject3_3, print_myobject3_4) + + for i, o in zip([9, 8, 9], [MyObject3(9), make_myobject3_1(), make_myobject3_2()]): + print(o) + with capture: + print_myobject3_1(o) + print_myobject3_2(o) + print_myobject3_3(o) + print_myobject3_4(o) + assert capture == "MyObject3[{i}]\n".format(i=i) * 4 + + cstats = ConstructorStats.get(MyObject3) + assert cstats.alive() == 1 + o = None + assert cstats.alive() == 0 + assert cstats.values() == ['MyObject3[9]', 'MyObject3[8]', 'MyObject3[9]'] + assert cstats.default_constructions == 0 + assert cstats.copy_constructions == 0 + # assert cstats.move_constructions >= 0 # Doesn't invoke any + assert cstats.copy_assignments == 0 + assert cstats.move_assignments == 0 + + # Object and ref + from pybind11_tests import Object, cstats_ref + + cstats = ConstructorStats.get(Object) + assert cstats.alive() == 0 + assert cstats.values() == [] + assert cstats.default_constructions == 10 + assert cstats.copy_constructions == 0 + # assert cstats.move_constructions >= 0 # Doesn't invoke any + assert cstats.copy_assignments == 0 + assert cstats.move_assignments == 0 + + cstats = cstats_ref() + assert cstats.alive() == 0 + assert cstats.values() == ['from pointer'] * 10 + assert cstats.default_constructions == 30 + assert cstats.copy_constructions == 12 + # assert cstats.move_constructions >= 0 # Doesn't invoke any + assert cstats.copy_assignments == 30 + assert cstats.move_assignments == 0 + + +def test_unique_nodelete(): + from pybind11_tests import MyObject4 + o = MyObject4(23) + assert o.value == 23 + cstats = ConstructorStats.get(MyObject4) + assert cstats.alive() == 1 + del o + cstats = ConstructorStats.get(MyObject4) + assert cstats.alive() == 1 # Leak, but that's intentional + + +def test_shared_ptr_and_references(): + from pybind11_tests.smart_ptr import SharedPtrRef, A + + s = SharedPtrRef() + stats = ConstructorStats.get(A) + assert stats.alive() == 2 + + ref = s.ref # init_holder_helper(holder_ptr=false, owned=false) + assert stats.alive() == 2 + assert s.set_ref(ref) + with pytest.raises(RuntimeError) as excinfo: + assert s.set_holder(ref) + assert "Unable to cast from non-held to held instance" in str(excinfo.value) + + copy = s.copy # init_holder_helper(holder_ptr=false, owned=true) + assert stats.alive() == 3 + assert s.set_ref(copy) + assert s.set_holder(copy) + + holder_ref = s.holder_ref # init_holder_helper(holder_ptr=true, owned=false) + assert stats.alive() == 3 + assert s.set_ref(holder_ref) + assert s.set_holder(holder_ref) + + holder_copy = s.holder_copy # init_holder_helper(holder_ptr=true, owned=true) + assert stats.alive() == 3 + assert s.set_ref(holder_copy) + assert s.set_holder(holder_copy) + + del ref, copy, holder_ref, holder_copy, s + assert stats.alive() == 0 + + +def test_shared_ptr_from_this_and_references(): + from pybind11_tests.smart_ptr import SharedFromThisRef, B + + s = SharedFromThisRef() + stats = ConstructorStats.get(B) + assert stats.alive() == 2 + + ref = s.ref # init_holder_helper(holder_ptr=false, owned=false, bad_wp=false) + assert stats.alive() == 2 + assert s.set_ref(ref) + assert s.set_holder(ref) # std::enable_shared_from_this can create a holder from a reference + + bad_wp = s.bad_wp # init_holder_helper(holder_ptr=false, owned=false, bad_wp=true) + assert stats.alive() == 2 + assert s.set_ref(bad_wp) + with pytest.raises(RuntimeError) as excinfo: + assert s.set_holder(bad_wp) + assert "Unable to cast from non-held to held instance" in str(excinfo.value) + + copy = s.copy # init_holder_helper(holder_ptr=false, owned=true, bad_wp=false) + assert stats.alive() == 3 + assert s.set_ref(copy) + assert s.set_holder(copy) + + holder_ref = s.holder_ref # init_holder_helper(holder_ptr=true, owned=false, bad_wp=false) + assert stats.alive() == 3 + assert s.set_ref(holder_ref) + assert s.set_holder(holder_ref) + + holder_copy = s.holder_copy # init_holder_helper(holder_ptr=true, owned=true, bad_wp=false) + assert stats.alive() == 3 + assert s.set_ref(holder_copy) + assert s.set_holder(holder_copy) + + del ref, bad_wp, copy, holder_ref, holder_copy, s + assert stats.alive() == 0 diff --git a/ext/pybind11/tests/test_stl_binders.cpp b/ext/pybind11/tests/test_stl_binders.cpp new file mode 100644 index 000000000..b9b56c15d --- /dev/null +++ b/ext/pybind11/tests/test_stl_binders.cpp @@ -0,0 +1,95 @@ +/* + tests/test_stl_binders.cpp -- Usage of stl_binders functions + + Copyright (c) 2016 Sergey Lyskov + + 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 <pybind11/stl_bind.h> +#include <map> +#include <deque> +#include <unordered_map> + +class El { +public: + El() = delete; + El(int v) : a(v) { } + + int a; +}; + +std::ostream & operator<<(std::ostream &s, El const&v) { + s << "El{" << v.a << '}'; + return s; +} + +/// Issue #487: binding std::vector<E> with E non-copyable +class E_nc { +public: + explicit E_nc(int i) : value{i} {} + E_nc(const E_nc &) = delete; + E_nc &operator=(const E_nc &) = delete; + E_nc(E_nc &&) = default; + E_nc &operator=(E_nc &&) = default; + + int value; +}; + +template <class Container> Container *one_to_n(int n) { + auto v = new Container(); + for (int i = 1; i <= n; i++) + v->emplace_back(i); + return v; +} + +template <class Map> Map *times_ten(int n) { + auto m = new Map(); + for (int i = 1; i <= n; i++) + m->emplace(int(i), E_nc(10*i)); + return m; +} + +test_initializer stl_binder_vector([](py::module &m) { + py::class_<El>(m, "El") + .def(py::init<int>()); + + py::bind_vector<std::vector<unsigned int>>(m, "VectorInt"); + py::bind_vector<std::vector<bool>>(m, "VectorBool"); + + py::bind_vector<std::vector<El>>(m, "VectorEl"); + + py::bind_vector<std::vector<std::vector<El>>>(m, "VectorVectorEl"); + +}); + +test_initializer stl_binder_map([](py::module &m) { + py::bind_map<std::map<std::string, double>>(m, "MapStringDouble"); + py::bind_map<std::unordered_map<std::string, double>>(m, "UnorderedMapStringDouble"); + + py::bind_map<std::map<std::string, double const>>(m, "MapStringDoubleConst"); + py::bind_map<std::unordered_map<std::string, double const>>(m, "UnorderedMapStringDoubleConst"); + +}); + +test_initializer stl_binder_noncopyable([](py::module &m) { + py::class_<E_nc>(m, "ENC") + .def(py::init<int>()) + .def_readwrite("value", &E_nc::value); + + py::bind_vector<std::vector<E_nc>>(m, "VectorENC"); + m.def("get_vnc", &one_to_n<std::vector<E_nc>>, py::return_value_policy::reference); + + py::bind_vector<std::deque<E_nc>>(m, "DequeENC"); + m.def("get_dnc", &one_to_n<std::deque<E_nc>>, py::return_value_policy::reference); + + py::bind_map<std::map<int, E_nc>>(m, "MapENC"); + m.def("get_mnc", ×_ten<std::map<int, E_nc>>, py::return_value_policy::reference); + + py::bind_map<std::unordered_map<int, E_nc>>(m, "UmapENC"); + m.def("get_umnc", ×_ten<std::unordered_map<int, E_nc>>, py::return_value_policy::reference); +}); + diff --git a/ext/pybind11/tests/test_stl_binders.py b/ext/pybind11/tests/test_stl_binders.py new file mode 100644 index 000000000..c9bcc7935 --- /dev/null +++ b/ext/pybind11/tests/test_stl_binders.py @@ -0,0 +1,140 @@ +def test_vector_int(): + from pybind11_tests import VectorInt + + v_int = VectorInt([0, 0]) + assert len(v_int) == 2 + assert bool(v_int) is True + + v_int2 = VectorInt([0, 0]) + assert v_int == v_int2 + v_int2[1] = 1 + assert v_int != v_int2 + + v_int2.append(2) + v_int2.append(3) + v_int2.insert(0, 1) + v_int2.insert(0, 2) + v_int2.insert(0, 3) + assert str(v_int2) == "VectorInt[3, 2, 1, 0, 1, 2, 3]" + + v_int.append(99) + v_int2[2:-2] = v_int + assert v_int2 == VectorInt([3, 2, 0, 0, 99, 2, 3]) + del v_int2[1:3] + assert v_int2 == VectorInt([3, 0, 99, 2, 3]) + del v_int2[0] + assert v_int2 == VectorInt([0, 99, 2, 3]) + + +def test_vector_custom(): + from pybind11_tests import El, VectorEl, VectorVectorEl + + v_a = VectorEl() + v_a.append(El(1)) + v_a.append(El(2)) + assert str(v_a) == "VectorEl[El{1}, El{2}]" + + vv_a = VectorVectorEl() + vv_a.append(v_a) + vv_b = vv_a[0] + assert str(vv_b) == "VectorEl[El{1}, El{2}]" + + +def test_vector_bool(): + from pybind11_tests import VectorBool + + vv_c = VectorBool() + for i in range(10): + vv_c.append(i % 2 == 0) + for i in range(10): + assert vv_c[i] == (i % 2 == 0) + assert str(vv_c) == "VectorBool[1, 0, 1, 0, 1, 0, 1, 0, 1, 0]" + + +def test_map_string_double(): + from pybind11_tests import MapStringDouble, UnorderedMapStringDouble + + m = MapStringDouble() + m['a'] = 1 + m['b'] = 2.5 + + assert list(m) == ['a', 'b'] + assert list(m.items()) == [('a', 1), ('b', 2.5)] + assert str(m) == "MapStringDouble{a: 1, b: 2.5}" + + um = UnorderedMapStringDouble() + um['ua'] = 1.1 + um['ub'] = 2.6 + + assert sorted(list(um)) == ['ua', 'ub'] + assert sorted(list(um.items())) == [('ua', 1.1), ('ub', 2.6)] + assert "UnorderedMapStringDouble" in str(um) + + +def test_map_string_double_const(): + from pybind11_tests import MapStringDoubleConst, UnorderedMapStringDoubleConst + + mc = MapStringDoubleConst() + mc['a'] = 10 + mc['b'] = 20.5 + assert str(mc) == "MapStringDoubleConst{a: 10, b: 20.5}" + + umc = UnorderedMapStringDoubleConst() + umc['a'] = 11 + umc['b'] = 21.5 + + str(umc) + + +def test_noncopyable_vector(): + from pybind11_tests import get_vnc + + vnc = get_vnc(5) + for i in range(0, 5): + assert vnc[i].value == i + 1 + + for i, j in enumerate(vnc, start=1): + assert j.value == i + + +def test_noncopyable_deque(): + from pybind11_tests import get_dnc + + dnc = get_dnc(5) + for i in range(0, 5): + assert dnc[i].value == i + 1 + + i = 1 + for j in dnc: + assert(j.value == i) + i += 1 + + +def test_noncopyable_map(): + from pybind11_tests import get_mnc + + mnc = get_mnc(5) + for i in range(1, 6): + assert mnc[i].value == 10 * i + + vsum = 0 + for k, v in mnc.items(): + assert v.value == 10 * k + vsum += v.value + + assert vsum == 150 + + +def test_noncopyable_unordered_map(): + from pybind11_tests import get_umnc + + mnc = get_umnc(5) + for i in range(1, 6): + assert mnc[i].value == 10 * i + + vsum = 0 + for k, v in mnc.items(): + assert v.value == 10 * k + vsum += v.value + + assert vsum == 150 diff --git a/ext/pybind11/tests/test_virtual_functions.cpp b/ext/pybind11/tests/test_virtual_functions.cpp new file mode 100644 index 000000000..0f8ed2afb --- /dev/null +++ b/ext/pybind11/tests/test_virtual_functions.cpp @@ -0,0 +1,347 @@ +/* + tests/test_virtual_functions.cpp -- overriding virtual functions from Python + + 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 <pybind11/functional.h> + +/* This is an example class that we'll want to be able to extend from Python */ +class ExampleVirt { +public: + ExampleVirt(int state) : state(state) { print_created(this, state); } + ExampleVirt(const ExampleVirt &e) : state(e.state) { print_copy_created(this); } + ExampleVirt(ExampleVirt &&e) : state(e.state) { print_move_created(this); e.state = 0; } + ~ExampleVirt() { print_destroyed(this); } + + virtual int run(int value) { + py::print("Original implementation of " + "ExampleVirt::run(state={}, value={}, str1={}, str2={})"_s.format(state, value, get_string1(), *get_string2())); + return state + value; + } + + virtual bool run_bool() = 0; + virtual void pure_virtual() = 0; + + // Returning a reference/pointer to a type converted from python (numbers, strings, etc.) is a + // bit trickier, because the actual int& or std::string& or whatever only exists temporarily, so + // we have to handle it specially in the trampoline class (see below). + virtual const std::string &get_string1() { return str1; } + virtual const std::string *get_string2() { return &str2; } + +private: + int state; + const std::string str1{"default1"}, str2{"default2"}; +}; + +/* This is a wrapper class that must be generated */ +class PyExampleVirt : public ExampleVirt { +public: + using ExampleVirt::ExampleVirt; /* Inherit constructors */ + + int run(int value) override { + /* Generate wrapping code that enables native function overloading */ + PYBIND11_OVERLOAD( + int, /* Return type */ + ExampleVirt, /* Parent class */ + run, /* Name of function */ + value /* Argument(s) */ + ); + } + + bool run_bool() override { + PYBIND11_OVERLOAD_PURE( + bool, /* Return type */ + ExampleVirt, /* Parent class */ + run_bool, /* Name of function */ + /* This function has no arguments. The trailing comma + in the previous line is needed for some compilers */ + ); + } + + void pure_virtual() override { + PYBIND11_OVERLOAD_PURE( + void, /* Return type */ + ExampleVirt, /* Parent class */ + pure_virtual, /* Name of function */ + /* This function has no arguments. The trailing comma + in the previous line is needed for some compilers */ + ); + } + + // We can return reference types for compatibility with C++ virtual interfaces that do so, but + // note they have some significant limitations (see the documentation). + const std::string &get_string1() override { + PYBIND11_OVERLOAD( + const std::string &, /* Return type */ + ExampleVirt, /* Parent class */ + get_string1, /* Name of function */ + /* (no arguments) */ + ); + } + + const std::string *get_string2() override { + PYBIND11_OVERLOAD( + const std::string *, /* Return type */ + ExampleVirt, /* Parent class */ + get_string2, /* Name of function */ + /* (no arguments) */ + ); + } + +}; + +class NonCopyable { +public: + NonCopyable(int a, int b) : value{new int(a*b)} { print_created(this, a, b); } + NonCopyable(NonCopyable &&o) { value = std::move(o.value); print_move_created(this); } + NonCopyable(const NonCopyable &) = delete; + NonCopyable() = delete; + void operator=(const NonCopyable &) = delete; + void operator=(NonCopyable &&) = delete; + std::string get_value() const { + if (value) return std::to_string(*value); else return "(null)"; + } + ~NonCopyable() { print_destroyed(this); } + +private: + std::unique_ptr<int> value; +}; + +// This is like the above, but is both copy and movable. In effect this means it should get moved +// when it is not referenced elsewhere, but copied if it is still referenced. +class Movable { +public: + Movable(int a, int b) : value{a+b} { print_created(this, a, b); } + Movable(const Movable &m) { value = m.value; print_copy_created(this); } + Movable(Movable &&m) { value = std::move(m.value); print_move_created(this); } + std::string get_value() const { return std::to_string(value); } + ~Movable() { print_destroyed(this); } +private: + int value; +}; + +class NCVirt { +public: + virtual NonCopyable get_noncopyable(int a, int b) { return NonCopyable(a, b); } + virtual Movable get_movable(int a, int b) = 0; + + std::string print_nc(int a, int b) { return get_noncopyable(a, b).get_value(); } + std::string print_movable(int a, int b) { return get_movable(a, b).get_value(); } +}; +class NCVirtTrampoline : public NCVirt { +#if !defined(__INTEL_COMPILER) + NonCopyable get_noncopyable(int a, int b) override { + PYBIND11_OVERLOAD(NonCopyable, NCVirt, get_noncopyable, a, b); + } +#endif + Movable get_movable(int a, int b) override { + PYBIND11_OVERLOAD_PURE(Movable, NCVirt, get_movable, a, b); + } +}; + +int runExampleVirt(ExampleVirt *ex, int value) { + return ex->run(value); +} + +bool runExampleVirtBool(ExampleVirt* ex) { + return ex->run_bool(); +} + +void runExampleVirtVirtual(ExampleVirt *ex) { + ex->pure_virtual(); +} + + +// Inheriting virtual methods. We do two versions here: the repeat-everything version and the +// templated trampoline versions mentioned in docs/advanced.rst. +// +// These base classes are exactly the same, but we technically need distinct +// classes for this example code because we need to be able to bind them +// properly (pybind11, sensibly, doesn't allow us to bind the same C++ class to +// multiple python classes). +class A_Repeat { +#define A_METHODS \ +public: \ + virtual int unlucky_number() = 0; \ + virtual std::string say_something(unsigned times) { \ + std::string s = ""; \ + for (unsigned i = 0; i < times; ++i) \ + s += "hi"; \ + return s; \ + } \ + std::string say_everything() { \ + return say_something(1) + " " + std::to_string(unlucky_number()); \ + } +A_METHODS +}; +class B_Repeat : public A_Repeat { +#define B_METHODS \ +public: \ + int unlucky_number() override { return 13; } \ + std::string say_something(unsigned times) override { \ + return "B says hi " + std::to_string(times) + " times"; \ + } \ + virtual double lucky_number() { return 7.0; } +B_METHODS +}; +class C_Repeat : public B_Repeat { +#define C_METHODS \ +public: \ + int unlucky_number() override { return 4444; } \ + double lucky_number() override { return 888; } +C_METHODS +}; +class D_Repeat : public C_Repeat { +#define D_METHODS // Nothing overridden. +D_METHODS +}; + +// Base classes for templated inheritance trampolines. Identical to the repeat-everything version: +class A_Tpl { A_METHODS }; +class B_Tpl : public A_Tpl { B_METHODS }; +class C_Tpl : public B_Tpl { C_METHODS }; +class D_Tpl : public C_Tpl { D_METHODS }; + + +// Inheritance approach 1: each trampoline gets every virtual method (11 in total) +class PyA_Repeat : public A_Repeat { +public: + using A_Repeat::A_Repeat; + int unlucky_number() override { PYBIND11_OVERLOAD_PURE(int, A_Repeat, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, A_Repeat, say_something, times); } +}; +class PyB_Repeat : public B_Repeat { +public: + using B_Repeat::B_Repeat; + int unlucky_number() override { PYBIND11_OVERLOAD(int, B_Repeat, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, B_Repeat, say_something, times); } + double lucky_number() override { PYBIND11_OVERLOAD(double, B_Repeat, lucky_number, ); } +}; +class PyC_Repeat : public C_Repeat { +public: + using C_Repeat::C_Repeat; + int unlucky_number() override { PYBIND11_OVERLOAD(int, C_Repeat, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, C_Repeat, say_something, times); } + double lucky_number() override { PYBIND11_OVERLOAD(double, C_Repeat, lucky_number, ); } +}; +class PyD_Repeat : public D_Repeat { +public: + using D_Repeat::D_Repeat; + int unlucky_number() override { PYBIND11_OVERLOAD(int, D_Repeat, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, D_Repeat, say_something, times); } + double lucky_number() override { PYBIND11_OVERLOAD(double, D_Repeat, lucky_number, ); } +}; + +// Inheritance approach 2: templated trampoline classes. +// +// Advantages: +// - we have only 2 (template) class and 4 method declarations (one per virtual method, plus one for +// any override of a pure virtual method), versus 4 classes and 6 methods (MI) or 4 classes and 11 +// methods (repeat). +// - Compared to MI, we also don't have to change the non-trampoline inheritance to virtual, and can +// properly inherit constructors. +// +// Disadvantage: +// - the compiler must still generate and compile 14 different methods (more, even, than the 11 +// required for the repeat approach) instead of the 6 required for MI. (If there was no pure +// method (or no pure method override), the number would drop down to the same 11 as the repeat +// approach). +template <class Base = A_Tpl> +class PyA_Tpl : public Base { +public: + using Base::Base; // Inherit constructors + int unlucky_number() override { PYBIND11_OVERLOAD_PURE(int, Base, unlucky_number, ); } + std::string say_something(unsigned times) override { PYBIND11_OVERLOAD(std::string, Base, say_something, times); } +}; +template <class Base = B_Tpl> +class PyB_Tpl : public PyA_Tpl<Base> { +public: + using PyA_Tpl<Base>::PyA_Tpl; // Inherit constructors (via PyA_Tpl's inherited constructors) + int unlucky_number() override { PYBIND11_OVERLOAD(int, Base, unlucky_number, ); } + double lucky_number() override { PYBIND11_OVERLOAD(double, Base, lucky_number, ); } +}; +// Since C_Tpl and D_Tpl don't declare any new virtual methods, we don't actually need these (we can +// use PyB_Tpl<C_Tpl> and PyB_Tpl<D_Tpl> for the trampoline classes instead): +/* +template <class Base = C_Tpl> class PyC_Tpl : public PyB_Tpl<Base> { +public: + using PyB_Tpl<Base>::PyB_Tpl; +}; +template <class Base = D_Tpl> class PyD_Tpl : public PyC_Tpl<Base> { +public: + using PyC_Tpl<Base>::PyC_Tpl; +}; +*/ + + +void initialize_inherited_virtuals(py::module &m) { + // Method 1: repeat + py::class_<A_Repeat, PyA_Repeat>(m, "A_Repeat") + .def(py::init<>()) + .def("unlucky_number", &A_Repeat::unlucky_number) + .def("say_something", &A_Repeat::say_something) + .def("say_everything", &A_Repeat::say_everything); + py::class_<B_Repeat, A_Repeat, PyB_Repeat>(m, "B_Repeat") + .def(py::init<>()) + .def("lucky_number", &B_Repeat::lucky_number); + py::class_<C_Repeat, B_Repeat, PyC_Repeat>(m, "C_Repeat") + .def(py::init<>()); + py::class_<D_Repeat, C_Repeat, PyD_Repeat>(m, "D_Repeat") + .def(py::init<>()); + + // Method 2: Templated trampolines + py::class_<A_Tpl, PyA_Tpl<>>(m, "A_Tpl") + .def(py::init<>()) + .def("unlucky_number", &A_Tpl::unlucky_number) + .def("say_something", &A_Tpl::say_something) + .def("say_everything", &A_Tpl::say_everything); + py::class_<B_Tpl, A_Tpl, PyB_Tpl<>>(m, "B_Tpl") + .def(py::init<>()) + .def("lucky_number", &B_Tpl::lucky_number); + py::class_<C_Tpl, B_Tpl, PyB_Tpl<C_Tpl>>(m, "C_Tpl") + .def(py::init<>()); + py::class_<D_Tpl, C_Tpl, PyB_Tpl<D_Tpl>>(m, "D_Tpl") + .def(py::init<>()); + +}; + + +test_initializer virtual_functions([](py::module &m) { + /* Important: indicate the trampoline class PyExampleVirt using the third + argument to py::class_. The second argument with the unique pointer + is simply the default holder type used by pybind11. */ + py::class_<ExampleVirt, PyExampleVirt>(m, "ExampleVirt") + .def(py::init<int>()) + /* Reference original class in function definitions */ + .def("run", &ExampleVirt::run) + .def("run_bool", &ExampleVirt::run_bool) + .def("pure_virtual", &ExampleVirt::pure_virtual); + + py::class_<NonCopyable>(m, "NonCopyable") + .def(py::init<int, int>()); + + py::class_<Movable>(m, "Movable") + .def(py::init<int, int>()); + +#if !defined(__INTEL_COMPILER) + py::class_<NCVirt, NCVirtTrampoline>(m, "NCVirt") + .def(py::init<>()) + .def("get_noncopyable", &NCVirt::get_noncopyable) + .def("get_movable", &NCVirt::get_movable) + .def("print_nc", &NCVirt::print_nc) + .def("print_movable", &NCVirt::print_movable); +#endif + + m.def("runExampleVirt", &runExampleVirt); + m.def("runExampleVirtBool", &runExampleVirtBool); + m.def("runExampleVirtVirtual", &runExampleVirtVirtual); + + m.def("cstats_debug", &ConstructorStats::get<ExampleVirt>); + initialize_inherited_virtuals(m); +}); diff --git a/ext/pybind11/tests/test_virtual_functions.py b/ext/pybind11/tests/test_virtual_functions.py new file mode 100644 index 000000000..a9aecd67f --- /dev/null +++ b/ext/pybind11/tests/test_virtual_functions.py @@ -0,0 +1,256 @@ +import pytest +import pybind11_tests +from pybind11_tests import ConstructorStats + + +def test_override(capture, msg): + from pybind11_tests import (ExampleVirt, runExampleVirt, runExampleVirtVirtual, + runExampleVirtBool) + + class ExtendedExampleVirt(ExampleVirt): + def __init__(self, state): + super(ExtendedExampleVirt, self).__init__(state + 1) + self.data = "Hello world" + + def run(self, value): + print('ExtendedExampleVirt::run(%i), calling parent..' % value) + return super(ExtendedExampleVirt, self).run(value + 1) + + def run_bool(self): + print('ExtendedExampleVirt::run_bool()') + return False + + def get_string1(self): + return "override1" + + def pure_virtual(self): + print('ExtendedExampleVirt::pure_virtual(): %s' % self.data) + + class ExtendedExampleVirt2(ExtendedExampleVirt): + def __init__(self, state): + super(ExtendedExampleVirt2, self).__init__(state + 1) + + def get_string2(self): + return "override2" + + ex12 = ExampleVirt(10) + with capture: + assert runExampleVirt(ex12, 20) == 30 + assert capture == """ + Original implementation of ExampleVirt::run(state=10, value=20, str1=default1, str2=default2) + """ # noqa: E501 line too long + + with pytest.raises(RuntimeError) as excinfo: + runExampleVirtVirtual(ex12) + assert msg(excinfo.value) == 'Tried to call pure virtual function "ExampleVirt::pure_virtual"' + + ex12p = ExtendedExampleVirt(10) + with capture: + assert runExampleVirt(ex12p, 20) == 32 + assert capture == """ + ExtendedExampleVirt::run(20), calling parent.. + Original implementation of ExampleVirt::run(state=11, value=21, str1=override1, str2=default2) + """ # noqa: E501 line too long + with capture: + assert runExampleVirtBool(ex12p) is False + assert capture == "ExtendedExampleVirt::run_bool()" + with capture: + runExampleVirtVirtual(ex12p) + assert capture == "ExtendedExampleVirt::pure_virtual(): Hello world" + + ex12p2 = ExtendedExampleVirt2(15) + with capture: + assert runExampleVirt(ex12p2, 50) == 68 + assert capture == """ + ExtendedExampleVirt::run(50), calling parent.. + Original implementation of ExampleVirt::run(state=17, value=51, str1=override1, str2=override2) + """ # noqa: E501 line too long + + cstats = ConstructorStats.get(ExampleVirt) + assert cstats.alive() == 3 + del ex12, ex12p, ex12p2 + assert cstats.alive() == 0 + assert cstats.values() == ['10', '11', '17'] + assert cstats.copy_constructions == 0 + assert cstats.move_constructions >= 0 + + +def test_inheriting_repeat(): + from pybind11_tests import A_Repeat, B_Repeat, C_Repeat, D_Repeat, A_Tpl, B_Tpl, C_Tpl, D_Tpl + + class AR(A_Repeat): + def unlucky_number(self): + return 99 + + class AT(A_Tpl): + def unlucky_number(self): + return 999 + + obj = AR() + assert obj.say_something(3) == "hihihi" + assert obj.unlucky_number() == 99 + assert obj.say_everything() == "hi 99" + + obj = AT() + assert obj.say_something(3) == "hihihi" + assert obj.unlucky_number() == 999 + assert obj.say_everything() == "hi 999" + + for obj in [B_Repeat(), B_Tpl()]: + assert obj.say_something(3) == "B says hi 3 times" + assert obj.unlucky_number() == 13 + assert obj.lucky_number() == 7.0 + assert obj.say_everything() == "B says hi 1 times 13" + + for obj in [C_Repeat(), C_Tpl()]: + assert obj.say_something(3) == "B says hi 3 times" + assert obj.unlucky_number() == 4444 + assert obj.lucky_number() == 888.0 + assert obj.say_everything() == "B says hi 1 times 4444" + + class CR(C_Repeat): + def lucky_number(self): + return C_Repeat.lucky_number(self) + 1.25 + + obj = CR() + assert obj.say_something(3) == "B says hi 3 times" + assert obj.unlucky_number() == 4444 + assert obj.lucky_number() == 889.25 + assert obj.say_everything() == "B says hi 1 times 4444" + + class CT(C_Tpl): + pass + + obj = CT() + assert obj.say_something(3) == "B says hi 3 times" + assert obj.unlucky_number() == 4444 + assert obj.lucky_number() == 888.0 + assert obj.say_everything() == "B says hi 1 times 4444" + + class CCR(CR): + def lucky_number(self): + return CR.lucky_number(self) * 10 + + obj = CCR() + assert obj.say_something(3) == "B says hi 3 times" + assert obj.unlucky_number() == 4444 + assert obj.lucky_number() == 8892.5 + assert obj.say_everything() == "B says hi 1 times 4444" + + class CCT(CT): + def lucky_number(self): + return CT.lucky_number(self) * 1000 + + obj = CCT() + assert obj.say_something(3) == "B says hi 3 times" + assert obj.unlucky_number() == 4444 + assert obj.lucky_number() == 888000.0 + assert obj.say_everything() == "B says hi 1 times 4444" + + class DR(D_Repeat): + def unlucky_number(self): + return 123 + + def lucky_number(self): + return 42.0 + + for obj in [D_Repeat(), D_Tpl()]: + assert obj.say_something(3) == "B says hi 3 times" + assert obj.unlucky_number() == 4444 + assert obj.lucky_number() == 888.0 + assert obj.say_everything() == "B says hi 1 times 4444" + + obj = DR() + assert obj.say_something(3) == "B says hi 3 times" + assert obj.unlucky_number() == 123 + assert obj.lucky_number() == 42.0 + assert obj.say_everything() == "B says hi 1 times 123" + + class DT(D_Tpl): + def say_something(self, times): + return "DT says:" + (' quack' * times) + + def unlucky_number(self): + return 1234 + + def lucky_number(self): + return -4.25 + + obj = DT() + assert obj.say_something(3) == "DT says: quack quack quack" + assert obj.unlucky_number() == 1234 + assert obj.lucky_number() == -4.25 + assert obj.say_everything() == "DT says: quack 1234" + + class DT2(DT): + def say_something(self, times): + return "DT2: " + ('QUACK' * times) + + def unlucky_number(self): + return -3 + + class BT(B_Tpl): + def say_something(self, times): + return "BT" * times + + def unlucky_number(self): + return -7 + + def lucky_number(self): + return -1.375 + + obj = BT() + assert obj.say_something(3) == "BTBTBT" + assert obj.unlucky_number() == -7 + assert obj.lucky_number() == -1.375 + assert obj.say_everything() == "BT -7" + + +@pytest.mark.skipif(not hasattr(pybind11_tests, 'NCVirt'), + reason="NCVirt test broken on ICPC") +def test_move_support(): + from pybind11_tests import NCVirt, NonCopyable, Movable + + class NCVirtExt(NCVirt): + def get_noncopyable(self, a, b): + # Constructs and returns a new instance: + nc = NonCopyable(a * a, b * b) + return nc + + def get_movable(self, a, b): + # Return a referenced copy + self.movable = Movable(a, b) + return self.movable + + class NCVirtExt2(NCVirt): + def get_noncopyable(self, a, b): + # Keep a reference: this is going to throw an exception + self.nc = NonCopyable(a, b) + return self.nc + + def get_movable(self, a, b): + # Return a new instance without storing it + return Movable(a, b) + + ncv1 = NCVirtExt() + assert ncv1.print_nc(2, 3) == "36" + assert ncv1.print_movable(4, 5) == "9" + ncv2 = NCVirtExt2() + assert ncv2.print_movable(7, 7) == "14" + # Don't check the exception message here because it differs under debug/non-debug mode + with pytest.raises(RuntimeError): + ncv2.print_nc(9, 9) + + nc_stats = ConstructorStats.get(NonCopyable) + mv_stats = ConstructorStats.get(Movable) + assert nc_stats.alive() == 1 + assert mv_stats.alive() == 1 + del ncv1, ncv2 + assert nc_stats.alive() == 0 + assert mv_stats.alive() == 0 + assert nc_stats.values() == ['4', '9', '9', '9'] + assert mv_stats.values() == ['4', '5', '7', '7'] + assert nc_stats.copy_constructions == 0 + assert mv_stats.copy_constructions == 1 + assert nc_stats.move_constructions >= 0 + assert mv_stats.move_constructions >= 0 |