1# This file is part of Hypothesis, which may be found at
2# https://github.com/HypothesisWorks/hypothesis/
3#
4# Copyright the Hypothesis Authors.
5# Individual contributors are listed in AUTHORS.rst and the git log.
6#
7# This Source Code Form is subject to the terms of the Mozilla Public License,
8# v. 2.0. If a copy of the MPL was not distributed with this file, You can
9# obtain one at https://mozilla.org/MPL/2.0/.
10
11from datetime import timedelta
12from typing import Any, Literal, Optional
13
14from hypothesis.internal.compat import ExceptionGroup
15
16
17class HypothesisException(Exception):
18 """Generic parent class for exceptions thrown by Hypothesis."""
19
20
21class _Trimmable(HypothesisException):
22 """Hypothesis can trim these tracebacks even if they're raised internally."""
23
24
25class UnsatisfiedAssumption(HypothesisException):
26 """An internal error raised by assume.
27
28 If you're seeing this error something has gone wrong.
29 """
30
31 def __init__(self, reason: Optional[str] = None) -> None:
32 self.reason = reason
33
34
35class NoSuchExample(HypothesisException):
36 """The condition we have been asked to satisfy appears to be always false.
37
38 This does not guarantee that no example exists, only that we were
39 unable to find one.
40 """
41
42 def __init__(self, condition_string: str, extra: str = "") -> None:
43 super().__init__(f"No examples found of condition {condition_string}{extra}")
44
45
46class Unsatisfiable(_Trimmable):
47 """We ran out of time or examples before we could find enough examples
48 which satisfy the assumptions of this hypothesis.
49
50 This could be because the function is too slow. If so, try upping
51 the timeout. It could also be because the function is using assume
52 in a way that is too hard to satisfy. If so, try writing a custom
53 strategy or using a better starting point (e.g if you are requiring
54 a list has unique values you could instead filter out all duplicate
55 values from the list)
56 """
57
58
59class ChoiceTooLarge(HypothesisException):
60 """An internal error raised by choice_from_index."""
61
62
63class Flaky(_Trimmable):
64 """Base class for indeterministic failures. Usually one of the more
65 specific subclasses (FlakyFailure or FlakyStrategyDefinition) is raised."""
66
67
68class FlakyReplay(Flaky):
69 """Internal error raised by the conjecture engine if flaky failures are
70 detected during replay.
71
72 Carries information allowing the runner to reconstruct the flakiness as
73 a FlakyFailure exception group for final presentation.
74 """
75
76 def __init__(self, reason, interesting_origins=None):
77 super().__init__(reason)
78 self.reason = reason
79 self._interesting_origins = interesting_origins
80
81
82class FlakyStrategyDefinition(Flaky):
83 """This function appears to cause inconsistent data generation.
84
85 Common causes for this problem are:
86 1. The strategy depends on external state. e.g. it uses an external
87 random number generator. Try to make a version that passes all the
88 relevant state in from Hypothesis.
89 """
90
91
92class _WrappedBaseException(Exception):
93 """Used internally for wrapping BaseExceptions as components of FlakyFailure."""
94
95
96class FlakyFailure(ExceptionGroup, Flaky):
97 """This function appears to fail non-deterministically: We have seen it
98 fail when passed this example at least once, but a subsequent invocation
99 did not fail, or caused a distinct error.
100
101 Common causes for this problem are:
102 1. The function depends on external state. e.g. it uses an external
103 random number generator. Try to make a version that passes all the
104 relevant state in from Hypothesis.
105 2. The function is suffering from too much recursion and its failure
106 depends sensitively on where it's been called from.
107 3. The function is timing sensitive and can fail or pass depending on
108 how long it takes. Try breaking it up into smaller functions which
109 don't do that and testing those instead.
110 """
111
112 def __new__(cls, msg, group):
113 # The Exception mixin forces this an ExceptionGroup (only accepting
114 # Exceptions, not BaseException). Usually BaseException is raised
115 # directly and will hence not be part of a FlakyFailure, but I'm not
116 # sure this assumption holds everywhere. So wrap any BaseExceptions.
117 group = list(group)
118 for i, exc in enumerate(group):
119 if not isinstance(exc, Exception):
120 err = _WrappedBaseException()
121 err.__cause__ = err.__context__ = exc
122 group[i] = err
123 return ExceptionGroup.__new__(cls, msg, group)
124
125
126class InvalidArgument(_Trimmable, TypeError):
127 """Used to indicate that the arguments to a Hypothesis function were in
128 some manner incorrect."""
129
130
131class ResolutionFailed(InvalidArgument):
132 """Hypothesis had to resolve a type to a strategy, but this failed.
133
134 Type inference is best-effort, so this only happens when an
135 annotation exists but could not be resolved for a required argument
136 to the target of ``builds()``, or where the user passed ``...``.
137 """
138
139
140class InvalidState(HypothesisException):
141 """The system is not in a state where you were allowed to do that."""
142
143
144class InvalidDefinition(_Trimmable, TypeError):
145 """Used to indicate that a class definition was not well put together and
146 has something wrong with it."""
147
148
149class HypothesisWarning(HypothesisException, Warning):
150 """A generic warning issued by Hypothesis."""
151
152
153class FailedHealthCheck(_Trimmable):
154 """Raised when a test fails a healthcheck."""
155
156
157class NonInteractiveExampleWarning(HypothesisWarning):
158 """SearchStrategy.example() is designed for interactive use,
159 but should never be used in the body of a test.
160 """
161
162
163class HypothesisDeprecationWarning(HypothesisWarning, FutureWarning):
164 """A deprecation warning issued by Hypothesis.
165
166 Actually inherits from FutureWarning, because DeprecationWarning is
167 hidden by the default warnings filter.
168
169 You can configure the Python :mod:`python:warnings` to handle these
170 warnings differently to others, either turning them into errors or
171 suppressing them entirely. Obviously we would prefer the former!
172 """
173
174
175class HypothesisSideeffectWarning(HypothesisWarning):
176 """A warning issued by Hypothesis when it sees actions that are
177 discouraged at import or initialization time because they are
178 slow or have user-visible side effects.
179 """
180
181
182class Frozen(HypothesisException):
183 """Raised when a mutation method has been called on a ConjectureData object
184 after freeze() has been called."""
185
186
187def __getattr__(name: str) -> Any:
188 if name == "MultipleFailures":
189 from hypothesis._settings import note_deprecation
190 from hypothesis.internal.compat import BaseExceptionGroup
191
192 note_deprecation(
193 "MultipleFailures is deprecated; use the builtin `BaseExceptionGroup` type "
194 "instead, or `exceptiongroup.BaseExceptionGroup` before Python 3.11",
195 since="2022-08-02",
196 has_codemod=False, # This would be a great PR though!
197 stacklevel=1,
198 )
199 return BaseExceptionGroup
200
201 raise AttributeError(f"Module 'hypothesis.errors' has no attribute {name}")
202
203
204class DeadlineExceeded(_Trimmable):
205 """Raised when an individual test body has taken too long to run."""
206
207 def __init__(self, runtime: timedelta, deadline: timedelta) -> None:
208 super().__init__(
209 f"Test took {runtime.total_seconds() * 1000:.2f}ms, which exceeds "
210 f"the deadline of {deadline.total_seconds() * 1000:.2f}ms"
211 )
212 self.runtime = runtime
213 self.deadline = deadline
214
215 def __reduce__(
216 self,
217 ) -> tuple[type["DeadlineExceeded"], tuple[timedelta, timedelta]]:
218 return (type(self), (self.runtime, self.deadline))
219
220
221class StopTest(BaseException):
222 """Raised when a test should stop running and return control to
223 the Hypothesis engine, which should then continue normally.
224 """
225
226 def __init__(self, testcounter: int) -> None:
227 super().__init__(repr(testcounter))
228 self.testcounter = testcounter
229
230
231class DidNotReproduce(HypothesisException):
232 pass
233
234
235class Found(HypothesisException):
236 """Signal that the example matches condition. Internal use only."""
237
238
239class RewindRecursive(Exception):
240 """Signal that the type inference should be rewound due to recursive types. Internal use only."""
241
242 def __init__(self, target: object) -> None:
243 self.target = target
244
245
246class SmallSearchSpaceWarning(HypothesisWarning):
247 """Indicates that an inferred strategy does not span the search space
248 in a meaningful way, for example by only creating default instances."""
249
250
251CannotProceedScopeT = Literal["verified", "exhausted", "discard_test_case", "other"]
252
253
254class BackendCannotProceed(HypothesisException):
255 """
256 Raised by alternative backends when a |PrimitiveProvider| cannot proceed.
257 This is expected to occur inside one of the ``.draw_*()`` methods, or for
258 symbolic execution perhaps in |PrimitiveProvider.realize|.
259
260 The optional ``scope`` argument can enable smarter integration:
261
262 verified:
263 Do not request further test cases from this backend. We *may*
264 generate more test cases with other backends; if one fails then
265 Hypothesis will report unsound verification in the backend too.
266
267 exhausted:
268 Do not request further test cases from this backend; finish testing
269 with test cases generated with the default backend. Common if e.g.
270 native code blocks symbolic reasoning very early.
271
272 discard_test_case:
273 This particular test case could not be converted to concrete values;
274 skip any further processing and continue with another test case from
275 this backend.
276 """
277
278 def __init__(self, scope: CannotProceedScopeT = "other", /) -> None:
279 self.scope = scope