Skip to content

Commit c5a0bbd

Browse files
authored
Merge pull request #4392 from tybug/db-equality
Implement `__eq__` for `ExampleDatabase` subclasses
2 parents 0a54469 + 86ef7d9 commit c5a0bbd

File tree

8 files changed

+107
-8
lines changed

8 files changed

+107
-8
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ jobs:
268268
# pyodide can't run multiple processes internally, so parallelize explicitly over
269269
# discovered test files instead (20 at a time)
270270
TEST_FILES=$(python -m pytest -p no:cacheprovider --setup-plan hypothesis-python/tests/cover | grep ^hypothesis-python/ | cut -d " " -f 1)
271+
echo "test files: $TEST_FILES"
271272
parallel --max-procs 100% --max-args 20 --keep-order --line-buffer \
272273
python -m pytest -p no:cacheprovider <<< $TEST_FILES
273274

hypothesis-python/RELEASE.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
RELEASE_TYPE: patch
2+
3+
All |ExampleDatabase| implementations in Hypothesis now implement ``__eq__``.

hypothesis-python/src/hypothesis/database.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@ def __init__(self) -> None:
347347
def __repr__(self) -> str:
348348
return f"InMemoryExampleDatabase({self.data!r})"
349349

350+
def __eq__(self, other: object) -> bool:
351+
return isinstance(other, InMemoryExampleDatabase) and self.data is other.data
352+
350353
def fetch(self, key: bytes) -> Iterable[bytes]:
351354
yield from self.data.get(key, ())
352355

@@ -414,6 +417,11 @@ def __init__(self, path: StrPathT) -> None:
414417
def __repr__(self) -> str:
415418
return f"DirectoryBasedExampleDatabase({self.path!r})"
416419

420+
def __eq__(self, other: object) -> bool:
421+
return (
422+
isinstance(other, DirectoryBasedExampleDatabase) and self.path == other.path
423+
)
424+
417425
def _key_path(self, key: bytes) -> Path:
418426
try:
419427
return self.keypaths[key]
@@ -630,6 +638,9 @@ def __init__(self, db: ExampleDatabase) -> None:
630638
def __repr__(self) -> str:
631639
return f"ReadOnlyDatabase({self._wrapped!r})"
632640

641+
def __eq__(self, other: object) -> bool:
642+
return isinstance(other, ReadOnlyDatabase) and self._wrapped == other._wrapped
643+
633644
def fetch(self, key: bytes) -> Iterable[bytes]:
634645
yield from self._wrapped.fetch(key)
635646

@@ -681,6 +692,11 @@ def __init__(self, *dbs: ExampleDatabase) -> None:
681692
def __repr__(self) -> str:
682693
return "MultiplexedDatabase({})".format(", ".join(map(repr, self._wrapped)))
683694

695+
def __eq__(self, other: object) -> bool:
696+
return (
697+
isinstance(other, MultiplexedDatabase) and self._wrapped == other._wrapped
698+
)
699+
684700
def fetch(self, key: bytes) -> Iterable[bytes]:
685701
seen = set()
686702
for db in self._wrapped:
@@ -828,6 +844,15 @@ def __repr__(self) -> str:
828844
f"repo={self.repo!r}, artifact_name={self.artifact_name!r})"
829845
)
830846

847+
def __eq__(self, other: object) -> bool:
848+
return (
849+
isinstance(other, GitHubArtifactDatabase)
850+
and self.owner == other.owner
851+
and self.repo == other.repo
852+
and self.artifact_name == other.artifact_name
853+
and self.path == other.path
854+
)
855+
831856
def _prepare_for_io(self) -> None:
832857
assert self._artifact is not None, "Artifact not loaded."
833858

@@ -1061,6 +1086,9 @@ def __init__(self, db: ExampleDatabase) -> None:
10611086
def __repr__(self) -> str:
10621087
return f"BackgroundWriteDatabase({self._db!r})"
10631088

1089+
def __eq__(self, other: object) -> bool:
1090+
return isinstance(other, BackgroundWriteDatabase) and self._db == other._db
1091+
10641092
def _worker(self) -> None:
10651093
while True:
10661094
method, args = self._queue.get()

hypothesis-python/src/hypothesis/extra/redis.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ def __repr__(self) -> str:
5757
f"RedisExampleDatabase({self.redis!r}, expire_after={self._expire_after!r})"
5858
)
5959

