Skip to content

Commit 3f07f69

Browse files
chore(firestore): skip pipeline verification tests outside enterprise db (#16523)
Fixing problems with system tests (I'm not entirely sure why they started popping up now) - tests attempt to run pipeline verification tests on all test dbs. Pipelines mode is only supported on enterprise databases, so we should skip the test on unsupported environments - added unique ids to prevent parallel tests from interfering - [improvement] use pytest subtests to separate pipeline validation from rest of test Diff is more complex than it should be, because adding subtests changed indentation --------- Co-authored-by: Chalmer Lowe <chalmerlowe@google.com>
1 parent 2a3458c commit 3f07f69

File tree

5 files changed

+243
-151
lines changed

5 files changed

+243
-151
lines changed

packages/google-cloud-firestore/noxfile.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
SYSTEM_TEST_PYTHON_VERSIONS: List[str] = ALL_PYTHON
7272
SYSTEM_TEST_STANDARD_DEPENDENCIES = [
7373
"mock",
74-
"pytest",
74+
"pytest>9.0",
7575
"google-cloud-testutils",
7676
]
7777
SYSTEM_TEST_EXTERNAL_DEPENDENCIES: List[str] = [

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import os
22
import re
3+
import time
4+
import datetime
5+
import contextlib
36

47
from test_utils.system import EmulatorCreds, unique_resource_id
58

69
from google.cloud.firestore_v1.base_client import _FIRESTORE_EMULATOR_HOST
10+
from google.cloud.firestore import SERVER_TIMESTAMP
11+
from google.api_core.exceptions import AlreadyExists
712

813
FIRESTORE_CREDS = os.environ.get("FIRESTORE_APPLICATION_CREDENTIALS")
914
FIRESTORE_PROJECT = os.environ.get("GCLOUD_PROJECT")
@@ -20,3 +25,41 @@
2025
# run all tests against default database, and a named database
2126
TEST_DATABASES = [None, FIRESTORE_OTHER_DB]
2227
TEST_DATABASES_W_ENTERPRISE = TEST_DATABASES + [FIRESTORE_ENTERPRISE_DB]
28+
29+
30+
@contextlib.contextmanager
31+
def system_test_lock(client, lock_name="system_test_lock", max_wait_minutes=65):
32+
"""
33+
Acquires a distributed lock using a Firestore document to prevent concurrent system tests.
34+
"""
35+
lock_ref = client.collection("system_tests").document(lock_name)
36+
start_time = time.time()
37+
max_wait_time = max_wait_minutes * 60
38+
39+
while time.time() - start_time < max_wait_time:
40+
try:
41+
lock_ref.create({"created_at": SERVER_TIMESTAMP})
42+
break # Lock acquired
43+
except AlreadyExists:
44+
lock_doc = lock_ref.get()
45+
if lock_doc.exists:
46+
created_at = lock_doc.to_dict().get("created_at")
47+
if created_at:
48+
now = datetime.datetime.now(datetime.timezone.utc)
49+
age = (now - created_at).total_seconds()
50+
if age > 3600:
51+
print(f"Lock is expired (age: {age}s). Stealing lock.")
52+
lock_ref.delete()
53+
continue
54+
else:
55+
print(
56+
f"Waiting for {lock_name}. Lock is {age:.0f}s old. Sleeping for 15s..."
57+
)
58+
time.sleep(15)
59+
else:
60+
raise TimeoutError(f"Timed out waiting for {lock_name}")
61+
62+
try:
63+
yield lock_ref
64+
finally:
65+
lock_ref.delete()

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

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import yaml
2727
from google.api_core.exceptions import GoogleAPIError
2828
from google.protobuf.json_format import MessageToDict
29-
from test__helpers import FIRESTORE_EMULATOR, FIRESTORE_ENTERPRISE_DB
29+
from test__helpers import FIRESTORE_EMULATOR, FIRESTORE_ENTERPRISE_DB, system_test_lock
3030

3131
from google.cloud.firestore import AsyncClient, Client
3232
from google.cloud.firestore_v1 import pipeline_expressions
@@ -364,21 +364,23 @@ def client():
364364
client = Client(project=FIRESTORE_PROJECT, database=FIRESTORE_ENTERPRISE_DB)
365365
data = yaml_loader("data", attach_file_name=False)
366366
to_delete = []
367-
try:
368-
# setup data
369-
batch = client.batch()
370-
for collection_name, documents in data.items():
371-
collection_ref = client.collection(collection_name)
372-
for document_id, document_data in documents.items():
373-
document_ref = collection_ref.document(document_id)
374-
to_delete.append(document_ref)
375-
batch.set(document_ref, _parse_yaml_types(document_data))
376-
batch.commit()
377-
yield client
378-
finally:
379-
# clear data
380-
for document_ref in to_delete:
381-
document_ref.delete()
367+
368+
with system_test_lock(client, lock_name="pipeline_e2e_lock"):
369+
try:
370+
# setup data
371+
batch = client.batch()
372+
for collection_name, documents in data.items():
373+
collection_ref = client.collection(collection_name)
374+
for document_id, document_data in documents.items():
375+
document_ref = collection_ref.document(document_id)
376+
to_delete.append(document_ref)
377+
batch.set(document_ref, _parse_yaml_types(document_data))
378+
batch.commit()
379+
yield client
380+
finally:
381+
# clear data
382+
for document_ref in to_delete:
383+
document_ref.delete()
382384

383385

384386
@pytest.fixture(scope="module")

0 commit comments

Comments
 (0)