def test_copying_preserves_argspec(f): af = getfullargspec(f) t = define_function_signature('foo', 'docstring', af)(universal_acceptor) at = getfullargspec(t) assert af.args == at.args assert af.varargs == at.varargs assert af.varkw == at.varkw assert len(af.defaults or ()) == len(at.defaults or ()) assert af.kwonlyargs == at.kwonlyargs assert af.kwonlydefaults == at.kwonlydefaults assert af.annotations == at.annotations
def __repr__(self): if self.__representation is None: _args = self.__args _kwargs = self.__kwargs argspec = getfullargspec(self.__function) defaults = dict(argspec.kwonlydefaults or {}) if argspec.defaults is not None: for name, value in zip(reversed(argspec.args), reversed(argspec.defaults)): defaults[name] = value if len(argspec.args) > 1 or argspec.defaults: _args, _kwargs = convert_positional_arguments( self.__function, _args, _kwargs) else: _args, _kwargs = convert_keyword_arguments( self.__function, _args, _kwargs) kwargs_for_repr = dict(_kwargs) for k, v in defaults.items(): if k in kwargs_for_repr and kwargs_for_repr[k] is defaults[k]: del kwargs_for_repr[k] self.__representation = '%s(%s)' % ( self.__function.__name__, arg_string( self.__function, _args, kwargs_for_repr, reorder=False), ) return self.__representation
def function_digest(function): """Returns a string that is stable across multiple invocations across multiple processes and is prone to changing significantly in response to minor changes to the function. No guarantee of uniqueness though it usually will be. """ hasher = hashlib.md5() try: hasher.update(to_unicode(inspect.getsource(function)).encode('utf-8')) # Different errors on different versions of python. What fun. except (OSError, IOError, TypeError): pass try: hasher.update(str_to_bytes(function.__name__)) except AttributeError: pass try: hasher.update(function.__module__.__name__.encode('utf-8')) except AttributeError: pass try: hasher.update(str_to_bytes(repr(getfullargspec(function)))) except TypeError: pass return hasher.digest()
def test_agrees_on_argspec(f): basic = inspect.getargspec(f) full = getfullargspec(f) assert basic.args == full.args assert basic.varargs == full.varargs assert basic.keywords == full.varkw assert basic.defaults == full.defaults
def required_args(target, args=(), kwargs=()): """Return a set of names of required args to target that were not supplied in args or kwargs. This is used in builds() to determine which arguments to attempt to fill from type hints. target may be any callable (including classes and bound methods). args and kwargs should be as they are passed to builds() - that is, a tuple of values and a dict of names: values. """ # We start with a workaround for NamedTuples, which don't have nice inits if inspect.isclass(target) and is_typed_named_tuple(target): provided = set(kwargs) | set(target._fields[:len(args)]) return set(target._fields) - provided # Then we try to do the right thing with getfullargspec try: spec = getfullargspec( target.__init__ if inspect.isclass(target) else target) except TypeError: # pragma: no cover return None # self appears in the argspec of __init__ and bound methods, but it's an # error to explicitly supply it - so we might skip the first argument. skip_self = int(inspect.isclass(target) or inspect.ismethod(target)) # Start with the args that were not supplied and all kwonly arguments, # then remove all positional arguments with default values, and finally # remove kwonly defaults and any supplied keyword arguments return set(spec.args[skip_self + len(args):] + spec.kwonlyargs) \ - set(spec.args[len(spec.args) - len(spec.defaults or ()):]) \ - set(spec.kwonlydefaults or ()) - set(kwargs)
def test_name_does_not_clash_with_function_names(): def f(): pass @define_function_signature('f', 'A docstring for f', getfullargspec(f)) def g(): pass g()
def test_agrees_on_argspec(f): with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) basic = inspect.getargspec(f) full = getfullargspec(f) assert basic.args == full.args assert basic.varargs == full.varargs assert basic.keywords == full.varkw assert basic.defaults == full.defaults
def accept(proxy): return impersonate(target)( wraps(target)( define_function_signature( target.__name__.replace("<lambda>", "_lambda_"), target.__doc__, getfullargspec(target), )(proxy) ) )
def accept(f): fargspec = getfullargspec(f) must_pass_as_kwargs = [] invocation_parts = [] for a in argspec.args: if a not in fargspec.args and not fargspec.varargs: must_pass_as_kwargs.append(a) else: invocation_parts.append(a) if argspec.varargs: used_names.append(argspec.varargs) parts.append("*" + argspec.varargs) invocation_parts.append("*" + argspec.varargs) elif argspec.kwonlyargs: parts.append("*") for k in must_pass_as_kwargs: invocation_parts.append("%(k)s=%(k)s" % {"k": k}) for k in argspec.kwonlyargs: invocation_parts.append("%(k)s=%(k)s" % {"k": k}) if k in (argspec.kwonlydefaults or []): parts.append("%(k)s=not_set" % {"k": k}) else: parts.append(k) if argspec.varkw: used_names.append(argspec.varkw) parts.append("**" + argspec.varkw) invocation_parts.append("**" + argspec.varkw) candidate_names = ["f"] + [ "f_%d" % (i,) for i in hrange(1, len(used_names) + 2) ] for funcname in candidate_names: # pragma: no branch if funcname not in used_names: break base_accept = source_exec_as_module( COPY_ARGSPEC_SCRIPT % { "name": name, "funcname": funcname, "argspec": ", ".join(parts), "invocation": ", ".join(invocation_parts), } ).accept result = base_accept(f) result.__doc__ = docstring result.__defaults__ = argspec.defaults if argspec.kwonlydefaults: result.__kwdefaults__ = argspec.kwonlydefaults if argspec.annotations: result.__annotations__ = argspec.annotations return result
def accept(f): fargspec = getfullargspec(f) must_pass_as_kwargs = [] invocation_parts = [] for a in argspec.args: if a not in fargspec.args and not fargspec.varargs: must_pass_as_kwargs.append(a) else: invocation_parts.append(a) if argspec.varargs: used_names.append(argspec.varargs) parts.append('*' + argspec.varargs) invocation_parts.append('*' + argspec.varargs) elif argspec.kwonlyargs: parts.append('*') for k in must_pass_as_kwargs: invocation_parts.append('%(k)s=%(k)s' % {'k': k}) for k in argspec.kwonlyargs: invocation_parts.append('%(k)s=%(k)s' % {'k': k}) if k in (argspec.kwonlydefaults or []): parts.append('%(k)s=not_set' % {'k': k}) else: parts.append(k) if argspec.varkw: used_names.append(argspec.varkw) parts.append('**' + argspec.varkw) invocation_parts.append('**' + argspec.varkw) candidate_names = ['f'] + [ 'f_%d' % (i,) for i in hrange(1, len(used_names) + 2) ] for funcname in candidate_names: # pragma: no branch if funcname not in used_names: break base_accept = source_exec_as_module( COPY_ARGSPEC_SCRIPT % { 'name': name, 'funcname': funcname, 'argspec': ', '.join(parts), 'invocation': ', '.join(invocation_parts) }).accept result = base_accept(f) result.__doc__ = docstring result.__defaults__ = argspec.defaults if argspec.kwonlydefaults: result.__kwdefaults__ = argspec.kwonlydefaults if argspec.annotations: result.__annotations__ = argspec.annotations return result
def required_args(target, args=(), kwargs=()): """Return a set of required args to target.""" try: spec = getfullargspec( target.__init__ if inspect.isclass(target) else target) except TypeError: # pragma: no cover return None # For classes, self is present in the argspec but not really required posargs = spec.args[1:] if inspect.isclass(target) else spec.args return set(posargs + spec.kwonlyargs) \ - set(spec.args[len(spec.args) - len(spec.defaults or ()):]) \ - set(spec.kwonlydefaults or ()) - set(args) - set(kwargs)
def convert_positional_arguments(function, args, kwargs): """Return a tuple (new_args, new_kwargs) where all possible arguments have been moved to kwargs. new_args will only be non-empty if function has a variadic argument. """ argspec = getfullargspec(function) new_kwargs = dict(argspec.kwonlydefaults or {}) new_kwargs.update(kwargs) if not argspec.varkw: for k in new_kwargs.keys(): if k not in argspec.args and k not in argspec.kwonlyargs: raise TypeError( '%s() got an unexpected keyword argument %r' % ( function.__name__, k )) if len(args) < len(argspec.args): for i in hrange( len(args), len(argspec.args) - len(argspec.defaults or ()) ): if argspec.args[i] not in kwargs: raise TypeError('No value provided for argument %s' % ( argspec.args[i], )) for kw in argspec.kwonlyargs: if kw not in new_kwargs: raise TypeError('No value provided for argument %s' % kw) if len(args) > len(argspec.args) and not argspec.varargs: raise TypeError( '%s() takes at most %d positional arguments (%d given)' % ( function.__name__, len(argspec.args), len(args) ) ) for arg, name in zip(args, argspec.args): if name in new_kwargs: raise TypeError( '%s() got multiple values for keyword argument %r' % ( function.__name__, name )) else: new_kwargs[name] = arg return ( tuple(args[len(argspec.args):]), new_kwargs, )
def arg_string(f, args, kwargs, reorder=True): if reorder: args, kwargs = convert_positional_arguments(f, args, kwargs) argspec = getfullargspec(f) bits = [] for a in argspec.args: if a in kwargs: bits.append("%s=%s" % (a, nicerepr(kwargs.pop(a)))) if kwargs: for a in sorted(kwargs): bits.append("%s=%s" % (a, nicerepr(kwargs[a]))) return ", ".join([nicerepr(x) for x in args] + bits)
def arg_string(f, args, kwargs, reorder=True): if reorder: args, kwargs = convert_positional_arguments(f, args, kwargs) argspec = getfullargspec(f) bits = [] for a in argspec.args: if a in kwargs: bits.append('%s=%s' % (a, nicerepr(kwargs.pop(a)))) if kwargs: for a in sorted(kwargs): bits.append('%s=%s' % (a, nicerepr(kwargs[a]))) return ', '.join([nicerepr(x) for x in args] + bits)
def convert_keyword_arguments(function, args, kwargs): """Returns a pair of a tuple and a dictionary which would be equivalent passed as positional and keyword args to the function. Unless function has. **kwargs the dictionary will always be empty. """ argspec = getfullargspec(function) new_args = [] kwargs = dict(kwargs) defaults = dict(argspec.kwonlydefaults or {}) if argspec.defaults: for name, value in zip( argspec.args[-len(argspec.defaults):], argspec.defaults ): defaults[name] = value n = max(len(args), len(argspec.args)) for i in hrange(n): if i < len(args): new_args.append(args[i]) else: arg_name = argspec.args[i] if arg_name in kwargs: new_args.append(kwargs.pop(arg_name)) elif arg_name in defaults: new_args.append(defaults[arg_name]) else: raise TypeError('No value provided for argument %r' % ( arg_name )) if kwargs and not argspec.varkw: if len(kwargs) > 1: raise TypeError('%s() got unexpected keyword arguments %s' % ( function.__name__, ', '.join(map(repr, kwargs)) )) else: bad_kwarg = next(iter(kwargs)) raise TypeError('%s() got an unexpected keyword argument %r' % ( function.__name__, bad_kwarg )) return tuple(new_args), kwargs
def composite(f): """Defines a strategy that is built out of potentially arbitrarily many other strategies. This is intended to be used as a decorator. See the full documentation for more details about how to use this function. """ from hypothesis.internal.reflection import define_function_signature argspec = getfullargspec(f) if (argspec.defaults is not None and len(argspec.defaults) == len(argspec.args)): raise InvalidArgument( 'A default value for initial argument will never be used') if len(argspec.args) == 0 and not argspec.varargs: raise InvalidArgument( 'Functions wrapped with composite must take at least one ' 'positional argument.') annots = { k: v for k, v in argspec.annotations.items() if k in (argspec.args + argspec.kwonlyargs + ['return']) } new_argspec = argspec._replace(args=argspec.args[1:], annotations=annots) @defines_strategy @define_function_signature(f.__name__, f.__doc__, new_argspec) def accept(*args, **kwargs): class CompositeStrategy(SearchStrategy): def do_draw(self, data): first_draw = [True] def draw(strategy): if not first_draw[0]: data.mark_bind() first_draw[0] = False return data.draw(strategy) return f(draw, *args, **kwargs) return CompositeStrategy() accept.__module__ = f.__module__ return accept
def execute_explicit_examples(state, wrapped_test, arguments, kwargs): original_argspec = getfullargspec(state.test) for example in reversed( getattr(wrapped_test, "hypothesis_explicit_examples", ())): example_kwargs = dict(original_argspec.kwonlydefaults or {}) if example.args: if len(example.args) > len(original_argspec.args): raise InvalidArgument( "example has too many arguments for test. " "Expected at most %d but got %d" % (len(original_argspec.args), len(example.args))) example_kwargs.update( dict( zip(original_argspec.args[-len(example.args):], example.args))) else: example_kwargs.update(example.kwargs) if Phase.explicit not in state.settings.phases: continue example_kwargs.update(kwargs) with local_settings(state.settings): fragments_reported = [] def report_buffered(): for f in fragments_reported: report(f) del fragments_reported[:] try: with with_reporter(fragments_reported.append): state.execute_once( ArtificialDataForExample(example_kwargs), is_final=True, print_example=True, ) except BaseException: report_buffered() raise if current_verbosity() >= Verbosity.verbose: prefix = "Falsifying example" assert fragments_reported[0].startswith(prefix) fragments_reported[0] = ("Trying example" + fragments_reported[0][len(prefix):]) report_buffered()
def execute_explicit_examples( test_runner, test, wrapped_test, settings, arguments, kwargs ): original_argspec = getfullargspec(test) for example in reversed(getattr( wrapped_test, 'hypothesis_explicit_examples', () )): example_kwargs = dict(original_argspec.kwonlydefaults or {}) if example.args: if len(example.args) > len(original_argspec.args): raise InvalidArgument( 'example has too many arguments for test. ' 'Expected at most %d but got %d' % ( len(original_argspec.args), len(example.args))) example_kwargs.update(dict(zip( original_argspec.args[-len(example.args):], example.args ))) else: example_kwargs.update(example.kwargs) if Phase.explicit not in settings.phases: continue example_kwargs.update(kwargs) # Note: Test may mutate arguments and we can't rerun explicit # examples, so we have to calculate the failure message at this # point rather than than later. example_string = '%s(%s)' % ( test.__name__, arg_string(test, arguments, example_kwargs) ) try: with BuildContext(None) as b: if settings.verbosity >= Verbosity.verbose: report('Trying example: ' + example_string) test_runner( None, lambda data: test(*arguments, **example_kwargs) ) except BaseException: traceback.print_exc() report('Falsifying example: ' + example_string) for n in b.notes: report(n) raise
def execute_explicit_examples( test_runner, test, wrapped_test, settings, arguments, kwargs ): original_argspec = getfullargspec(test) for example in reversed(getattr( wrapped_test, 'hypothesis_explicit_examples', () )): example_kwargs = dict(original_argspec.kwonlydefaults or {}) if example.args: if len(example.args) > len(original_argspec.args): raise InvalidArgument( 'example has too many arguments for test. ' 'Expected at most %d but got %d' % ( len(original_argspec.args), len(example.args))) example_kwargs.update(dict(zip( original_argspec.args[-len(example.args):], example.args ))) else: example_kwargs.update(example.kwargs) if Phase.explicit not in settings.phases: continue example_kwargs.update(kwargs) # Note: Test may mutate arguments and we can't rerun explicit # examples, so we have to calculate the failure message at this # point rather than than later. example_string = '%s(%s)' % ( test.__name__, arg_string(test, arguments, example_kwargs) ) try: with BuildContext(None) as b: if settings.verbosity >= Verbosity.verbose: report('Trying example: ' + example_string) test_runner( None, lambda data: test(*arguments, **example_kwargs) ) except BaseException: report('Falsifying example: ' + example_string) for n in b.notes: report(n) raise
def required_args(target, args=(), kwargs=()): """Return a set of names of required args to target that were not supplied in args or kwargs. This is used in builds() to determine which arguments to attempt to fill from type hints. target may be any callable (including classes and bound methods). args and kwargs should be as they are passed to builds() - that is, a tuple of values and a dict of names: values. """ try: spec = getfullargspec( target.__init__ if inspect.isclass(target) else target) except TypeError: # pragma: no cover return None # self appears in the argspec of __init__ and bound methods, but it's an # error to explicitly supply it - so we might skip the first argument. skip_self = int(inspect.isclass(target) or inspect.ismethod(target)) # Start with the args that were not supplied and all kwonly arguments, # then remove all positional arguments with default values, and finally # remove kwonly defaults and any supplied keyword arguments return set(spec.args[skip_self + len(args):] + spec.kwonlyargs) \ - set(spec.args[len(spec.args) - len(spec.defaults or ()):]) \ - set(spec.kwonlydefaults or ()) - set(kwargs)
def test_composite_edits_annotations(): spec_comp = getfullargspec(st.composite(pointless_composite)) assert spec_comp.annotations["return"] == int assert "nothing" in spec_comp.annotations assert "draw" not in spec_comp.annotations
def extract_lambda_source(f): """Extracts a single lambda expression from the string source. Returns a string indicating an unknown body if it gets confused in any way. This is not a good function and I am sorry for it. Forgive me my sins, oh lord """ argspec = getfullargspec(f) arg_strings = [] # In Python 2 you can have destructuring arguments to functions. This # results in an argspec with non-string values. I'm not very interested in # handling these properly, but it's important to not crash on them. bad_lambda = False for a in argspec.args: if isinstance(a, (tuple, list)): # pragma: no cover arg_strings.append("(%s)" % (", ".join(a), )) bad_lambda = True else: assert isinstance(a, str) arg_strings.append(a) if argspec.varargs: arg_strings.append("*" + argspec.varargs) elif argspec.kwonlyargs: arg_strings.append("*") for a in argspec.kwonlyargs or []: default = (argspec.kwonlydefaults or {}).get(a) if default: arg_strings.append("{}={}".format(a, default)) else: arg_strings.append(a) if arg_strings: if_confused = "lambda %s: <unknown>" % (", ".join(arg_strings), ) else: if_confused = "lambda: <unknown>" if bad_lambda: # pragma: no cover return if_confused try: source = inspect.getsource(f) except IOError: return if_confused source = LINE_CONTINUATION.sub(" ", source) source = WHITESPACE.sub(" ", source) source = source.strip() assert "lambda" in source tree = None try: tree = ast.parse(source) except SyntaxError: for i in hrange(len(source) - 1, len("lambda"), -1): prefix = source[:i] if "lambda" not in prefix: break try: tree = ast.parse(prefix) source = prefix break except SyntaxError: continue if tree is None: if source.startswith("@"): # This will always eventually find a valid expression because # the decorator must be a valid Python function call, so will # eventually be syntactically valid and break out of the loop. Thus # this loop can never terminate normally, so a no branch pragma is # appropriate. for i in hrange(len(source) + 1): # pragma: no branch p = source[1:i] if "lambda" in p: try: tree = ast.parse(p) source = p break except SyntaxError: pass if tree is None: return if_confused all_lambdas = extract_all_lambdas(tree) aligned_lambdas = [ l for l in all_lambdas if args_for_lambda_ast(l) == argspec.args ] if len(aligned_lambdas) != 1: return if_confused lambda_ast = aligned_lambdas[0] assert lambda_ast.lineno == 1 # If the source code contains Unicode characters, the bytes of the original # file don't line up with the string indexes, and `col_offset` doesn't match # the string we're using. We need to convert the source code into bytes # before slicing. # # Under the hood, the inspect module is using `tokenize.detect_encoding` to # detect the encoding of the original source file. We'll use the same # approach to get the source code as bytes. # # See https://github.com/HypothesisWorks/hypothesis/issues/1700 for an # example of what happens if you don't correct for this. # # Note: if the code doesn't come from a file (but, for example, a doctest), # `getsourcefile` will return `None` and the `open()` call will fail with # an OSError. Or if `f` is a built-in function, in which case we get a # TypeError. In both cases, fall back to splitting the Unicode string. # It's not perfect, but it's the best we can do. # # Note 2: You can only detect the encoding with `tokenize.detect_encoding` # in Python 3.2 or later. But that's okay, because the only version that # affects for us is Python 2.7, and 2.7 doesn't support non-ASCII identifiers: # https://www.python.org/dev/peps/pep-3131/. In this case we'll get an # AttributeError. # try: with open(inspect.getsourcefile(f), "rb") as src_f: encoding, _ = tokenize.detect_encoding(src_f.readline) source_bytes = source.encode(encoding) source_bytes = source_bytes[lambda_ast.col_offset:].strip() source = source_bytes.decode(encoding) except (AttributeError, OSError, TypeError): source = source[lambda_ast.col_offset:].strip() # This ValueError can be thrown in Python 3 if: # # - There's a Unicode character in the line before the Lambda, and # - For some reason we can't detect the source encoding of the file # # because slicing on `lambda_ast.col_offset` will account for bytes, but # the slice will be on Unicode characters. # # In practice this seems relatively rare, so we just give up rather than # trying to recover. try: source = source[source.index("lambda"):] except ValueError: return if_confused for i in hrange(len(source), len("lambda"), -1): # pragma: no branch try: parsed = ast.parse(source[:i]) assert len(parsed.body) == 1 assert parsed.body if isinstance(parsed.body[0].value, ast.Lambda): source = source[:i] break except SyntaxError: pass lines = source.split("\n") lines = [PROBABLY_A_COMMENT.sub("", l) for l in lines] source = "\n".join(lines) source = WHITESPACE.sub(" ", source) source = SPACE_FOLLOWS_OPEN_BRACKET.sub("(", source) source = SPACE_PRECEDES_CLOSE_BRACKET.sub(")", source) source = source.strip() return source
def run_test_with_generator(test): if hasattr(test, "_hypothesis_internal_test_function_without_warning"): # Pull out the original test function to avoid the warning we # stuck in about using @settings without @given. test = test._hypothesis_internal_test_function_without_warning if inspect.isclass(test): # Provide a meaningful error to users, instead of exceptions from # internals that assume we're dealing with a function. raise InvalidArgument("@given cannot be applied to a class.") generator_arguments = tuple(given_arguments) generator_kwargs = dict(given_kwargs) original_argspec = getfullargspec(test) check_invalid = is_invalid_test( test.__name__, original_argspec, generator_arguments, generator_kwargs ) if check_invalid is not None: return check_invalid for name, strategy in zip( reversed(original_argspec.args), reversed(generator_arguments) ): generator_kwargs[name] = strategy argspec = new_given_argspec(original_argspec, generator_kwargs) @impersonate(test) @define_function_signature(test.__name__, test.__doc__, argspec) def wrapped_test(*arguments, **kwargs): # Tell pytest to omit the body of this function from tracebacks __tracebackhide__ = True test = wrapped_test.hypothesis.inner_test if getattr(test, "is_hypothesis_test", False): raise InvalidArgument( ( "You have applied @given to the test %s more than once, which " "wraps the test several times and is extremely slow. A " "similar effect can be gained by combining the arguments " "of the two calls to given. For example, instead of " "@given(booleans()) @given(integers()), you could write " "@given(booleans(), integers())" ) % (test.__name__,) ) settings = wrapped_test._hypothesis_internal_use_settings random = get_random_for_wrapped_test(test, wrapped_test) if infer in generator_kwargs.values(): hints = get_type_hints(test) for name in [ name for name, value in generator_kwargs.items() if value is infer ]: if name not in hints: raise InvalidArgument( "passed %s=infer for %s, but %s has no type annotation" % (name, test.__name__, name) ) generator_kwargs[name] = st.from_type(hints[name]) processed_args = process_arguments_to_given( wrapped_test, arguments, kwargs, generator_arguments, generator_kwargs, argspec, test, settings, ) arguments, kwargs, test_runner, search_strategy = processed_args runner = getattr(search_strategy, "runner", None) if isinstance(runner, TestCase) and test.__name__ in dir(TestCase): msg = ( "You have applied @given to the method %s, which is " "used by the unittest runner but is not itself a test." " This is not useful in any way." % test.__name__ ) fail_health_check(settings, msg, HealthCheck.not_a_test_method) if bad_django_TestCase(runner): # pragma: no cover # Covered by the Django tests, but not the pytest coverage task raise InvalidArgument( "You have applied @given to a method on %s, but this " "class does not inherit from the supported versions in " "`hypothesis.extra.django`. Use the Hypothesis variants " "to ensure that each example is run in a separate " "database transaction." % qualname(type(runner)) ) state = StateForActualGivenExecution( test_runner, search_strategy, test, settings, random, had_seed=wrapped_test._hypothesis_internal_use_seed, ) reproduce_failure = wrapped_test._hypothesis_internal_use_reproduce_failure if reproduce_failure is not None: expected_version, failure = reproduce_failure if expected_version != __version__: raise InvalidArgument( ( "Attempting to reproduce a failure from a different " "version of Hypothesis. This failure is from %s, but " "you are currently running %r. Please change your " "Hypothesis version to a matching one." ) % (expected_version, __version__) ) try: state.execute( ConjectureData.for_buffer(decode_failure(failure)), print_example=True, is_final=True, ) raise DidNotReproduce( "Expected the test to raise an error, but it " "completed successfully." ) except StopTest: raise DidNotReproduce( "The shape of the test data has changed in some way " "from where this blob was defined. Are you sure " "you're running the same test?" ) except UnsatisfiedAssumption: raise DidNotReproduce( "The test data failed to satisfy an assumption in the " "test. Have you added it since this blob was " "generated?" ) execute_explicit_examples( test_runner, test, wrapped_test, settings, arguments, kwargs ) if settings.max_examples <= 0: return if not ( Phase.reuse in settings.phases or Phase.generate in settings.phases ): return try: if isinstance(runner, TestCase) and hasattr(runner, "subTest"): subTest = runner.subTest try: setattr(runner, "subTest", fake_subTest) state.run() finally: setattr(runner, "subTest", subTest) else: state.run() except BaseException as e: generated_seed = wrapped_test._hypothesis_internal_use_generated_seed with local_settings(settings): if not (state.failed_normally or generated_seed is None): if running_under_pytest: report( "You can add @seed(%(seed)d) to this test or " "run pytest with --hypothesis-seed=%(seed)d " "to reproduce this failure." % {"seed": generated_seed} ) else: report( "You can add @seed(%d) to this test to " "reproduce this failure." % (generated_seed,) ) # The dance here is to avoid showing users long tracebacks # full of Hypothesis internals they don't care about. # We have to do this inline, to avoid adding another # internal stack frame just when we've removed the rest. if PY2: # Python 2 doesn't have Exception.with_traceback(...); # instead it has a three-argument form of the `raise` # statement. Unfortunately this is a SyntaxError on # Python 3, and before Python 2.7.9 it was *also* a # SyntaxError to use it in a nested function so we # can't `exec` or `eval` our way out (BPO-21591). # So unless we break some versions of Python 2, none # of them get traceback elision. raise # On Python 3, we swap out the real traceback for our # trimmed version. Using a variable ensures that the line # which will actually appear in trackbacks is as clear as # possible - "raise the_error_hypothesis_found". the_error_hypothesis_found = e.with_traceback( get_trimmed_traceback() ) raise the_error_hypothesis_found for attrib in dir(test): if not (attrib.startswith("_") or hasattr(wrapped_test, attrib)): setattr(wrapped_test, attrib, getattr(test, attrib)) wrapped_test.is_hypothesis_test = True if hasattr(test, "_hypothesis_internal_settings_applied"): # Used to check if @settings is applied twice. wrapped_test._hypothesis_internal_settings_applied = True wrapped_test._hypothesis_internal_use_seed = getattr( test, "_hypothesis_internal_use_seed", None ) wrapped_test._hypothesis_internal_use_settings = ( getattr(test, "_hypothesis_internal_use_settings", None) or Settings.default ) wrapped_test._hypothesis_internal_use_reproduce_failure = getattr( test, "_hypothesis_internal_use_reproduce_failure", None ) wrapped_test.hypothesis = HypothesisHandle(test) return wrapped_test
def extract_lambda_source(f): """Extracts a single lambda expression from the string source. Returns a string indicating an unknown body if it gets confused in any way. This is not a good function and I am sorry for it. Forgive me my sins, oh lord """ argspec = getfullargspec(f) arg_strings = [] # In Python 2 you can have destructuring arguments to functions. This # results in an argspec with non-string values. I'm not very interested in # handling these properly, but it's important to not crash on them. bad_lambda = False for a in argspec.args: if isinstance(a, (tuple, list)): # pragma: no cover arg_strings.append("(%s)" % (", ".join(a),)) bad_lambda = True else: assert isinstance(a, str) arg_strings.append(a) if argspec.varargs: arg_strings.append("*" + argspec.varargs) elif argspec.kwonlyargs: arg_strings.append("*") for a in argspec.kwonlyargs or []: default = (argspec.kwonlydefaults or {}).get(a) if default: arg_strings.append("{}={}".format(a, default)) else: arg_strings.append(a) if arg_strings: if_confused = "lambda %s: <unknown>" % (", ".join(arg_strings),) else: if_confused = "lambda: <unknown>" if bad_lambda: # pragma: no cover return if_confused try: source = inspect.getsource(f) except IOError: return if_confused source = LINE_CONTINUATION.sub(" ", source) source = WHITESPACE.sub(" ", source) source = source.strip() assert "lambda" in source tree = None try: tree = ast.parse(source) except SyntaxError: for i in hrange(len(source) - 1, len("lambda"), -1): prefix = source[:i] if "lambda" not in prefix: break try: tree = ast.parse(prefix) source = prefix break except SyntaxError: continue if tree is None: if source.startswith("@"): # This will always eventually find a valid expression because # the decorator must be a valid Python function call, so will # eventually be syntactically valid and break out of the loop. Thus # this loop can never terminate normally, so a no branch pragma is # appropriate. for i in hrange(len(source) + 1): # pragma: no branch p = source[1:i] if "lambda" in p: try: tree = ast.parse(p) source = p break except SyntaxError: pass if tree is None: return if_confused all_lambdas = extract_all_lambdas(tree) aligned_lambdas = [l for l in all_lambdas if args_for_lambda_ast(l) == argspec.args] if len(aligned_lambdas) != 1: return if_confused lambda_ast = aligned_lambdas[0] assert lambda_ast.lineno == 1 # If the source code contains Unicode characters, the bytes of the original # file don't line up with the string indexes, and `col_offset` doesn't match # the string we're using. We need to convert the source code into bytes # before slicing. # # Under the hood, the inspect module is using `tokenize.detect_encoding` to # detect the encoding of the original source file. We'll use the same # approach to get the source code as bytes. # # See https://github.com/HypothesisWorks/hypothesis/issues/1700 for an # example of what happens if you don't correct for this. # # Note: if the code doesn't come from a file (but, for example, a doctest), # `getsourcefile` will return `None` and the `open()` call will fail with # an OSError. Or if `f` is a built-in function, in which case we get a # TypeError. In both cases, fall back to splitting the Unicode string. # It's not perfect, but it's the best we can do. # # Note 2: You can only detect the encoding with `tokenize.detect_encoding` # in Python 3.2 or later. But that's okay, because the only version that # affects for us is Python 2.7, and 2.7 doesn't support non-ASCII identifiers: # https://www.python.org/dev/peps/pep-3131/. In this case we'll get an # AttributeError. # try: with open(inspect.getsourcefile(f), "rb") as src_f: encoding, _ = tokenize.detect_encoding(src_f.readline) source_bytes = source.encode(encoding) source_bytes = source_bytes[lambda_ast.col_offset :].strip() source = source_bytes.decode(encoding) except (AttributeError, OSError, TypeError): source = source[lambda_ast.col_offset :].strip() # This ValueError can be thrown in Python 3 if: # # - There's a Unicode character in the line before the Lambda, and # - For some reason we can't detect the source encoding of the file # # because slicing on `lambda_ast.col_offset` will account for bytes, but # the slice will be on Unicode characters. # # In practice this seems relatively rare, so we just give up rather than # trying to recover. try: source = source[source.index("lambda") :] except ValueError: return if_confused for i in hrange(len(source), len("lambda"), -1): # pragma: no branch try: parsed = ast.parse(source[:i]) assert len(parsed.body) == 1 assert parsed.body if isinstance(parsed.body[0].value, ast.Lambda): source = source[:i] break except SyntaxError: pass lines = source.split("\n") lines = [PROBABLY_A_COMMENT.sub("", l) for l in lines] source = "\n".join(lines) source = WHITESPACE.sub(" ", source) source = SPACE_FOLLOWS_OPEN_BRACKET.sub("(", source) source = SPACE_PRECEDES_CLOSE_BRACKET.sub(")", source) source = source.strip() return source
def test_can_call_default_like_arg(): # This test is somewhat silly, but coverage complains about the uncovered # branch for calling it otherwise and alternative workarounds are worse. like, returns = getfullargspec(functions).defaults assert like() is None assert returns.example() is None
def test_uses_defaults(): f = define_function_signature( "foo", "A docstring for foo", getfullargspec(has_a_default))(universal_acceptor) assert f(3, 2) == ((3, 2, 1), {})
def run_test_with_generator(test): generator_arguments = tuple(given_arguments) generator_kwargs = dict(given_kwargs) original_argspec = getfullargspec(test) check_invalid = is_invalid_test(test.__name__, original_argspec, generator_arguments, generator_kwargs) if check_invalid is not None: return check_invalid for name, strategy in zip(reversed(original_argspec.args), reversed(generator_arguments)): generator_kwargs[name] = strategy argspec = new_given_argspec(original_argspec, generator_kwargs) @impersonate(test) @define_function_signature(test.__name__, test.__doc__, argspec) def wrapped_test(*arguments, **kwargs): # Tell pytest to omit the body of this function from tracebacks __tracebackhide__ = True if getattr(test, 'is_hypothesis_test', False): note_deprecation( ('You have applied @given to a test more than once. In ' 'future this will be an error. Applying @given twice ' 'wraps the test twice, which can be extremely slow. A ' 'similar effect can be gained by combining the arguments ' 'to the two calls to given. For example, instead of ' '@given(booleans()) @given(integers()), you could write ' '@given(booleans(), integers())')) settings = wrapped_test._hypothesis_internal_use_settings random = get_random_for_wrapped_test(test, wrapped_test) if infer in generator_kwargs.values(): hints = get_type_hints(test) for name in [ name for name, value in generator_kwargs.items() if value is infer ]: if name not in hints: raise InvalidArgument( 'passed %s=infer for %s, but %s has no type annotation' % (name, test.__name__, name)) generator_kwargs[name] = st.from_type(hints[name]) processed_args = process_arguments_to_given( wrapped_test, arguments, kwargs, generator_arguments, generator_kwargs, argspec, test, settings) arguments, kwargs, test_runner, search_strategy = processed_args state = StateForActualGivenExecution( test_runner, search_strategy, test, settings, random, had_seed=wrapped_test._hypothesis_internal_use_seed) reproduce_failure = \ wrapped_test._hypothesis_internal_use_reproduce_failure if reproduce_failure is not None: expected_version, failure = reproduce_failure if expected_version != __version__: raise InvalidArgument( ('Attempting to reproduce a failure from a different ' 'version of Hypothesis. This failure is from %s, but ' 'you are currently running %r. Please change your ' 'Hypothesis version to a matching one.') % (expected_version, __version__)) try: state.execute( ConjectureData.for_buffer(decode_failure(failure)), print_example=True, is_final=True, ) raise DidNotReproduce( 'Expected the test to raise an error, but it ' 'completed successfully.') except StopTest: raise DidNotReproduce( 'The shape of the test data has changed in some way ' 'from where this blob was defined. Are you sure ' "you're running the same test?") except UnsatisfiedAssumption: raise DidNotReproduce( 'The test data failed to satisfy an assumption in the ' 'test. Have you added it since this blob was ' 'generated?') execute_explicit_examples(test_runner, test, wrapped_test, settings, arguments, kwargs) if settings.max_examples <= 0: return if not (Phase.reuse in settings.phases or Phase.generate in settings.phases): return try: state.run() except BaseException: generated_seed = \ wrapped_test._hypothesis_internal_use_generated_seed if generated_seed is not None and not state.failed_normally: if running_under_pytest: report( ('You can add @seed(%(seed)d) to this test or run ' 'pytest with --hypothesis-seed=%(seed)d to ' 'reproduce this failure.') % {'seed': generated_seed}, ) else: report( ('You can add @seed(%d) to this test to reproduce ' 'this failure.') % (generated_seed, )) raise for attrib in dir(test): if not (attrib.startswith('_') or hasattr(wrapped_test, attrib)): setattr(wrapped_test, attrib, getattr(test, attrib)) wrapped_test.is_hypothesis_test = True wrapped_test._hypothesis_internal_use_seed = getattr( test, '_hypothesis_internal_use_seed', None) wrapped_test._hypothesis_internal_use_settings = getattr( test, '_hypothesis_internal_use_settings', None) or Settings.default wrapped_test._hypothesis_internal_use_reproduce_failure = getattr( test, '_hypothesis_internal_use_reproduce_failure', None) return wrapped_test
def extract_lambda_source(f): """Extracts a single lambda expression from the string source. Returns a string indicating an unknown body if it gets confused in any way. This is not a good function and I am sorry for it. Forgive me my sins, oh lord """ argspec = getfullargspec(f) arg_strings = [] # In Python 2 you can have destructuring arguments to functions. This # results in an argspec with non-string values. I'm not very interested in # handling these properly, but it's important to not crash on them. bad_lambda = False for a in argspec.args: if isinstance(a, (tuple, list)): # pragma: no cover arg_strings.append('(%s)' % (', '.join(a), )) bad_lambda = True else: assert isinstance(a, str) arg_strings.append(a) if argspec.varargs: arg_strings.append('*' + argspec.varargs) elif argspec.kwonlyargs: arg_strings.append('*') for a in (argspec.kwonlyargs or []): default = (argspec.kwonlydefaults or {}).get(a) if default: arg_strings.append('{}={}'.format(a, default)) else: arg_strings.append(a) if_confused = 'lambda %s: <unknown>' % (', '.join(arg_strings), ) if bad_lambda: # pragma: no cover return if_confused try: source = inspect.getsource(f) except IOError: return if_confused source = LINE_CONTINUATION.sub(' ', source) source = WHITESPACE.sub(' ', source) source = source.strip() assert 'lambda' in source tree = None try: tree = ast.parse(source) except SyntaxError: for i in hrange(len(source) - 1, len('lambda'), -1): prefix = source[:i] if 'lambda' not in prefix: break try: tree = ast.parse(prefix) source = prefix break except SyntaxError: continue if tree is None: if source.startswith('@'): # This will always eventually find a valid expression because # the decorator must be a valid Python function call, so will # eventually be syntactically valid and break out of the loop. Thus # this loop can never terminate normally, so a no branch pragma is # appropriate. for i in hrange(len(source) + 1): # pragma: no branch p = source[1:i] if 'lambda' in p: try: tree = ast.parse(p) source = p break except SyntaxError: pass if tree is None: return if_confused all_lambdas = extract_all_lambdas(tree) aligned_lambdas = [ l for l in all_lambdas if args_for_lambda_ast(l) == argspec.args ] if len(aligned_lambdas) != 1: return if_confused lambda_ast = aligned_lambdas[0] assert lambda_ast.lineno == 1 source = source[lambda_ast.col_offset:].strip() source = source[source.index('lambda'):] for i in hrange(len(source), len('lambda'), -1): # pragma: no branch try: parsed = ast.parse(source[:i]) assert len(parsed.body) == 1 assert parsed.body if isinstance(parsed.body[0].value, ast.Lambda): source = source[:i] break except SyntaxError: pass lines = source.split('\n') lines = [PROBABLY_A_COMMENT.sub('', l) for l in lines] source = '\n'.join(lines) source = WHITESPACE.sub(' ', source) source = SPACE_FOLLOWS_OPEN_BRACKET.sub('(', source) source = SPACE_PRECEDES_CLOSE_BRACKET.sub(')', source) source = source.strip() return source
def test_uses_varargs(): f = define_function_signature( 'foo', 'A docstring for foo', getfullargspec(has_varargs))(universal_acceptor) assert f(1, 2) == ((1, 2), {})
def test_uses_defaults(): f = define_function_signature( 'foo', 'A docstring for foo', getfullargspec(has_a_default))(universal_acceptor) assert f(3, 2) == ((3, 2, 1), {})
def test_copying_sets_docstring(): f = define_function_signature( 'foo', 'A docstring for foo', getfullargspec(has_two_args))(universal_acceptor) assert f.__doc__ == 'A docstring for foo'
def test_copying_sets_name(): f = define_function_signature( 'hello_world', 'A docstring for hello_world', getfullargspec(has_two_args))(universal_acceptor) assert f.__name__ == 'hello_world'
def test_copying_sets_name(): f = define_function_signature( "hello_world", "A docstring for hello_world", getfullargspec(has_two_args))(universal_acceptor) assert f.__name__ == "hello_world"
def test_copying_sets_docstring(): f = define_function_signature( "foo", "A docstring for foo", getfullargspec(has_two_args))(universal_acceptor) assert f.__doc__ == "A docstring for foo"
def run_test_with_generator(test): generator_arguments = tuple(given_arguments) generator_kwargs = dict(given_kwargs) original_argspec = getfullargspec(test) check_invalid = is_invalid_test( test.__name__, original_argspec, generator_arguments, generator_kwargs) if check_invalid is not None: return check_invalid for name, strategy in zip(reversed(original_argspec.args), reversed(generator_arguments)): generator_kwargs[name] = strategy argspec = new_given_argspec(original_argspec, generator_kwargs) @impersonate(test) @define_function_signature( test.__name__, test.__doc__, argspec ) def wrapped_test(*arguments, **kwargs): # Tell pytest to omit the body of this function from tracebacks __tracebackhide__ = True settings = wrapped_test._hypothesis_internal_use_settings random = get_random_for_wrapped_test(test, wrapped_test) if infer in generator_kwargs.values(): hints = get_type_hints(test) for name in [name for name, value in generator_kwargs.items() if value is infer]: if name not in hints: raise InvalidArgument( 'passed %s=infer for %s, but %s has no type annotation' % (name, test.__name__, name)) generator_kwargs[name] = st.from_type(hints[name]) processed_args = process_arguments_to_given( wrapped_test, arguments, kwargs, generator_arguments, generator_kwargs, argspec, test, settings ) arguments, kwargs, test_runner, search_strategy = processed_args execute_explicit_examples( test_runner, test, wrapped_test, settings, arguments, kwargs ) if settings.max_examples <= 0: return if not ( Phase.reuse in settings.phases or Phase.generate in settings.phases ): return try: perform_health_checks( random, settings, test_runner, search_strategy) state = StateForActualGivenExecution( test_runner, search_strategy, test, settings, random) state.run() except: generated_seed = \ wrapped_test._hypothesis_internal_use_generated_seed if generated_seed is not None: if running_under_pytest: report(( 'You can add @seed(%(seed)d) to this test or run ' 'pytest with --hypothesis-seed=%(seed)d to ' 'reproduce this failure.') % { 'seed': generated_seed},) else: report(( 'You can add @seed(%d) to this test to reproduce ' 'this failure.') % (generated_seed,)) raise for attrib in dir(test): if not (attrib.startswith('_') or hasattr(wrapped_test, attrib)): setattr(wrapped_test, attrib, getattr(test, attrib)) wrapped_test.is_hypothesis_test = True wrapped_test._hypothesis_internal_use_seed = getattr( test, '_hypothesis_internal_use_seed', None ) wrapped_test._hypothesis_internal_use_settings = getattr( test, '_hypothesis_internal_use_settings', None ) or Settings.default return wrapped_test
def test_uses_varargs(): f = define_function_signature( "foo", "A docstring for foo", getfullargspec(has_varargs))(universal_acceptor) assert f(1, 2) == ((1, 2), {})
def run(data): # Set up dynamic context needed by a single test run. with local_settings(self.settings): with deterministic_PRNG(): with BuildContext(data, is_final=is_final): # Generate all arguments to the test function. args, kwargs = data.draw(self.search_strategy) if expected_failure is not None: text_repr[0] = arg_string(test, args, kwargs) if print_example or current_verbosity( ) >= Verbosity.verbose: output = CUnicodeIO() printer = RepresentationPrinter(output) if print_example: printer.text("Falsifying example:") else: printer.text("Trying example:") if self.print_given_args: printer.text(" ") printer.text(test.__name__) with printer.group(indent=4, open="(", close=""): printer.break_() for v in args: printer.pretty(v) # We add a comma unconditionally because # generated arguments will always be # kwargs, so there will always be more # to come. printer.text(",") printer.breakable() # We need to make sure to print these in the argument order for # Python 2 and older versionf of Python 3.5. In modern versions # this isn't an issue because kwargs is ordered. arg_order = { v: i for i, v in enumerate( getfullargspec(self.test).args) } for i, (k, v) in enumerate( sorted( kwargs.items(), key=lambda t: ( arg_order.get( t[0], float("inf")), t[0], ), )): printer.text(k) printer.text("=") printer.pretty(v) printer.text(",") if i + 1 < len(kwargs): printer.breakable() printer.break_() printer.text(")") printer.flush() report(output.getvalue()) return test(*args, **kwargs)
def run_test_with_generator(test): generator_arguments = tuple(given_arguments) generator_kwargs = dict(given_kwargs) original_argspec = getfullargspec(test) check_invalid = is_invalid_test(test.__name__, original_argspec, generator_arguments, generator_kwargs) if check_invalid is not None: return check_invalid for name, strategy in zip(reversed(original_argspec.args), reversed(generator_arguments)): generator_kwargs[name] = strategy argspec = new_given_argspec(original_argspec, generator_kwargs) @impersonate(test) @define_function_signature(test.__name__, test.__doc__, argspec) def wrapped_test(*arguments, **kwargs): # Tell pytest to omit the body of this function from tracebacks __tracebackhide__ = True settings = wrapped_test._hypothesis_internal_use_settings random = get_random_for_wrapped_test(test, wrapped_test) if infer in generator_kwargs.values(): hints = get_type_hints(test) for name in [ name for name, value in generator_kwargs.items() if value is infer ]: if name not in hints: raise InvalidArgument( 'passed %s=infer for %s, but %s has no type annotation' % (name, test.__name__, name)) generator_kwargs[name] = st.from_type(hints[name]) processed_args = process_arguments_to_given( wrapped_test, arguments, kwargs, generator_arguments, generator_kwargs, argspec, test, settings) arguments, kwargs, test_runner, search_strategy = processed_args execute_explicit_examples(test_runner, test, wrapped_test, settings, arguments, kwargs) if settings.max_examples <= 0: return if not (Phase.reuse in settings.phases or Phase.generate in settings.phases): return perform_health_checks(random, settings, test_runner, search_strategy) state = StateForActualGivenExecution(test_runner, search_strategy, test, settings, random) state.run() for test_attr in dir(test): if test_attr[0] != '_' and not hasattr(wrapped_test, test_attr): setattr(wrapped_test, test_attr, getattr(test, test_attr)) wrapped_test.is_hypothesis_test = True wrapped_test._hypothesis_internal_use_seed = getattr( test, '_hypothesis_internal_use_seed', None) wrapped_test._hypothesis_internal_use_settings = getattr( test, '_hypothesis_internal_use_settings', None) or Settings.default return wrapped_test
def test_composite_edits_annotations(): spec_comp = getfullargspec(st.composite(pointless_composite)) assert spec_comp.annotations['return'] == int assert 'nothing' in spec_comp.annotations assert 'draw' not in spec_comp.annotations
def run_test_with_generator(test): if hasattr(test, '_hypothesis_internal_test_function_without_warning'): # Pull out the original test function to avoid the warning we # stuck in about using @settings without @given. test = test._hypothesis_internal_test_function_without_warning if inspect.isclass(test): # Provide a meaningful error to users, instead of exceptions from # internals that assume we're dealing with a function. raise InvalidArgument('@given cannot be applied to a class.') generator_arguments = tuple(given_arguments) generator_kwargs = dict(given_kwargs) original_argspec = getfullargspec(test) check_invalid = is_invalid_test(test.__name__, original_argspec, generator_arguments, generator_kwargs) if check_invalid is not None: return check_invalid for name, strategy in zip(reversed(original_argspec.args), reversed(generator_arguments)): generator_kwargs[name] = strategy argspec = new_given_argspec(original_argspec, generator_kwargs) @impersonate(test) @define_function_signature(test.__name__, test.__doc__, argspec) def wrapped_test(*arguments, **kwargs): # Tell pytest to omit the body of this function from tracebacks __tracebackhide__ = True if getattr(test, 'is_hypothesis_test', False): note_deprecation( 'You have applied @given to a test more than once. In ' 'future this will be an error. Applying @given twice ' 'wraps the test twice, which can be extremely slow. A ' 'similar effect can be gained by combining the arguments ' 'of the two calls to given. For example, instead of ' '@given(booleans()) @given(integers()), you could write ' '@given(booleans(), integers())') settings = wrapped_test._hypothesis_internal_use_settings random = get_random_for_wrapped_test(test, wrapped_test) if infer in generator_kwargs.values(): hints = get_type_hints(test) for name in [ name for name, value in generator_kwargs.items() if value is infer ]: if name not in hints: raise InvalidArgument( 'passed %s=infer for %s, but %s has no type annotation' % (name, test.__name__, name)) generator_kwargs[name] = st.from_type(hints[name]) processed_args = process_arguments_to_given( wrapped_test, arguments, kwargs, generator_arguments, generator_kwargs, argspec, test, settings) arguments, kwargs, test_runner, search_strategy = processed_args runner = getattr(search_strategy, 'runner', None) if isinstance(runner, TestCase) and test.__name__ in dir(TestCase): msg = ('You have applied @given to the method %s, which is ' 'used by the unittest runner but is not itself a test.' ' This is not useful in any way.' % test.__name__) fail_health_check(settings, msg, HealthCheck.not_a_test_method) if bad_django_TestCase(runner): # pragma: no cover # Covered by the Django tests, but not the pytest coverage task raise InvalidArgument( 'You have applied @given to a method on %s, but this ' 'class does not inherit from the supported versions in ' '`hypothesis.extra.django`. Use the Hypothesis variants ' 'to ensure that each example is run in a separate ' 'database transaction.' % qualname(type(runner))) state = StateForActualGivenExecution( test_runner, search_strategy, test, settings, random, had_seed=wrapped_test._hypothesis_internal_use_seed) reproduce_failure = \ wrapped_test._hypothesis_internal_use_reproduce_failure if reproduce_failure is not None: expected_version, failure = reproduce_failure if expected_version != __version__: raise InvalidArgument( ('Attempting to reproduce a failure from a different ' 'version of Hypothesis. This failure is from %s, but ' 'you are currently running %r. Please change your ' 'Hypothesis version to a matching one.') % (expected_version, __version__)) try: state.execute( ConjectureData.for_buffer(decode_failure(failure)), print_example=True, is_final=True, ) raise DidNotReproduce( 'Expected the test to raise an error, but it ' 'completed successfully.') except StopTest: raise DidNotReproduce( 'The shape of the test data has changed in some way ' 'from where this blob was defined. Are you sure ' "you're running the same test?") except UnsatisfiedAssumption: raise DidNotReproduce( 'The test data failed to satisfy an assumption in the ' 'test. Have you added it since this blob was ' 'generated?') execute_explicit_examples(test_runner, test, wrapped_test, settings, arguments, kwargs) if settings.max_examples <= 0: return if not (Phase.reuse in settings.phases or Phase.generate in settings.phases): return try: if isinstance(runner, TestCase) and hasattr(runner, 'subTest'): subTest = runner.subTest try: setattr(runner, 'subTest', fake_subTest) state.run() finally: setattr(runner, 'subTest', subTest) else: state.run() except BaseException: generated_seed = \ wrapped_test._hypothesis_internal_use_generated_seed if generated_seed is not None and not state.failed_normally: if running_under_pytest: report( ('You can add @seed(%(seed)d) to this test or run ' 'pytest with --hypothesis-seed=%(seed)d to ' 'reproduce this failure.') % {'seed': generated_seed}, ) else: report( ('You can add @seed(%d) to this test to reproduce ' 'this failure.') % (generated_seed, )) raise for attrib in dir(test): if not (attrib.startswith('_') or hasattr(wrapped_test, attrib)): setattr(wrapped_test, attrib, getattr(test, attrib)) wrapped_test.is_hypothesis_test = True if hasattr(test, '_hypothesis_internal_settings_applied'): # Used to check if @settings is applied twice. wrapped_test._hypothesis_internal_settings_applied = True wrapped_test._hypothesis_internal_use_seed = getattr( test, '_hypothesis_internal_use_seed', None) wrapped_test._hypothesis_internal_use_settings = getattr( test, '_hypothesis_internal_use_settings', None) or Settings.default wrapped_test._hypothesis_internal_use_reproduce_failure = getattr( test, '_hypothesis_internal_use_reproduce_failure', None) return wrapped_test
def run_test_with_generator(test): if hasattr(test, '_hypothesis_internal_test_function_without_warning'): # Pull out the original test function to avoid the warning we # stuck in about using @settings without @given. test = test._hypothesis_internal_test_function_without_warning if inspect.isclass(test): # Provide a meaningful error to users, instead of exceptions from # internals that assume we're dealing with a function. raise InvalidArgument('@given cannot be applied to a class.') generator_arguments = tuple(given_arguments) generator_kwargs = dict(given_kwargs) original_argspec = getfullargspec(test) check_invalid = is_invalid_test( test.__name__, original_argspec, generator_arguments, generator_kwargs) if check_invalid is not None: return check_invalid for name, strategy in zip(reversed(original_argspec.args), reversed(generator_arguments)): generator_kwargs[name] = strategy argspec = new_given_argspec(original_argspec, generator_kwargs) @impersonate(test) @define_function_signature( test.__name__, test.__doc__, argspec ) def wrapped_test(*arguments, **kwargs): # Tell pytest to omit the body of this function from tracebacks __tracebackhide__ = True test = wrapped_test.hypothesis.inner_test if getattr(test, 'is_hypothesis_test', False): note_deprecation(( 'You have applied @given to test: %s more than once. In ' 'future this will be an error. Applying @given twice ' 'wraps the test twice, which can be extremely slow. A ' 'similar effect can be gained by combining the arguments ' 'of the two calls to given. For example, instead of ' '@given(booleans()) @given(integers()), you could write ' '@given(booleans(), integers())') % (test.__name__, ) ) settings = wrapped_test._hypothesis_internal_use_settings random = get_random_for_wrapped_test(test, wrapped_test) if infer in generator_kwargs.values(): hints = get_type_hints(test) for name in [name for name, value in generator_kwargs.items() if value is infer]: if name not in hints: raise InvalidArgument( 'passed %s=infer for %s, but %s has no type annotation' % (name, test.__name__, name)) generator_kwargs[name] = st.from_type(hints[name]) processed_args = process_arguments_to_given( wrapped_test, arguments, kwargs, generator_arguments, generator_kwargs, argspec, test, settings ) arguments, kwargs, test_runner, search_strategy = processed_args runner = getattr(search_strategy, 'runner', None) if isinstance(runner, TestCase) and test.__name__ in dir(TestCase): msg = ('You have applied @given to the method %s, which is ' 'used by the unittest runner but is not itself a test.' ' This is not useful in any way.' % test.__name__) fail_health_check(settings, msg, HealthCheck.not_a_test_method) if bad_django_TestCase(runner): # pragma: no cover # Covered by the Django tests, but not the pytest coverage task raise InvalidArgument( 'You have applied @given to a method on %s, but this ' 'class does not inherit from the supported versions in ' '`hypothesis.extra.django`. Use the Hypothesis variants ' 'to ensure that each example is run in a separate ' 'database transaction.' % qualname(type(runner)) ) state = StateForActualGivenExecution( test_runner, search_strategy, test, settings, random, had_seed=wrapped_test._hypothesis_internal_use_seed ) reproduce_failure = \ wrapped_test._hypothesis_internal_use_reproduce_failure if reproduce_failure is not None: expected_version, failure = reproduce_failure if expected_version != __version__: raise InvalidArgument(( 'Attempting to reproduce a failure from a different ' 'version of Hypothesis. This failure is from %s, but ' 'you are currently running %r. Please change your ' 'Hypothesis version to a matching one.' ) % (expected_version, __version__)) try: state.execute(ConjectureData.for_buffer( decode_failure(failure)), print_example=True, is_final=True, ) raise DidNotReproduce( 'Expected the test to raise an error, but it ' 'completed successfully.' ) except StopTest: raise DidNotReproduce( 'The shape of the test data has changed in some way ' 'from where this blob was defined. Are you sure ' "you're running the same test?" ) except UnsatisfiedAssumption: raise DidNotReproduce( 'The test data failed to satisfy an assumption in the ' 'test. Have you added it since this blob was ' 'generated?' ) execute_explicit_examples( test_runner, test, wrapped_test, settings, arguments, kwargs ) if settings.max_examples <= 0: return if not ( Phase.reuse in settings.phases or Phase.generate in settings.phases ): return try: if isinstance(runner, TestCase) and hasattr(runner, 'subTest'): subTest = runner.subTest try: setattr(runner, 'subTest', fake_subTest) state.run() finally: setattr(runner, 'subTest', subTest) else: state.run() except BaseException: generated_seed = \ wrapped_test._hypothesis_internal_use_generated_seed if generated_seed is not None and not state.failed_normally: with local_settings(settings): if running_under_pytest: report( 'You can add @seed(%(seed)d) to this test or ' 'run pytest with --hypothesis-seed=%(seed)d ' 'to reproduce this failure.' % { 'seed': generated_seed}) else: report( 'You can add @seed(%d) to this test to ' 'reproduce this failure.' % (generated_seed,)) raise for attrib in dir(test): if not (attrib.startswith('_') or hasattr(wrapped_test, attrib)): setattr(wrapped_test, attrib, getattr(test, attrib)) wrapped_test.is_hypothesis_test = True if hasattr(test, '_hypothesis_internal_settings_applied'): # Used to check if @settings is applied twice. wrapped_test._hypothesis_internal_settings_applied = True wrapped_test._hypothesis_internal_use_seed = getattr( test, '_hypothesis_internal_use_seed', None ) wrapped_test._hypothesis_internal_use_settings = getattr( test, '_hypothesis_internal_use_settings', None ) or Settings.default wrapped_test._hypothesis_internal_use_reproduce_failure = getattr( test, '_hypothesis_internal_use_reproduce_failure', None ) wrapped_test.hypothesis = HypothesisHandle(test) return wrapped_test
# generate keyword inital values and bindings kwargv = draw(kwarginit) kwargc = len(kwargv) kwargb = draw(hs.lists( hs.from_regex(binding_regex), min_size=kwargc, max_size=kwargc, unique=True, unique_by=lambda x: x not in argb.keys() )) else: kwargc = 0 kwargb = [] kwargv = [] spec = getfullargspec(body) if (spec.varargs is None and spec.varkw is None): #(min_argc is not None and min_argc < len(spc.args)) or \ #(max_argc is not None and max_argc > len(spc.args)) or \ # NOTE: # can't validate signature for kwargs so we're gonna require the # wrapper function contain both varargs and varkw just to be safe. raise InvalidArgument( "function body %s cannot support generated argument range" % (body) ) if manual_argument_bindings is not None: for key, value in manual_argument_bindings.items(): check_type(int, key, name="") check_type(text_type, value, name="")
def accept(proxy): return impersonate(target)(wraps(target)(define_function_signature( target.__name__.replace("<lambda>", "_lambda_"), target.__doc__, getfullargspec(target), )(proxy)))
def run_test_with_generator(test): generator_arguments = tuple(given_arguments) generator_kwargs = dict(given_kwargs) original_argspec = getfullargspec(test) check_invalid = is_invalid_test( test.__name__, original_argspec, generator_arguments, generator_kwargs) if check_invalid is not None: return check_invalid for name, strategy in zip(reversed(original_argspec.args), reversed(generator_arguments)): generator_kwargs[name] = strategy argspec = new_given_argspec(original_argspec, generator_kwargs) @impersonate(test) @define_function_signature( test.__name__, test.__doc__, argspec ) def wrapped_test(*arguments, **kwargs): # Tell pytest to omit the body of this function from tracebacks __tracebackhide__ = True if getattr(test, 'is_hypothesis_test', False): note_deprecation(( 'You have applied @given to a test more than once. In ' 'future this will be an error. Applying @given twice ' 'wraps the test twice, which can be extremely slow. A ' 'similar effect can be gained by combining the arguments ' 'to the two calls to given. For example, instead of ' '@given(booleans()) @given(integers()), you could write ' '@given(booleans(), integers())' )) settings = wrapped_test._hypothesis_internal_use_settings random = get_random_for_wrapped_test(test, wrapped_test) if infer in generator_kwargs.values(): hints = get_type_hints(test) for name in [name for name, value in generator_kwargs.items() if value is infer]: if name not in hints: raise InvalidArgument( 'passed %s=infer for %s, but %s has no type annotation' % (name, test.__name__, name)) generator_kwargs[name] = st.from_type(hints[name]) processed_args = process_arguments_to_given( wrapped_test, arguments, kwargs, generator_arguments, generator_kwargs, argspec, test, settings ) arguments, kwargs, test_runner, search_strategy = processed_args execute_explicit_examples( test_runner, test, wrapped_test, settings, arguments, kwargs ) if settings.max_examples <= 0: return if not ( Phase.reuse in settings.phases or Phase.generate in settings.phases ): return try: state = StateForActualGivenExecution( test_runner, search_strategy, test, settings, random, had_seed=wrapped_test._hypothesis_internal_use_seed ) state.run() except BaseException: generated_seed = \ wrapped_test._hypothesis_internal_use_generated_seed if ( generated_seed is not None and not state.used_examples_from_database ): if running_under_pytest: report(( 'You can add @seed(%(seed)d) to this test or run ' 'pytest with --hypothesis-seed=%(seed)d to ' 'reproduce this failure.') % { 'seed': generated_seed},) else: report(( 'You can add @seed(%d) to this test to reproduce ' 'this failure.') % (generated_seed,)) raise for attrib in dir(test): if not (attrib.startswith('_') or hasattr(wrapped_test, attrib)): setattr(wrapped_test, attrib, getattr(test, attrib)) wrapped_test.is_hypothesis_test = True wrapped_test._hypothesis_internal_use_seed = getattr( test, '_hypothesis_internal_use_seed', None ) wrapped_test._hypothesis_internal_use_settings = getattr( test, '_hypothesis_internal_use_settings', None ) or Settings.default return wrapped_test
def accept(proxy): return impersonate(target)(wraps(target)(define_function_signature( target.__name__, target.__doc__, getfullargspec(target))(proxy)))
def test_given_edits_annotations(nargs): spec_given = getfullargspec(given(*(nargs * [st.none()]))(pointless_composite)) assert spec_given.annotations.pop("return") is None assert len(spec_given.annotations) == 3 - nargs
def run_test_as_given(test): if inspect.isclass(test): # Provide a meaningful error to users, instead of exceptions from # internals that assume we're dealing with a function. raise InvalidArgument("@given cannot be applied to a class.") given_arguments = tuple(_given_arguments) given_kwargs = dict(_given_kwargs) original_argspec = getfullargspec(test) check_invalid = is_invalid_test(test.__name__, original_argspec, given_arguments, given_kwargs) # If the argument check found problems, return a dummy test function # that will raise an error if it is actually called. if check_invalid is not None: return check_invalid # Because the argument check succeeded, we can convert @given's # positional arguments into keyword arguments for simplicity. if given_arguments: assert not given_kwargs for name, strategy in zip(reversed(original_argspec.args), reversed(given_arguments)): given_kwargs[name] = strategy # These have been converted, so delete them to prevent accidental use. del given_arguments argspec = new_given_argspec(original_argspec, given_kwargs) @impersonate(test) @define_function_signature(test.__name__, test.__doc__, argspec) def wrapped_test(*arguments, **kwargs): # Tell pytest to omit the body of this function from tracebacks __tracebackhide__ = True test = wrapped_test.hypothesis.inner_test if getattr(test, "is_hypothesis_test", False): raise InvalidArgument(( "You have applied @given to the test %s more than once, which " "wraps the test several times and is extremely slow. A " "similar effect can be gained by combining the arguments " "of the two calls to given. For example, instead of " "@given(booleans()) @given(integers()), you could write " "@given(booleans(), integers())") % (test.__name__, )) settings = wrapped_test._hypothesis_internal_use_settings random = get_random_for_wrapped_test(test, wrapped_test) # Use type information to convert "infer" arguments into appropriate # strategies. if infer in given_kwargs.values(): hints = get_type_hints(test) for name in [ name for name, value in given_kwargs.items() if value is infer ]: if name not in hints: raise InvalidArgument( "passed %s=infer for %s, but %s has no type annotation" % (name, test.__name__, name)) given_kwargs[name] = st.from_type(hints[name]) processed_args = process_arguments_to_given( wrapped_test, arguments, kwargs, given_kwargs, argspec, test, settings, ) arguments, kwargs, test_runner, search_strategy = processed_args runner = getattr(search_strategy, "runner", None) if isinstance(runner, TestCase) and test.__name__ in dir(TestCase): msg = ("You have applied @given to the method %s, which is " "used by the unittest runner but is not itself a test." " This is not useful in any way." % test.__name__) fail_health_check(settings, msg, HealthCheck.not_a_test_method) if bad_django_TestCase(runner): # pragma: no cover # Covered by the Django tests, but not the pytest coverage task raise InvalidArgument( "You have applied @given to a method on %s, but this " "class does not inherit from the supported versions in " "`hypothesis.extra.django`. Use the Hypothesis variants " "to ensure that each example is run in a separate " "database transaction." % qualname(type(runner))) state = StateForActualGivenExecution( test_runner, search_strategy, test, settings, random, wrapped_test, ) reproduce_failure = wrapped_test._hypothesis_internal_use_reproduce_failure # If there was a @reproduce_failure decorator, use it to reproduce # the error (or complain that we couldn't). Either way, this will # always raise some kind of error. if reproduce_failure is not None: expected_version, failure = reproduce_failure if expected_version != __version__: raise InvalidArgument( ("Attempting to reproduce a failure from a different " "version of Hypothesis. This failure is from %s, but " "you are currently running %r. Please change your " "Hypothesis version to a matching one.") % (expected_version, __version__)) try: state.execute_once( ConjectureData.for_buffer(decode_failure(failure)), print_example=True, is_final=True, ) raise DidNotReproduce( "Expected the test to raise an error, but it " "completed successfully.") except StopTest: raise DidNotReproduce( "The shape of the test data has changed in some way " "from where this blob was defined. Are you sure " "you're running the same test?") except UnsatisfiedAssumption: raise DidNotReproduce( "The test data failed to satisfy an assumption in the " "test. Have you added it since this blob was " "generated?") # There was no @reproduce_failure, so start by running any explicit # examples from @example decorators. execute_explicit_examples(state, wrapped_test, arguments, kwargs) # If there were any explicit examples, they all ran successfully. # The next step is to use the Conjecture engine to run the test on # many different inputs. if settings.max_examples <= 0: return if not (Phase.reuse in settings.phases or Phase.generate in settings.phases): return try: if isinstance(runner, TestCase) and hasattr(runner, "subTest"): subTest = runner.subTest try: runner.subTest = fake_subTest state.run_engine() finally: runner.subTest = subTest else: state.run_engine() except BaseException as e: # The exception caught here should either be an actual test # failure (or MultipleFailures), or some kind of fatal error # that caused the engine to stop. generated_seed = wrapped_test._hypothesis_internal_use_generated_seed with local_settings(settings): if not (state.failed_normally or generated_seed is None): if running_under_pytest: report( "You can add @seed(%(seed)d) to this test or " "run pytest with --hypothesis-seed=%(seed)d " "to reproduce this failure." % {"seed": generated_seed}) else: report("You can add @seed(%d) to this test to " "reproduce this failure." % (generated_seed, )) # The dance here is to avoid showing users long tracebacks # full of Hypothesis internals they don't care about. # We have to do this inline, to avoid adding another # internal stack frame just when we've removed the rest. if PY2: # Python 2 doesn't have Exception.with_traceback(...); # instead it has a three-argument form of the `raise` # statement. Unfortunately this is a SyntaxError on # Python 3, and before Python 2.7.9 it was *also* a # SyntaxError to use it in a nested function so we # can't `exec` or `eval` our way out (BPO-21591). # So unless we break some versions of Python 2, none # of them get traceback elision. raise # On Python 3, we swap out the real traceback for our # trimmed version. Using a variable ensures that the line # which will actually appear in tracebacks is as clear as # possible - "raise the_error_hypothesis_found". the_error_hypothesis_found = e.with_traceback( get_trimmed_traceback()) raise the_error_hypothesis_found # After having created the decorated test function, we need to copy # over some attributes to make the switch as seamless as possible. for attrib in dir(test): if not (attrib.startswith("_") or hasattr(wrapped_test, attrib)): setattr(wrapped_test, attrib, getattr(test, attrib)) wrapped_test.is_hypothesis_test = True if hasattr(test, "_hypothesis_internal_settings_applied"): # Used to check if @settings is applied twice. wrapped_test._hypothesis_internal_settings_applied = True wrapped_test._hypothesis_internal_use_seed = getattr( test, "_hypothesis_internal_use_seed", None) wrapped_test._hypothesis_internal_use_settings = (getattr( test, "_hypothesis_internal_use_settings", None) or Settings.default) wrapped_test._hypothesis_internal_use_reproduce_failure = getattr( test, "_hypothesis_internal_use_reproduce_failure", None) wrapped_test.hypothesis = HypothesisHandle(test) return wrapped_test
def test_given_edits_annotations(nargs): spec_given = getfullargspec( given(*(nargs * [st.none()]))(pointless_composite)) assert spec_given.annotations.pop('return') is None assert len(spec_given.annotations) == 3 - nargs
def run_test_with_generator(test): generator_arguments = tuple(given_arguments) generator_kwargs = dict(given_kwargs) original_argspec = getfullargspec(test) check_invalid = is_invalid_test(test.__name__, original_argspec, generator_arguments, generator_kwargs) if check_invalid is not None: return check_invalid arguments = original_argspec.args for name, strategy in zip(arguments[-len(generator_arguments):], generator_arguments): generator_kwargs[name] = strategy argspec = original_argspec._replace( args=[a for a in arguments if a not in generator_kwargs], defaults=None, ) annots = { k: v for k, v in argspec.annotations.items() if k in (argspec.args + argspec.kwonlyargs) } annots['return'] = None argspec = argspec._replace(annotations=annots) @impersonate(test) @define_function_signature(test.__name__, test.__doc__, argspec) def wrapped_test(*arguments, **kwargs): # Tell pytest to omit the body of this function from tracebacks __tracebackhide__ = True settings = wrapped_test._hypothesis_internal_use_settings random = get_random_for_wrapped_test(test, wrapped_test) processed_args = process_arguments_to_given( wrapped_test, arguments, kwargs, generator_arguments, generator_kwargs, argspec, test, settings) arguments, kwargs, test_runner, search_strategy = processed_args execute_explicit_examples(test_runner, test, wrapped_test, settings, arguments, kwargs) if settings.max_examples <= 0: return if not (Phase.reuse in settings.phases or Phase.generate in settings.phases): return perform_health_checks(random, settings, test_runner, search_strategy) state = StateForActualGivenExecution(test_runner, search_strategy, test, settings, random) state.run() for attr in dir(test): if attr[0] != '_' and not hasattr(wrapped_test, attr): setattr(wrapped_test, attr, getattr(test, attr)) wrapped_test.is_hypothesis_test = True wrapped_test._hypothesis_internal_use_seed = getattr( test, '_hypothesis_internal_use_seed', None) wrapped_test._hypothesis_internal_use_settings = getattr( test, '_hypothesis_internal_use_settings', None) or Settings.default return wrapped_test
def extract_lambda_source(f): """Extracts a single lambda expression from the string source. Returns a string indicating an unknown body if it gets confused in any way. This is not a good function and I am sorry for it. Forgive me my sins, oh lord """ argspec = getfullargspec(f) arg_strings = [] # In Python 2 you can have destructuring arguments to functions. This # results in an argspec with non-string values. I'm not very interested in # handling these properly, but it's important to not crash on them. bad_lambda = False for a in argspec.args: if isinstance(a, (tuple, list)): # pragma: no cover arg_strings.append('(%s)' % (', '.join(a),)) bad_lambda = True else: assert isinstance(a, str) arg_strings.append(a) if argspec.varargs: arg_strings.append('*' + argspec.varargs) elif argspec.kwonlyargs: arg_strings.append('*') for a in (argspec.kwonlyargs or []): default = (argspec.kwonlydefaults or {}).get(a) if default: arg_strings.append('{}={}'.format(a, default)) else: arg_strings.append(a) if_confused = 'lambda %s: <unknown>' % (', '.join(arg_strings),) if bad_lambda: # pragma: no cover return if_confused try: source = inspect.getsource(f) except IOError: return if_confused source = LINE_CONTINUATION.sub(' ', source) source = WHITESPACE.sub(' ', source) source = source.strip() assert 'lambda' in source tree = None try: tree = ast.parse(source) except SyntaxError: for i in hrange(len(source) - 1, len('lambda'), -1): prefix = source[:i] if 'lambda' not in prefix: break try: tree = ast.parse(prefix) source = prefix break except SyntaxError: continue if tree is None: if source.startswith('@'): # This will always eventually find a valid expression because # the decorator must be a valid Python function call, so will # eventually be syntactically valid and break out of the loop. Thus # this loop can never terminate normally, so a no branch pragma is # appropriate. for i in hrange(len(source) + 1): # pragma: no branch p = source[1:i] if 'lambda' in p: try: tree = ast.parse(p) source = p break except SyntaxError: pass if tree is None: return if_confused all_lambdas = extract_all_lambdas(tree) aligned_lambdas = [ l for l in all_lambdas if args_for_lambda_ast(l) == argspec.args ] if len(aligned_lambdas) != 1: return if_confused lambda_ast = aligned_lambdas[0] assert lambda_ast.lineno == 1 source = source[lambda_ast.col_offset:].strip() source = source[source.index('lambda'):] for i in hrange(len(source), len('lambda'), -1): # pragma: no branch try: parsed = ast.parse(source[:i]) assert len(parsed.body) == 1 assert parsed.body if isinstance(parsed.body[0].value, ast.Lambda): source = source[:i] break except SyntaxError: pass lines = source.split('\n') lines = [PROBABLY_A_COMMENT.sub('', l) for l in lines] source = '\n'.join(lines) source = WHITESPACE.sub(' ', source) source = SPACE_FOLLOWS_OPEN_BRACKET.sub('(', source) source = SPACE_PRECEDES_CLOSE_BRACKET.sub(')', source) source = source.strip() return source