Skip to content

Commit 63ff2c6

Browse files
authored
tests: ensure systests pass under emulator in a clean environment (#464)
Add Github workflow to run systests under emulator. Avoid default credential/project lookup under emulator Closes #463.
1 parent 0c6adef commit 63ff2c6

File tree

7 files changed

+188
-25
lines changed

7 files changed

+188
-25
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: "Run systests on emulator"
2+
on:
3+
pull_request:
4+
branches:
5+
- main
6+
7+
jobs:
8+
9+
run-systests:
10+
runs-on: ubuntu-20.04
11+
12+
steps:
13+
14+
- name: Checkout
15+
uses: actions/checkout@v2
16+
17+
- name: Setup Python
18+
uses: actions/setup-python@v2
19+
with:
20+
python-version: '3.7'
21+
22+
- name: Setup GCloud SDK
23+
uses: google-github-actions/setup-gcloud@v0.2.1
24+
25+
- name: Install / run Nox
26+
run: |
27+
python -m pip install --upgrade setuptools pip
28+
python -m pip install nox
29+
nox -s system_emulated

packages/google-cloud-firestore/google/cloud/firestore_v1/base_client.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import os
2828
import grpc # type: ignore
2929

30+
from google.auth.credentials import AnonymousCredentials
3031
import google.api_core.client_options # type: ignore
3132
import google.api_core.path_template # type: ignore
3233
from google.api_core import retry as retries # type: ignore
@@ -61,6 +62,7 @@
6162

6263
DEFAULT_DATABASE = "(default)"
6364
"""str: The default database used in a :class:`~google.cloud.firestore_v1.client.Client`."""
65+
_DEFAULT_EMULATOR_PROJECT = "google-cloud-firestore-emulator"
6466
_BAD_OPTION_ERR = (
6567
"Exactly one of ``last_update_time`` or ``exists`` " "must be provided."
6668
)
@@ -122,6 +124,14 @@ def __init__(
122124
# NOTE: This API has no use for the _http argument, but sending it
123125
# will have no impact since the _http() @property only lazily
124126
# creates a working HTTP object.
127+
self._emulator_host = os.getenv(_FIRESTORE_EMULATOR_HOST)
128+
129+
if self._emulator_host is not None:
130+
if credentials is None:
131+
credentials = AnonymousCredentials()
132+
if project is None:
133+
project = _DEFAULT_EMULATOR_PROJECT
134+
125135
super(BaseClient, self).__init__(
126136
project=project,
127137
credentials=credentials,
@@ -137,7 +147,6 @@ def __init__(
137147
self._client_options = client_options
138148

139149
self._database = database
140-
self._emulator_host = os.getenv(_FIRESTORE_EMULATOR_HOST)
141150

142151
def _firestore_api_helper(self, transport, client_class, client_module) -> Any:
143152
"""Lazy-loading getter GAPIC Firestore API.

packages/google-cloud-firestore/noxfile.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
# 'docfx' is excluded since it only needs to run in 'docs-presubmit'
3838
nox.options.sessions = [
3939
"unit",
40+
"system_emulated",
4041
"system",
4142
"cover",
4243
"lint",
@@ -128,6 +129,44 @@ def unit(session):
128129
default(session)
129130

130131

132+
@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS)
133+
def system_emulated(session):
134+
import subprocess
135+
import signal
136+
137+
try:
138+
subprocess.call(["gcloud", "--version"])
139+
except OSError:
140+
session.skip("gcloud not found but required for emulator support")
141+
142+
# Currently, CI/CD doesn't have beta component of gcloud.
143+
subprocess.call(
144+
["gcloud", "components", "install", "beta", "cloud-firestore-emulator",]
145+
)
146+
147+
hostport = "localhost:8789"
148+
session.env["FIRESTORE_EMULATOR_HOST"] = hostport
149+
150+
p = subprocess.Popen(
151+
[
152+
"gcloud",
153+
"--quiet",
154+
"beta",
155+
"emulators",
156+
"firestore",
157+
"start",
158+
"--host-port",
159+
hostport,
160+
]
161+
)
162+
163+
try:
164+
system(session)
165+
finally:
166+
# Stop Emulator
167+
os.killpg(os.getpgid(p.pid), signal.SIGKILL)
168+
169+
131170
@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS)
132171
def system(session):
133172
"""Run the system test suite."""

packages/google-cloud-firestore/owlbot.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,76 @@ def update_fixup_scripts(library):
143143

144144
s.move(templated_files)
145145

146+
# ----------------------------------------------------------------------------
147+
# Customize noxfile.py
148+
# ----------------------------------------------------------------------------
149+
150+
def place_before(path, text, *before_text, escape=None):
151+
replacement = "\n".join(before_text) + "\n" + text
152+
if escape:
153+
for c in escape:
154+
text = text.replace(c, '\\' + c)
155+
s.replace([path], text, replacement)
156+
157+
system_emulated_session = """
158+
@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS)
159+
def system_emulated(session):
160+
import subprocess
161+
import signal
162+
163+
try:
164+
subprocess.call(["gcloud", "--version"])
165+
except OSError:
166+
session.skip("gcloud not found but required for emulator support")
167+
168+
# Currently, CI/CD doesn't have beta component of gcloud.
169+
subprocess.call(
170+
["gcloud", "components", "install", "beta", "cloud-firestore-emulator",]
171+
)
172+
173+
hostport = "localhost:8789"
174+
session.env["FIRESTORE_EMULATOR_HOST"] = hostport
175+
176+
p = subprocess.Popen(
177+
[
178+
"gcloud",
179+
"--quiet",
180+
"beta",
181+
"emulators",
182+
"firestore",
183+
"start",
184+
"--host-port",
185+
hostport,
186+
]
187+
)
188+
189+
try:
190+
system(session)
191+
finally:
192+
# Stop Emulator
193+
os.killpg(os.getpgid(p.pid), signal.SIGKILL)
194+
195+
"""
196+
197+
place_before(
198+
"noxfile.py",
199+
"@nox.session(python=SYSTEM_TEST_PYTHON_VERSIONS)\n"
200+
"def system(session):",
201+
system_emulated_session,
202+
escape="()"
203+
)
204+
205+
# add system_emulated nox session
206+
s.replace("noxfile.py",
207+
"""nox.options.sessions = \[
208+
"unit",
209+
"system",""",
210+
"""nox.options.sessions = [
211+
"unit",
212+
"system_emulated",
213+
"system",""",
214+
)
215+
146216
s.replace(
147217
"noxfile.py",
148218
"""\"--quiet\",

packages/google-cloud-firestore/tests/unit/v1/test_async_client.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,6 @@ def test_constructor(self):
5151
self.assertEqual(client._credentials, credentials)
5252
self.assertEqual(client._database, DEFAULT_DATABASE)
5353
self.assertIs(client._client_info, _CLIENT_INFO)
54-
self.assertIsNone(client._emulator_host)
55-
56-
def test_constructor_with_emulator_host(self):
57-
from google.cloud.firestore_v1.base_client import _FIRESTORE_EMULATOR_HOST
58-
59-
credentials = _make_credentials()
60-
emulator_host = "localhost:8081"
61-
with mock.patch("os.getenv") as getenv:
62-
getenv.return_value = emulator_host
63-
client = self._make_one(project=self.PROJECT, credentials=credentials)
64-
self.assertEqual(client._emulator_host, emulator_host)
65-
getenv.assert_called_once_with(_FIRESTORE_EMULATOR_HOST)
6654

6755
def test_constructor_explicit(self):
6856
from google.api_core.client_options import ClientOptions

packages/google-cloud-firestore/tests/unit/v1/test_base_client.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,46 @@ def _make_default_one(self):
3737
credentials = _make_credentials()
3838
return self._make_one(project=self.PROJECT, credentials=credentials)
3939

40+
def test_constructor_with_emulator_host_defaults(self):
41+
from google.auth.credentials import AnonymousCredentials
42+
from google.cloud.firestore_v1.base_client import _DEFAULT_EMULATOR_PROJECT
43+
from google.cloud.firestore_v1.base_client import _FIRESTORE_EMULATOR_HOST
44+
45+
emulator_host = "localhost:8081"
46+
47+
with mock.patch("os.environ", {_FIRESTORE_EMULATOR_HOST: emulator_host}):
48+
client = self._make_one()
49+
50+
self.assertEqual(client._emulator_host, emulator_host)
51+
self.assertIsInstance(client._credentials, AnonymousCredentials)
52+
self.assertEqual(client.project, _DEFAULT_EMULATOR_PROJECT)
53+
54+
def test_constructor_with_emulator_host_w_project(self):
55+
from google.auth.credentials import AnonymousCredentials
56+
from google.cloud.firestore_v1.base_client import _FIRESTORE_EMULATOR_HOST
57+
58+
emulator_host = "localhost:8081"
59+
60+
with mock.patch("os.environ", {_FIRESTORE_EMULATOR_HOST: emulator_host}):
61+
client = self._make_one(project=self.PROJECT)
62+
63+
self.assertEqual(client._emulator_host, emulator_host)
64+
self.assertIsInstance(client._credentials, AnonymousCredentials)
65+
66+
def test_constructor_with_emulator_host_w_creds(self):
67+
from google.cloud.firestore_v1.base_client import _DEFAULT_EMULATOR_PROJECT
68+
from google.cloud.firestore_v1.base_client import _FIRESTORE_EMULATOR_HOST
69+
70+
credentials = _make_credentials()
71+
emulator_host = "localhost:8081"
72+
73+
with mock.patch("os.environ", {_FIRESTORE_EMULATOR_HOST: emulator_host}):
74+
client = self._make_one(credentials=credentials)
75+
76+
self.assertEqual(client._emulator_host, emulator_host)
77+
self.assertIs(client._credentials, credentials)
78+
self.assertEqual(client.project, _DEFAULT_EMULATOR_PROJECT)
79+
4080
@mock.patch(
4181
"google.cloud.firestore_v1.services.firestore.client.FirestoreClient",
4282
autospec=True,

packages/google-cloud-firestore/tests/unit/v1/test_client.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,6 @@ def test_constructor(self):
4949
self.assertEqual(client._credentials, credentials)
5050
self.assertEqual(client._database, DEFAULT_DATABASE)
5151
self.assertIs(client._client_info, _CLIENT_INFO)
52-
self.assertIsNone(client._emulator_host)
53-
54-
def test_constructor_with_emulator_host(self):
55-
from google.cloud.firestore_v1.base_client import _FIRESTORE_EMULATOR_HOST
56-
57-
credentials = _make_credentials()
58-
emulator_host = "localhost:8081"
59-
with mock.patch("os.getenv") as getenv:
60-
getenv.return_value = emulator_host
61-
client = self._make_one(project=self.PROJECT, credentials=credentials)
62-
self.assertEqual(client._emulator_host, emulator_host)
63-
getenv.assert_called_once_with(_FIRESTORE_EMULATOR_HOST)
6452

6553
def test_constructor_explicit(self):
6654
from google.api_core.client_options import ClientOptions

0 commit comments

Comments
 (0)