Skip to content

Commit 03d4104

Browse files
committed
Merge pull request #1369 from dhermes/ds-emulator-system-test
Adding support for datastore emulator in system test.
2 parents 1ec6d63 + fd084d5 commit 03d4104

File tree

5 files changed

+132
-63
lines changed

5 files changed

+132
-63
lines changed

CONTRIBUTING.rst

Lines changed: 53 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ Contributing
66
#. Make sure that your commit messages clearly describe the changes.
77
#. Send a pull request.
88

9-
Here are some guidelines for hacking on gcloud-python.
9+
Here are some guidelines for hacking on ``gcloud-python``.
1010

1111
Using a Development Checkout
1212
----------------------------
1313

14-
You'll have to create a development environment to hack on gcloud-python,
14+
You'll have to create a development environment to hack on ``gcloud-python``,
1515
using a Git checkout:
1616

17-
- While logged into your GitHub account, navigate to the gcloud-python repo on
18-
GitHub.
17+
- While logged into your GitHub account, navigate to the ``gcloud-python`` repo
18+
on GitHub.
1919

2020
https://github.com/GoogleCloudPlatform/gcloud-python
2121

22-
- Fork and clone the gcloud-python repository to your GitHub account by
22+
- Fork and clone the ``gcloud-python`` repository to your GitHub account by
2323
clicking the "Fork" button.
2424

25-
- Clone your fork of gcloud-python from your GitHub account to your local
25+
- Clone your fork of ``gcloud-python`` from your GitHub account to your local
2626
computer, substituting your account username and specifying the destination
2727
as "hack-on-gcloud". E.g.::
2828

@@ -39,25 +39,25 @@ using a Git checkout:
3939
Now your local repo is set up such that you will push changes to your GitHub
4040
repo, from which you can submit a pull request.
4141

42-
- Create a virtualenv in which to install gcloud-python::
42+
- Create a virtualenv in which to install ``gcloud-python``::
4343

4444
$ cd ~/hack-on-gcloud
45-
$ virtualenv -ppython2.7 env
45+
$ virtualenv --python python2.7 env
4646

4747
Note that very old versions of virtualenv (virtualenv versions below, say,
4848
1.10 or thereabouts) require you to pass a ``--no-site-packages`` flag to
4949
get a completely isolated environment.
5050

51-
You can choose which Python version you want to use by passing a ``-p``
52-
flag to ``virtualenv``. For example, ``virtualenv -ppython2.7``
51+
You can choose which Python version you want to use by passing a ``--python``
52+
flag to ``virtualenv``. For example, ``virtualenv --python python2.7``
5353
chooses the Python 2.7 interpreter to be installed.
5454

5555
From here on in within these instructions, the ``~/hack-on-gcloud/env``
5656
virtual environment you created above will be referred to as ``$VENV``.
5757
To use the instructions in the steps that follow literally, use the
5858
``export VENV=~/hack-on-gcloud/env`` command.
5959

60-
- Install gcloud-python from the checkout into the virtualenv using
60+
- Install ``gcloud-python`` from the checkout into the virtualenv using
6161
``setup.py develop``. Running ``setup.py develop`` *must* be done while
6262
the current working directory is the ``gcloud-python`` checkout directory::
6363

@@ -83,7 +83,7 @@ and then ``pip install`` the dependencies again::
8383
Adding Features
8484
---------------
8585

86-
In order to add a feature to gcloud-python:
86+
In order to add a feature to ``gcloud-python``:
8787

8888
- The feature must be documented in both the API and narrative
8989
documentation (in ``docs/``).
@@ -124,18 +124,18 @@ Exceptions to PEP8:
124124
Running Tests
125125
--------------
126126

127-
- To run all tests for gcloud-python on a single Python version, run
127+
- To run all tests for ``gcloud-python`` on a single Python version, run
128128
``nosetests`` from your development virtualenv (See
129129
*Using a Development Checkout* above).
130130

