Skip to content

Commit 25455bf

Browse files
ohmayrchalmerlowe
authored andcommitted
chore(firestore): fix firestore flaky tests (#17439)
Fixes: #17428 Additional change: using 10 workers instead of `auto` seems to be a sweet spot.
1 parent b49322b commit 25455bf

4 files changed

Lines changed: 48 additions & 85 deletions

File tree

.librarian/generator-input/client-post-processing/firestore-integration.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ replacements:
644644
session.run(
645645
"py.test",
646646
"-n",
647-
"auto",
647+
"10",
648648
"--quiet",
649649
f"--junitxml=system_{session.python}_sponge_log.xml",
650650
system_test_path,
@@ -654,7 +654,7 @@ replacements:
654654
session.run(
655655
"py.test",
656656
"-n",
657-
"auto",
657+
"10",
658658
"--quiet",
659659
f"--junitxml=system_{session.python}_sponge_log.xml",
660660
system_test_folder_path,

packages/google-cloud-firestore/noxfile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ def system(session):
403403
session.run(
404404
"py.test",
405405
"-n",
406-
"auto",
406+
"10",
407407
"--quiet",
408408
f"--junitxml=system_{session.python}_sponge_log.xml",
409409
system_test_path,
@@ -413,7 +413,7 @@ def system(session):
413413
session.run(
414414
"py.test",
415415
"-n",
416-
"auto",
416+
"10",
417417
"--quiet",
418418
f"--junitxml=system_{session.python}_sponge_log.xml",
419419
system_test_folder_path,

packages/google-cloud-firestore/tests/system/test_system.py

Lines changed: 42 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import concurrent.futures
1516
import datetime
1617
import itertools
1718
import math
1819
import operator
19-
from time import sleep
2020
from typing import Callable, Dict, List, Optional
2121

2222
import google.auth
@@ -80,8 +80,8 @@ def cleanup():
8080
operations = []
8181
yield operations.append
8282

83-
for operation in operations:
84-
operation()
83+
with concurrent.futures.ThreadPoolExecutor() as executor:
84+
list(executor.map(lambda op: op(), operations))
8585

8686

8787
@pytest.fixture
@@ -389,15 +389,18 @@ def test_create_document_w_vector(client, cleanup, database):
389389
for v in client.collection(collection_id).order_by("embedding").get()
390390
] == [data3, data1, data2]
391391

392+
vector_query_future = concurrent.futures.Future()
393+
392394
def on_snapshot(docs, changes, read_time):
393395
on_snapshot.results += docs
396+
if len(on_snapshot.results) >= 3 and not vector_query_future.done():
397+
vector_query_future.set_result(True)
394398

395399
on_snapshot.results = []
396400
client.collection(collection_id).order_by("embedding").on_snapshot(on_snapshot)
397401

398-
# delay here so initial on_snapshot occurs and isn't combined with set
399-
sleep(1)
400-
assert [v.to_dict() for v in on_snapshot.results] == [data3, data1, data2]
402+
vector_query_future.result(timeout=60.0)
403+
assert [v.to_dict() for v in on_snapshot.results[:3]] == [data3, data1, data2]
401404

402405

403406
@pytest.mark.skipif(FIRESTORE_EMULATOR, reason="Require index and seed data")
@@ -2336,15 +2339,13 @@ def test_watch_document(client, cleanup, database):
23362339
doc_ref.set({"first": "Jane", "last": "Doe", "born": 1900})
23372340
cleanup(doc_ref.delete)
23382341

2339-
# TODO(https://github.com/googleapis/google-cloud-python/issues/17428):
2340-
# Investigate why these sleep/polling delays are needed for listener tests.
2341-
# Having arbitrary delays is fragile and can lead to flakiness.
2342-
# Explore event-driven synchronization.
2343-
sleep(0.2)
2344-
23452342
# Setup listener
2343+
ada_future = concurrent.futures.Future()
2344+
23462345
def on_snapshot(docs, changes, read_time):
23472346
on_snapshot.called_count += 1
2347+
if docs and docs[0].get("first") == "Ada" and not ada_future.done():
2348+
ada_future.set_result(True)
23482349

23492350
on_snapshot.called_count = 0
23502351

@@ -2353,12 +2354,8 @@ def on_snapshot(docs, changes, read_time):
23532354
# Alter document
23542355
doc_ref.set({"first": "Ada", "last": "Lovelace", "born": 1815})
23552356

2356-
sleep(0.2)
2357-
2358-
for _ in range(50):
2359-
if on_snapshot.called_count > 0:
2360-
break
2361-
sleep(0.2)
2357+
# Wait for the event-driven callback to resolve the future
2358+
ada_future.result(timeout=60.0)
23622359

23632360
if on_snapshot.called_count not in (1, 2):
23642361
raise AssertionError(
@@ -2378,34 +2375,18 @@ def test_watch_collection(client, cleanup, database):
23782375
cleanup(doc_ref.delete)
23792376

23802377
# Setup listener
2378+
born_1815_future = concurrent.futures.Future()
2379+
23812380
def on_snapshot(docs, changes, read_time):
2382-
on_snapshot.called_count += 1
23832381
for doc in [doc for doc in docs if doc.id == doc_ref.id]:
2384-
on_snapshot.born = doc.get("born")
2385-
2386-
on_snapshot.called_count = 0
2387-
on_snapshot.born = 0
2382+
if doc.get("born") == 1815 and not born_1815_future.done():
2383+
born_1815_future.set_result(True)
23882384

23892385
collection_ref.on_snapshot(on_snapshot)
23902386

2391-
# TODO(https://github.com/googleapis/google-cloud-python/issues/17428):
2392-
# Investigate why these sleep/polling delays are needed for listener tests.
2393-
# Having arbitrary delays is fragile and can lead to flakiness.
2394-
# Explore event-driven synchronization.
2395-
# delay here so initial on_snapshot occurs and isn't combined with set
2396-
sleep(0.2)
2397-
23982387
doc_ref.set({"first": "Ada", "last": "Lovelace", "born": 1815})
23992388

2400-
for _ in range(50):
2401-
if on_snapshot.born == 1815:
2402-
break
2403-
sleep(0.2)
2404-
2405-
if on_snapshot.born != 1815:
2406-
raise AssertionError(
2407-
"Expected the last document update to update born: " + str(on_snapshot.born)
2408-
)
2389+
born_1815_future.result(timeout=60.0)
24092390

24102391

24112392
@pytest.mark.parametrize("database", TEST_DATABASES, indirect=True)
@@ -2419,20 +2400,21 @@ def test_watch_query(client, cleanup, database):
24192400
doc_ref.set({"first": "Jane", "last": "Doe", "born": 1900})
24202401
cleanup(doc_ref.delete)
24212402

2422-
# TODO(https://github.com/googleapis/google-cloud-python/issues/17428):
2423-
# Investigate why these sleep/polling delays are needed for listener tests.
2424-
# Having arbitrary delays is fragile and can lead to flakiness.
2425-
# Explore event-driven synchronization.
2426-
sleep(0.2)
2427-
24282403
# Setup listener
2404+
one_doc_future = concurrent.futures.Future()
2405+
24292406
def on_snapshot(docs, changes, read_time):
24302407
on_snapshot.called_count += 1
24312408

2432-
# A snapshot should return the same thing as if a query ran now.
2433-
query_ran_query = collection_ref.where(filter=FieldFilter("first", "==", "Ada"))
2434-
query_ran = query_ran_query.stream()
2435-
assert len(docs) == len([i for i in query_ran])
2409+
if docs:
2410+
# A snapshot should return the same thing as if a query ran now.
2411+
query_ran_query = collection_ref.where(
2412+
filter=FieldFilter("first", "==", "Ada")
2413+
)
2414+
query_ran = query_ran_query.stream()
2415+
assert len(docs) == len([i for i in query_ran])
2416+
if not one_doc_future.done():
2417+
one_doc_future.set_result(True)
24362418

24372419
on_snapshot.called_count = 0
24382420

@@ -2441,14 +2423,11 @@ def on_snapshot(docs, changes, read_time):
24412423
# Alter document
24422424
doc_ref.set({"first": "Ada", "last": "Lovelace", "born": 1815})
24432425

2444-
for _ in range(50):
2445-
if on_snapshot.called_count == 1:
2446-
return
2447-
sleep(0.2)
2426+
one_doc_future.result(timeout=60.0)
24482427

2449-
if on_snapshot.called_count != 1:
2428+
if on_snapshot.called_count not in (1, 2):
24502429
raise AssertionError(
2451-
"Failed to get exactly one document change: count: "
2430+
"Failed to get expected document change count: "
24522431
+ str(on_snapshot.called_count)
24532432
)
24542433

@@ -2788,6 +2767,8 @@ def test_watch_query_order(client, cleanup, database):
27882767
)
27892768

27902769
# Setup listener
2770+
five_docs_future = concurrent.futures.Future()
2771+
27912772
def on_snapshot(docs, changes, read_time):
27922773
try:
27932774
docs = [i for i in docs if i.id.endswith(UNIQUE_RESOURCE_ID)]
@@ -2808,22 +2789,14 @@ def on_snapshot(docs, changes, read_time):
28082789
assert snapshot.get("born") == query.get("born"), (
28092790
"expect the sort order to match, born"
28102791
)
2811-
on_snapshot.called_count += 1
2812-
on_snapshot.last_doc_count = len(docs)
2792+
if not five_docs_future.done():
2793+
five_docs_future.set_result(True)
28132794
except Exception as e:
2814-
on_snapshot.failed = e
2795+
if not five_docs_future.done():
2796+
five_docs_future.set_exception(e)
28152797

2816-
on_snapshot.called_count = 0
2817-
on_snapshot.last_doc_count = 0
2818-
on_snapshot.failed = None
28192798
query_ref.on_snapshot(on_snapshot)
28202799

2821-
# TODO(https://github.com/googleapis/google-cloud-python/issues/17428):
2822-
# Investigate why these sleep/polling delays are needed for listener tests.
2823-
# Having arbitrary delays is fragile and can lead to flakiness.
2824-
# Explore event-driven synchronization.
2825-
sleep(0.2)
2826-
28272800
doc_ref1.set({"first": "Ada", "last": "Lovelace", "born": 1815})
28282801
cleanup(doc_ref1.delete)
28292802

@@ -2839,18 +2812,8 @@ def on_snapshot(docs, changes, read_time):
28392812
doc_ref5.set({"first": "Ada", "last": "lovelace", "born": 1815})
28402813
cleanup(doc_ref5.delete)
28412814

2842-
for _ in range(50):
2843-
if on_snapshot.last_doc_count == 5:
2844-
break
2845-
sleep(0.2)
2846-
2847-
if on_snapshot.failed:
2848-
raise on_snapshot.failed
2849-
2850-
if on_snapshot.last_doc_count != 5:
2851-
raise AssertionError(
2852-
"5 docs expected in snapshot method " + str(on_snapshot.last_doc_count)
2853-
)
2815+
# Wait for the future to be resolved
2816+
five_docs_future.result(timeout=60.0)
28542817

28552818

28562819
@pytest.mark.parametrize("database", TEST_DATABASES_W_ENTERPRISE, indirect=True)

packages/google-cloud-firestore/tests/system/test_system_async.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,8 @@ async def cleanup():
160160
operations = []
161161
yield operations.append
162162

163-
for operation in operations:
164-
await operation()
163+
if operations:
164+
await asyncio.gather(*[operation() for operation in operations])
165165

166166

167167
@pytest.fixture

0 commit comments

Comments
 (0)