[livecoding] r140 committed - - tests/test_reloading.py: testAttributeLeaking now replaced by testUp...

2 views
Skip to first unread message

livec...@googlecode.com

unread,
Feb 13, 2010, 2:35:17 AM2/13/10
to py-livecod...@googlegroups.com
Revision: 140
Author: richard.m.tew
Date: Fri Feb 12 23:34:45 2010
Log: - tests/test_reloading.py: testAttributeLeaking now replaced by
testUpdateAttributeLeaking and testUpdateAttributeLeaking.
- reloader.py: Leaked attributes are not namespace leaks. And both the
exportable and copyable to globals values that come from
ScriptFile.GetExportableAttributes are put in the appropriate places.
- namespace.py: Contributed attributes are now namespace contributions.
And GetExportableAttributes provides values that should be copied into the
globals of the destination script and values that should actually be
exported into the namespace in a way that can be differentiated. Rather
than just providing the latter type of value.
http://code.google.com/p/livecoding/source/detail?r=140

Modified:
/trunk/namespace.py
/trunk/reloader.py
/trunk/tests/test_reloading.py

=======================================
--- /trunk/namespace.py Fri Feb 12 18:43:15 2010
+++ /trunk/namespace.py Fri Feb 12 23:34:45 2010
@@ -19,7 +19,7 @@

class ScriptFile(object):
lastError = None
- contributedAttributes = None
+ namespaceContributions = None

def __init__(self, filePath, namespacePath, implicitLoad=True,
delGlobals=False):
self.filePath = filePath
@@ -48,11 +48,11 @@
def GetAttributeValue(self, attributeName):
return self.scriptGlobals[attributeName]

- def SetContributedAttributes(self, contributedAttributes):
- self.contributedAttributes = contributedAttributes
-
- def AddContributedAttributes(self, contributedAttributes):
- self.contributedAttributes |= contributedAttributes
+ def SetNamespaceContributions(self, namespaceContributions):
+ self.namespaceContributions = namespaceContributions
+
+ def AddNamespaceContributions(self, namespaceContributions):
+ self.namespaceContributions |= namespaceContributions

def Run(self):
self.scriptGlobals = {}
@@ -141,19 +141,19 @@
continue

valueType = type(v)
+ exportable = True
# Modules will have been imported from elsewhere.
- #if isinstance(v, types.ModuleType):
- # continue
-
- if valueType in (types.ClassType, types.TypeType):
+ if isinstance(v, types.ModuleType):
+ exportable = False
+ elif valueType in (types.ClassType, types.TypeType):
# Classes with valid modules will have been imported from
elsewhere.
if v.__module__ != "__builtin__":
- continue
+ exportable = False
# Skip actual builtin objects.
- if v in builtinValues:
- continue
-
- yield k, v, valueType
+ elif v in builtinValues:
+ exportable = False
+
+ yield k, v, valueType, exportable


class ScriptDirectory(object):
@@ -379,29 +379,32 @@
namespace.__file__ += ";"
namespace.__file__ += scriptFile.filePath

- contributedAttributes = set()
- for k, v, valueType in scriptFile.GetExportableAttributes():
+ namespaceContributions = set()
+ for k, v, valueType, exportable in
scriptFile.GetExportableAttributes():
+ logger.debug("InsertModuleAttribute %s.%s exported=%s",
moduleName, k, exportable)
+
+ if not exportable:
+ logger.debug("Added a non-exported global: %s %s", k,
valueType)
+ continue
+
# By default we never overwrite. This way we can identify
duplicate contributions.
if hasattr(namespace, k) and k not in overwritableAttributes:
- logger.error("Duplicate namespace contribution for '%s.%s'
from '%s', our class = %s", moduleName, k, scriptFile.filePath, v.__file__
== scriptFile.filePath)
+ logger.error("Duplicate namespace contribution for '%s.%s'
from '%s', our class = %s", moduleName, k, scriptFile.filePath, v.__file__
== scriptFile.filePath)
continue

- logger.debug("InsertModuleAttribute %s.%s", moduleName, k)
-
if valueType in (types.ClassType, types.TypeType):
v.__module__ = moduleName
v.__file__ = scriptFile.filePath

setattr(namespace, k, v)
- contributedAttributes.add(k)
-
- logger.info("Added '%s.%s'", moduleName, k)
-
+
+ namespaceContributions.add(k)
+
if type(v) in (types.TypeType, types.ClassType):
self.BroadcastClassCreationEvent(namespace, k, v)
# print namespace, k, type(v)

