Skip to content

Commit 929618b

Browse files
committed
Basic regression/functional tests for datastore.
This commit only tests - Adding a basic entity with an auto allocated ID, a given integer ID or a given name. - Adding multiple entities via a transaction - The given kind has no other entities (i.e. no false positives) This commit adds - A sample file to set environment variables for a test runner. - A test runner. - A utility for getting test relevant environ variables.
1 parent 8b97707 commit 929618b

File tree

8 files changed

+187
-1
lines changed

8 files changed

+187
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,6 @@ docs/_build
4242
# Virtual environment
4343
env/
4444
coverage.xml
45+
46+
# Regression test environment variables.
47+
regression/local_test_setup

regression/__init__.py

Whitespace-only changes.

regression/datastore.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import datetime
2+
import pytz
3+
import unittest2
4+
5+
from gcloud import datastore
6+
# This assumes the command is being run via tox hence the
7+
# repository root is the current directory.
8+
from regression import regression_utils
9+
10+
11+
class TestDatastore(unittest2.TestCase):
12+
13+
def setUp(self):
14+
environ = regression_utils.get_environ()
15+
self._dataset_id = environ['dataset_id']
16+
self._client_email = environ['client_email']
17+
self._key_filename = environ['key_filename']
18+
self._datasets = {}
19+
20+
self.entities_to_delete = []
21+
22+
def tearDown(self):
23+
for entity in self.entities_to_delete:
24+
entity.delete()
25+
26+
def _get_dataset(self):
27+
if self._dataset_id not in self._datasets:
28+
self._datasets[self._dataset_id] = datastore.get_dataset(
29+
self._dataset_id, self._client_email, self._key_filename)
30+
return self._datasets[self._dataset_id]
31+
32+
def _get_post(self, name=None, key_id=None, post_content=None):
33+
post_content = post_content or {
34+
'title': 'How to make the perfect pizza in your grill',
35+
'tags': ['pizza', 'grill'],
36+
# NOTE: We don't support datetime.date, but should.
37+
# NOTE: Without a tz, assertEqual fails with
38+
# "can't compare offset-naive and offset-aware datetimes"
39+
'publishedAt': datetime.datetime(2001, 1, 1, tzinfo=pytz.utc),
40+
'author': 'Silvano',
41+
'isDraft': False,
42+
'wordCount': 400,
43+
'rating': 5.0,
44+
}
45+
# Create an entity with the given content in our dataset.
46+
dataset = self._get_dataset()
47+
entity = dataset.entity(kind='Post')
48+
entity.update(post_content)
49+
50+
# Update the entity key.
51+
key = None
52+
if name is not None:
53+
key = entity.key().name(name)
54+
if key_id is not None:
55+
key = entity.key().id(key_id)
56+
if key is not None:
57+
entity.key(key)
58+
59+
return entity
60+
61+
def _generic_test_post(self, name=None, key_id=None):
62+
entity = self._get_post(name=name, key_id=key_id)
63+
entity.save()
64+
65+
# Register entity to be deleted.
66+
self.entities_to_delete.append(entity)
67+
68+
if name is not None:
69+
self.assertEqual(entity.key().name(), name)
70+
if key_id is not None:
71+
self.assertEqual(entity.key().id(), key_id)
72+
retrieved_entity = self._get_dataset().get_entity(entity.key())
73+
# Check the keys are the same.
74+
self.assertEqual(retrieved_entity.key().path(),
75+
entity.key().path())
76+
# Check the data is the same.
77+
retrieved_dict = dict(retrieved_entity.items())
78+
entity_dict = dict(entity.items())
79+
self.assertEqual(retrieved_dict, entity_dict)
80+
81+
def test_post_with_name(self):
82+
self._generic_test_post(name='post1')
83+
84+
def test_post_with_id(self):
85+
self._generic_test_post(key_id=123456789)
86+
87+
def test_post_with_generated_id(self):
88+
self._generic_test_post()
89+
90+
def test_save_multiple(self):
91+
dataset = self._get_dataset()
92+
with dataset.transaction():
93+
entity1 = self._get_post()
94+
entity1.save()
95+
# Register entity to be deleted.
96+
self.entities_to_delete.append(entity1)
97+
98+
second_post_content = {
99+
'title': 'How to make the perfect homemade pasta',
100+
'tags': ['pasta', 'homemade'],
101+
'publishedAt': datetime.datetime(2001, 1, 1),
102+
'author': 'Silvano',
103+
'isDraft': False,
104+
'wordCount': 450,
105+
'rating': 4.5,
106+
}
107+
entity2 = self._get_post(post_content=second_post_content)
108+
entity2.save()
109+
# Register entity to be deleted.
110+
self.entities_to_delete.append(entity2)
111+
112+
keys = [entity1.key(), entity2.key()]
113+
matches = dataset.get_entities(keys)
114+
self.assertEqual(len(matches), 2)
115+
116+
def test_empty_kind(self):
117+
posts = self._get_dataset().query().kind('Post').limit(2).fetch()
118+
self.assertEqual(posts, [])

