Beispiel #1
0
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
Beispiel #2
0
    def define_setting(cls, name, description, default, options=None):
        """Add a new setting.

        - name is the name of the property that will be used to access the
          setting. This must be a valid python identifier.
        - description will appear in the property's docstring
        - default is the default value. This may be a zero argument
          function in which case it is evaluated and its result is stored
          the first time it is accessed on any given Settings object.

        """
        if options is not None:
            options = tuple(options)
            if default not in options:
                raise InvalidArgument(
                    u'Default value %r is not in options %r' % (
                        default, options
                    )
                )

        all_settings[name] = Setting(
            name, description.strip(), default, options)
        setattr(cls, name, SettingsProperty(name))
Beispiel #3
0
def fulfill(contract_func):
    """Decorate ``contract_func`` to reject calls which violate preconditions,
    and retry them with different arguments.

    This is a convenience function for testing internal code that uses
    :pypi:`dpcontracts`, to automatically filter out arguments that would be
    rejected by the public interface before triggering a contract error.

    This can be used as ``builds(fulfill(func), ...)`` or in the body of the
    test e.g. ``assert fulfill(func)(*args)``.
    """
    if not hasattr(contract_func, "__contract_wrapped_func__"):
        raise InvalidArgument(
            f"{contract_func.__name__} has no dpcontracts preconditions")

    @proxies(contract_func)
    def inner(*args, **kwargs):
        try:
            return contract_func(*args, **kwargs)
        except PreconditionError:
            reject()

    return inner
Beispiel #4
0
def models(model, **extra):
    result = {}
    mandatory = set()
    for f in model._meta.concrete_fields:
        try:
            strategy = _get_strategy_for_field(f)
        except UnmappedFieldError:
            mandatory.add(f.name)
            continue
        if strategy is not None:
            result[f.name] = strategy
    missed = {x for x in mandatory if x not in extra}
    if missed:
        raise InvalidArgument(
            (u'Missing arguments for mandatory field%s %s for model %s' % (
                u's' if len(missed) > 1 else u'',
                u', '.join(missed),
                model.__name__,
            )))
    result.update(extra)
    # Remove default_values so we don't try to generate anything for those.
    result = {k: v for k, v in result.items() if v is not default_value}
    return ModelStrategy(model, result)
Beispiel #5
0
 def __setattr__(self, name, value):
     if name in settings._WHITELISTED_REAL_PROPERTIES:
         return object.__setattr__(self, name, value)
     elif name in all_settings:
         if self._construction_complete:
             raise AttributeError(
                 'settings objects are immutable and may not be assigned to'
                 ' after construction.'
             )
         else:
             setting = all_settings[name]
             if (
                 setting.options is not None and
                 value not in setting.options
             ):
                 raise InvalidArgument(
                     'Invalid %s, %r. Valid options: %r' % (
                         name, value, setting.options
                     )
                 )
             return object.__setattr__(self, name, value)
     else:
         raise AttributeError('No such setting %s' % (name,))
 def __init__(self,
              whitelist_categories=None,
              blacklist_categories=None,
              blacklist_characters=None,
              min_codepoint=None,
              max_codepoint=None,
              whitelist_characters=None):
     intervals = charmap.query(
         include_categories=whitelist_categories,
         exclude_categories=blacklist_categories,
         min_codepoint=min_codepoint,
         max_codepoint=max_codepoint,
         include_characters=whitelist_characters,
         exclude_characters=blacklist_characters,
     )
     if not intervals:
         raise InvalidArgument('No valid characters in set')
     self.intervals = IntervalSet(intervals)
     if whitelist_characters:
         self.whitelist_characters = set(whitelist_characters)
     else:
         self.whitelist_characters = set()
     self.zero_point = self.intervals.index_above(ord('0'))
Beispiel #7
0
def dictionaries(keys,
                 values,
                 dict_class=dict,
                 min_size=None,
                 average_size=None,
                 max_size=None):
    """Generates dictionaries of type dict_class with keys drawn from the keys
    argument and values drawn from the values argument.

    The size parameters have the same interpretation as for lists.

    """
    check_valid_sizes(min_size, average_size, max_size)
    if max_size == 0:
        return fixed_dictionaries(dict_class())
    check_strategy(keys)
    check_strategy(values)

    if min_size is not None and min_size > keys.template_upper_bound:
        raise InvalidArgument(
            (u'Cannot generate dictionaries of size %d with keys from %r, '
             u'which contains no more than %d distinct values') % (
                 min_size,
                 keys,
                 keys.template_upper_bound,
             ))

    if max_size is None:
        max_size = keys.template_upper_bound
    else:
        max_size = min(max_size, keys.template_upper_bound)

    return lists(tuples(keys, values),
                 min_size=min_size,
                 average_size=average_size,
                 max_size=max_size,
                 unique_by=lambda x: x[0]).map(dict_class)
Beispiel #8
0
def fractions(min_value=None, max_value=None, max_denominator=None):
    """Returns a strategy which generates Fractions.

    If min_value is not None then all generated values are no less than
    min_value.

    If max_value is not None then all generated values are no greater than
    max_value.

    If max_denominator is not None then the absolute value of the denominator
    of any generated values is no greater than max_denominator. Note that
    max_denominator must be at least 1.

    """
    check_valid_bound(min_value, 'min_value')
    check_valid_bound(max_value, 'max_value')
    check_valid_interval(min_value, max_value, 'min_value', 'max_value')

    check_valid_integer(max_denominator)
    if max_denominator is not None and max_denominator < 1:
        raise InvalidArgument(
            u'Invalid denominator bound %s' % max_denominator
        )

    denominator_strategy = integers(min_value=1, max_value=max_denominator)

    def dm_func(denom):
        max_num = max_value * denom if max_value is not None else None
        min_num = min_value * denom if min_value is not None else None

        return builds(
            Fraction,
            integers(min_value=min_num, max_value=max_num),
            just(denom)
        )

    return denominator_strategy.flatmap(dm_func)
