Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0da63f9

Browse files
committed
- Fixes for memory issues in the Python 2.7 runtime.
- Test cleanup.
1 parent 00d65f8 commit 0da63f9

File tree

5 files changed

+113
-68
lines changed

5 files changed

+113
-68
lines changed

ndb/context_test.py

Lines changed: 52 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,19 +1536,16 @@ def MakeContext(self, *args, **kwargs):
15361536

15371537

15381538
@real_unittest.skipUnless(datastore_pbs._CLOUD_DATASTORE_ENABLED,
1539-
"V1 must be supported to run V1 tests.")
1540-
class ContextV1WithRemoteAPITests(ContextTestMixin,
1541-
ContextMemcacheTestMixin,
1542-
ContextTaskQueueTestMixin,
1543-
test_utils.NDBCloudDatastoreV1Test):
1539+
'V1 must be supported to run V1 tests.')
1540+
class ContextV1Tests(ContextTestMixin,
1541+
test_utils.NDBCloudDatastoreV1Test):
15441542
"""Context tests that use a Cloud Datastore V1 connection.
15451543
1546-
These tests run with memcache and taskqueue stubs available."""
1544+
These tests run with memcache and taskqueue stubs available.
1545+
"""
15471546

15481547
def setUp(self):
1549-
super(ContextV1WithRemoteAPITests, self).setUp()
1550-
self.testbed.init_memcache_stub()
1551-
self.testbed.init_taskqueue_stub()
1548+
super(ContextV1Tests, self).setUp()
15521549
self.HRTest()
15531550
MyAutoBatcher.reset_log()
15541551
self.ctx = self.MakeContext(default_model=model.Expando,
@@ -1558,75 +1555,74 @@ def setUp(self):
15581555
def make_bad_transaction(*arg, **kwargs):
15591556
return ''
15601557

1558+
def testContext_AutoBatcher_Errors(self):
1559+
# Performs tests via direct memcache call, which is disabled by default
1560+
# in V1.
1561+
pass
1562+
1563+
def testContext_AllocateIds(self):
1564+
# V1 does not support Allocate id range.
1565+
pass
1566+
15611567
def testContext_TransactionAddTask(self):
15621568
# Transactional AddTask still will be unavailable.
1563-
self.ExpectWarnings()
1564-
key = model.Key('Foo', 1)
1565-
1566-
@tasklets.tasklet
15671569
def foo():
1568-
ent = model.Expando(key=key, bar=1)
1569-
1570-
@tasklets.tasklet
1571-
def callback():
1572-
ctx = tasklets.get_context()
1573-
yield ctx.put(ent)
1574-
taskqueue.add(url='/', transactional=True)
1575-
yield self.ctx.transaction(callback)
1576-
self.assertRaises(ValueError, foo().check_success)
1570+
taskqueue.add(url='/', transactional=True)
1571+
self.assertRaises(ValueError, model.transaction, foo)
15771572

15781573
def MakeContext(self, *args, **kwargs):
1579-
ctx = super(ContextV1WithRemoteAPITests, self).MakeContext(*args, **kwargs)
1580-
# Re-enable default cache policy.
1574+
ctx = super(ContextV1Tests, self).MakeContext(*args, **kwargs)
1575+
# Re-enable in-context cache. Memcache must remain off because
1576+
# the stub is not enabled.
15811577
ctx.set_cache_policy(None)
1582-
ctx.set_memcache_policy(None)
15831578
return ctx
15841579

1580+
15851581
@real_unittest.skipUnless(datastore_pbs._CLOUD_DATASTORE_ENABLED,
1586-
"V1 must be supported to run V1 tests.")
1587-
class ContextV1Tests(ContextTestMixin,
1588-
test_utils.NDBCloudDatastoreV1Test):
1582+
'V1 must be supported to run V1 tests.')
1583+
class ContextV1WithRemoteAPITests(ContextV1Tests,
1584+
ContextMemcacheTestMixin,
1585+
ContextTaskQueueTestMixin):
15891586
"""Context tests that use a Cloud Datastore V1 connection.
15901587
1591-
These tests run with memcache and taskqueue stubs available."""
1588+
These tests run with memcache and taskqueue stubs available.
1589+
"""
15921590

15931591
def setUp(self):
1594-
super(ContextV1Tests, self).setUp()
1592+
# testbed needs to get set up first.
1593+
super(ContextV1WithRemoteAPITests, self).setUp()
15951594
self.testbed.init_memcache_stub()
15961595
self.testbed.init_taskqueue_stub()
1597-
self.HRTest()
1598-
MyAutoBatcher.reset_log()
1599-
self.ctx = self.MakeContext(default_model=model.Expando,
1600-
auto_batcher_class=MyAutoBatcher)
1601-
tasklets.set_context(self.ctx)
1602-
1603-
def make_bad_transaction(*arg, **kwargs):
1604-
return ''
16051596

1606-
def testContext_TransactionAddTask(self):
1607-
# Transactional AddTask still will be unavailable.
1597+
def testContext_AutoBatcher_Errors(self):
1598+
# Test that errors are properly distributed over all Futures.
16081599
self.ExpectWarnings()
1609-
key = model.Key('Foo', 1)
1610-
1611-
@tasklets.tasklet
1612-
def foo():
1613-
ent = model.Expando(key=key, bar=1)
16141600

