File: test_composite.py

package info (click to toggle)
python-hypothesis 6.138.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 15,272 kB
  • sloc: python: 62,853; ruby: 1,107; sh: 253; makefile: 41; javascript: 6
file content (229 lines) | stat: -rw-r--r-- 6,483 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis/
#
# Copyright the Hypothesis Authors.
# Individual contributors are listed in AUTHORS.rst and the git log.
#
# This Source Code Form is subject to the terms of the Mozilla Public License,
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
# obtain one at https://mozilla.org/MPL/2.0/.

import sys
import typing

import pytest

from hypothesis import HealthCheck, assume, given, settings, strategies as st
from hypothesis.errors import (
    HypothesisDeprecationWarning,
    HypothesisWarning,
    InvalidArgument,
)

from tests.common.debug import minimal
from tests.common.utils import flaky


@st.composite
def badly_draw_lists(draw, m=0):
    length = draw(st.integers(m, m + 10))
    return [draw(st.integers()) for _ in range(length)]


def test_simplify_draws():
    assert minimal(badly_draw_lists(), lambda x: len(x) >= 3) == [0] * 3


def test_can_pass_through_arguments():
    assert minimal(badly_draw_lists(5)) == [0] * 5
    assert minimal(badly_draw_lists(m=6)) == [0] * 6


@st.composite
def draw_ordered_with_assume(draw):
    x = draw(st.floats())
    y = draw(st.floats())
    assume(x < y)
    return (x, y)


@given(draw_ordered_with_assume())
@settings(suppress_health_check=[HealthCheck.filter_too_much])
def test_can_assume_in_draw(xy):
    assert xy[0] < xy[1]


def test_uses_definitions_for_reprs():
    assert repr(badly_draw_lists()) == "badly_draw_lists()"
    assert repr(badly_draw_lists(1)) == "badly_draw_lists(m=1)"
    assert repr(badly_draw_lists(m=1)) == "badly_draw_lists(m=1)"


def test_errors_given_default_for_draw():
    with pytest.raises(InvalidArgument):

        @st.composite
        def foo(x=None):
            pass


def test_errors_given_function_of_no_arguments():
    with pytest.raises(InvalidArgument):

        @st.composite
        def foo():
            pass


def test_errors_given_kwargs_only():
    with pytest.raises(InvalidArgument):

        @st.composite
        def foo(**kwargs):
            pass


def test_warning_given_no_drawfn_call():
    with pytest.warns(HypothesisDeprecationWarning):

        @st.composite
        def foo(_):
            return "bar"


def test_can_use_pure_args():
    @st.composite
    def stuff(*args):
        return args[0](st.sampled_from(args[1:]))

    assert minimal(stuff(1, 2, 3, 4, 5)) == 1


def test_composite_of_lists():
    @st.composite
    def f(draw):
        return draw(st.integers()) + draw(st.integers())

    assert minimal(st.lists(f()), lambda x: len(x) >= 10) == [0] * 10


@flaky(min_passes=2, max_runs=5)
def test_can_shrink_matrices_with_length_param():
    @st.composite
    def matrix(draw):
        rows = draw(st.integers(1, 10))
        columns = draw(st.integers(1, 10))
        return [
            [draw(st.integers(0, 10000)) for _ in range(columns)] for _ in range(rows)
        ]

    def transpose(m):
        return [[row[i] for row in m] for i in range(len(m[0]))]

    def is_square(m):
        return len(m) == len(m[0])

    value = minimal(matrix(), lambda m: is_square(m) and transpose(m) != m)
    assert len(value) == 2
    assert len(value[0]) == 2
    assert sorted(value[0] + value[1]) == [0, 0, 0, 1]


class MyList(list):
    pass


@given(st.data(), st.lists(st.integers()).map(MyList))
def test_does_not_change_arguments(data, ls):
    # regression test for issue #1017 or other argument mutation
    @st.composite
    def strat(draw, arg):
        draw(st.none())
        return arg

    ex = data.draw(strat(ls))
    assert ex is ls


class ClsWithStrategyMethods:
    @classmethod
    @st.composite
    def st_classmethod_then_composite(draw, cls):
        return draw(st.integers(0, 10))

    @st.composite
    @classmethod
    def st_composite_then_classmethod(draw, cls):
        return draw(st.integers(0, 10))

    @staticmethod
    @st.composite
    def st_staticmethod_then_composite(draw):
        return draw(st.integers(0, 10))

    @st.composite
    @staticmethod
    def st_composite_then_staticmethod(draw):
        return draw(st.integers(0, 10))

    @st.composite
    def st_composite_method(draw, self):
        return draw(st.integers(0, 10))


@given(st.data())
def test_applying_composite_decorator_to_methods(data):
    instance = ClsWithStrategyMethods()
    for strategy in [
        ClsWithStrategyMethods.st_classmethod_then_composite(),
        ClsWithStrategyMethods.st_composite_then_classmethod(),
        ClsWithStrategyMethods.st_staticmethod_then_composite(),
        ClsWithStrategyMethods.st_composite_then_staticmethod(),
        instance.st_classmethod_then_composite(),
        instance.st_composite_then_classmethod(),
        instance.st_staticmethod_then_composite(),
        instance.st_composite_then_staticmethod(),
        instance.st_composite_method(),
    ]:
        x = data.draw(strategy)
        assert isinstance(x, int)
        assert 0 <= x <= 10


def test_drawfn_cannot_be_instantiated():
    with pytest.raises(TypeError):
        st.DrawFn()


@pytest.mark.skipif(sys.version_info[:2] == (3, 9), reason="stack depth varies???")
def test_warns_on_strategy_annotation():
    # TODO: print the stack on Python 3.10 and 3.11 to determine the appropriate
    #       stack depth to use.  Consider adding a debug-print if IN_COVERAGE_TESTS
    #       and the relevant depth is_hypothesis_file(), for easier future fixing.
    #
    # Meanwhile, the test is not skipped on 3.10/3.11 as it is still required for
    # coverage of the warning-generating branch.
    with pytest.warns(HypothesisWarning, match="Return-type annotation") as w:

        @st.composite
        def my_integers(draw: st.DrawFn) -> st.SearchStrategy[int]:
            return draw(st.integers())

    if sys.version_info[:2] > (3, 11):  # TEMP: see PR #3961
        assert len(w.list) == 1
        assert w.list[0].filename == __file__  # check stacklevel points to user code


def test_composite_allows_overload_without_draw():
    # See https://github.com/HypothesisWorks/hypothesis/issues/3970
    @st.composite
    @typing.overload
    def overloaded(draw: st.DrawFn, *, x: int) -> typing.Literal[True]: ...

    @st.composite
    @typing.overload
    def overloaded(draw: st.DrawFn, *, x: str) -> typing.Literal[False]: ...

    @st.composite
    def overloaded(draw: st.DrawFn, *, x: typing.Union[int, str]) -> bool:
        return draw(st.just(isinstance(x, int)))