예제 #1
0
def _get_strategy_for_field(f):
    # type: (Type[dm.Field]) -> st.SearchStrategy[Any]
    if f.choices:
        choices = []  # type: list
        for value, name_or_optgroup in f.choices:
            if isinstance(name_or_optgroup, (list, tuple)):
                choices.extend(key for key, _ in name_or_optgroup)
            else:
                choices.append(value)
        if isinstance(f, (dm.CharField, dm.TextField)) and f.blank:
            choices.insert(0, u'')
        strategy = st.sampled_from(choices)
    elif type(f) == dm.SlugField:
        strategy = st.text(alphabet=string.ascii_letters + string.digits,
                           min_size=(None if f.blank else 1),
                           max_size=f.max_length)
    elif type(f) == dm.GenericIPAddressField:
        lookup = {'both': ip4_addr_strings() | ip6_addr_strings(),
                  'ipv4': ip4_addr_strings(), 'ipv6': ip6_addr_strings()}
        strategy = lookup[f.protocol.lower()]
    elif type(f) in (dm.TextField, dm.CharField):
        strategy = st.text(
            alphabet=st.characters(blacklist_characters=u'\x00',
                                   blacklist_categories=('Cs',)),
            min_size=(None if f.blank else 1),
            max_size=f.max_length,
        )
        # We can infer a vastly more precise strategy by considering the
        # validators as well as the field type.  This is a minimal proof of
        # concept, but we intend to leverage the idea much more heavily soon.
        # See https://github.com/HypothesisWorks/hypothesis-python/issues/1116
        re_validators = [
            v for v in f.validators
            if isinstance(v, validators.RegexValidator) and not v.inverse_match
        ]
        if re_validators:
            regexes = [re.compile(v.regex, v.flags) if isinstance(v.regex, str)
                       else v.regex for v in re_validators]
            # This strategy generates according to one of the regexes, and
            # filters using the others.  It can therefore learn to generate
            # from the most restrictive and filter with permissive patterns.
            # Not maximally efficient, but it makes pathological cases rarer.
            # If you want a challenge: extend https://qntm.org/greenery to
            # compute intersections of the full Python regex language.
            strategy = st.one_of(*[st.from_regex(r) for r in regexes])
    elif type(f) == dm.DecimalField:
        bound = Decimal(10 ** f.max_digits - 1) / (10 ** f.decimal_places)
        strategy = st.decimals(min_value=-bound, max_value=bound,
                               places=f.decimal_places)
    else:
        strategy = field_mappings().get(type(f), st.nothing())
    if f.validators:
        strategy = strategy.filter(validator_to_filter(f))
    if f.null:
        strategy = st.one_of(st.none(), strategy)
    return strategy
예제 #2
0
def _for_form_ip(field):
    # the IP address form fields have no direct indication of which type
    #  of address they want, so direct comparison with the validator
    #  function has to be used instead. Sorry for the potato logic here
    if validate_ipv46_address in field.default_validators:
        return ip4_addr_strings() | ip6_addr_strings()
    if validate_ipv4_address in field.default_validators:
        return ip4_addr_strings()
    if validate_ipv6_address in field.default_validators:
        return ip6_addr_strings()
    raise InvalidArgument("No IP version validator on field=%r" % field)
class TestIPAddress:
    @given(value=ip4_addr_strings())
    def test_init__valid(self, value):
        core.IPAddress(*value.split("."))

    @pytest.mark.parametrize("value", ("127.0.0", "localhost", ""))
    def test_init__invalid(self, value):
        with pytest.raises(ValueError,
                           match="IPAddress must consist of 4 items"):
            core.IPAddress(*value.split("."))

    @given(value=ip4_addr_strings())
    def test_str(self, value):
        target = core.IPAddress(*value.split("."))

        actual = str(target)

        assert actual == value