1615-
@tasklets.tasklet
1616-
def callback():
1617-
ctx = tasklets.get_context()
1618-
yield ctx.put(ent)
1619-
taskqueue.add(url='/', transactional=True)
1620-
yield self.ctx.transaction(callback)
1621-
self.assertRaises(ValueError, foo().check_success)
1601+
class Blobby(model.Model):
1602+
blob = model.BlobProperty()
1603+
ent1 = Blobby()
1604+
ent2 = Blobby(blob='x' * 2000000)
1605+
fut1 = self.ctx.put(ent1)
1606+
fut2 = self.ctx.put(ent2) # Error
1607+
err1 = fut1.get_exception()
1608+
err2 = fut2.get_exception()
1609+
self.assertTrue(isinstance(err1, datastore_errors.BadRequestError))
1610+
self.assertTrue(err1 is err2)
1611+
# Try memcache as well (different tasklet, different error).
1612+
fut1 = self.ctx.memcache_set('key1', 'x')
1613+
fut2 = self.ctx.memcache_set('key2', 'x' * 1000001)
1614+
err1 = fut1.get_exception()
1615+
err2 = fut1.get_exception()
1616+
self.assertTrue(isinstance(err1, ValueError))
1617+
self.assertTrue(err1 is err2)
16221618

16231619
def MakeContext(self, *args, **kwargs):
1624-
ctx = super(ContextV1Tests, self).MakeContext(*args, **kwargs)
1625-
# Re-enable in-context cache. Memcache must remain off because
1626-
# the stub is not enabled.
1627-
ctx.set_cache_policy(None)
1620+
ctx = super(ContextV1WithRemoteAPITests, self).MakeContext(*args, **kwargs)
1621+
# Re-enable memcache.
1622+
ctx.set_memcache_policy(None)
16281623
return ctx
16291624

1625+
16301626
class ContextFutureCachingTests(test_utils.NDBTest):
16311627
# See issue 62. http://goo.gl/5zLkK
16321628

ndb/google_imports.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,17 @@ def set_appengine_imports():
8888
from google.appengine.ext import db
8989
from google.appengine.ext import gql
9090
try:
91+
# For the python-compat runtime.
9192
from google.appengine.ext.vmruntime import callback
9293
except ImportError:
93-
callback = None
94+
# For the python 2.7 runtime.
95+
try:
96+
from google.appengine.runtime import apiproxy as callback
97+
# Python 2.5 and dev_appserver is not supported.
98+
if not hasattr(callback, 'SetRequestEndCallback'):
99+
callback = None
100+
except ImportError:
101+
callback = None
94102
from google.appengine.runtime import apiproxy_errors
95103
from google.net.proto import ProtocolBuffer
96104
else:

ndb/tasklets.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ def foo():
106106

107107
_CALLBACK_KEY = '__CALLBACK__'
108108

109+
# Python 2.5 compatability.
110+
if hasattr(weakref, 'WeakSet'):
111+
_set = weakref.WeakSet
112+
else:
113+
_set = set
114+
109115