- scriptFile.SetContributedAttributes(contributedAttributes)
+ scriptFile.SetNamespaceContributions(namespaceContributions)

def BroadcastClassCreationEvent(self, *args):
if self.classCreationCallback:
@@ -417,7 +420,7 @@

def RemoveModuleAttributes(self, scriptFile, namespace):
logger.debug("RemoveModuleAttributes %s", scriptFile.filePath)
- if scriptFile.contributedAttributes is None:
+ if scriptFile.namespaceContributions is None:
return True

paths = namespace.__file__.split(";")
@@ -426,7 +429,7 @@
paths.remove(scriptFile.filePath)
namespace.__file__ = ";".join(paths)

- for k in scriptFile.contributedAttributes:
+ for k in scriptFile.namespaceContributions:
delattr(namespace, k)

return True
=======================================
--- /trunk/reloader.py Fri Feb 12 18:43:15 2010
+++ /trunk/reloader.py Fri Feb 12 23:34:45 2010
@@ -9,17 +9,18 @@

logger = logging.getLogger("reloader")

-import namespace
+# TODO: rename 'namespace.py' to 'namespaces.py' ... need to think about
it...
+import namespace as namespaces

MODE_OVERWRITE = 1
MODE_UPDATE = 2

class NonExistentValue: pass

-class ReloadableScriptFile(namespace.ScriptFile):
+class ReloadableScriptFile(namespaces.ScriptFile):
version = 1

-class ReloadableScriptDirectory(namespace.ScriptDirectory):
+class ReloadableScriptDirectory(namespaces.ScriptDirectory):
scriptFileClass = ReloadableScriptFile
unitTest = True

@@ -33,7 +34,7 @@
self.monitorFileChanges = monitorFileChanges

self.directoriesByPath = {}
- self.leakedAttributes = {}
+ self.namespaceLeaks = {}
self.classCreationCallback = None
self.classUpdateCallback = None

@@ -212,12 +213,12 @@
scriptDirectory.UnregisterScript(oldScriptFile)
scriptDirectory.RegisterScript(newScriptFile)

- scriptDirectory.SetModuleAttributes(newScriptFile, namespace,
overwritableAttributes=self.leakedAttributes)
+ scriptDirectory.SetModuleAttributes(newScriptFile, namespace,
overwritableAttributes=self.namespaceLeaks)

# Remove as leaks the attributes the new version contributed.
self.RemoveLeakedAttributes(newScriptFile)
elif self.mode == MODE_UPDATE:
- self.UpdateModuleAttributes(oldScriptFile, newScriptFile,
namespace, overwritableAttributes=self.leakedAttributes)
+ self.UpdateModuleAttributes(oldScriptFile, newScriptFile,
namespace, overwritableAttributes=self.namespaceLeaks)
oldScriptFile.version += 1

# Remove as leaks the attributes the new version contributed.
@@ -237,28 +238,37 @@
attributeChanges = {}

# Collect existing values for entries.
- for k in scriptFile.contributedAttributes:
+ for k in scriptFile.namespaceContributions:
v = getattr(namespace, k)
valueType = type(v)
attributeChanges[k] = [ (v, valueType), (NonExistentValue,
None) ]

- # Collect entries for the attributes contributed by the new script
file.
- for k, v, valueType in newScriptFile.GetExportableAttributes():
- if k not in attributeChanges:
- attributeChanges[k] = [ (NonExistentValue, None), (v,
valueType) ]
+ # Collect entries for the attributes imported or defined by the
new script file.
+ for k, v, valueType, exportable in
newScriptFile.GetExportableAttributes():
+ if exportable:
+ if hasattr(namespace, k) and k not in
overwritableAttributes:
+ logger.error("Duplicate namespace contribution
for '%s.%s' from '%s', our class = %s", moduleName, k, scriptFile.filePath,
v.__file__ == scriptFile.filePath)
+ continue
+
+ if k not in attributeChanges:
+ attributeChanges[k] = [ (NonExistentValue, None), (v,
valueType) ]
+ else:
+ attributeChanges[k][1] = (v, valueType)
else:
- attributeChanges[k][1] = (v, valueType)
+ if k in scriptFile.scriptGlobals:
+ logger.debug("Updated a non-exported global: %s %s",
k, valueType)
+ else:
+ logger.debug("Added a non-exported global: %s %s", k,
valueType)
+ scriptFile.scriptGlobals[k] = v

