1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
|
# This file is part of Hypothesis, which may be found at
# https://github.com/HypothesisWorks/hypothesis/
#
# Copyright the Hypothesis Authors.
# Individual contributors are listed in AUTHORS.rst and the git log.
#
# 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 https://mozilla.org/MPL/2.0/.
import random
from dataclasses import dataclass, field
from typing import Any
import pytest
from hypothesis import (
HealthCheck,
Phase,
Verbosity,
assume,
given,
note,
settings,
strategies as st,
)
from hypothesis.internal.conjecture.data import Status
from hypothesis.internal.conjecture.engine import ConjectureRunner
from tests.common.utils import Why, skipif_threading, xfail_on_crosshair
from tests.conjecture.common import interesting_origin
@dataclass
class Write:
value: Any
child: Any
@dataclass
class Branch:
bits: Any
children: Any = field(default_factory=dict)
@dataclass
class Terminal:
status: Any
payload: Any = field(default=None)
nodes = st.deferred(lambda: terminals | writes | branches)
# Does not include Status.OVERFLOW by design: That happens because of the size
# of the string, not the input language.
terminals = st.one_of(
st.just(Terminal(Status.VALID)),
st.just(Terminal(Status.INVALID)),
st.builds(Terminal, status=st.just(Status.INTERESTING), payload=st.integers(0, 10)),
)
branches = st.builds(Branch, bits=st.integers(1, 64))
writes = st.builds(Write, value=st.binary(min_size=1), child=nodes)
# Remember what the default phases are with no test running, so that we can
# run an outer test with non-default phases and then restore the defaults for
# the inner test.
_default_phases = settings.default.phases
def run_language_test_for(root, data, seed):
random.seed(seed)
def test(local_data):
node = root
while not isinstance(node, Terminal):
if isinstance(node, Write):
local_data.draw_bytes(
len(node.value), len(node.value), forced=node.value
)
node = node.child
else:
assert isinstance(node, Branch)
c = local_data.draw_integer(0, 2**node.bits - 1)
try:
node = node.children[c]
except KeyError:
if data is None:
return
node = node.children.setdefault(c, data.draw(nodes))
assert isinstance(node, Terminal)
if node.status == Status.INTERESTING:
local_data.mark_interesting(interesting_origin(node.payload))
elif node.status == Status.INVALID:
local_data.mark_invalid()
runner = ConjectureRunner(
test,
settings=settings(
max_examples=1,
database=None,
suppress_health_check=list(HealthCheck),
verbosity=Verbosity.quiet,
# Restore the global default phases, so that we don't inherit the
# phases setting from the outer test.
phases=_default_phases,
),
)
try:
runner.run()
finally:
if data is not None:
note(root)
assume(runner.interesting_examples)
# modifying global random, causing warnings from deprecate_random_in_strategy.
# unclear why the warning doesn't trigger on a
# single thread, but that was previous behavior so I'm not looking too deep into it.
@skipif_threading
@xfail_on_crosshair(Why.nested_given) # technically nested-engine, but same problem
@settings(
suppress_health_check=list(HealthCheck),
deadline=None,
phases=set(settings.default.phases) - {Phase.shrink},
)
@given(st.data())
def test_explore_an_arbitrary_language(data):
root = data.draw(writes | branches)
seed = data.draw(st.integers(0, 2**64 - 1))
run_language_test_for(root, data, seed)
@pytest.mark.parametrize("seed, language", [])
def test_run_specific_example(seed, language):
"""This test recreates individual languages generated with the main test.
These are typically manually pruned down a bit - e.g. it's
OK to remove VALID nodes because KeyError is treated as if it lead to one
in this test (but not in the @given test).
These tests are likely to be fairly fragile with respect to changes in the
underlying engine. Feel free to delete examples if they start failing after
a change.
"""
run_language_test_for(language, None, seed)
|