Skip to content

Commit 49f8a23

Browse files
committed
Updating datastore system test to allow concurrent runs.
Using a namespace for any key which is changed (inserted/deleted) so that concurrent tests don't operate on the same data. The one exception is `TestDatastoreQuery` which depends on entities which need to be already stored and indexed before running. For this test case, a new client is created without any namespace.
1 parent ffee6e7 commit 49f8a23

File tree

2 files changed

+57
-23
lines changed

2 files changed

+57
-23
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"type": "service_account",
3+
"private_key_id": "890954bc98d1d3fa88f2e0809846889fa0990f1d",
4+
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCWs7n8VH/zYDdM\nmk02357GxGyiPLakgjzgYQ7KPJsFo7ZtFM03cdF7p+3azkcxL/R4NnWdnq259mYY\nHeEp9szmovnicmTfnY53iJifiXgr6C83ezb9npP2Vq+eMEUGoP+npXW8kk/Dkald\n5VUkMXM4vDDpPVdreTpOg1FQ33zUhI/X9dnkTdyYKHgx6OhhfnxMWbYcRr7IsVHp\nVgx7/M8hcRRWLwaAx5ItUv0iYVoVFKGtz7OKIzODjpHNadc0BmasAYxgasAxC6TT\nPLMxsIgjGnaEIaQunXWbR9ezVP993izTjUkBzqFQZWnS1hhM4yOhmTVeM59sTXX8\nxsbpt8JdAgMBAAECggEAdwpRB3jX+S8vf1sDUy0hBnFyPlUnEJuv6q3AOQ/WhIXl\nPBUKZZmKl9r2j7gRlFEFE1AxZ+hH4nU4ACTaD4PcJvLl4HHsLJcQSmuj46F7Q3OM\nmI7DyWLSDew7nQEUBUB3sb2JWwNX7mvJFVpp/sTw9wfwusyxmTuRSyN/wBpJSLSX\nezegkaKI4zraUfH2P54yeBoTX06WjrQWX9qUadjOGW9n2Cib52b+yJua/Ge+Gch/\nFtnTM2ezLSH0Y/GSmqKD8Vyk+/HptQLIhWov2cptn70k1jPqiZjG787fJhlfzY41\nhkMxoRTNkfOa9mLa8IGZxqJXNxoDtzwlhNl4xzKpAQKBgQDFqBIPFrZgzb8014Xk\npR1QbcCKQBxtwmeE40k106PN3ky3PVb3r05aUH0z87BiyEX19Vrv8iuCyIK0qcvi\nZXaa5Jter/RK+PKaMfNbQBEwVQOGurU5eYuHxaQoGrhZx2OCp0/4NPmemZV0H/T0\nlHL3Ynxkmv34typ6SU9np4MUeQKBgQDDL4nFnUWywIq+cYxlAkMYN0+6jNXnVKVR\n+BSSRiu1LNeHlnzjk/EOzNcxOmNLjsM7uepUXIhLEwFBAHUOqLdI+H3YHZ4H1oSU\nsoWRrvNfqr9xxKIEuyVj9g0Ath81mhWIAmt75o33Kx3Aju3fJPiv3X6zSEl/OCns\nJgpQVzw8BQKBgFfUoYG28//8LVUL2GGLxYTx4DcO2hMj6eAxJ10V2JooiCp8xWJ2\nsfiL/7bQOLcMi5oKFwi8sf0BoKMrOIA32gZmQ2xF/+Y4zYUbYSpRQ9IYVOLPoKwT\niJ8ighmYFgc/BMcQFgcTc0C4uqLJeI4eUSAA0YnbHbQhoGyKmxBSbXrxAoGAeXDy\nf4nC+9zgdm2ftJkbtc10RdWqoSwBUGbSJdCTET69OxdezRoXOeYpXe++KBzDTD34\nllScWFmSxibKrjw7DaPOaNDEqovGId6RTHtV4YgTZW26gMcQFInavT6TXH5qNx2K\nUqS4X7jr4gsL5XLTaFifpFytngIL02o3sJUWXSECgYBJ4W3E5JiypGP/X8J6RXeL\nKfmBzMXgXnSFap5Q1RrSifL0jNH4HEP4UqsUx7+veUWIHoL1Db51HXCudHRKWYcU\n6RO2K5Unh3ALrRD4RRlKHWzEiLeVPysYIuXN6dRQnoIzld/mBPaNutbA0/Er9wr1\nBA+YZ1PHFdEVk024eyV5iQ==\n-----END PRIVATE KEY-----\n",
5+
"client_email": "522695182322-uld8n1q3nrar5r6k3nk249fvppes5u73@developer.gserviceaccount.com",
6+
"client_id": "522695182322-uld8n1q3nrar5r6k3nk249fvppes5u73.apps.googleusercontent.com",
7+
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
8+
"token_uri": "https://accounts.google.com/o/oauth2/token",
9+
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
10+
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/522695182322-uld8n1q3nrar5r6k3nk249fvppes5u73%40developer.gserviceaccount.com"
11+
}

system_tests/datastore.py

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414

1515
import datetime
1616
import os
17+
import time
1718

1819
import unittest2
1920