Beispiel #9
0
 def __init__(self,
              whitelist_categories=None,
              blacklist_categories=None,
              blacklist_characters=None,
              min_codepoint=None,
              max_codepoint=None,
              whitelist_characters=None):
     assert set(whitelist_categories or ()).issubset(charmap.categories())
     assert set(blacklist_categories or ()).issubset(charmap.categories())
     intervals = charmap.query(
         include_categories=whitelist_categories,
         exclude_categories=blacklist_categories,
         min_codepoint=min_codepoint,
         max_codepoint=max_codepoint,
         include_characters=whitelist_characters,
         exclude_characters=blacklist_characters,
     )
     if not intervals:
         arguments = [
             ('whitelist_categories', whitelist_categories),
             ('blacklist_categories', blacklist_categories),
             ('whitelist_characters', whitelist_characters),
             ('blacklist_characters', blacklist_characters),
             ('min_codepoint', min_codepoint),
             ('max_codepoint', max_codepoint),
         ]
         raise InvalidArgument(
             'No characters are allowed to be generated by this '
             'combination of arguments: ' +
             ', '.join('%s=%r' % arg
                       for arg in arguments if arg[1] is not None))
     self.intervals = IntervalSet(intervals)
     if whitelist_characters:
         self.whitelist_characters = set(whitelist_characters)
     else:
         self.whitelist_characters = set()
     self.zero_point = self.intervals.index_above(ord('0'))
Beispiel #10
0
    def draw(self, strategy, label=None):
        if self.is_find and not strategy.supports_find:
            raise InvalidArgument(
                ("Cannot use strategy %r within a call to find (presumably "
                 "because it would be invalid after the call had ended).") %
                (strategy, ))

        at_top_level = self.depth == 0
        if at_top_level:
            # We start this timer early, because accessing attributes on a LazyStrategy
            # can be almost arbitrarily slow.  In cases like characters() and text()
            # where we cache something expensive, this led to Flaky deadline errors!
            # See https://github.com/HypothesisWorks/hypothesis/issues/2108
            start_time = time.perf_counter()

        strategy.validate()

        if strategy.is_empty:
            self.mark_invalid()

        if self.depth >= MAX_DEPTH:
            self.mark_invalid()

        if label is None:
            label = strategy.label
        self.start_example(label=label)
        try:
            if not at_top_level:
                return strategy.do_draw(self)
            else:
                strategy.validate()
                try:
                    return strategy.do_draw(self)
                finally:
                    self.draw_times.append(time.perf_counter() - start_time)
        finally:
            self.stop_example()
Beispiel #11
0
    def __init__(self, parent=None, **kwargs):
        # type: (settings, **Any) -> None
        if (
            kwargs.get('database', not_set) is not_set and
            kwargs.get('database_file', not_set) is not not_set
        ):
            if kwargs['database_file'] is None:
                kwargs['database'] = None
            else:
                from hypothesis.database import ExampleDatabase
                kwargs['database'] = ExampleDatabase(kwargs['database_file'])
        if not kwargs.get('perform_health_check', True):
            kwargs['suppress_health_check'] = HealthCheck.all()
        self._construction_complete = False
        deprecations = []
        defaults = parent or settings.default
        if defaults is not None:
            for setting in all_settings.values():
                if kwargs.get(setting.name, not_set) is not_set:
                    kwargs[setting.name] = getattr(defaults, setting.name)
                else:
                    if kwargs[setting.name] != setting.future_default:
                        if setting.deprecation_message is not None:
                            deprecations.append(setting)
                    if setting.validator:
                        kwargs[setting.name] = setting.validator(
                            kwargs[setting.name])
        for name, value in kwargs.items():
            if name not in all_settings:
                raise InvalidArgument(
                    'Invalid argument: %r is not a valid setting' % (name,))
            setattr(self, name, value)
        self.storage = threading.local()
        self._construction_complete = True

        for d in deprecations:
            note_deprecation(d.deprecation_message, self)
Beispiel #12
0
def from_dtype(dtype):
    # Compound datatypes, eg 'f4,f4,f4'
    if dtype.names is not None:
        # mapping np.void.type over a strategy is nonsense, so return now.
        return st.tuples(
            *[from_dtype(dtype.fields[name][0]) for name in dtype.names])

    # Subarray datatypes, eg '(2, 3)i4'
    if dtype.subdtype is not None:
        subtype, shape = dtype.subdtype
        return arrays(subtype, shape)

    # Scalar datatypes
    if dtype.kind == u'b':
        result = st.booleans()
    elif dtype.kind == u'f':
        result = st.floats()
    elif dtype.kind == u'c':
        result = st.complex_numbers()
    elif dtype.kind in (u'S', u'a'):
        result = st.binary()
    elif dtype.kind == u'u':
        result = st.integers(
            min_value=0, max_value=1 << (4 * dtype.itemsize) - 1)
    elif dtype.kind == u'i':
        min_integer = -1 << (4 * dtype.itemsize - 1)
        result = st.integers(min_value=min_integer, max_value=-min_integer - 1)
    elif dtype.kind == u'U':
        result = st.text()
    elif dtype.kind in (u'm', u'M'):
        res = st.just(dtype.str[-2]) if '[' in dtype.str else \
            st.sampled_from(TIME_RESOLUTIONS)
        result = st.builds(dtype.type, st.integers(1 - 2**63, 2**63 - 1), res)
    else:
        raise InvalidArgument(u'No strategy inference for {}'.format(dtype))
    return result.map(dtype.type)
