Skip to content

Commit c06e154

Browse files
cunlaromange
andauthored
Fix test hypothesis (#4927)
* fix:test-hypothesis --------- Co-authored-by: Roman Gershman <[email protected]>
1 parent 30a98c4 commit c06e154

File tree

17 files changed

+337
-279
lines changed

17 files changed

+337
-279
lines changed

.github/workflows/test-fakeredis.yml

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,6 @@ jobs:
2424
permissions:
2525
pull-requests: write
2626
checks: read
27-
# services:
28-
# redis:
29-
# image: docker.dragonflydb.io/dragonflydb/dragonfly:${{ matrix.DRAGONFLY_VERSION }}
30-
# ports:
31-
# - 6380:6379
32-
# options: >-
33-
# --health-cmd "redis-cli ping"
34-
# --health-interval 10s
35-
# --health-timeout 5s
36-
# --health-retries 5
3727

3828
steps:
3929
- uses: actions/checkout@v4
@@ -69,14 +59,8 @@ jobs:
6959
- name: Run tests
7060
working-directory: tests/fakeredis
7161
run: |
72-
poetry run pytest test/ \
73-
--ignore test/test_hypothesis/test_hash.py \
74-
--ignore test/test_hypothesis/test_set.py \
75-
--ignore test/test_hypothesis/test_zset.py \
76-
--ignore test/test_hypothesis/test_joint.py \
77-
--ignore test/test_hypothesis/test_transaction.py \
78-
--ignore test/test_mixins/test_bitmap_commands.py \
79-
--junit-xml=results-tests.xml --html=report-tests.html -v
62+
poetry run pytest -s test/ \
63+
--junit-xml=results-tests.xml --html=report-tests.html -v
8064
continue-on-error: true # For now to mark the flow as successful
8165

8266
- name: Show Dragonfly stats

tests/fakeredis/poetry.lock

Lines changed: 227 additions & 184 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/fakeredis/pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pytest = "^8.3"
2525
pytest-timeout = "^2.3.1"
2626
pytest-asyncio = "^0.24"
2727
pytest-cov = "^5.0"
28-
pytest-mock = "^3.14"
28+
pytest-mock = "^3.14"
2929
pytest-html = "^4.1"
3030

3131
[tool.pytest.ini_options]
@@ -45,4 +45,5 @@ generate_report_on_test = true
4545
render_collapsed = "failed,error"
4646
addopts = [
4747
"--self-contained-html",
48+
"--import-mode=importlib",
4849
]

tests/fakeredis/test/test_hypotesis_joint/__init__.py

Whitespace-only changes.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import hypothesis.strategies as st
2+
3+
from .. import test_hypothesis as tests
4+
from ..test_hypothesis.base import BaseTest, common_commands, commands
5+
from ..test_hypothesis.test_string import string_commands
6+
7+
bad_commands = (
8+
# redis-py splits the command on spaces, and hangs if that ends up being an empty list
9+
commands(
10+
st.text().filter(lambda x: bool(x.split())), st.lists(st.binary() | st.text())
11+
)
12+
)
13+
14+
15+
class TestJoint(BaseTest):
16+
create_command_strategy = (
17+
tests.TestString.create_command_strategy
18+
| tests.TestHash.create_command_strategy
19+
| tests.TestList.create_command_strategy
20+
| tests.TestSet.create_command_strategy
21+
| tests.TestZSet.create_command_strategy
22+
)
23+
command_strategy = (
24+
tests.TestServer.server_commands
25+
| tests.TestConnection.connection_commands
26+
| string_commands
27+
| tests.TestHash.hash_commands
28+
| tests.TestList.list_commands
29+
| tests.TestSet.set_commands
30+
| tests.TestZSet.zset_commands
31+
| common_commands
32+
| bad_commands
33+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
__all__ = [
2+
"TestConnection",
3+
"TestHash",
4+
"TestList",
5+
"TestServer",
6+
"TestSet",
7+
"TestString",
8+
"TestTransaction",
9+
"TestZSet",
10+
]
11+
12+
from .test_connection import TestConnection
13+
from .test_hash import TestHash
14+
from .test_list import TestList
15+
from .test_server import TestServer
16+
from .test_set import TestSet
17+
from .test_string import TestString
18+
from .test_transaction import TestTransaction
19+
from .test_zset import TestZSet

tests/fakeredis/test/test_hypothesis/base.py

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import functools
22
import math
3+
import string
34
import sys
45
from typing import Any, List, Tuple, Type, Optional
56

@@ -34,11 +35,15 @@ def sample_attr(draw, name):
3435
values = sample_attr("values")
3536
scores = sample_attr("scores")
3637

38+
eng_text = st.builds(
39+
lambda x: x.encode(), st.text(alphabet=string.ascii_letters, min_size=1)
40+
)
3741
ints = st.integers(min_value=MIN_INT, max_value=MAX_INT)
3842
int_as_bytes = st.builds(lambda x: str(_default_normalize(x)).encode(), ints)
39-
float_as_bytes = st.builds(
40-
lambda x: repr(_default_normalize(x)).encode(), st.floats(width=32)
43+
floats = st.floats(
44+
width=32, allow_nan=False, allow_subnormal=False, allow_infinity=False
4145
)
46+
float_as_bytes = st.builds(lambda x: repr(_default_normalize(x)).encode(), floats)
4247
counts = st.integers(min_value=-3, max_value=3) | ints
4348
# Redis has an integer overflow bug in swapdb, so we confine the numbers to
4449
# a limited range (https://github.com/antirez/redis/issues/5737).
@@ -51,8 +56,8 @@ def sample_attr(draw, name):
5156
) | st.binary().filter(lambda x: b"\0" not in x)
5257

5358
# Redis has integer overflow bugs in time computations, which is why we set a maximum.
54-
expires_seconds = st.integers(min_value=100000, max_value=MAX_INT)
55-
expires_ms = st.integers(min_value=100000000, max_value=MAX_INT)
59+
expires_seconds = st.integers(min_value=5, max_value=1_000)
60+
expires_ms = st.integers(min_value=5_000, max_value=50_000)
5661

5762

5863
class WrappedException:
@@ -98,6 +103,8 @@ def _sort_list(lst):
98103

99104

100105
def _normalize_if_number(x):
106+
if isinstance(x, list):
107+
return [_normalize_if_number(i) for i in x]
101108
try:
102109
res = float(x)
103110
return x if math.isnan(res) else res
@@ -149,6 +156,7 @@ def normalize(self):
149156
b"sinter",
150157
b"sunion",
151158
b"smembers",
159+
b"hexpire",
152160
}
153161
if command in unordered:
154162
return _sort_list
@@ -159,7 +167,7 @@ def normalize(self):
159167
def testable(self) -> bool:
160168
"""Whether this command is suitable for a test.
161169
162-
The fuzzer can create commands with behaviour that is non-deterministic, not supported, or which hits redis bugs.
170+
The fuzzer can create commands with behavior that is non-deterministic, not supported, or which hits redis bugs.
163171
"""
164172
N = len(self.args)
165173
if N == 0:
@@ -201,20 +209,6 @@ def commands(*args, **kwargs):
201209
| commands(st.just("sort"), keys, *zero_or_more("asc", "desc", "alpha"))
202210
)
203211