2021
from gcloud._helpers import UTC
2122
from gcloud import datastore
22-
from gcloud.datastore import client
23+
from gcloud.datastore import client as client_mod
2324
from gcloud.environment_vars import GCD_DATASET
2425
from gcloud.environment_vars import TESTS_DATASET
2526
from gcloud.exceptions import Conflict
@@ -29,6 +30,8 @@
2930
from system_tests import populate_datastore
3031

3132

33+
# Isolated namespace so concurrent test runs don't collide.
34+
TEST_NAMESPACE = 'ns%d' % (1000 * time.time(),)
3235
EMULATOR_DATASET = os.getenv(GCD_DATASET)
3336

3437

@@ -41,18 +44,22 @@ class Config(object):
4144
CLIENT = None
4245

4346

47+
def clone_client(client):
48+
# Fool the Client constructor to avoid creating a new connection.
49+
cloned_client = datastore.Client(project=client.project,
50+
namespace=client.namespace,
51+
http=object())
52+
cloned_client.connection = client.connection
53+
return cloned_client
54+
55+
4456
def setUpModule():
4557
if EMULATOR_DATASET is None:
46-
client.DATASET = TESTS_DATASET
47-
Config.CLIENT = datastore.Client()
58+
client_mod.DATASET = TESTS_DATASET
59+
Config.CLIENT = datastore.Client(namespace=TEST_NAMESPACE)
4860
else:
49-
Config.CLIENT = datastore.Client(project=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)
61+
Config.CLIENT = datastore.Client(project=EMULATOR_DATASET,
62+
namespace=TEST_NAMESPACE)
5663

5764

5865
class TestDatastore(unittest2.TestCase):
@@ -87,7 +94,6 @@ class TestDatastoreSave(TestDatastore):
8794

8895
@classmethod
8996
def setUpClass(cls):
90-
super(TestDatastoreSave, cls).setUpClass()
9197
cls.PARENT = Config.CLIENT.key('Blog', 'PizzaMan')
9298

9399
def _get_post(self, id_or_name=None, post_content=None):
@@ -194,13 +200,32 @@ class TestDatastoreQuery(TestDatastore):
194200

195201
@classmethod
196202
def setUpClass(cls):
197-
super(TestDatastoreQuery, cls).setUpClass()
203+
cls.CLIENT = clone_client(Config.CLIENT)
204+
# Remove the namespace from the cloned client, since these
205+
# query tests rely on the entities to be already stored and indexed,
206+
# hence ``TEST_NAMESPACE`` set at runtime can't be used.
207+
cls.CLIENT.namespace = None
208+
209+
# In the emulator, re-populating the datastore is cheap.
210+
if EMULATOR_DATASET is not None:
211+
# Populate the datastore with the cloned client.
212+
populate_datastore.add_characters(client=cls.CLIENT)
213+
198214
cls.CHARACTERS = populate_datastore.CHARACTERS
199-
cls.ANCESTOR_KEY = Config.CLIENT.key(*populate_datastore.ANCESTOR)
215+
# Use the client for this test instead of the global.
216+
cls.ANCESTOR_KEY = cls.CLIENT.key(*populate_datastore.ANCESTOR)
217+
218+
@classmethod
219+
def tearDownClass(cls):
220+
# In the emulator, destroy the query entities.
221+
if EMULATOR_DATASET is not None:
222+
# Use the client for this test instead of the global.
223+
clear_datastore.remove_all_entities(client=cls.CLIENT)
200224

201225
def _base_query(self):
202-
return Config.CLIENT.query(kind='Character',
203-
ancestor=self.ANCESTOR_KEY)
226+
# Use the client for this test instead of the global.
227+
return self.CLIENT.query(kind='Character',
228+
ancestor=self.ANCESTOR_KEY)
204229

205230
def test_limit_queries(self):
206231
limit = 5
@@ -245,7 +270,8 @@ def test_ancestor_query(self):
245270
self.assertEqual(len(entities), expected_matches)
246271

247272
def test_query___key___filter(self):
248-
rickard_key = Config.CLIENT.key(*populate_datastore.RICKARD)
273+
# Use the client for this test instead of the global.
274+
rickard_key = self.CLIENT.key(*populate_datastore.RICKARD)
249275

250276
query = self._base_query()
251277
query.add_filter('__key__', '=', rickard_key)
@@ -377,11 +403,8 @@ def test_transaction(self):
377403
self.assertEqual(retrieved_entity, entity)
378404

379405
def test_failure_with_contention(self):
380-
contention_key = 'baz'
381-
# Fool the Client constructor to avoid creating a new connection.
382-
local_client = datastore.Client(project=Config.CLIENT.project,
383-
http=object())
384-
local_client.connection = Config.CLIENT.connection
406+
contention_prop_name = 'baz'
407+
local_client = clone_client(Config.CLIENT)
385408

386409
# Insert an entity which will be retrieved in a transaction
387410
# and updated outside it with a contentious value.
@@ -396,10 +419,10 @@ def test_failure_with_contention(self):
396419
entity_in_txn = local_client.get(key)
397420

398421
# Update the original entity outside the transaction.
399-
orig_entity[contention_key] = u'outside'
422+
orig_entity[contention_prop_name] = u'outside'
400423
Config.CLIENT.put(orig_entity)
401424

402425
# Try to update the entity which we already updated outside the
403426
# transaction.
404-
entity_in_txn[contention_key] = u'inside'
427+
entity_in_txn[contention_prop_name] = u'inside'
405428
txn.put(entity_in_txn)

0 commit comments

Comments
 (0)