regression/local_test_setup.sample

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export GCLOUD_TESTS_DATASET_ID="my-dataset"
2+
export GCLOUD_TESTS_CLIENT_EMAIL="some-account@developer.gserviceaccount.com"
3+
export GCLOUD_TESTS_KEY_FILE="path.key"

regression/regression_utils.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import os
2+
import sys
3+
4+
5+
# Defaults from shell environ. May be None.
6+
DATASET_ID = os.getenv('GCLOUD_TESTS_DATASET_ID')
7+
CLIENT_EMAIL = os.getenv('GCLOUD_TESTS_CLIENT_EMAIL')
8+
KEY_FILENAME = os.getenv('GCLOUD_TESTS_KEY_FILE')
9+
10+
ENVIRON_ERROR_MSG = """\
11+
To run the regression tests, you need to set some environment variables.
12+
Please check the Contributing guide for instructions.
13+
"""
14+
15+
16+
def get_environ():
17+
if DATASET_ID is None or CLIENT_EMAIL is None or KEY_FILENAME is None:
18+
print >> sys.stderr, ENVIRON_ERROR_MSG
19+
sys.exit(1)
20+
21+
return {
22+
'dataset_id': DATASET_ID,
23+
'client_email': CLIENT_EMAIL,
24+
'key_filename': KEY_FILENAME,
25+
}

regression/run_regression.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import argparse
2+
import unittest2
3+
4+
5+
def get_parser():
6+
parser = argparse.ArgumentParser(
7+
description='GCloud test runner against actual project.')
8+
parser.add_argument('--package', dest='package',
9+
choices=('datastore',),
10+
default='datastore', help='Package to be tested.')
11+
return parser
12+
13+
14+
def run_module_tests(module_name):
15+
suite = unittest2.TestSuite()
16+
tests = unittest2.defaultTestLoader.loadTestsFromName(module_name)
17+
suite.addTest(tests)
18+
unittest2.TextTestRunner(verbosity=2).run(suite)
19+
20+
21+
def main():
22+
parser = get_parser()
23+
args = parser.parse_args()
24+
run_module_tests(args.package)
25+
26+
27+
if __name__ == '__main__':
28+
main()

run_pylint.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ def is_production_filename(filename):
3232
:rtype: `bool`
3333
:returns: Boolean indicating production status.
3434
"""
35-
return not ('demo' in filename or 'test' in filename)
35+
return not ('demo' in filename or 'test' in filename
36+
or filename.startswith('regression'))
3637

3738

3839
def get_python_files():

tox.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,11 @@ deps =
4343
pep8
4444
pylint
4545
unittest2
46+
47+
[testenv:regression]
48+
basepython =
49+
python2.7
50+
commands =
51+
python regression/run_regression.py --package datastore
52+
deps =
53+
unittest2

0 commit comments

Comments
 (0)