pybind11/tests/test_pytypes.py
Ralf W. Grosse-Kunstleve 1d81191077
Disable implicit conversion of 0 to pybind11::handle. (#4008)
* Disable implicit conversion from `0` to `pybind11::handle`.

* Reverse or-ed condition in an attempt to resolve GCC 8.3.0 errors (i386/debian:buster).

* Trying the simpler `std::is_same<T, PyObject *>`

* Add implicit_conversion_from_pytorch_THPObjectPtr_to_handle test.

* Accommodate types with implicit conversions to `PyObject *`, other than `handle` & `handle` subclasses, or integral types.

* Fix copy-paste mishap (picked wrong name).

* Revamp SFINAE construct to actually fix the pytorch issue (already validated against pytorch proper).

The first version of the reduced pytorch code was critically missing the move ctor. The first version of the accompanying test was meaningless.

Note: It turns out the `!std::is_arithmetic<T>` condition is not needed: `int` is not in general implicitly convertible to `PyObject *`, only the literal `0` is.

* Use `NOLINT(performance-noexcept-move-constructor)` for reduced code from the wild (rather than changing the code).

* Use any_of, all_of, negation. It turns out to clang-format nicer.

* Clean up comments for changed code.

* Reduce pytorch situation further, add test for operator ... const.

* Use `none_of` as suggested by @skylion007

* Add `pure_compile_tests_for_handle_from_PyObject_pointers()`

* Fix inconsequential oversight (retested).

* Factor our `is_pyobj_ptr_or_nullptr_t` to make the SFINAE conditions more readable.

* Remove stray line (oversight).

* Make the `pure_compile_tests_for_handle_from_PyObject_pointers()` "rhs-const-complete", too.

* Remove the temporary PYBIND11_UNDO_PR4008 `#ifdef`.
2022-07-14 09:53:39 -07:00

742 lines
20 KiB
Python

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