Beispiel #13
0
def mutations(
    schema: Union[str, graphql.GraphQLSchema],
    *,
    fields: Optional[Iterable[str]] = None,
    custom_scalars: Optional[CustomScalarStrategies] = None,
    print_ast: AstPrinter = graphql.print_ast,
) -> st.SearchStrategy[str]:
    """A strategy for generating valid mutations for the given GraphQL schema.

    The output mutation will contain a subset of fields defined in the `Mutation` type.

    :param schema: GraphQL schema as a string or `graphql.GraphQLSchema`.
    :param fields: Restrict generated fields to ones in this list.
    :param custom_scalars: Strategies for generating custom scalars.
    :param print_ast: A function to convert the generated AST to a string.
    """
    parsed_schema = validation.maybe_parse_schema(schema)
    if parsed_schema.mutation_type is None:
        raise InvalidArgument("Mutation type is not defined in the schema")
    return (
        _make_strategy(parsed_schema, type_=parsed_schema.mutation_type, fields=fields, custom_scalars=custom_scalars)
        .map(make_mutation)
        .map(print_ast)
    )
Beispiel #14
0
def _convert_targets(targets, target):
    """Single validator and convertor for target arguments."""
    if target is not None:
        if targets:
            note_deprecation(
                "Passing both targets=%r and target=%r is redundant, and "
                "will become an error in a future version of Hypothesis.  "
                "Pass targets=%r instead." % (targets, target, tuple(targets) +
                                              (target, )),
                since="2018-08-18",
            )
        targets = tuple(targets) + (target, )

    converted_targets = []
    for t in targets:
        if isinstance(t, string_types):
            note_deprecation(
                "Got %r as a target, but passing the name of a Bundle is "
                "deprecated - please pass the Bundle directly." % (t, ),
                since="2018-08-18",
            )
        elif not isinstance(t, Bundle):
            msg = ("Got invalid target %r of type %r, but all targets must "
                   "be either a Bundle or the name of a Bundle.")
            if isinstance(t, OneOfStrategy):
                msg += (
                    "\nIt looks like you passed `one_of(a, b)` or `a | b` as "
                    "a target.  You should instead pass `targets=(a, b)` to "
                    "add the return value of this rule to both the `a` and "
                    "`b` bundles, or define a rule for each target if it "
                    "should be added to exactly one.")
            raise InvalidArgument(msg % (t, type(t)))
        while isinstance(t, Bundle):
            t = t.name
        converted_targets.append(t)
    return tuple(converted_targets)
Beispiel #15
0
 def __init__(self, parent=None, **kwargs):
     self._construction_complete = False
     self._database = kwargs.pop('database', not_set)
     explicit_kwargs = list(kwargs)
     defaults = parent or settings.default
     if defaults is not None:
         for setting in all_settings.values():
             if kwargs.get(setting.name, not_set) is not_set:
                 kwargs[setting.name] = getattr(defaults, setting.name)
             elif setting.validator:
                 kwargs[setting.name] = setting.validator(
                     kwargs[setting.name])
         if self._database is not_set:
             self._database = defaults.database
     for name, value in kwargs.items():
         if name not in all_settings:
             raise InvalidArgument('Invalid argument %s' % (name, ))
         setattr(self, name, value)
     self.storage = threading.local()
     self._construction_complete = True
     for k in explicit_kwargs:
         deprecation = all_settings[k].deprecation
         if deprecation:
             note_deprecation(deprecation, self)
Beispiel #16
0
 def by_name(cls, key):
     result = getattr(cls, key, None)
     if isinstance(result, Verbosity):
         return result
     raise InvalidArgument(u'No such verbosity level %r' % (key,))
