Commit dec36cac authored by Chris McDonough's avatar Chris McDonough

Moved TransientObjects into their own module.

Removed wrap_with argument from new and new_or_existing methods
of Transient Data Containers.

Removed delete method of Transient Data Containers.

Added out-of-memory protection to Transient Data Containers.  A
  new __init__ value ('limit') is used to specify the max number
  of objects that can be contained within a transient data container.
  A new envvar ZSESSION_OBJECT_LIMIT can be used to control the
  limit of the default session_data TDC.  Also updated help and
  API docs with this change.

Added a new exception, MaxTransientObjectsExceeded, which is raised
  when the OOM protection kicks in.

Various implementation changes including the use of a BTrees Length
  object to store Transient Data Container length info as well
  as improvements to how buckets are expired.

Addition of tests for OOM protection fatures.
parent 4ba39f69
......@@ -275,6 +275,9 @@ class HomogeneousItemContainer(Interface.Base):
"""
Return value associated with key k via __getitem__. If value
associated with k does not exist, return default.
Returned item is acquisition-wrapped in self unless a default
is passed in and returned.
"""
def has_key(self, k):
......@@ -283,13 +286,8 @@ class HomogeneousItemContainer(Interface.Base):
return false.
"""
def delete(self, k):
"""
Delete value associated with key k, raise a KeyError if nonexistent.
"""
class StringKeyedHomogeneousItemContainer(HomogeneousItemContainer):
def new(self, k, wrap_with=None):
def new(self, k):
"""
Creates a new subobject of the type supported by this container
with key "k" and returns it.
......@@ -297,14 +295,15 @@ class StringKeyedHomogeneousItemContainer(HomogeneousItemContainer):
If an object already exists in the container with key "k", a
KeyError is raised.
If wrap_with is non-None, the subobject is returned in the
acquisition context of wrap_in, else it is returned in
the acquisition context of this transient object container.
"k" must be a string, else a TypeError is raised.
If the container is 'full', a MaxTransientObjectsExceeded exception
will be raised.
Returned object is acquisition-wrapped in self.
"""
def new_or_existing(self, k, wrap_with=None):
def new_or_existing(self, k):
"""
If an object already exists in the container with key "k", it
is returned.
......@@ -312,11 +311,12 @@ class StringKeyedHomogeneousItemContainer(HomogeneousItemContainer):
Otherwise, create a new subobject of the type supported by this
container with key "k" and return it.
If wrap_with is non-None, the subobject is returned in the
acquisition context of wrap_in, else it is returned in
the acquisition context of this transient object container.
"k" must be a string, else a TypeError is raised.
If a new object needs to be created and the container is 'full',
a MaxTransientObjectsExceeded exception will be raised.
Returned object is acquisition-wrapped in self.
"""
class TransientItemContainer(Interface.Base):
......
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
"""
Simple ZODB-based transient object implementation.
$Id: TransientObject.py,v 1.1 2001/11/21 22:46:36 chrism Exp $
"""
__version__='$Revision: 1.1 $'[11:-2]
from Persistence import Persistent
from Acquisition import Implicit, aq_base
import time, random, sys
from TransienceInterfaces import ItemWithId, Transient, DictionaryLike,\
TTWDictionary, ImmutablyValuedMappingOfPickleableObjects
from AccessControl import ClassSecurityInfo
import Globals
from zLOG import LOG, BLATHER
_notfound = []
WRITEGRANULARITY=30 # Timing granularity for write clustering, in seconds
class TransientObject(Persistent, Implicit):
""" Dictionary-like object that supports additional methods
concerning expiration and containment in a transient object container
"""
__implements__ = (ItemWithId, # randomly generate an id
Transient,
DictionaryLike,
TTWDictionary,
ImmutablyValuedMappingOfPickleableObjects
)
security = ClassSecurityInfo()
security.setDefaultAccess('allow')
security.declareObjectPublic()
def __init__(self, containerkey):
self.token = containerkey
self.id = self._generateUniqueId()
self._container = {}
self._created = self._last_accessed = time.time()
# -----------------------------------------------------------------
# ItemWithId
#
def getId(self):
return self.id
# -----------------------------------------------------------------
# Transient
#
def invalidate(self):
self._invalid = None
def isValid(self):
return not hasattr(self, '_invalid')
def getLastAccessed(self):
return self._last_accessed
def setLastAccessed(self, WG=WRITEGRANULARITY):
# check to see if the last_accessed time is too recent, and avoid
# setting if so, to cut down on heavy writes
t = time.time()
if (self._last_accessed + WG) < t:
self._last_accessed = t
def getCreated(self):
return self._created
def getContainerKey(self):
return self.token
# -----------------------------------------------------------------
# DictionaryLike
#
def keys(self):
return self._container.keys()
def values(self):
return self._container.values()
def items(self):
return self._container.items()
def get(self, k, default=_notfound):
v = self._container.get(k, default)
if v is _notfound: return None
return v
def has_key(self, k):
if self._container.get(k, _notfound) is not _notfound: return 1
return 0
def clear(self):
self._container.clear()
self._p_changed = 1
def update(self, d):
for k in d.keys():
self[k] = d[k]
# -----------------------------------------------------------------
# ImmutablyValuedMappingOfPickleableObjects (what a mouthful!)
#
def __setitem__(self, k, v):
# if the key or value is a persistent instance,
# set up its _p_jar immediately
if hasattr(v, '_p_jar') and v._p_jar is None:
v._p_jar = self._p_jar
v._p_changed = 1
if hasattr(k, '_p_jar') and k._p_jar is None:
k._p_jar = self._p_jar
k._p_changed = 1
self._container[k] = v
self._p_changed = 1
def __getitem__(self, k):
return self._container[k]
def __delitem__(self, k):
del self._container[k]
# -----------------------------------------------------------------
# TTWDictionary
#
set = __setitem__
def delete(self, k):
del self._container[k]
self._p_changed = 1
__guarded_setitem__ = __setitem__
# -----------------------------------------------------------------
# Other non interface code
#
def _p_independent(self):
# My state doesn't depend on or materially effect the state of
# other objects (eliminates read conflicts).
return 1
def _p_resolveConflict(self, saved, state1, state2):
attrs = ['token', 'id', '_created', '_invalid']
# note that last_accessed and _container are the only attrs
# missing from this list. The only time we can clearly resolve
# the conflict is if everything but the last_accessed time and
# the contents are the same, so we make sure nothing else has
# changed. We're being slightly sneaky here by accepting
# possibly conflicting data in _container, but it's acceptable
# in this context.
LOG('Transience', BLATHER, 'Resolving conflict in TransientObject')
for attr in attrs:
old = saved.get(attr)
st1 = state1.get(attr)
st2 = state2.get(attr)
if not (old == st1 == st2):
return None
# return the object with the most recent last_accessed value.
if state1['_last_accessed'] > state2['_last_accessed']:
return state1
else:
return state2
getName = getId # this is for SQLSession compatibility
def _generateUniqueId(self):
t = str(int(time.time()))
d = "%010d" % random.randint(0, sys.maxint-1)
return "%s%s" % (t, d)
def __repr__(self):
return "id: %s, token: %s, contents: %s" % (
self.id, self.token, `self.items()`
)
Globals.InitializeClass(TransientObject)
......@@ -85,10 +85,15 @@
"""
Transience initialization routines
$Id: __init__.py,v 1.4 2001/11/07 06:46:36 chrism Exp $
$Id: __init__.py,v 1.5 2001/11/21 22:46:36 chrism Exp $
"""
import ZODB # this is to help out testrunner, don't remove.
import Transience
# import of MaxTransientObjectsExceeded for easy import from scripts,
# this is protected by a module security info declaration in the
# Sessions package.
from Transience import MaxTransientObjectsExceeded
def initialize(context):
context.registerClass(
......@@ -100,3 +105,4 @@ def initialize(context):
)
context.registerHelp()
context.registerHelpTitle('Zope Help')
......@@ -73,6 +73,20 @@ the Zope physical path to the method to be invoked to receive the notification
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<div class="form-label">
Maximum number of subobjects
</div>
<div class="form-help">
("0" means infinite)
</div>
</TD>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="TEXT" NAME="limit:int" SIZE="10" value="1000">
</TD>
</TR>
<TR>
<TD ALIGN="LEFT" VALIGN="TOP">
<div class="form-label">
......
......@@ -13,7 +13,7 @@ Transient Object Containers are used to store transient data.
Transient data will persist, but only for a user-specified period of time
(the "data object timeout") after which it will be flushed.
</p>
<dtml-call nudge><!-- turn the buckets if necessary -->
<p class="form-label">
<font color="green">
<dtml-let l=getLen>
......@@ -55,6 +55,21 @@ Transient data will persist, but only for a user-specified period of time
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Maximum number of subobjects
</div>
<div class="form-help">
("0" means infinite)
</div>
</td>
<td align="left" valign="top">
<input type="text" name="limit:int" size=10
value=&dtml-getSubobjectLimit;>
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
......
......@@ -30,6 +30,19 @@ TransientObjectContainer - Add
they may not be deleted exactly after this number of minutes elapses.
A setting of "0" indicates that objects should not expire.
- **Maximum number of subobjects **
The maximum number of subobjects that this container may
simultaneously hold.
If the value is "0", the number of objects addable to the container
will be not be artificially limited.
Note: This setting is useful to prevent accidental or deliberate denial
of service due to RAM shortage if the transient object container is
instantiated in a storage which is backed solely by RAM, such
as a Temporary Folder.
- **Script to call when objects are added**
*Optional*
......
......@@ -34,6 +34,19 @@ TransientObjectContainer - Manage
If the timeout value is "0", objects will not time out.
- **Maximum number of subobjects **
The maximum number of subobjects that this container may
simultaneously hold.
If the value is "0", the number of objects addable to the container
will be not be artificially limited.
This setting is useful to prevent accidental or deliberate denial
of service due to RAM shortage if the transient object container is
instantiated in a storage which is backed solely by RAM, such
as a Temporary Folder.
- **Script to call when objects are added**
*Optional*
......
......@@ -112,13 +112,6 @@ class TransientObjectContainer:
Permission -- 'Access Transient Objects'
"""
def delete(self, k):
"""
Delete value associated with key k, raise a KeyError if nonexistent.
Permission -- 'Access Transient Objects'
"""
def new(self, k):
"""
Creates a new subobject of the type supported by this container
......@@ -129,6 +122,9 @@ class TransientObjectContainer:
"k" must be a string, else a TypeError is raised.
If the container is 'full', a MaxTransientObjectsExceeded will
be raised.
Permission -- 'Create Transient Objects'
"""
......@@ -142,6 +138,9 @@ class TransientObjectContainer:
"k" must be a string, else a TypeError is raised.
If the container is 'full', a MaxTransientObjectsExceeded exception
be raised.
Permission -- 'Create Transient Objects'
"""
......@@ -345,6 +344,16 @@ class TransientObject:
Permission -- Always available
"""
class MaxTransientObjectsExceeded:
"""
An exception importable from the Products.Transience.Transience module
which is raised when an attempt is made to add an item to a
TransientObjectContainer that is 'full'.
This exception may be caught in PythonScripts through a normal import.
A successful import of the exception can be achieved via::
from Products.Transience import MaxTransientObjectsExceeded
"""
import time as origtime
epoch = origtime.time()
def time():
""" False timer -- returns time 10 x faster than normal time """
return (origtime.time() - epoch) * 10.0
def sleep(duration):
""" False sleep -- sleep for 1/10 the time specifed """
origtime.sleep(duration / 10.0)
......@@ -10,15 +10,18 @@ import Acquisition
from Acquisition import aq_base
from Products.Transience.Transience import TransientObjectContainer
import Products.Transience.Transience
import Products.Transience.TransientObject
from Products.PythonScripts.PythonScript import PythonScript
from ZODB.POSException import InvalidObjectReference
from DateTime import DateTime
from unittest import TestCase, TestSuite, TextTestRunner, makeSuite
from ZODB.DemoStorage import DemoStorage
from OFS.Application import Application
import time, threading, whrandom
import threading, whrandom
import fauxtime
import time as oldtime
WRITEGRANULARITY = 30
epoch = time.time()
stuff = {}
def _getApp():
......@@ -54,6 +57,7 @@ def _delApp():
class TestBase(TestCase):
def setUp(self):
Products.Transience.Transience.time = fauxtime
Products.Transience.TransientObject.time = fauxtime
self.app = makerequest.makerequest(_getApp())
timeout = self.timeout = 1
sm=TransientObjectContainer(
......@@ -66,12 +70,14 @@ class TestBase(TestCase):
get_transaction().abort()
_delApp()
del self.app
Products.Transience.Transience.time = oldtime
Products.Transience.TransientObject.time = oldtime
class TestLastAccessed(TestBase):
def testLastAccessed(self):
sdo = self.app.sm.new_or_existing('TempObject')
la1 = sdo.getLastAccessed()
fauxsleep(WRITEGRANULARITY + 1)
fauxtime.sleep(WRITEGRANULARITY + 1)
sdo = self.app.sm['TempObject']
assert sdo.getLastAccessed() > la1, (sdo.getLastAccessed(), la1)
......@@ -79,7 +85,7 @@ class TestNotifications(TestBase):
def testAddNotification(self):
self.app.sm.setAddNotificationTarget(addNotificationTarget)
sdo = self.app.sm.new_or_existing('TempObject')
now = fauxtime()
now = fauxtime.time()
k = sdo.get('starttime')
assert type(k) == type(now)
assert k <= now
......@@ -88,35 +94,26 @@ class TestNotifications(TestBase):
self.app.sm.setDelNotificationTarget(delNotificationTarget)
sdo = self.app.sm.new_or_existing('TempObject')
timeout = self.timeout * 60
fauxsleep(timeout + (timeout * .33))
fauxtime.sleep(timeout + (timeout * .33))
try: sdo1 = self.app.sm['TempObject']
except KeyError: pass
now = fauxtime()
now = fauxtime.time()
k = sdo.get('endtime')
assert type(k) == type(now)
assert k <= now
def addNotificationTarget(item, context):
item['starttime'] = fauxtime()
item['starttime'] = fauxtime.time()
def delNotificationTarget(item, context):
item['endtime'] = fauxtime()
def fauxtime():
""" False timer -- returns time 10 x faster than normal time """
return (time.time() - epoch) * 10.0
def fauxsleep(duration):
""" False sleep -- sleep for 1/10 the time specifed """
time.sleep(duration / 10.0)
item['endtime'] = fauxtime.time()
def test_suite():
last_accessed = makeSuite(TestLastAccessed, 'test')
start_end = makeSuite(TestNotifications, 'test')
runner = TextTestRunner()
suite = TestSuite((start_end, last_accessed))
return suite
if __name__ == '__main__':
runner = TextTestRunner(sys.stdout)
runner = TextTestRunner(verbosity=9)
runner.run(test_suite())
......@@ -82,34 +82,38 @@
# attributions are listed in the accompanying credits file.
#
##############################################################################
import sys, os, time, whrandom, unittest
import sys, os, whrandom, unittest
if __name__ == "__main__":
sys.path.insert(0, '../../..')
#os.chdir('../../..')
import ZODB
from Products.Transience.Transience import \
TransientObjectContainer, TransientObject
from Products.Transience.Transience import TransientObjectContainer
from Products.Transience.TransientObject import TransientObject
import Products.Transience.TransientObject
import Products.Transience.Transience
from unittest import TestCase, TestSuite, TextTestRunner, makeSuite
epoch = time.time()
import time as oldtime
import fauxtime
class TestTransientObject(TestCase):
def setUp(self):
Products.Transience.Transience.time = fauxtime
Products.Transience.TransientObject.time = fauxtime
self.errmargin = .20
self.timeout = 60
Products.Transience.Transience.time = fauxtime
self.t = TransientObjectContainer('sdc', timeout_mins=self.timeout/60)
def tearDown(self):
Products.Transience.Transience.time = oldtime
Products.Transience.TransientObject.time = oldtime
self.t = None
del self.t
def test_id(self):
t = self.t.new('xyzzy')
assert t.getId() != 'xyzzy'
assert t.getContainerKey() == 'xyzzy'
def test_validate(self):
t = self.t.new('xyzzy')
......@@ -119,22 +123,22 @@ class TestTransientObject(TestCase):
def test_getLastAccessed(self):
t = self.t.new('xyzzy')
ft = fauxtime()
ft = fauxtime.time()
assert t.getLastAccessed() <= ft
def test_getCreated(self):
t = self.t.new('xyzzy')
ft = fauxtime()
ft = fauxtime.time()
assert t.getCreated() <= ft
def test_setLastAccessed(self):
t = self.t.new('xyzzy')
ft = fauxtime()
ft = fauxtime.time()
assert t.getLastAccessed() <= ft
fauxsleep(self.timeout) # go to sleep past the granuarity
ft2 = fauxtime()
fauxtime.sleep(self.timeout) # go to sleep past the granuarity
ft2 = fauxtime.time()
t.setLastAccessed()
ft3 = fauxtime()
ft3 = fauxtime.time()
assert t.getLastAccessed() <= ft3
assert t.getLastAccessed() >= ft2
......@@ -175,19 +179,11 @@ def test_suite():
alltests = TestSuite((testsuite,))
return alltests
def fauxtime():
""" False timer -- returns time 10 x faster than normal time """
return (time.time() - epoch) * 10.0
def fauxsleep(duration):
""" False sleep -- sleep for 1/10 the time specifed """
time.sleep(duration / 10.0)
data = {
'a': 'a',
1: 1,
'Mary': 'no little lamb for you today!',
'epoch': epoch,
'epoch': 999999999,
'fauxtime': fauxtime
}
......
......@@ -86,28 +86,30 @@ import sys, os, time, whrandom, unittest
if __name__ == "__main__":
sys.path.insert(0, '../../..')
#os.chdir('../../..')
import ZODB
from Products.Transience.Transience import \
TransientObjectContainer, TransientObject
from Products.Transience.Transience import TransientObjectContainer,\
MaxTransientObjectsExceeded
from Products.Transience.TransientObject import TransientObject
import Products.Transience.Transience
import Products.Transience.TransientObject
from ExtensionClass import Base
from unittest import TestCase, TestSuite, TextTestRunner, makeSuite
epoch = time.time()
stash = {}
import time as oldtime
import fauxtime
class TestTransientObjectContainer(TestCase):
def setUp(self):
Products.Transience.Transience.time = fauxtime
Products.Transience.TransientObject.time = fauxtime
self.errmargin = .20
self.timeout = 60
Products.Transience.Transience.time = fauxtime
self.t = TransientObjectContainer('sdc', timeout_mins=self.timeout/60)
def tearDown(self):
self.t = None
del self.t
Products.Transience.Transience.time = oldtime
Products.Transience.TransientObject.time = oldtime
def testGetItemFails(self):
self.assertRaises(KeyError, self._getitemfail)
......@@ -357,7 +359,7 @@ class TestTransientObjectContainer(TestCase):
for x in range(10, 110):
self.t[x] = x
# these items will time out while we sleep
fauxsleep(self.timeout * (self.errmargin+1))
fauxtime.sleep(self.timeout * (self.errmargin+1))
for x in range(110, 210):
self.t[x] = x
assert len(self.t.keys()) == 100, len(self.t.keys())
......@@ -374,7 +376,7 @@ class TestTransientObjectContainer(TestCase):
# 1 minute
for x in range(10, 110):
self.t[x] = x
fauxsleep(self.timeout * (self.errmargin+1))
fauxtime.sleep(self.timeout * (self.errmargin+1))
assert len(self.t.keys()) == 0, len(self.t.keys())
# 2 minutes
......@@ -382,9 +384,9 @@ class TestTransientObjectContainer(TestCase):
self.t._reset()
for x in range(10, 110):
self.t[x] = x
fauxsleep(self.timeout * (self.errmargin+1))
fauxtime.sleep(self.timeout * (self.errmargin+1))
assert len(self.t.keys()) == 100, len(self.t.keys())
fauxsleep(self.timeout * (self.errmargin+1))
fauxtime.sleep(self.timeout * (self.errmargin+1))
assert len(self.t.keys()) == 0, len(self.t.keys())
# 3 minutes
......@@ -392,22 +394,22 @@ class TestTransientObjectContainer(TestCase):
self.t._reset()
for x in range(10, 110):
self.t[x] = x
fauxsleep(self.timeout * (self.errmargin+1))
fauxtime.sleep(self.timeout * (self.errmargin+1))
assert len(self.t.keys()) == 100, len(self.t.keys())
fauxsleep(self.timeout * (self.errmargin+1))
fauxtime.sleep(self.timeout * (self.errmargin+1))
assert len(self.t.keys()) == 100, len(self.t.keys())
fauxsleep(self.timeout * (self.errmargin+1))
fauxtime.sleep(self.timeout * (self.errmargin+1))
assert len(self.t.keys()) == 0, len(self.t.keys())
def testGetItemDelaysTimeout(self):
for x in range(10, 110):
self.t[x] = x
# current bucket will become old after we sleep for a while.
fauxsleep(self.timeout/2)
fauxtime.sleep(self.timeout/2)
# these items will be added to the new current bucket by getitem
for x in range(10, 110):
self.t[x]
fauxsleep(self.timeout/2)
fauxtime.sleep(self.timeout/2)
assert len(self.t.keys()) == 100, len(self.t.keys())
for x in range(10, 110):
assert self.t[x] == x
......@@ -416,11 +418,11 @@ class TestTransientObjectContainer(TestCase):
for x in range(10, 110):
self.t[x] = x
# current bucket will become old after we sleep for a while.
fauxsleep(self.timeout/2)
fauxtime.sleep(self.timeout/2)
# these items will be added to the new current bucket by getitem
for x in range(10, 110):
self.t[x] = x + 1
fauxsleep(self.timeout/2)
fauxtime.sleep(self.timeout/2)
assert len(self.t.keys()) == 100, len(self.t.keys())
for x in range(10, 110):
assert self.t[x] == x + 1
......@@ -429,11 +431,11 @@ class TestTransientObjectContainer(TestCase):
for x in range(10, 110):
self.t[x] = x
# current bucket will become old after we sleep for a while.
fauxsleep(self.timeout/2)
fauxtime.sleep(self.timeout/2)
# these items will be added to the new current bucket by getitem
for x in range(10, 110):
self.t.get(x)
fauxsleep(self.timeout/2)
fauxtime.sleep(self.timeout/2)
assert len(self.t.keys()) == 100, len(self.t.keys())
for x in range(10, 110):
assert self.t[x] == x
......@@ -479,9 +481,18 @@ class TestTransientObjectContainer(TestCase):
def test_getId(self):
assert self.t.getId() == 'sdc'
def test_getContainerKey(self):
t = self.t.new('foobieblech')
assert t.getContainerKey() == 'foobieblech'
def testSubobjectLimitWorks(self):
self.t = TransientObjectContainer('a', timeout_mins=self.timeout/60,
limit=10)
self.assertRaises(MaxTransientObjectsExceeded, self._maxOut)
def testUnlimitedSubobjectLimitWorks(self):
self._maxOut()
def _maxOut(self):
for x in range(11):
self.t.new(str(x))
def lsubtract(l1, l2):
l1=list(l1)
......@@ -491,19 +502,10 @@ def lsubtract(l1, l2):
return l
def test_suite():
#print "TransientObjectContainer tests take just about forever (10+ mins)"
testsuite = makeSuite(TestTransientObjectContainer, 'test')
alltests = TestSuite((testsuite,))
return alltests
def fauxtime():
""" False timer -- returns time 10 x faster than normal time """
return (time.time() - epoch) * 10.0
def fauxsleep(duration):
""" False sleep -- sleep for 1/10 the time specifed """
time.sleep(duration / 10.0)
if __name__ == '__main__':
runner = TextTestRunner(verbosity=9)
runner.run(test_suite())
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment