test_kwargs_and_defaults.py 7.73 KB
Newer Older
1
# -*- coding: utf-8 -*-
Dean Moldovan's avatar
Dean Moldovan committed
2
import pytest
3
from pybind11_tests import kwargs_and_defaults as m
Dean Moldovan's avatar
Dean Moldovan committed
4

Henry Schreiner's avatar
Henry Schreiner committed
5
6
7
8
9
import platform
import sys

pypy = platform.python_implementation() == "PyPy"

Dean Moldovan's avatar
Dean Moldovan committed
10
11

def test_function_signatures(doc):
12
13
    assert doc(m.kw_func0) == "kw_func0(arg0: int, arg1: int) -> str"
    assert doc(m.kw_func1) == "kw_func1(x: int, y: int) -> str"
14
15
16
17
18
    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"
19
20
21
22
23
24
    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) == \
        "foo0(self: m.kwargs_and_defaults.KWClass, arg0: int, arg1: float) -> None"
    assert doc(m.KWClass.foo1) == \
        "foo1(self: m.kwargs_and_defaults.KWClass, x: int, y: float) -> None"
Dean Moldovan's avatar
Dean Moldovan committed
25
26


27
def test_named_arguments(msg):
28
    assert m.kw_func0(5, 10) == "x=5, y=10"
Dean Moldovan's avatar
Dean Moldovan committed
29

30
31
32
    assert m.kw_func1(5, 10) == "x=5, y=10"
    assert m.kw_func1(5, y=10) == "x=5, y=10"
    assert m.kw_func1(y=10, x=5) == "x=5, y=10"
33

34
35
36
37
38
39
    assert m.kw_func2() == "x=100, y=200"
    assert m.kw_func2(5) == "x=5, y=200"
    assert m.kw_func2(x=5) == "x=5, y=200"
    assert m.kw_func2(y=10) == "x=100, y=10"
    assert m.kw_func2(5, 10) == "x=5, y=10"
    assert m.kw_func2(x=5, y=10) == "x=5, y=10"
Dean Moldovan's avatar
Dean Moldovan committed
40
41
42

    with pytest.raises(TypeError) as excinfo:
        # noinspection PyArgumentList
43
        m.kw_func2(x=5, y=10, z=12)
44
45
    assert excinfo.match(
        r'(?s)^kw_func2\(\): incompatible.*Invoked with: kwargs: ((x=5|y=10|z=12)(, |$))' + '{3}$')
Dean Moldovan's avatar
Dean Moldovan committed
46

47
48
    assert m.kw_func4() == "{13 17}"
    assert m.kw_func4(myList=[1, 2, 3]) == "{1 2 3}"
Dean Moldovan's avatar
Dean Moldovan committed
49

50
51
    assert m.kw_func_udl(x=5, y=10) == "x=5, y=10"
    assert m.kw_func_udl_z(x=5) == "x=5, y=0"
Dean Moldovan's avatar
Dean Moldovan committed
52
53


54
55
def test_arg_and_kwargs():
    args = 'arg1_value', 'arg2_value', 3
56
    assert m.args_function(*args) == args
57
58
59

    args = 'a1', 'a2'
    kwargs = dict(arg3='a3', arg4=4)
60
    assert m.args_kwargs_function(*args, **kwargs) == (args, kwargs)
61
62
63


def test_mixed_args_and_kwargs(msg):
64
65
66
67
    mpa = m.mixed_plus_args
    mpk = m.mixed_plus_kwargs
    mpak = m.mixed_plus_args_kwargs
    mpakd = m.mixed_plus_args_kwargs_defaults
68
69
70
71
72
73
74
75
76
77

    assert mpa(1, 2.5, 4, 99.5, None) == (1, 2.5, (4, 99.5, None))
    assert mpa(1, 2.5) == (1, 2.5, ())
    with pytest.raises(TypeError) as excinfo:
        assert mpa(1)
    assert msg(excinfo.value) == """
        mixed_plus_args(): incompatible function arguments. The following argument types are supported:
            1. (arg0: int, arg1: float, *args) -> tuple

        Invoked with: 1
78
    """  # noqa: E501 line too long
79
80
81
82
83
84
85
    with pytest.raises(TypeError) as excinfo:
        assert mpa()
    assert msg(excinfo.value) == """
        mixed_plus_args(): incompatible function arguments. The following argument types are supported:
            1. (arg0: int, arg1: float, *args) -> tuple

        Invoked with:
86
    """  # noqa: E501 line too long
87
88
89
90
91
92
93
94
95
96

    assert mpk(-2, 3.5, pi=3.14159, e=2.71828) == (-2, 3.5, {'e': 2.71828, 'pi': 3.14159})
    assert mpak(7, 7.7, 7.77, 7.777, 7.7777, minusseven=-7) == (
        7, 7.7, (7.77, 7.777, 7.7777), {'minusseven': -7})
    assert mpakd() == (1, 3.14159, (), {})
    assert mpakd(3) == (3, 3.14159, (), {})
    assert mpakd(j=2.71828) == (1, 2.71828, (), {})
    assert mpakd(k=42) == (1, 3.14159, (), {'k': 42})
    assert mpakd(1, 1, 2, 3, 5, 8, then=13, followedby=21) == (
        1, 1, (2, 3, 5, 8), {'then': 13, 'followedby': 21})
97
    # Arguments specified both positionally and via kwargs should fail:
98
99
100
    with pytest.raises(TypeError) as excinfo:
        assert mpakd(1, i=1)
    assert msg(excinfo.value) == """
101
        mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
102
            1. (i: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple
103
104
105

        Invoked with: 1; kwargs: i=1
    """  # noqa: E501 line too long
106
107
108
    with pytest.raises(TypeError) as excinfo:
        assert mpakd(1, 2, j=1)
    assert msg(excinfo.value) == """
109
        mixed_plus_args_kwargs_defaults(): incompatible function arguments. The following argument types are supported:
110
            1. (i: int = 1, j: float = 3.14159, *args, **kwargs) -> tuple
111
112
113

        Invoked with: 1, 2; kwargs: j=1
    """  # noqa: E501 line too long
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
143
144
145
146
147
148
149
def test_keyword_only_args(msg):
    assert m.kwonly_all(i=1, j=2) == (1, 2)
    assert m.kwonly_all(j=1, i=2) == (2, 1)

    with pytest.raises(TypeError) as excinfo:
        assert m.kwonly_all(i=1) == (1,)
    assert "incompatible function arguments" in str(excinfo.value)

    with pytest.raises(TypeError) as excinfo:
        assert m.kwonly_all(1, 2) == (1, 2)
    assert "incompatible function arguments" in str(excinfo.value)

    assert m.kwonly_some(1, k=3, j=2) == (1, 2, 3)

    assert m.kwonly_with_defaults(z=8) == (3, 4, 5, 8)
    assert m.kwonly_with_defaults(2, z=8) == (2, 4, 5, 8)
    assert m.kwonly_with_defaults(2, j=7, k=8, z=9) == (2, 7, 8, 9)
    assert m.kwonly_with_defaults(2, 7, z=9, k=8) == (2, 7, 8, 9)

    assert m.kwonly_mixed(1, j=2) == (1, 2)
    assert m.kwonly_mixed(j=2, i=3) == (3, 2)
    assert m.kwonly_mixed(i=2, j=3) == (2, 3)

    assert m.kwonly_plus_more(4, 5, k=6, extra=7) == (4, 5, 6, {'extra': 7})
    assert m.kwonly_plus_more(3, k=5, j=4, extra=6) == (3, 4, 5, {'extra': 6})
    assert m.kwonly_plus_more(2, k=3, extra=4) == (2, -1, 3, {'extra': 4})

    with pytest.raises(TypeError) as excinfo:
        assert m.kwonly_mixed(i=1) == (1,)
    assert "incompatible function arguments" in str(excinfo.value)

    with pytest.raises(RuntimeError) as excinfo:
        m.register_invalid_kwonly(m)
    assert msg(excinfo.value) == """
150
        arg(): cannot specify an unnamed argument after an kwonly() annotation
151
152
153
    """


Henry Schreiner's avatar
Henry Schreiner committed
154
155
@pytest.mark.xfail(pypy and sys.version_info < (3, 0),
                   reason="PyPy2 doesn't seem to double count")
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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)
194
195

    assert m.class_default_argument() == "<class 'decimal.Decimal'>"