Beispiel #17
0
    def run_test_with_generator(test):
        if settings.derandomize:
            assert provided_random is None
            random = Random(function_digest(test))
        else:
            random = provided_random or Random()

        original_argspec = inspect.getargspec(test)
        if original_argspec.varargs:
            raise InvalidArgument('varargs are not supported with @given')
        extra_kwargs = [
            k for k in generator_kwargs if k not in original_argspec.args
        ]
        if extra_kwargs and not original_argspec.keywords:
            raise InvalidArgument(
                '%s() got an unexpected keyword argument %r' %
                (test.__name__, extra_kwargs[0]))
        if (len(generator_arguments) > len(original_argspec.args)):
            raise InvalidArgument(
                ('Too many positional arguments for %s() (got %d but'
                 ' expected at most %d') %
                (test.__name__, len(generator_arguments),
                 len(original_argspec.args)))
        arguments = original_argspec.args + sorted(extra_kwargs)
        specifiers = list(generator_arguments)
        seen_kwarg = None
        for a in arguments:
            if a in generator_kwargs:
                seen_kwarg = seen_kwarg or a
                specifiers.append(generator_kwargs[a])
            else:
                if seen_kwarg is not None:
                    raise InvalidArgument(
                        ('Argument %s comes after keyword %s which has been '
                         'specified, but does not itself have a '
                         'specification') % (a, seen_kwarg))

        argspec = inspect.ArgSpec(args=arguments,
                                  keywords=original_argspec.keywords,
                                  varargs=original_argspec.varargs,
                                  defaults=tuple(
                                      map(HypothesisProvided, specifiers)))

        @copy_argspec(test.__name__, argspec)
        def wrapped_test(*arguments, **kwargs):
            selfy = None
            # Because we converted all kwargs to given into real args and
            # error if we have neither args nor kwargs, this should always
            # be valid
            assert argspec.args
            selfy = kwargs.get(argspec.args[0])
            if isinstance(selfy, HypothesisProvided):
                selfy = None
            test_runner = executor(selfy)

            for example in getattr(wrapped_test,
                                   'hypothesis_explicit_examples', ()):
                if example.args:
                    example_kwargs = dict(
                        zip(argspec.args[-len(example.args):], example.args))
                else:
                    example_kwargs = dict(example.kwargs)

                for k, v in kwargs.items():
                    if not isinstance(v, HypothesisProvided):
                        example_kwargs[k] = v

                test_runner(lambda: test(*arguments, **example_kwargs))

            if not any(
                    isinstance(x, HypothesisProvided)
                    for xs in (arguments, kwargs.values()) for x in xs):
                # All arguments have been satisfied without needing to invoke
                # hypothesis
                test_runner(lambda: test(*arguments, **kwargs))
                return

            def convert_to_specifier(v):
                if isinstance(v, HypothesisProvided):
                    return v.value
                else:
                    return just(v)

            given_specifier = (tuple(map(convert_to_specifier, arguments)),
                               dict([(k, convert_to_specifier(v))
                                     for (k, v) in kwargs.items()]))

            search_strategy = strategy(given_specifier, settings)

            if settings.database:
                storage = settings.database.storage_for(
                    given_specifier, search_strategy)
            else:
                storage = None

            def is_template_example(xs):
                try:
                    test_runner(reify_and_execute(search_strategy, xs, test))
                    return False
                except UnsatisfiedAssumption as e:
                    raise e
                except Exception:
                    return True

            is_template_example.__name__ = test.__name__
            is_template_example.__qualname__ = getattr(test, '__qualname__',
                                                       test.__name__)

            falsifying_template = None
            try:
                falsifying_template = best_satisfying_template(
                    search_strategy, random, is_template_example, settings,
                    storage)
            except NoSuchExample:
                return

            test_runner(
                reify_and_execute(search_strategy,
                                  falsifying_template,
                                  test,
                                  print_example=True))

            test_runner(
                reify_and_execute(search_strategy,
                                  falsifying_template,
                                  test_is_flaky(test),
                                  print_example=True))

        wrapped_test.__name__ = test.__name__
        wrapped_test.__doc__ = test.__doc__
        wrapped_test.is_hypothesis_test = True
        wrapped_test.hypothesis_explicit_examples = getattr(
            test, 'hypothesis_explicit_examples', [])
        return wrapped_test
    def pytest_runtest_call(item):
        if not (hasattr(item, "obj") and "hypothesis" in sys.modules):
            yield
            return

        from hypothesis import core
        from hypothesis.internal.detection import is_hypothesis_test

        core.running_under_pytest = True

        if not is_hypothesis_test(item.obj):
            # If @given was not applied, check whether other hypothesis
            # decorators were applied, and raise an error if they were.
            if getattr(item.obj, "is_hypothesis_strategy_function", False):
                from hypothesis.errors import InvalidArgument

                raise InvalidArgument(
                    f"{item.nodeid} is a function that returns a Hypothesis strategy, "
                    "but pytest has collected it as a test function.  This is useless "
                    "as the function body will never be executed.  To define a test "
                    "function, use @given instead of @composite.")
            message = "Using `@%s` on a test without `@given` is completely pointless."
            for name, attribute in [
                ("example", "hypothesis_explicit_examples"),
                ("seed", "_hypothesis_internal_use_seed"),
                ("settings", "_hypothesis_internal_settings_applied"),
                ("reproduce_example",
                 "_hypothesis_internal_use_reproduce_failure"),
            ]:
                if hasattr(item.obj, attribute):
                    from hypothesis.errors import InvalidArgument

                    raise InvalidArgument(message % (name, ))
            yield
        else:
            from hypothesis import HealthCheck, settings
            from hypothesis.internal.escalation import current_pytest_item
            from hypothesis.internal.healthcheck import fail_health_check
            from hypothesis.reporting import with_reporter
            from hypothesis.statistics import collector, describe_statistics

            # Retrieve the settings for this test from the test object, which
            # is normally a Hypothesis wrapped_test wrapper. If this doesn't
            # work, the test object is probably something weird
            # (e.g a stateful test wrapper), so we skip the function-scoped
            # fixture check.
            settings = getattr(item.obj, "_hypothesis_internal_use_settings",
                               None)

            # Check for suspicious use of function-scoped fixtures, but only
            # if the corresponding health check is not suppressed.
            if (settings is not None and HealthCheck.function_scoped_fixture
                    not in settings.suppress_health_check):
                # Warn about function-scoped fixtures, excluding autouse fixtures because
                # the advice is probably not actionable and the status quo seems OK...
                # See https://github.com/HypothesisWorks/hypothesis/issues/377 for detail.
                argnames = None
                for fx_defs in item._request._fixturemanager.getfixtureinfo(
                        node=item, func=item.function,
                        cls=None).name2fixturedefs.values():
                    if argnames is None:
                        argnames = frozenset(
                            signature(item.function).parameters)
                    for fx in fx_defs:
                        if fx.argname in argnames:
                            active_fx = item._request._get_active_fixturedef(
                                fx.argname)
                            if active_fx.scope == "function":
                                fail_health_check(
                                    settings,
                                    _FIXTURE_MSG.format(
                                        fx.argname, item.nodeid),
                                    HealthCheck.function_scoped_fixture,
                                )

            if item.get_closest_marker("parametrize") is not None:
                # Give every parametrized test invocation a unique database key
                key = item.nodeid.encode()
                item.obj.hypothesis.inner_test._hypothesis_internal_add_digest = key

            store = StoringReporter(item.config)

            def note_statistics(stats):
                stats["nodeid"] = item.nodeid
                item.hypothesis_statistics = base64.b64encode(
                    describe_statistics(stats).encode()).decode()

            with collector.with_value(note_statistics):
                with with_reporter(store):
                    with current_pytest_item.with_value(item):
                        yield
            if store.results:
                item.hypothesis_report_information = list(store.results)
