test_callbacks.py 4.27 KB
Newer Older
1
# -*- coding: utf-8 -*-
Dean Moldovan's avatar
Dean Moldovan committed
2
import pytest
3
from pybind11_tests import callbacks as m
4
from threading import Thread
Dean Moldovan's avatar
Dean Moldovan committed
5
6


7
def test_callbacks():
Dean Moldovan's avatar
Dean Moldovan committed
8
9
10
    from functools import partial

    def func1():
11
        return "func1"
Dean Moldovan's avatar
Dean Moldovan committed
12
13

    def func2(a, b, c, d):
14
        return "func2", a, b, c, d
Dean Moldovan's avatar
Dean Moldovan committed
15
16

    def func3(a):
17
18
        return "func3({})".format(a)

19
20
21
22
23
    assert m.test_callback1(func1) == "func1"
    assert m.test_callback2(func2) == ("func2", "Hello", "x", True, 5)
    assert m.test_callback1(partial(func2, 1, 2, 3, 4)) == ("func2", 1, 2, 3, 4)
    assert m.test_callback1(partial(func3, "partial")) == "func3(partial)"
    assert m.test_callback3(lambda i: i + 1) == "func(43) = 44"
Dean Moldovan's avatar
Dean Moldovan committed
24

25
    f = m.test_callback4()
Dean Moldovan's avatar
Dean Moldovan committed
26
    assert f(43) == 44
27
    f = m.test_callback5()
Dean Moldovan's avatar
Dean Moldovan committed
28
29
30
    assert f(number=43) == 44


31
32
33
34
35
36
37
def test_bound_method_callback():
    # Bound Python method:
    class MyClass:
        def double(self, val):
            return 2 * val

    z = MyClass()
38
    assert m.test_callback3(z.double) == "func(43) = 86"
39

40
41
    z = m.CppBoundMethodTest()
    assert m.test_callback3(z.triple) == "func(43) = 129"
42
43


44
45
46
47
48
def test_keyword_args_and_generalized_unpacking():

    def f(*args, **kwargs):
        return args, kwargs

49
50
51
52
53
    assert m.test_tuple_unpacking(f) == (("positional", 1, 2, 3, 4, 5, 6), {})
    assert m.test_dict_unpacking(f) == (("positional", 1), {"key": "value", "a": 1, "b": 2})
    assert m.test_keyword_args(f) == ((), {"x": 10, "y": 20})
    assert m.test_unpacking_and_keywords1(f) == ((1, 2), {"c": 3, "d": 4})
    assert m.test_unpacking_and_keywords2(f) == (
54
55
56
57
58
        ("positional", 1, 2, 3, 4, 5),
        {"key": "value", "a": 1, "b": 2, "c": 3, "d": 4, "e": 5}
    )

    with pytest.raises(TypeError) as excinfo:
59
        m.test_unpacking_error1(f)
60
61
62
    assert "Got multiple values for keyword argument" in str(excinfo.value)

    with pytest.raises(TypeError) as excinfo:
63
        m.test_unpacking_error2(f)
64
65
66
    assert "Got multiple values for keyword argument" in str(excinfo.value)

    with pytest.raises(RuntimeError) as excinfo:
67
        m.test_arg_conversion_error1(f)
68
69
70
    assert "Unable to convert call argument" in str(excinfo.value)

    with pytest.raises(RuntimeError) as excinfo:
71
        m.test_arg_conversion_error2(f)
72
73
74
    assert "Unable to convert call argument" in str(excinfo.value)


Dean Moldovan's avatar
Dean Moldovan committed
75
def test_lambda_closure_cleanup():
76
77
    m.test_cleanup()
    cstats = m.payload_cstats()
Dean Moldovan's avatar
Dean Moldovan committed
78
79
80
81
82
    assert cstats.alive() == 0
    assert cstats.copy_constructions == 1
    assert cstats.move_constructions >= 1


83
def test_cpp_function_roundtrip():
Dean Moldovan's avatar
Dean Moldovan committed
84
85
    """Test if passing a function pointer from C++ -> Python -> C++ yields the original pointer"""

86
87
88
89
90
91
    assert m.test_dummy_function(m.dummy_function) == "matches dummy_function: eval(1) = 2"
    assert (m.test_dummy_function(m.roundtrip(m.dummy_function)) ==
            "matches dummy_function: eval(1) = 2")
    assert m.roundtrip(None, expect_none=True) is None
    assert (m.test_dummy_function(lambda x: x + 2) ==
            "can't convert to function pointer: eval(1) = 3")
Dean Moldovan's avatar
Dean Moldovan committed
92

93
    with pytest.raises(TypeError) as excinfo:
94
        m.test_dummy_function(m.dummy_function2)
95
    assert "incompatible function arguments" in str(excinfo.value)
Dean Moldovan's avatar
Dean Moldovan committed
96

97
    with pytest.raises(TypeError) as excinfo:
98
        m.test_dummy_function(lambda x, y: x + y)
99
100
    assert any(s in str(excinfo.value) for s in ("missing 1 required positional argument",
                                                 "takes exactly 2 arguments"))
Dean Moldovan's avatar
Dean Moldovan committed
101
102
103


def test_function_signatures(doc):
104
105
    assert doc(m.test_callback3) == "test_callback3(arg0: Callable[[int], int]) -> str"
    assert doc(m.test_callback4) == "test_callback4() -> Callable[[int], int]"
106
107
108


def test_movable_object():
109
    assert m.callback_with_movable(lambda _: None) is True
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137


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()