def _get_strategy_for_field(f):
    if f.choices:
        choices = []
        for value, name_or_optgroup in f.choices:
            if isinstance(name_or_optgroup, (list, tuple)):
                choices.extend(key for key, _ in name_or_optgroup)
            else:
                choices.append(value)
        if isinstance(f, (dm.CharField, dm.TextField)) and f.blank:
            choices.insert(0, u'')
        strategy = st.sampled_from(choices)
    elif type(f) == dm.SlugField:
        strategy = st.text(alphabet=string.ascii_letters + string.digits,
                           min_size=(None if f.blank else 1),
                           max_size=f.max_length)
    elif type(f) == dm.GenericIPAddressField:
        lookup = {
            'both': ip4_addr_strings() | ip6_addr_strings(),
            'ipv4': ip4_addr_strings(),
            'ipv6': ip6_addr_strings()
        }
        strategy = lookup[f.protocol.lower()]
    elif type(f) in (dm.TextField, dm.CharField):
        strategy = st.text(
            alphabet=st.characters(blacklist_characters=u'\x00',
                                   blacklist_categories=('Cs', )),
            min_size=(None if f.blank else 1),
            max_size=f.max_length,
        )
    elif type(f) == dm.DecimalField:
        bound = Decimal(10**f.max_digits - 1) / (10**f.decimal_places)
        strategy = st.decimals(min_value=-bound,
                               max_value=bound,
                               places=f.decimal_places)
    else:
        strategy = field_mappings().get(type(f), st.nothing())
    if f.validators:
        strategy = strategy.filter(validator_to_filter(f))
    if f.null:
        strategy = st.one_of(st.none(), strategy)
    return strategy
def string_schema(schema: dict) -> st.SearchStrategy[str]:
    """Handle schemata for strings."""
    # also https://json-schema.org/latest/json-schema-validation.html#rfc.section.7
    min_size = schema.get("minLength", 0)
    max_size = schema.get("maxLength", float("inf"))
    strategy = st.text(min_size=min_size, max_size=schema.get("maxLength"))
    if "format" in schema:
        url_synonyms = [
            "uri", "uri-reference", "iri", "iri-reference", "uri-template"
        ]
        domains = prov.domains()  # type: ignore
        formats = {
            # A value of None indicates a known but unsupported format.
            **{name: rfc3339(name)
               for name in RFC3339_FORMATS},
            "date": rfc3339("full-date"),
            "time": rfc3339("full-time"),
            # Hypothesis' provisional strategies are not type-annotated.
            # We should get a principled plan for them at some point I guess...
            "email": st.emails(),  # type: ignore
            "idn-email": st.emails(),  # type: ignore
            "hostname": domains,
            "idn-hostname": domains,
            "ipv4": prov.ip4_addr_strings(),  # type: ignore
            "ipv6": prov.ip6_addr_strings(),  # type: ignore
            **{
                name: domains.map("https://{}".format)
                for name in url_synonyms
            },
            "json-pointer": st.just(""),
            "relative-json-pointer": st.just(""),
            "regex": REGEX_PATTERNS,
        }
        if schema["format"] not in formats:
            raise InvalidArgument(
                f"Unsupported string format={schema['format']}")
        strategy = formats[schema["format"]]
        if "pattern" in schema:  # pragma: no cover
            # This isn't really supported, but we'll do our best.
            strategy = strategy.filter(
                lambda s: re.search(schema["pattern"], string=s) is not None)
    elif "pattern" in schema:
        try:
            re.compile(schema["pattern"])
            strategy = st.from_regex(schema["pattern"])
        except re.error:
            # Patterns that are invalid in Python, or just malformed
            return st.nothing()
    # TODO: mypy should be able to tell that the lambda is returning a bool
    # without the explicit cast, but can't as of v 0.720 - report upstream.
    return strategy.filter(lambda s: bool(min_size <= len(s) <= max_size))
예제 #6
0
def string_schema(schema: dict) -> st.SearchStrategy[str]:
    """Handle schemata for strings."""
    # also https://json-schema.org/latest/json-schema-validation.html#rfc.section.7
    min_size = schema.get("minLength", 0)
    max_size = schema.get("maxLength", float("inf"))
    strategy: str = st.text(min_size=min_size,
                            max_size=schema.get("maxLength"))
    if "format" in schema:
        url_synonyms = [
            "uri", "uri-reference", "iri", "iri-reference", "uri-template"
        ]
        domains = prov.domains()
        strategy = {
            # A value of None indicates a known but unsupported format.
            **{name: rfc3339(name)
               for name in RFC3339_FORMATS},
            "date": rfc3339("full-date"),
            "time": rfc3339("full-time"),
            "email": st.emails(),
            "idn-email": st.emails(),
            "hostname": domains,
            "idn-hostname": domains,
            "ipv4": prov.ip4_addr_strings(),
            "ipv6": prov.ip6_addr_strings(),
            **{
                name: domains.map("https://{}".format)
                for name in url_synonyms
            },
            "json-pointer": st.just(""),
            "relative-json-pointer": st.just(""),
            "regex": REGEX_PATTERNS,
        }.get(schema["format"])
        if strategy is None:
            raise InvalidArgument(
                f"Unsupported string format={schema['format']}")
        if "pattern" in schema:  # pragma: no cover
            # This isn't really supported, but we'll do our best.
            strategy = strategy.filter(
                lambda s: re.search(schema["pattern"], string=s) is not None)
    elif "pattern" in schema:
        try:
            re.compile(schema["pattern"])
            strategy = st.from_regex(schema["pattern"])
        except re.error:
            # Patterns that are invalid in Python, or just malformed
            strategy = st.nothing()
    return strategy.filter(lambda s: min_size <= len(s) <= max_size)