Beispiel #19
0
        def assign_rows(draw):
            index = draw(index_strategy)

            result = pandas.DataFrame(OrderedDict(
                (c.name,
                 pandas.Series(np.zeros(dtype=c.dtype, shape=len(index)),
                               dtype=c.dtype)) for c in rewritten_columns),
                                      index=index)

            fills = {}

            any_unique = any(c.unique for c in rewritten_columns)

            if any_unique:
                all_seen = [
                    set() if c.unique else None for c in rewritten_columns
                ]
                while all_seen[-1] is None:
                    all_seen.pop()

            for row_index in hrange(len(index)):
                for _ in hrange(5):
                    original_row = draw(rows)
                    row = original_row
                    if isinstance(row, dict):
                        as_list = [None] * len(rewritten_columns)
                        for i, c in enumerate(rewritten_columns):
                            try:
                                as_list[i] = row[c.name]
                            except KeyError:
                                try:
                                    as_list[i] = fills[i]
                                except KeyError:
                                    fills[i] = draw(c.fill)
                                    as_list[i] = fills[i]
                        for k in row:
                            if k not in column_names:
                                raise InvalidArgument(
                                    ('Row %r contains column %r not in '
                                     'columns %r)' %
                                     (row, k,
                                      [c.name for c in rewritten_columns])))
                        row = as_list
                    if any_unique:
                        has_duplicate = False
                        for seen, value in zip(all_seen, row):
                            if seen is None:
                                continue
                            if value in seen:
                                has_duplicate = True
                                break
                            seen.add(value)
                        if has_duplicate:
                            continue
                    row = list(st.try_convert(tuple, row, 'draw(rows)'))

                    if len(row) > len(rewritten_columns):
                        raise InvalidArgument(
                            ('Row %r contains too many entries. Has %d but '
                             'expected at most %d') %
                            (original_row, len(row), len(rewritten_columns)))
                    while len(row) < len(rewritten_columns):
                        row.append(draw(rewritten_columns[len(row)].fill))
                    result.iloc[row_index] = row
                    break
                else:
                    reject()
            return result
