Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove hypothesis (at least for now) #586

Merged
merged 12 commits into from
May 3, 2023
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
*~
.DS_Store
/.eggs
/.hypothesis/
/.tox/
/apidocs/
/build/
Expand Down
5 changes: 0 additions & 5 deletions mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,6 @@ ignore_missing_imports = True
[mypy-treq.*]
ignore_missing_imports = True

[mypy-hypothesis]
ignore_missing_imports = True
[mypy-hypothesis.*]
ignore_missing_imports = True

[mypy-idna]
ignore_missing_imports = True

Expand Down
1 change: 0 additions & 1 deletion requirements/tox-tests.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
treq==22.2.0
hypothesis==6.48.2
idna==3.3
12 changes: 0 additions & 12 deletions src/klein/test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,3 @@
"""
Tests for L{klein}.
"""

from hypothesis import HealthCheck, settings


settings.register_profile(
"patience",
settings(
deadline=None,
suppress_health_check=[HealthCheck.too_slow],
),
)
settings.load_profile("patience")
175 changes: 175 additions & 0 deletions src/klein/test/not_hypothesis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
"""
We have had a history of U{bad
experiences<https://github.com/twisted/klein/issues/561>} with Hypothesis in
Klein, and maybe it's not actually a good application of this tool at all. As
such we have removed it, at least for now. This module presents a vaguely
Hypothesis-like stub, to keep the structure of our tests in a
Hypothesis-friendly shape, in case we want to put it back.
"""

from functools import wraps
from itertools import product
from string import ascii_uppercase
from typing import Callable, Iterable, Optional, Tuple, TypeVar

from hyperlink import DecodedURL


T = TypeVar("T")
S = TypeVar("S")


def given(
*args: Callable[[], Iterable[T]],
**kwargs: Callable[[], Iterable[T]],
) -> Callable[[Callable[..., None]], Callable[..., None]]:
def decorator(testMethod: Callable[..., None]) -> Callable[..., None]:
@wraps(testMethod)
def realTestMethod(self: S) -> None:
everyPossibleArgs = product(
*[eachFactory() for eachFactory in args]
)
everyPossibleKwargs = product(
*[
[(name, eachValue) for eachValue in eachFactory()]
for (name, eachFactory) in kwargs.items()
]
)
everyPossibleSignature = product(
everyPossibleArgs, everyPossibleKwargs
)
# not quite the _full_ cartesian product but the whole point is
# that we're making a feeble attempt at this rather than bringing
# in hypothesis.
for computedArgs, computedPairs in everyPossibleSignature:
computedKwargs = dict(computedPairs)
testMethod(self, *computedArgs, **computedKwargs)

return realTestMethod

return decorator


def binary() -> Callable[[], Iterable[bytes]]:
"""
Generate some binary data.
"""

def params() -> Iterable[bytes]:
return [b"data", b"data data data", b"\x00" * 50, b""]

return params


def ascii_text(min_size: int) -> Callable[[], Iterable[str]]:
"""
Generate some ASCII strs.
"""

def params() -> Iterable[str]:
yield from [
"ascii-text",
"some more ascii text",
]
assert min_size, "nothing needs 0-length strings right now"

return params


def latin1_text(min_size: int = 0) -> Callable[[], Iterable[str]]:
"""
Generate some strings encodable as latin1
"""

def params() -> Iterable[str]:
yield from [
"latin1-text",
"some more latin1 text",
"hére is latin1 text",
]
if not min_size:
yield ""

return params


def text(
min_size: int = 0, alphabet: Optional[str] = None
) -> Callable[[], Iterable[str]]:
"""
Generate some text.
"""

def params() -> Iterable[str]:
if alphabet == ascii_uppercase:
yield from ascii_text(min_size)()
return
yield from latin1_text(min_size)()
yield "\N{SNOWMAN}"

return params


def textHeaderPairs() -> Callable[[], Iterable[Iterable[Tuple[str, str]]]]:
"""
Generate some pairs of headers with text values.
"""

def params() -> Iterable[Iterable[Tuple[str, str]]]:
return [[], [("text", "header")]]

return params


def bytesHeaderPairs() -> Callable[[], Iterable[Iterable[Tuple[str, bytes]]]]:
"""
Generate some pairs of headers with bytes values.
"""

def params() -> Iterable[Iterable[Tuple[str, bytes]]]:
return [[], [("bytes", b"header")]]

return params


def booleans() -> Callable[[], Iterable[bool]]:
def parameters() -> Iterable[bool]:
yield True
yield False

