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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
# 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 itertools
import math
from math import inf
import pytest
from hypothesis import (
HealthCheck,
assume,
example,
given,
note,
reject,
settings,
strategies as st,
)
from hypothesis.internal.conjecture.dfa import DEAD, ConcreteDFA
def test_enumeration_when_sizes_do_not_agree():
dfa = ConcreteDFA([{0: 1, 1: 2}, {}, {1: 3}, {}], {1, 3}) # 0 # 1 # 2 # 3
assert list(dfa.all_matching_strings()) == [b"\0", b"\1\1"]
def test_enumeration_of_very_long_strings():
"""This test is mainly testing that it terminates. If we were
to use a naive breadth first search for this it would take
forever to run because it would run in time roughly 256 ** 50.
"""
size = 50
dfa = ConcreteDFA(
[{c: n + 1 for c in range(256)} for n in range(100)] + [{}], {size}
)
for i, s in enumerate(dfa.all_matching_strings()):
assert len(s) == size
assert int.from_bytes(s, "big") == i
if i >= 1000:
break
def test_is_dead_with_cache_reuse():
dfa = ConcreteDFA([{0: i + 1, 1: 11} for i in range(10)] + [{}, {}], {10})
for n in range(10, -1, -1):
assert not dfa.is_dead(n)
def test_max_length_of_empty_dfa_is_zero():
dfa = ConcreteDFA([{}], {0})
assert dfa.max_length(dfa.start) == 0
def test_mixed_dfa_initialization():
d = ConcreteDFA([[(2, 1)], [(0, 5, 2)], {4: 0, 3: 1}], {0})
assert d.transition(0, 2) == 1
assert d.transition(0, 3) == DEAD
for n in range(6):
assert d.transition(1, n) == 2
assert d.transition(1, 6) == DEAD
assert d.transition(2, 4) == 0
assert d.transition(2, 3) == 1
assert d.transition(2, 5) == DEAD
@st.composite
def dfas(draw):
states = draw(st.integers(1, 20))
a_state = st.integers(0, states - 1)
a_byte = st.integers(0, 255)
start = draw(a_state)
accepting = draw(st.sets(a_state, min_size=1))
transitions = [draw(st.dictionaries(a_byte, a_state)) for _ in range(states)]
return ConcreteDFA(transitions, accepting, start)
@settings(max_examples=20)
@given(dfas(), st.booleans())
@example(
ConcreteDFA(
transitions=[[(0, 2), (1, 255, 1)], [(0, 2), (1, 255, 0)], []],
accepting={2},
),
False,
)
def test_canonicalised_matches_same_strings(dfa, via_repr):
canon = dfa.canonicalise()
note(canon)
if via_repr:
canon = eval(repr(canon))
assert dfa.max_length(dfa.start) == canon.max_length(canon.start)
try:
minimal = next(dfa.all_matching_strings())
except StopIteration:
reject()
assert minimal == next(canon.all_matching_strings())
assert dfa.count_strings(dfa.start, len(minimal)) == canon.count_strings(
canon.start, len(minimal)
)
# filters about 80% of examples. should potentially improve at some point.
@settings(max_examples=20, suppress_health_check=[HealthCheck.filter_too_much])
@given(dfas())
def test_has_string_of_max_length(dfa):
length = dfa.max_length(dfa.start)
assume(math.isfinite(length))
assume(not dfa.is_dead(dfa.start))
assert dfa.count_strings(dfa.start, length) > 0
def test_converts_long_tables_to_dicts():
dfa = ConcreteDFA(
[[(0, 0), (1, 1), (2, 2), (3, 1), (4, 0), (7, 10, 1)], [(0, 0)], []], {2}
)
assert dfa.transition(0, 2) == 2
assert dfa.transition(1, 0) == 0
assert isinstance(dfa._ConcreteDFA__transitions[0], dict)
assert isinstance(dfa._ConcreteDFA__transitions[1], list)
@settings(max_examples=20)
@given(dfas(), dfas())
def test_dfa_with_different_string_is_not_equivalent(x, y):
assume(not x.is_dead(x.start))
s = next(x.all_matching_strings())
assume(not y.matches(s))
assert not x.equivalent(y)
@example(x=b"", y=b"\0", z=b"\0")
@given(x=st.binary(), y=st.binary(min_size=1), z=st.binary())
def test_all_matching_regions_include_all_matches(x, y, z):
y_matcher = ConcreteDFA([{c: i + 1} for i, c in enumerate(y)] + [[]], {len(y)})
assert y_matcher.matches(y)
s = x + y + z
assert (len(x), len(x) + len(y)) in y_matcher.all_matching_regions(s)
@pytest.mark.parametrize("n", [1, 10, 100, 1000])
def test_max_length_of_long_dfa(n):
dfa = ConcreteDFA([{0: i + 1} for i in range(n)] + [{}], {n})
assert not dfa.is_dead(dfa.start)
assert dfa.max_length(dfa.start) == n
def test_dfa_with_cached_dead():
dfa = ConcreteDFA([[{0: 1, 1: 2}], [], []], {2})
assert dfa.is_dead(1)
assert dfa.is_dead(0)
@pytest.mark.parametrize("order", itertools.permutations((0, 1, 2)))
def test_dead_nodes(order):
dfa = ConcreteDFA([{0: 1, 1: 2}, {}, {}], {2})
for i in order:
assert dfa.is_dead(i) == (i == 1)
@given(st.permutations(range(5)))
def test_max_length_of_recursive_dfa(order):
dfa = ConcreteDFA([{0: 1, 1: 2, 2: 3}, {0: 2}, {0: 1}, {0: 0, 1: 4}, {}], {4})
for i in order:
dfa.max_length(i)
assert dfa.max_length(0) == inf
assert dfa.max_length(1) == 0
assert dfa.max_length(2) == 0
assert dfa.max_length(3) == inf
assert dfa.max_length(4) == 0
def test_transitions_out_of_dead_are_empty():
dfa = ConcreteDFA([{}], {0})
assert list(dfa.raw_transitions(DEAD)) == []
def test_can_transition_from_dead():
dfa = ConcreteDFA([{}], {0})
assert dfa.transition(DEAD, 0) == DEAD
|