Beispiel #20
0
def data_frames(columns=None, rows=None, index=None):
    """Provides a strategy for producing a :class:`pandas.DataFrame`.

    Arguments:

    * columns: An iterable of :class:`column` objects describing the shape
      of the generated DataFrame.

    * rows: A strategy for generating a row object. Should generate
      either dicts mapping column names to values or a sequence mapping
      column position to the value in that position (note that unlike the
      :class:`pandas.DataFrame` constructor, single values are not allowed
      here. Passing e.g. an integer is an error, even if there is only one
      column).

      At least one of rows and columns must be provided. If both are
      provided then the generated rows will be validated against the
      columns and an error will be raised if they don't match.

      Caveats on using rows:

      * In general you should prefer using columns to rows, and only use
        rows if the columns interface is insufficiently flexible to
        describe what you need - you will get better performance and
        example quality that way.
      * If you provide rows and not columns, then the shape and dtype of
        the resulting DataFrame may vary. e.g. if you have a mix of int
        and float in the values for one column in your row entries, the
        column will sometimes have an integral dtype and sometimes a float.

    * index: If not None, a strategy for generating indexes for the
      resulting DataFrame. This can generate either :class:`pandas.Index`
      objects or any sequence of values (which will be passed to the
      Index constructor).

      You will probably find it most convenient to use the
      :func:`~hypothesis.extra.pandas.indexes` or
      :func:`~hypothesis.extra.pandas.range_indexes` function to produce
      values for this argument.

    Usage:

    The expected usage pattern is that you use :class:`column` and
    :func:`columns` to specify a fixed shape of the DataFrame you want as
    follows. For example the following gives a two column data frame:

    .. code-block:: pycon

        >>> from hypothesis.extra.pandas import column, data_frames
        >>> data_frames([
        ... column('A', dtype=int), column('B', dtype=float)]).example()
                    A              B
        0  2021915903  1.793898e+232
        1  1146643993            inf
        2 -2096165693   1.000000e+07

    If you want the values in different columns to interact in some way you
    can use the rows argument. For example the following gives a two column
    DataFrame where the value in the first column is always at most the value
    in the second:

    .. code-block:: pycon

        >>> from hypothesis.extra.pandas import column, data_frames
        >>> import hypothesis.strategies as st
        >>> data_frames(
        ...     rows=st.tuples(st.floats(allow_nan=False),
        ...                    st.floats(allow_nan=False)).map(sorted)
        ... ).example()
                       0             1
        0  -3.402823e+38  9.007199e+15
        1 -1.562796e-298  5.000000e-01

    You can also combine the two:

    .. code-block:: pycon

        >>> from hypothesis.extra.pandas import column, data_frames
        >>> import hypothesis.strategies as st
        >>> data_frames(
        ...     columns=columns(["lo", "hi"], dtype=float),
        ...     rows=st.tuples(st.floats(allow_nan=False),
        ...                    st.floats(allow_nan=False)).map(sorted)
        ... ).example()
                 lo            hi
        0   9.314723e-49  4.353037e+45
        1  -9.999900e-01  1.000000e+07
        2 -2.152861e+134 -1.069317e-73

    (Note that the column dtype must still be specified and will not be
    inferred from the rows. This restriction may be lifted in future).

    Combining rows and columns has the following behaviour:

    * The column names and dtypes will be used.
    * If the column is required to be unique, this will be enforced.
    * Any values missing from the generated rows will be provided using the
      column's fill.
    * Any values in the row not present in the column specification (if
      dicts are passed, if there are keys with no corresponding column name,
      if sequences are passed if there are too many items) will result in
      InvalidArgument being raised.

    """

    if index is None:
        index = range_indexes()
    else:
        st.check_strategy(index)

    index_strategy = index

    if columns is None:
        if rows is None:
            raise InvalidArgument(
                'At least one of rows and columns must be provided')
        else:

            @st.composite
            def rows_only(draw):
                index = draw(index_strategy)

                @check_function
                def row():
                    result = draw(rows)
                    st.check_type(Iterable, result, 'draw(row)')
                    return result

                if len(index) > 0:
                    return pandas.DataFrame([row() for _ in index],
                                            index=index)
                else:
                    # If we haven't drawn any rows we need to draw one row and
                    # then discard it so that we get a consistent shape for the
                    # DataFrame.
                    base = pandas.DataFrame([row()])
                    return base.drop(0)

            return rows_only()

    assert columns is not None
    columns = st.try_convert(tuple, columns, 'columns')

    rewritten_columns = []
    column_names = set()

    for i, c in enumerate(columns):
        st.check_type(column, c, 'columns[%d]' % (i, ))

        c = copy(c)
        if c.name is None:
            label = 'columns[%d]' % (i, )
            c.name = i
        else:
            label = c.name
            try:
                hash(c.name)
            except TypeError:
                raise InvalidArgument(
                    'Column names must be hashable, but columns[%d].name was '
                    '%r of type %s, which cannot be hashed.' % (
                        i,
                        c.name,
                        type(c.name).__name__,
                    ))

        if c.name in column_names:
            raise InvalidArgument('duplicate definition of column name %r' %
                                  (c.name, ))

        column_names.add(c.name)

        c.elements, c.dtype = elements_and_dtype(c.elements, c.dtype, label)

        if c.dtype is None and rows is not None:
            raise InvalidArgument(
                'Must specify a dtype for all columns when combining rows with'
                ' columns.')

        c.fill = npst.fill_for(fill=c.fill,
                               elements=c.elements,
                               unique=c.unique,
                               name=label)

        rewritten_columns.append(c)

    if rows is None:

        @st.composite
        def just_draw_columns(draw):
            index = draw(index_strategy)
            local_index_strategy = st.just(index)

            data = OrderedDict((c.name, None) for c in rewritten_columns)

            # Depending on how the columns are going to be generated we group
            # them differently to get better shrinking. For columns with fill
            # enabled, the elements can be shrunk independently of the size,
            # so we can just shrink by shrinking the index then shrinking the
            # length and are generally much more free to move data around.

            # For columns with no filling the problem is harder, and drawing
            # them like that would result in rows being very far apart from
            # eachother in the underlying data stream, which gets in the way
            # of shrinking. So what we do is reorder and draw those columns
            # row wise, so that the values of each row are next to each other.
            # This makes life easier for the shrinker when deleting blocks of
            # data.
            columns_without_fill = [
                c for c in rewritten_columns if c.fill.is_empty
            ]

            if columns_without_fill:
                for c in columns_without_fill:
                    data[c.name] = pandas.Series(
                        np.zeros(shape=len(index), dtype=c.dtype),
                        index=index,
                    )
                seen = {
                    c.name: set()
                    for c in columns_without_fill if c.unique
                }

                for i in hrange(len(index)):
                    for c in columns_without_fill:
                        if c.unique:
                            for _ in range(5):
                                value = draw(c.elements)
                                if value not in seen[c.name]:
                                    seen[c.name].add(value)
                                    break
                            else:
                                reject()
                        else:
                            value = draw(c.elements)
                        data[c.name][i] = value

            for c in rewritten_columns:
                if not c.fill.is_empty:
                    data[c.name] = draw(
                        series(index=local_index_strategy,
                               dtype=c.dtype,
                               elements=c.elements,
                               fill=c.fill,
                               unique=c.unique))

            return pandas.DataFrame(data, index=index)

        return just_draw_columns()
    else:

        @st.composite
        def assign_rows(draw):
            index = draw(index_strategy)

            result = pandas.DataFrame(OrderedDict(
                (c.name,
                 pandas.Series(np.zeros(dtype=c.dtype, shape=len(index)),
                               dtype=c.dtype)) for c in rewritten_columns),
                                      index=index)

            fills = {}

            any_unique = any(c.unique for c in rewritten_columns)

            if any_unique:
                all_seen = [
                    set() if c.unique else None for c in rewritten_columns
                ]
                while all_seen[-1] is None:
                    all_seen.pop()

            for row_index in hrange(len(index)):
                for _ in hrange(5):
                    original_row = draw(rows)
                    row = original_row
                    if isinstance(row, dict):
                        as_list = [None] * len(rewritten_columns)
                        for i, c in enumerate(rewritten_columns):
                            try:
                                as_list[i] = row[c.name]
                            except KeyError:
                                try:
                                    as_list[i] = fills[i]
                                except KeyError:
                                    fills[i] = draw(c.fill)
                                    as_list[i] = fills[i]
                        for k in row:
                            if k not in column_names:
                                raise InvalidArgument(
                                    ('Row %r contains column %r not in '
                                     'columns %r)' %
                                     (row, k,
                                      [c.name for c in rewritten_columns])))
                        row = as_list
                    if any_unique:
                        has_duplicate = False
                        for seen, value in zip(all_seen, row):
                            if seen is None:
                                continue
                            if value in seen:
                                has_duplicate = True
                                break
                            seen.add(value)
                        if has_duplicate:
                            continue
                    row = list(st.try_convert(tuple, row, 'draw(rows)'))

                    if len(row) > len(rewritten_columns):
                        raise InvalidArgument(
                            ('Row %r contains too many entries. Has %d but '
                             'expected at most %d') %
                            (original_row, len(row), len(rewritten_columns)))
                    while len(row) < len(rewritten_columns):
                        row.append(draw(rewritten_columns[len(row)].fill))
                    result.iloc[row_index] = row
                    break
                else:
                    reject()
            return result

        return assign_rows()
Beispiel #21
0
        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
Beispiel #22
0
def _validate_stateful_step_count(x):
    check_type(int, x, name="stateful_step_count")
    if x < 1:
        raise InvalidArgument(f"stateful_step_count={x!r} must be at least one.")
    return x
Beispiel #23
0
def _validate_phases(phases):
    phases = tuple(phases)
    for a in phases:
        if not isinstance(a, Phase):
            raise InvalidArgument(f"{a!r} is not a valid phase")
    return tuple(p for p in list(Phase) if p in phases)