return parameters


def jsonObjects() -> Callable[[], Iterable[object]]:
def parameters() -> Iterable[object]:
yield {}
yield {"hello": "world"}
yield {"here is": {"some": "nesting"}}
yield {
"and": "multiple",
"keys": {
"with": "nesting",
"and": 1234,
"numbers": ["with", "lists", "too"],
"also": ("tuples", "can", "serialize"),
},
}

return parameters


def decoded_urls() -> Callable[[], Iterable[DecodedURL]]:
"""
Generate a few URLs U{with only path and domain names
<https://github.com/python-hyper/hyperlink/issues/181>} kind of like
Hyperlink's own hypothesis strategy.
"""

def parameters() -> Iterable[DecodedURL]:
yield DecodedURL.from_text("https://example.com/")
yield DecodedURL.from_text("https://example.com")
yield DecodedURL.from_text("http://example.com/")
yield DecodedURL.from_text("https://example.com/é")
yield DecodedURL.from_text("https://súbdomain.example.com/ascii/path/")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want a URL str that has a %-encoded path element here?


return parameters
73 changes: 10 additions & 63 deletions src/klein/test/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

from abc import ABC, abstractmethod
from collections import defaultdict
from string import ascii_letters
from typing import (
AnyStr,
Callable,
Expand All @@ -20,17 +19,6 @@
cast,
)

from hypothesis import given
from hypothesis.strategies import (
binary,
characters,
composite,
iterables,
lists,
text,
tuples,
)

from .._headers import (
HEADER_NAME_ENCODING,
HEADER_VALUE_ENCODING,
Expand All @@ -49,6 +37,14 @@
normalizeRawHeadersFrozen,
)
from ._trial import TestCase
from .not_hypothesis import (
binary,
bytesHeaderPairs,
given,
latin1_text,
text,
textHeaderPairs,
)


__all__ = ()
Expand All @@ -58,55 +54,6 @@
DrawCallable = Callable[[Callable[..., T]], T]


@composite
def ascii_text(
draw: DrawCallable,
min_size: Optional[int] = 0,
max_size: Optional[int] = None,
) -> str: # pragma: no cover
"""
A strategy which generates ASCII-encodable text.

@param min_size: The minimum number of characters in the text.
C{None} is treated as C{0}.

@param max_size: The maximum number of characters in the text.
Use C{None} for an unbounded size.
"""
return cast(
str,
draw(
text(min_size=min_size, max_size=max_size, alphabet=ascii_letters)
),
)


@composite # pragma: no cover
def latin1_text(
draw: DrawCallable,
min_size: Optional[int] = 0,
max_size: Optional[int] = None,
) -> str:
"""
A strategy which generates ISO-8859-1-encodable text.

@param min_size: The minimum number of characters in the text.
C{None} is treated as C{0}.

@param max_size: The maximum number of characters in the text.
Use C{None} for an unbounded size.
"""
return "".join(
draw(
lists(
characters(max_codepoint=255),
min_size=min_size,
max_size=max_size,
)
)
)


def encodeName(name: str) -> Optional[bytes]:
return name.encode(HEADER_NAME_ENCODING)

Expand Down Expand Up @@ -304,7 +251,7 @@ def headerNormalize(self, value: str) -> str:
"""
return value

@given(iterables(tuples(ascii_text(min_size=1), latin1_text())))
@given(textHeaderPairs())
def test_getTextName(self, textPairs: Iterable[Tuple[str, str]]) -> None:
"""
C{getValues} returns an iterable of L{str} values for
Expand Down Expand Up @@ -333,7 +280,7 @@ def test_getTextName(self, textPairs: Iterable[Tuple[str, str]]) -> None:
f"header name: {name!r}",
)

@given(iterables(tuples(ascii_text(min_size=1), binary())))
@given(bytesHeaderPairs())
def test_getTextNameBinaryValues(
self, pairs: Iterable[Tuple[str, bytes]]
) -> None:
Expand Down
4 changes: 1 addition & 3 deletions src/klein/test/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@
from abc import ABC, abstractmethod
from typing import cast

from hypothesis import given
from hypothesis.strategies import binary

from twisted.internet.defer import ensureDeferred

from .._imessage import IHTTPMessage
from .._message import FountAlreadyAccessedError, bytesToFount, fountToBytes
from ._trial import TestCase
from .not_hypothesis import binary, given


__all__ = ()
Expand Down
Loading