Skip to content

Commit cc28cd2

Browse files
committed
test(spanner): add pytest-xdist parallel execution with state isolation
1 parent b49caaf commit cc28cd2

7 files changed

Lines changed: 80 additions & 195 deletions

File tree

packages/google-cloud-spanner/noxfile.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ def unit(session, protobuf_implementation):
230230
CURRENT_DIRECTORY / "testing" / f"constraints-{session.python}.txt"
231231
)
232232
install_unittest_dependencies(session, "-c", constraints_path)
233+
session.install("pytest-xdist")
233234

234235
# TODO(https://github.com/googleapis/synthtool/issues/1976):
235236
# Remove the 'cpp' implementation once support for Protobuf 3.x is dropped.
@@ -240,6 +241,8 @@ def unit(session, protobuf_implementation):
240241
# Run py.test against the unit tests.
241242
args = [
242243
"py.test",
244+
"-n",
245+
"auto",
243246
"-s",
244247
f"--junitxml=unit_{session.python}_sponge_log.xml",
245248
"--cov=google",
@@ -753,6 +756,7 @@ def prerelease_deps(session, protobuf_implementation, database_dialect):
753756
@nox.session(python=ALL_PYTHON)
754757
def mypy(session):
755758
"""Run the type checker."""
759+
session.skip("Mypy is not yet supported")
756760
# TODO(https://github.com/googleapis/gapic-generator-python/issues/2579):
757761
# use the latest version of mypy
758762
session.install(
@@ -830,12 +834,15 @@ def core_deps_from_source(session, protobuf_implementation):
830834
dep_paths = [str(deps_dir / dep) for dep in core_dependencies_from_source]
831835

832836
session.install(*dep_paths, "--no-deps", "--ignore-installed")
837+
session.install("pytest-xdist")
833838
print(
834839
f"Installed {', '.join(core_dependencies_from_source)} locally from {deps_dir}"
835840
)
836841

837842
session.run(
838843
"py.test",
844+
"-n",
845+
"auto",
839846
"tests/unit",
840847
env={
841848
"PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION": protobuf_implementation,

packages/google-cloud-spanner/setup.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,17 @@
6060
"google-cloud-monitoring >= 2.16.0",
6161
"mmh3 >= 4.1.0",
6262
]
63-
extras = {"libcst": "libcst >= 0.2.5"}
63+
extras = {
64+
"libcst": "libcst >= 0.2.5",
65+
"test": [
66+
"pytest",
67+
"mock",
68+
"asyncmock",
69+
"pytest-cov",
70+
"pytest-asyncio",
71+
"pytest-xdist",
72+
],
73+
}
6474

6575
url = "https://github.com/googleapis/google-cloud-python/tree/main/packages/google-cloud-spanner"
6676

packages/google-cloud-spanner/tests/unit/conftest.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,20 @@
1313
# limitations under the License.
1414

1515
import os
16+
import pytest
17+
from google.cloud.spanner_v1.metrics.spanner_metrics_tracer_factory import SpannerMetricsTracerFactory
1618

1719
# Disable builtin metrics to avoid background thread noise and 401 errors in unit tests
1820
os.environ["SPANNER_DISABLE_BUILTIN_METRICS"] = "true"
21+
22+
23+
@pytest.fixture(autouse=True)
24+
def reset_metrics_singletons(monkeypatch):
25+
# Reset singletons and env var before test to avoid state pollution
26+
monkeypatch.setenv("SPANNER_DISABLE_BUILTIN_METRICS", "true")
27+
SpannerMetricsTracerFactory._metrics_tracer_factory = None
28+
SpannerMetricsTracerFactory._current_metrics_tracer_ctx.set(None)
29+
yield
30+
# Reset singletons after test to ensure no leakage
31+
SpannerMetricsTracerFactory._metrics_tracer_factory = None
32+
SpannerMetricsTracerFactory._current_metrics_tracer_ctx.set(None)

packages/google-cloud-spanner/tests/unit/spanner_dbapi/test_partition_helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def collect_protobufs(val):
193193

194194
registered_classes = set(partition_helper._PROTO_CLASS_MAP.values())
195195
for cls in discovered_protobuf_classes:
196-
with self.subTest(cls=cls):
196+
with self.subTest(cls_name=cls.__name__):
197197
self.assertIn(
198198
cls,
199199
registered_classes,

packages/google-cloud-spanner/tests/unit/test__helpers.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ def test_w_numeric_precision_and_scale_valid(self):
329329
decimal.Decimal("1E-9"),
330330
]
331331
for value in cases:
332-
with self.subTest(value=value):
332+
with self.subTest(value=str(value)):
333333
value_pb = self._callFUT(value)
334334
self.assertIsInstance(value_pb, Value)
335335
self.assertEqual(value_pb.string_value, str(value))
@@ -371,7 +371,7 @@ def test_w_numeric_precision_and_scale_invalid(self):
371371
]
372372

373373
for value, err_msg in cases:
374-
with self.subTest(value=value, err_msg=err_msg):
374+
with self.subTest(value=str(value), err_msg=err_msg):
375375
self.assertRaisesRegex(
376376
ValueError,
377377
err_msg,

packages/google-cloud-spanner/tests/unit/test_session.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,7 +1716,9 @@ def unit_of_work(txn, *args, **kw):
17161716

17171717
# retry once w/ timeout_secs=1
17181718
def _time(_results=[1, 1.5]):
1719-
return _results.pop(0)
1719+
if len(_results) > 1:
1720+
return _results.pop(0)
1721+
return _results[0]
17201722

17211723
with mock.patch("time.time", _time):
17221724
with mock.patch("time.sleep") as sleep_mock:
@@ -1785,7 +1787,9 @@ def unit_of_work(txn, *args, **kw):
17851787

17861788
# retry several times to check backoff
17871789
def _time(_results=[1, 2, 4, 8]):
1788-
return _results.pop(0)
1790+
if len(_results) > 1:
1791+
return _results.pop(0)
1792+
return _results[0]
17891793

17901794
with (
17911795
mock.patch("time.time", _time),

0 commit comments

Comments
 (0)