131-
- To run the full set of gcloud-python tests on all platforms, install ``tox``
132-
(https://testrun.org/tox/) into a system Python. The ``tox`` console
131+
- To run the full set of ``gcloud-python`` tests on all platforms, install
132+
``tox`` (https://testrun.org/tox/) into a system Python. The ``tox`` console
133133
script will be installed into the scripts location for that Python. While
134-
``cd``'ed to the gcloud-python checkout root directory (it contains ``tox.ini``),
135-
invoke the ``tox`` console script. This will read the ``tox.ini`` file and
136-
execute the tests on multiple Python versions and platforms; while it runs,
137-
it creates a virtualenv for each version/platform combination. For
138-
example::
134+
``cd``'ed to the ``gcloud-python`` checkout root directory (it contains
135+
``tox.ini``), invoke the ``tox`` console script. This will read the
136+
``tox.ini`` file and execute the tests on multiple Python versions and
137+
platforms; while it runs, it creates a virtualenv for each version/platform
138+
combination. For example::
139139

140140
$ sudo /usr/bin/pip install tox
141141
$ cd ~/hack-on-gcloud/
@@ -211,6 +211,36 @@ Running System Tests
211211

212212
$ python system_tests/clear_datastore.py
213213

214+
- System tests can also be run against local `emulators`_ that mock
215+
the production services. For example, to run the system tests
216+
with the ``datastore`` emulator, first start the emulator and
217+
take note of the process ID::
218+
219+
$ gcloud beta emulators datastore start &
220+
[1] 33333
221+
222+
then determine the environment variables needed to interact with
223+
the emulator::
224+
225+
$ gcloud beta emulators datastore env-init
226+
export DATASTORE_LOCAL_HOST=localhost:8417
227+
export DATASTORE_HOST=http://localhost:8417
228+
export DATASTORE_DATASET=gcloud-settings-app-id
229+
export DATASTORE_PROJECT_ID=gcloud-settings-app-id
230+
231+
using these environment variables run the emulator::
232+
233+
$ DATASTORE_HOST=http://localhost:8471 \
234+
> DATASTORE_DATASET=gcloud-settings-app-id \
235+
> GCLOUD_NO_PRINT=true \
236+
> python system_tests/run_system_test.py --package=datastore
237+
238+
and after completion stop the emulator::
239+
240+
$ kill 33333
241+
242+
.. _emulators: https://cloud.google.com/sdk/gcloud/reference/beta/emulators/
243+
214244
Test Coverage
215245
-------------
216246

@@ -230,22 +260,22 @@ changed to reflect the bug fix, ideally in the same commit that fixes the bug
230260
or adds the feature.
231261

232262
To build and review docs (where ``$VENV`` refers to the virtualenv you're
233-
using to develop gcloud-python):
263+
using to develop ``gcloud-python``):
234264

235265
1. After following the steps above in "Using a Development Checkout", install
236266
Sphinx and all development requirements in your virtualenv::
237267

238268
$ cd ~/hack-on-gcloud
239269
$ $VENV/bin/pip install Sphinx
240270

241-
2. Change into the ``docs`` directory within your gcloud-python checkout and
271+
2. Change into the ``docs`` directory within your ``gcloud-python`` checkout and
242272
execute the ``make`` command with some flags::
243273

244274
$ cd ~/hack-on-gcloud/gcloud-python/docs
245275
$ make clean html SPHINXBUILD=$VENV/bin/sphinx-build
246276