Beispiel #24
0
 def wrapped_test(*arguments, **kwargs):
     raise InvalidArgument(message)
Beispiel #25
0
    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
Beispiel #26
0
def _arrays(
    xp: Any,
    dtype: Union[DataType, str, st.SearchStrategy[DataType],
                 st.SearchStrategy[str]],
    shape: Union[int, Shape, st.SearchStrategy[Shape]],
    *,
    elements: Optional[Union[Mapping[str, Any], st.SearchStrategy]] = None,
    fill: Optional[st.SearchStrategy[Any]] = None,
    unique: bool = False,
) -> st.SearchStrategy:
    """Returns a strategy for :xp-ref:`arrays <array_object.html>`.

    * ``dtype`` may be a :xp-ref:`valid dtype <data_types.html>` object or name,
      or a strategy that generates such values.
    * ``shape`` may be an integer >= 0, a tuple of such integers, or a strategy
      that generates such values.
    * ``elements`` is a strategy for values to put in the array. If ``None``
      then a suitable value will be inferred based on the dtype, which may give
      any legal value (including e.g. NaN for floats). If a mapping, it will be
      passed as ``**kwargs`` to :func:`from_dtype()` when inferring based on the dtype.
    * ``fill`` is a strategy that may be used to generate a single background
      value for the array. If ``None``, a suitable default will be inferred
      based on the other arguments. If set to
      :func:`~hypothesis.strategies.nothing` then filling behaviour will be
      disabled entirely and every element will be generated independently.
    * ``unique`` specifies if the elements of the array should all be distinct
      from one another; if fill is also set, the only valid values for fill to
      return are NaN values.

    Arrays of specified ``dtype`` and ``shape`` are generated for example
    like this:

    .. code-block:: pycon

      >>> from numpy import array_api as xp
      >>> xps.arrays(xp, xp.int8, (2, 3)).example()
      Array([[-8,  6,  3],
             [-6,  4,  6]], dtype=int8)

    Specifying element boundaries by a :obj:`python:dict` of the kwargs to pass
    to :func:`from_dtype` will ensure ``dtype`` bounds will be respected.

    .. code-block:: pycon

      >>> xps.arrays(xp, xp.int8, 3, elements={"min_value": 10}).example()
      Array([125, 13, 79], dtype=int8)

    Refer to :doc:`What you can generate and how <data>` for passing
    your own elements strategy.

    .. code-block:: pycon

      >>> xps.arrays(xp, xp.float32, 3, elements=floats(0, 1, width=32)).example()
      Array([ 0.88974794,  0.77387938,  0.1977879 ], dtype=float32)

    Array values are generated in two parts:

    1. A single value is drawn from the fill strategy and is used to create a
       filled array.
    2. Some subset of the coordinates of the array are populated with a value
       drawn from the elements strategy (or its inferred form).

    You can set ``fill`` to :func:`~hypothesis.strategies.nothing` if you want
    to disable this behaviour and draw a value for every element.

    By default ``arrays`` will attempt to infer the correct fill behaviour: if
    ``unique`` is also ``True``, no filling will occur. Otherwise, if it looks
    safe to reuse the values of elements across multiple coordinates (this will
    be the case for any inferred strategy, and for most of the builtins, but is
    not the case for mutable values or strategies built with flatmap, map,
    composite, etc.) then it will use the elements strategy as the fill, else it
    will default to having no fill.

    Having a fill helps Hypothesis craft high quality examples, but its
    main importance is when the array generated is large: Hypothesis is
    primarily designed around testing small examples. If you have arrays with
    hundreds or more elements, having a fill value is essential if you want
    your tests to run in reasonable time.
    """
    check_xp_attributes(xp, [
        "finfo", "asarray", "zeros", "full", "all", "isnan", "isfinite",
        "reshape"
    ])

    if isinstance(dtype, st.SearchStrategy):
        return dtype.flatmap(lambda d: _arrays(
            xp, d, shape, elements=elements, fill=fill, unique=unique))
    elif isinstance(dtype, str):
        dtype = dtype_from_name(xp, dtype)

    if isinstance(shape, st.SearchStrategy):
        return shape.flatmap(lambda s: _arrays(
            xp, dtype, s, elements=elements, fill=fill, unique=unique))
    elif isinstance(shape, int):
        shape = (shape, )
    elif not isinstance(shape, tuple):
        raise InvalidArgument(
            f"shape={shape} is not a valid shape or strategy")
    check_argument(
        all(isinstance(x, int) and x >= 0 for x in shape),
        f"shape={shape!r}, but all dimensions must be non-negative integers.",
    )

    if elements is None:
        elements = _from_dtype(xp, dtype)
    elif isinstance(elements, Mapping):
        elements = _from_dtype(xp, dtype, **elements)
    check_strategy(elements, "elements")

    if fill is None:
        assert isinstance(elements, st.SearchStrategy)  # for mypy
        if unique or not elements.has_reusable_values:
            fill = st.nothing()
        else:
            fill = elements
    check_strategy(fill, "fill")

    return ArrayStrategy(xp, elements, dtype, shape, fill, unique)