110116
def _is_generator(obj):
111117
"""Helper to test for a generator object.
@@ -122,20 +128,25 @@ class _State(utils.threading_local):
122128
def __init__(self):
123129
super(_State, self).__init__()
124130
self.current_context = None
125-
self.all_generators = weakref.WeakSet()
131+
self.all_generators = _set()
126132
self.all_pending = set()
127133

128134
def set_context(self, ctx):
135+
self.current_context = ctx
136+
137+
def add_generator(self, gen):
129138
if _request_callback and _CALLBACK_KEY not in os.environ:
130139
_request_callback.SetRequestEndCallback(self.reset)
131140
os.environ[_CALLBACK_KEY] = '1'
132-
self.current_context = ctx
133141

134-
def add_generator(self, gen):
135142
_logging_debug('all_generators: add %s', gen)
136143
self.all_generators.add(gen)
137144

138145
def add_pending(self, fut):
146+
if _request_callback and _CALLBACK_KEY not in os.environ:
147+
_request_callback.SetRequestEndCallback(self.reset)
148+
os.environ[_CALLBACK_KEY] = '1'
149+
139150
_logging_debug('all_pending: add %s', fut)
140151
self.all_pending.add(fut)
141152

@@ -172,7 +183,7 @@ def dump_all_pending(self, verbose=False):
172183
pending.append(line)
173184
return '\n'.join(pending)
174185

175-
def reset(self):
186+
def reset(self, unused_req_id):
176187
self.current_context = None
177188
ev = eventloop.get_event_loop()
178189
ev.clear()

ndb/tasklets_test.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -855,31 +855,50 @@ class CloudDatastoreContextCreationTests(test_utils.NDBCloudDatastoreV1Test):
855855
def setUp(self):
856856
super(CloudDatastoreContextCreationTests, self).setUp()
857857
self.HRTest()
858-
self.ctx = tasklets._make_cloud_datastore_context(self.APP_ID)
859-
tasklets.set_context(self.ctx)
860-
861-
def testCloudDatastoreContext(self):
858+
self.old_env = dict(os.environ)
862859
del os.environ['APPLICATION_ID'] # Clear APPLICATION_ID for test.
860+
861+
def tearDown(self):
862+
super(CloudDatastoreContextCreationTests, self).setUp()
863+
os.environ.clear()
864+
os.environ.update(self.old_env)
865+
866+
def testCloudDatastoreContext_ProjectIdNotSufficient(self):
863867
os.environ['DATASTORE_PROJECT_ID'] = 'project'
864868
# Setting just a project id isn't enough info.
865869
self.assertRaisesRegexp(ValueError,
866870
'Could not determine app id\..*',
867871
tasklets.make_default_context)
868-
# Unless the override is specified.
869-
os.environ['DATASTORE_USE_PROJECT_ID_AS_APP_ID'] = 'True'
872+
873+
def testCloudDatastoreContext_UseProjectIdAsAppId(self):
874+
os.environ['DATASTORE_PROJECT_ID'] = 'project'
875+
os.environ['DATASTORE_USE_PROJECT_ID_AS_APP_ID'] = 'true'
870876
ctx = tasklets.make_default_context()
871877
self.assertEqual(datastore_rpc._CLOUD_DATASTORE_V1, ctx._conn._api_version)
878+
879+
def testCloudDatastoreContext_AppIdAndUseProjectIdAsAppId(self):
880+
os.environ['DATASTORE_PROJECT_ID'] = 'project'
881+
os.environ['DATASTORE_USE_PROJECT_ID_AS_APP_ID'] = 'true'
872882
os.environ['DATASTORE_APP_ID'] = 's~project'
873883
# App id and override should not both be specified
874884
self.assertRaisesRegexp(ValueError,
875885
'App id was provided .* but .* was set to true\.',
876886
tasklets.make_default_context)
877-
del os.environ['DATASTORE_USE_PROJECT_ID_AS_APP_ID']
887+
888+
def testCloudDatastoreContext_AppId(self):
889+
os.environ['DATASTORE_PROJECT_ID'] = 'project'
890+
os.environ['DATASTORE_APP_ID'] = 's~project'
878891
ctx = tasklets.make_default_context()
879892
self.assertEqual(datastore_rpc._CLOUD_DATASTORE_V1, ctx._conn._api_version)
893+
894+
def testCloudDatastoreContext_DifferentAppId(self):
895+
os.environ['DATASTORE_PROJECT_ID'] = 'project'
880896
os.environ['DATASTORE_APP_ID'] = 's~anotherproject'
881897
# The provided app id must resolve to the provided project id.
882898
self.assertRaises(ValueError, tasklets.make_default_context)
899+
900+
def testCloudDatastoreContext_ManyAppIds(self):
901+
os.environ['DATASTORE_PROJECT_ID'] = 'project'
883902
os.environ['DATASTORE_APP_ID'] = 's~project'
884903
os.environ['DATASTORE_ADDITIONAL_APP_IDS'] = 's~anotherapp,s~andanother'
885904
ctx = tasklets.make_default_context()
@@ -895,13 +914,19 @@ def testCloudDatastoreContextWithExistingApplicationId(self):
895914
self.assertRaises(ValueError, tasklets.make_default_context)
896915

897916
def testContext_MemcacheUnavailable(self):
917+
os.environ['DATASTORE_PROJECT_ID'] = self.APP_ID
918+
os.environ['DATASTORE_USE_PROJECT_ID_AS_APP_ID'] = 'true'
919+
tasklets.set_context(tasklets.make_default_context())
898920
key = model.Key('Foo', 1)
899921
ent = model.Expando(key=key, bar=1)
900922
ctx = tasklets.get_context()
901923
ctx.set_memcache_policy(True)
902924
self.assertRaises(NotImplementedError, ctx.put(ent).get_result)
903925

904926
def testContext_TaskQueueUnavailable(self):
927+
os.environ['DATASTORE_PROJECT_ID'] = self.APP_ID
928+
os.environ['DATASTORE_USE_PROJECT_ID_AS_APP_ID'] = 'true'
929+
tasklets.set_context(tasklets.make_default_context())
905930
self.assertRaises(NotImplementedError, taskqueue.add, url='/')
906931

907932

ndb/test_utils.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import logging
2424
import tempfile
25+
import os
2526

2627
from .google_imports import apiproxy_stub_map
2728
from .google_imports import datastore
@@ -178,6 +179,10 @@ def tearDownClass(cls):
178179

179180
def setUp(self):
180181
super(NDBCloudDatastoreV1Test, self).setUp()
182+
# The host doesn't get used since we override the stub, however the
183+
# existence of this environment variable can break if we try to get
184+
# credentials.
185+
os.environ['DATASTORE_EMULATOR_HOST'] = 'localhost:1234'
181186
self.datastore.Clear()
182187
stub = cloud_datastore_v1_remote_stub.CloudDatastoreV1RemoteStub(
183188
self.datastore.GetDatastore())

0 commit comments

Comments
 (0)