예제 #7
0
def string_schema(schema: dict) -> st.SearchStrategy[str]:
    """Handle schemata for strings."""
    # also https://json-schema.org/latest/json-schema-validation.html#rfc.section.7
    min_size = schema.get("minLength", 0)
    max_size = schema.get("maxLength", float("inf"))
    strategy: Any = st.text(min_size=min_size,
                            max_size=schema.get("maxLength"))
    assert not (
        "format" in schema and "pattern" in schema
    ), "format and regex constraints are supported, but not both at once."
    if "pattern" in schema:
        strategy = st.from_regex(schema["pattern"])
    elif "format" in schema:
        url_synonyms = [
            "uri", "uri-reference", "iri", "iri-reference", "uri-template"
        ]
        domains = prov.domains()  # type: ignore
        strategy = {
            # A value of None indicates a known but unsupported format.
            **{name: rfc3339(name)
               for name in RFC3339_FORMATS},
            "date": rfc3339("full-date"),
            "time": rfc3339("full-time"),
            "email": st.emails(),  # type: ignore
            "idn-email": st.emails(),  # type: ignore
            "hostname": domains,
            "idn-hostname": domains,
            "ipv4": prov.ip4_addr_strings(),  # type: ignore
            "ipv6": prov.ip6_addr_strings(),  # type: ignore
            **{
                name: domains.map("https://{}".format)
                for name in url_synonyms
            },
            "json-pointer": st.just(""),
            "relative-json-pointer": st.just(""),
            "regex": REGEX_PATTERNS,
        }.get(schema["format"])
        if strategy is None:
            raise InvalidArgument(
                f"Unsupported string format={schema['format']}")
    return strategy.filter(
        lambda s: min_size <= len(s) <= max_size)  # type: ignore
예제 #8
0
#
# 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

from __future__ import division, print_function, absolute_import

from binascii import unhexlify

from hypothesis import given
from hypothesis.provisional import ip4_addr_strings, ip6_addr_strings


@given(ip4_addr_strings())
def test_is_IP4_addr(address):
    as_num = [int(n) for n in address.split('.')]
    assert len(as_num) == 4
    assert all(0 <= n <= 255 for n in as_num)


@given(ip6_addr_strings())
def test_is_IP6_addr(address):
    # Works for non-normalised addresses produced by this strategy, but not
    # a particularly general test
    assert address == address.upper()
    as_hex = address.split(':')
    assert len(as_hex) == 8
    assert all(len(part) == 4 for part in as_hex)
    raw = unhexlify(address.replace(u':', u'').encode('ascii'))
from hypothesis.provisional import domains, ip4_addr_strings, ip6_addr_strings, urls
from tests.common.debug import find_any


@given(urls())
def test_is_URL(url):
    allowed_chars = set(string.ascii_letters + string.digits + "$-_.+!*'(),%/")
    url_schemeless = url.split("://", 1)[1]
    path = url_schemeless.split("/", 1)[1] if "/" in url_schemeless else ""
    assert all(c in allowed_chars for c in path)
    assert all(
        re.match("^[0-9A-Fa-f]{2}", after_perc) for after_perc in path.split("%")[1:]
    )


@given(ip4_addr_strings())
def test_is_IP4_addr(address):
    as_num = [int(n) for n in address.split(".")]
    assert len(as_num) == 4
    assert all(0 <= n <= 255 for n in as_num)


@given(ip6_addr_strings())
def test_is_IP6_addr(address):
    # Works for non-normalised addresses produced by this strategy, but not
    # a particularly general test
    assert address == address.upper()
    as_hex = address.split(":")
    assert len(as_hex) == 8
    assert all(len(part) == 4 for part in as_hex)
    raw = unhexlify(address.replace(u":", u"").encode("ascii"))
예제 #10
0
def _for_model_ip(field):
    return {
        "ipv4": ip4_addr_strings(),
        "ipv6": ip6_addr_strings(),
        "both": ip4_addr_strings() | ip6_addr_strings(),
    }[field.protocol.lower()]