60+
def __eq__(self, other: object) -> bool:
61+
return (
62+
isinstance(other, RedisExampleDatabase)
63+
and self.redis == other.redis
64+
and self._prefix == other._prefix
65+
and self.listener_channel == other.listener_channel
66+
)
67+
6068
@contextmanager
6169
def _pipeline(
6270
self,

hypothesis-python/tests/conjecture/common.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
from random import Random
1515
from typing import Optional
1616

17-
import pytest
18-
1917
from hypothesis import HealthCheck, Phase, assume, settings, strategies as st
2018
from hypothesis.control import current_build_context, currently_in_test_context
2119
from hypothesis.internal.conjecture import engine as engine_module
@@ -104,6 +102,9 @@ def accept(f):
104102

105103

106104
def fresh_data(*, random=None, observer=None) -> ConjectureData:
105+
# support importing this file from our nose job, which doesn't have pytest
106+
import pytest
107+
107108
context = current_build_context() if currently_in_test_context() else None
108109
if context is not None and settings().backend == "crosshair":
109110
# we should reeaxmine fresh_data sometime and see if we can replace it

hypothesis-python/tests/cover/test_database_backend.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,3 +802,50 @@ def test_deprecated_example_database_memory():
802802
@checks_deprecated_behaviour
803803
def test_deprecated_example_database_no_args():
804804
ExampleDatabase()
805+
806+
807+
@pytest.mark.parametrize(
808+
"db1, db2",
809+
[
810+
(DirectoryBasedExampleDatabase("a"), DirectoryBasedExampleDatabase("a")),
811+
(
812+
MultiplexedDatabase(
813+
DirectoryBasedExampleDatabase("a"), DirectoryBasedExampleDatabase("b")
814+
),
815+
MultiplexedDatabase(
816+
DirectoryBasedExampleDatabase("a"), DirectoryBasedExampleDatabase("b")
817+
),
818+
),
819+
(
820+
ReadOnlyDatabase(DirectoryBasedExampleDatabase("a")),
821+
ReadOnlyDatabase(DirectoryBasedExampleDatabase("a")),
822+
),
823+
(
824+
GitHubArtifactDatabase("owner1", "repo1"),
825+
GitHubArtifactDatabase("owner1", "repo1"),
826+
),
827+
],
828+
)
829+
def test_database_equal(db1, db2):
830+
assert db1 == db2
831+
832+
833+
@pytest.mark.parametrize(
834+
"db1, db2",
835+
[
836+
(InMemoryExampleDatabase(), InMemoryExampleDatabase()),
837+
(InMemoryExampleDatabase(), DirectoryBasedExampleDatabase("a")),
838+
(BackgroundWriteDatabase(InMemoryExampleDatabase()), InMemoryExampleDatabase()),
839+
(DirectoryBasedExampleDatabase("a"), DirectoryBasedExampleDatabase("b")),
840+
(
841+
ReadOnlyDatabase(DirectoryBasedExampleDatabase("a")),
842+
ReadOnlyDatabase(DirectoryBasedExampleDatabase("b")),
843+
),
844+
(
845+
GitHubArtifactDatabase("owner1", "repo1"),
846+
GitHubArtifactDatabase("owner2", "repo2"),
847+
),
848+
],
849+
)
850+
def test_database_not_equal(db1, db2):
851+
assert db1 != db2

hypothesis-python/tests/cover/test_testdecorators.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
skipif_emscripten,
5151
xfail_on_crosshair,
5252
)
53+
from tests.conjecture.common import buffer_size_limit
5354

5455
# This particular test file is run under both pytest and nose, so it can't
5556
# rely on pytest-specific helpers like `pytest.raises` unless we define a
@@ -518,7 +519,7 @@ def f(v):
518519

519520

520521
def test_notes_high_overrun_rates_in_unsatisfiable_error():
521-
@given(st.binary(min_size=9000))
522+
@given(st.binary(min_size=100))
522523
@settings(
523524
suppress_health_check=[
524525
HealthCheck.data_too_large,
@@ -529,11 +530,14 @@ def test_notes_high_overrun_rates_in_unsatisfiable_error():
529530
def f(v):
530531
pass
531532

532-
with raises(
533-
Unsatisfiable,
534-
match=(
535-
r"1000 of 1000 examples were too large to finish generating; "
536-
r"try reducing the typical size of your inputs\?"
533+
with (
534+
raises(
535+
Unsatisfiable,
536+
match=(
537+
r"1000 of 1000 examples were too large to finish generating; "
538+
r"try reducing the typical size of your inputs\?"
539+
),
537540
),
541+
buffer_size_limit(10),
538542
):
539543
f()

hypothesis-python/tests/redis/test_redis_exampledatabase.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,10 @@ def listener(event):
142142
db.save(b"a", b"c")
143143
flush_messages(db)
144144
assert calls == 3
145+
146+
147+
def test_redis_equality():
148+
redis = FakeRedis()
149+
assert RedisExampleDatabase(redis) == RedisExampleDatabase(redis)
150+
# FakeRedis() != FakeRedis(), not much we can do here
151+
assert RedisExampleDatabase(FakeRedis()) != RedisExampleDatabase(FakeRedis())

0 commit comments

Comments
 (0)