File: test_exceptiongroup.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 (171 lines) | stat: -rw-r--r-- 5,584 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
# 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 asyncio
import sys
from typing import Callable

import pytest

from hypothesis import errors, given, reject, strategies as st
from hypothesis.internal.compat import ExceptionGroup
from hypothesis.strategies import DataObject

# this file is not typechecked by mypy, which only runs py310

if sys.version_info < (3, 11):
    pytest.skip("asyncio.TaskGroup not available on <py3.11", allow_module_level=True)


def test_exceptiongroup_discard_frozen():
    """Basic test that raises Frozen+Unsatisfiable.
    Frozen is thrown out, and Unsatisfiable is raised"""

    @given(st.data())
    def test_function(data):
        async def task(pred):
            return data.draw(st.booleans().filter(pred))

        async def _main():
            async with asyncio.TaskGroup() as tg:
                tg.create_task(task(bool))
                tg.create_task(task(lambda _: False))

        asyncio.run(_main())

    with pytest.raises(errors.Unsatisfiable):
        test_function()


def test_exceptiongroup_nested() -> None:
    @given(st.data())
    def test_function(data: DataObject) -> None:
        async def task(pred: Callable[[bool], bool]) -> None:
            data.draw(st.booleans().filter(pred))

        async def _main() -> None:
            async with asyncio.TaskGroup():
                async with asyncio.TaskGroup() as tg2:
                    tg2.create_task(task(bool))
                    tg2.create_task(task(lambda _: False))

        asyncio.run(_main())

    with pytest.raises(errors.Unsatisfiable):
        test_function()


def test_exceptiongroup_user_originated() -> None:
    @given(st.data())
    def test_function(data):
        raise ExceptionGroup("foo", [ValueError(), ValueError()])

    with pytest.raises(ExceptionGroup) as exc_info:
        test_function()
    e = exc_info.value
    assert e.message == "foo"
    assert isinstance(e, ExceptionGroup)
    assert len(e.exceptions) == 2
    assert all(isinstance(child_e, ValueError) for child_e in e.exceptions)

    @given(st.data())
    def test_single_exc_group(data):
        raise ExceptionGroup("important message for user", [ValueError()])

    with pytest.raises(ExceptionGroup) as exc_info:
        test_single_exc_group()
    e = exc_info.value
    assert e.message == "important message for user"
    assert isinstance(e, ExceptionGroup)
    assert len(e.exceptions) == 1
    assert isinstance(e.exceptions[0], ValueError)


def test_exceptiongroup_multiple_stop() -> None:
    @given(st.data())
    def test_function(data):
        async def task(d: DataObject) -> None:
            d.conjecture_data.mark_invalid()

        async def _main(d: DataObject) -> None:
            async with asyncio.TaskGroup() as tg:
                tg.create_task(task(d))
                tg.create_task(task(d))

        asyncio.run(_main(data))

    # multiple stoptests -> single stoptest -> unsatisfiable
    with pytest.raises(errors.Unsatisfiable):
        test_function()


def test_exceptiongroup_stop_and_hypothesisexception() -> None:
    # group with stoptest+invalidargument -> invalidargument
    @given(st.data())
    def test_function(data):
        async def task_stoptest(d: DataObject) -> None:
            # only mark some runs as invalid to not raise Unsatisfiable
            if d.draw(st.integers(min_value=0, max_value=1)) == 1:
                d.conjecture_data.mark_invalid()

        async def task_invalid_argument(d: DataObject) -> None:
            d.draw(st.integers(max_value=2, min_value=3))

        async def _main(d: DataObject) -> None:
            async with asyncio.TaskGroup() as tg:
                tg.create_task(task_stoptest(d))
                tg.create_task(task_invalid_argument(d))

        asyncio.run(_main(data))

    with pytest.raises(errors.InvalidArgument):
        test_function()


def test_exceptiongroup_multiple_hypothesisexception() -> None:
    # multiple UnsatisfiedAssumption => first one is reraised => engine suppresses it

    @given(st.integers(min_value=0, max_value=3))
    def test_function(val: int) -> None:
        async def task(value: int) -> None:
            if value == 0:
                reject()

        async def _main(value: int) -> None:
            async with asyncio.TaskGroup() as tg:
                tg.create_task(task(value))
                tg.create_task(task(value))

        asyncio.run(_main(val))

    test_function()


def test_exceptiongroup_multiple_InvalidArgument() -> None:
    # multiple InvalidArgument => only first one is reraised... which seems bad.
    # But raising a group might break ghostwriter(?)

    @given(st.data())
    def test_function(data: DataObject) -> None:
        async def task1(d: DataObject) -> None:
            d.draw(st.integers(max_value=1, min_value=3))

        async def task2(d: DataObject) -> None:
            d.draw(st.integers(max_value=2, min_value=3))

        async def _main(d: DataObject) -> None:
            async with asyncio.TaskGroup() as tg:
                tg.create_task(task1(d))
                tg.create_task(task2(d))

        asyncio.run(_main(data))

    with pytest.raises(errors.InvalidArgument):
        test_function()