"vscode:/vscode.git/clone" did not exist on "8bd31c71e432323ecc6cee6d33daabb0aec8ceed"
test_callbacks.cpp 10.6 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.
*/

10
#include <pybind11/functional.h>
Wenzel Jakob's avatar
Wenzel Jakob committed
11

12
13
14
15
#include "constructor_stats.h"
#include "pybind11_tests.h"

#include <thread>
Wenzel Jakob's avatar
Wenzel Jakob committed
16

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

19
20
TEST_SUBMODULE(callbacks, m) {
    // test_callbacks, test_function_signatures
21
22
    m.def("test_callback1", [](const py::object &func) { return func(); });
    m.def("test_callback2", [](const py::object &func) { return func("Hello", 'x', true, 5); });
23
    m.def("test_callback3", [](const std::function<int(int)> &func) {
24
        return "func(43) = " + std::to_string(func(43));
25
    });
26
27
28
29
    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")); });
30

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

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

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

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

53
    m.def("test_unpacking_and_keywords2", [](const py::function &f) {
54
55
56
57
58
59
60
61
62
63
64
65
        auto kwargs1 = py::dict("a"_a = 1);
        auto kwargs2 = py::dict("c"_a = 3, "d"_a = 4);
        return f("positional",
                 *py::make_tuple(1),
                 2,
                 *py::make_tuple(3, 4),
                 5,
                 "key"_a = "value",
                 **kwargs1,
                 "b"_a = 2,
                 **kwargs2,
                 "e"_a = 5);
66
67
    });

68
    m.def("test_unpacking_error1", [](const py::function &f) {
69
70
        auto kwargs = py::dict("x"_a = 3);
        return f("x"_a = 1, "y"_a = 2, **kwargs); // duplicate ** after keyword
71
72
    });

73
    m.def("test_unpacking_error2", [](const py::function &f) {
74
75
        auto kwargs = py::dict("x"_a = 3);
        return f(**kwargs, "x"_a = 1); // duplicate keyword after **
76
    });
77

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

81
    m.def("test_arg_conversion_error2", [](const py::function &f) {
82
        f(234, "expected_name"_a = UnregisteredType(), "kw"_a = 567);
83
84
    });

85
86
87
88
89
    // test_lambda_closure_cleanup
    struct Payload {
        Payload() { print_default_created(this); }
        ~Payload() { print_destroyed(this); }
        Payload(const Payload &) { print_copy_created(this); }
90
        Payload(Payload &&) noexcept { print_move_created(this); }
91
92
93
    };
    // Export the payload constructor statistics for testing purposes:
    m.def("payload_cstats", &ConstructorStats::get<Payload>);
94
    m.def("test_lambda_closure_cleanup", []() -> std::function<void()> {
95
96
        Payload p;

97
98
        // In this situation, `Func` in the implementation of
        // `cpp_function::initialize` is NOT trivially destructible.
99
100
        return [p]() {
            /* p should be cleaned up when the returned function is garbage collected */
101
            (void) p;
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
133
134
135
136
137
138
139
140
141
142
    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;
    });

143
    // test_cpp_function_roundtrip
144
145
    /* Test if passing a function pointer from C++ -> Python -> C++ yields the original pointer */
    m.def("dummy_function", &dummy_function);
146
147
    m.def("dummy_function_overloaded", [](int i, int j) { return i + j; });
    m.def("dummy_function_overloaded", &dummy_function);
148
    m.def("dummy_function2", [](int i, int j) { return i + j; });
149
150
151
152
153
154
155
156
157
158
    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);
159
160
    m.def("test_dummy_function", [](const std::function<int(int)> &f) -> std::string {
        using fn_type = int (*)(int);
161
        const auto *result = f.target<fn_type>();
162
163
164
        if (!result) {
            auto r = f(1);
            return "can't convert to function pointer: eval(1) = " + std::to_string(r);
165
166
        }
        if (*result == dummy_function) {
167
168
169
            auto r = (*result)(1);
            return "matches dummy_function: eval(1) = " + std::to_string(r);
        }
170
        return "argument does NOT match dummy_function. This should never happen!";
171
    });
172

173
174
    class AbstractBase {
    public:
175
        // [workaround(intel)] = default does not work here
176
177
        // Defaulting this destructor results in linking errors with the Intel compiler
        // (in Debug builds only, tested with icpc (ICC) 2021.1 Beta 20200827)
178
        virtual ~AbstractBase() {} // NOLINT(modernize-use-equals-default)
179
180
        virtual unsigned int func() = 0;
    };
181
182
    m.def("func_accepting_func_accepting_base",
          [](const std::function<double(AbstractBase &)> &) {});
183
184
185
186
187
188
189

    struct MovableObject {
        bool valid = true;

        MovableObject() = default;
        MovableObject(const MovableObject &) = default;
        MovableObject &operator=(const MovableObject &) = default;
190
191
        MovableObject(MovableObject &&o) noexcept : valid(o.valid) { o.valid = false; }
        MovableObject &operator=(MovableObject &&o) noexcept {
192
193
194
195
196
            valid = o.valid;
            o.valid = false;
            return *this;
        }
    };
197
198
    py::class_<MovableObject>(m, "MovableObject");

199
    // test_movable_object
200
    m.def("callback_with_movable", [](const std::function<void(MovableObject &)> &f) {
201
        auto x = MovableObject();
202
        f(x);           // lvalue reference shouldn't move out object
203
        return x.valid; // must still return `true`
204
    });
205

206
    // test_bound_method_callback
207
208
209
210
    struct CppBoundMethodTest {};
    py::class_<CppBoundMethodTest>(m, "CppBoundMethodTest")
        .def(py::init<>())
        .def("triple", [](CppBoundMethodTest &, int val) { return 3 * val; });
211

212
213
    // This checks that builtin functions can be passed as callbacks
    // rather than throwing RuntimeError due to trying to extract as capsule
214
215
216
217
    m.def("test_sum_builtin",
          [](const std::function<double(py::iterable)> &sum_builtin, const py::iterable &i) {
              return sum_builtin(i);
          });
218

219
220
    // test async Python callbacks
    using callback_f = std::function<void(int)>;
221
    m.def("test_async_callback", [](const callback_f &f, const py::list &work) {
222
223
224
225
226
227
228
229
230
231
232
        // 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
233
        for (auto i : work) {
234
            start_f(py::cast<int>(i));
235
        }
236
    });
237

238
    m.def("callback_num_times", [](const py::function &f, std::size_t num) {
239
240
241
242
        for (std::size_t i = 0; i < num; i++) {
            f();
        }
    });
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279

    auto *custom_def = []() {
        static PyMethodDef def;
        def.ml_name = "example_name";
        def.ml_doc = "Example doc";
        def.ml_meth = [](PyObject *, PyObject *args) -> PyObject * {
            if (PyTuple_Size(args) != 1) {
                throw std::runtime_error("Invalid number of arguments for example_name");
            }
            PyObject *first = PyTuple_GetItem(args, 0);
            if (!PyLong_Check(first)) {
                throw std::runtime_error("Invalid argument to example_name");
            }
            auto result = py::cast(PyLong_AsLong(first) * 9);
            return result.release().ptr();
        };
        def.ml_flags = METH_VARARGS;
        return &def;
    }();

    // rec_capsule with name that has the same value (but not pointer) as our internal one
    // This capsule should be detected by our code as foreign and not inspected as the pointers
    // shouldn't match
    constexpr const char *rec_capsule_name
        = pybind11::detail::internals_function_record_capsule_name;
    py::capsule rec_capsule(std::malloc(1), [](void *data) { std::free(data); });
    rec_capsule.set_name(rec_capsule_name);
    m.add_object("custom_function", PyCFunction_New(custom_def, rec_capsule.ptr()));

    // This test requires a new ABI version to pass
#if PYBIND11_INTERNALS_VERSION > 4
    // rec_capsule with nullptr name
    py::capsule rec_capsule2(std::malloc(1), [](void *data) { std::free(data); });
    m.add_object("custom_function2", PyCFunction_New(custom_def, rec_capsule2.ptr()));
#else
    m.add_object("custom_function2", py::none());
#endif
280
}