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

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

92 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 dataclasses import dataclass 

17from functools import partial 

18from inspect import getframeinfo 

19from pathlib import Path 

20from types import ModuleType, TracebackType 

21from typing import Callable, Optional 

22 

23import hypothesis 

24from hypothesis.errors import _Trimmable 

25from hypothesis.internal.compat import BaseExceptionGroup 

26from hypothesis.utils.dynamicvariables import DynamicVariable 

27 

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

29 

30 

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

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

33 return lambda filepath: False 

34 

35 assert package.__file__ is not None 

36 FILE_CACHE.setdefault(package, {}) 

37 cache = FILE_CACHE[package] 

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

39 

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

41 try: 

42 return cache[filepath] 

43 except KeyError: 

44 pass 

45 try: 

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

47 result = True 

48 except Exception: 

49 result = False 

50 cache[filepath] = result 

51 return result 

52 

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

54 return accept 

55 

56 

57is_hypothesis_file = belongs_to(hypothesis) 

58 

59 

60def get_trimmed_traceback( 

61 exception: Optional[BaseException] = None, 

62) -> Optional[TracebackType]: 

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

64 if exception is None: 

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

66 else: 

67 tb = exception.__traceback__ 

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

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

70 # HYPOTHESIS_NO_TRACEBACK_TRIM is respected if nonempty, because verbose 

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

72 assert hypothesis.settings.default is not None 

73 if ( 

74 tb is None 

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

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

77 or ( 

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

79 and not isinstance(exception, _Trimmable) 

80 ) 

81 ): 

82 return tb 

83 while tb.tb_next is not None and ( 

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

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

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

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

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

89 ): 

90 tb = tb.tb_next 

91 return tb 

92 

93 

94@dataclass(frozen=True) 

95class InterestingOrigin: 

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

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

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

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

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

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

102 exc_type: type[BaseException] 

103 filename: Optional[str] 

104 lineno: Optional[int] 

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

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

107 

108 def __str__(self) -> str: 

109 ctx = "" 

110 if self.context: 

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

112 group = "" 

113 if self.group_elems: 

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

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

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

117 

118 @classmethod 

119 def from_exception( 

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

121 ) -> "InterestingOrigin": 

122 filename, lineno = None, None 

123 if tb := get_trimmed_traceback(exception): 

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

125 seen = (*seen, exception) 

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

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

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

129 context = make(exception.__context__) 

130 return cls( 

131 type(exception), 

132 filename, 

133 lineno, 

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

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

136 context, 

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

138 ( 

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

140 if isinstance(exception, BaseExceptionGroup) 

141 else () 

142 ), 

143 ) 

144 

145 

146current_pytest_item = DynamicVariable(None) 

147 

148 

149def _get_exceptioninfo(): 

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

151 if "pytest" in sys.modules: 

152 with contextlib.suppress(Exception): 

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

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

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

156 with contextlib.suppress(Exception): 

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

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

159 

160 

161def format_exception(err, tb): 

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

163 ExceptionInfo = _get_exceptioninfo() 

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

165 item = current_pytest_item.value 

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

167 

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

169 if "better_exceptions" in sys.modules: 

170 better_exceptions = sys.modules["better_exceptions"] 

171 if sys.excepthook is better_exceptions.excepthook: 

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

173 

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

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