def new_buffer(self): self.last_data = TestData(max_length=self.settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(self.random, n)) self.test_function(self.last_data) self.last_data.freeze() self.note_for_corpus(self.last_data)
def test_distribution_is_correctly_translated(inter, rnd): assert inter == sorted(inter) lower, c1, c2, upper = inter d = TestData(draw_bytes=lambda data, n, distribution: distribution(rnd, n), max_length=10**6) assert d.draw(interval(lower, upper, c1, lambda r: c2)) == c2 assert d.draw(interval(lower, upper, c2, lambda r: c1)) == c1
def test_distribution_is_correctly_translated(inter, rnd): assert inter == sorted(inter) lower, c1, c2, upper = inter d = TestData( draw_bytes=lambda data, n, distribution: distribution(rnd, n), max_length=10 ** 6 ) assert d.draw(interval(lower, upper, c1, lambda r: c2)) == c2 assert d.draw(interval(lower, upper, c2, lambda r: c1)) == c1
def incorporate_new_buffer(self, buffer): if buffer in self.seen: return False assert self.last_data.status == Status.INTERESTING if ( self.settings.timeout > 0 and time.time() >= self.start_time + self.settings.timeout ): raise RunIsComplete() self.examples_considered += 1 buffer = buffer[:self.last_data.index] if sort_key(buffer) >= sort_key(self.last_data.buffer): return False assert sort_key(buffer) <= sort_key(self.last_data.buffer) data = TestData.for_buffer(buffer) self.test_function(data) data.freeze() self.note_for_corpus(data) if self.consider_new_test_data(data): self.shrinks += 1 self.last_data = data if self.shrinks >= self.settings.max_shrinks: raise RunIsComplete() self.last_data = data self.changed += 1 return True return False
def test_draw_past_end_sets_overflow(): x = TestData.for_buffer(bytes(5)) with pytest.raises(StopTest) as e: x.draw_bytes(6) assert e.value.testcounter == x.testcounter assert x.frozen assert x.status == Status.OVERRUN
def incorporate_new_buffer(self, buffer): if buffer in self.seen: return False assert self.last_data.status == Status.INTERESTING if (self.settings.timeout > 0 and time.time() >= self.start_time + self.settings.timeout): raise RunIsComplete() self.examples_considered += 1 buffer = buffer[:self.last_data.index] if sort_key(buffer) >= sort_key(self.last_data.buffer): return False assert sort_key(buffer) <= sort_key(self.last_data.buffer) data = TestData.for_buffer(buffer) self.test_function(data) data.freeze() self.note_for_corpus(data) if self.consider_new_test_data(data): self.shrinks += 1 self.last_data = data if self.shrinks >= self.settings.max_shrinks: raise RunIsComplete() self.last_data = data self.changed += 1 return True return False
def new_buffer(self): self.last_data = TestData( max_length=self.settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(self.random, n) ) self.test_function(self.last_data) self.last_data.freeze()
def test_variadic_draw(): def draw_list(data): result = [] while True: data.start_example() d = data.draw_bytes(1)[0] & 7 if d: result.append(data.draw_bytes(d)) data.stop_example() if not d: break return result @run_to_buffer def b(data): if any(all(d) for d in draw_list(data)): data.mark_interesting() l = draw_list(TestData.for_buffer(b)) assert len(l) == 1 assert len(l[0]) == 1
def test_can_double_freeze(): x = TestData.for_buffer(b'hi') x.freeze() assert x.frozen x.freeze() assert x.frozen
def wrapped_test(*arguments, **kwargs): settings = wrapped_test._hypothesis_internal_use_settings if wrapped_test._hypothesis_internal_use_seed is not None: random = Random(wrapped_test._hypothesis_internal_use_seed) elif settings.derandomize: random = Random(function_digest(test)) else: random = new_random() import hypothesis.strategies as sd selfy = None arguments, kwargs = convert_positional_arguments( wrapped_test, arguments, kwargs) # If the test function is a method of some kind, the bound object # will be the first named argument if there are any, otherwise the # first vararg (if any). if argspec.args: selfy = kwargs.get(argspec.args[0]) elif arguments: selfy = arguments[0] test_runner = new_style_executor(selfy) for example in reversed( getattr(wrapped_test, 'hypothesis_explicit_examples', ())): 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 = dict( zip(original_argspec.args[-len(example.args):], example.args)) else: example_kwargs = 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. message_on_failure = 'Falsifying example: %s(%s)' % ( test.__name__, arg_string(test, arguments, example_kwargs)) try: with BuildContext() as b: test_runner( None, lambda data: test(*arguments, **example_kwargs)) except BaseException: report(message_on_failure) for n in b.notes: report(n) raise if settings.max_examples <= 0: return arguments = tuple(arguments) given_specifier = sd.tuples( sd.just(arguments), sd.fixed_dictionaries(generator_kwargs).map( lambda args: dict(args, **kwargs))) def fail_health_check(message, label): if label in settings.suppress_health_check: return message += ( '\nSee http://hypothesis.readthedocs.org/en/latest/health' 'checks.html for more information about this. ') message += ( 'If you want to disable just this health check, add %s ' 'to the suppress_health_check settings for this test.') % ( label, ) raise FailedHealthCheck(message) search_strategy = given_specifier if selfy is not None: search_strategy = WithRunner(search_strategy, selfy) search_strategy.validate() perform_health_check = settings.perform_health_check perform_health_check &= Settings.default.perform_health_check from hypothesis.internal.conjecture.data import TestData, Status, \ StopTest if not (Phase.reuse in settings.phases or Phase.generate in settings.phases): return if perform_health_check: health_check_random = Random(random.getrandbits(128)) # We "pre warm" the health check with one draw to give it some # time to calculate any cached data. This prevents the case # where the first draw of the health check takes ages because # of loading unicode data the first time. data = TestData(max_length=settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(health_check_random, n)) with Settings(settings, verbosity=Verbosity.quiet): try: test_runner( data, reify_and_execute( search_strategy, lambda *args, **kwargs: None, )) except BaseException: pass count = 0 overruns = 0 filtered_draws = 0 start = time.time() while (count < 10 and time.time() < start + 1 and filtered_draws < 50 and overruns < 20): try: data = TestData( max_length=settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(health_check_random, n)) with Settings(settings, verbosity=Verbosity.quiet): test_runner( data, reify_and_execute( search_strategy, lambda *args, **kwargs: None, )) count += 1 except UnsatisfiedAssumption: filtered_draws += 1 except StopTest: if data.status == Status.INVALID: filtered_draws += 1 else: assert data.status == Status.OVERRUN overruns += 1 except InvalidArgument: raise except Exception: if (HealthCheck.exception_in_generation in settings.suppress_health_check): raise report(traceback.format_exc()) if test_runner is default_new_style_executor: fail_health_check( 'An exception occurred during data ' 'generation in initial health check. ' 'This indicates a bug in the strategy. ' 'This could either be a Hypothesis bug or ' "an error in a function you've passed to " 'it to construct your data.', HealthCheck.exception_in_generation, ) else: fail_health_check( 'An exception occurred during data ' 'generation in initial health check. ' 'This indicates a bug in the strategy. ' 'This could either be a Hypothesis bug or ' 'an error in a function you\'ve passed to ' 'it to construct your data. Additionally, ' 'you have a custom executor, which means ' 'that this could be your executor failing ' 'to handle a function which returns None. ', HealthCheck.exception_in_generation, ) if overruns >= 20 or (not count and overruns > 0): fail_health_check(( 'Examples routinely exceeded the max allowable size. ' '(%d examples overran while generating %d valid ones)' '. Generating examples this large will usually lead to' ' bad results. You should try setting average_size or ' 'max_size parameters on your collections and turning ' 'max_leaves down on recursive() calls.') % (overruns, count), HealthCheck.data_too_large) if filtered_draws >= 50 or (not count and filtered_draws > 0): fail_health_check(( 'It looks like your strategy is filtering out a lot ' 'of data. Health check found %d filtered examples but ' 'only %d good ones. This will make your tests much ' 'slower, and also will probably distort the data ' 'generation quite a lot. You should adapt your ' 'strategy to filter less. This can also be caused by ' 'a low max_leaves parameter in recursive() calls') % (filtered_draws, count), HealthCheck.filter_too_much) runtime = time.time() - start if runtime > 1.0 or count < 10: fail_health_check( ('Data generation is extremely slow: Only produced ' '%d valid examples in %.2f seconds (%d invalid ones ' 'and %d exceeded maximum size). Try decreasing ' "size of the data you're generating (with e.g." 'average_size or max_leaves parameters).') % (count, runtime, filtered_draws, overruns), HealthCheck.too_slow, ) last_exception = [None] repr_for_last_exception = [None] def evaluate_test_data(data): try: result = test_runner( data, reify_and_execute( search_strategy, test, )) if result is not None and settings.perform_health_check: fail_health_check( ('Tests run under @given should return None, but ' '%s returned %r instead.') % (test.__name__, result), HealthCheck.return_value) return False except UnsatisfiedAssumption: data.mark_invalid() except ( HypothesisDeprecationWarning, FailedHealthCheck, StopTest, ): raise except Exception: last_exception[0] = traceback.format_exc() verbose_report(last_exception[0]) data.mark_interesting() from hypothesis.internal.conjecture.engine import TestRunner falsifying_example = None database_key = str_to_bytes(fully_qualified_name(test)) start_time = time.time() runner = TestRunner( evaluate_test_data, settings=settings, random=random, database_key=database_key, ) runner.run() run_time = time.time() - start_time timed_out = (settings.timeout > 0 and run_time >= settings.timeout) if runner.last_data is None: return if runner.last_data.status == Status.INTERESTING: falsifying_example = runner.last_data.buffer if settings.database is not None: settings.database.save(database_key, falsifying_example) else: if runner.valid_examples < min( settings.min_satisfying_examples, settings.max_examples, ): if timed_out: raise Timeout( ('Ran out of time before finding a satisfying ' 'example for ' '%s. Only found %d examples in ' + '%.2fs.') % (get_pretty_function_description(test), runner.valid_examples, run_time)) else: raise Unsatisfiable( ('Unable to satisfy assumptions of hypothesis ' '%s. Only %d examples considered ' 'satisfied assumptions') % ( get_pretty_function_description(test), runner.valid_examples, )) return assert last_exception[0] is not None try: with settings: test_runner( TestData.for_buffer(falsifying_example), reify_and_execute(search_strategy, test, print_example=True, is_final=True)) except (UnsatisfiedAssumption, StopTest): report(traceback.format_exc()) raise Flaky( 'Unreliable assumption: An example which satisfied ' 'assumptions on the first run now fails it.') report( 'Failed to reproduce exception. Expected: \n' + last_exception[0], ) filter_message = ( 'Unreliable test data: Failed to reproduce a failure ' 'and then when it came to recreating the example in ' 'order to print the test data with a flaky result ' 'the example was filtered out (by e.g. a ' 'call to filter in your strategy) when we didn\'t ' 'expect it to be.') try: test_runner( TestData.for_buffer(falsifying_example), reify_and_execute(search_strategy, test_is_flaky( test, repr_for_last_exception[0]), print_example=True, is_final=True)) except (UnsatisfiedAssumption, StopTest): raise Flaky(filter_message)
def test_buffer_draws_as_self(buf): x = TestData.for_buffer(buf) assert x.draw_bytes(len(buf), bogus_dist) == buf
def _run(self): self.last_data = None mutations = 0 start_time = time.time() if ( self.settings.database is not None and self.database_key is not None ): corpus = sorted( self.settings.database.fetch(self.database_key), key=lambda d: (len(d), d) ) for existing in corpus: if self.valid_examples >= self.settings.max_examples: self.exit_reason = ExitReason.max_examples return if self.call_count >= max( self.settings.max_iterations, self.settings.max_examples ): self.exit_reason = ExitReason.max_iterations return data = TestData.for_buffer(existing) self.test_function(data) data.freeze() self.last_data = data if data.status < Status.VALID: self.settings.database.delete( self.database_key, existing) elif data.status == Status.VALID: # Incremental garbage collection! we store a lot of # examples in the DB as we shrink: Those that stay # interesting get kept, those that become invalid get # dropped, but those that are merely valid gradually go # away over time. if self.random.randint(0, 2) == 0: self.settings.database.delete( self.database_key, existing) else: assert data.status == Status.INTERESTING self.last_data = data break if Phase.generate in self.settings.phases: if ( self.last_data is None or self.last_data.status < Status.INTERESTING ): self.new_buffer() mutator = self._new_mutator() while self.last_data.status != Status.INTERESTING: if self.valid_examples >= self.settings.max_examples: self.exit_reason = ExitReason.max_examples return if self.call_count >= max( self.settings.max_iterations, self.settings.max_examples ): self.exit_reason = ExitReason.max_iterations return if ( self.settings.timeout > 0 and time.time() >= start_time + self.settings.timeout ): self.exit_reason = ExitReason.timeout return if mutations >= self.settings.max_mutations: mutations = 0 self.new_buffer() mutator = self._new_mutator() else: data = TestData( draw_bytes=mutator, max_length=self.settings.buffer_size ) self.test_function(data) data.freeze() prev_data = self.last_data if self.consider_new_test_data(data): self.last_data = data if data.status > prev_data.status: mutations = 0 else: mutator = self._new_mutator() mutations += 1 data = self.last_data if data is None: self.exit_reason = ExitReason.finished return assert isinstance(data.output, text_type) if self.settings.max_shrinks <= 0: self.exit_reason = ExitReason.max_shrinks return if Phase.shrink not in self.settings.phases: self.exit_reason = ExitReason.finished return if not self.last_data.buffer: self.exit_reason = ExitReason.finished return data = TestData.for_buffer(self.last_data.buffer) self.test_function(data) if data.status != Status.INTERESTING: self.exit_reason = ExitReason.flaky return change_counter = -1 while self.changed > change_counter: change_counter = self.changed self.debug('Random interval deletes') failed_deletes = 0 while self.last_data.intervals and failed_deletes < 10: if self.random.randint(0, 1): u, v = self.random.choice(self.last_data.intervals) else: n = len(self.last_data.buffer) - 1 u, v = sorted(( self.random.choice(self.last_data.intervals) )) if ( v < len(self.last_data.buffer) ) and self.incorporate_new_buffer( self.last_data.buffer[:u] + self.last_data.buffer[v:] ): failed_deletes = 0 else: failed_deletes += 1 self.debug('Structured interval deletes') i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] if not self.incorporate_new_buffer( self.last_data.buffer[:u] + self.last_data.buffer[v:] ): i += 1 if change_counter != self.changed: self.debug('Restarting') continue self.debug('Lexicographical minimization of whole buffer') minimize( self.last_data.buffer, self.incorporate_new_buffer, cautious=True ) if change_counter != self.changed: self.debug('Restarting') continue self.debug('Replacing blocks with simpler blocks') i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] buf = self.last_data.buffer block = buf[u:v] n = v - u all_blocks = sorted(set([bytes(n)] + [ buf[a:a + n] for a in self.last_data.block_starts[n] ])) better_blocks = all_blocks[:all_blocks.index(block)] for b in better_blocks: if self.incorporate_new_buffer( buf[:u] + b + buf[v:] ): break i += 1 self.debug('Simultaneous shrinking of duplicated blocks') block_counter = -1 while block_counter < self.changed: block_counter = self.changed blocks = [ k for k, count in Counter( self.last_data.buffer[u:v] for u, v in self.last_data.blocks).items() if count > 1 ] for block in blocks: parts = [ self.last_data.buffer[r:s] for r, s in self.last_data.blocks ] def replace(b): return b''.join( bytes(b if c == block else c) for c in parts ) minimize( block, lambda b: self.incorporate_new_buffer(replace(b)), self.random ) self.debug('Shrinking of individual blocks') i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] minimize( self.last_data.buffer[u:v], lambda b: self.incorporate_new_buffer( self.last_data.buffer[:u] + b + self.last_data.buffer[v:], ), self.random ) i += 1 self.debug('Replacing intervals with simpler intervals') interval_counter = -1 while interval_counter != self.changed: interval_counter = self.changed i = 0 alternatives = None while i < len(self.last_data.intervals): if alternatives is None: alternatives = sorted(set( self.last_data.buffer[u:v] for u, v in self.last_data.intervals), key=len) u, v = self.last_data.intervals[i] for a in alternatives: buf = self.last_data.buffer if ( len(a) < v - u or (len(a) == (v - u) and a < buf[u:v]) ): if self.incorporate_new_buffer( buf[:u] + a + buf[v:] ): alternatives = None break i += 1 if change_counter != self.changed: self.debug('Restarting') continue self.debug('Shuffling suffixes while shrinking %r' % ( self.last_data.bind_points, )) b = 0 while b < len(self.last_data.bind_points): cutoff = sorted(self.last_data.bind_points)[b] def test_value(prefix): for t in hrange(5): alphabet = {} for i, j in self.last_data.blocks[b:]: alphabet.setdefault(j - i, []).append((i, j)) if t > 0: for v in alphabet.values(): self.random.shuffle(v) buf = bytearray(prefix) for i, j in self.last_data.blocks[b:]: u, v = alphabet[j - i].pop() buf.extend(self.last_data.buffer[u:v]) if self.incorporate_new_buffer(hbytes(buf)): return True return False minimize( self.last_data.buffer[:cutoff], test_value, cautious=True ) b += 1 self.exit_reason = ExitReason.finished
def _run(self): self.last_data = None mutations = 0 start_time = time.time() if (self.settings.database is not None and self.database_key is not None): corpus = sorted(self.settings.database.fetch(self.database_key), key=lambda d: (len(d), d)) for existing in corpus: if self.valid_examples >= self.settings.max_examples: return if self.iterations >= max(self.settings.max_iterations, self.settings.max_examples): return data = TestData.for_buffer(existing) self.test_function(data) data.freeze() self.last_data = data if data.status < Status.VALID: self.settings.database.delete(self.database_key, existing) elif data.status == Status.VALID: # Incremental garbage collection! we store a lot of # examples in the DB as we shrink: Those that stay # interesting get kept, those that become invalid get # dropped, but those that are merely valid gradually go # away over time. if self.random.randint(0, 2) == 0: self.settings.database.delete(self.database_key, existing) else: assert data.status == Status.INTERESTING self.last_data = data break if Phase.generate in self.settings.phases: if (self.last_data is None or self.last_data.status < Status.INTERESTING): self.new_buffer() mutator = self._new_mutator() while self.last_data.status != Status.INTERESTING: if self.valid_examples >= self.settings.max_examples: return if self.iterations >= max(self.settings.max_iterations, self.settings.max_examples): return if (self.settings.timeout > 0 and time.time() >= start_time + self.settings.timeout): return if mutations >= self.settings.max_mutations: mutations = 0 self.new_buffer() mutator = self._new_mutator() else: data = TestData(draw_bytes=mutator, max_length=self.settings.buffer_size) self.test_function(data) data.freeze() self.note_for_corpus(data) prev_data = self.last_data if self.consider_new_test_data(data): self.last_data = data if data.status > prev_data.status: mutations = 0 else: mutator = self._new_mutator() mutations += 1 data = self.last_data if data is None: return assert isinstance(data.output, text_type) if self.settings.max_shrinks <= 0: return if Phase.shrink not in self.settings.phases: return if not self.last_data.buffer: return data = TestData.for_buffer(self.last_data.buffer) self.test_function(data) if data.status != Status.INTERESTING: return change_counter = -1 while self.changed > change_counter: change_counter = self.changed failed_deletes = 0 while self.last_data.intervals and failed_deletes < 10: if self.random.randint(0, 1): u, v = self.random.choice(self.last_data.intervals) else: n = len(self.last_data.buffer) - 1 u, v = sorted( (self.random.choice(self.last_data.intervals))) if ( v < len(self.last_data.buffer) ) and self.incorporate_new_buffer(self.last_data.buffer[:u] + self.last_data.buffer[v:]): failed_deletes = 0 else: failed_deletes += 1 i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] if not self.incorporate_new_buffer(self.last_data.buffer[:u] + self.last_data.buffer[v:]): i += 1 i = 0 while i + 1 < len(self.last_data.buffer): if not self.incorporate_new_buffer(self.last_data.buffer[:i] + self.last_data.buffer[i + 1:]): i += 1 i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] buf = self.last_data.buffer block = buf[u:v] n = v - u all_blocks = sorted( set([bytes(n)] + [buf[a:a + n] for a in self.last_data.block_starts[n]])) better_blocks = all_blocks[:all_blocks.index(block)] for b in better_blocks: if self.incorporate_new_buffer(buf[:u] + b + buf[v:]): break i += 1 block_counter = -1 while block_counter < self.changed: block_counter = self.changed blocks = [ k for k, count in Counter( self.last_data.buffer[u:v] for u, v in self.last_data.blocks).items() if count > 1 ] for block in blocks: parts = [ self.last_data.buffer[r:s] for r, s in self.last_data.blocks ] def replace(b): return b''.join( bytes(b if c == block else c) for c in parts) minimize(block, lambda b: self.incorporate_new_buffer(replace(b)), self.random) i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] minimize( self.last_data.buffer[u:v], lambda b: self.incorporate_new_buffer( self.last_data.buffer[:u] + b + self.last_data.buffer[ v:], ), self.random) i += 1 i = 0 alternatives = None while i < len(self.last_data.intervals): if alternatives is None: alternatives = sorted(set( self.last_data.buffer[u:v] for u, v in self.last_data.intervals), key=len) u, v = self.last_data.intervals[i] for a in alternatives: buf = self.last_data.buffer if (len(a) < v - u or (len(a) == (v - u) and a < buf[u:v])): if self.incorporate_new_buffer(buf[:u] + a + buf[v:]): alternatives = None break i += 1
def wrapped_test(*arguments, **kwargs): settings = wrapped_test._hypothesis_internal_use_settings if wrapped_test._hypothesis_internal_use_seed is not None: random = Random( wrapped_test._hypothesis_internal_use_seed) elif settings.derandomize: random = Random(function_digest(test)) else: random = new_random() import hypothesis.strategies as sd selfy = None arguments, kwargs = convert_positional_arguments( wrapped_test, arguments, kwargs) # If the test function is a method of some kind, the bound object # will be the first named argument if there are any, otherwise the # first vararg (if any). if argspec.args: selfy = kwargs.get(argspec.args[0]) elif arguments: selfy = arguments[0] test_runner = new_style_executor(selfy) for example in reversed(getattr( wrapped_test, 'hypothesis_explicit_examples', () )): 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 = dict(zip( original_argspec.args[-len(example.args):], example.args )) else: example_kwargs = example.kwargs 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. message_on_failure = 'Falsifying example: %s(%s)' % ( test.__name__, arg_string(test, arguments, example_kwargs) ) try: with BuildContext() as b: test_runner( None, lambda data: test(*arguments, **example_kwargs) ) except BaseException: report(message_on_failure) for n in b.notes: report(n) raise if settings.max_examples <= 0: return arguments = tuple(arguments) given_specifier = sd.tuples( sd.just(arguments), sd.fixed_dictionaries(generator_kwargs).map( lambda args: dict(args, **kwargs) ) ) def fail_health_check(message): message += ( '\nSee http://hypothesis.readthedocs.org/en/latest/health' 'checks.html for more information about this.' ) raise FailedHealthCheck(message) search_strategy = given_specifier search_strategy.validate() perform_health_check = settings.perform_health_check perform_health_check &= Settings.default.perform_health_check from hypothesis.internal.conjecture.data import TestData, Status, \ StopTest if perform_health_check: initial_state = getglobalrandomstate() health_check_random = Random(random.getrandbits(128)) # We "pre warm" the health check with one draw to give it some # time to calculate any cached data. This prevents the case # where the first draw of the health check takes ages because # of loading unicode data the first time. data = TestData( max_length=settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(health_check_random, n) ) with Settings(settings, verbosity=Verbosity.quiet): try: test_runner(data, reify_and_execute( search_strategy, lambda *args, **kwargs: None, )) except BaseException: pass count = 0 overruns = 0 filtered_draws = 0 start = time.time() while ( count < 10 and time.time() < start + 1 and filtered_draws < 50 and overruns < 20 ): try: data = TestData( max_length=settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(health_check_random, n) ) with Settings(settings, verbosity=Verbosity.quiet): test_runner(data, reify_and_execute( search_strategy, lambda *args, **kwargs: None, )) count += 1 except UnsatisfiedAssumption: filtered_draws += 1 except StopTest: if data.status == Status.INVALID: filtered_draws += 1 else: assert data.status == Status.OVERRUN overruns += 1 except Exception: report(traceback.format_exc()) if test_runner is default_new_style_executor: fail_health_check( 'An exception occurred during data ' 'generation in initial health check. ' 'This indicates a bug in the strategy. ' 'This could either be a Hypothesis bug or ' "an error in a function yo've passed to " 'it to construct your data.' ) else: fail_health_check( 'An exception occurred during data ' 'generation in initial health check. ' 'This indicates a bug in the strategy. ' 'This could either be a Hypothesis bug or ' 'an error in a function you\'ve passed to ' 'it to construct your data. Additionally, ' 'you have a custom executor, which means ' 'that this could be your executor failing ' 'to handle a function which returns None. ' ) if overruns >= 20 or ( not count and overruns > 0 ): fail_health_check(( 'Examples routinely exceeded the max allowable size. ' '(%d examples overran while generating %d valid ones)' '. Generating examples this large will usually lead to' ' bad results. You should try setting average_size or ' 'max_size parameters on your collections and turning ' 'max_leaves down on recursive() calls.') % ( overruns, count )) if filtered_draws >= 50 or ( not count and filtered_draws > 0 ): fail_health_check(( 'It looks like your strategy is filtering out a lot ' 'of data. Health check found %d filtered examples but ' 'only %d good ones. This will make your tests much ' 'slower, and also will probably distort the data ' 'generation quite a lot. You should adapt your ' 'strategy to filter less. This can also be caused by ' 'a low max_leaves parameter in recursive() calls') % ( filtered_draws, count )) runtime = time.time() - start if runtime > 1.0 or count < 10: fail_health_check(( 'Data generation is extremely slow: Only produced ' '%d valid examples in %.2f seconds (%d invalid ones ' 'and %d exceeded maximum size). Try decreasing ' "size of the data you're generating (with e.g." 'average_size or max_leaves parameters).' ) % (count, runtime, filtered_draws, overruns)) if getglobalrandomstate() != initial_state: fail_health_check( 'Data generation depends on global random module. ' 'This makes results impossible to replay, which ' 'prevents Hypothesis from working correctly. ' 'If you want to use methods from random, use ' 'randoms() from hypothesis.strategies to get an ' 'instance of Random you can use. Alternatively, you ' 'can use the random_module() strategy to explicitly ' 'seed the random module.' ) last_exception = [None] repr_for_last_exception = [None] performed_random_check = [False] def evaluate_test_data(data): if perform_health_check and not performed_random_check[0]: initial_state = getglobalrandomstate() performed_random_check[0] = True else: initial_state = None try: result = test_runner(data, reify_and_execute( search_strategy, test, )) if result is not None and settings.perform_health_check: raise FailedHealthCheck(( 'Tests run under @given should return None, but ' '%s returned %r instead.' ) % (test.__name__, result), settings) return False except UnsatisfiedAssumption: data.mark_invalid() except ( HypothesisDeprecationWarning, FailedHealthCheck, StopTest, ): raise except Exception: last_exception[0] = traceback.format_exc() verbose_report(last_exception[0]) data.mark_interesting() finally: if ( initial_state is not None and getglobalrandomstate() != initial_state ): fail_health_check( 'Your test used the global random module. ' 'This is unlikely to work correctly. You should ' 'consider using the randoms() strategy from ' 'hypothesis.strategies instead. Alternatively, ' 'you can use the random_module() strategy to ' 'explicitly seed the random module.') from hypothesis.internal.conjecture.engine import TestRunner falsifying_example = None database_key = str_to_bytes(fully_qualified_name(test)) start_time = time.time() runner = TestRunner( evaluate_test_data, settings=settings, random=random, database_key=database_key, ) runner.run() run_time = time.time() - start_time timed_out = ( settings.timeout > 0 and run_time >= settings.timeout ) if runner.last_data.status == Status.INTERESTING: falsifying_example = runner.last_data.buffer if settings.database is not None: settings.database.save( database_key, falsifying_example ) else: if runner.valid_examples < min( settings.min_satisfying_examples, settings.max_examples, ): if timed_out: raise Timeout(( 'Ran out of time before finding a satisfying ' 'example for ' '%s. Only found %d examples in ' + '%.2fs.' ) % ( get_pretty_function_description(test), runner.valid_examples, run_time )) else: raise Unsatisfiable(( 'Unable to satisfy assumptions of hypothesis ' '%s. Only %d examples considered ' 'satisfied assumptions' ) % ( get_pretty_function_description(test), runner.valid_examples,)) return assert last_exception[0] is not None try: with settings: test_runner( TestData.for_buffer(falsifying_example), reify_and_execute( search_strategy, test, print_example=True, is_final=True )) except (UnsatisfiedAssumption, StopTest): report(traceback.format_exc()) raise Flaky( 'Unreliable assumption: An example which satisfied ' 'assumptions on the first run now fails it.' ) report( 'Failed to reproduce exception. Expected: \n' + last_exception[0], ) filter_message = ( 'Unreliable test data: Failed to reproduce a failure ' 'and then when it came to recreating the example in ' 'order to print the test data with a flaky result ' 'the example was filtered out (by e.g. a ' 'call to filter in your strategy) when we didn\'t ' 'expect it to be.' ) try: test_runner( TestData.for_buffer(falsifying_example), reify_and_execute( search_strategy, test_is_flaky(test, repr_for_last_exception[0]), print_example=True, is_final=True )) except (UnsatisfiedAssumption, StopTest): raise Flaky(filter_message)
def bc(): return BuildContext(TD.for_buffer(b''))
def find(specifier, condition, settings=None, random=None, database_key=None): settings = settings or Settings( max_examples=2000, min_satisfying_examples=0, max_shrinks=2000, ) 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__)) search = specifier random = random or new_random() successful_examples = [0] last_data = [None] def template_condition(data): with BuildContext(): try: data.is_find = True 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(lambda: u'Trying example %s' % (nicerepr(result), )) elif success: if successful_examples[0] == 1: report(lambda: u'Found satisfying example %s' % (nicerepr(result), )) else: report(lambda: u'Shrunk example to %s' % (nicerepr(result), )) last_data[0] = data if success and not data.frozen: data.mark_interesting() from hypothesis.internal.conjecture.engine import TestRunner from hypothesis.internal.conjecture.data import TestData, Status start = time.time() runner = TestRunner( template_condition, settings=settings, random=random, database_key=database_key, ) runner.run() run_time = time.time() - start if runner.last_data.status == Status.INTERESTING: with BuildContext(): return TestData.for_buffer(runner.last_data.buffer).draw(search) if runner.valid_examples <= settings.min_satisfying_examples: if settings.timeout > 0 and run_time > settings.timeout: raise Timeout( ('Ran out of time before finding enough valid examples for ' '%s. Only %d valid examples found in %.2f seconds.') % (get_pretty_function_description(condition), runner.valid_examples, run_time)) else: raise Unsatisfiable( ('Unable to satisfy assumptions of ' '%s. Only %d examples considered satisfied assumptions') % ( get_pretty_function_description(condition), runner.valid_examples, )) raise NoSuchExample(get_pretty_function_description(condition))
def test_can_mark_interesting(): x = TestData.for_buffer(bytes()) with pytest.raises(StopTest): x.mark_interesting() assert x.frozen assert x.status == Status.INTERESTING
def test_closes_interval_on_error_in_strategy(): x = TestData.for_buffer(b'hi') with pytest.raises(ValueError): x.draw(BoomStrategy()) x.freeze() assert len(x.intervals) == 1
class TestRunner(object): def __init__( self, test_function, settings=None, random=None, database_key=None, ): self._test_function = test_function self.settings = settings or Settings() self.last_data = None self.changed = 0 self.shrinks = 0 self.examples_considered = 0 self.iterations = 0 self.valid_examples = 0 self.start_time = time.time() self.random = random or Random(getrandbits(128)) self.database_key = database_key self.seen = set() def new_buffer(self): self.last_data = TestData(max_length=self.settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(self.random, n)) self.test_function(self.last_data) self.last_data.freeze() self.note_for_corpus(self.last_data) def test_function(self, data): self.iterations += 1 try: self._test_function(data) data.freeze() except StopTest as e: if e.testcounter != data.testcounter: self.save_buffer(data.buffer) raise e except: self.save_buffer(data.buffer) raise if (data.status == Status.INTERESTING and (self.last_data is None or data.buffer != self.last_data.buffer)): self.debug_data(data) if data.status >= Status.VALID: self.valid_examples += 1 def consider_new_test_data(self, data): # Transition rules: # 1. Transition cannot decrease the status # 2. Any transition which increases the status is valid # 3. If the previous status was interesting, only shrinking # transitions are allowed. self.seen.add(hbytes(data.buffer)) if data.buffer == self.last_data.buffer: return False if self.last_data.status < data.status: return True if self.last_data.status > data.status: return False if data.status == Status.INVALID: return data.index >= self.last_data.index if data.status == Status.OVERRUN: return data.overdraw <= self.last_data.overdraw if data.status == Status.INTERESTING: assert len(data.buffer) <= len(self.last_data.buffer) if len(data.buffer) == len(self.last_data.buffer): assert data.buffer < self.last_data.buffer return True return True def save_buffer(self, buffer): if (self.settings.database is not None and self.database_key is not None and Phase.reuse in self.settings.phases): self.settings.database.save(self.database_key, hbytes(buffer)) def note_for_corpus(self, data): if data.status == Status.INTERESTING: self.save_buffer(data.buffer) def debug(self, message): with self.settings: debug_report(message) def debug_data(self, data): self.debug(u'%d bytes %s -> %s, %s' % ( data.index, unicode_safe_repr(list(data.buffer[:data.index])), unicode_safe_repr(data.status), data.output, )) def incorporate_new_buffer(self, buffer): if buffer in self.seen: return False assert self.last_data.status == Status.INTERESTING if (self.settings.timeout > 0 and time.time() >= self.start_time + self.settings.timeout): raise RunIsComplete() self.examples_considered += 1 buffer = buffer[:self.last_data.index] if sort_key(buffer) >= sort_key(self.last_data.buffer): return False assert sort_key(buffer) <= sort_key(self.last_data.buffer) data = TestData.for_buffer(buffer) self.test_function(data) data.freeze() self.note_for_corpus(data) if self.consider_new_test_data(data): self.shrinks += 1 self.last_data = data if self.shrinks >= self.settings.max_shrinks: raise RunIsComplete() self.last_data = data self.changed += 1 return True return False def run(self): with self.settings: try: self._run() except RunIsComplete: pass self.debug( u'Run complete after %d examples (%d valid) and %d shrinks' % ( self.iterations, self.valid_examples, self.shrinks, )) def _new_mutator(self): def draw_new(data, n, distribution): return distribution(self.random, n) def draw_existing(data, n, distribution): return self.last_data.buffer[data.index:data.index + n] def draw_smaller(data, n, distribution): existing = self.last_data.buffer[data.index:data.index + n] r = distribution(self.random, n) if r <= existing: return r return _draw_predecessor(self.random, existing) def draw_larger(data, n, distribution): existing = self.last_data.buffer[data.index:data.index + n] r = distribution(self.random, n) if r >= existing: return r return _draw_successor(self.random, existing) def reuse_existing(data, n, distribution): choices = data.block_starts.get(n, []) or \ self.last_data.block_starts.get(n, []) if choices: i = self.random.choice(choices) return self.last_data.buffer[i:i + n] else: return distribution(self.random, n) def flip_bit(data, n, distribution): buf = bytearray(self.last_data.buffer[data.index:data.index + n]) i = self.random.randint(0, n - 1) k = self.random.randint(0, 7) buf[i] ^= (1 << k) return hbytes(buf) def draw_zero(data, n, distribution): return b'\0' * n def draw_constant(data, n, distribution): return bytes_from_list([self.random.randint(0, 255)] * n) options = [ draw_new, reuse_existing, reuse_existing, draw_existing, draw_smaller, draw_larger, flip_bit, draw_zero, draw_constant, ] bits = [self.random.choice(options) for _ in hrange(3)] def draw_mutated(data, n, distribution): if (data.index + n > len(self.last_data.buffer)): return distribution(self.random, n) return self.random.choice(bits)(data, n, distribution) return draw_mutated def _run(self): self.last_data = None mutations = 0 start_time = time.time() if (self.settings.database is not None and self.database_key is not None): corpus = sorted(self.settings.database.fetch(self.database_key), key=lambda d: (len(d), d)) for existing in corpus: if self.valid_examples >= self.settings.max_examples: return if self.iterations >= max(self.settings.max_iterations, self.settings.max_examples): return data = TestData.for_buffer(existing) self.test_function(data) data.freeze() self.last_data = data if data.status < Status.VALID: self.settings.database.delete(self.database_key, existing) elif data.status == Status.VALID: # Incremental garbage collection! we store a lot of # examples in the DB as we shrink: Those that stay # interesting get kept, those that become invalid get # dropped, but those that are merely valid gradually go # away over time. if self.random.randint(0, 2) == 0: self.settings.database.delete(self.database_key, existing) else: assert data.status == Status.INTERESTING self.last_data = data break if Phase.generate in self.settings.phases: if (self.last_data is None or self.last_data.status < Status.INTERESTING): self.new_buffer() mutator = self._new_mutator() while self.last_data.status != Status.INTERESTING: if self.valid_examples >= self.settings.max_examples: return if self.iterations >= max(self.settings.max_iterations, self.settings.max_examples): return if (self.settings.timeout > 0 and time.time() >= start_time + self.settings.timeout): return if mutations >= self.settings.max_mutations: mutations = 0 self.new_buffer() mutator = self._new_mutator() else: data = TestData(draw_bytes=mutator, max_length=self.settings.buffer_size) self.test_function(data) data.freeze() self.note_for_corpus(data) prev_data = self.last_data if self.consider_new_test_data(data): self.last_data = data if data.status > prev_data.status: mutations = 0 else: mutator = self._new_mutator() mutations += 1 data = self.last_data if data is None: return assert isinstance(data.output, text_type) if self.settings.max_shrinks <= 0: return if Phase.shrink not in self.settings.phases: return if not self.last_data.buffer: return data = TestData.for_buffer(self.last_data.buffer) self.test_function(data) if data.status != Status.INTERESTING: return change_counter = -1 while self.changed > change_counter: change_counter = self.changed failed_deletes = 0 while self.last_data.intervals and failed_deletes < 10: if self.random.randint(0, 1): u, v = self.random.choice(self.last_data.intervals) else: n = len(self.last_data.buffer) - 1 u, v = sorted( (self.random.choice(self.last_data.intervals))) if ( v < len(self.last_data.buffer) ) and self.incorporate_new_buffer(self.last_data.buffer[:u] + self.last_data.buffer[v:]): failed_deletes = 0 else: failed_deletes += 1 i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] if not self.incorporate_new_buffer(self.last_data.buffer[:u] + self.last_data.buffer[v:]): i += 1 i = 0 while i + 1 < len(self.last_data.buffer): if not self.incorporate_new_buffer(self.last_data.buffer[:i] + self.last_data.buffer[i + 1:]): i += 1 i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] buf = self.last_data.buffer block = buf[u:v] n = v - u all_blocks = sorted( set([bytes(n)] + [buf[a:a + n] for a in self.last_data.block_starts[n]])) better_blocks = all_blocks[:all_blocks.index(block)] for b in better_blocks: if self.incorporate_new_buffer(buf[:u] + b + buf[v:]): break i += 1 block_counter = -1 while block_counter < self.changed: block_counter = self.changed blocks = [ k for k, count in Counter( self.last_data.buffer[u:v] for u, v in self.last_data.blocks).items() if count > 1 ] for block in blocks: parts = [ self.last_data.buffer[r:s] for r, s in self.last_data.blocks ] def replace(b): return b''.join( bytes(b if c == block else c) for c in parts) minimize(block, lambda b: self.incorporate_new_buffer(replace(b)), self.random) i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] minimize( self.last_data.buffer[u:v], lambda b: self.incorporate_new_buffer( self.last_data.buffer[:u] + b + self.last_data.buffer[ v:], ), self.random) i += 1 i = 0 alternatives = None while i < len(self.last_data.intervals): if alternatives is None: alternatives = sorted(set( self.last_data.buffer[u:v] for u, v in self.last_data.intervals), key=len) u, v = self.last_data.intervals[i] for a in alternatives: buf = self.last_data.buffer if (len(a) < v - u or (len(a) == (v - u) and a < buf[u:v])): if self.incorporate_new_buffer(buf[:u] + a + buf[v:]): alternatives = None break i += 1
def test_does_not_double_freeze_in_interval_close(): x = TestData.for_buffer(b'hi') with pytest.raises(StopTest): x.draw(BigStrategy()) assert x.frozen assert len(x.intervals) == 0
def test_fixed_size_bytes_just_draw_bytes(): from hypothesis.internal.conjecture.data import TestData x = TestData.for_buffer(b'foo') assert x.draw(binary(min_size=3, max_size=3)) == b'foo'
def test_cannot_draw_after_freeze(): x = TestData.for_buffer(b'hi') x.draw_bytes(1) x.freeze() with pytest.raises(Frozen): x.draw_bytes(1)
def test_can_draw_zero_bytes(): x = TestData.for_buffer(b'') for _ in range(10): assert x.draw_bytes(0) == b''
def test_notes_repr(): x = TestData.for_buffer(b'') x.note(b'hi') assert repr(b'hi') in x.output
def test_can_mark_invalid(): x = TestData.for_buffer(bytes()) with pytest.raises(StopTest): x.mark_invalid() assert x.frozen assert x.status == Status.INVALID
def _run(self): self.last_data = None mutations = 0 start_time = time.time() if ( self.settings.database is not None and self.database_key is not None ): corpus = sorted( self.settings.database.fetch(self.database_key), key=lambda d: (len(d), d) ) for existing in corpus: if self.valid_examples >= self.settings.max_examples: return if self.iterations >= max( self.settings.max_iterations, self.settings.max_examples ): return data = TestData.for_buffer(existing) self.test_function(data) data.freeze() self.last_data = data if data.status < Status.VALID: self.settings.database.delete( self.database_key, existing) elif data.status == Status.VALID: # Incremental garbage collection! we store a lot of # examples in the DB as we shrink: Those that stay # interesting get kept, those that become invalid get # dropped, but those that are merely valid gradually go # away over time. if self.random.randint(0, 2) == 0: self.settings.database.delete( self.database_key, existing) else: assert data.status == Status.INTERESTING self.last_data = data break if Phase.generate in self.settings.phases: if ( self.last_data is None or self.last_data.status < Status.INTERESTING ): self.new_buffer() mutator = self._new_mutator() while self.last_data.status != Status.INTERESTING: if self.valid_examples >= self.settings.max_examples: return if self.iterations >= max( self.settings.max_iterations, self.settings.max_examples ): return if ( self.settings.timeout > 0 and time.time() >= start_time + self.settings.timeout ): return if mutations >= self.settings.max_mutations: mutations = 0 self.new_buffer() mutator = self._new_mutator() else: data = TestData( draw_bytes=mutator, max_length=self.settings.buffer_size ) self.test_function(data) data.freeze() self.note_for_corpus(data) prev_data = self.last_data if self.consider_new_test_data(data): self.last_data = data if data.status > prev_data.status: mutations = 0 else: mutator = self._new_mutator() mutations += 1 data = self.last_data if data is None: return assert isinstance(data.output, text_type) if self.settings.max_shrinks <= 0: return if Phase.shrink not in self.settings.phases: return if not self.last_data.buffer: return data = TestData.for_buffer(self.last_data.buffer) self.test_function(data) if data.status != Status.INTERESTING: return change_counter = -1 while self.changed > change_counter: change_counter = self.changed failed_deletes = 0 while self.last_data.intervals and failed_deletes < 10: if self.random.randint(0, 1): u, v = self.random.choice(self.last_data.intervals) else: n = len(self.last_data.buffer) - 1 u, v = sorted(( self.random.choice(self.last_data.intervals) )) if ( v < len(self.last_data.buffer) ) and self.incorporate_new_buffer( self.last_data.buffer[:u] + self.last_data.buffer[v:] ): failed_deletes = 0 else: failed_deletes += 1 i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] if not self.incorporate_new_buffer( self.last_data.buffer[:u] + self.last_data.buffer[v:] ): i += 1 i = 0 while i + 1 < len(self.last_data.buffer): if not self.incorporate_new_buffer( self.last_data.buffer[:i] + self.last_data.buffer[i + 1:] ): i += 1 i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] buf = self.last_data.buffer block = buf[u:v] n = v - u all_blocks = sorted(set([bytes(n)] + [ buf[a:a + n] for a in self.last_data.block_starts[n] ])) better_blocks = all_blocks[:all_blocks.index(block)] for b in better_blocks: if self.incorporate_new_buffer( buf[:u] + b + buf[v:] ): break i += 1 block_counter = -1 while block_counter < self.changed: block_counter = self.changed blocks = [ k for k, count in Counter( self.last_data.buffer[u:v] for u, v in self.last_data.blocks).items() if count > 1 ] for block in blocks: parts = [ self.last_data.buffer[r:s] for r, s in self.last_data.blocks ] def replace(b): return b''.join( bytes(b if c == block else c) for c in parts ) minimize( block, lambda b: self.incorporate_new_buffer(replace(b)), self.random ) i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] minimize( self.last_data.buffer[u:v], lambda b: self.incorporate_new_buffer( self.last_data.buffer[:u] + b + self.last_data.buffer[v:], ), self.random ) i += 1 i = 0 alternatives = None while i < len(self.last_data.intervals): if alternatives is None: alternatives = sorted(set( self.last_data.buffer[u:v] for u, v in self.last_data.intervals), key=len) u, v = self.last_data.intervals[i] for a in alternatives: buf = self.last_data.buffer if ( len(a) < v - u or (len(a) == (v - u) and a < buf[u:v]) ): if self.incorporate_new_buffer(buf[:u] + a + buf[v:]): alternatives = None break i += 1
class TestRunner(object): def __init__( self, test_function, settings=None, random=None, database_key=None, ): self._test_function = test_function self.settings = settings or Settings() self.last_data = None self.changed = 0 self.shrinks = 0 self.examples_considered = 0 self.iterations = 0 self.valid_examples = 0 self.start_time = time.time() self.random = random or Random(getrandbits(128)) self.database_key = database_key self.seen = set() def new_buffer(self): self.last_data = TestData( max_length=self.settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(self.random, n) ) self.test_function(self.last_data) self.last_data.freeze() self.note_for_corpus(self.last_data) def test_function(self, data): self.iterations += 1 try: self._test_function(data) data.freeze() except StopTest as e: if e.testcounter != data.testcounter: self.save_buffer(data.buffer) raise e except: self.save_buffer(data.buffer) raise if ( data.status == Status.INTERESTING and ( self.last_data is None or data.buffer != self.last_data.buffer ) ): self.debug_data(data) if data.status >= Status.VALID: self.valid_examples += 1 def consider_new_test_data(self, data): # Transition rules: # 1. Transition cannot decrease the status # 2. Any transition which increases the status is valid # 3. If the previous status was interesting, only shrinking # transitions are allowed. key = hbytes(data.buffer) if key in self.seen: return False self.seen.add(key) if data.buffer == self.last_data.buffer: return False if self.last_data.status < data.status: return True if self.last_data.status > data.status: return False if data.status == Status.INVALID: return data.index >= self.last_data.index if data.status == Status.OVERRUN: return data.overdraw <= self.last_data.overdraw if data.status == Status.INTERESTING: assert len(data.buffer) <= len(self.last_data.buffer) if len(data.buffer) == len(self.last_data.buffer): assert data.buffer < self.last_data.buffer return True return True def save_buffer(self, buffer): if ( self.settings.database is not None and self.database_key is not None and Phase.reuse in self.settings.phases ): self.settings.database.save( self.database_key, hbytes(buffer) ) def note_for_corpus(self, data): if data.status == Status.INTERESTING: self.save_buffer(data.buffer) def debug(self, message): with self.settings: debug_report(message) def debug_data(self, data): self.debug(u'%d bytes %s -> %s, %s' % ( data.index, unicode_safe_repr(list(data.buffer[:data.index])), unicode_safe_repr(data.status), data.output, )) def incorporate_new_buffer(self, buffer): if buffer in self.seen: return False assert self.last_data.status == Status.INTERESTING if ( self.settings.timeout > 0 and time.time() >= self.start_time + self.settings.timeout ): raise RunIsComplete() self.examples_considered += 1 buffer = buffer[:self.last_data.index] if sort_key(buffer) >= sort_key(self.last_data.buffer): return False assert sort_key(buffer) <= sort_key(self.last_data.buffer) data = TestData.for_buffer(buffer) self.test_function(data) data.freeze() self.note_for_corpus(data) if self.consider_new_test_data(data): self.shrinks += 1 self.last_data = data if self.shrinks >= self.settings.max_shrinks: raise RunIsComplete() self.last_data = data self.changed += 1 return True return False def run(self): with self.settings: try: self._run() except RunIsComplete: pass self.debug( u'Run complete after %d examples (%d valid) and %d shrinks' % ( self.iterations, self.valid_examples, self.shrinks, )) def _new_mutator(self): def draw_new(data, n, distribution): return distribution(self.random, n) def draw_existing(data, n, distribution): return self.last_data.buffer[data.index:data.index + n] def draw_smaller(data, n, distribution): existing = self.last_data.buffer[data.index:data.index + n] r = distribution(self.random, n) if r <= existing: return r return _draw_predecessor(self.random, existing) def draw_larger(data, n, distribution): existing = self.last_data.buffer[data.index:data.index + n] r = distribution(self.random, n) if r >= existing: return r return _draw_successor(self.random, existing) def reuse_existing(data, n, distribution): choices = data.block_starts.get(n, []) or \ self.last_data.block_starts.get(n, []) if choices: i = self.random.choice(choices) return self.last_data.buffer[i:i + n] else: return distribution(self.random, n) def flip_bit(data, n, distribution): buf = bytearray( self.last_data.buffer[data.index:data.index + n]) i = self.random.randint(0, n - 1) k = self.random.randint(0, 7) buf[i] ^= (1 << k) return hbytes(buf) def draw_zero(data, n, distribution): return b'\0' * n def draw_constant(data, n, distribution): return bytes_from_list([ self.random.randint(0, 255) ] * n) options = [ draw_new, reuse_existing, reuse_existing, draw_existing, draw_smaller, draw_larger, flip_bit, draw_zero, draw_constant, ] bits = [ self.random.choice(options) for _ in hrange(3) ] def draw_mutated(data, n, distribution): if ( data.index + n > len(self.last_data.buffer) ): return distribution(self.random, n) return self.random.choice(bits)(data, n, distribution) return draw_mutated def _run(self): self.last_data = None mutations = 0 start_time = time.time() if ( self.settings.database is not None and self.database_key is not None ): corpus = sorted( self.settings.database.fetch(self.database_key), key=lambda d: (len(d), d) ) for existing in corpus: if self.valid_examples >= self.settings.max_examples: return if self.iterations >= max( self.settings.max_iterations, self.settings.max_examples ): return data = TestData.for_buffer(existing) self.test_function(data) data.freeze() self.last_data = data if data.status < Status.VALID: self.settings.database.delete( self.database_key, existing) elif data.status == Status.VALID: # Incremental garbage collection! we store a lot of # examples in the DB as we shrink: Those that stay # interesting get kept, those that become invalid get # dropped, but those that are merely valid gradually go # away over time. if self.random.randint(0, 2) == 0: self.settings.database.delete( self.database_key, existing) else: assert data.status == Status.INTERESTING self.last_data = data break if Phase.generate in self.settings.phases: if ( self.last_data is None or self.last_data.status < Status.INTERESTING ): self.new_buffer() mutator = self._new_mutator() while self.last_data.status != Status.INTERESTING: if self.valid_examples >= self.settings.max_examples: return if self.iterations >= max( self.settings.max_iterations, self.settings.max_examples ): return if ( self.settings.timeout > 0 and time.time() >= start_time + self.settings.timeout ): return if mutations >= self.settings.max_mutations: mutations = 0 self.new_buffer() mutator = self._new_mutator() else: data = TestData( draw_bytes=mutator, max_length=self.settings.buffer_size ) self.test_function(data) data.freeze() self.note_for_corpus(data) prev_data = self.last_data if self.consider_new_test_data(data): self.last_data = data if data.status > prev_data.status: mutations = 0 else: mutator = self._new_mutator() mutations += 1 data = self.last_data if data is None: return assert isinstance(data.output, text_type) if self.settings.max_shrinks <= 0: return if Phase.shrink not in self.settings.phases: return if not self.last_data.buffer: return data = TestData.for_buffer(self.last_data.buffer) self.test_function(data) if data.status != Status.INTERESTING: return change_counter = -1 while self.changed > change_counter: change_counter = self.changed failed_deletes = 0 while self.last_data.intervals and failed_deletes < 10: if self.random.randint(0, 1): u, v = self.random.choice(self.last_data.intervals) else: n = len(self.last_data.buffer) - 1 u, v = sorted(( self.random.choice(self.last_data.intervals) )) if ( v < len(self.last_data.buffer) ) and self.incorporate_new_buffer( self.last_data.buffer[:u] + self.last_data.buffer[v:] ): failed_deletes = 0 else: failed_deletes += 1 i = 0 while i < len(self.last_data.intervals): u, v = self.last_data.intervals[i] if not self.incorporate_new_buffer( self.last_data.buffer[:u] + self.last_data.buffer[v:] ): i += 1 i = 0 while i + 1 < len(self.last_data.buffer): if not self.incorporate_new_buffer( self.last_data.buffer[:i] + self.last_data.buffer[i + 1:] ): i += 1 i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] buf = self.last_data.buffer block = buf[u:v] n = v - u all_blocks = sorted(set([bytes(n)] + [ buf[a:a + n] for a in self.last_data.block_starts[n] ])) better_blocks = all_blocks[:all_blocks.index(block)] for b in better_blocks: if self.incorporate_new_buffer( buf[:u] + b + buf[v:] ): break i += 1 block_counter = -1 while block_counter < self.changed: block_counter = self.changed blocks = [ k for k, count in Counter( self.last_data.buffer[u:v] for u, v in self.last_data.blocks).items() if count > 1 ] for block in blocks: parts = [ self.last_data.buffer[r:s] for r, s in self.last_data.blocks ] def replace(b): return b''.join( bytes(b if c == block else c) for c in parts ) minimize( block, lambda b: self.incorporate_new_buffer(replace(b)), self.random ) i = 0 while i < len(self.last_data.blocks): u, v = self.last_data.blocks[i] minimize( self.last_data.buffer[u:v], lambda b: self.incorporate_new_buffer( self.last_data.buffer[:u] + b + self.last_data.buffer[v:], ), self.random ) i += 1 i = 0 alternatives = None while i < len(self.last_data.intervals): if alternatives is None: alternatives = sorted(set( self.last_data.buffer[u:v] for u, v in self.last_data.intervals), key=len) u, v = self.last_data.intervals[i] for a in alternatives: buf = self.last_data.buffer if ( len(a) < v - u or (len(a) == (v - u) and a < buf[u:v]) ): if self.incorporate_new_buffer(buf[:u] + a + buf[v:]): alternatives = None break i += 1
def test_does_not_draw_data_for_empty_range(): assert integer_range(TestData.for_buffer(b''), 1, 1) == 1
def wrapped_test(*arguments, **kwargs): settings = wrapped_test._hypothesis_internal_use_settings if wrapped_test._hypothesis_internal_use_seed is not None: random = Random(wrapped_test._hypothesis_internal_use_seed) elif settings.derandomize: random = Random(function_digest(test)) else: random = new_random() import hypothesis.strategies as sd selfy = None arguments, kwargs = convert_positional_arguments(wrapped_test, arguments, kwargs) # If the test function is a method of some kind, the bound object # will be the first named argument if there are any, otherwise the # first vararg (if any). if argspec.args: selfy = kwargs.get(argspec.args[0]) elif arguments: selfy = arguments[0] test_runner = new_style_executor(selfy) for example in reversed(getattr(wrapped_test, "hypothesis_explicit_examples", ())): 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 = dict(zip(original_argspec.args[-len(example.args) :], example.args)) else: example_kwargs = 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. message_on_failure = "Falsifying example: %s(%s)" % ( test.__name__, arg_string(test, arguments, example_kwargs), ) try: with BuildContext() as b: test_runner(None, lambda data: test(*arguments, **example_kwargs)) except BaseException: report(message_on_failure) for n in b.notes: report(n) raise if settings.max_examples <= 0: return arguments = tuple(arguments) given_specifier = sd.tuples( sd.just(arguments), sd.fixed_dictionaries(generator_kwargs).map(lambda args: dict(args, **kwargs)) ) def fail_health_check(message, label): if label in settings.suppress_health_check: return message += ( "\nSee https://hypothesis.readthedocs.io/en/latest/health" "checks.html for more information about this. " ) message += ( "If you want to disable just this health check, add %s " "to the suppress_health_check settings for this test." ) % (label,) raise FailedHealthCheck(message) search_strategy = given_specifier if selfy is not None: search_strategy = WithRunner(search_strategy, selfy) search_strategy.validate() perform_health_check = settings.perform_health_check perform_health_check &= Settings.default.perform_health_check from hypothesis.internal.conjecture.data import TestData, Status, StopTest if not (Phase.reuse in settings.phases or Phase.generate in settings.phases): return if perform_health_check: health_check_random = Random(random.getrandbits(128)) # We "pre warm" the health check with one draw to give it some # time to calculate any cached data. This prevents the case # where the first draw of the health check takes ages because # of loading unicode data the first time. data = TestData( max_length=settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(health_check_random, n), ) with Settings(settings, verbosity=Verbosity.quiet): try: test_runner(data, reify_and_execute(search_strategy, lambda *args, **kwargs: None)) except BaseException: pass count = 0 overruns = 0 filtered_draws = 0 start = time.time() while count < 10 and time.time() < start + 1 and filtered_draws < 50 and overruns < 20: try: data = TestData( max_length=settings.buffer_size, draw_bytes=lambda data, n, distribution: distribution(health_check_random, n), ) with Settings(settings, verbosity=Verbosity.quiet): test_runner(data, reify_and_execute(search_strategy, lambda *args, **kwargs: None)) count += 1 except UnsatisfiedAssumption: filtered_draws += 1 except StopTest: if data.status == Status.INVALID: filtered_draws += 1 else: assert data.status == Status.OVERRUN overruns += 1 except InvalidArgument: raise except Exception: if HealthCheck.exception_in_generation in settings.suppress_health_check: raise report(traceback.format_exc()) if test_runner is default_new_style_executor: fail_health_check( "An exception occurred during data " "generation in initial health check. " "This indicates a bug in the strategy. " "This could either be a Hypothesis bug or " "an error in a function you've passed to " "it to construct your data.", HealthCheck.exception_in_generation, ) else: fail_health_check( "An exception occurred during data " "generation in initial health check. " "This indicates a bug in the strategy. " "This could either be a Hypothesis bug or " "an error in a function you've passed to " "it to construct your data. Additionally, " "you have a custom executor, which means " "that this could be your executor failing " "to handle a function which returns None. ", HealthCheck.exception_in_generation, ) if overruns >= 20 or (not count and overruns > 0): fail_health_check( ( "Examples routinely exceeded the max allowable size. " "(%d examples overran while generating %d valid ones)" ". Generating examples this large will usually lead to" " bad results. You should try setting average_size or " "max_size parameters on your collections and turning " "max_leaves down on recursive() calls." ) % (overruns, count), HealthCheck.data_too_large, ) if filtered_draws >= 50 or (not count and filtered_draws > 0): fail_health_check( ( "It looks like your strategy is filtering out a lot " "of data. Health check found %d filtered examples but " "only %d good ones. This will make your tests much " "slower, and also will probably distort the data " "generation quite a lot. You should adapt your " "strategy to filter less. This can also be caused by " "a low max_leaves parameter in recursive() calls" ) % (filtered_draws, count), HealthCheck.filter_too_much, ) runtime = time.time() - start if runtime > 1.0 or count < 10: fail_health_check( ( "Data generation is extremely slow: Only produced " "%d valid examples in %.2f seconds (%d invalid ones " "and %d exceeded maximum size). Try decreasing " "size of the data you're generating (with e.g." "average_size or max_leaves parameters)." ) % (count, runtime, filtered_draws, overruns), HealthCheck.too_slow, ) last_exception = [None] repr_for_last_exception = [None] def evaluate_test_data(data): try: result = test_runner(data, reify_and_execute(search_strategy, test)) if result is not None and settings.perform_health_check: fail_health_check( ("Tests run under @given should return None, but " "%s returned %r instead.") % (test.__name__, result), HealthCheck.return_value, ) return False except UnsatisfiedAssumption: data.mark_invalid() except (HypothesisDeprecationWarning, FailedHealthCheck, StopTest): raise except Exception: last_exception[0] = traceback.format_exc() verbose_report(last_exception[0]) data.mark_interesting() from hypothesis.internal.conjecture.engine import TestRunner falsifying_example = None database_key = str_to_bytes(fully_qualified_name(test)) start_time = time.time() runner = TestRunner(evaluate_test_data, settings=settings, random=random, database_key=database_key) runner.run() run_time = time.time() - start_time timed_out = settings.timeout > 0 and run_time >= settings.timeout if runner.last_data is None: return if runner.last_data.status == Status.INTERESTING: falsifying_example = runner.last_data.buffer if settings.database is not None: settings.database.save(database_key, falsifying_example) else: if runner.valid_examples < min(settings.min_satisfying_examples, settings.max_examples): if timed_out: raise Timeout( ( "Ran out of time before finding a satisfying " "example for " "%s. Only found %d examples in " + "%.2fs." ) % (get_pretty_function_description(test), runner.valid_examples, run_time) ) else: raise Unsatisfiable( ( "Unable to satisfy assumptions of hypothesis " "%s. Only %d examples considered " "satisfied assumptions" ) % (get_pretty_function_description(test), runner.valid_examples) ) return assert last_exception[0] is not None try: with settings: test_runner( TestData.for_buffer(falsifying_example), reify_and_execute(search_strategy, test, print_example=True, is_final=True), ) except (UnsatisfiedAssumption, StopTest): report(traceback.format_exc()) raise Flaky( "Unreliable assumption: An example which satisfied " "assumptions on the first run now fails it." ) report("Failed to reproduce exception. Expected: \n" + last_exception[0]) filter_message = ( "Unreliable test data: Failed to reproduce a failure " "and then when it came to recreating the example in " "order to print the test data with a flaky result " "the example was filtered out (by e.g. a " "call to filter in your strategy) when we didn't " "expect it to be." ) try: test_runner( TestData.for_buffer(falsifying_example), reify_and_execute( search_strategy, test_is_flaky(test, repr_for_last_exception[0]), print_example=True, is_final=True, ), ) except (UnsatisfiedAssumption, StopTest): raise Flaky(filter_message)
def find(specifier, condition, settings=None, random=None, database_key=None): settings = settings or Settings(max_examples=2000, min_satisfying_examples=0, max_shrinks=2000) 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__)) search = specifier random = random or new_random() successful_examples = [0] last_data = [None] def template_condition(data): with BuildContext(): try: data.is_find = True 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(lambda: u"Trying example %s" % (nicerepr(result),)) elif success: if successful_examples[0] == 1: report(lambda: u"Found satisfying example %s" % (nicerepr(result),)) else: report(lambda: u"Shrunk example to %s" % (nicerepr(result),)) last_data[0] = data if success and not data.frozen: data.mark_interesting() from hypothesis.internal.conjecture.engine import TestRunner from hypothesis.internal.conjecture.data import TestData, Status start = time.time() runner = TestRunner(template_condition, settings=settings, random=random, database_key=database_key) runner.run() run_time = time.time() - start if runner.last_data.status == Status.INTERESTING: with BuildContext(): return TestData.for_buffer(runner.last_data.buffer).draw(search) if runner.valid_examples <= settings.min_satisfying_examples: if settings.timeout > 0 and run_time > settings.timeout: raise Timeout( ( "Ran out of time before finding enough valid examples for " "%s. Only %d valid examples found in %.2f seconds." ) % (get_pretty_function_description(condition), runner.valid_examples, run_time) ) else: raise Unsatisfiable( ("Unable to satisfy assumptions of " "%s. Only %d examples considered satisfied assumptions") % (get_pretty_function_description(condition), runner.valid_examples) ) raise NoSuchExample(get_pretty_function_description(condition))
def test_does_not_draw_data_for_empty_range(): assert integer_range(TestData.for_buffer(b""), 1, 1) == 1