247277
The ``SPHINXBUILD=...`` argument tells Sphinx to use the virtualenv Python,
248-
which will have both Sphinx and gcloud-python (for API documentation
278+
which will have both Sphinx and ``gcloud-python`` (for API documentation
249279
generation) installed.
250280

251281
3. Open the ``docs/_build/html/index.html`` file to see the resulting HTML

scripts/pep8_on_repo.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import os
2323
import subprocess
24+
import sys
2425

2526

2627
def main():
@@ -32,7 +33,8 @@ def main():
3233
python_files = python_files.strip().split()
3334

3435
pep8_command = ['pep8'] + python_files
35-
subprocess.call(pep8_command)
36+
status_code = subprocess.call(pep8_command)
37+
sys.exit(status_code)
3638

3739

3840
if __name__ == '__main__':

system_tests/clear_datastore.py

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,16 @@
1414

1515
"""Script to populate datastore with system test data."""
1616

17+
from __future__ import print_function
18+
19+
import os
20+
1721
from six.moves import input
1822

1923
from gcloud import datastore
20-
from gcloud.datastore import client
2124
from gcloud.environment_vars import TESTS_DATASET
2225

2326

24-
client.DATASET = TESTS_DATASET
25-
CLIENT = datastore.Client()
26-
27-
2827
FETCH_MAX = 20
2928
ALL_KINDS = [
3029
'Character',
@@ -36,9 +35,14 @@
3635
TRANSACTION_MAX_GROUPS = 5
3736

3837

39-
def fetch_keys(kind, fetch_max=FETCH_MAX, query=None, cursor=None):
38+
def print_func(message):
39+
if os.getenv('GCLOUD_NO_PRINT') != 'true':
40+
print(message)
41+
42+
43+
def fetch_keys(kind, client, fetch_max=FETCH_MAX, query=None, cursor=None):
4044
if query is None:
41-
query = CLIENT.query(kind=kind, projection=['__key__'])
45+
query = client.query(kind=kind, projection=['__key__'])
4246

4347
iterator = query.fetch(limit=fetch_max, start_cursor=cursor)
4448

@@ -53,46 +57,49 @@ def get_ancestors(entities):
5357
return list(set(key_roots))
5458

5559

56-
def remove_kind(kind):
60+
def remove_kind(kind, client):
5761
results = []
5862

59-
query, curr_results, cursor = fetch_keys(kind)
63+
query, curr_results, cursor = fetch_keys(kind, client)
6064
results.extend(curr_results)
6165
while curr_results:
62-
query, curr_results, cursor = fetch_keys(kind, query=query,
63-
cursor=cursor)
66+
query, curr_results, cursor = fetch_keys(
67+
kind, client, query=query, cursor=cursor)
6468
results.extend(curr_results)
6569

6670
if not results:
6771
return
6872

6973
delete_outside_transaction = False
70-
with CLIENT.transaction():
74+
with client.transaction():
7175
# Now that we have all results, we seek to delete.
72-
print('Deleting keys:')
73-
print(results)
76+
print_func('Deleting keys:')
77+
print_func(results)
7478

7579
ancestors = get_ancestors(results)
7680
if len(ancestors) > TRANSACTION_MAX_GROUPS:
7781
delete_outside_transaction = True
7882
else:
79-
CLIENT.delete_multi([result.key for result in results])
83+
client.delete_multi([result.key for result in results])
8084

8185
if delete_outside_transaction:
82-
CLIENT.delete_multi([result.key for result in results])
86+
client.delete_multi([result.key for result in results])
8387

8488

85-
def remove_all_entities():
86-
print('This command will remove all entities for the following kinds:')
87-
print('\n'.join(['- ' + val for val in ALL_KINDS]))
88-
response = input('Is this OK [y/n]? ')
89-
if response.lower() != 'y':
90-
print('Doing nothing.')
91-
return
92-
89+
def remove_all_entities(client=None):
90+
if client is None:
91+
# Get a client that uses the test dataset.
92+
client = datastore.Client(dataset_id=TESTS_DATASET)
9393
for kind in ALL_KINDS:
94-
remove_kind(kind)
94+
remove_kind(kind, client)
9595

9696

9797
if __name__ == '__main__':
98-
remove_all_entities()
98+
print_func('This command will remove all entities for '
99+
'the following kinds:')
100+
print_func('\n'.join(['- ' + val for val in ALL_KINDS]))
101+
response = input('Is this OK [y/n]? ')
102+
if response.lower() == 'y':
103+
remove_all_entities()
104+
else:
105+
print_func('Doing nothing.')

system_tests/datastore.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,25 @@
1313
# limitations under the License.
1414

1515
import datetime
16+
import os
17+
1618
import unittest2
1719

1820
from gcloud._helpers import UTC
1921
from gcloud import datastore
2022
from gcloud.datastore import client
23+
from gcloud.environment_vars import GCD_DATASET
2124
from gcloud.environment_vars import TESTS_DATASET
2225
from gcloud.exceptions import Conflict
2326
# This assumes the command is being run via tox hence the
2427
# repository root is the current directory.
28+
from system_tests import clear_datastore
2529
from system_tests import populate_datastore
2630

2731

32+
EMULATOR_DATASET = os.getenv(GCD_DATASET)
33+
34+
2835
class Config(object):
2936
"""Run-time configuration to be modified at set-up.
3037
@@ -35,8 +42,17 @@ class Config(object):
3542

3643

3744
def setUpModule():
38-
client.DATASET = TESTS_DATASET
39-
Config.CLIENT = datastore.Client()
45+
if EMULATOR_DATASET is None:
46+
client.DATASET = TESTS_DATASET
47+
Config.CLIENT = datastore.Client()
48+
else:
49+
Config.CLIENT = datastore.Client(dataset_id=EMULATOR_DATASET)
50+
populate_datastore.add_characters(client=Config.CLIENT)
51+
52+
53+
def tearDownModule():
54+
if EMULATOR_DATASET is not None:
55+
clear_datastore.remove_all_entities(client=Config.CLIENT)
4056

4157

4258
class TestDatastore(unittest2.TestCase):
@@ -271,15 +287,20 @@ def test_projection_query(self):
271287
self.assertEqual(catelyn_stark_dict,
272288
{'name': 'Catelyn', 'family': 'Stark'})
273289

274-
catelyn_tully_entity = entities[3]
290+
if EMULATOR_DATASET is None:
291+
catelyn_tully_entity = entities[3]
292+
sansa_entity = entities[8]
293+
else:
294+
catelyn_tully_entity = entities[8]
295+
sansa_entity = entities[7]
296+
275297
catelyn_tully_dict = dict(catelyn_tully_entity)
276298
self.assertEqual(catelyn_tully_dict,
277299
{'name': 'Catelyn', 'family': 'Tully'})
278300

279301
# Check both Catelyn keys are the same.
280302
self.assertEqual(catelyn_stark_entity.key, catelyn_tully_entity.key)
281303

282-
sansa_entity = entities[8]
283304
sansa_dict = dict(sansa_entity)
284305
self.assertEqual(sansa_dict, {'name': 'Sansa', 'family': 'Stark'})
285306

0 commit comments

Comments
 (0)