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
11"""This file can approximately be considered the collection of hypothesis going
12to really unreasonable lengths to produce pretty output."""
13
14import ast
15import hashlib
16import inspect
17import re
18import textwrap
19import types
20import warnings
21from collections.abc import Callable, Sequence
22from functools import partial, wraps
23from inspect import Parameter, Signature
24from io import StringIO
25from keyword import iskeyword
26from random import _inst as global_random_instance
27from tokenize import COMMENT, generate_tokens, untokenize
28from types import EllipsisType, ModuleType
29from typing import TYPE_CHECKING, Any, TypeVar, Union
30from unittest.mock import _patch as PatchType
31
32from hypothesis.errors import HypothesisWarning
33from hypothesis.internal import lambda_sources
34from hypothesis.internal.compat import is_typed_named_tuple
35from hypothesis.utils.conventions import not_set
36from hypothesis.vendor.pretty import pretty
37
38if TYPE_CHECKING:
39 from hypothesis.strategies._internal.strategies import SearchStrategy
40
41T = TypeVar("T")
42
43
44def is_mock(obj: object) -> bool:
45 """Determine if the given argument is a mock type."""
46
47 # We want to be able to detect these when dealing with various test
48 # args. As they are sneaky and can look like almost anything else,
49 # we'll check this by looking for an attribute with a name that it's really
50 # unlikely to implement accidentally, and that anyone who implements it
51 # deliberately should know what they're doing. This is more robust than
52 # looking for types.
53 return hasattr(obj, "hypothesis_internal_is_this_a_mock_check")
54
55
56def _clean_source(src: str) -> bytes:
57 """Return the source code as bytes, without decorators or comments.
58
59 Because this is part of our database key, we reduce the cache invalidation
60 rate by ignoring decorators, comments, trailing whitespace, and empty lines.
61 We can't just use the (dumped) AST directly because it changes between Python
62 versions (e.g. ast.Constant)
63 """
64 # Get the (one-indexed) line number of the function definition, and drop preceding
65 # lines - i.e. any decorators, so that adding `@example()`s keeps the same key.
66 try:
67 funcdef = ast.parse(src).body[0]
68 src = "".join(src.splitlines(keepends=True)[funcdef.lineno - 1 :])
69 except Exception:
70 pass
71 # Remove blank lines and use the tokenize module to strip out comments,
72 # so that those can be changed without changing the database key.
73 try:
74 src = untokenize(
75 t for t in generate_tokens(StringIO(src).readline) if t.type != COMMENT
76 )
77 except Exception:
78 pass
79 # Finally, remove any trailing whitespace and empty lines as a last cleanup.
80 return "\n".join(x.rstrip() for x in src.splitlines() if x.rstrip()).encode()
81
82
83def function_digest(function: Any) -> bytes:
84 """Returns a string that is stable across multiple invocations across
85 multiple processes and is prone to changing significantly in response to
86 minor changes to the function.
87
88 No guarantee of uniqueness though it usually will be. Digest collisions
89 lead to unfortunate but not fatal problems during database replay.
90 """
91 hasher = hashlib.sha384()
92 try:
93 src = inspect.getsource(function)
94 except (OSError, TypeError):
95 # If we can't actually get the source code, try for the name as a fallback.
96 # NOTE: We might want to change this to always adding function.__qualname__,
97 # to differentiate f.x. two classes having the same function implementation
98 # with class-dependent behaviour.
99 try:
100 hasher.update(function.__name__.encode())
101 except AttributeError:
102 pass
103 else:
104 hasher.update(_clean_source(src))
105 try:
106 # This is additional to the source code because it can include the effects
107 # of decorators, or of post-hoc assignment to the .__signature__ attribute.
108 hasher.update(repr(get_signature(function)).encode())
109 except Exception:
110 pass
111 try:
112 # We set this in order to distinguish e.g. @pytest.mark.parametrize cases.
113 hasher.update(function._hypothesis_internal_add_digest)
114 except AttributeError:
115 pass
116 return hasher.digest()
117
118
119def check_signature(sig: Signature) -> None: # pragma: no cover # 3.10 only
120 # Backport from Python 3.11; see https://github.com/python/cpython/pull/92065
121 for p in sig.parameters.values():
122 if iskeyword(p.name) and p.kind is not p.POSITIONAL_ONLY:
123 raise ValueError(
124 f"Signature {sig!r} contains a parameter named {p.name!r}, "
125 f"but this is a SyntaxError because `{p.name}` is a keyword. "
126 "You, or a library you use, must have manually created an "
127 "invalid signature - this will be an error in Python 3.11+"
128 )
129
130
131def get_signature(
132 target: Any, *, follow_wrapped: bool = True, eval_str: bool = False
133) -> Signature:
134 # Special case for use of `@unittest.mock.patch` decorator, mimicking the
135 # behaviour of getfullargspec instead of reporting unusable arguments.
136 patches = getattr(target, "patchings", None)
137 if isinstance(patches, list) and all(isinstance(p, PatchType) for p in patches):
138 return Signature(
139 [
140 Parameter("args", Parameter.VAR_POSITIONAL),
141 Parameter("keywargs", Parameter.VAR_KEYWORD),
142 ]
143 )
144
145 if isinstance(getattr(target, "__signature__", None), Signature):
146 # This special case covers unusual codegen like Pydantic models
147 sig = target.__signature__
148 check_signature(sig)
149 # And *this* much more complicated block ignores the `self` argument
150 # if that's been (incorrectly) included in the custom signature.
151 if sig.parameters and (inspect.isclass(target) or inspect.ismethod(target)):
152 selfy = next(iter(sig.parameters.values()))
153 if (
154 selfy.name == "self"
155 and selfy.default is Parameter.empty
156 and selfy.kind.name.startswith("POSITIONAL_")
157 ):
158 return sig.replace(
159 parameters=[v for k, v in sig.parameters.items() if k != "self"]
160 )
161 return sig
162 sig = inspect.signature(target, follow_wrapped=follow_wrapped, eval_str=eval_str)
163 check_signature(sig)
164 return sig
165
166
167def arg_is_required(param: Parameter) -> bool:
168 return param.default is Parameter.empty and param.kind in (
169 Parameter.POSITIONAL_OR_KEYWORD,
170 Parameter.KEYWORD_ONLY,
171 )
172
173
174def required_args(
175 target: Callable[..., Any],
176 args: tuple["SearchStrategy[Any]", ...] = (),
177 kwargs: dict[str, Union["SearchStrategy[Any]", EllipsisType]] | None = None,
178) -> set[str]:
179 """Return a set of names of required args to target that were not supplied
180 in args or kwargs.
181
182 This is used in builds() to determine which arguments to attempt to
183 fill from type hints. target may be any callable (including classes
184 and bound methods). args and kwargs should be as they are passed to
185 builds() - that is, a tuple of values and a dict of names: values.
186 """
187 kwargs = {} if kwargs is None else kwargs
188 # We start with a workaround for NamedTuples, which don't have nice inits
189 if inspect.isclass(target) and is_typed_named_tuple(target):
190 fields = target._fields # type: ignore
191 provided = set(kwargs) | set(fields[: len(args)])
192 return set(fields) - provided
193 # Then we try to do the right thing with inspect.signature
194 try:
195 sig = get_signature(target)
196 except (ValueError, TypeError):
197 return set()
198 return {
199 name
200 for name, param in list(sig.parameters.items())[len(args) :]
201 if arg_is_required(param) and name not in kwargs
202 }
203
204
205def convert_keyword_arguments(
206 function: Any, args: Sequence[object], kwargs: dict[str, object]
207) -> tuple[tuple[object, ...], dict[str, object]]:
208 """Returns a pair of a tuple and a dictionary which would be equivalent
209 passed as positional and keyword args to the function. Unless function has
210 kwonlyargs or **kwargs the dictionary will always be empty.
211 """
212 sig = inspect.signature(function, follow_wrapped=False)
213 bound = sig.bind(*args, **kwargs)
214 return bound.args, bound.kwargs
215
216
217def convert_positional_arguments(
218 function: Any, args: Sequence[object], kwargs: dict[str, object]
219) -> tuple[tuple[object, ...], dict[str, object]]:
220 """Return a tuple (new_args, new_kwargs) where all possible arguments have
221 been moved to kwargs.
222
223 new_args will only be non-empty if function has pos-only args or *args.
224 """
225 sig = inspect.signature(function, follow_wrapped=False)
226 bound = sig.bind(*args, **kwargs)
227 new_args = []
228 new_kwargs = dict(bound.arguments)
229 for p in sig.parameters.values():
230 if p.name in new_kwargs:
231 if p.kind is p.POSITIONAL_ONLY:
232 new_args.append(new_kwargs.pop(p.name))
233 elif p.kind is p.VAR_POSITIONAL:
234 new_args.extend(new_kwargs.pop(p.name))
235 elif p.kind is p.VAR_KEYWORD:
236 assert set(new_kwargs[p.name]).isdisjoint(set(new_kwargs) - {p.name})
237 new_kwargs.update(new_kwargs.pop(p.name))
238 return tuple(new_args), new_kwargs
239
240
241def ast_arguments_matches_signature(args: ast.arguments, sig: Signature) -> bool:
242 expected: list[tuple[str, int]] = []
243 for node in args.posonlyargs:
244 expected.append((node.arg, Parameter.POSITIONAL_ONLY))
245 for node in args.args:
246 expected.append((node.arg, Parameter.POSITIONAL_OR_KEYWORD))
247 if args.vararg is not None:
248 expected.append((args.vararg.arg, Parameter.VAR_POSITIONAL))
249 for node in args.kwonlyargs:
250 expected.append((node.arg, Parameter.KEYWORD_ONLY))
251 if args.kwarg is not None:
252 expected.append((args.kwarg.arg, Parameter.VAR_KEYWORD))
253 return expected == [(p.name, p.kind) for p in sig.parameters.values()]
254
255
256def is_first_param_referenced_in_function(f: Any) -> bool:
257 """Is the given name referenced within f?"""
258 try:
259 tree = ast.parse(textwrap.dedent(inspect.getsource(f)))
260 except Exception:
261 return True # Assume it's OK unless we know otherwise
262 name = next(iter(get_signature(f).parameters))
263 return any(
264 isinstance(node, ast.Name)
265 and node.id == name
266 and isinstance(node.ctx, ast.Load)
267 for node in ast.walk(tree)
268 )
269
270
271def function_location(f: object) -> str | None:
272 """Return a ``filename:lineno`` string for where ``f`` was defined, if known."""
273 try:
274 code = f.__code__ # type: ignore[attr-defined]
275 return f"{code.co_filename}:{code.co_firstlineno}"
276 except AttributeError:
277 return None
278
279
280def get_pretty_function_description(f: object) -> str:
281 # Anything which knows how to pretty-print itself knows better than we do -
282 # e.g. the constant functions from st.functions(), which would otherwise
283 # borrow the name of the function they imitate. We skip classes, whose
284 # instance method would be called with the printer as `self`, and require
285 # a callable so that pretty() can't bounce straight back to us.
286 if not isinstance(f, type) and callable(getattr(f, "_repr_pretty_", None)):
287 return pretty(f)
288 if isinstance(f, partial):
289 return pretty(f)
290 if not hasattr(f, "__name__"):
291 return repr(f)
292 name = f.__name__
293 if name == "<lambda>":
294 return lambda_sources.lambda_description(f)
295 elif isinstance(f, (types.MethodType, types.BuiltinMethodType)):
296 self = f.__self__
297 # Some objects, like `builtins.abs` are of BuiltinMethodType but have
298 # their module as __self__. This might include c-extensions generally?
299 if not (self is None or inspect.isclass(self) or inspect.ismodule(self)):
300 if self is global_random_instance:
301 return f"random.{name}"
302 return f"{self!r}.{name}"
303 elif isinstance(name, str) and getattr(dict, name, object()) is f:
304 # special case for keys/values views in from_type() / ghostwriter output
305 return f"dict.{name}"
306 return name
307
308
309def nicerepr(v: Any) -> str:
310 if inspect.isfunction(v):
311 return get_pretty_function_description(v)
312 elif isinstance(v, type):
313 return v.__name__
314 else:
315 # With TypeVar T, show List[T] instead of TypeError on List[~T]
316 return re.sub(r"(\[)~([A-Z][a-z]*\])", r"\g<1>\g<2>", pretty(v))
317
318
319def repr_call(
320 f: Any, args: Sequence[object], kwargs: dict[str, object], *, reorder: bool = True
321) -> str:
322 # Note: for multi-line pretty-printing, see RepresentationPrinter.repr_call()
323 if reorder:
324 args, kwargs = convert_positional_arguments(f, args, kwargs)
325
326 bits = [nicerepr(x) for x in args]
327
328 for p in get_signature(f).parameters.values():
329 if p.name in kwargs and not p.kind.name.startswith("VAR_"):
330 bits.append(f"{p.name}={nicerepr(kwargs.pop(p.name))}")
331 if kwargs:
332 for a in sorted(kwargs):
333 bits.append(f"{a}={nicerepr(kwargs[a])}")
334
335 rep = nicerepr(f)
336 if rep.startswith("lambda") and ":" in rep:
337 rep = f"({rep})"
338 repr_len = len(rep) + sum(len(b) for b in bits) # approx
339 if repr_len > 30000:
340 warnings.warn(
341 "Generating overly large repr. This is an expensive operation, and with "
342 f"a length of {repr_len//1000} kB is unlikely to be useful. Use -Wignore "
343 "to ignore the warning, or -Werror to get a traceback.",
344 HypothesisWarning,
345 stacklevel=2,
346 )
347 return rep + "(" + ", ".join(bits) + ")"
348
349
350def check_valid_identifier(identifier: str) -> None:
351 if not identifier.isidentifier():
352 raise ValueError(f"{identifier!r} is not a valid python identifier")
353
354
355eval_cache: dict[str, ModuleType] = {}
356
357
358def source_exec_as_module(source: str) -> ModuleType:
359 try:
360 return eval_cache[source]
361 except KeyError:
362 pass
363
364 hexdigest = hashlib.sha384(source.encode()).hexdigest()
365 result = ModuleType("hypothesis_temporary_module_" + hexdigest)
366 # ModuleType() sets __spec__ = None. Later, we call @impersonate on functions defined
367 # in the module, which gives it a real co_filename. Python traceback formatting then
368 # calls internal linecache code which warns on the combination of "real on-disk file"
369 # + "null __spec__".
370 #
371 # Sidestep this by deleting __spec__; we don't need it.
372 del result.__spec__
373 assert isinstance(source, str)
374 exec(source, result.__dict__)
375 eval_cache[source] = result
376 return result
377
378
379COPY_SIGNATURE_SCRIPT = """
380from contextlib import aclosing, closing
381
382from hypothesis.utils.conventions import not_set
383
384def accept({funcname}):
385 {def_prefix}def {name}{signature}:
386 {body}
387 return {name}
388""".lstrip()
389
390
391def get_varargs(
392 sig: Signature, kind: int = Parameter.VAR_POSITIONAL
393) -> Parameter | None:
394 for p in sig.parameters.values():
395 if p.kind is kind:
396 return p
397 return None
398
399
400def define_function_signature(name, docstring, signature):
401 """A decorator which sets the name, signature and docstring of the function
402 passed into it."""
403 if name == "<lambda>":
404 name = "_lambda_"
405 check_valid_identifier(name)
406 for a in signature.parameters:
407 check_valid_identifier(a)
408
409 used_names = {*signature.parameters, name}
410
411 newsig = signature.replace(
412 parameters=[
413 p if p.default is signature.empty else p.replace(default=not_set)
414 for p in (
415 p.replace(annotation=signature.empty)
416 for p in signature.parameters.values()
417 )
418 ],
419 return_annotation=signature.empty,
420 )
421
422 pos_args = [
423 p
424 for p in signature.parameters.values()
425 if p.kind.name.startswith("POSITIONAL_")
426 ]
427
428 def accept(f):
429 fsig = inspect.signature(f, follow_wrapped=False)
430 must_pass_as_kwargs = []
431 invocation_parts = []
432 for p in pos_args:
433 if p.name not in fsig.parameters and get_varargs(fsig) is None:
434 must_pass_as_kwargs.append(p.name)
435 else:
436 invocation_parts.append(p.name)
437 if get_varargs(signature) is not None:
438 invocation_parts.append("*" + get_varargs(signature).name)
439 for k in must_pass_as_kwargs:
440 invocation_parts.append(f"{k}={k}")
441 for p in signature.parameters.values():
442 if p.kind is p.KEYWORD_ONLY:
443 invocation_parts.append(f"{p.name}={p.name}")
444 varkw = get_varargs(signature, kind=Parameter.VAR_KEYWORD)
445 if varkw:
446 invocation_parts.append("**" + varkw.name)
447
448 candidate_names = ["f"] + [f"f_{i}" for i in range(1, len(used_names) + 4)]
449 free_names = [n for n in candidate_names if n not in used_names]
450 funcname, gen, val = free_names[:3]
451
452 invocation = f"{funcname}({', '.join(invocation_parts)})"
453 # Preserve the kind of the wrapped function, so that e.g. proxies for
454 # async or generator functions are themselves async or generators -
455 # closing the proxy also closes the underlying (async) generator.
456 if inspect.iscoroutinefunction(f):
457 def_prefix, body = "async ", f"return await {invocation}"
458 elif inspect.isasyncgenfunction(f):
459 def_prefix = "async "
460 body = (
461 f"async with aclosing({invocation}) as {gen}:\n"
462 f" async for {val} in {gen}:\n"
463 f" yield {val}"
464 )
465 elif inspect.isgeneratorfunction(f):
466 def_prefix = ""
467 body = (
468 f"with closing({invocation}) as {gen}:\n"
469 f" return (yield from {gen})"
470 )
471 else:
472 def_prefix, body = "", f"return {invocation}"
473
474 source = COPY_SIGNATURE_SCRIPT.format(
475 name=name,
476 funcname=funcname,
477 signature=str(newsig),
478 def_prefix=def_prefix,
479 body=body,
480 )
481 result = source_exec_as_module(source).accept(f)
482 result.__doc__ = docstring
483 result.__defaults__ = tuple(
484 p.default
485 for p in signature.parameters.values()
486 if p.default is not signature.empty and "POSITIONAL" in p.kind.name
487 )
488 kwdefaults = {
489 p.name: p.default
490 for p in signature.parameters.values()
491 if p.default is not signature.empty and p.kind is p.KEYWORD_ONLY
492 }
493 if kwdefaults:
494 result.__kwdefaults__ = kwdefaults
495 annotations = {
496 p.name: p.annotation
497 for p in signature.parameters.values()
498 if p.annotation is not signature.empty
499 }
500 if signature.return_annotation is not signature.empty:
501 annotations["return"] = signature.return_annotation
502 if annotations:
503 result.__annotations__ = annotations
504 return result
505
506 return accept
507
508
509def impersonate(target):
510 """Decorator to update the attributes of a function so that to external
511 introspectors it will appear to be the target function.
512
513 Note that this updates the function in place, it doesn't return a
514 new one.
515 """
516
517 def accept(f):
518 # Lie shamelessly about where this code comes from, to hide the hypothesis
519 # internals from pytest, ipython, and other runtime introspection.
520 f.__code__ = f.__code__.replace(
521 co_filename=target.__code__.co_filename,
522 co_firstlineno=target.__code__.co_firstlineno,
523 )
524 f.__name__ = target.__name__
525 f.__module__ = target.__module__
526 f.__doc__ = target.__doc__
527 f.__globals__["__hypothesistracebackhide__"] = True
528 # But leave an breadcrumb for _describe_lambda to follow, it's
529 # just confused by the lies above
530 f.__wrapped_target = target
531 return f
532
533 return accept
534
535
536def proxies(target: T) -> Callable[[Callable], T]:
537 replace_sig = define_function_signature(
538 target.__name__.replace("<lambda>", "_lambda_"), # type: ignore
539 target.__doc__,
540 get_signature(target, follow_wrapped=False),
541 )
542
543 def accept(proxy):
544 return impersonate(target)(wraps(target)(replace_sig(proxy)))
545
546 return accept
547
548
549def is_identity_function(f: Callable) -> bool:
550 try:
551 code = f.__code__
552 except AttributeError:
553 try:
554 f = f.__call__ # type: ignore
555 code = f.__code__
556 except AttributeError:
557 return False
558
559 # We only accept a single unbound argument. While it would be possible to
560 # accept extra defaulted arguments, it would be pointless as they couldn't
561 # be referenced at all in the code object (or the co_code check would fail).
562 bound_args = int(inspect.ismethod(f))
563 if code.co_argcount != bound_args + 1 or code.co_kwonlyargcount > 0:
564 return False
565
566 # We know that f accepts a single positional argument, now check that its
567 # code object is simply "return first unbound argument".
568 template = (lambda self, x: x) if bound_args else (lambda x: x)
569 try:
570 return code.co_code == template.__code__.co_code
571 except AttributeError: # pragma: no cover # pypy only
572 # In PyPy, some builtin functions have a code object ('builtin-code')
573 # lacking co_code, perhaps because they are native-compiled and don't have
574 # a corresponding bytecode. Regardless, since Python doesn't have any
575 # builtin identity function it seems safe to say that this one isn't
576 return False