test_callbacks.cpp 8.73 KB
Newer Older
Wenzel Jakob's avatar
Wenzel Jakob committed
1
/*
Dean Moldovan's avatar
Dean Moldovan committed
2
    tests/test_callbacks.cpp -- callbacks
Wenzel Jakob's avatar
Wenzel Jakob committed
3

4
    Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
Wenzel Jakob's avatar
Wenzel Jakob committed
5
6
7
8
9

    All rights reserved. Use of this source code is governed by a
    BSD-style license that can be found in the LICENSE file.
*/

Dean Moldovan's avatar
Dean Moldovan committed
10
11
#include "pybind11_tests.h"
#include "constructor_stats.h"
12
#include <pybind11/functional.h>
13
#include <thread>
Wenzel Jakob's avatar
Wenzel Jakob committed
14
15


16
17
int dummy_function(int i) { return i + 1; }

18
19
TEST_SUBMODULE(callbacks, m) {
    // test_callbacks, test_function_signatures
20
21
    m.def("test_callback1", [](const py::object &func) { return func(); });
    m.def("test_callback2", [](const py::object &func) { return func("Hello", 'x', true, 5); });
22
23
24
25
26
27
    m.def("test_callback3", [](const std::function<int(int)> &func) {
        return "func(43) = " + std::to_string(func(43)); });
    m.def("test_callback4", []() -> std::function<int(int)> { return [](int i) { return i+1; }; });
    m.def("test_callback5", []() {
        return py::cpp_function([](int i) { return i+1; }, py::arg("number"));
    });
28

29
    // test_keyword_args_and_generalized_unpacking
30
    m.def("test_tuple_unpacking", [](const py::function &f) {
31
32
33
34
35
        auto t1 = py::make_tuple(2, 3);
        auto t2 = py::make_tuple(5, 6);
        return f("positional", 1, *t1, 4, *t2);
    });

36
    m.def("test_dict_unpacking", [](const py::function &f) {
37
        auto d1 = py::dict("key"_a="value", "a"_a=1);
38
        auto d2 = py::dict();
39
        auto d3 = py::dict("b"_a=2);
40
41
42
        return f("positional", 1, **d1, **d2, **d3);
    });

43
    m.def("test_keyword_args", [](const py::function &f) { return f("x"_a = 10, "y"_a = 20); });
44

45
    m.def("test_unpacking_and_keywords1", [](const py::function &f) {
46
        auto args = py::make_tuple(2);
47
        auto kwargs = py::dict("d"_a=4);
48
49
50
        return f(1, *args, "c"_a=3, **kwargs);
    });

51
    m.def("test_unpacking_and_keywords2", [](const py::function &f) {
52
53
        auto kwargs1 = py::dict("a"_a=1);
        auto kwargs2 = py::dict("c"_a=3, "d"_a=4);
54
55
56
57
        return f("positional", *py::make_tuple(1), 2, *py::make_tuple(3, 4), 5,
                 "key"_a="value", **kwargs1, "b"_a=2, **kwargs2, "e"_a=5);
    });

58
    m.def("test_unpacking_error1", [](const py::function &f) {
59
        auto kwargs = py::dict("x"_a=3);
60
61
62
        return f("x"_a=1, "y"_a=2, **kwargs); // duplicate ** after keyword
    });

63
    m.def("test_unpacking_error2", [](const py::function &f) {
64
        auto kwargs = py::dict("x"_a=3);
65
66
        return f(**kwargs, "x"_a=1); // duplicate keyword after **
    });
67

68
69
    m.def("test_arg_conversion_error1",
          [](const py::function &f) { f(234, UnregisteredType(), "kw"_a = 567); });
70

71
    m.def("test_arg_conversion_error2", [](const py::function &f) {
72
        f(234, "expected_name"_a=UnregisteredType(), "kw"_a=567);
73
74
    });

75
76
77
78
79
    // test_lambda_closure_cleanup
    struct Payload {
        Payload() { print_default_created(this); }
        ~Payload() { print_destroyed(this); }
        Payload(const Payload &) { print_copy_created(this); }
80
        Payload(Payload &&) noexcept { print_move_created(this); }
81
82
83
    };
    // Export the payload constructor statistics for testing purposes:
    m.def("payload_cstats", &ConstructorStats::get<Payload>);
84
    m.def("test_lambda_closure_cleanup", []() -> std::function<void()> {
85
86
        Payload p;

87
88
        // In this situation, `Func` in the implementation of
        // `cpp_function::initialize` is NOT trivially destructible.
89
90
        return [p]() {
            /* p should be cleaned up when the returned function is garbage collected */
91
            (void) p;
92
93
        };
    });
94

95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
    class CppCallable {
    public:
        CppCallable() { track_default_created(this); }
        ~CppCallable() { track_destroyed(this); }
        CppCallable(const CppCallable &) { track_copy_created(this); }
        CppCallable(CppCallable &&) noexcept { track_move_created(this); }
        void operator()() {}
    };

    m.def("test_cpp_callable_cleanup", []() {
        // Related issue: https://github.com/pybind/pybind11/issues/3228
        // Related PR: https://github.com/pybind/pybind11/pull/3229
        py::list alive_counts;
        ConstructorStats &stat = ConstructorStats::get<CppCallable>();
        alive_counts.append(stat.alive());
        {
            CppCallable cpp_callable;
            alive_counts.append(stat.alive());
            {
                // In this situation, `Func` in the implementation of
                // `cpp_function::initialize` IS trivially destructible,
                // only `capture` is not.
                py::cpp_function py_func(cpp_callable);
                py::detail::silence_unused_warnings(py_func);
                alive_counts.append(stat.alive());
            }
            alive_counts.append(stat.alive());
            {
                py::cpp_function py_func(std::move(cpp_callable));
                py::detail::silence_unused_warnings(py_func);
                alive_counts.append(stat.alive());
            }
            alive_counts.append(stat.alive());
        }
        alive_counts.append(stat.alive());
        return alive_counts;
    });

133
    // test_cpp_function_roundtrip
134
135
    /* Test if passing a function pointer from C++ -> Python -> C++ yields the original pointer */
    m.def("dummy_function", &dummy_function);
136
137
    m.def("dummy_function_overloaded", [](int i, int j) { return i + j; });
    m.def("dummy_function_overloaded", &dummy_function);
138
139
140
141
142
143
144
145
146
147
148
149
    m.def("dummy_function2", [](int i, int j) { return i + j; });
    m.def("roundtrip", [](std::function<int(int)> f, bool expect_none = false) {
        if (expect_none && f)
            throw std::runtime_error("Expected None to be converted to empty std::function");
        return f;
    }, py::arg("f"), py::arg("expect_none")=false);
    m.def("test_dummy_function", [](const std::function<int(int)> &f) -> std::string {
        using fn_type = int (*)(int);
        auto result = f.target<fn_type>();
        if (!result) {
            auto r = f(1);
            return "can't convert to function pointer: eval(1) = " + std::to_string(r);
150
151
        }
        if (*result == dummy_function) {
152
153
154
            auto r = (*result)(1);
            return "matches dummy_function: eval(1) = " + std::to_string(r);
        }
155
156
        return "argument does NOT match dummy_function. This should never happen!";

157
    });
158

159
160
    class AbstractBase {
    public:
161
        // [workaround(intel)] = default does not work here
162
163
        // Defaulting this destructor results in linking errors with the Intel compiler
        // (in Debug builds only, tested with icpc (ICC) 2021.1 Beta 20200827)
164
        virtual ~AbstractBase() {} // NOLINT(modernize-use-equals-default)
165
166
        virtual unsigned int func() = 0;
    };
167
168
    m.def("func_accepting_func_accepting_base",
          [](const std::function<double(AbstractBase &)> &) {});
169
170
171
172
173
174
175

    struct MovableObject {
        bool valid = true;

        MovableObject() = default;
        MovableObject(const MovableObject &) = default;
        MovableObject &operator=(const MovableObject &) = default;
176
177
        MovableObject(MovableObject &&o) noexcept : valid(o.valid) { o.valid = false; }
        MovableObject &operator=(MovableObject &&o) noexcept {
178
179
180
181
182
            valid = o.valid;
            o.valid = false;
            return *this;
        }
    };
183
184
    py::class_<MovableObject>(m, "MovableObject");

185
    // test_movable_object
186
    m.def("callback_with_movable", [](const std::function<void(MovableObject &)> &f) {
187
188
189
        auto x = MovableObject();
        f(x); // lvalue reference shouldn't move out object
        return x.valid; // must still return `true`
190
    });
191

192
    // test_bound_method_callback
193
194
195
196
    struct CppBoundMethodTest {};
    py::class_<CppBoundMethodTest>(m, "CppBoundMethodTest")
        .def(py::init<>())
        .def("triple", [](CppBoundMethodTest &, int val) { return 3 * val; });
197

198
199
200
201
202
203
    // This checks that builtin functions can be passed as callbacks
    // rather than throwing RuntimeError due to trying to extract as capsule
    m.def("test_sum_builtin", [](const std::function<double(py::iterable)> &sum_builtin, const py::iterable &i) {
      return sum_builtin(i);
    });

204
205
    // test async Python callbacks
    using callback_f = std::function<void(int)>;
206
    m.def("test_async_callback", [](const callback_f &f, const py::list &work) {
207
208
209
210
211
212
213
214
215
216
217
218
219
220
        // 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));
    });
221

222
    m.def("callback_num_times", [](const py::function &f, std::size_t num) {
223
224
225
226
        for (std::size_t i = 0; i < num; i++) {
            f();
        }
    });
227
}