diff options
author | Bobby R. Bruce <bbruce@ucdavis.edu> | 2019-09-23 13:52:58 -0700 |
---|---|---|
committer | Bobby R. Bruce <bbruce@ucdavis.edu> | 2019-09-24 21:40:15 +0000 |
commit | f97cf54db7a6f7642cc9fd122f23c4396c39bcf0 (patch) | |
tree | 17d2ed22a1114cb138500d46afddb3bafcc2b418 /ext/pybind11/tests | |
parent | 9235ae56c282d5a02ada3ed9b4e0fe2ee5738bde (diff) | |
download | gem5-f97cf54db7a6f7642cc9fd122f23c4396c39bcf0.tar.xz |
ext: Updated Pybind11 to version 2.4.1.
This updates Pybind11 from version 2.2.1 to version 2.4.1. This fixes
warning/error received when "<experiment/optional>" is used when
compiling using c++14 with clang. It should be noted that
"ext/pybind11/include/pybind11/std.h" has been changed to include a fix
added by commit ba42457254cc362eddc099f22b60d469cc6369e0. This is
necessary to avoid build errors.
Built: Linux (gcc, c++11) and MacOS (clang, c++14).
Tested: Ran quick tests for X86, ARM, and RISC-V.
Deprecates: https://gem5-review.googlesource.com/c/public/gem5/+/21019
Change-Id: Ie9783511cb6be50136076a55330e645f4f36d075
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/21119
Reviewed-by: Jason Lowe-Power <jason@lowepower.com>
Reviewed-by: Andreas Sandberg <andreas.sandberg@arm.com>
Maintainer: Jason Lowe-Power <jason@lowepower.com>
Maintainer: Andreas Sandberg <andreas.sandberg@arm.com>
Tested-by: kokoro <noreply+kokoro@google.com>
Diffstat (limited to 'ext/pybind11/tests')
64 files changed, 1890 insertions, 141 deletions
diff --git a/ext/pybind11/tests/CMakeLists.txt b/ext/pybind11/tests/CMakeLists.txt index 25e06662c..765c47adb 100644 --- a/ext/pybind11/tests/CMakeLists.txt +++ b/ext/pybind11/tests/CMakeLists.txt @@ -26,6 +26,7 @@ endif() # Full set of test files (you can override these; see below) set(PYBIND11_TEST_FILES + test_async.cpp test_buffers.cpp test_builtin_casters.cpp test_call_policies.cpp @@ -40,6 +41,7 @@ set(PYBIND11_TEST_FILES test_eval.cpp test_exceptions.cpp test_factory_constructors.cpp + test_gil_scoped.cpp test_iostream.cpp test_kwargs_and_defaults.cpp test_local_bindings.cpp @@ -57,6 +59,8 @@ set(PYBIND11_TEST_FILES test_smart_ptr.cpp test_stl.cpp test_stl_binders.cpp + test_tagbased_polymorphic.cpp + test_union.cpp test_virtual_functions.cpp ) @@ -68,6 +72,13 @@ if (PYBIND11_TEST_OVERRIDE) set(PYBIND11_TEST_FILES ${PYBIND11_TEST_OVERRIDE}) endif() +# Skip test_async for Python < 3.5 +list(FIND PYBIND11_TEST_FILES test_async.cpp PYBIND11_TEST_FILES_ASYNC_I) +if((PYBIND11_TEST_FILES_ASYNC_I GREATER -1) AND ("${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}" VERSION_LESS 3.5)) + message(STATUS "Skipping test_async because Python version ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} < 3.5") + list(REMOVE_AT PYBIND11_TEST_FILES ${PYBIND11_TEST_FILES_ASYNC_I}) +endif() + string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}") # Contains the set of test files that require pybind11_cross_module_tests to be @@ -80,6 +91,10 @@ set(PYBIND11_CROSS_MODULE_TESTS test_stl_binders.py ) +set(PYBIND11_CROSS_MODULE_GIL_TESTS + test_gil_scoped.py +) + # 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). @@ -89,7 +104,7 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) # Eigen 3.3.1+ exports a cmake 3.0+ target for handling dependency requirements, but also # produces a fatal error if loaded from a pre-3.0 cmake. if (NOT CMAKE_VERSION VERSION_LESS 3.0) - find_package(Eigen3 QUIET CONFIG) + find_package(Eigen3 3.2.7 QUIET CONFIG) if (EIGEN3_FOUND) if (EIGEN3_VERSION_STRING AND NOT EIGEN3_VERSION_STRING VERSION_LESS 3.3.1) set(PYBIND11_EIGEN_VIA_TARGET 1) @@ -99,7 +114,7 @@ if(PYBIND11_TEST_FILES_EIGEN_I GREATER -1) if (NOT EIGEN3_FOUND) # Couldn't load via target, so fall back to allowing module mode finding, which will pick up # tools/FindEigen3.cmake - find_package(Eigen3 QUIET) + find_package(Eigen3 3.2.7 QUIET) endif() if(EIGEN3_FOUND) @@ -123,14 +138,14 @@ find_package(Boost 1.56) function(pybind11_enable_warnings target_name) if(MSVC) target_compile_options(${target_name} PRIVATE /W4) - else() - target_compile_options(${target_name} PRIVATE -Wall -Wextra -Wconversion -Wcast-qual) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang)") + target_compile_options(${target_name} PRIVATE -Wall -Wextra -Wconversion -Wcast-qual -Wdeprecated) endif() if(PYBIND11_WERROR) if(MSVC) target_compile_options(${target_name} PRIVATE /WX) - else() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Intel|Clang)") target_compile_options(${target_name} PRIVATE -Werror) endif() endif() @@ -147,6 +162,14 @@ foreach(t ${PYBIND11_CROSS_MODULE_TESTS}) endif() endforeach() +foreach(t ${PYBIND11_CROSS_MODULE_GIL_TESTS}) + list(FIND PYBIND11_PYTEST_FILES ${t} i) + if (i GREATER -1) + list(APPEND test_targets cross_module_gil_utils) + break() + endif() +endforeach() + set(testdir ${CMAKE_CURRENT_SOURCE_DIR}) foreach(target ${test_targets}) set(test_files ${PYBIND11_TEST_FILES}) diff --git a/ext/pybind11/tests/conftest.py b/ext/pybind11/tests/conftest.py index f4c228260..57f681c66 100644 --- a/ext/pybind11/tests/conftest.py +++ b/ext/pybind11/tests/conftest.py @@ -17,6 +17,11 @@ _unicode_marker = re.compile(r'u(\'[^\']*\')') _long_marker = re.compile(r'([0-9])L') _hexadecimal = re.compile(r'0x[0-9a-fA-F]+') +# test_async.py requires support for async and await +collect_ignore = [] +if sys.version_info[:2] < (3, 5): + collect_ignore.append("test_async.py") + def _strip_and_dedent(s): """For triple-quote strings""" @@ -75,7 +80,7 @@ class Capture(object): self.capfd.readouterr() return self - def __exit__(self, *_): + def __exit__(self, *args): self.out, self.err = self.capfd.readouterr() def __eq__(self, other): @@ -185,7 +190,7 @@ def gc_collect(): gc.collect() -def pytest_namespace(): +def pytest_configure(): """Add import suppression and test requirements to `pytest` namespace""" try: import numpy as np @@ -202,19 +207,17 @@ def pytest_namespace(): pypy = platform.python_implementation() == "PyPy" 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"), - 'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"), - 'unsupported_on_py2': skipif(sys.version_info.major < 3, - reason="unsupported on Python 2.x"), - 'gc_collect': gc_collect - } + pytest.suppress = suppress + pytest.requires_numpy = skipif(not np, reason="numpy is not installed") + pytest.requires_scipy = skipif(not np, reason="scipy is not installed") + pytest.requires_eigen_and_numpy = skipif(not have_eigen or not np, + reason="eigen and/or numpy are not installed") + pytest.requires_eigen_and_scipy = skipif( + not have_eigen or not scipy, reason="eigen and/or scipy are not installed") + pytest.unsupported_on_pypy = skipif(pypy, reason="unsupported on PyPy") + pytest.unsupported_on_py2 = skipif(sys.version_info.major < 3, + reason="unsupported on Python 2.x") + pytest.gc_collect = gc_collect def _test_import_pybind11(): diff --git a/ext/pybind11/tests/constructor_stats.h b/ext/pybind11/tests/constructor_stats.h index babded032..f026e70f9 100644 --- a/ext/pybind11/tests/constructor_stats.h +++ b/ext/pybind11/tests/constructor_stats.h @@ -180,7 +180,7 @@ public: } } } - catch (std::out_of_range) {} + catch (const std::out_of_range &) {} if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); auto &cs1 = get(*t1); // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever diff --git a/ext/pybind11/tests/cross_module_gil_utils.cpp b/ext/pybind11/tests/cross_module_gil_utils.cpp new file mode 100644 index 000000000..07db9f6e4 --- /dev/null +++ b/ext/pybind11/tests/cross_module_gil_utils.cpp @@ -0,0 +1,73 @@ +/* + tests/cross_module_gil_utils.cpp -- tools for acquiring GIL from a different module + + Copyright (c) 2019 Google LLC + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ +#include <pybind11/pybind11.h> +#include <cstdint> + +// This file mimics a DSO that makes pybind11 calls but does not define a +// PYBIND11_MODULE. The purpose is to test that such a DSO can create a +// py::gil_scoped_acquire when the running thread is in a GIL-released state. +// +// Note that we define a Python module here for convenience, but in general +// this need not be the case. The typical scenario would be a DSO that implements +// shared logic used internally by multiple pybind11 modules. + +namespace { + +namespace py = pybind11; +void gil_acquire() { py::gil_scoped_acquire gil; } + +constexpr char kModuleName[] = "cross_module_gil_utils"; + +#if PY_MAJOR_VERSION >= 3 +struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + kModuleName, + NULL, + 0, + NULL, + NULL, + NULL, + NULL, + NULL +}; +#else +PyMethodDef module_methods[] = { + {NULL, NULL, 0, NULL} +}; +#endif + +} // namespace + +extern "C" PYBIND11_EXPORT +#if PY_MAJOR_VERSION >= 3 +PyObject* PyInit_cross_module_gil_utils() +#else +void initcross_module_gil_utils() +#endif +{ + + PyObject* m = +#if PY_MAJOR_VERSION >= 3 + PyModule_Create(&moduledef); +#else + Py_InitModule(kModuleName, module_methods); +#endif + + if (m != NULL) { + static_assert( + sizeof(&gil_acquire) == sizeof(void*), + "Function pointer must have the same size as void*"); + PyModule_AddObject(m, "gil_acquire_funcaddr", + PyLong_FromVoidPtr(reinterpret_cast<void*>(&gil_acquire))); + } + +#if PY_MAJOR_VERSION >= 3 + return m; +#endif +} diff --git a/ext/pybind11/tests/pytest.ini b/ext/pybind11/tests/pytest.ini index 1e44f0a05..f209964a4 100644 --- a/ext/pybind11/tests/pytest.ini +++ b/ext/pybind11/tests/pytest.ini @@ -13,3 +13,4 @@ filterwarnings = ignore::ImportWarning # bogus numpy ABI warning (see numpy/#432) ignore:.*numpy.dtype size changed.*:RuntimeWarning + ignore:.*numpy.ufunc size changed.*:RuntimeWarning diff --git a/ext/pybind11/tests/test_async.cpp b/ext/pybind11/tests/test_async.cpp new file mode 100644 index 000000000..f0ad0d535 --- /dev/null +++ b/ext/pybind11/tests/test_async.cpp @@ -0,0 +1,26 @@ +/* + tests/test_async.cpp -- __await__ support + + Copyright (c) 2019 Google Inc. + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "pybind11_tests.h" + +TEST_SUBMODULE(async_module, m) { + struct DoesNotSupportAsync {}; + py::class_<DoesNotSupportAsync>(m, "DoesNotSupportAsync") + .def(py::init<>()); + struct SupportsAsync {}; + py::class_<SupportsAsync>(m, "SupportsAsync") + .def(py::init<>()) + .def("__await__", [](const SupportsAsync& self) -> py::object { + static_cast<void>(self); + py::object loop = py::module::import("asyncio.events").attr("get_event_loop")(); + py::object f = loop.attr("create_future")(); + f.attr("set_result")(5); + return f.attr("__await__")(); + }); +} diff --git a/ext/pybind11/tests/test_async.py b/ext/pybind11/tests/test_async.py new file mode 100644 index 000000000..e1c959d60 --- /dev/null +++ b/ext/pybind11/tests/test_async.py @@ -0,0 +1,23 @@ +import asyncio +import pytest +from pybind11_tests import async_module as m + + +@pytest.fixture +def event_loop(): + loop = asyncio.new_event_loop() + yield loop + loop.close() + + +async def get_await_result(x): + return await x + + +def test_await(event_loop): + assert 5 == event_loop.run_until_complete(get_await_result(m.SupportsAsync())) + + +def test_await_missing(event_loop): + with pytest.raises(TypeError): + event_loop.run_until_complete(get_await_result(m.DoesNotSupportAsync())) diff --git a/ext/pybind11/tests/test_buffers.cpp b/ext/pybind11/tests/test_buffers.cpp index 5be717730..433dfeee6 100644 --- a/ext/pybind11/tests/test_buffers.cpp +++ b/ext/pybind11/tests/test_buffers.cpp @@ -78,7 +78,7 @@ TEST_SUBMODULE(buffers, m) { py::class_<Matrix>(m, "Matrix", py::buffer_protocol()) .def(py::init<ssize_t, ssize_t>()) /// Construct from a buffer - .def(py::init([](py::buffer b) { + .def(py::init([](py::buffer const 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!"); @@ -107,7 +107,7 @@ TEST_SUBMODULE(buffers, m) { return py::buffer_info( m.data(), /* Pointer to buffer */ { m.rows(), m.cols() }, /* Buffer dimensions */ - { sizeof(float) * size_t(m.rows()), /* Strides (in bytes) for each index */ + { sizeof(float) * size_t(m.cols()), /* Strides (in bytes) for each index */ sizeof(float) } ); }) diff --git a/ext/pybind11/tests/test_buffers.py b/ext/pybind11/tests/test_buffers.py index c348be5dd..f006552bf 100644 --- a/ext/pybind11/tests/test_buffers.py +++ b/ext/pybind11/tests/test_buffers.py @@ -36,17 +36,21 @@ def test_from_python(): # https://bitbucket.org/pypy/pypy/issues/2444 @pytest.unsupported_on_pypy def test_to_python(): - mat = m.Matrix(5, 5) - assert memoryview(mat).shape == (5, 5) + mat = m.Matrix(5, 4) + assert memoryview(mat).shape == (5, 4) assert mat[2, 3] == 0 - mat[2, 3] = 4 + mat[2, 3] = 4.0 + mat[3, 2] = 7.0 assert mat[2, 3] == 4 + assert mat[3, 2] == 7 + assert struct.unpack_from('f', mat, (3 * 4 + 2) * 4) == (7, ) + assert struct.unpack_from('f', mat, (2 * 4 + 3) * 4) == (4, ) mat2 = np.array(mat, copy=False) - assert mat2.shape == (5, 5) - assert abs(mat2).sum() == 4 - assert mat2[2, 3] == 4 + assert mat2.shape == (5, 4) + assert abs(mat2).sum() == 11 + assert mat2[2, 3] == 4 and mat2[3, 2] == 7 mat2[2, 3] = 5 assert mat2[2, 3] == 5 @@ -58,7 +62,7 @@ def test_to_python(): del mat2 # holds a mat reference pytest.gc_collect() assert cstats.alive() == 0 - assert cstats.values() == ["5x5 matrix"] + assert cstats.values() == ["5x4 matrix"] assert cstats.copy_constructions == 0 # assert cstats.move_constructions >= 0 # Don't invoke any assert cstats.copy_assignments == 0 diff --git a/ext/pybind11/tests/test_builtin_casters.cpp b/ext/pybind11/tests/test_builtin_casters.cpp index b73e96ea5..e026127f8 100644 --- a/ext/pybind11/tests/test_builtin_casters.cpp +++ b/ext/pybind11/tests/test_builtin_casters.cpp @@ -50,7 +50,9 @@ TEST_SUBMODULE(builtin_casters, m) { // test_single_char_arguments m.attr("wchar_size") = py::cast(sizeof(wchar_t)); m.def("ord_char", [](char c) -> int { return static_cast<unsigned char>(c); }); + m.def("ord_char_lv", [](char &c) -> int { return static_cast<unsigned char>(c); }); m.def("ord_char16", [](char16_t c) -> uint16_t { return c; }); + m.def("ord_char16_lv", [](char16_t &c) -> uint16_t { return c; }); m.def("ord_char32", [](char32_t c) -> uint32_t { return c; }); m.def("ord_wchar", [](wchar_t c) -> int { return c; }); @@ -153,4 +155,16 @@ TEST_SUBMODULE(builtin_casters, m) { // test_complex m.def("complex_cast", [](float x) { return "{}"_s.format(x); }); m.def("complex_cast", [](std::complex<float> x) { return "({}, {})"_s.format(x.real(), x.imag()); }); + + // test int vs. long (Python 2) + m.def("int_cast", []() {return (int) 42;}); + m.def("long_cast", []() {return (long) 42;}); + m.def("longlong_cast", []() {return ULLONG_MAX;}); + + /// test void* cast operator + m.def("test_void_caster", []() -> bool { + void *v = (void *) 0xabcd; + py::object o = py::cast(v); + return py::cast<void *>(o) == v; + }); } diff --git a/ext/pybind11/tests/test_builtin_casters.py b/ext/pybind11/tests/test_builtin_casters.py index bc094a381..73cc465f5 100644 --- a/ext/pybind11/tests/test_builtin_casters.py +++ b/ext/pybind11/tests/test_builtin_casters.py @@ -44,6 +44,7 @@ def test_single_char_arguments(): toolong_message = "Expected a character, but multi-character string found" assert m.ord_char(u'a') == 0x61 # simple ASCII + assert m.ord_char_lv(u'b') == 0x62 assert m.ord_char(u'é') == 0xE9 # requires 2 bytes in utf-8, but can be stuffed in a char with pytest.raises(ValueError) as excinfo: assert m.ord_char(u'Ā') == 0x100 # requires 2 bytes, doesn't fit in a char @@ -54,9 +55,11 @@ def test_single_char_arguments(): assert m.ord_char16(u'a') == 0x61 assert m.ord_char16(u'é') == 0xE9 + assert m.ord_char16_lv(u'ê') == 0xEA assert m.ord_char16(u'Ā') == 0x100 assert m.ord_char16(u'‽') == 0x203d assert m.ord_char16(u'♥') == 0x2665 + assert m.ord_char16_lv(u'♡') == 0x2661 with pytest.raises(ValueError) as excinfo: assert m.ord_char16(u'🎂') == 0x1F382 # requires surrogate pair assert str(excinfo.value) == toobig_message(0x10000) @@ -320,3 +323,20 @@ def test_numpy_bool(): assert convert(np.bool_(False)) is False assert noconvert(np.bool_(True)) is True assert noconvert(np.bool_(False)) is False + + +def test_int_long(): + """In Python 2, a C++ int should return a Python int rather than long + if possible: longs are not always accepted where ints are used (such + as the argument to sys.exit()). A C++ long long is always a Python + long.""" + + import sys + must_be_long = type(getattr(sys, 'maxint', 1) + 1) + assert isinstance(m.int_cast(), int) + assert isinstance(m.long_cast(), int) + assert isinstance(m.longlong_cast(), must_be_long) + + +def test_void_caster_2(): + assert m.test_void_caster() diff --git a/ext/pybind11/tests/test_call_policies.cpp b/ext/pybind11/tests/test_call_policies.cpp index 8642188f9..fd2455783 100644 --- a/ext/pybind11/tests/test_call_policies.cpp +++ b/ext/pybind11/tests/test_call_policies.cpp @@ -36,6 +36,8 @@ TEST_SUBMODULE(call_policies, m) { class Child { public: Child() { py::print("Allocating child."); } + Child(const Child &) = default; + Child(Child &&) = default; ~Child() { py::print("Releasing child."); } }; py::class_<Child>(m, "Child") diff --git a/ext/pybind11/tests/test_callbacks.cpp b/ext/pybind11/tests/test_callbacks.cpp index 273eacc30..71b88c44c 100644 --- a/ext/pybind11/tests/test_callbacks.cpp +++ b/ext/pybind11/tests/test_callbacks.cpp @@ -10,6 +10,7 @@ #include "pybind11_tests.h" #include "constructor_stats.h" #include <pybind11/functional.h> +#include <thread> int dummy_function(int i) { return i + 1; } @@ -146,4 +147,22 @@ TEST_SUBMODULE(callbacks, m) { py::class_<CppBoundMethodTest>(m, "CppBoundMethodTest") .def(py::init<>()) .def("triple", [](CppBoundMethodTest &, int val) { return 3 * val; }); + + // test async Python callbacks + using callback_f = std::function<void(int)>; + m.def("test_async_callback", [](callback_f f, py::list work) { + // make detached thread that calls `f` with piece of work after a little delay + auto start_f = [f](int j) { + auto invoke_f = [f, j] { + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + f(j); + }; + auto t = std::thread(std::move(invoke_f)); + t.detach(); + }; + + // spawn worker threads + for (auto i : work) + start_f(py::cast<int>(i)); + }); } diff --git a/ext/pybind11/tests/test_callbacks.py b/ext/pybind11/tests/test_callbacks.py index 93c42c22b..6439c8e72 100644 --- a/ext/pybind11/tests/test_callbacks.py +++ b/ext/pybind11/tests/test_callbacks.py @@ -1,5 +1,6 @@ import pytest from pybind11_tests import callbacks as m +from threading import Thread def test_callbacks(): @@ -105,3 +106,31 @@ def test_function_signatures(doc): def test_movable_object(): assert m.callback_with_movable(lambda _: None) is True + + +def test_async_callbacks(): + # serves as state for async callback + class Item: + def __init__(self, value): + self.value = value + + res = [] + + # generate stateful lambda that will store result in `res` + def gen_f(): + s = Item(3) + return lambda j: res.append(s.value + j) + + # do some work async + work = [1, 2, 3, 4] + m.test_async_callback(gen_f(), work) + # wait until work is done + from time import sleep + sleep(0.5) + assert sum(res) == sum([x + 3 for x in work]) + + +def test_async_async_callbacks(): + t = Thread(target=test_async_callbacks) + t.start() + t.join() diff --git a/ext/pybind11/tests/test_chrono.cpp b/ext/pybind11/tests/test_chrono.cpp index 195a93bba..899d08d8d 100644 --- a/ext/pybind11/tests/test_chrono.cpp +++ b/ext/pybind11/tests/test_chrono.cpp @@ -14,6 +14,10 @@ TEST_SUBMODULE(chrono, m) { using system_time = std::chrono::system_clock::time_point; using steady_time = std::chrono::steady_clock::time_point; + + using timespan = std::chrono::duration<int64_t, std::nano>; + using timestamp = std::chrono::time_point<std::chrono::system_clock, timespan>; + // test_chrono_system_clock // Return the current time off the wall clock m.def("test_chrono1", []() { return std::chrono::system_clock::now(); }); @@ -44,4 +48,8 @@ TEST_SUBMODULE(chrono, m) { // Float durations (issue #719) m.def("test_chrono_float_diff", [](std::chrono::duration<float> a, std::chrono::duration<float> b) { return a - b; }); + + m.def("test_nano_timepoint", [](timestamp start, timespan delta) -> timestamp { + return start + delta; + }); } diff --git a/ext/pybind11/tests/test_chrono.py b/ext/pybind11/tests/test_chrono.py index 2b75bd191..55c954406 100644 --- a/ext/pybind11/tests/test_chrono.py +++ b/ext/pybind11/tests/test_chrono.py @@ -40,6 +40,62 @@ def test_chrono_system_clock_roundtrip(): assert diff.microseconds == 0 +def test_chrono_system_clock_roundtrip_date(): + date1 = datetime.date.today() + + # Roundtrip the time + datetime2 = m.test_chrono2(date1) + date2 = datetime2.date() + time2 = datetime2.time() + + # The returned value should be a datetime + assert isinstance(datetime2, datetime.datetime) + assert isinstance(date2, datetime.date) + assert isinstance(time2, datetime.time) + + # They should be identical (no information lost on roundtrip) + diff = abs(date1 - date2) + assert diff.days == 0 + assert diff.seconds == 0 + assert diff.microseconds == 0 + + # Year, Month & Day should be the same after the round trip + assert date1.year == date2.year + assert date1.month == date2.month + assert date1.day == date2.day + + # There should be no time information + assert time2.hour == 0 + assert time2.minute == 0 + assert time2.second == 0 + assert time2.microsecond == 0 + + +def test_chrono_system_clock_roundtrip_time(): + time1 = datetime.datetime.today().time() + + # Roundtrip the time + datetime2 = m.test_chrono2(time1) + date2 = datetime2.date() + time2 = datetime2.time() + + # The returned value should be a datetime + assert isinstance(datetime2, datetime.datetime) + assert isinstance(date2, datetime.date) + assert isinstance(time2, datetime.time) + + # Hour, Minute, Second & Microsecond should be the same after the round trip + assert time1.hour == time2.hour + assert time1.minute == time2.minute + assert time1.second == time2.second + assert time1.microsecond == time2.microsecond + + # There should be no date information (i.e. date = python base date) + assert date2.year == 1970 + assert date2.month == 1 + assert date2.day == 1 + + def test_chrono_duration_roundtrip(): # Get the difference between two times (a timedelta) @@ -70,6 +126,19 @@ def test_chrono_duration_subtraction_equivalence(): assert cpp_diff.microseconds == diff.microseconds +def test_chrono_duration_subtraction_equivalence_date(): + + date1 = datetime.date.today() + date2 = datetime.date.today() + + diff = date2 - date1 + cpp_diff = m.test_chrono4(date2, date1) + + assert cpp_diff.days == diff.days + assert cpp_diff.seconds == diff.seconds + assert cpp_diff.microseconds == diff.microseconds + + def test_chrono_steady_clock(): time1 = m.test_chrono5() assert isinstance(time1, datetime.timedelta) @@ -99,3 +168,9 @@ def test_floating_point_duration(): diff = m.test_chrono_float_diff(43.789012, 1.123456) assert diff.seconds == 42 assert 665556 <= diff.microseconds <= 665557 + + +def test_nano_timepoint(): + time = datetime.datetime.now() + time1 = m.test_nano_timepoint(time, datetime.timedelta(seconds=60)) + assert(time1 == time + datetime.timedelta(seconds=60)) diff --git a/ext/pybind11/tests/test_class.cpp b/ext/pybind11/tests/test_class.cpp index 222190617..499d0cc51 100644 --- a/ext/pybind11/tests/test_class.cpp +++ b/ext/pybind11/tests/test_class.cpp @@ -10,10 +10,27 @@ #include "pybind11_tests.h" #include "constructor_stats.h" #include "local_bindings.h" +#include <pybind11/stl.h> + +#if defined(_MSC_VER) +# pragma warning(disable: 4324) // warning C4324: structure was padded due to alignment specifier +#endif + +// test_brace_initialization +struct NoBraceInitialization { + NoBraceInitialization(std::vector<int> v) : vec{std::move(v)} {} + template <typename T> + NoBraceInitialization(std::initializer_list<T> l) : vec(l) {} + + std::vector<int> vec; +}; TEST_SUBMODULE(class_, m) { // test_instance struct NoConstructor { + NoConstructor() = default; + NoConstructor(const NoConstructor &) = default; + NoConstructor(NoConstructor &&) = default; static NoConstructor *new_instance() { auto *ptr = new NoConstructor(); print_created(ptr, "via new_instance"); @@ -82,7 +99,12 @@ TEST_SUBMODULE(class_, m) { m.def("dog_bark", [](const Dog &dog) { return dog.bark(); }); // test_automatic_upcasting - struct BaseClass { virtual ~BaseClass() {} }; + struct BaseClass { + BaseClass() = default; + BaseClass(const BaseClass &) = default; + BaseClass(BaseClass &&) = default; + virtual ~BaseClass() {} + }; struct DerivedClass1 : BaseClass { }; struct DerivedClass2 : BaseClass { }; @@ -291,6 +313,12 @@ TEST_SUBMODULE(class_, m) { .def(py::init<int, const std::string &>()) .def_readwrite("field1", &BraceInitialization::field1) .def_readwrite("field2", &BraceInitialization::field2); + // We *don't* want to construct using braces when the given constructor argument maps to a + // constructor, because brace initialization could go to the wrong place (in particular when + // there is also an `initializer_list<T>`-accept constructor): + py::class_<NoBraceInitialization>(m, "NoBraceInitialization") + .def(py::init<std::vector<int>>()) + .def_readonly("vec", &NoBraceInitialization::vec); // test_reentrant_implicit_conversion_failure // #1035: issue with runaway reentrant implicit conversion @@ -302,6 +330,43 @@ TEST_SUBMODULE(class_, m) { .def(py::init<const BogusImplicitConversion &>()); py::implicitly_convertible<int, BogusImplicitConversion>(); + + // test_qualname + // #1166: nested class docstring doesn't show nested name + // Also related: tests that __qualname__ is set properly + struct NestBase {}; + struct Nested {}; + py::class_<NestBase> base(m, "NestBase"); + base.def(py::init<>()); + py::class_<Nested>(base, "Nested") + .def(py::init<>()) + .def("fn", [](Nested &, int, NestBase &, Nested &) {}) + .def("fa", [](Nested &, int, NestBase &, Nested &) {}, + "a"_a, "b"_a, "c"_a); + base.def("g", [](NestBase &, Nested &) {}); + base.def("h", []() { return NestBase(); }); + + // test_error_after_conversion + // The second-pass path through dispatcher() previously didn't + // remember which overload was used, and would crash trying to + // generate a useful error message + + struct NotRegistered {}; + struct StringWrapper { std::string str; }; + m.def("test_error_after_conversions", [](int) {}); + m.def("test_error_after_conversions", + [](StringWrapper) -> NotRegistered { return {}; }); + py::class_<StringWrapper>(m, "StringWrapper").def(py::init<std::string>()); + py::implicitly_convertible<std::string, StringWrapper>(); + + #if defined(PYBIND11_CPP17) + struct alignas(1024) Aligned { + std::uintptr_t ptr() const { return (uintptr_t) this; } + }; + py::class_<Aligned>(m, "Aligned") + .def(py::init<>()) + .def("ptr", &Aligned::ptr); + #endif } template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; }; diff --git a/ext/pybind11/tests/test_class.py b/ext/pybind11/tests/test_class.py index 412d6798e..ed63ca853 100644 --- a/ext/pybind11/tests/test_class.py +++ b/ext/pybind11/tests/test_class.py @@ -44,6 +44,31 @@ def test_docstrings(doc): """ +def test_qualname(doc): + """Tests that a properly qualified name is set in __qualname__ (even in pre-3.3, where we + backport the attribute) and that generated docstrings properly use it and the module name""" + assert m.NestBase.__qualname__ == "NestBase" + assert m.NestBase.Nested.__qualname__ == "NestBase.Nested" + + assert doc(m.NestBase.__init__) == """ + __init__(self: m.class_.NestBase) -> None + """ + assert doc(m.NestBase.g) == """ + g(self: m.class_.NestBase, arg0: m.class_.NestBase.Nested) -> None + """ + assert doc(m.NestBase.Nested.__init__) == """ + __init__(self: m.class_.NestBase.Nested) -> None + """ + assert doc(m.NestBase.Nested.fn) == """ + fn(self: m.class_.NestBase.Nested, arg0: int, arg1: m.class_.NestBase, arg2: m.class_.NestBase.Nested) -> None + """ # noqa: E501 line too long + assert doc(m.NestBase.Nested.fa) == """ + fa(self: m.class_.NestBase.Nested, a: int, b: m.class_.NestBase, c: m.class_.NestBase.Nested) -> None + """ # noqa: E501 line too long + assert m.NestBase.__module__ == "pybind11_tests.class_" + assert m.NestBase.Nested.__module__ == "pybind11_tests.class_" + + def test_inheritance(msg): roger = m.Rabbit('Rabbit') assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot" @@ -203,6 +228,12 @@ def test_brace_initialization(): assert a.field1 == 123 assert a.field2 == "test" + # Tests that a non-simple class doesn't get brace initialization (if the + # class defines an initializer_list constructor, in particular, it would + # win over the expected constructor). + b = m.NoBraceInitialization([123, 456]) + assert b.vec == [123, 456] + @pytest.unsupported_on_pypy def test_class_refcount(): @@ -229,7 +260,22 @@ def test_reentrant_implicit_conversion_failure(msg): # ensure that there is no runaway reentrant implicit conversion (#1035) with pytest.raises(TypeError) as excinfo: m.BogusImplicitConversion(0) - assert msg(excinfo.value) == '''__init__(): incompatible constructor arguments. The following argument types are supported: - 1. m.class_.BogusImplicitConversion(arg0: m.class_.BogusImplicitConversion) + assert msg(excinfo.value) == ''' + __init__(): incompatible constructor arguments. The following argument types are supported: + 1. m.class_.BogusImplicitConversion(arg0: m.class_.BogusImplicitConversion) + + Invoked with: 0 + ''' + + +def test_error_after_conversions(): + with pytest.raises(TypeError) as exc_info: + m.test_error_after_conversions("hello") + assert str(exc_info.value).startswith( + "Unable to convert function return value to a Python type!") + -Invoked with: 0''' +def test_aligned(): + if hasattr(m, "Aligned"): + p = m.Aligned().ptr() + assert p % 1024 == 0 diff --git a/ext/pybind11/tests/test_constants_and_functions.cpp b/ext/pybind11/tests/test_constants_and_functions.cpp index 8c9ef7f67..e8ec74b7b 100644 --- a/ext/pybind11/tests/test_constants_and_functions.cpp +++ b/ext/pybind11/tests/test_constants_and_functions.cpp @@ -49,7 +49,14 @@ namespace test_exc_sp { int f1(int x) noexcept { return x+1; } int f2(int x) noexcept(true) { return x+2; } int f3(int x) noexcept(false) { return x+3; } +#if defined(__GNUG__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated" +#endif int f4(int x) throw() { return x+4; } // Deprecated equivalent to noexcept(true) +#if defined(__GNUG__) +# pragma GCC diagnostic pop +#endif struct C { int m1(int x) noexcept { return x-1; } int m2(int x) const noexcept { return x-2; } @@ -57,8 +64,15 @@ struct C { int m4(int x) const noexcept(true) { return x-4; } int m5(int x) noexcept(false) { return x-5; } int m6(int x) const noexcept(false) { return x-6; } +#if defined(__GNUG__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wdeprecated" +#endif int m7(int x) throw() { return x-7; } int m8(int x) const throw() { return x-8; } +#if defined(__GNUG__) +# pragma GCC diagnostic pop +#endif }; } diff --git a/ext/pybind11/tests/test_copy_move.cpp b/ext/pybind11/tests/test_copy_move.cpp index 94113e3af..98d5e0a0b 100644 --- a/ext/pybind11/tests/test_copy_move.cpp +++ b/ext/pybind11/tests/test_copy_move.cpp @@ -86,7 +86,7 @@ template <> struct type_caster<CopyOnlyInt> { protected: CopyOnlyInt value; public: - static PYBIND11_DESCR name() { return _("CopyOnlyInt"); } + static constexpr auto name = _("CopyOnlyInt"); bool load(handle src, bool) { value = CopyOnlyInt(src.cast<int>()); return true; } static handle cast(const CopyOnlyInt &m, return_value_policy r, handle p) { return pybind11::cast(m.value, r, p); } static handle cast(const CopyOnlyInt *src, return_value_policy policy, handle parent) { diff --git a/ext/pybind11/tests/test_eigen.cpp b/ext/pybind11/tests/test_eigen.cpp index 17b156ce4..aba088d72 100644 --- a/ext/pybind11/tests/test_eigen.cpp +++ b/ext/pybind11/tests/test_eigen.cpp @@ -11,6 +11,11 @@ #include "constructor_stats.h" #include <pybind11/eigen.h> #include <pybind11/stl.h> + +#if defined(_MSC_VER) +# pragma warning(disable: 4996) // C4996: std::unary_negation is deprecated +#endif + #include <Eigen/Cholesky> using MatrixXdR = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>; @@ -119,7 +124,7 @@ TEST_SUBMODULE(eigen, m) { // This one accepts a matrix of any stride: m.def("add_any", [](py::EigenDRef<Eigen::MatrixXd> x, int r, int c, double v) { x(r,c) += v; }); - // Return mutable references (numpy maps into eigen varibles) + // Return mutable references (numpy maps into eigen variables) m.def("get_cm_ref", []() { return Eigen::Ref<Eigen::MatrixXd>(get_cm()); }); m.def("get_rm_ref", []() { return Eigen::Ref<MatrixXdR>(get_rm()); }); // The same references, but non-mutable (numpy maps into eigen variables, but is !writeable) @@ -288,6 +293,13 @@ TEST_SUBMODULE(eigen, m) { m.def("iss738_f1", &adjust_matrix<const Eigen::Ref<const Eigen::MatrixXd> &>, py::arg().noconvert()); m.def("iss738_f2", &adjust_matrix<const Eigen::Ref<const Eigen::Matrix<double, -1, -1, Eigen::RowMajor>> &>, py::arg().noconvert()); + // test_issue1105 + // Issue #1105: when converting from a numpy two-dimensional (Nx1) or (1xN) value into a dense + // eigen Vector or RowVector, the argument would fail to load because the numpy copy would fail: + // numpy won't broadcast a Nx1 into a 1-dimensional vector. + m.def("iss1105_col", [](Eigen::VectorXd) { return true; }); + m.def("iss1105_row", [](Eigen::RowVectorXd) { return true; }); + // test_named_arguments // Make sure named arguments are working properly: m.def("matrix_multiply", [](const py::EigenDRef<const Eigen::MatrixXd> A, const py::EigenDRef<const Eigen::MatrixXd> B) diff --git a/ext/pybind11/tests/test_eigen.py b/ext/pybind11/tests/test_eigen.py index 4ac8cbf5d..55d935173 100644 --- a/ext/pybind11/tests/test_eigen.py +++ b/ext/pybind11/tests/test_eigen.py @@ -19,7 +19,7 @@ def assert_equal_ref(mat): def assert_sparse_equal_ref(sparse_mat): - assert_equal_ref(sparse_mat.todense()) + assert_equal_ref(sparse_mat.toarray()) def test_fixed(): @@ -181,8 +181,7 @@ def test_negative_stride_from_python(msg): double_threer(): incompatible function arguments. The following argument types are supported: 1. (arg0: numpy.ndarray[float32[1, 3], flags.writeable]) -> None - Invoked with: array([ 5., 4., 3.], dtype=float32) - """ # noqa: E501 line too long + Invoked with: """ + repr(np.array([ 5., 4., 3.], dtype='float32')) # noqa: E501 line too long with pytest.raises(TypeError) as excinfo: m.double_threec(second_col) @@ -190,8 +189,7 @@ def test_negative_stride_from_python(msg): double_threec(): incompatible function arguments. The following argument types are supported: 1. (arg0: numpy.ndarray[float32[3, 1], flags.writeable]) -> None - Invoked with: array([ 7., 4., 1.], dtype=float32) - """ # noqa: E501 line too long + Invoked with: """ + repr(np.array([ 7., 4., 1.], dtype='float32')) # noqa: E501 line too long def test_nonunit_stride_to_python(): @@ -672,6 +670,21 @@ def test_issue738(): assert np.all(m.iss738_f2(np.array([[1.], [2], [3]])) == np.array([[1.], [12], [23]])) +def test_issue1105(): + """Issue 1105: 1xN or Nx1 input arrays weren't accepted for eigen + compile-time row vectors or column vector""" + assert m.iss1105_row(np.ones((1, 7))) + assert m.iss1105_col(np.ones((7, 1))) + + # These should still fail (incompatible dimensions): + with pytest.raises(TypeError) as excinfo: + m.iss1105_row(np.ones((7, 1))) + assert "incompatible function arguments" in str(excinfo.value) + with pytest.raises(TypeError) as excinfo: + m.iss1105_col(np.ones((1, 7))) + assert "incompatible function arguments" in str(excinfo.value) + + def test_custom_operator_new(): """Using Eigen types as member variables requires a class-specific operator new with proper alignment""" diff --git a/ext/pybind11/tests/test_embed/CMakeLists.txt b/ext/pybind11/tests/test_embed/CMakeLists.txt index 0a43e0e22..8b4f1f843 100644 --- a/ext/pybind11/tests/test_embed/CMakeLists.txt +++ b/ext/pybind11/tests/test_embed/CMakeLists.txt @@ -5,7 +5,9 @@ if(${PYTHON_MODULE_EXTENSION} MATCHES "pypy") endif() find_package(Catch 1.9.3) -if(NOT CATCH_FOUND) +if(CATCH_FOUND) + message(STATUS "Building interpreter tests using Catch v${CATCH_VERSION}") +else() message(STATUS "Catch not detected. Interpreter tests will be skipped. Install Catch headers" " manually or use `cmake -DDOWNLOAD_CATCH=1` to fetch them automatically.") return() @@ -31,4 +33,9 @@ target_link_libraries(test_embed PUBLIC ${CMAKE_THREAD_LIBS_INIT}) add_custom_target(cpptest COMMAND $<TARGET_FILE:test_embed> WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) + +pybind11_add_module(external_module THIN_LTO external_module.cpp) +set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) +add_dependencies(cpptest external_module) + add_dependencies(check cpptest) diff --git a/ext/pybind11/tests/test_embed/catch.cpp b/ext/pybind11/tests/test_embed/catch.cpp index cface485d..dd137385c 100644 --- a/ext/pybind11/tests/test_embed/catch.cpp +++ b/ext/pybind11/tests/test_embed/catch.cpp @@ -3,12 +3,18 @@ #include <pybind11/embed.h> +#ifdef _MSC_VER +// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to catch +// 2.0.1; this should be fixed in the next catch release after 2.0.1). +# pragma warning(disable: 4996) +#endif + #define CATCH_CONFIG_RUNNER #include <catch.hpp> namespace py = pybind11; -int main(int argc, const char *argv[]) { +int main(int argc, char *argv[]) { py::scoped_interpreter guard{}; auto result = Catch::Session().run(argc, argv); diff --git a/ext/pybind11/tests/test_embed/external_module.cpp b/ext/pybind11/tests/test_embed/external_module.cpp new file mode 100644 index 000000000..e9a6058b1 --- /dev/null +++ b/ext/pybind11/tests/test_embed/external_module.cpp @@ -0,0 +1,23 @@ +#include <pybind11/pybind11.h> + +namespace py = pybind11; + +/* Simple test module/test class to check that the referenced internals data of external pybind11 + * modules aren't preserved over a finalize/initialize. + */ + +PYBIND11_MODULE(external_module, m) { + class A { + public: + A(int value) : v{value} {}; + int v; + }; + + py::class_<A>(m, "A") + .def(py::init<int>()) + .def_readwrite("value", &A::v); + + m.def("internals_at", []() { + return reinterpret_cast<uintptr_t>(&py::detail::get_internals()); + }); +} diff --git a/ext/pybind11/tests/test_embed/test_interpreter.cpp b/ext/pybind11/tests/test_embed/test_interpreter.cpp index 6b5f051f2..222bd565f 100644 --- a/ext/pybind11/tests/test_embed/test_interpreter.cpp +++ b/ext/pybind11/tests/test_embed/test_interpreter.cpp @@ -1,4 +1,11 @@ #include <pybind11/embed.h> + +#ifdef _MSC_VER +// Silence MSVC C++17 deprecation warning from Catch regarding std::uncaught_exceptions (up to catch +// 2.0.1; this should be fixed in the next catch release after 2.0.1). +# pragma warning(disable: 4996) +#endif + #include <catch.hpp> #include <thread> @@ -94,7 +101,8 @@ bool has_pybind11_internals_builtin() { }; bool has_pybind11_internals_static() { - return py::detail::get_internals_ptr() != nullptr; + auto **&ipp = py::detail::get_internals_pp(); + return ipp && *ipp; } TEST_CASE("Restart the interpreter") { @@ -102,6 +110,11 @@ TEST_CASE("Restart the interpreter") { REQUIRE(py::module::import("widget_module").attr("add")(1, 2).cast<int>() == 3); REQUIRE(has_pybind11_internals_builtin()); REQUIRE(has_pybind11_internals_static()); + REQUIRE(py::module::import("external_module").attr("A")(123).attr("value").cast<int>() == 123); + + // local and foreign module internals should point to the same internals: + REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp()) == + py::module::import("external_module").attr("internals_at")().cast<uintptr_t>()); // Restart the interpreter. py::finalize_interpreter(); @@ -116,6 +129,8 @@ TEST_CASE("Restart the interpreter") { pybind11::detail::get_internals(); REQUIRE(has_pybind11_internals_builtin()); REQUIRE(has_pybind11_internals_static()); + REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp()) == + py::module::import("external_module").attr("internals_at")().cast<uintptr_t>()); // Make sure that an interpreter with no get_internals() created until finalize still gets the // internals destroyed diff --git a/ext/pybind11/tests/test_enum.cpp b/ext/pybind11/tests/test_enum.cpp index 49f31ba1f..315308920 100644 --- a/ext/pybind11/tests/test_enum.cpp +++ b/ext/pybind11/tests/test_enum.cpp @@ -13,11 +13,13 @@ TEST_SUBMODULE(enums, m) { // test_unscoped_enum enum UnscopedEnum { EOne = 1, - ETwo + ETwo, + EThree }; - py::enum_<UnscopedEnum>(m, "UnscopedEnum", py::arithmetic()) - .value("EOne", EOne) - .value("ETwo", ETwo) + py::enum_<UnscopedEnum>(m, "UnscopedEnum", py::arithmetic(), "An unscoped enumeration") + .value("EOne", EOne, "Docstring for EOne") + .value("ETwo", ETwo, "Docstring for ETwo") + .value("EThree", EThree, "Docstring for EThree") .export_values(); // test_scoped_enum @@ -68,4 +70,18 @@ TEST_SUBMODULE(enums, m) { m.def("test_enum_to_int", [](int) { }); m.def("test_enum_to_uint", [](uint32_t) { }); m.def("test_enum_to_long_long", [](long long) { }); + + // test_duplicate_enum_name + enum SimpleEnum + { + ONE, TWO, THREE + }; + + m.def("register_bad_enum", [m]() { + py::enum_<SimpleEnum>(m, "SimpleEnum") + .value("ONE", SimpleEnum::ONE) //NOTE: all value function calls are called with the same first parameter value + .value("ONE", SimpleEnum::TWO) + .value("ONE", SimpleEnum::THREE) + .export_values(); + }); } diff --git a/ext/pybind11/tests/test_enum.py b/ext/pybind11/tests/test_enum.py index d8eff5278..7fe9b618d 100644 --- a/ext/pybind11/tests/test_enum.py +++ b/ext/pybind11/tests/test_enum.py @@ -6,9 +6,22 @@ def test_unscoped_enum(): assert str(m.UnscopedEnum.EOne) == "UnscopedEnum.EOne" assert str(m.UnscopedEnum.ETwo) == "UnscopedEnum.ETwo" assert str(m.EOne) == "UnscopedEnum.EOne" + + # name property + assert m.UnscopedEnum.EOne.name == "EOne" + assert m.UnscopedEnum.ETwo.name == "ETwo" + assert m.EOne.name == "EOne" + # name readonly + with pytest.raises(AttributeError): + m.UnscopedEnum.EOne.name = "" + # name returns a copy + foo = m.UnscopedEnum.EOne.name + foo = "bar" + assert m.UnscopedEnum.EOne.name == "EOne" + # __members__ property assert m.UnscopedEnum.__members__ == \ - {"EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo} + {"EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo, "EThree": m.UnscopedEnum.EThree} # __members__ readonly with pytest.raises(AttributeError): m.UnscopedEnum.__members__ = {} @@ -16,12 +29,57 @@ def test_unscoped_enum(): foo = m.UnscopedEnum.__members__ foo["bar"] = "baz" assert m.UnscopedEnum.__members__ == \ - {"EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo} + {"EOne": m.UnscopedEnum.EOne, "ETwo": m.UnscopedEnum.ETwo, "EThree": m.UnscopedEnum.EThree} + + for docstring_line in '''An unscoped enumeration + +Members: + + EOne : Docstring for EOne + + ETwo : Docstring for ETwo - # no TypeError exception for unscoped enum ==/!= int comparisons + EThree : Docstring for EThree'''.split('\n'): + assert docstring_line in m.UnscopedEnum.__doc__ + + # Unscoped enums will accept ==/!= int comparisons y = m.UnscopedEnum.ETwo assert y == 2 + assert 2 == y assert y != 3 + assert 3 != y + # Compare with None + assert (y != None) # noqa: E711 + assert not (y == None) # noqa: E711 + # Compare with an object + assert (y != object()) + assert not (y == object()) + # Compare with string + assert y != "2" + assert "2" != y + assert not ("2" == y) + assert not (y == "2") + + with pytest.raises(TypeError): + y < object() + + with pytest.raises(TypeError): + y <= object() + + with pytest.raises(TypeError): + y > object() + + with pytest.raises(TypeError): + y >= object() + + with pytest.raises(TypeError): + y | object() + + with pytest.raises(TypeError): + y & object() + + with pytest.raises(TypeError): + y ^ object() assert int(m.UnscopedEnum.ETwo) == 2 assert str(m.UnscopedEnum(2)) == "UnscopedEnum.ETwo" @@ -40,17 +98,37 @@ def test_unscoped_enum(): assert not (m.UnscopedEnum.ETwo < m.UnscopedEnum.EOne) assert not (2 < m.UnscopedEnum.EOne) + # arithmetic + assert m.UnscopedEnum.EOne & m.UnscopedEnum.EThree == m.UnscopedEnum.EOne + assert m.UnscopedEnum.EOne | m.UnscopedEnum.ETwo == m.UnscopedEnum.EThree + assert m.UnscopedEnum.EOne ^ m.UnscopedEnum.EThree == m.UnscopedEnum.ETwo + def test_scoped_enum(): assert m.test_scoped_enum(m.ScopedEnum.Three) == "ScopedEnum::Three" z = m.ScopedEnum.Two assert m.test_scoped_enum(z) == "ScopedEnum::Two" - # expected TypeError exceptions for scoped enum ==/!= int comparisons + # Scoped enums will *NOT* accept ==/!= int comparisons (Will always return False) + assert not z == 3 + assert not 3 == z + assert z != 3 + assert 3 != z + # Compare with None + assert (z != None) # noqa: E711 + assert not (z == None) # noqa: E711 + # Compare with an object + assert (z != object()) + assert not (z == object()) + # Scoped enums will *NOT* accept >, <, >= and <= int comparisons (Will throw exceptions) + with pytest.raises(TypeError): + z > 3 with pytest.raises(TypeError): - assert z == 2 + z < 3 with pytest.raises(TypeError): - assert z != 3 + z >= 3 + with pytest.raises(TypeError): + z <= 3 # order assert m.ScopedEnum.Two < m.ScopedEnum.Three @@ -100,6 +178,7 @@ def test_binary_operators(): assert int(m.Flags.Read | m.Flags.Execute) == 5 assert int(m.Flags.Write | m.Flags.Execute) == 3 assert int(m.Flags.Write | 1) == 3 + assert ~m.Flags.Write == -3 state = m.Flags.Read | m.Flags.Write assert (state & m.Flags.Read) != 0 @@ -119,3 +198,9 @@ def test_enum_to_int(): m.test_enum_to_uint(m.ClassWithUnscopedEnum.EMode.EFirstMode) m.test_enum_to_long_long(m.Flags.Read) m.test_enum_to_long_long(m.ClassWithUnscopedEnum.EMode.EFirstMode) + + +def test_duplicate_enum_name(): + with pytest.raises(ValueError) as excinfo: + m.register_bad_enum() + assert str(excinfo.value) == 'SimpleEnum: element "ONE" already exists!' diff --git a/ext/pybind11/tests/test_exceptions.cpp b/ext/pybind11/tests/test_exceptions.cpp index ae28abb48..d30139037 100644 --- a/ext/pybind11/tests/test_exceptions.cpp +++ b/ext/pybind11/tests/test_exceptions.cpp @@ -9,7 +9,7 @@ #include "pybind11_tests.h" -// A type that should be raised as an exeption in Python +// A type that should be raised as an exception in Python class MyException : public std::exception { public: explicit MyException(const char * m) : message{m} {} @@ -118,10 +118,38 @@ TEST_SUBMODULE(exceptions, m) { m.def("throws_logic_error", []() { throw std::logic_error("this error should fall through to the standard handler"); }); m.def("exception_matches", []() { py::dict foo; - try { foo["bar"]; } + try { + // Assign to a py::object to force read access of nonexistent dict entry + py::object o = foo["bar"]; + } catch (py::error_already_set& ex) { if (!ex.matches(PyExc_KeyError)) throw; + return true; + } + return false; + }); + m.def("exception_matches_base", []() { + py::dict foo; + try { + // Assign to a py::object to force read access of nonexistent dict entry + py::object o = foo["bar"]; } + catch (py::error_already_set &ex) { + if (!ex.matches(PyExc_Exception)) throw; + return true; + } + return false; + }); + m.def("modulenotfound_exception_matches_base", []() { + try { + // On Python >= 3.6, this raises a ModuleNotFoundError, a subclass of ImportError + py::module::import("nonexistent"); + } + catch (py::error_already_set &ex) { + if (!ex.matches(PyExc_ImportError)) throw; + return true; + } + return false; }); m.def("throw_already_set", [](bool err) { diff --git a/ext/pybind11/tests/test_exceptions.py b/ext/pybind11/tests/test_exceptions.py index 8d37c09b8..6edff9fe4 100644 --- a/ext/pybind11/tests/test_exceptions.py +++ b/ext/pybind11/tests/test_exceptions.py @@ -48,7 +48,9 @@ def test_python_call_in_catch(): def test_exception_matches(): - m.exception_matches() + assert m.exception_matches() + assert m.exception_matches_base() + assert m.modulenotfound_exception_matches_base() def test_custom(msg): diff --git a/ext/pybind11/tests/test_factory_constructors.cpp b/ext/pybind11/tests/test_factory_constructors.cpp index fb33377b2..5cfbfdc3f 100644 --- a/ext/pybind11/tests/test_factory_constructors.cpp +++ b/ext/pybind11/tests/test_factory_constructors.cpp @@ -13,7 +13,7 @@ #include <cmath> // Classes for testing python construction via C++ factory function: -// Not publically constructible, copyable, or movable: +// Not publicly constructible, copyable, or movable: class TestFactory1 { friend class TestFactoryHelper; TestFactory1() : value("(empty)") { print_default_created(this); } @@ -285,6 +285,7 @@ TEST_SUBMODULE(factory_constructors, m) { // test_reallocations // Class that has verbose operator_new/operator_delete calls struct NoisyAlloc { + NoisyAlloc(const NoisyAlloc &) = default; NoisyAlloc(int i) { py::print(py::str("NoisyAlloc(int {})").format(i)); } NoisyAlloc(double d) { py::print(py::str("NoisyAlloc(double {})").format(d)); } ~NoisyAlloc() { py::print("~NoisyAlloc()"); } diff --git a/ext/pybind11/tests/test_gil_scoped.cpp b/ext/pybind11/tests/test_gil_scoped.cpp new file mode 100644 index 000000000..76c17fdc7 --- /dev/null +++ b/ext/pybind11/tests/test_gil_scoped.cpp @@ -0,0 +1,52 @@ +/* + tests/test_gil_scoped.cpp -- acquire and release gil + + Copyright (c) 2017 Borja Zarco (Google LLC) <bzarco@google.com> + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "pybind11_tests.h" +#include <pybind11/functional.h> + + +class VirtClass { +public: + virtual ~VirtClass() {} + virtual void virtual_func() {} + virtual void pure_virtual_func() = 0; +}; + +class PyVirtClass : public VirtClass { + void virtual_func() override { + PYBIND11_OVERLOAD(void, VirtClass, virtual_func,); + } + void pure_virtual_func() override { + PYBIND11_OVERLOAD_PURE(void, VirtClass, pure_virtual_func,); + } +}; + +TEST_SUBMODULE(gil_scoped, m) { + py::class_<VirtClass, PyVirtClass>(m, "VirtClass") + .def(py::init<>()) + .def("virtual_func", &VirtClass::virtual_func) + .def("pure_virtual_func", &VirtClass::pure_virtual_func); + + m.def("test_callback_py_obj", + [](py::object func) { func(); }); + m.def("test_callback_std_func", + [](const std::function<void()> &func) { func(); }); + m.def("test_callback_virtual_func", + [](VirtClass &virt) { virt.virtual_func(); }); + m.def("test_callback_pure_virtual_func", + [](VirtClass &virt) { virt.pure_virtual_func(); }); + m.def("test_cross_module_gil", + []() { + auto cm = py::module::import("cross_module_gil_utils"); + auto gil_acquire = reinterpret_cast<void (*)()>( + PyLong_AsVoidPtr(cm.attr("gil_acquire_funcaddr").ptr())); + py::gil_scoped_release gil_release; + gil_acquire(); + }); +} diff --git a/ext/pybind11/tests/test_gil_scoped.py b/ext/pybind11/tests/test_gil_scoped.py new file mode 100644 index 000000000..1548337cc --- /dev/null +++ b/ext/pybind11/tests/test_gil_scoped.py @@ -0,0 +1,85 @@ +import multiprocessing +import threading +from pybind11_tests import gil_scoped as m + + +def _run_in_process(target, *args, **kwargs): + """Runs target in process and returns its exitcode after 10s (None if still alive).""" + process = multiprocessing.Process(target=target, args=args, kwargs=kwargs) + process.daemon = True + try: + process.start() + # Do not need to wait much, 10s should be more than enough. + process.join(timeout=10) + return process.exitcode + finally: + if process.is_alive(): + process.terminate() + + +def _python_to_cpp_to_python(): + """Calls different C++ functions that come back to Python.""" + class ExtendedVirtClass(m.VirtClass): + def virtual_func(self): + pass + + def pure_virtual_func(self): + pass + + extended = ExtendedVirtClass() + m.test_callback_py_obj(lambda: None) + m.test_callback_std_func(lambda: None) + m.test_callback_virtual_func(extended) + m.test_callback_pure_virtual_func(extended) + + +def _python_to_cpp_to_python_from_threads(num_threads, parallel=False): + """Calls different C++ functions that come back to Python, from Python threads.""" + threads = [] + for _ in range(num_threads): + thread = threading.Thread(target=_python_to_cpp_to_python) + thread.daemon = True + thread.start() + if parallel: + threads.append(thread) + else: + thread.join() + for thread in threads: + thread.join() + + +def test_python_to_cpp_to_python_from_thread(): + """Makes sure there is no GIL deadlock when running in a thread. + + It runs in a separate process to be able to stop and assert if it deadlocks. + """ + assert _run_in_process(_python_to_cpp_to_python_from_threads, 1) == 0 + + +def test_python_to_cpp_to_python_from_thread_multiple_parallel(): + """Makes sure there is no GIL deadlock when running in a thread multiple times in parallel. + + It runs in a separate process to be able to stop and assert if it deadlocks. + """ + assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=True) == 0 + + +def test_python_to_cpp_to_python_from_thread_multiple_sequential(): + """Makes sure there is no GIL deadlock when running in a thread multiple times sequentially. + + It runs in a separate process to be able to stop and assert if it deadlocks. + """ + assert _run_in_process(_python_to_cpp_to_python_from_threads, 8, parallel=False) == 0 + + +def test_python_to_cpp_to_python_from_process(): + """Makes sure there is no GIL deadlock when using processes. + + This test is for completion, but it was never an issue. + """ + assert _run_in_process(_python_to_cpp_to_python) == 0 + + +def test_cross_module_gil(): + """Makes sure that the GIL can be acquired by another module from a GIL-released state.""" + m.test_cross_module_gil() # Should not raise a SIGSEGV diff --git a/ext/pybind11/tests/test_iostream.py b/ext/pybind11/tests/test_iostream.py index 3364849a4..27095b270 100644 --- a/ext/pybind11/tests/test_iostream.py +++ b/ext/pybind11/tests/test_iostream.py @@ -54,6 +54,17 @@ def test_captured(capsys): assert stderr == msg +def test_captured_large_string(capsys): + # Make this bigger than the buffer used on the C++ side: 1024 chars + msg = "I've been redirected to Python, I hope!" + msg = msg * (1024 // len(msg) + 1) + + m.captured_output_default(msg) + stdout, stderr = capsys.readouterr() + assert stdout == msg + assert stderr == '' + + def test_guard_capture(capsys): msg = "I've been redirected to Python, I hope!" m.guard_output(msg) diff --git a/ext/pybind11/tests/test_kwargs_and_defaults.cpp b/ext/pybind11/tests/test_kwargs_and_defaults.cpp index 165f8017e..6563fb9ad 100644 --- a/ext/pybind11/tests/test_kwargs_and_defaults.cpp +++ b/ext/pybind11/tests/test_kwargs_and_defaults.cpp @@ -8,6 +8,7 @@ */ #include "pybind11_tests.h" +#include "constructor_stats.h" #include <pybind11/stl.h> TEST_SUBMODULE(kwargs_and_defaults, m) { @@ -33,7 +34,9 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { m.def("kw_func_udl_z", kw_func, "x"_a, "y"_a=0); // test_args_and_kwargs - m.def("args_function", [](py::args args) -> py::tuple { return args; }); + m.def("args_function", [](py::args args) -> py::tuple { + return std::move(args); + }); m.def("args_kwargs_function", [](py::args args, py::kwargs kwargs) { return py::make_tuple(args, kwargs); }); @@ -53,6 +56,34 @@ TEST_SUBMODULE(kwargs_and_defaults, m) { m.def("mixed_plus_args_kwargs_defaults", mixed_plus_both, py::arg("i") = 1, py::arg("j") = 3.14159); + // test_args_refcount + // PyPy needs a garbage collection to get the reference count values to match CPython's behaviour + #ifdef PYPY_VERSION + #define GC_IF_NEEDED ConstructorStats::gc() + #else + #define GC_IF_NEEDED + #endif + m.def("arg_refcount_h", [](py::handle h) { GC_IF_NEEDED; return h.ref_count(); }); + m.def("arg_refcount_h", [](py::handle h, py::handle, py::handle) { GC_IF_NEEDED; return h.ref_count(); }); + m.def("arg_refcount_o", [](py::object o) { GC_IF_NEEDED; return o.ref_count(); }); + m.def("args_refcount", [](py::args a) { + GC_IF_NEEDED; + py::tuple t(a.size()); + for (size_t i = 0; i < a.size(); i++) + // Use raw Python API here to avoid an extra, intermediate incref on the tuple item: + t[i] = (int) Py_REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast<ssize_t>(i))); + return t; + }); + m.def("mixed_args_refcount", [](py::object o, py::args a) { + GC_IF_NEEDED; + py::tuple t(a.size() + 1); + t[0] = o.ref_count(); + for (size_t i = 0; i < a.size(); i++) + // Use raw Python API here to avoid an extra, intermediate incref on the tuple item: + t[i + 1] = (int) Py_REFCNT(PyTuple_GET_ITEM(a.ptr(), static_cast<ssize_t>(i))); + return t; + }); + // pybind11 won't allow these to be bound: args and kwargs, if present, must be at the end. // Uncomment these to test that the static_assert is indeed working: // m.def("bad_args1", [](py::args, int) {}); diff --git a/ext/pybind11/tests/test_kwargs_and_defaults.py b/ext/pybind11/tests/test_kwargs_and_defaults.py index 733fe8593..27a05a024 100644 --- a/ext/pybind11/tests/test_kwargs_and_defaults.py +++ b/ext/pybind11/tests/test_kwargs_and_defaults.py @@ -5,11 +5,11 @@ from pybind11_tests import kwargs_and_defaults as m def test_function_signatures(doc): assert doc(m.kw_func0) == "kw_func0(arg0: int, arg1: int) -> str" assert doc(m.kw_func1) == "kw_func1(x: int, y: int) -> str" - assert doc(m.kw_func2) == "kw_func2(x: int=100, y: int=200) -> str" - assert doc(m.kw_func3) == "kw_func3(data: str='Hello world!') -> None" - assert doc(m.kw_func4) == "kw_func4(myList: List[int]=[13, 17]) -> str" - assert doc(m.kw_func_udl) == "kw_func_udl(x: int, y: int=300) -> str" - assert doc(m.kw_func_udl_z) == "kw_func_udl_z(x: int, y: int=0) -> str" + assert doc(m.kw_func2) == "kw_func2(x: int = 100, y: int = 200) -> str" + assert doc(m.kw_func3) == "kw_func3(data: str = 'Hello world!') -> None" + assert doc(m.kw_func4) == "kw_func4(myList: List[int] = [13, 17]) -> str" + assert doc(m.kw_func_udl) == "kw_func_udl(x: int, y: int = 300) -> str" + assert doc(m.kw_func_udl_z) == "kw_func_udl_z(x: int, y: int = 0) -> str" assert doc(m.args_function) == "args_function(*args) -> tuple" assert doc(m.args_kwargs_function) == "args_kwargs_function(*args, **kwargs) -> tuple" assert doc(m.KWClass.foo0) == \ @@ -93,7 +93,7 @@ def test_mixed_args_and_kwargs(msg): assert mpakd(1, i=1) assert msg(excinfo.value) == """ mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported: - 1. (i: int=1, j: float=3.14159, *args, **kwargs) -> tuple + 1. (i: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple Invoked with: 1; kwargs: i=1 """ # noqa: E501 line too long @@ -101,7 +101,47 @@ def test_mixed_args_and_kwargs(msg): assert mpakd(1, 2, j=1) assert msg(excinfo.value) == """ mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported: - 1. (i: int=1, j: float=3.14159, *args, **kwargs) -> tuple + 1. (i: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple Invoked with: 1, 2; kwargs: j=1 """ # noqa: E501 line too long + + +def test_args_refcount(): + """Issue/PR #1216 - py::args elements get double-inc_ref()ed when combined with regular + arguments""" + refcount = m.arg_refcount_h + + myval = 54321 + expected = refcount(myval) + assert m.arg_refcount_h(myval) == expected + assert m.arg_refcount_o(myval) == expected + 1 + assert m.arg_refcount_h(myval) == expected + assert refcount(myval) == expected + + assert m.mixed_plus_args(1, 2.0, "a", myval) == (1, 2.0, ("a", myval)) + assert refcount(myval) == expected + + assert m.mixed_plus_kwargs(3, 4.0, a=1, b=myval) == (3, 4.0, {"a": 1, "b": myval}) + assert refcount(myval) == expected + + assert m.args_function(-1, myval) == (-1, myval) + assert refcount(myval) == expected + + assert m.mixed_plus_args_kwargs(5, 6.0, myval, a=myval) == (5, 6.0, (myval,), {"a": myval}) + assert refcount(myval) == expected + + assert m.args_kwargs_function(7, 8, myval, a=1, b=myval) == \ + ((7, 8, myval), {"a": 1, "b": myval}) + assert refcount(myval) == expected + + exp3 = refcount(myval, myval, myval) + assert m.args_refcount(myval, myval, myval) == (exp3, exp3, exp3) + assert refcount(myval) == expected + + # This function takes the first arg as a `py::object` and the rest as a `py::args`. Unlike the + # previous case, when we have both positional and `py::args` we need to construct a new tuple + # for the `py::args`; in the previous case, we could simply inc_ref and pass on Python's input + # tuple without having to inc_ref the individual elements, but here we can't, hence the extra + # refs. + assert m.mixed_args_refcount(myval, myval, myval) == (exp3 + 3, exp3 + 3, exp3 + 3) diff --git a/ext/pybind11/tests/test_local_bindings.py b/ext/pybind11/tests/test_local_bindings.py index b3dc3619c..b380376e2 100644 --- a/ext/pybind11/tests/test_local_bindings.py +++ b/ext/pybind11/tests/test_local_bindings.py @@ -220,7 +220,7 @@ def test_cross_module_calls(): c, d = m.MixGL2(3), cm.MixGL2(4) with pytest.raises(TypeError) as excinfo: m.get_gl_value(c) - assert "incompatible function arguments" in str(excinfo) + assert "incompatible function arguments" in str(excinfo.value) with pytest.raises(TypeError) as excinfo: m.get_gl_value(d) - assert "incompatible function arguments" in str(excinfo) + assert "incompatible function arguments" in str(excinfo.value) diff --git a/ext/pybind11/tests/test_methods_and_attributes.cpp b/ext/pybind11/tests/test_methods_and_attributes.cpp index cd15869f4..c7b82f13d 100644 --- a/ext/pybind11/tests/test_methods_and_attributes.cpp +++ b/ext/pybind11/tests/test_methods_and_attributes.cpp @@ -11,6 +11,11 @@ #include "pybind11_tests.h" #include "constructor_stats.h" +#if !defined(PYBIND11_OVERLOAD_CAST) +template <typename... Args> +using overload_cast_ = pybind11::detail::overload_cast_impl<Args...>; +#endif + class ExampleMandA { public: ExampleMandA() { print_default_created(this); } @@ -242,15 +247,16 @@ TEST_SUBMODULE(methods_and_attributes, m) { .def("overloaded_const", py::overload_cast<int, int>(&ExampleMandA::overloaded, py::const_)) .def("overloaded_const", py::overload_cast<float, float>(&ExampleMandA::overloaded, py::const_)) #else - .def("overloaded", static_cast<py::str (ExampleMandA::*)()>(&ExampleMandA::overloaded)) - .def("overloaded", static_cast<py::str (ExampleMandA::*)(int)>(&ExampleMandA::overloaded)) - .def("overloaded", static_cast<py::str (ExampleMandA::*)(int, float)>(&ExampleMandA::overloaded)) + // Use both the traditional static_cast method and the C++11 compatible overload_cast_ + .def("overloaded", overload_cast_<>()(&ExampleMandA::overloaded)) + .def("overloaded", overload_cast_<int>()(&ExampleMandA::overloaded)) + .def("overloaded", overload_cast_<int, float>()(&ExampleMandA::overloaded)) .def("overloaded", static_cast<py::str (ExampleMandA::*)(float, int)>(&ExampleMandA::overloaded)) .def("overloaded", static_cast<py::str (ExampleMandA::*)(int, int)>(&ExampleMandA::overloaded)) .def("overloaded", static_cast<py::str (ExampleMandA::*)(float, float)>(&ExampleMandA::overloaded)) - .def("overloaded_float", static_cast<py::str (ExampleMandA::*)(float, float)>(&ExampleMandA::overloaded)) - .def("overloaded_const", static_cast<py::str (ExampleMandA::*)(int ) const>(&ExampleMandA::overloaded)) - .def("overloaded_const", static_cast<py::str (ExampleMandA::*)(int, float) const>(&ExampleMandA::overloaded)) + .def("overloaded_float", overload_cast_<float, float>()(&ExampleMandA::overloaded)) + .def("overloaded_const", overload_cast_<int >()(&ExampleMandA::overloaded, py::const_)) + .def("overloaded_const", overload_cast_<int, float>()(&ExampleMandA::overloaded, py::const_)) .def("overloaded_const", static_cast<py::str (ExampleMandA::*)(float, int) const>(&ExampleMandA::overloaded)) .def("overloaded_const", static_cast<py::str (ExampleMandA::*)(int, int) const>(&ExampleMandA::overloaded)) .def("overloaded_const", static_cast<py::str (ExampleMandA::*)(float, float) const>(&ExampleMandA::overloaded)) @@ -279,12 +285,20 @@ TEST_SUBMODULE(methods_and_attributes, m) { .def(py::init<>()) .def_readonly("def_readonly", &TestProperties::value) .def_readwrite("def_readwrite", &TestProperties::value) + .def_property("def_writeonly", nullptr, + [](TestProperties& s,int v) { s.value = v; } ) + .def_property("def_property_writeonly", nullptr, &TestProperties::set) .def_property_readonly("def_property_readonly", &TestProperties::get) .def_property("def_property", &TestProperties::get, &TestProperties::set) + .def_property("def_property_impossible", nullptr, nullptr) .def_readonly_static("def_readonly_static", &TestProperties::static_value) .def_readwrite_static("def_readwrite_static", &TestProperties::static_value) + .def_property_static("def_writeonly_static", nullptr, + [](py::object, int v) { TestProperties::static_value = v; }) .def_property_readonly_static("def_property_readonly_static", [](py::object) { return TestProperties::static_get(); }) + .def_property_static("def_property_writeonly_static", nullptr, + [](py::object, int v) { return TestProperties::static_set(v); }) .def_property_static("def_property_static", [](py::object) { return TestProperties::static_get(); }, [](py::object, int v) { TestProperties::static_set(v); }) diff --git a/ext/pybind11/tests/test_methods_and_attributes.py b/ext/pybind11/tests/test_methods_and_attributes.py index 9fd9cb75c..f1c862be8 100644 --- a/ext/pybind11/tests/test_methods_and_attributes.py +++ b/ext/pybind11/tests/test_methods_and_attributes.py @@ -98,23 +98,52 @@ def test_properties(): instance.def_property = 3 assert instance.def_property == 3 + with pytest.raises(AttributeError) as excinfo: + dummy = instance.def_property_writeonly # noqa: F841 unused var + assert "unreadable attribute" in str(excinfo.value) + + instance.def_property_writeonly = 4 + assert instance.def_property_readonly == 4 + + with pytest.raises(AttributeError) as excinfo: + dummy = instance.def_property_impossible # noqa: F841 unused var + assert "unreadable attribute" in str(excinfo.value) + + with pytest.raises(AttributeError) as excinfo: + instance.def_property_impossible = 5 + assert "can't set attribute" in str(excinfo.value) + def test_static_properties(): assert m.TestProperties.def_readonly_static == 1 with pytest.raises(AttributeError) as excinfo: m.TestProperties.def_readonly_static = 2 - assert "can't set attribute" in str(excinfo) + assert "can't set attribute" in str(excinfo.value) m.TestProperties.def_readwrite_static = 2 assert m.TestProperties.def_readwrite_static == 2 - assert m.TestProperties.def_property_readonly_static == 2 with pytest.raises(AttributeError) as excinfo: - m.TestProperties.def_property_readonly_static = 3 - assert "can't set attribute" in str(excinfo) + dummy = m.TestProperties.def_writeonly_static # noqa: F841 unused var + assert "unreadable attribute" in str(excinfo.value) + + m.TestProperties.def_writeonly_static = 3 + assert m.TestProperties.def_readonly_static == 3 + + assert m.TestProperties.def_property_readonly_static == 3 + with pytest.raises(AttributeError) as excinfo: + m.TestProperties.def_property_readonly_static = 99 + assert "can't set attribute" in str(excinfo.value) + + m.TestProperties.def_property_static = 4 + assert m.TestProperties.def_property_static == 4 + + with pytest.raises(AttributeError) as excinfo: + dummy = m.TestProperties.def_property_writeonly_static + assert "unreadable attribute" in str(excinfo.value) - m.TestProperties.def_property_static = 3 - assert m.TestProperties.def_property_static == 3 + m.TestProperties.def_property_writeonly_static = 5 + assert m.TestProperties.def_property_static == 5 # Static property read and write via instance instance = m.TestProperties() @@ -127,6 +156,13 @@ def test_static_properties(): assert m.TestProperties.def_readwrite_static == 2 assert instance.def_readwrite_static == 2 + with pytest.raises(AttributeError) as excinfo: + dummy = instance.def_property_writeonly_static # noqa: F841 unused var + assert "unreadable attribute" in str(excinfo.value) + + instance.def_property_writeonly_static = 4 + assert instance.def_property_static == 4 + # It should be possible to override properties in derived classes assert m.TestPropertiesOverride().def_readonly == 99 assert m.TestPropertiesOverride.def_readonly_static == 99 diff --git a/ext/pybind11/tests/test_multiple_inheritance.cpp b/ext/pybind11/tests/test_multiple_inheritance.cpp index 35f9d9c4e..ba1674fb2 100644 --- a/ext/pybind11/tests/test_multiple_inheritance.cpp +++ b/ext/pybind11/tests/test_multiple_inheritance.cpp @@ -130,8 +130,8 @@ TEST_SUBMODULE(multiple_inheritance, m) { // test_mi_unaligned_base // test_mi_base_return // Issue #801: invalid casting to derived type with MI bases - struct I801B1 { int a = 1; virtual ~I801B1() = default; }; - struct I801B2 { int b = 2; virtual ~I801B2() = default; }; + struct I801B1 { int a = 1; I801B1() = default; I801B1(const I801B1 &) = default; virtual ~I801B1() = default; }; + struct I801B2 { int b = 2; I801B2() = default; I801B2(const I801B2 &) = default; virtual ~I801B2() = default; }; struct I801C : I801B1, I801B2 {}; struct I801D : I801C {}; // Indirect MI // Unregistered classes: @@ -205,7 +205,7 @@ TEST_SUBMODULE(multiple_inheritance, m) { // test_diamond_inheritance // Issue #959: segfault when constructing diamond inheritance instance // All of these have int members so that there will be various unequal pointers involved. - struct B { int b; virtual ~B() = default; }; + struct B { int b; B() = default; B(const B&) = default; virtual ~B() = default; }; struct C0 : public virtual B { int c0; }; struct C1 : public virtual B { int c1; }; struct D : public C0, public C1 { int d; }; diff --git a/ext/pybind11/tests/test_numpy_array.cpp b/ext/pybind11/tests/test_numpy_array.cpp index 2046c0e03..156a3bfa8 100644 --- a/ext/pybind11/tests/test_numpy_array.cpp +++ b/ext/pybind11/tests/test_numpy_array.cpp @@ -14,6 +14,67 @@ #include <cstdint> +// Size / dtype checks. +struct DtypeCheck { + py::dtype numpy{}; + py::dtype pybind11{}; +}; + +template <typename T> +DtypeCheck get_dtype_check(const char* name) { + py::module np = py::module::import("numpy"); + DtypeCheck check{}; + check.numpy = np.attr("dtype")(np.attr(name)); + check.pybind11 = py::dtype::of<T>(); + return check; +} + +std::vector<DtypeCheck> get_concrete_dtype_checks() { + return { + // Normalization + get_dtype_check<std::int8_t>("int8"), + get_dtype_check<std::uint8_t>("uint8"), + get_dtype_check<std::int16_t>("int16"), + get_dtype_check<std::uint16_t>("uint16"), + get_dtype_check<std::int32_t>("int32"), + get_dtype_check<std::uint32_t>("uint32"), + get_dtype_check<std::int64_t>("int64"), + get_dtype_check<std::uint64_t>("uint64") + }; +} + +struct DtypeSizeCheck { + std::string name{}; + int size_cpp{}; + int size_numpy{}; + // For debugging. + py::dtype dtype{}; +}; + +template <typename T> +DtypeSizeCheck get_dtype_size_check() { + DtypeSizeCheck check{}; + check.name = py::type_id<T>(); + check.size_cpp = sizeof(T); + check.dtype = py::dtype::of<T>(); + check.size_numpy = check.dtype.attr("itemsize").template cast<int>(); + return check; +} + +std::vector<DtypeSizeCheck> get_platform_dtype_size_checks() { + return { + get_dtype_size_check<short>(), + get_dtype_size_check<unsigned short>(), + get_dtype_size_check<int>(), + get_dtype_size_check<unsigned int>(), + get_dtype_size_check<long>(), + get_dtype_size_check<unsigned long>(), + get_dtype_size_check<long long>(), + get_dtype_size_check<unsigned long long>(), + }; +} + +// Arrays. using arr = py::array; using arr_t = py::array_t<uint16_t, 0>; static_assert(std::is_same<arr_t::value_type, uint16_t>::value, ""); @@ -68,10 +129,33 @@ template <typename T, typename T2> py::handle auxiliaries(T &&r, T2 &&r2) { return l.release(); } +// note: declaration at local scope would create a dangling reference! +static int data_i = 42; + TEST_SUBMODULE(numpy_array, sm) { try { py::module::import("numpy"); } catch (...) { return; } + // test_dtypes + py::class_<DtypeCheck>(sm, "DtypeCheck") + .def_readonly("numpy", &DtypeCheck::numpy) + .def_readonly("pybind11", &DtypeCheck::pybind11) + .def("__repr__", [](const DtypeCheck& self) { + return py::str("<DtypeCheck numpy={} pybind11={}>").format( + self.numpy, self.pybind11); + }); + sm.def("get_concrete_dtype_checks", &get_concrete_dtype_checks); + + py::class_<DtypeSizeCheck>(sm, "DtypeSizeCheck") + .def_readonly("name", &DtypeSizeCheck::name) + .def_readonly("size_cpp", &DtypeSizeCheck::size_cpp) + .def_readonly("size_numpy", &DtypeSizeCheck::size_numpy) + .def("__repr__", [](const DtypeSizeCheck& self) { + return py::str("<DtypeSizeCheck name='{}' size_cpp={} size_numpy={} dtype={}>").format( + self.name, self.size_cpp, self.size_numpy, self.dtype); + }); + sm.def("get_platform_dtype_size_checks", &get_platform_dtype_size_checks); + // test_array_attributes sm.def("ndim", [](const arr& a) { return a.ndim(); }); sm.def("shape", [](const arr& a) { return arr(a.ndim(), a.shape()); }); @@ -102,6 +186,11 @@ TEST_SUBMODULE(numpy_array, sm) { sm.def("make_f_array", [] { return py::array_t<float>({ 2, 2 }, { 4, 8 }); }); sm.def("make_c_array", [] { return py::array_t<float>({ 2, 2 }, { 8, 4 }); }); + // test_empty_shaped_array + sm.def("make_empty_shaped_array", [] { return py::array(py::dtype("f"), {}, {}); }); + // test numpy scalars (empty shape, ndim==0) + sm.def("scalar_int", []() { return py::array(py::dtype("i"), {}, {}, &data_i); }); + // test_wrap sm.def("wrap", [](py::array a) { return py::array( @@ -292,4 +381,10 @@ TEST_SUBMODULE(numpy_array, sm) { std::fill(a.mutable_data(), a.mutable_data() + a.size(), 42.); return a; }); + +#if PY_MAJOR_VERSION >= 3 + sm.def("index_using_ellipsis", [](py::array a) { + return a[py::make_tuple(0, py::ellipsis(), 0)]; + }); +#endif } diff --git a/ext/pybind11/tests/test_numpy_array.py b/ext/pybind11/tests/test_numpy_array.py index 27433934f..d0a6324df 100644 --- a/ext/pybind11/tests/test_numpy_array.py +++ b/ext/pybind11/tests/test_numpy_array.py @@ -7,6 +7,21 @@ with pytest.suppress(ImportError): import numpy as np +def test_dtypes(): + # See issue #1328. + # - Platform-dependent sizes. + for size_check in m.get_platform_dtype_size_checks(): + print(size_check) + assert size_check.size_cpp == size_check.size_numpy, size_check + # - Concrete sizes. + for check in m.get_concrete_dtype_checks(): + print(check) + assert check.numpy == check.pybind11, check + if check.numpy.num != check.pybind11.num: + print("NOTE: typenum mismatch for {}: {} != {}".format( + check, check.numpy.num, check.pybind11.num)) + + @pytest.fixture(scope='function') def arr(): return np.array([[1, 2, 3], [4, 5, 6]], '=u2') @@ -135,8 +150,18 @@ def test_make_c_f_array(): assert not m.make_f_array().flags.c_contiguous +def test_make_empty_shaped_array(): + m.make_empty_shaped_array() + + # empty shape means numpy scalar, PEP 3118 + assert m.scalar_int().ndim == 0 + assert m.scalar_int().shape == () + assert m.scalar_int() == 42 + + def test_wrap(): def assert_references(a, b, base=None): + from distutils.version import LooseVersion if base is None: base = a assert a is not b @@ -147,7 +172,10 @@ def test_wrap(): 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 + if LooseVersion(np.__version__) >= LooseVersion("1.14.0"): + assert a.flags.writebackifcopy == b.flags.writebackifcopy + else: + assert a.flags.updateifcopy == b.flags.updateifcopy assert np.all(a == b) assert not b.flags.owndata assert b.base is base @@ -282,17 +310,17 @@ def test_overload_resolution(msg): 1. (arg0: numpy.ndarray[int32]) -> str 2. (arg0: numpy.ndarray[float64]) -> str - Invoked with:""" + Invoked with: """ with pytest.raises(TypeError) as excinfo: m.overloaded3(np.array([1], dtype='uintc')) - assert msg(excinfo.value) == expected_exc + " array([1], dtype=uint32)" + assert msg(excinfo.value) == expected_exc + repr(np.array([1], dtype='uint32')) with pytest.raises(TypeError) as excinfo: m.overloaded3(np.array([1], dtype='float32')) - assert msg(excinfo.value) == expected_exc + " array([ 1.], dtype=float32)" + assert msg(excinfo.value) == expected_exc + repr(np.array([1.], dtype='float32')) with pytest.raises(TypeError) as excinfo: m.overloaded3(np.array([1], dtype='complex')) - assert msg(excinfo.value) == expected_exc + " array([ 1.+0.j])" + assert msg(excinfo.value) == expected_exc + repr(np.array([1. + 0.j])) # Exact matches: assert m.overloaded4(np.array([1], dtype='double')) == 'double' @@ -400,3 +428,20 @@ def test_array_create_and_resize(msg): a = m.create_and_resize(2) assert(a.size == 4) assert(np.all(a == 42.)) + + +@pytest.unsupported_on_py2 +def test_index_using_ellipsis(): + a = m.index_using_ellipsis(np.zeros((5, 6, 7))) + assert a.shape == (6,) + + +@pytest.unsupported_on_pypy +def test_dtype_refcount_leak(): + from sys import getrefcount + dtype = np.dtype(np.float_) + a = np.array([1], dtype=dtype) + before = getrefcount(dtype) + m.ndim(a) + after = getrefcount(dtype) + assert after == before diff --git a/ext/pybind11/tests/test_numpy_dtypes.cpp b/ext/pybind11/tests/test_numpy_dtypes.cpp index ddec851f6..467e0253f 100644 --- a/ext/pybind11/tests/test_numpy_dtypes.cpp +++ b/ext/pybind11/tests/test_numpy_dtypes.cpp @@ -29,6 +29,13 @@ std::ostream& operator<<(std::ostream& os, const SimpleStruct& v) { return os << "s:" << v.bool_ << "," << v.uint_ << "," << v.float_ << "," << v.ldbl_; } +struct SimpleStructReordered { + bool bool_; + float float_; + uint32_t uint_; + long double ldbl_; +}; + PYBIND11_PACKED(struct PackedStruct { bool bool_; uint32_t uint_; @@ -244,6 +251,9 @@ py::list test_dtype_ctors() { return list; } +struct A {}; +struct B {}; + TEST_SUBMODULE(numpy_dtypes, m) { try { py::module::import("numpy"); } catch (...) { return; } @@ -252,6 +262,7 @@ TEST_SUBMODULE(numpy_dtypes, m) { py::class_<SimpleStruct>(m, "SimpleStruct"); PYBIND11_NUMPY_DTYPE(SimpleStruct, bool_, uint_, float_, ldbl_); + PYBIND11_NUMPY_DTYPE(SimpleStructReordered, bool_, uint_, float_, ldbl_); PYBIND11_NUMPY_DTYPE(PackedStruct, bool_, uint_, float_, ldbl_); PYBIND11_NUMPY_DTYPE(NestedStruct, a, b); PYBIND11_NUMPY_DTYPE(PartialStruct, bool_, uint_, float_, ldbl_); @@ -271,6 +282,15 @@ TEST_SUBMODULE(numpy_dtypes, m) { // struct NotPOD { std::string v; NotPOD() : v("hi") {}; }; // PYBIND11_NUMPY_DTYPE(NotPOD, v); + // Check that dtypes can be registered programmatically, both from + // initializer lists of field descriptors and from other containers. + py::detail::npy_format_descriptor<A>::register_dtype( + {} + ); + py::detail::npy_format_descriptor<B>::register_dtype( + std::vector<py::detail::field_descriptor>{} + ); + // test_recarray, test_scalar_conversion m.def("create_rec_simple", &create_recarray<SimpleStruct>); m.def("create_rec_packed", &create_recarray<PackedStruct>); @@ -448,4 +468,7 @@ TEST_SUBMODULE(numpy_dtypes, m) { // test_register_dtype m.def("register_dtype", []() { PYBIND11_NUMPY_DTYPE(SimpleStruct, bool_, uint_, float_, ldbl_); }); + + // test_str_leak + m.def("dtype_wrapper", [](py::object d) { return py::dtype::from_args(std::move(d)); }); } diff --git a/ext/pybind11/tests/test_numpy_dtypes.py b/ext/pybind11/tests/test_numpy_dtypes.py index 5f9a95404..2e6388517 100644 --- a/ext/pybind11/tests/test_numpy_dtypes.py +++ b/ext/pybind11/tests/test_numpy_dtypes.py @@ -103,7 +103,7 @@ def test_dtype(simple_dtype): partial_nested_fmt(), "[('a', 'S3'), ('b', 'S3')]", ("{{'names':['a','b','c','d'], " + - "'formats':[('S4', (3,)),('<i4', (2,)),('u1', (3,)),('<f4', (4, 2))], " + + "'formats':[('S4', (3,)),('" + e + "i4', (2,)),('u1', (3,)),('" + e + "f4', (4, 2))], " + "'offsets':[0,12,20,24], 'itemsize':56}}").format(e=e), "[('e1', '" + e + "i8'), ('e2', 'u1')]", "[('x', 'i1'), ('y', '" + e + "u8')]", @@ -215,7 +215,7 @@ def test_array_array(): arr = m.create_array_array(3) assert str(arr.dtype) == ( "{{'names':['a','b','c','d'], " + - "'formats':[('S4', (3,)),('<i4', (2,)),('u1', (3,)),('{e}f4', (4, 2))], " + + "'formats':[('S4', (3,)),('" + e + "i4', (2,)),('u1', (3,)),('{e}f4', (4, 2))], " + "'offsets':[0,12,20,24], 'itemsize':56}}").format(e=e) assert m.print_array_array(arr) == [ "a={{A,B,C,D},{K,L,M,N},{U,V,W,X}},b={0,1}," + @@ -293,6 +293,18 @@ def test_register_dtype(): assert 'dtype is already registered' in str(excinfo.value) -@pytest.requires_numpy +@pytest.unsupported_on_pypy +def test_str_leak(): + from sys import getrefcount + fmt = "f4" + pytest.gc_collect() + start = getrefcount(fmt) + d = m.dtype_wrapper(fmt) + assert d is np.dtype("f4") + del d + pytest.gc_collect() + assert getrefcount(fmt) == start + + def test_compare_buffer_info(): assert all(m.compare_buffer_info()) diff --git a/ext/pybind11/tests/test_opaque_types.cpp b/ext/pybind11/tests/test_opaque_types.cpp index 5e83df0f6..0d20d9a01 100644 --- a/ext/pybind11/tests/test_opaque_types.cpp +++ b/ext/pybind11/tests/test_opaque_types.cpp @@ -11,10 +11,14 @@ #include <pybind11/stl.h> #include <vector> -using StringList = std::vector<std::string>; +// IMPORTANT: Disable internal pybind11 translation mechanisms for STL data structures +// +// This also deliberately doesn't use the below StringList type alias to test +// that MAKE_OPAQUE can handle a type containing a `,`. (The `std::allocator` +// bit is just the default `std::vector` allocator). +PYBIND11_MAKE_OPAQUE(std::vector<std::string, std::allocator<std::string>>); -/* IMPORTANT: Disable internal pybind11 translation mechanisms for STL data structures */ -PYBIND11_MAKE_OPAQUE(StringList); +using StringList = std::vector<std::string, std::allocator<std::string>>; TEST_SUBMODULE(opaque_types, m) { // test_string_list diff --git a/ext/pybind11/tests/test_opaque_types.py b/ext/pybind11/tests/test_opaque_types.py index 2d3aef5d1..6b3802fdb 100644 --- a/ext/pybind11/tests/test_opaque_types.py +++ b/ext/pybind11/tests/test_opaque_types.py @@ -4,21 +4,21 @@ from pybind11_tests import ConstructorStats, UserType def test_string_list(): - l = m.StringList() - l.push_back("Element 1") - l.push_back("Element 2") - assert m.print_opaque_list(l) == "Opaque list: [Element 1, Element 2]" - assert l.back() == "Element 2" + lst = m.StringList() + lst.push_back("Element 1") + lst.push_back("Element 2") + assert m.print_opaque_list(lst) == "Opaque list: [Element 1, Element 2]" + assert lst.back() == "Element 2" - for i, k in enumerate(l, start=1): + for i, k in enumerate(lst, start=1): assert k == "Element {}".format(i) - l.pop_back() - assert m.print_opaque_list(l) == "Opaque list: [Element 1]" + lst.pop_back() + assert m.print_opaque_list(lst) == "Opaque list: [Element 1]" cvp = m.ClassWithSTLVecProperty() assert m.print_opaque_list(cvp.stringList) == "Opaque list: []" - cvp.stringList = l + cvp.stringList = lst cvp.stringList.push_back("Element 3") assert m.print_opaque_list(cvp.stringList) == "Opaque list: [Element 1, Element 3]" diff --git a/ext/pybind11/tests/test_operator_overloading.cpp b/ext/pybind11/tests/test_operator_overloading.cpp index 4ad34d104..7b111704b 100644 --- a/ext/pybind11/tests/test_operator_overloading.cpp +++ b/ext/pybind11/tests/test_operator_overloading.cpp @@ -23,6 +23,7 @@ public: std::string toString() const { return "[" + std::to_string(x) + ", " + std::to_string(y) + "]"; } + Vector2 operator-() const { return Vector2(-x, -y); } Vector2 operator+(const Vector2 &v) const { return Vector2(x + v.x, y + v.y); } Vector2 operator-(const Vector2 &v) const { return Vector2(x - v.x, y - v.y); } Vector2 operator-(float value) const { return Vector2(x - value, y - value); } @@ -62,6 +63,25 @@ namespace std { }; } +// MSVC warns about unknown pragmas, and warnings are errors. +#ifndef _MSC_VER + #pragma GCC diagnostic push + // clang 7.0.0 and Apple LLVM 10.0.1 introduce `-Wself-assign-overloaded` to + // `-Wall`, which is used here for overloading (e.g. `py::self += py::self `). + // Here, we suppress the warning using `#pragma diagnostic`. + // Taken from: https://github.com/RobotLocomotion/drake/commit/aaf84b46 + // TODO(eric): This could be resolved using a function / functor (e.g. `py::self()`). + #if (__APPLE__) && (__clang__) + #if (__clang_major__ >= 10) && (__clang_minor__ >= 0) && (__clang_patchlevel__ >= 1) + #pragma GCC diagnostic ignored "-Wself-assign-overloaded" + #endif + #elif (__clang__) + #if (__clang_major__ >= 7) + #pragma GCC diagnostic ignored "-Wself-assign-overloaded" + #endif + #endif +#endif + TEST_SUBMODULE(operators, m) { // test_operator_overloading @@ -85,6 +105,7 @@ TEST_SUBMODULE(operators, m) { .def(float() - py::self) .def(float() * py::self) .def(float() / py::self) + .def(-py::self) .def("__str__", &Vector2::toString) .def(hash(py::self)) ; @@ -144,3 +165,7 @@ TEST_SUBMODULE(operators, m) { .def_readwrite("b", &NestC::b); m.def("get_NestC", [](const NestC &c) { return c.value; }); } + +#ifndef _MSC_VER + #pragma GCC diagnostic pop +#endif diff --git a/ext/pybind11/tests/test_operator_overloading.py b/ext/pybind11/tests/test_operator_overloading.py index 0d80e5ed3..bd36ac2a5 100644 --- a/ext/pybind11/tests/test_operator_overloading.py +++ b/ext/pybind11/tests/test_operator_overloading.py @@ -9,6 +9,8 @@ def test_operator_overloading(): assert str(v1) == "[1.000000, 2.000000]" assert str(v2) == "[3.000000, -1.000000]" + assert str(-v2) == "[-3.000000, 1.000000]" + assert str(v1 + v2) == "[4.000000, 1.000000]" assert str(v1 - v2) == "[-2.000000, 3.000000]" assert str(v1 - 8) == "[-7.000000, -6.000000]" @@ -44,13 +46,13 @@ def test_operator_overloading(): 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]', - '[3.000000, -2.000000]', '[3.000000, -0.500000]', - '[6.000000, -2.000000]'] + '[-3.000000, 1.000000]', '[4.000000, 1.000000]', + '[-2.000000, 3.000000]', '[-7.000000, -6.000000]', + '[9.000000, 10.000000]', '[8.000000, 16.000000]', + '[0.125000, 0.250000]', '[7.000000, 6.000000]', + '[9.000000, 10.000000]', '[8.000000, 16.000000]', + '[8.000000, 4.000000]', '[3.000000, -2.000000]', + '[3.000000, -0.500000]', '[6.000000, -2.000000]'] assert cstats.default_constructions == 0 assert cstats.copy_constructions == 0 assert cstats.move_constructions >= 10 @@ -98,7 +100,7 @@ def test_nested(): del c pytest.gc_collect() - del a # Should't delete while abase is still alive + del a # Shouldn't delete while abase is still alive pytest.gc_collect() assert abase.value == 42 diff --git a/ext/pybind11/tests/test_pickling.py b/ext/pybind11/tests/test_pickling.py index 707d34786..5ae05aaa0 100644 --- a/ext/pybind11/tests/test_pickling.py +++ b/ext/pybind11/tests/test_pickling.py @@ -34,3 +34,9 @@ def test_roundtrip_with_dict(cls_name): assert p2.value == p.value assert p2.extra == p.extra assert p2.dynamic == p.dynamic + + +def test_enum_pickle(): + from pybind11_tests import enums as e + data = pickle.dumps(e.EOne, 2) + assert e.EOne == pickle.loads(data) diff --git a/ext/pybind11/tests/test_pytypes.cpp b/ext/pybind11/tests/test_pytypes.cpp index a962f0ccc..244e1db0d 100644 --- a/ext/pybind11/tests/test_pytypes.cpp +++ b/ext/pybind11/tests/test_pytypes.cpp @@ -17,6 +17,8 @@ TEST_SUBMODULE(pytypes, m) { list.append("value"); py::print("Entry at position 0:", list[0]); list[0] = py::str("overwritten"); + list.insert(0, "inserted-0"); + list.insert(2, "inserted-2"); return list; }); m.def("print_list", [](py::list list) { @@ -37,6 +39,12 @@ TEST_SUBMODULE(pytypes, m) { for (auto item : set) py::print("key:", item); }); + m.def("set_contains", [](py::set set, py::object key) { + return set.contains(key); + }); + m.def("set_contains", [](py::set set, const char* key) { + return set.contains(key); + }); // test_dict m.def("get_dict", []() { return py::dict("key"_a="value"); }); @@ -49,6 +57,12 @@ TEST_SUBMODULE(pytypes, m) { auto d2 = py::dict("z"_a=3, **d1); return d2; }); + m.def("dict_contains", [](py::dict dict, py::object val) { + return dict.contains(val); + }); + m.def("dict_contains", [](py::dict dict, const char* val) { + return dict.contains(val); + }); // test_str m.def("str_from_string", []() { return py::str(std::string("baz")); }); @@ -269,4 +283,28 @@ TEST_SUBMODULE(pytypes, m) { m.def("print_failure", []() { py::print(42, UnregisteredType()); }); m.def("hash_function", [](py::object obj) { return py::hash(obj); }); + + m.def("test_number_protocol", [](py::object a, py::object b) { + py::list l; + l.append(a.equal(b)); + l.append(a.not_equal(b)); + l.append(a < b); + l.append(a <= b); + l.append(a > b); + l.append(a >= b); + l.append(a + b); + l.append(a - b); + l.append(a * b); + l.append(a / b); + l.append(a | b); + l.append(a & b); + l.append(a ^ b); + l.append(a >> b); + l.append(a << b); + return l; + }); + + m.def("test_list_slicing", [](py::list a) { + return a[py::slice(0, -1, 2)]; + }); } diff --git a/ext/pybind11/tests/test_pytypes.py b/ext/pybind11/tests/test_pytypes.py index 94c90a909..0e8d6c33a 100644 --- a/ext/pybind11/tests/test_pytypes.py +++ b/ext/pybind11/tests/test_pytypes.py @@ -1,3 +1,4 @@ +from __future__ import division import pytest import sys @@ -7,15 +8,17 @@ from pybind11_tests import debug_enabled def test_list(capture, doc): with capture: - l = m.get_list() - assert l == ["overwritten"] + lst = m.get_list() + assert lst == ["inserted-0", "overwritten", "inserted-2"] - l.append("value2") - m.print_list(l) + lst.append("value2") + m.print_list(lst) assert capture.unordered == """ Entry at position 0: value - list item 0: overwritten - list item 1: value2 + list item 0: inserted-0 + list item 1: overwritten + list item 2: inserted-2 + list item 3: value2 """ assert doc(m.get_list) == "get_list() -> list" @@ -36,6 +39,10 @@ def test_set(capture, doc): key: key4 """ + assert not m.set_contains(set([]), 42) + assert m.set_contains({42}, 42) + assert m.set_contains({"foo"}, "foo") + assert doc(m.get_list) == "get_list() -> list" assert doc(m.print_list) == "print_list(arg0: list) -> None" @@ -52,6 +59,10 @@ def test_dict(capture, doc): key: key2, value=value2 """ + assert not m.dict_contains({}, 42) + assert m.dict_contains({42: None}, 42) + assert m.dict_contains({"foo": None}, "foo") + assert doc(m.get_dict) == "get_dict() -> dict" assert doc(m.print_dict) == "print_dict(arg0: dict) -> None" @@ -238,3 +249,15 @@ def test_hash(): assert m.hash_function(Hashable(42)) == 42 with pytest.raises(TypeError): m.hash_function(Unhashable()) + + +def test_number_protocol(): + for a, b in [(1, 1), (3, 5)]: + li = [a == b, a != b, a < b, a <= b, a > b, a >= b, a + b, + a - b, a * b, a / b, a | b, a & b, a ^ b, a >> b, a << b] + assert m.test_number_protocol(a, b) == li + + +def test_list_slicing(): + li = list(range(100)) + assert li[::2] == m.test_list_slicing(li) diff --git a/ext/pybind11/tests/test_sequences_and_iterators.cpp b/ext/pybind11/tests/test_sequences_and_iterators.cpp index a45521256..87ccf99d6 100644 --- a/ext/pybind11/tests/test_sequences_and_iterators.cpp +++ b/ext/pybind11/tests/test_sequences_and_iterators.cpp @@ -71,6 +71,25 @@ py::list test_random_access_iterator(PythonType x) { } TEST_SUBMODULE(sequences_and_iterators, m) { + // test_sliceable + class Sliceable{ + public: + Sliceable(int n): size(n) {} + int start,stop,step; + int size; + }; + py::class_<Sliceable>(m,"Sliceable") + .def(py::init<int>()) + .def("__getitem__",[](const Sliceable &s, py::slice slice) { + ssize_t start, stop, step, slicelength; + if (!slice.compute(s.size, &start, &stop, &step, &slicelength)) + throw py::error_already_set(); + int istart = static_cast<int>(start); + int istop = static_cast<int>(stop); + int istep = static_cast<int>(step); + return std::make_tuple(istart,istop,istep); + }) + ; // test_sequence class Sequence { diff --git a/ext/pybind11/tests/test_sequences_and_iterators.py b/ext/pybind11/tests/test_sequences_and_iterators.py index 640ca07bd..6bd160640 100644 --- a/ext/pybind11/tests/test_sequences_and_iterators.py +++ b/ext/pybind11/tests/test_sequences_and_iterators.py @@ -33,6 +33,19 @@ def test_generalized_iterators(): next(it) +def test_sliceable(): + sliceable = m.Sliceable(100) + assert sliceable[::] == (0, 100, 1) + assert sliceable[10::] == (10, 100, 1) + assert sliceable[:10:] == (0, 10, 1) + assert sliceable[::10] == (0, 100, 10) + assert sliceable[-10::] == (90, 100, 1) + assert sliceable[:-10:] == (0, 90, 1) + assert sliceable[::-10] == (99, -1, -10) + assert sliceable[50:60:1] == (50, 60, 1) + assert sliceable[50:60:-1] == (50, 60, -1) + + def test_sequence(): cstats = ConstructorStats.get(m.Sequence) @@ -131,9 +144,9 @@ def test_python_iterator_in_cpp(): m.iterator_to_list(iter(bad_next_call, None)) assert str(excinfo.value) == "py::iterator::advance() should propagate errors" - l = [1, None, 0, None] - assert m.count_none(l) == 2 - assert m.find_none(l) is True + lst = [1, None, 0, None] + assert m.count_none(lst) == 2 + assert m.find_none(lst) is True assert m.count_nonzeros({"a": 0, "b": 1, "c": 2}) == 2 r = range(5) diff --git a/ext/pybind11/tests/test_smart_ptr.cpp b/ext/pybind11/tests/test_smart_ptr.cpp index dccb1e9be..87c9be8c2 100644 --- a/ext/pybind11/tests/test_smart_ptr.cpp +++ b/ext/pybind11/tests/test_smart_ptr.cpp @@ -19,7 +19,7 @@ // ref<T> is a wrapper for 'Object' which uses intrusive reference counting // It is always possible to construct a ref<T> from an Object* pointer without -// possible incosistencies, hence the 'true' argument at the end. +// possible inconsistencies, hence the 'true' argument at the end. PYBIND11_DECLARE_HOLDER_TYPE(T, ref<T>, true); // Make pybind11 aware of the non-standard getter member function namespace pybind11 { namespace detail { @@ -55,6 +55,35 @@ public: }; PYBIND11_DECLARE_HOLDER_TYPE(T, custom_unique_ptr<T>); +// Simple custom holder that works like shared_ptr and has operator& overload +// To obtain address of an instance of this holder pybind should use std::addressof +// Attempt to get address via operator& may leads to segmentation fault +template <typename T> +class shared_ptr_with_addressof_operator { + std::shared_ptr<T> impl; +public: + shared_ptr_with_addressof_operator( ) = default; + shared_ptr_with_addressof_operator(T* p) : impl(p) { } + T* get() const { return impl.get(); } + T** operator&() { throw std::logic_error("Call of overloaded operator& is not expected"); } +}; +PYBIND11_DECLARE_HOLDER_TYPE(T, shared_ptr_with_addressof_operator<T>); + +// Simple custom holder that works like unique_ptr and has operator& overload +// To obtain address of an instance of this holder pybind should use std::addressof +// Attempt to get address via operator& may leads to segmentation fault +template <typename T> +class unique_ptr_with_addressof_operator { + std::unique_ptr<T> impl; +public: + unique_ptr_with_addressof_operator() = default; + unique_ptr_with_addressof_operator(T* p) : impl(p) { } + T* get() const { return impl.get(); } + T* release_ptr() { return impl.release(); } + T** operator&() { throw std::logic_error("Call of overloaded operator& is not expected"); } +}; +PYBIND11_DECLARE_HOLDER_TYPE(T, unique_ptr_with_addressof_operator<T>); + TEST_SUBMODULE(smart_ptr, m) { @@ -98,6 +127,7 @@ TEST_SUBMODULE(smart_ptr, m) { // Object managed by a std::shared_ptr<> class MyObject2 { public: + MyObject2(const MyObject2 &) = default; MyObject2(int value) : value(value) { print_created(this, toString()); } std::string toString() const { return "MyObject2[" + std::to_string(value) + "]"; } virtual ~MyObject2() { print_destroyed(this); } @@ -116,6 +146,7 @@ TEST_SUBMODULE(smart_ptr, m) { // 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(const MyObject3 &) = default; MyObject3(int value) : value(value) { print_created(this, toString()); } std::string toString() const { return "MyObject3[" + std::to_string(value) + "]"; } virtual ~MyObject3() { print_destroyed(this); } @@ -155,6 +186,32 @@ TEST_SUBMODULE(smart_ptr, m) { .def(py::init<int>()) .def_readwrite("value", &MyObject4::value); + // test_unique_deleter + // Object with std::unique_ptr<T, D> where D is not matching the base class + // Object with a protected destructor + class MyObject4a { + public: + MyObject4a(int i) { + value = i; + print_created(this); + }; + int value; + protected: + virtual ~MyObject4a() { print_destroyed(this); } + }; + py::class_<MyObject4a, std::unique_ptr<MyObject4a, py::nodelete>>(m, "MyObject4a") + .def(py::init<int>()) + .def_readwrite("value", &MyObject4a::value); + + // Object derived but with public destructor and no Deleter in default holder + class MyObject4b : public MyObject4a { + public: + MyObject4b(int i) : MyObject4a(i) { print_created(this); } + ~MyObject4b() { print_destroyed(this); } + }; + py::class_<MyObject4b, MyObject4a>(m, "MyObject4b") + .def(py::init<int>()); + // test_large_holder class MyObject5 { // managed by huge_unique_ptr public: @@ -219,6 +276,8 @@ TEST_SUBMODULE(smart_ptr, m) { // Issue #865: shared_from_this doesn't work with virtual inheritance struct SharedFromThisVBase : std::enable_shared_from_this<SharedFromThisVBase> { + SharedFromThisVBase() = default; + SharedFromThisVBase(const SharedFromThisVBase &) = default; virtual ~SharedFromThisVBase() = default; }; struct SharedFromThisVirt : virtual SharedFromThisVBase {}; @@ -234,6 +293,41 @@ TEST_SUBMODULE(smart_ptr, m) { py::class_<C, custom_unique_ptr<C>>(m, "TypeWithMoveOnlyHolder") .def_static("make", []() { return custom_unique_ptr<C>(new C); }); + // test_holder_with_addressof_operator + struct TypeForHolderWithAddressOf { + TypeForHolderWithAddressOf() { print_created(this); } + TypeForHolderWithAddressOf(const TypeForHolderWithAddressOf &) { print_copy_created(this); } + TypeForHolderWithAddressOf(TypeForHolderWithAddressOf &&) { print_move_created(this); } + ~TypeForHolderWithAddressOf() { print_destroyed(this); } + std::string toString() const { + return "TypeForHolderWithAddressOf[" + std::to_string(value) + "]"; + } + int value = 42; + }; + using HolderWithAddressOf = shared_ptr_with_addressof_operator<TypeForHolderWithAddressOf>; + py::class_<TypeForHolderWithAddressOf, HolderWithAddressOf>(m, "TypeForHolderWithAddressOf") + .def_static("make", []() { return HolderWithAddressOf(new TypeForHolderWithAddressOf); }) + .def("get", [](const HolderWithAddressOf &self) { return self.get(); }) + .def("print_object_1", [](const TypeForHolderWithAddressOf *obj) { py::print(obj->toString()); }) + .def("print_object_2", [](HolderWithAddressOf obj) { py::print(obj.get()->toString()); }) + .def("print_object_3", [](const HolderWithAddressOf &obj) { py::print(obj.get()->toString()); }) + .def("print_object_4", [](const HolderWithAddressOf *obj) { py::print((*obj).get()->toString()); }); + + // test_move_only_holder_with_addressof_operator + struct TypeForMoveOnlyHolderWithAddressOf { + TypeForMoveOnlyHolderWithAddressOf(int value) : value{value} { print_created(this); } + ~TypeForMoveOnlyHolderWithAddressOf() { print_destroyed(this); } + std::string toString() const { + return "MoveOnlyHolderWithAddressOf[" + std::to_string(value) + "]"; + } + int value; + }; + using MoveOnlyHolderWithAddressOf = unique_ptr_with_addressof_operator<TypeForMoveOnlyHolderWithAddressOf>; + py::class_<TypeForMoveOnlyHolderWithAddressOf, MoveOnlyHolderWithAddressOf>(m, "TypeForMoveOnlyHolderWithAddressOf") + .def_static("make", []() { return MoveOnlyHolderWithAddressOf(new TypeForMoveOnlyHolderWithAddressOf(0)); }) + .def_readwrite("value", &TypeForMoveOnlyHolderWithAddressOf::value) + .def("print_object", [](const TypeForMoveOnlyHolderWithAddressOf *obj) { py::print(obj->toString()); }); + // test_smart_ptr_from_default struct HeldByDefaultHolder { }; py::class_<HeldByDefaultHolder>(m, "HeldByDefaultHolder") @@ -242,7 +336,9 @@ TEST_SUBMODULE(smart_ptr, m) { // test_shared_ptr_gc // #187: issue involving std::shared_ptr<> return value policy & garbage collection - struct ElementBase { virtual void foo() { } /* Force creation of virtual table */ }; + struct ElementBase { + virtual ~ElementBase() { } /* Force creation of virtual table */ + }; py::class_<ElementBase, std::shared_ptr<ElementBase>>(m, "ElementBase"); struct ElementA : ElementBase { diff --git a/ext/pybind11/tests/test_smart_ptr.py b/ext/pybind11/tests/test_smart_ptr.py index 4dfe0036f..c6627043b 100644 --- a/ext/pybind11/tests/test_smart_ptr.py +++ b/ext/pybind11/tests/test_smart_ptr.py @@ -115,6 +115,27 @@ def test_unique_nodelete(): assert cstats.alive() == 1 # Leak, but that's intentional +def test_unique_nodelete4a(): + o = m.MyObject4a(23) + assert o.value == 23 + cstats = ConstructorStats.get(m.MyObject4a) + assert cstats.alive() == 1 + del o + assert cstats.alive() == 1 # Leak, but that's intentional + + +def test_unique_deleter(): + o = m.MyObject4b(23) + assert o.value == 23 + cstats4a = ConstructorStats.get(m.MyObject4a) + assert cstats4a.alive() == 2 # Two because of previous test + cstats4b = ConstructorStats.get(m.MyObject4b) + assert cstats4b.alive() == 1 + del o + assert cstats4a.alive() == 1 # Should now only be one leftover from previous test + assert cstats4b.alive() == 0 # Should be deleted + + def test_large_holder(): o = m.MyObject5(5) assert o.value == 5 @@ -203,11 +224,56 @@ def test_move_only_holder(): assert stats.alive() == 0 +def test_holder_with_addressof_operator(): + # this test must not throw exception from c++ + a = m.TypeForHolderWithAddressOf.make() + a.print_object_1() + a.print_object_2() + a.print_object_3() + a.print_object_4() + + stats = ConstructorStats.get(m.TypeForHolderWithAddressOf) + assert stats.alive() == 1 + + np = m.TypeForHolderWithAddressOf.make() + assert stats.alive() == 2 + del a + assert stats.alive() == 1 + del np + assert stats.alive() == 0 + + b = m.TypeForHolderWithAddressOf.make() + c = b + assert b.get() is c.get() + assert stats.alive() == 1 + + del b + assert stats.alive() == 1 + + del c + assert stats.alive() == 0 + + +def test_move_only_holder_with_addressof_operator(): + a = m.TypeForMoveOnlyHolderWithAddressOf.make() + a.print_object() + + stats = ConstructorStats.get(m.TypeForMoveOnlyHolderWithAddressOf) + assert stats.alive() == 1 + + a.value = 42 + assert a.value == 42 + + del a + assert stats.alive() == 0 + + def test_smart_ptr_from_default(): instance = m.HeldByDefaultHolder() with pytest.raises(RuntimeError) as excinfo: m.HeldByDefaultHolder.load_shared_ptr(instance) - assert "Unable to load a custom holder type from a default-holder instance" in str(excinfo) + assert "Unable to load a custom holder type from a " \ + "default-holder instance" in str(excinfo.value) def test_shared_ptr_gc(): diff --git a/ext/pybind11/tests/test_stl.cpp b/ext/pybind11/tests/test_stl.cpp index 7d53e9c18..207c9fb2b 100644 --- a/ext/pybind11/tests/test_stl.cpp +++ b/ext/pybind11/tests/test_stl.cpp @@ -8,8 +8,12 @@ */ #include "pybind11_tests.h" +#include "constructor_stats.h" #include <pybind11/stl.h> +#include <vector> +#include <string> + // Test with `std::variant` in C++17 mode, or with `boost::variant` in C++11/14 #if PYBIND11_HAS_VARIANT using std::variant; @@ -32,6 +36,8 @@ struct visit_helper<boost::variant> { }} // namespace pybind11::detail #endif +PYBIND11_MAKE_OPAQUE(std::vector<std::string, std::allocator<std::string>>); + /// Issue #528: templated constructor struct TplCtorClass { template <typename T> TplCtorClass(const T &) { } @@ -57,6 +63,10 @@ TEST_SUBMODULE(stl, m) { static std::vector<RValueCaster> lvv{2}; m.def("cast_ptr_vector", []() { return &lvv; }); + // test_deque + m.def("cast_deque", []() { return std::deque<int>{1}; }); + m.def("load_deque", [](const std::deque<int> &v) { return v.at(0) == 1 && v.at(1) == 2; }); + // test_array m.def("cast_array", []() { return std::array<int, 2> {{1 , 2}}; }); m.def("load_array", [](const std::array<int, 2> &a) { return a[0] == 1 && a[1] == 2; }); @@ -235,4 +245,40 @@ TEST_SUBMODULE(stl, m) { // test_stl_pass_by_pointer m.def("stl_pass_by_pointer", [](std::vector<int>* v) { return *v; }, "v"_a=nullptr); + + // #1258: pybind11/stl.h converts string to vector<string> + m.def("func_with_string_or_vector_string_arg_overload", [](std::vector<std::string>) { return 1; }); + m.def("func_with_string_or_vector_string_arg_overload", [](std::list<std::string>) { return 2; }); + m.def("func_with_string_or_vector_string_arg_overload", [](std::string) { return 3; }); + + class Placeholder { + public: + Placeholder() { print_created(this); } + Placeholder(const Placeholder &) = delete; + ~Placeholder() { print_destroyed(this); } + }; + py::class_<Placeholder>(m, "Placeholder"); + + /// test_stl_vector_ownership + m.def("test_stl_ownership", + []() { + std::vector<Placeholder *> result; + result.push_back(new Placeholder()); + return result; + }, + py::return_value_policy::take_ownership); + + m.def("array_cast_sequence", [](std::array<int, 3> x) { return x; }); + + /// test_issue_1561 + struct Issue1561Inner { std::string data; }; + struct Issue1561Outer { std::vector<Issue1561Inner> list; }; + + py::class_<Issue1561Inner>(m, "Issue1561Inner") + .def(py::init<std::string>()) + .def_readwrite("data", &Issue1561Inner::data); + + py::class_<Issue1561Outer>(m, "Issue1561Outer") + .def(py::init<>()) + .def_readwrite("list", &Issue1561Outer::list); } diff --git a/ext/pybind11/tests/test_stl.py b/ext/pybind11/tests/test_stl.py index db8515e7a..2335cb9fd 100644 --- a/ext/pybind11/tests/test_stl.py +++ b/ext/pybind11/tests/test_stl.py @@ -2,15 +2,16 @@ import pytest from pybind11_tests import stl as m from pybind11_tests import UserType +from pybind11_tests import ConstructorStats def test_vector(doc): """std::vector <-> list""" - l = m.cast_vector() - assert l == [1] - l.append(2) - assert m.load_vector(l) - assert m.load_vector(tuple(l)) + lst = m.cast_vector() + assert lst == [1] + lst.append(2) + assert m.load_vector(lst) + assert m.load_vector(tuple(lst)) assert m.cast_bool_vector() == [True, False] assert m.load_bool_vector([True, False]) @@ -22,11 +23,20 @@ def test_vector(doc): assert m.cast_ptr_vector() == ["lvalue", "lvalue"] +def test_deque(doc): + """std::deque <-> list""" + lst = m.cast_deque() + assert lst == [1] + lst.append(2) + assert m.load_deque(lst) + assert m.load_deque(tuple(lst)) + + def test_array(doc): """std::array <-> list""" - l = m.cast_array() - assert l == [1, 2] - assert m.load_array(l) + lst = m.cast_array() + assert lst == [1, 2] + assert m.load_array(lst) assert doc(m.cast_array) == "cast_array() -> List[int[2]]" assert doc(m.load_array) == "load_array(arg0: List[int[2]]) -> bool" @@ -34,9 +44,9 @@ def test_array(doc): def test_valarray(doc): """std::valarray <-> list""" - l = m.cast_valarray() - assert l == [1, 4, 9] - assert m.load_valarray(l) + lst = m.cast_valarray() + assert lst == [1, 4, 9] + assert m.load_valarray(lst) assert doc(m.cast_valarray) == "cast_valarray() -> List[int]" assert doc(m.load_valarray) == "load_valarray(arg0: List[int]) -> bool" @@ -46,7 +56,9 @@ def test_map(doc): """std::map <-> dict""" d = m.cast_map() assert d == {"key": "value"} + assert "key" in d d["key2"] = "value2" + assert "key2" in d assert m.load_map(d) assert doc(m.cast_map) == "cast_map() -> Dict[str, str]" @@ -164,7 +176,7 @@ def test_stl_pass_by_pointer(msg): m.stl_pass_by_pointer() # default value is `nullptr` assert msg(excinfo.value) == """ stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported: - 1. (v: List[int]=None) -> List[int] + 1. (v: List[int] = None) -> List[int] Invoked with: """ # noqa: E501 line too long @@ -173,7 +185,7 @@ def test_stl_pass_by_pointer(msg): m.stl_pass_by_pointer(None) assert msg(excinfo.value) == """ stl_pass_by_pointer(): incompatible function arguments. The following argument types are supported: - 1. (v: List[int]=None) -> List[int] + 1. (v: List[int] = None) -> List[int] Invoked with: None """ # noqa: E501 line too long @@ -198,3 +210,32 @@ def test_missing_header_message(): with pytest.raises(TypeError) as excinfo: cm.missing_header_return() assert expected_message in str(excinfo.value) + + +def test_function_with_string_and_vector_string_arg(): + """Check if a string is NOT implicitly converted to a list, which was the + behavior before fix of issue #1258""" + assert m.func_with_string_or_vector_string_arg_overload(('A', 'B', )) == 2 + assert m.func_with_string_or_vector_string_arg_overload(['A', 'B']) == 2 + assert m.func_with_string_or_vector_string_arg_overload('A') == 3 + + +def test_stl_ownership(): + cstats = ConstructorStats.get(m.Placeholder) + assert cstats.alive() == 0 + r = m.test_stl_ownership() + assert len(r) == 1 + del r + assert cstats.alive() == 0 + + +def test_array_cast_sequence(): + assert m.array_cast_sequence((1, 2, 3)) == [1, 2, 3] + + +def test_issue_1561(): + """ check fix for issue #1561 """ + bar = m.Issue1561Outer() + bar.list = [m.Issue1561Inner('bar')] + bar.list + assert bar.list[0].data == 'bar' diff --git a/ext/pybind11/tests/test_stl_binders.py b/ext/pybind11/tests/test_stl_binders.py index bf1aa674c..6d5a15983 100644 --- a/ext/pybind11/tests/test_stl_binders.py +++ b/ext/pybind11/tests/test_stl_binders.py @@ -11,6 +11,10 @@ def test_vector_int(): assert len(v_int) == 2 assert bool(v_int) is True + # test construction from a generator + v_int1 = m.VectorInt(x for x in range(5)) + assert v_int1 == m.VectorInt([0, 1, 2, 3, 4]) + v_int2 = m.VectorInt([0, 0]) assert v_int == v_int2 v_int2[1] = 1 @@ -33,6 +37,32 @@ def test_vector_int(): del v_int2[0] assert v_int2 == m.VectorInt([0, 99, 2, 3]) + v_int2.extend(m.VectorInt([4, 5])) + assert v_int2 == m.VectorInt([0, 99, 2, 3, 4, 5]) + + v_int2.extend([6, 7]) + assert v_int2 == m.VectorInt([0, 99, 2, 3, 4, 5, 6, 7]) + + # test error handling, and that the vector is unchanged + with pytest.raises(RuntimeError): + v_int2.extend([8, 'a']) + + assert v_int2 == m.VectorInt([0, 99, 2, 3, 4, 5, 6, 7]) + + # test extending from a generator + v_int2.extend(x for x in range(5)) + assert v_int2 == m.VectorInt([0, 99, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 4]) + + # test negative indexing + assert v_int2[-1] == 4 + + # insert with negative index + v_int2.insert(-1, 88) + assert v_int2 == m.VectorInt([0, 99, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 88, 4]) + + # delete negative index + del v_int2[-1] + assert v_int2 == m.VectorInt([0, 99, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3, 88]) # related to the PyPy's buffer protocol. @pytest.unsupported_on_pypy @@ -181,3 +211,25 @@ def test_noncopyable_containers(): vsum += v.value assert vsum == 150 + + +def test_map_delitem(): + mm = m.MapStringDouble() + mm['a'] = 1 + mm['b'] = 2.5 + + assert list(mm) == ['a', 'b'] + assert list(mm.items()) == [('a', 1), ('b', 2.5)] + del mm['a'] + assert list(mm) == ['b'] + assert list(mm.items()) == [('b', 2.5)] + + um = m.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)] + del um['ua'] + assert sorted(list(um)) == ['ub'] + assert sorted(list(um.items())) == [('ub', 2.6)] diff --git a/ext/pybind11/tests/test_tagbased_polymorphic.cpp b/ext/pybind11/tests/test_tagbased_polymorphic.cpp new file mode 100644 index 000000000..272e460c9 --- /dev/null +++ b/ext/pybind11/tests/test_tagbased_polymorphic.cpp @@ -0,0 +1,136 @@ +/* + tests/test_tagbased_polymorphic.cpp -- test of polymorphic_type_hook + + Copyright (c) 2018 Hudson River Trading LLC <opensource@hudson-trading.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/stl.h> + +struct Animal +{ + enum class Kind { + Unknown = 0, + Dog = 100, Labrador, Chihuahua, LastDog = 199, + Cat = 200, Panther, LastCat = 299 + }; + static const std::type_info* type_of_kind(Kind kind); + static std::string name_of_kind(Kind kind); + + const Kind kind; + const std::string name; + + protected: + Animal(const std::string& _name, Kind _kind) + : kind(_kind), name(_name) + {} +}; + +struct Dog : Animal +{ + Dog(const std::string& _name, Kind _kind = Kind::Dog) : Animal(_name, _kind) {} + std::string bark() const { return name_of_kind(kind) + " " + name + " goes " + sound; } + std::string sound = "WOOF!"; +}; + +struct Labrador : Dog +{ + Labrador(const std::string& _name, int _excitement = 9001) + : Dog(_name, Kind::Labrador), excitement(_excitement) {} + int excitement; +}; + +struct Chihuahua : Dog +{ + Chihuahua(const std::string& _name) : Dog(_name, Kind::Chihuahua) { sound = "iyiyiyiyiyi"; } + std::string bark() const { return Dog::bark() + " and runs in circles"; } +}; + +struct Cat : Animal +{ + Cat(const std::string& _name, Kind _kind = Kind::Cat) : Animal(_name, _kind) {} + std::string purr() const { return "mrowr"; } +}; + +struct Panther : Cat +{ + Panther(const std::string& _name) : Cat(_name, Kind::Panther) {} + std::string purr() const { return "mrrrRRRRRR"; } +}; + +std::vector<std::unique_ptr<Animal>> create_zoo() +{ + std::vector<std::unique_ptr<Animal>> ret; + ret.emplace_back(new Labrador("Fido", 15000)); + + // simulate some new type of Dog that the Python bindings + // haven't been updated for; it should still be considered + // a Dog, not just an Animal. + ret.emplace_back(new Dog("Ginger", Dog::Kind(150))); + + ret.emplace_back(new Chihuahua("Hertzl")); + ret.emplace_back(new Cat("Tiger", Cat::Kind::Cat)); + ret.emplace_back(new Panther("Leo")); + return ret; +} + +const std::type_info* Animal::type_of_kind(Kind kind) +{ + switch (kind) { + case Kind::Unknown: break; + + case Kind::Dog: break; + case Kind::Labrador: return &typeid(Labrador); + case Kind::Chihuahua: return &typeid(Chihuahua); + case Kind::LastDog: break; + + case Kind::Cat: break; + case Kind::Panther: return &typeid(Panther); + case Kind::LastCat: break; + } + + if (kind >= Kind::Dog && kind <= Kind::LastDog) return &typeid(Dog); + if (kind >= Kind::Cat && kind <= Kind::LastCat) return &typeid(Cat); + return nullptr; +} + +std::string Animal::name_of_kind(Kind kind) +{ + std::string raw_name = type_of_kind(kind)->name(); + py::detail::clean_type_id(raw_name); + return raw_name; +} + +namespace pybind11 { + template <typename itype> + struct polymorphic_type_hook<itype, detail::enable_if_t<std::is_base_of<Animal, itype>::value>> + { + static const void *get(const itype *src, const std::type_info*& type) + { type = src ? Animal::type_of_kind(src->kind) : nullptr; return src; } + }; +} + +TEST_SUBMODULE(tagbased_polymorphic, m) { + py::class_<Animal>(m, "Animal") + .def_readonly("name", &Animal::name); + py::class_<Dog, Animal>(m, "Dog") + .def(py::init<std::string>()) + .def_readwrite("sound", &Dog::sound) + .def("bark", &Dog::bark); + py::class_<Labrador, Dog>(m, "Labrador") + .def(py::init<std::string, int>(), "name"_a, "excitement"_a = 9001) + .def_readwrite("excitement", &Labrador::excitement); + py::class_<Chihuahua, Dog>(m, "Chihuahua") + .def(py::init<std::string>()) + .def("bark", &Chihuahua::bark); + py::class_<Cat, Animal>(m, "Cat") + .def(py::init<std::string>()) + .def("purr", &Cat::purr); + py::class_<Panther, Cat>(m, "Panther") + .def(py::init<std::string>()) + .def("purr", &Panther::purr); + m.def("create_zoo", &create_zoo); +}; diff --git a/ext/pybind11/tests/test_tagbased_polymorphic.py b/ext/pybind11/tests/test_tagbased_polymorphic.py new file mode 100644 index 000000000..2574d7de7 --- /dev/null +++ b/ext/pybind11/tests/test_tagbased_polymorphic.py @@ -0,0 +1,20 @@ +from pybind11_tests import tagbased_polymorphic as m + + +def test_downcast(): + zoo = m.create_zoo() + assert [type(animal) for animal in zoo] == [ + m.Labrador, m.Dog, m.Chihuahua, m.Cat, m.Panther + ] + assert [animal.name for animal in zoo] == [ + "Fido", "Ginger", "Hertzl", "Tiger", "Leo" + ] + zoo[1].sound = "woooooo" + assert [dog.bark() for dog in zoo[:3]] == [ + "Labrador Fido goes WOOF!", + "Dog Ginger goes woooooo", + "Chihuahua Hertzl goes iyiyiyiyiyi and runs in circles" + ] + assert [cat.purr() for cat in zoo[3:]] == ["mrowr", "mrrrRRRRRR"] + zoo[0].excitement -= 1000 + assert zoo[0].excitement == 14000 diff --git a/ext/pybind11/tests/test_union.cpp b/ext/pybind11/tests/test_union.cpp new file mode 100644 index 000000000..7b98ea216 --- /dev/null +++ b/ext/pybind11/tests/test_union.cpp @@ -0,0 +1,22 @@ +/* + tests/test_class.cpp -- test py::class_ definitions and basic functionality + + Copyright (c) 2019 Roland Dreier <roland.dreier@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" + +TEST_SUBMODULE(union_, m) { + union TestUnion { + int value_int; + unsigned value_uint; + }; + + py::class_<TestUnion>(m, "TestUnion") + .def(py::init<>()) + .def_readonly("as_int", &TestUnion::value_int) + .def_readwrite("as_uint", &TestUnion::value_uint); +} diff --git a/ext/pybind11/tests/test_union.py b/ext/pybind11/tests/test_union.py new file mode 100644 index 000000000..e1866e701 --- /dev/null +++ b/ext/pybind11/tests/test_union.py @@ -0,0 +1,8 @@ +from pybind11_tests import union_ as m + + +def test_union(): + instance = m.TestUnion() + + instance.as_uint = 10 + assert instance.as_int == 10 diff --git a/ext/pybind11/tests/test_virtual_functions.cpp b/ext/pybind11/tests/test_virtual_functions.cpp index 953b390b8..ccf018d99 100644 --- a/ext/pybind11/tests/test_virtual_functions.cpp +++ b/ext/pybind11/tests/test_virtual_functions.cpp @@ -10,6 +10,7 @@ #include "pybind11_tests.h" #include "constructor_stats.h" #include <pybind11/functional.h> +#include <thread> /* This is an example class that we'll want to be able to extend from Python */ class ExampleVirt { @@ -17,7 +18,7 @@ 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 ~ExampleVirt() { print_destroyed(this); } virtual int run(int value) { py::print("Original implementation of " @@ -128,6 +129,7 @@ private: class NCVirt { public: + virtual ~NCVirt() { } virtual NonCopyable get_noncopyable(int a, int b) { return NonCopyable(a, b); } virtual Movable get_movable(int a, int b) = 0; @@ -157,6 +159,28 @@ struct DispatchIssue : Base { } }; +static void test_gil() { + { + py::gil_scoped_acquire lock; + py::print("1st lock acquired"); + + } + + { + py::gil_scoped_acquire lock; + py::print("2nd lock acquired"); + } + +} + +static void test_gil_from_thread() { + py::gil_scoped_release release; + + std::thread t(test_gil); + t.join(); +} + + // Forward declaration (so that we can put the main tests here; the inherited virtual approaches are // rather long). void initialize_inherited_virtuals(py::module &m); @@ -207,7 +231,9 @@ TEST_SUBMODULE(virtual_functions, m) { void f() override { py::print("PyA.f()"); - PYBIND11_OVERLOAD(void, A, f); + // This convolution just gives a `void`, but tests that PYBIND11_TYPE() works to protect + // a type containing a , + PYBIND11_OVERLOAD(PYBIND11_TYPE(typename std::enable_if<true, void>::type), A, f); } }; @@ -249,7 +275,7 @@ TEST_SUBMODULE(virtual_functions, m) { m.def("dispatch_issue_go", [](const Base * b) { return b->dispatch(); }); // test_override_ref - // #392/397: overridding reference-returning functions + // #392/397: overriding reference-returning functions class OverrideTest { public: struct A { std::string value = "hi"; }; @@ -414,7 +440,6 @@ public: }; */ - void initialize_inherited_virtuals(py::module &m) { // test_inherited_virtuals @@ -447,4 +472,8 @@ void initialize_inherited_virtuals(py::module &m) { py::class_<D_Tpl, C_Tpl, PyB_Tpl<D_Tpl>>(m, "D_Tpl") .def(py::init<>()); + + // Fix issue #1454 (crash when acquiring/releasing GIL on another thread in Python 2.7) + m.def("test_gil", &test_gil); + m.def("test_gil_from_thread", &test_gil_from_thread); }; diff --git a/ext/pybind11/tests/test_virtual_functions.py b/ext/pybind11/tests/test_virtual_functions.py index b91ebfa3e..5ce9abd35 100644 --- a/ext/pybind11/tests/test_virtual_functions.py +++ b/ext/pybind11/tests/test_virtual_functions.py @@ -227,7 +227,7 @@ def test_dispatch_issue(msg): def test_override_ref(): - """#392/397: overridding reference-returning functions""" + """#392/397: overriding reference-returning functions""" o = m.OverrideTest("asdf") # Not allowed (see associated .cpp comment) @@ -369,3 +369,9 @@ def test_inherited_virtuals(): assert obj.unlucky_number() == -7 assert obj.lucky_number() == -1.375 assert obj.say_everything() == "BT -7" + + +def test_issue_1454(): + # Fix issue #1454 (crash when acquiring/releasing GIL on another thread in Python 2.7) + m.test_gil() + m.test_gil_from_thread() |