# The globals dictionary of the retained original script file.
globals_ = scriptFile.scriptGlobals

- contributedAttributes = set()
- leakedAttributes = set()
+ namespaceContributions = set()

for attrName, ((oldValue, oldType), (newValue, newType)) in
attributeChanges.iteritems():
# No new value -> the old value is being leaked.
if newValue is NonExistentValue:
- leakedAttributes.add(attrName)
continue

if newType is types.ClassType or newType is types.TypeType:
@@ -266,7 +276,7 @@

# If there was an old value, it is updated.
if oldValue is not None:
- contributedAttributes.add(attrName)
+ namespaceContributions.add(attrName)
continue

# Otherwise, the new value is being added.
@@ -276,7 +286,7 @@
logger.debug("Encountered new class '%s'", attrName)
elif oldType is newType and oldValue == newValue:
# Skip constants whose value has not changed.
- logger.debug("Skippped unchanged attribute '%s'", attrName)
+ logger.debug("Skipped unchanged attribute '%s'", attrName)
continue
elif isinstance(newValue, types.FunctionType):
logger.debug("Rebound method '%s'", attrName)
@@ -289,11 +299,10 @@
globals_[attrName] = newValue

setattr(namespace, attrName, newValue)
- contributedAttributes.add(attrName)
-
- scriptFile.AddContributedAttributes(contributedAttributes)
- newScriptFile.SetContributedAttributes(contributedAttributes)
- # self.leakedAttributes |= leakedAttributes
+ namespaceContributions.add(attrName)
+
+ scriptFile.AddNamespaceContributions(namespaceContributions)
+ newScriptFile.SetNamespaceContributions(namespaceContributions)

def UpdateClass(self, value, newValue, globals_):
if value is None or value is NonExistentValue:
@@ -335,21 +344,21 @@
# Leaked attribute support

def IsAttributeLeaked(self, attributeName):
- return attributeName in self.leakedAttributes
+ return attributeName in self.namespaceLeaks

def GetLeakedAttributeVersion(self, attributeName):
- return self.leakedAttributes[attributeName][1]
+ return self.namespaceLeaks[attributeName][1]

def AddLeakedAttributes(self, oldScriptFile):
filePath = oldScriptFile.filePath

- for attributeName in oldScriptFile.contributedAttributes:
- self.leakedAttributes[attributeName] = (filePath,
oldScriptFile.version)
+ for attributeName in oldScriptFile.namespaceContributions:
+ self.namespaceLeaks[attributeName] = (filePath,
oldScriptFile.version)

def RemoveLeakedAttributes(self, newScriptFile):
- for attributeName in newScriptFile.contributedAttributes:
- if attributeName in self.leakedAttributes:
- del self.leakedAttributes[attributeName]
+ for attributeName in newScriptFile.namespaceContributions:
+ if attributeName in self.namespaceLeaks:
+ del self.namespaceLeaks[attributeName]

#
------------------------------------------------------------------------
# Attribute compatibility support
=======================================
--- /trunk/tests/test_reloading.py Fri Feb 12 18:43:15 2010
+++ /trunk/tests/test_reloading.py Fri Feb 12 23:34:45 2010
@@ -706,7 +706,13 @@
# Test that a bad path will not find a handler when there are
valid ones for other paths.
self.failUnless(cr.FindDirectory("unregistered path") is
None, "Got a script directory handler for an unregistered path")

- def testAttributeLeaking(self):
+ def testUpdateAttributeLeaking(self):
+ self.gtestAttributeLeaking(reloader.MODE_UPDATE)
+
+ def testOverwriteAttributeLeaking(self):
+ self.gtestAttributeLeaking(reloader.MODE_OVERWRITE)
+
+ def gtestAttributeLeaking(self, reloadingMode):
"""
This test is intended to exercise the leaked attribute tracking.

@@ -733,7 +739,7 @@
leakName = "NewStyleSubclassViaClassReference"

scriptDirPath = GetScriptDirectory()
- cr = self.codeReloader = reloader.CodeReloader()
+ cr = self.codeReloader = reloader.CodeReloader(mode=reloadingMode)
cr.scriptDirectoryClass = ReloadableScriptDirectoryNoUnitTesting
scriptDirectory = cr.AddDirectory("game", scriptDirPath)
self.failUnless(scriptDirectory is not None, "Script loading
failure")

Reply all
Reply to author
Forward
0 new messages