Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/hypothesis/internal/escalation.py: 41%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

90 statements  

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 

11import contextlib 

12import os 

13import sys 

14import textwrap 

15import traceback 

16from functools import partial 

17from inspect import getframeinfo 

18from pathlib import Path 

19from types import ModuleType, TracebackType 

20from typing import Callable, NamedTuple, Optional 

21 

22import hypothesis 

23from hypothesis.errors import _Trimmable 

24from hypothesis.internal.compat import BaseExceptionGroup 

25from hypothesis.utils.dynamicvariables import DynamicVariable 

26 

27FILE_CACHE: dict[ModuleType, dict[str, bool]] = {} 

28 

29 

30def belongs_to(package: ModuleType) -> Callable[[str], bool]: 

31 if getattr(package, "__file__", None) is None: # pragma: no cover 

32 return lambda filepath: False 

33 

34 assert package.__file__ is not None 

35 FILE_CACHE.setdefault(package, {}) 

36 cache = FILE_CACHE[package] 

37 root = Path(package.__file__).resolve().parent 

38 

39 def accept(filepath: str) -> bool: 

40 try: 

41 return cache[filepath] 

42 except KeyError: 

43 pass 

44 try: 

45 Path(filepath).resolve().relative_to(root) 

46 result = True 

47 except Exception: 

48 result = False 

49 cache[filepath] = result 

50 return result 

51 

52 accept.__name__ = f"is_{package.__name__}_file" 

53 return accept 

54 

55 

56is_hypothesis_file = belongs_to(hypothesis) 

57 

58 

59def get_trimmed_traceback( 

60 exception: Optional[BaseException] = None, 

61) -> Optional[TracebackType]: 

62 """Return the current traceback, minus any frames added by Hypothesis.""" 

63 if exception is None: 

64 _, exception, tb = sys.exc_info() 

65 else: 

66 tb = exception.__traceback__ 

67 # Avoid trimming the traceback if we're in verbose mode, or the error 

68 # was raised inside Hypothesis. Additionally, the environment variable 

69 # HYPOTHESIS_NO_TRACEBACK_TRIM is respected if nonempty, because verbose 

70 # mode is prohibitively slow when debugging strategy recursion errors. 

71 assert hypothesis.settings.default is not None 

72 if ( 

73 tb is None 

74 or os.environ.get("HYPOTHESIS_NO_TRACEBACK_TRIM") 

75 or hypothesis.settings.default.verbosity >= hypothesis.Verbosity.debug 

76 or ( 

77 is_hypothesis_file(traceback.extract_tb(tb)[-1][0]) 

78 and not isinstance(exception, _Trimmable) 

79 ) 

80 ): 

81 return tb 

82 while tb.tb_next is not None and ( 

83 # If the frame is from one of our files, it's been added by Hypothesis. 

84 is_hypothesis_file(getframeinfo(tb.tb_frame).filename) 

85 # But our `@proxies` decorator overrides the source location, 

86 # so we check for an attribute it injects into the frame too. 

87 or tb.tb_frame.f_globals.get("__hypothesistracebackhide__") is True 

88 ): 

89 tb = tb.tb_next 

90 return tb 

91 

92 

93class InterestingOrigin(NamedTuple): 

94 # The `interesting_origin` is how Hypothesis distinguishes between multiple 

95 # failures, for reporting and also to replay from the example database (even 

96 # if report_multiple_bugs=False). We traditionally use the exception type and 

97 # location, but have extracted this logic in order to see through `except ...:` 

98 # blocks and understand the __cause__ (`raise x from y`) or __context__ that 

99 # first raised an exception as well as PEP-654 exception groups. 

100 exc_type: type[BaseException] 

101 filename: Optional[str] 

102 lineno: Optional[int] 

103 context: "InterestingOrigin | tuple[()]" 

104 group_elems: "tuple[InterestingOrigin, ...]" 

105 

106 def __str__(self) -> str: 

107 ctx = "" 

108 if self.context: 

109 ctx = textwrap.indent(f"\ncontext: {self.context}", prefix=" ") 

110 group = "" 

111 if self.group_elems: 

112 chunks = "\n ".join(str(x) for x in self.group_elems) 

113 group = textwrap.indent(f"\nchild exceptions:\n {chunks}", prefix=" ") 

114 return f"{self.exc_type.__name__} at {self.filename}:{self.lineno}{ctx}{group}" 

115 

116 @classmethod 

117 def from_exception( 

118 cls, exception: BaseException, /, seen: tuple[BaseException, ...] = () 

119 ) -> "InterestingOrigin": 

120 filename, lineno = None, None 

121 if tb := get_trimmed_traceback(exception): 

122 filename, lineno, *_ = traceback.extract_tb(tb)[-1] 

123 seen = (*seen, exception) 

124 make = partial(cls.from_exception, seen=seen) 

125 context: InterestingOrigin | tuple[()] = () 

126 if exception.__context__ is not None and exception.__context__ not in seen: 

127 context = make(exception.__context__) 

128 return cls( 

129 type(exception), 

130 filename, 

131 lineno, 

132 # Note that if __cause__ is set it is always equal to __context__, explicitly 

133 # to support introspection when debugging, so we can use that unconditionally. 

134 context, 

135 # We distinguish exception groups by the inner exceptions, as for __context__ 

136 ( 

137 tuple(make(exc) for exc in exception.exceptions if exc not in seen) 

138 if isinstance(exception, BaseExceptionGroup) 

139 else () 

140 ), 

141 ) 

142 

143 

144current_pytest_item = DynamicVariable(None) 

145 

146 

147def _get_exceptioninfo(): 

148 # ExceptionInfo was moved to the top-level namespace in Pytest 7.0 

149 if "pytest" in sys.modules: 

150 with contextlib.suppress(Exception): 

151 # From Pytest 7, __init__ warns on direct calls. 

152 return sys.modules["pytest"].ExceptionInfo.from_exc_info 

153 if "_pytest._code" in sys.modules: # old versions only 

154 with contextlib.suppress(Exception): 

155 return sys.modules["_pytest._code"].ExceptionInfo 

156 return None # pragma: no cover # coverage tests always use pytest 

157 

158 

159def format_exception(err, tb): 

160 # Try using Pytest to match the currently configured traceback style 

161 ExceptionInfo = _get_exceptioninfo() 

162 if current_pytest_item.value is not None and ExceptionInfo is not None: 

163 item = current_pytest_item.value 

164 return str(item.repr_failure(ExceptionInfo((type(err), err, tb)))) + "\n" 

165 

166 # Or use better_exceptions, if that's installed and enabled 

167 if "better_exceptions" in sys.modules: 

168 better_exceptions = sys.modules["better_exceptions"] 

169 if sys.excepthook is better_exceptions.excepthook: 

170 return "".join(better_exceptions.format_exception(type(err), err, tb)) 

171 

172 # If all else fails, use the standard-library formatting tools 

173 return "".join(traceback.format_exception(type(err), err, tb))