123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740 |
- # coding=utf-8
- #
- # This file is part of Hypothesis, which may be found at
- # https://github.com/HypothesisWorks/hypothesis-python
- #
- # Most of this work is copyright (C) 2013-2018 David R. MacIver
- # (david@drmaciver.com), but it contains contributions by others. See
- # CONTRIBUTING.rst for a full list of people who may hold copyright, and
- # consult the git log if you need to determine who owns an individual
- # contribution.
- #
- # This Source Code Form is subject to the terms of the Mozilla Public License,
- # v. 2.0. If a copy of the MPL was not distributed with this file, You can
- # obtain one at http://mozilla.org/MPL/2.0/.
- #
- # END HEADER
- """A module controlling settings for Hypothesis to use in falsification.
- Either an explicit settings object can be used or the default object on
- this module can be modified.
- """
- from __future__ import division, print_function, absolute_import
- import os
- import inspect
- import warnings
- import threading
- from enum import Enum, IntEnum, unique
- import attr
- from hypothesis.errors import InvalidArgument, HypothesisDeprecationWarning
- from hypothesis.configuration import hypothesis_home_dir
- from hypothesis.utils.conventions import UniqueIdentifier, not_set
- from hypothesis.internal.validation import try_convert
- from hypothesis.utils.dynamicvariables import DynamicVariable
- __all__ = [
- 'settings',
- ]
- unlimited = UniqueIdentifier('unlimited')
- all_settings = {}
- _db_cache = {}
- class settingsProperty(object):
- def __init__(self, name, show_default):
- self.name = name
- self.show_default = show_default
- def __get__(self, obj, type=None):
- if obj is None:
- return self
- else:
- try:
- return obj.__dict__[self.name]
- except KeyError:
- raise AttributeError(self.name)
- def __set__(self, obj, value):
- obj.__dict__[self.name] = value
- def __delete__(self, obj):
- raise AttributeError('Cannot delete attribute %s' % (self.name,))
- @property
- def __doc__(self):
- description = all_settings[self.name].description
- deprecation_message = all_settings[self.name].deprecation_message
- default = repr(getattr(settings.default, self.name)) if \
- self.show_default else '(dynamically calculated)'
- return '\n\n'.join([description, 'default value: %s' % (default,),
- (deprecation_message or '').strip()]).strip()
- default_variable = DynamicVariable(None)
- class settingsMeta(type):
- def __init__(self, *args, **kwargs):
- super(settingsMeta, self).__init__(*args, **kwargs)
- @property
- def default(self):
- v = default_variable.value
- if v is not None:
- return v
- if hasattr(settings, '_current_profile'):
- settings.load_profile(settings._current_profile)
- assert default_variable.value is not None
- return default_variable.value
- @default.setter
- def default(self, value):
- raise AttributeError('Cannot assign settings.default')
- def _assign_default_internal(self, value):
- default_variable.value = value
- class settings(settingsMeta('settings', (object,), {})):
- """A settings object controls a variety of parameters that are used in
- falsification. These may control both the falsification strategy and the
- details of the data that is generated.
- Default values are picked up from the settings.default object and
- changes made there will be picked up in newly created settings.
- """
- _WHITELISTED_REAL_PROPERTIES = [
- '_database', '_construction_complete', 'storage'
- ]
- __definitions_are_locked = False
- _profiles = {}
- def __getattr__(self, name):
- if name in all_settings:
- d = all_settings[name].default
- if inspect.isfunction(d):
- d = d()
- return d
- else:
- raise AttributeError('settings has no attribute %s' % (name,))
- def __init__(
- self,
- parent=None,
- **kwargs
- ):
- self._construction_complete = False
- self._database = kwargs.pop('database', not_set)
- database_file = kwargs.get('database_file', not_set)
- 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])
- if self._database is not_set and database_file 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 d in deprecations:
- note_deprecation(d.deprecation_message, self)
- def defaults_stack(self):
- try:
- return self.storage.defaults_stack
- except AttributeError:
- self.storage.defaults_stack = []
- return self.storage.defaults_stack
- def __call__(self, test):
- test._hypothesis_internal_use_settings = self
- return test
- @classmethod
- def define_setting(
- cls, name, description, default, options=None, deprecation=None,
- validator=None, show_default=True, future_default=not_set,
- deprecation_message=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 settings.__definitions_are_locked:
- from hypothesis.errors import InvalidState
- raise InvalidState(
- 'settings have been locked and may no longer be defined.'
- )
- if options is not None:
- options = tuple(options)
- assert default in options
- if future_default is not_set:
- future_default = default
- all_settings[name] = Setting(
- name, description.strip(), default, options, validator,
- future_default, deprecation_message,
- )
- setattr(settings, name, settingsProperty(name, show_default))
- @classmethod
- def lock_further_definitions(cls):
- settings.__definitions_are_locked = True
- def __setattr__(self, name, value):
- if name in settings._WHITELISTED_REAL_PROPERTIES:
- return object.__setattr__(self, name, value)
- elif name == 'database':
- assert self._construction_complete
- raise AttributeError(
- 'settings objects are immutable and may not be assigned to'
- ' after construction.'
- )
- 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 __repr__(self):
- bits = []
- for name in all_settings:
- value = getattr(self, name)
- bits.append('%s=%r' % (name, value))
- bits.sort()
- return 'settings(%s)' % ', '.join(bits)
- @property
- def database(self):
- """An ExampleDatabase instance to use for storage of examples. May be
- None.
- If this was explicitly set at settings instantiation then that
- value will be used (even if it was None). If not and the
- database_file setting is not None this will be lazily loaded as
- an ExampleDatabase, using that file the first time that this
- property is accessed on a particular thread.
- """
- if self._database is not_set and self.database_file is not None:
- from hypothesis.database import ExampleDatabase
- if self.database_file not in _db_cache:
- _db_cache[self.database_file] = (
- ExampleDatabase(self.database_file))
- return _db_cache[self.database_file]
- if self._database is not_set:
- self._database = None
- return self._database
- def __enter__(self):
- default_context_manager = default_variable.with_value(self)
- self.defaults_stack().append(default_context_manager)
- default_context_manager.__enter__()
- return self
- def __exit__(self, *args, **kwargs):
- default_context_manager = self.defaults_stack().pop()
- return default_context_manager.__exit__(*args, **kwargs)
- @staticmethod
- def register_profile(name, settings):
- """registers a collection of values to be used as a settings profile.
- These settings can be loaded in by name. Enable different defaults for
- different settings.
- - settings is a settings object
- """
- settings._profiles[name] = settings
- @staticmethod
- def get_profile(name):
- """Return the profile with the given name.
- - name is a string representing the name of the profile
- to load
- A InvalidArgument exception will be thrown if the
- profile does not exist
- """
- try:
- return settings._profiles[name]
- except KeyError:
- raise InvalidArgument(
- "Profile '{0}' has not been registered".format(
- name
- )
- )
- @staticmethod
- def load_profile(name):
- """Loads in the settings defined in the profile provided If the profile
- does not exist an InvalidArgument will be thrown.
- Any setting not defined in the profile will be the library
- defined default for that setting
- """
- settings._current_profile = name
- settings._assign_default_internal(settings.get_profile(name))
- @attr.s()
- class Setting(object):
- name = attr.ib()
- description = attr.ib()
- default = attr.ib()
- options = attr.ib()
- validator = attr.ib()
- future_default = attr.ib()
- deprecation_message = attr.ib()
- settings.define_setting(
- 'min_satisfying_examples',
- default=5,
- description="""
- Raise Unsatisfiable for any tests which do not produce at least this many
- values that pass all assume() calls and which have not exhaustively covered the
- search space.
- """
- )
- settings.define_setting(
- 'max_examples',
- default=100,
- description="""
- Once this many satisfying examples have been considered without finding any
- counter-example, falsification will terminate.
- """
- )
- settings.define_setting(
- 'max_iterations',
- default=1000,
- description="""
- Once this many iterations of the example loop have run, including ones which
- failed to satisfy assumptions and ones which produced duplicates, falsification
- will terminate.
- """
- )
- settings.define_setting(
- 'buffer_size',
- default=8 * 1024,
- description="""
- The size of the underlying data used to generate examples. If you need to
- generate really large examples you may want to increase this, but it will make
- your tests slower.
- """
- )
- settings.define_setting(
- 'max_shrinks',
- default=500,
- description="""
- Once this many successful shrinks have been performed, Hypothesis will assume
- something has gone a bit wrong and give up rather than continuing to try to
- shrink the example.
- """
- )
- def _validate_timeout(n):
- if n is unlimited:
- return -1
- else:
- return n
- settings.define_setting(
- 'timeout',
- default=60,
- description="""
- Once this many seconds have passed, falsify will terminate even
- if it has not found many examples. This is a soft rather than a hard
- limit - Hypothesis won't e.g. interrupt execution of the called
- function to stop it. If this value is <= 0 then no timeout will be
- applied.
- """,
- deprecation_message="""
- The timeout setting is deprecated and will be removed in a future version of
- Hypothesis. To get the future behaviour set ``timeout=hypothesis.unlimited``
- instead (which will remain valid for a further deprecation period after this
- setting has gone away).
- """,
- future_default=unlimited,
- validator=_validate_timeout
- )
- settings.define_setting(
- 'derandomize',
- default=False,
- description="""
- If this is True then hypothesis will run in deterministic mode
- where each falsification uses a random number generator that is seeded
- based on the hypothesis to falsify, which will be consistent across
- multiple runs. This has the advantage that it will eliminate any
- randomness from your tests, which may be preferable for some situations.
- It does have the disadvantage of making your tests less likely to
- find novel breakages.
- """
- )
- settings.define_setting(
- 'strict',
- default=os.getenv('HYPOTHESIS_STRICT_MODE') == 'true',
- description="""
- If set to True, anything that would cause Hypothesis to issue a warning will
- instead raise an error. Note that new warnings may be added at any time, so
- running with strict set to True means that new Hypothesis releases may validly
- break your code. Note also that, as strict mode is itself deprecated,
- enabling it is now an error!
- You can enable this setting temporarily by setting the HYPOTHESIS_STRICT_MODE
- environment variable to the string 'true'.
- """,
- deprecation_message="""
- Strict mode is deprecated and will go away in a future version of Hypothesis.
- To get the same behaviour, use
- warnings.simplefilter('error', HypothesisDeprecationWarning).
- """,
- future_default=False,
- )
- settings.define_setting(
- 'database_file',
- default=lambda: (
- os.getenv('HYPOTHESIS_DATABASE_FILE') or
- os.path.join(hypothesis_home_dir(), 'examples')
- ),
- show_default=False,
- description="""
- An instance of hypothesis.database.ExampleDatabase that will be
- used to save examples to and load previous examples from. May be None
- in which case no storage will be used.
- """
- )
- @unique
- class Phase(IntEnum):
- explicit = 0
- reuse = 1
- generate = 2
- shrink = 3
- @unique
- class HealthCheck(Enum):
- """Arguments for :attr:`~hypothesis.settings.suppress_health_check`.
- Each member of this enum is a type of health check to suppress.
- """
- exception_in_generation = 0
- """Deprecated and no longer does anything. It used to convert errors in
- data generation into FailedHealthCheck error."""
- data_too_large = 1
- """Check for when the typical size of the examples you are generating
- exceeds the maximum allowed size too often."""
- filter_too_much = 2
- """Check for when the test is filtering out too many examples, either
- through use of :func:`~hypothesis.assume()` or :ref:`filter() <filtering>`,
- or occasionally for Hypothesis internal reasons."""
- too_slow = 3
- """Check for when your data generation is extremely slow and likely to hurt
- testing."""
- random_module = 4
- """Deprecated and no longer does anything. It used to check for whether
- your tests used the global random module. Now @given tests automatically
- seed random so this is no longer an error."""
- return_value = 5
- """Checks if your tests return a non-None value (which will be ignored and
- is unlikely to do what you want)."""
- hung_test = 6
- """Checks if your tests have been running for a very long time."""
- large_base_example = 7
- """Checks if the natural example to shrink towards is very large."""
- @unique
- class Statistics(IntEnum):
- never = 0
- interesting = 1
- always = 2
- class Verbosity(object):
- def __repr__(self):
- return 'Verbosity.%s' % (self.name,)
- def __init__(self, name, level):
- self.name = name
- self.level = level
- def __eq__(self, other):
- return isinstance(other, Verbosity) and (
- self.level == other.level
- )
- def __ne__(self, other):
- return not self.__eq__(other)
- def __hash__(self):
- return self.level
- def __lt__(self, other):
- return self.level < other.level
- def __le__(self, other):
- return self.level <= other.level
- def __gt__(self, other):
- return self.level > other.level
- def __ge__(self, other):
- return self.level >= other.level
- @classmethod
- def by_name(cls, key):
- result = getattr(cls, key, None)
- if isinstance(result, Verbosity):
- return result
- raise InvalidArgument('No such verbosity level %r' % (key,))
- Verbosity.quiet = Verbosity('quiet', 0)
- Verbosity.normal = Verbosity('normal', 1)
- Verbosity.verbose = Verbosity('verbose', 2)
- Verbosity.debug = Verbosity('debug', 3)
- Verbosity.all = [
- Verbosity.quiet, Verbosity.normal, Verbosity.verbose, Verbosity.debug
- ]
- ENVIRONMENT_VERBOSITY_OVERRIDE = os.getenv('HYPOTHESIS_VERBOSITY_LEVEL')
- if ENVIRONMENT_VERBOSITY_OVERRIDE: # pragma: no cover
- DEFAULT_VERBOSITY = Verbosity.by_name(ENVIRONMENT_VERBOSITY_OVERRIDE)
- else:
- DEFAULT_VERBOSITY = Verbosity.normal
- settings.define_setting(
- 'verbosity',
- options=Verbosity.all,
- default=DEFAULT_VERBOSITY,
- description='Control the verbosity level of Hypothesis messages',
- )
- def _validate_phases(phases):
- if phases is None:
- return tuple(Phase)
- phases = tuple(phases)
- for a in phases:
- if not isinstance(a, Phase):
- raise InvalidArgument('%r is not a valid phase' % (a,))
- return phases
- settings.define_setting(
- 'phases',
- default=tuple(Phase),
- description=(
- 'Control which phases should be run. ' +
- 'See :ref:`the full documentation for more details <phases>`'
- ),
- validator=_validate_phases,
- )
- settings.define_setting(
- name='stateful_step_count',
- default=50,
- description="""
- Number of steps to run a stateful program for before giving up on it breaking.
- """
- )
- settings.define_setting(
- 'perform_health_check',
- default=True,
- description=u"""
- If set to True, Hypothesis will run a preliminary health check before
- attempting to actually execute your test.
- """
- )
- def validate_health_check_suppressions(suppressions):
- suppressions = try_convert(list, suppressions, 'suppress_health_check')
- for s in suppressions:
- if not isinstance(s, HealthCheck):
- note_deprecation((
- 'Non-HealthCheck value %r of type %s in suppress_health_check '
- 'will be ignored, and will become an error in a future '
- 'version of Hypothesis') % (
- s, type(s).__name__,
- ))
- elif s in (
- HealthCheck.exception_in_generation, HealthCheck.random_module
- ):
- note_deprecation((
- '%s is now ignored and suppressing it is a no-op. This will '
- 'become an error in a future version of Hypothesis. Simply '
- 'remove it from your list of suppressions to get the same '
- 'effect.') % (s,))
- return suppressions
- settings.define_setting(
- 'suppress_health_check',
- default=(),
- description="""A list of health checks to disable.""",
- validator=validate_health_check_suppressions
- )
- settings.define_setting(
- 'deadline',
- default=not_set,
- description=u"""
- If set, a time in milliseconds (which may be a float to express
- smaller units of time) that each individual example (i.e. each time your test
- function is called, not the whole decorated test) within a test is not
- allowed to exceed. Tests which take longer than that may be converted into
- errors (but will not necessarily be if close to the deadline, to allow some
- variability in test run time).
- Set this to None to disable this behaviour entirely.
- In future this will default to 200. For now, a
- HypothesisDeprecationWarning will be emitted if you exceed that default
- deadline and have not explicitly set a deadline yourself.
- """
- )
- settings.define_setting(
- 'use_coverage',
- default=True,
- description="""
- Whether to use coverage information to improve Hypothesis's ability to find
- bugs.
- You should generally leave this turned on unless your code performs
- poorly when run under coverage. If you turn it off, please file a bug report
- or add a comment to an existing one about the problem that prompted you to do
- so.
- """
- )
- class PrintSettings(Enum):
- """Flags to determine whether or not to print a detailed example blob to
- use with :func:`~hypothesis.reproduce_failure` for failing test cases."""
- NEVER = 0
- """Never print a blob."""
- INFER = 1
- """Make an educated guess as to whether it would be appropriate to print
- the blob.
- The current rules are that this will print if both:
- 1. The output from Hypothesis appears to be unsuitable for use with
- :func:`~hypothesis.example`.
- 2. The output is not too long."""
- ALWAYS = 2
- """Always print a blob on failure."""
- settings.define_setting(
- 'print_blob',
- default=PrintSettings.INFER,
- description="""
- Determines whether to print blobs after tests that can be used to reproduce
- failures.
- See :ref:`the documentation on @reproduce_failure <reproduce_failure>` for
- more details of this behaviour.
- """
- )
- settings.lock_further_definitions()
- settings.register_profile('default', settings())
- settings.load_profile('default')
- assert settings.default is not None
- def note_deprecation(message, s=None):
- if s is None:
- s = settings.default
- assert s is not None
- verbosity = s.verbosity
- warning = HypothesisDeprecationWarning(message)
- if verbosity > Verbosity.quiet:
- warnings.warn(warning, stacklevel=3)
|