Skip to content

Commit 8781447

Browse files
authored
Merge pull request #4290 from tybug/listener-test-deflake
Try making db listener tests more reliable
2 parents a8f9089 + 5a42684 commit 8781447

File tree

2 files changed

+72
-39
lines changed

2 files changed

+72
-39
lines changed

hypothesis-python/tests/cover/test_database_backend.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import shutil
1414
import tempfile
1515
import zipfile
16+
from collections import Counter
1617
from collections.abc import Iterable, Iterator
1718
from contextlib import contextmanager, nullcontext
1819
from datetime import datetime, timedelta, timezone
@@ -635,7 +636,10 @@ def move(self, k1, k2, v):
635636
def events_agree(self):
636637
if flush is not None:
637638
flush(self.db)
638-
assert self.expected_events == self.actual_events
639+
# events *generally* don't arrive out of order, but we've had
640+
# flakes reported here, especially on weirder / older machines.
641+
# see https://github.com/HypothesisWorks/hypothesis/issues/4274
642+
assert Counter(self.expected_events) == Counter(self.actual_events)
639643

640644
def teardown(self):
641645
shutil.rmtree(self.temp_dir)

hypothesis-python/tests/watchdog/test_database.py

Lines changed: 67 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,18 @@
88
# v. 2.0. If a copy of the MPL was not distributed with this file, You can
99
# obtain one at https://mozilla.org/MPL/2.0/.
1010

11+
import math
1112
import sys
1213
import time
1314
from collections import Counter
1415

15-
import pytest
16-
1716
from hypothesis import Phase, settings
1817
from hypothesis.database import (
1918
DirectoryBasedExampleDatabase,
2019
InMemoryExampleDatabase,
2120
MultiplexedDatabase,
2221
)
22+
from hypothesis.internal.reflection import get_pretty_function_description
2323

2424
from tests.cover.test_database_backend import _database_conforms_to_listener_api
2525

@@ -79,8 +79,17 @@ def listener(event):
7979
}
8080

8181

82-
# TODO flaky failure on linux
83-
@pytest.mark.xfail(strict=False)
82+
def wait_for(condition, *, timeout=1, interval=0.01):
83+
for _ in range(math.ceil(timeout / interval)):
84+
if condition():
85+
return
86+
time_sleep(interval)
87+
raise Exception(
88+
f"timing out after waiting {timeout}s for condition "
89+
f"{get_pretty_function_description(condition)}"
90+
)
91+
92+
8493
def test_database_listener_directory_explicit(tmp_path):
8594
db = DirectoryBasedExampleDatabase(tmp_path)
8695
events = []
@@ -91,8 +100,7 @@ def listener(event):
91100
db.add_listener(listener)
92101

93102
db.save(b"k1", b"v1")
94-
time_sleep(0.2)
95-
assert events == [("save", (b"k1", b"v1"))]
103+
wait_for(lambda: events == [("save", (b"k1", b"v1"))])
96104

97105
db.remove_listener(listener)
98106
db.delete(b"k1", b"v1")
@@ -103,55 +111,76 @@ def listener(event):
103111
db.add_listener(listener)
104112
db.delete(b"k1", b"v2")
105113
db.save(b"k1", b"v3")
106-
time_sleep(0.2)
107-
assert events[1:] == [
108-
("delete", (b"k1", None)),
109-
("save", (b"k1", b"v3")),
110-
]
114+
wait_for(
115+
lambda: events[1:]
116+
== [
117+
("delete", (b"k1", None)),
118+
("save", (b"k1", b"v3")),
119+
]
120+
)
111121

112122
# moving into a nonexistent key
113123
db.move(b"k1", b"k2", b"v3")
114-
time_sleep(0.2)
124+
time_sleep(0.5)
115125
# moving back into an existing key
116126
db.move(b"k2", b"k1", b"v3")
117-
time_sleep(0.2)
127+
time_sleep(0.5)
118128

119129
if sys.platform.startswith("darwin"):
120-
expected = [
130+
assert events[3:] == [
121131
("delete", (b"k1", b"v3")),
122132
("save", (b"k2", b"v3")),
123133
("delete", (b"k2", b"v3")),
124134
("save", (b"k1", b"v3")),
125-
]
135+
], str(events[3:])
126136
elif sys.platform.startswith("win"):
127-
# windows fires a save/delete event for our particular moves
128-
# at the os-level instead of a move (or watchdog just isn't picking
129-
# up on it correctly on windows). This means we don't get the exact
130-
# deleted values for us to broadcast.
131-
expected = [
137+
# watchdog fires save/delete events instead of move events on windows.
138+
# This means we don't broadcast the exact deleted value.
139+
assert events[3:] == [
132140
("delete", (b"k1", None)),
133141
("save", (b"k2", b"v3")),
134142
("delete", (b"k2", None)),
135143
("save", (b"k1", b"v3")),
136-
]
144+
], str(events[3:])
137145
elif sys.platform.startswith("linux"):
138-
expected = [
139-
# as far as I can tell, linux fires both a save and a move event
140-
# for the first move event. I don't know if this is our bug or an os
141-
# implementation detail. I am leaning towards the latter, since other
142-
# os' are fine.
143-
("save", (b"k2", b"v3")),
144-
# first move event is normal...
145-
("delete", (b"k1", b"v3")),
146-
("save", (b"k2", b"v3")),
147-
# ...but the second move event gets picked up by watchdog as an individual
148-
# save/delete, not a move. I'm not sure why. Therefore we don't have
149-
# the delete value present; and the ordering is also different from
150-
# normal.
151-
("save", (b"k1", b"v3")),
152-
("delete", (b"k2", None)),
153-
]
146+
# move #1
147+
assert ("save", (b"k2", b"v3")) in events
148+
# sometimes watchdog fires a move event (= save + delete with value),
149+
# and other times it fires separate save and delete events (= delete with
150+
# no value). I think this is due to particulars of what happens when
151+
# a new directory gets created very close to the time when a file is
152+
# saved to that directory.
153+
assert any(("delete", (b"k1", val)) in events for val in [b"v3", None])
154+
155+
# move #2
156+
assert ("save", (b"k1", b"v3")) in events
157+
assert any(("delete", (b"k2", val)) in events for val in [b"v3", None])
154158
else:
155159
raise NotImplementedError(f"unknown platform {sys.platform}")
156160

157-
assert events[3:] == expected, str(events[3:])
161+
162+
def test_database_listener_directory_move(tmp_path):
163+
db = DirectoryBasedExampleDatabase(tmp_path)
164+
events = []
165+
166+
def listener(event):
167+
events.append(event)
168+
169+
# make sure both keys exist and that v1 exists in k1 and not k2
170+
db.save(b"k1", b"v1")
171+
db.save(b"k2", b"v_unrelated")
172+
173+
time_sleep(0.1)
174+
db.add_listener(listener)
175+
time_sleep(0.1)
176+
177+
db.move(b"k1", b"k2", b"v1")
178+
# events might arrive in either order
179+
wait_for(
180+
lambda: set(events)
181+
== {
182+
("save", (b"k2", b"v1")),
183+
# windows doesn't fire move events, so value is None
184+
("delete", (b"k1", None if sys.platform.startswith("win") else b"v1")),
185+
}
186+
)

0 commit comments

Comments
 (0)