-
Notifications
You must be signed in to change notification settings - Fork 491
Expand file tree
/
Copy pathconftest.py
More file actions
124 lines (104 loc) · 4.73 KB
/
conftest.py
File metadata and controls
124 lines (104 loc) · 4.73 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# Copyright 2026 Google LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Pytest configuration file.
This file sets up isolated test environments when running tests in parallel
using pytest-xdist. For every parallel worker thread that pytest spawns,
this script starts a dedicated, in-memory Google Cloud Datastore emulator.
This prevents race conditions and data corruption between parallel tests.
"""
import os
import subprocess
import time
import pytest
import requests
def pytest_configure(config):
"""Pytest hook that runs exactly once per worker process before any tests start.
If tests are being run in parallel (via pytest-xdist), this function will:
1. Identify which worker process this is (e.g., gw0, gw1).
2. Assign a unique port for its Datastore emulator.
3. Override the environment variables so Google Cloud SDK uses this local port.
4. Boot up the gcloud datastore emulator as a background process.
5. Block until the emulator is fully responsive.
""" # noqa: E501
# PYTEST_XDIST_WORKER is set by the pytest-xdist plugin (e.g., "gw0", "gw1")
worker_id = os.environ.get('PYTEST_XDIST_WORKER')
if worker_id is not None:
# Calculate a unique port for this worker based on its ID
# Base port is 15606. gw0 -> 15607, gw1 -> 15608, etc.
port = 15606 + int(worker_id.replace('gw', '')) + 1
# Override Google Cloud environment variables to force all Datastore
# traffic from this worker to its dedicated local emulator.
os.environ['DATASTORE_EMULATOR_HOST'] = f'localhost:{port}'
os.environ['DATASTORE_EMULATOR_HOST_PATH'] = (
f'localhost:{port}/datastore' # noqa: E501
)
os.environ['DATASTORE_HOST'] = f'http://localhost:{port}'
# Command to start the in-memory datastore emulator
cmd = [
'gcloud',
'beta',
'emulators',
'datastore',
'start',
'--project=cr-status-staging',
f'--host-port=:{port}',
'--no-store-on-disk', # Ensures data never writes to your actual hard drive # noqa: E501
'--use-firestore-in-datastore-mode',
]
# Start the emulator process in the background, suppressing its logs
proc = subprocess.Popen(
cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
) # noqa: E501
# Store the process reference in the pytest config so we can kill it later # noqa: E501
config.worker_emulator_proc = proc
# Poll the emulator until it returns a 200 OK, indicating it is ready to accept traffic # noqa: E501
for _ in range(30):
try:
r = requests.get(f'http://localhost:{port}/')
if r.status_code == 200:
break
except Exception:
pass
time.sleep(0.5)
def pytest_unconfigure(config):
"""Pytest hook that runs exactly once per worker process after all tests finish.
This ensures that the Java Datastore emulator processes are safely
terminated and don't remain running as orphaned background processes.
""" # noqa: E501
proc = getattr(config, 'worker_emulator_proc', None)
if proc:
proc.terminate()
proc.wait()
@pytest.fixture(autouse=True)
def reset_datastore_emulator():
"""A pytest fixture that automatically runs BEFORE every single test function.
This guarantees strict test isolation by wiping the Datastore emulator clean
before a test executes. It prevents residual data from previous tests from
causing false positives or failures.
""" # noqa: E501
worker_id = os.environ.get('PYTEST_XDIST_WORKER')
if worker_id is not None:
# We are running in parallel, wipe the worker-specific emulator
port = 15606 + int(worker_id.replace('gw', '')) + 1
try:
requests.post(f'http://localhost:{port}/reset', timeout=2)
except Exception:
pass
else:
# We are running sequentially (single process mode), wipe the default emulator # noqa: E501
host = os.environ.get('DATASTORE_EMULATOR_HOST', 'localhost:15606')
try:
requests.post(f'http://{host}/reset', timeout=2)
except Exception:
pass