Beispiel #27
0
    def do_draw(self, data):
        if 0 in self.shape:
            return self.xp.zeros(self.shape, dtype=self.dtype)

        if self.fill.is_empty:
            # We have no fill value (either because the user explicitly
            # disabled it or because the default behaviour was used and our
            # elements strategy does not produce reusable values), so we must
            # generate a fully dense array with a freshly drawn value for each
            # entry.
            elems = data.draw(
                st.lists(
                    self.elements_strategy,
                    min_size=self.array_size,
                    max_size=self.array_size,
                    unique=self.unique,
                ))
            try:
                result = self.xp.asarray(elems, dtype=self.dtype)
            except Exception as e:
                if len(elems) <= 6:
                    f_elems = str(elems)
                else:
                    f_elems = f"[{elems[0]}, {elems[1]}, ..., {elems[-2]}, {elems[-1]}]"
                types = tuple(
                    sorted({type(e)
                            for e in elems}, key=lambda t: t.__name__))
                f_types = f"type {types[0]}" if len(
                    types) == 1 else f"types {types}"
                raise InvalidArgument(
                    f"Generated elements {f_elems} from strategy "
                    f"{self.elements_strategy} could not be converted "
                    f"to array of dtype {self.dtype}. "
                    f"Consider if elements of {f_types} "
                    f"are compatible with {self.dtype}.") from e
            for i in range(self.array_size):
                self.check_set_value(elems[i], result[i],
                                     self.elements_strategy)
        else:
            # We draw arrays as "sparse with an offset". We assume not every
            # element will be assigned and so first draw a single value from our
            # fill strategy to create a full array. We then draw a collection of
            # index assignments within the array and assign fresh values from
            # our elements strategy to those indices.

            fill_val = data.draw(self.fill)
            try:
                result = self.xp.full(self.array_size,
                                      fill_val,
                                      dtype=self.dtype)
            except Exception as e:
                raise InvalidArgument(
                    f"Could not create full array of dtype={self.dtype} "
                    f"with fill value {fill_val!r}") from e
            sample = result[0]
            self.check_set_value(fill_val, sample, self.fill)
            if self.unique and not self.xp.all(self.xp.isnan(result)):
                raise InvalidArgument(
                    f"Array module {self.xp.__name__} did not recognise fill "
                    f"value {fill_val!r} as NaN - instead got {sample!r}. "
                    "Cannot fill unique array with non-NaN values.")

            elements = cu.many(
                data,
                min_size=0,
                max_size=self.array_size,
                # sqrt isn't chosen for any particularly principled reason. It
                # just grows reasonably quickly but sublinearly, and for small
                # arrays it represents a decent fraction of the array size.
                average_size=min(
                    0.9 *
                    self.array_size,  # ensure small arrays sometimes use fill
                    max(10,
                        math.sqrt(self.array_size)),  # ...but *only* sometimes
                ),
            )

            assigned = set()
            seen = set()

            while elements.more():
                i = cu.integer_range(data, 0, self.array_size - 1)
                if i in assigned:
                    elements.reject()
                    continue
                val = data.draw(self.elements_strategy)
                if self.unique:
                    if val in seen:
                        elements.reject()
                        continue
                    else:
                        seen.add(val)
                try:
                    result[i] = val
                except Exception as e:
                    raise InvalidArgument(
                        f"Could not add generated array element {val!r} "
                        f"of type {type(val)} to array of dtype {result.dtype}."
                    ) from e
                self.check_set_value(val, result[i], self.elements_strategy)
                assigned.add(i)

        result = self.xp.reshape(result, self.shape)

        return result
Beispiel #28
0
def find(
        specifier,  # type: SearchStrategy
        condition,  # type: Callable[[Any], bool]
        settings=None,  # type: Settings
        random=None,  # type: Any
        database_key=None,  # type: bytes
):
    # type: (...) -> Any
    """Returns the minimal example from the given strategy ``specifier`` that
    matches the predicate function ``condition``."""
    if settings is None:
        settings = Settings(max_examples=2000)
    settings = Settings(settings, suppress_health_check=HealthCheck.all())

    if database_key is None and settings.database is not None:
        database_key = function_digest(condition)

    if not isinstance(specifier, SearchStrategy):
        raise InvalidArgument("Expected SearchStrategy but got %r of type %s" %
                              (specifier, type(specifier).__name__))
    specifier.validate()

    search = specifier

    random = random or new_random()
    successful_examples = [0]
    last_data = [None]
    last_repr = [None]

    def template_condition(data):
        with BuildContext(data):
            try:
                data.is_find = True
                with deterministic_PRNG():
                    result = data.draw(search)
                    data.note(result)
                    success = condition(result)
            except UnsatisfiedAssumption:
                data.mark_invalid()

        if success:
            successful_examples[0] += 1

        if settings.verbosity >= Verbosity.verbose:
            if not successful_examples[0]:
                report(u"Tried non-satisfying example %s" %
                       (nicerepr(result), ))
            elif success:
                if successful_examples[0] == 1:
                    last_repr[0] = nicerepr(result)
                    report(u"Found satisfying example %s" % (last_repr[0], ))
                    last_data[0] = data
                elif (sort_key(hbytes(data.buffer)) < sort_key(
                        last_data[0].buffer)
                      ) and nicerepr(result) != last_repr[0]:
                    last_repr[0] = nicerepr(result)
                    report(u"Shrunk example to %s" % (last_repr[0], ))
                    last_data[0] = data
        if success and not data.frozen:
            data.mark_interesting()

    runner = ConjectureRunner(template_condition,
                              settings=settings,
                              random=random,
                              database_key=database_key)
    runner.run()
    note_engine_for_statistics(runner)
    if runner.interesting_examples:
        data = ConjectureData.for_buffer(
            list(runner.interesting_examples.values())[0].buffer)
        with BuildContext(data):
            with deterministic_PRNG():
                return data.draw(search)
    if runner.valid_examples == 0 and (runner.exit_reason !=
                                       ExitReason.finished):
        raise Unsatisfiable("Unable to satisfy assumptions of %s." %
                            (get_pretty_function_description(condition), ))

    raise NoSuchExample(get_pretty_function_description(condition))
Beispiel #29
0
def current_build_context():
    context = _current_build_context.value
    if context is None:
        raise InvalidArgument(u"No build context registered")
    return context
Beispiel #30
0
 def validator(value):
     if value not in options:
         msg = f"Invalid {name}, {value!r}. Valid options: {options!r}"
         raise InvalidArgument(msg)
     return value