diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-07-28 23:52:46 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-07-29 00:05:07 +0300 |
commit | 6db791437e19a60e5e4d0f42544ca5d1ad2c93f3 (patch) | |
tree | 5321dafbc20d3b382d9a0050d167492ad9de99ca /contrib/python/hypothesis/py3 | |
parent | 93600b3cefbd9b32f6cca41696896434d248c383 (diff) | |
download | ydb-6db791437e19a60e5e4d0f42544ca5d1ad2c93f3.tar.gz |
Intermediate changes
Diffstat (limited to 'contrib/python/hypothesis/py3')
8 files changed, 148 insertions, 44 deletions
diff --git a/contrib/python/hypothesis/py3/.dist-info/METADATA b/contrib/python/hypothesis/py3/.dist-info/METADATA index c9d68c0949..4e06dfd75c 100644 --- a/contrib/python/hypothesis/py3/.dist-info/METADATA +++ b/contrib/python/hypothesis/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: hypothesis -Version: 6.107.0 +Version: 6.108.0 Summary: A library for property-based testing Home-page: https://hypothesis.works Author: David R. MacIver and Zac Hatfield-Dodds diff --git a/contrib/python/hypothesis/py3/hypothesis/core.py b/contrib/python/hypothesis/py3/hypothesis/core.py index 2e21b98f64..6b443a1427 100644 --- a/contrib/python/hypothesis/py3/hypothesis/core.py +++ b/contrib/python/hypothesis/py3/hypothesis/core.py @@ -58,7 +58,8 @@ from hypothesis.errors import ( DeadlineExceeded, DidNotReproduce, FailedHealthCheck, - Flaky, + FlakyFailure, + FlakyReplay, Found, HypothesisException, HypothesisWarning, @@ -999,12 +1000,27 @@ class StateForActualGivenExecution: ) else: report("Failed to reproduce exception. Expected: \n" + traceback) - raise Flaky( + raise FlakyFailure( f"Hypothesis {text_repr} produces unreliable results: " - "Falsified on the first call but did not on a subsequent one" - ) from exception + "Falsified on the first call but did not on a subsequent one", + [exception], + ) return result + def _flaky_replay_to_failure( + self, err: FlakyReplay, context: BaseException + ) -> FlakyFailure: + interesting_examples = [ + self._runner.interesting_examples[io] + for io in err._interesting_origins + if io + ] + exceptions = [ + ie.extra_information._expected_exception for ie in interesting_examples + ] + exceptions.append(context) # the offending assume (or whatever) + return FlakyFailure(err.reason, exceptions) + def _execute_once_for_engine(self, data: ConjectureData) -> None: """Wrapper around ``execute_once`` that intercepts test failure exceptions and single-test control exceptions, and turns them into @@ -1037,7 +1053,12 @@ class StateForActualGivenExecution: except UnsatisfiedAssumption as e: # An "assume" check failed, so instead we inform the engine that # this test run was invalid. - data.mark_invalid(e.reason) + try: + data.mark_invalid(e.reason) + except FlakyReplay as err: + # This was unexpected, meaning that the assume was flaky. + # Report it as such. + raise self._flaky_replay_to_failure(err, e) from None except StopTest: # The engine knows how to handle this control exception, so it's # OK to re-raise it. @@ -1217,22 +1238,28 @@ class StateForActualGivenExecution: info._expected_traceback, ), ) - except StopTest: - err = Flaky( - "Inconsistent results: An example which failed on the " - "first run now succeeds (or fails with another error)." - ) + except StopTest as e: # Link the expected exception from the first run. Not sure # how to access the current exception, if it failed - # differently on this run. - err.__cause__ = err.__context__ = info._expected_exception + # differently on this run. In fact, in the only known + # reproducer, the StopTest is caused by OVERRUN before the + # test is even executed. Possibly because all initial examples + # failed until the final non-traced replay, and something was + # exhausted? Possibly a FIXME, but sufficiently weird to + # ignore for now. + err = FlakyFailure( + "Inconsistent results: An example failed on the " + "first run but now succeeds (or fails with another " + "error, or is for some reason not runnable).", + [info._expected_exception or e], # (note: e is a BaseException) + ) errors_to_report.append((fragments, err)) except UnsatisfiedAssumption as e: # pragma: no cover # ironically flaky - err = Flaky( + err = FlakyFailure( "Unreliable assumption: An example which satisfied " - "assumptions on the first run now fails it." + "assumptions on the first run now fails it.", + [e], ) - err.__cause__ = err.__context__ = e errors_to_report.append((fragments, err)) except BaseException as e: # If we have anything for explain-mode, this is the time to report. diff --git a/contrib/python/hypothesis/py3/hypothesis/errors.py b/contrib/python/hypothesis/py3/hypothesis/errors.py index 0b2c297084..1dcf4493b2 100644 --- a/contrib/python/hypothesis/py3/hypothesis/errors.py +++ b/contrib/python/hypothesis/py3/hypothesis/errors.py @@ -8,6 +8,8 @@ # 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/. +from hypothesis.internal.compat import ExceptionGroup + class HypothesisException(Exception): """Generic parent class for exceptions thrown by Hypothesis.""" @@ -52,9 +54,42 @@ class Unsatisfiable(_Trimmable): class Flaky(_Trimmable): + """Base class for indeterministic failures. Usually one of the more + specific subclasses (FlakyFailure or FlakyStrategyDefinition) is raised.""" + + +class FlakyReplay(Flaky): + """Internal error raised by the conjecture engine if flaky failures are + detected during replay. + + Carries information allowing the runner to reconstruct the flakiness as + a FlakyFailure exception group for final presentation. + """ + + def __init__(self, reason, interesting_origins=None): + super().__init__(reason) + self.reason = reason + self._interesting_origins = interesting_origins + + +class FlakyStrategyDefinition(Flaky): + """This function appears to cause inconsistent data generation. + + Common causes for this problem are: + 1. The strategy depends on external state. e.g. it uses an external + random number generator. Try to make a version that passes all the + relevant state in from Hypothesis. + """ + + +class _WrappedBaseException(Exception): + """Used internally for wrapping BaseExceptions as components of FlakyFailure.""" + + +class FlakyFailure(ExceptionGroup, Flaky): """This function appears to fail non-deterministically: We have seen it fail when passed this example at least once, but a subsequent invocation - did not fail. + did not fail, or caused a distinct error. Common causes for this problem are: 1. The function depends on external state. e.g. it uses an external @@ -67,6 +102,19 @@ class Flaky(_Trimmable): don't do that and testing those instead. """ + def __new__(cls, msg, group): + # The Exception mixin forces this an ExceptionGroup (only accepting + # Exceptions, not BaseException). Usually BaseException is raised + # directly and will hence not be part of a FlakyFailure, but I'm not + # sure this assumption holds everywhere. So wrap any BaseExceptions. + group = list(group) + for i, exc in enumerate(group): + if not isinstance(exc, Exception): + err = _WrappedBaseException() + err.__cause__ = err.__context__ = exc + group[i] = err + return ExceptionGroup.__new__(cls, msg, group) + class InvalidArgument(_Trimmable, TypeError): """Used to indicate that the arguments to a Hypothesis function were in diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/charmap.py b/contrib/python/hypothesis/py3/hypothesis/internal/charmap.py index 6f41fbf021..468beeb5e8 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/charmap.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/charmap.py @@ -19,6 +19,7 @@ from functools import lru_cache from typing import Dict, Tuple from hypothesis.configuration import storage_directory +from hypothesis.control import _current_build_context from hypothesis.errors import InvalidArgument from hypothesis.internal.intervalsets import IntervalSet @@ -214,10 +215,14 @@ def _query_for_key(key): >>> _query_for_key(('Zl', 'Zp', 'Co')) ((8232, 8233), (57344, 63743), (983040, 1048573), (1048576, 1114109)) """ - try: - return category_index_cache[key] - except KeyError: - pass + context = _current_build_context.value + if context is None or not context.data.provider.avoid_realization: + try: + return category_index_cache[key] + except KeyError: + pass + elif not key: # pragma: no cover # only on alternative backends + return () assert key if set(key) == set(categories()): result = IntervalSet([(0, sys.maxunicode)]) @@ -226,7 +231,8 @@ def _query_for_key(key): IntervalSet(charmap()[key[-1]]) ) assert isinstance(result, IntervalSet) - category_index_cache[key] = result.intervals + if context is None or not context.data.provider.avoid_realization: + category_index_cache[key] = result.intervals return result.intervals @@ -268,15 +274,18 @@ def query( character_intervals.intervals, exclude_intervals.intervals, ) - try: - return limited_category_index_cache[qkey] - except KeyError: - pass + context = _current_build_context.value + if context is None or not context.data.provider.avoid_realization: + try: + return limited_category_index_cache[qkey] + except KeyError: + pass base = _query_for_key(catkey) result = [] for u, v in base: if v >= min_codepoint and u <= max_codepoint: result.append((max(u, min_codepoint), min(v, max_codepoint))) result = (IntervalSet(result) | character_intervals) - exclude_intervals - limited_category_index_cache[qkey] = result + if context is None or not context.data.provider.avoid_realization: + limited_category_index_cache[qkey] = result return result diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py index 7768d5ec64..ead0157395 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/datatree.py @@ -14,7 +14,12 @@ from typing import List, Optional, Union import attr -from hypothesis.errors import Flaky, HypothesisException, StopTest +from hypothesis.errors import ( + FlakyReplay, + FlakyStrategyDefinition, + HypothesisException, + StopTest, +) from hypothesis.internal import floats as flt from hypothesis.internal.compat import int_to_bytes from hypothesis.internal.conjecture.data import ( @@ -45,7 +50,7 @@ class PreviouslyUnseenBehaviour(HypothesisException): def inconsistent_generation(): - raise Flaky( + raise FlakyStrategyDefinition( "Inconsistent data generation! Data generation behaved differently " "between different runs. Is your data generation depending on external " "state?" @@ -469,7 +474,7 @@ class TreeNode: Splits the tree so that it can incorporate a decision at the draw call corresponding to the node at position i. - Raises Flaky if node i was forced. + Raises FlakyStrategyDefinition if node i was forced. """ if i in self.forced: @@ -1142,10 +1147,13 @@ class TreeRecordingObserver(DataObserver): node.transition.status != Status.INTERESTING or new_transition.status != Status.VALID ): - raise Flaky( + old_origin = node.transition.interesting_origin + new_origin = new_transition.interesting_origin + raise FlakyReplay( f"Inconsistent results from replaying a test case!\n" - f" last: {node.transition.status.name} from {node.transition.interesting_origin}\n" - f" this: {new_transition.status.name} from {new_transition.interesting_origin}" + f" last: {node.transition.status.name} from {old_origin}\n" + f" this: {new_transition.status.name} from {new_origin}", + (old_origin, new_origin), ) else: node.transition = new_transition diff --git a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py index 6e818f341a..71f2ccc0b5 100644 --- a/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py +++ b/contrib/python/hypothesis/py3/hypothesis/internal/conjecture/engine.py @@ -10,6 +10,7 @@ import importlib import math +import textwrap import time from collections import defaultdict from contextlib import contextmanager @@ -39,7 +40,12 @@ import attr from hypothesis import HealthCheck, Phase, Verbosity, settings as Settings from hypothesis._settings import local_settings from hypothesis.database import ExampleDatabase -from hypothesis.errors import Flaky, HypothesisException, InvalidArgument, StopTest +from hypothesis.errors import ( + FlakyReplay, + HypothesisException, + InvalidArgument, + StopTest, +) from hypothesis.internal.cache import LRUReusedCache from hypothesis.internal.compat import ( NotRequired, @@ -297,7 +303,7 @@ class ConjectureRunner: def __stoppable_test_function(self, data: ConjectureData) -> None: """Run ``self._test_function``, but convert a ``StopTest`` exception - into a normal return and avoid raising Flaky for RecursionErrors. + into a normal return and avoid raising anything flaky for RecursionErrors. """ # We ensure that the test has this much stack space remaining, no # matter the size of the stack when called, to de-flake RecursionErrors @@ -528,18 +534,24 @@ class ConjectureRunner: # drive the ir tree through the test function to convert it # to a buffer initial_origin = data.interesting_origin + initial_traceback = data.extra_information._expected_traceback # type: ignore data = ConjectureData.for_ir_tree(data.examples.ir_tree_nodes) self.__stoppable_test_function(data) data.freeze() - # we'd like to use expected_failure machinery here from - # StateForActualGivenExecution for better diagnostic reports of eg - # flaky deadlines, but we're too low down in the engine for that. - # for now a worse generic flaky error will have to do. + # TODO: Convert to FlakyFailure on the way out. Should same-origin + # also be checked? if data.status != Status.INTERESTING: - raise Flaky( + desc_new_status = { + data.status.VALID: "passed", + data.status.INVALID: "failed filters", + data.status.OVERRUN: "overran", + }[data.status] + wrapped_tb = textwrap.indent(initial_traceback, " | ") + raise FlakyReplay( f"Inconsistent results from replaying a failing test case!\n" - f" last: {Status.INTERESTING.name} from {initial_origin}\n" - f" this: {data.status.name}" + f"{wrapped_tb}on backend={self.settings.backend!r} but " + f"{desc_new_status} under backend='hypothesis'", + interesting_origins=[initial_origin], ) self._cache(data) diff --git a/contrib/python/hypothesis/py3/hypothesis/version.py b/contrib/python/hypothesis/py3/hypothesis/version.py index d81e1c4cef..43afdc8b83 100644 --- a/contrib/python/hypothesis/py3/hypothesis/version.py +++ b/contrib/python/hypothesis/py3/hypothesis/version.py @@ -8,5 +8,5 @@ # 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/. -__version_info__ = (6, 107, 0) +__version_info__ = (6, 108, 0) __version__ = ".".join(map(str, __version_info__)) diff --git a/contrib/python/hypothesis/py3/ya.make b/contrib/python/hypothesis/py3/ya.make index adf8c79866..3af6afae24 100644 --- a/contrib/python/hypothesis/py3/ya.make +++ b/contrib/python/hypothesis/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(6.107.0) +VERSION(6.108.0) LICENSE(MPL-2.0) |