204-
attrs = st.fixed_dictionaries(
205-
{
206-
"keys": st.lists(st.binary(), min_size=2, max_size=5, unique=True),
207-
"fields": st.lists(st.binary(), min_size=2, max_size=5, unique=True),
208-
"values": st.lists(
209-
st.binary() | int_as_bytes | float_as_bytes,
210-
min_size=2,
211-
max_size=5,
212-
unique=True,
213-
),
214-
"scores": st.lists(st.floats(width=32), min_size=2, max_size=5, unique=True),
215-
}
216-
)
217-
218212

219213
@hypothesis.settings(max_examples=1000)
220214
class CommonMachine(hypothesis.stateful.RuleBasedStateMachine):
@@ -291,6 +285,16 @@ def _compare(self, command: Command) -> None:
291285
for n, r, f in zip(self.transaction_normalize, real_result, fake_result):
292286
assert n(f) == n(r)
293287
self.transaction_normalize = []
288+
elif isinstance(fake_result, list):
289+
assert len(fake_result) == len(real_result), (
290+
f"Discrepancy when running command {command}, fake({fake_result}) != real({real_result})",
291+
)
292+
for i in range(len(fake_result)):
293+
assert fake_result[i] == real_result[i] or (
294+
type(fake_result[i]) is float
295+
and fake_result[i] == pytest.approx(real_result[i])
296+
), f"Discrepancy when running command {command}, fake({fake_result}) != real({real_result})"
297+
294298
else:
295299
assert fake_result == real_result or (
296300
type(fake_result) is float and fake_result == pytest.approx(real_result)
@@ -307,7 +311,26 @@ def _compare(self, command: Command) -> None:
307311
):
308312
self.transaction_normalize = []
309313

310-
@initialize(attrs=attrs)
314+
@initialize(
315+
attrs=st.fixed_dictionaries(
316+
dict(
317+
keys=st.lists(eng_text, min_size=2, max_size=5, unique=True),
318+
fields=st.lists(eng_text, min_size=2, max_size=5, unique=True),
319+
values=st.lists(
320+
eng_text | int_as_bytes | float_as_bytes,
321+
min_size=2,
322+
max_size=5,
323+
unique=True,
324+
),
325+
scores=st.lists(
326+
floats,
327+
min_size=2,
328+
max_size=5,
329+
unique=True,
330+
),
331+
)
332+
)
333+
)
311334
def init_attrs(self, attrs):
312335
for key, value in attrs.items():
313336
setattr(self, key, value)

tests/fakeredis/test/test_hypothesis/test_connection.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import hypothesis.strategies as st
22

3-
from test.test_hypothesis.base import BaseTest, commands, values, common_commands
3+
from .base import BaseTest, commands, values, common_commands
44

55

66
class TestConnection(BaseTest):

tests/fakeredis/test/test_hypothesis/test_hash.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import hypothesis.strategies as st
22

3-
from test.test_hypothesis.base import (
3+
from .base import (
44
BaseTest,
55
commands,
66
values,
@@ -37,7 +37,7 @@ class TestHash(BaseTest):
3737
expires_seconds,
3838
st.just("fields"),
3939
st.just(2),
40-
st.lists(fields, min_size=2, max_size=2),
40+
st.lists(fields, min_size=2, max_size=2, unique=True),
4141
)
4242
)
4343
create_command_strategy = commands(

tests/fakeredis/test/test_hypothesis/test_joint.py

Lines changed: 0 additions & 38 deletions
This file was deleted.

0 commit comments

Comments
 (0)