예제 #11
0
def _for_model_ip(field):
    return dict(
        ipv4=ip4_addr_strings(),
        ipv6=ip6_addr_strings(),
        both=ip4_addr_strings() | ip6_addr_strings(),
    )[field.protocol.lower()]
예제 #12
0
def test_cpp_string_escape(string, expected):
    actual = helpers.cpp_string_escape(string)

    assert actual == expected


@pytest.mark.parametrize("host", (
    "127.0.0", "localhost", "127.0.0.b",
))
def test_is_ip_address__invalid(host):
    actual = helpers.is_ip_address(host)

    assert actual is False


@given(value=ip4_addr_strings())
def test_is_ip_address__valid(value):
    actual = helpers.is_ip_address(value)

    assert actual is True


@pytest.mark.parametrize("var, value, default, expected", (
        ("FOO", None, False, False),
        ("FOO", None, True, True),
        ("FOO", "", False, False),
        ("FOO", "Yes", False, True),
        ("FOO", "123", False, True),
))
def test_get_bool_env(monkeypatch, var, value, default, expected):
    if value is None:
예제 #13
0
REGEX_PATTERNS = regex_patterns()

STRING_FORMATS = {
    # A value of None indicates a known but unsupported format.
    **{name: rfc3339(name)
       for name in RFC3339_FORMATS},
    "date": rfc3339("full-date"),
    "time": rfc3339("full-time"),
    # Hypothesis' provisional strategies are not type-annotated.
    # We should get a principled plan for them at some point I guess...
    "email": st.emails(),  # type: ignore
    "idn-email": st.emails(),  # type: ignore
    "hostname": prov.domains(),
    "idn-hostname": prov.domains(),
    "ipv4": prov.ip4_addr_strings(),
    "ipv6": prov.ip6_addr_strings(),
    **{
        name: prov.domains().map("https://{}".format)
        for name in [
            "uri", "uri-reference", "iri", "iri-reference", "uri-template"
        ]
    },
    "json-pointer": st.just(""),
    "relative-json-pointer": st.just(""),
    "regex": REGEX_PATTERNS,
}


def string_schema(schema: dict) -> st.SearchStrategy[str]:
    """Handle schemata for strings."""
예제 #14
0
REGEX_PATTERNS = regex_patterns()

_domains = prov.domains()  # type: ignore
STRING_FORMATS = {
    # A value of None indicates a known but unsupported format.
    **{name: rfc3339(name)
       for name in RFC3339_FORMATS},
    "date": rfc3339("full-date"),
    "time": rfc3339("full-time"),
    # Hypothesis' provisional strategies are not type-annotated.
    # We should get a principled plan for them at some point I guess...
    "email": st.emails(),  # type: ignore
    "idn-email": st.emails(),  # type: ignore
    "hostname": _domains,
    "idn-hostname": _domains,
    "ipv4": prov.ip4_addr_strings(),  # type: ignore
    "ipv6": prov.ip6_addr_strings(),  # type: ignore
    **{
        name: _domains.map("https://{}".format)
        for name in [
            "uri", "uri-reference", "iri", "iri-reference", "uri-template"
        ]
    },
    "json-pointer": st.just(""),
    "relative-json-pointer": st.just(""),
    "regex": REGEX_PATTERNS,
}


def string_schema(schema: dict) -> st.SearchStrategy[str]:
    """Handle schemata for strings."""
예제 #15
0

class InvalidFormatError(ValueError):

    def __init__(self, fmt: str):
        super().__init__(f"{fmt} is not currently supported.")


FORMATS = {
    "uuid": st.uuids().map(str),
    "email": st.emails(),
    "idn-email": st.emails(),
    "date-time": st.datetimes().map(lambda x: x.isoformat()),
    "date": st.dates().map(lambda x: x.isoformat()),
    "time": st.times().map(lambda x: x.isoformat()),
    "ipv4": pst.ip4_addr_strings(),
    "ipv6": pst.ip6_addr_strings(),
}


def format_strings(fmt: str) -> Strategy:
    try:
        return FORMATS[fmt]
    except KeyError:
        raise InvalidFormatError(fmt)


def pattern_strings(pattern: str, min_length: Optional[int] = None, max_length: Optional[int] = None) -> Strategy:
    if pattern[-1] == "$":
        pattern = rf"{pattern[:-1]}\Z"
    schema = st.from_regex(pattern)