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

96 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 collections.abc import Callable 

17from dataclasses import dataclass 

18from functools import partial 

19from inspect import getfile, getsourcefile 

20from pathlib import Path 

21from types import ModuleType, TracebackType 

22 

23import hypothesis 

24from hypothesis.errors import _Trimmable 

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 _last_frame_location(tb: TracebackType) -> tuple[str, int | None]: 

60 # Equivalent to ``traceback.extract_tb(tb)[-1][:2]``, but much cheaper because 

61 # we don't compute positions or look up source lines for every frame. 

62 while tb.tb_next is not None: 

63 tb = tb.tb_next 

64 return tb.tb_frame.f_code.co_filename, tb.tb_lineno 

65 

66 

67def get_trimmed_traceback( 

68 exception: BaseException | None = None, 

69) -> TracebackType | None: 

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

71 if exception is None: 

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

73 else: 

74 tb = exception.__traceback__ 

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

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

77 # HYPOTHESIS_NO_TRACEBACK_TRIM is respected if nonempty, because verbose 

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

79 assert hypothesis.settings.default is not None 

80 if ( 

81 tb is None 

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

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

84 or ( 

85 is_hypothesis_file(_last_frame_location(tb)[0]) 

86 and not isinstance(exception, _Trimmable) 

87 ) 

88 ): 

89 return tb 

90 while tb.tb_next is not None and ( 

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

92 is_hypothesis_file(getsourcefile(tb.tb_frame) or getfile(tb.tb_frame)) 

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

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

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

96 ): 

97 tb = tb.tb_next 

98 return tb 

99 

100 

101@dataclass(slots=True, frozen=True) 

102class InterestingOrigin: 

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

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

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

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

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

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

109 exc_type: type[BaseException] 

110 filename: str | None 

111 lineno: int | None 

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

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

114 

115 def __str__(self) -> str: 

116 ctx = "" 

117 if self.context: 

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

119 group = "" 

120 if self.group_elems: 

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

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

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

124 

125 @classmethod 

126 def from_exception( 

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

128 ) -> "InterestingOrigin": 

129 filename, lineno = None, None 

130 if tb := get_trimmed_traceback(exception): 

131 filename, lineno = _last_frame_location(tb) 

132 seen = (*seen, exception) 

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

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

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

136 context = make(exception.__context__) 

137 return cls( 

138 type(exception), 

139 filename, 

140 lineno, 

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

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

143 context, 

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

145 ( 

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

147 if isinstance(exception, BaseExceptionGroup) 

148 else () 

149 ), 

150 ) 

151 

152 

153current_pytest_item = DynamicVariable(None) 

154 

155 

156def _get_exceptioninfo(): 

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

158 if "pytest" in sys.modules: 

159 with contextlib.suppress(Exception): 

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

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

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

163 with contextlib.suppress(Exception): 

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

165 return None 

166 

167 

168def format_exception(err, tb): 

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

170 ExceptionInfo = _get_exceptioninfo() 

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

172 item = current_pytest_item.value 

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

174 

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

176 if "better_exceptions" in sys.modules: 

177 better_exceptions = sys.modules["better_exceptions"] 

178 if sys.excepthook is better_exceptions.excepthook: 

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

180 

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

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