/appengine_django/tests/serialization_test.py

http://google-app-engine-django.googlecode.com/ · Python · 326 lines · 290 code · 13 blank · 23 comment · 0 complexity · 009dad01bbaab2a869a1a3c9cf6e4fad MD5 · raw file

  1. #!/usr/bin/python2.4
  2. #
  3. # Copyright 2008 Google Inc.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """Tests that the serialization modules are functioning correctly.
  17. In particular, these tests verify that the modifications made to the standard
  18. Django serialization modules function correctly and that the combined datastore
  19. and Django models can be dumped and loaded to all of the provided formats.
  20. """
  21. import os
  22. import re
  23. import unittest
  24. from StringIO import StringIO
  25. from django.core import serializers
  26. from google.appengine.ext import db
  27. from appengine_django.models import BaseModel
  28. class ModelA(BaseModel):
  29. description = db.StringProperty()
  30. class ModelB(BaseModel):
  31. description = db.StringProperty()
  32. friend = db.Reference(ModelA)
  33. class ModelC(BaseModel):
  34. dt_value = db.DateTimeProperty(auto_now_add=True)
  35. d_value = db.DateProperty(auto_now_add=True)
  36. t_value = db.TimeProperty(auto_now_add=True)
  37. class TestAllFormats(type):
  38. def __new__(cls, name, bases, attrs):
  39. """Extends base test functions to be called for every serialisation format.
  40. Looks for functions matching 'run.*Test', where the wildcard in the middle
  41. matches the desired test name and ensures that a test case is setup to call
  42. that function once for every defined serialisation format. The test case
  43. that is created will be called 'test<format><name>'. Eg, for the function
  44. 'runKeyedObjectTest' functions like 'testJsonKeyedObject' will be created.
  45. """
  46. test_formats = serializers.get_serializer_formats()
  47. test_formats.remove("python") # Python serializer is only used indirectly.
  48. for func_name in attrs.keys():
  49. m = re.match("^run(.*)Test$", func_name)
  50. if not m:
  51. continue
  52. for format in test_formats:
  53. test_name = "test%s%s" % (format.title(), m.group(1))
  54. test_func = eval("lambda self: getattr(self, \"%s\")(\"%s\")" %
  55. (func_name, format))
  56. attrs[test_name] = test_func
  57. # ensure keys match the current app ID by populating them dynamically:
  58. obj = ModelA(key_name="test")
  59. obj.put()
  60. pk = obj.key()
  61. for k, v in attrs['SERIALIZED_WITH_NON_EXISTANT_PARENT'].items():
  62. attrs['SERIALIZED_WITH_NON_EXISTANT_PARENT'][k] = v % str(pk)
  63. return super(TestAllFormats, cls).__new__(cls, name, bases, attrs)
  64. class SerializationTest(unittest.TestCase):
  65. """Unit tests for the serialization/deserialization functionality.
  66. Tests that every loaded serialization format can successfully dump and then
  67. reload objects without the objects changing.
  68. """
  69. __metaclass__ = TestAllFormats
  70. def compareObjects(self, orig, new, format="unknown"):
  71. """Compares two objects to ensure they are identical.
  72. Args:
  73. orig: The original object, must be an instance of db.Model.
  74. new: The new object, must be an instance of db.Model.
  75. format: The serialization format being tested, used to make error output
  76. more helpful.
  77. Raises:
  78. The function has no return value, but will raise assertion errors if the
  79. objects do not match correctly.
  80. """
  81. if orig.key().name():
  82. # Only compare object keys when the key is named. Key IDs are not static
  83. # and will change between dump/load. If you want stable Keys they need to
  84. # be named!
  85. self.assertEqual(orig.key(), new.key(),
  86. "keys not equal after %s serialization: %s != %s" %
  87. (format, repr(orig.key()), repr(new.key())))
  88. for key in orig.properties().keys():
  89. oval = getattr(orig, key)
  90. nval = getattr(new, key)
  91. if isinstance(orig.properties()[key], db.Reference):
  92. # Need to compare object keys not the objects themselves.
  93. oval = oval.key()
  94. nval = nval.key()
  95. self.assertEqual(oval, nval, "%s attribute differs after %s "
  96. "serialization: %s != %s" % (key, format, oval, nval))
  97. def doSerialisationTest(self, format, obj, rel_attr=None, obj_ref=None):
  98. """Runs a serialization test on an object for the specified format.
  99. Args:
  100. format: The name of the Django serialization class to use.
  101. obj: The object to {,de}serialize, must be an instance of db.Model.
  102. rel_attr: Name of the attribute of obj references another model.
  103. obj_ref: The expected object reference, must be an instance of db.Model.
  104. Raises:
  105. The function has no return value but raises assertion errors if the
  106. object cannot be successfully serialized and then deserialized back to an
  107. identical object. If rel_attr and obj_ref are specified the deserialized
  108. object must also retain the references from the original object.
  109. """
  110. serialised = serializers.serialize(format, [obj])
  111. # Try and get the object back from the serialized string.
  112. result = list(serializers.deserialize(format, StringIO(serialised)))
  113. self.assertEqual(1, len(result),
  114. "%s serialization should create 1 object" % format)
  115. result[0].save() # Must save back into the database to get a Key.
  116. self.compareObjects(obj, result[0].object, format)
  117. if rel_attr and obj_ref:
  118. rel = getattr(result[0].object, rel_attr)
  119. if callable(rel):
  120. rel = rel()
  121. self.compareObjects(rel, obj_ref, format)
  122. def doLookupDeserialisationReferenceTest(self, lookup_dict, format):
  123. """Tests the Key reference is loaded OK for a format.
  124. Args:
  125. lookup_dict: A dictionary indexed by format containing serialized strings
  126. of the objects to load.
  127. format: The format to extract from the dict and deserialize.
  128. Raises:
  129. This function has no return value but raises assertion errors if the
  130. string cannot be deserialized correctly or the resulting object does not
  131. reference the object correctly.
  132. """
  133. if format not in lookup_dict:
  134. # Check not valid for this format.
  135. return
  136. obj = ModelA(description="test object", key_name="test")
  137. obj.put()
  138. s = lookup_dict[format]
  139. result = list(serializers.deserialize(format, StringIO(s)))
  140. self.assertEqual(1, len(result), "expected 1 object from %s" % format)
  141. result[0].save()
  142. self.compareObjects(obj, result[0].object.friend, format)
  143. def doModelKeyDeserialisationReferenceTest(self, lookup_dict, format):
  144. """Tests a model with a key can be loaded OK for a format.
  145. Args:
  146. lookup_dict: A dictionary indexed by format containing serialized strings
  147. of the objects to load.
  148. format: The format to extract from the dict and deserialize.
  149. Returns:
  150. This function has no return value but raises assertion errors if the
  151. string cannot be deserialized correctly or the resulting object is not an
  152. instance of ModelA with a key named 'test'.
  153. """
  154. if format not in lookup_dict:
  155. # Check not valid for this format.
  156. return
  157. s = lookup_dict[format]
  158. result = list(serializers.deserialize(format, StringIO(s)))
  159. self.assertEqual(1, len(result), "expected 1 object from %s" % format)
  160. result[0].save()
  161. self.assert_(isinstance(result[0].object, ModelA))
  162. self.assertEqual("test", result[0].object.key().name())
  163. # Lookup dicts for the above (doLookupDeserialisationReferenceTest) function.
  164. SERIALIZED_WITH_KEY_AS_LIST = {
  165. "json": """[{"pk": "agR0ZXN0chMLEgZNb2RlbEIiB21vZGVsYmkM", """
  166. """"model": "tests.modelb", "fields": {"description": "test", """
  167. """"friend": ["ModelA", "test"] }}]""",
  168. "yaml": """- fields: {description: !!python/unicode 'test', friend: """
  169. """ [ModelA, test]}\n model: tests.modelb\n pk: """
  170. """ agR0ZXN0chMLEgZNb2RlbEEiB21vZGVsYWkM\n"""
  171. }
  172. SERIALIZED_WITH_KEY_REPR = {
  173. "json": """[{"pk": "agR0ZXN0chMLEgZNb2RlbEIiB21vZGVsYmkM", """
  174. """"model": "tests.modelb", "fields": {"description": "test", """
  175. """"friend": "datastore_types.Key.from_path("""
  176. """'ModelA', 'test')" }}]""",
  177. "yaml": """- fields: {description: !!python/unicode 'test', friend: """
  178. """\'datastore_types.Key.from_path("ModelA", "test")\'}\n """
  179. """model: tests.modelb\n pk: """
  180. """ agR0ZXN0chMLEgZNb2RlbEEiB21vZGVsYWkM\n"""
  181. }
  182. # Lookup dict for the doModelKeyDeserialisationReferenceTest function.
  183. MK_SERIALIZED_WITH_LIST = {
  184. "json": """[{"pk": ["ModelA", "test"], "model": "tests.modela", """
  185. """"fields": {}}]""",
  186. "yaml": """-\n fields: {description: null}\n model: tests.modela\n """
  187. """pk: [ModelA, test]\n"""
  188. }
  189. MK_SERIALIZED_WITH_KEY_REPR = {
  190. "json": """[{"pk": "datastore_types.Key.from_path('ModelA', 'test')", """
  191. """"model": "tests.modela", "fields": {}}]""",
  192. "yaml": """-\n fields: {description: null}\n model: tests.modela\n """
  193. """pk: \'datastore_types.Key.from_path("ModelA", "test")\'\n"""
  194. }
  195. MK_SERIALIZED_WITH_KEY_AS_TEXT = {
  196. "json": """[{"pk": "test", "model": "tests.modela", "fields": {}}]""",
  197. "yaml": """-\n fields: {description: null}\n model: tests.modela\n """
  198. """pk: test\n"""
  199. }
  200. # Lookup dict for the function.
  201. # Note that pk values are set in __new__()
  202. SERIALIZED_WITH_NON_EXISTANT_PARENT = {
  203. "json": """[{"pk": "%s", """
  204. """"model": "tests.modela", "fields": """
  205. """{"description": null}}]""",
  206. "yaml": """- fields: {description: null}\n """
  207. """model: tests.modela\n """
  208. """pk: %s\n""",
  209. "xml": """<?xml version="1.0" encoding="utf-8"?>\n"""
  210. """<django-objects version="1.0">\n"""
  211. """<entity kind="tests.modela" key="%s">\n """
  212. """<key>tag:google-app-engine-django.gmail.com,"""
  213. """2008-05-13:ModelA[ahhnb29nbGUtYXBwLWVuZ2luZS1kam"""
  214. """FuZ29yIgsSBk1vZGVsQiIGcGFyZW50DAsSBk1vZGVsQSIEdGVzdAw"""
  215. """]</key>\n <property name="description" """
  216. """type="null"></property>\n</entity>\n</django-objects>"""
  217. }
  218. # The following functions are all expanded by the metaclass to be run once
  219. # for every registered Django serialization module.
  220. def runKeyedObjectTest(self, format):
  221. """Test serialization of a basic object with a named key."""
  222. obj = ModelA(description="test object", key_name="test")
  223. obj.put()
  224. self.doSerialisationTest(format, obj)
  225. def runObjectWithIdTest(self, format):
  226. """Test serialization of a basic object with a numeric ID key."""
  227. obj = ModelA(description="test object")
  228. obj.put()
  229. self.doSerialisationTest(format, obj)
  230. def runObjectWithReferenceTest(self, format):
  231. """Test serialization of an object that references another object."""
  232. obj = ModelA(description="test object", key_name="test")
  233. obj.put()
  234. obj2 = ModelB(description="friend object", friend=obj)
  235. obj2.put()
  236. self.doSerialisationTest(format, obj2, "friend", obj)
  237. def runObjectWithParentTest(self, format):
  238. """Test serialization of an object that has a parent object reference."""
  239. obj = ModelA(description="parent object", key_name="parent")
  240. obj.put()
  241. obj2 = ModelA(description="child object", key_name="child", parent=obj)
  242. obj2.put()
  243. self.doSerialisationTest(format, obj2, "parent", obj)
  244. def runObjectWithNonExistantParentTest(self, format):
  245. """Test deserialization of an object referencing a non-existant parent."""
  246. self.doModelKeyDeserialisationReferenceTest(
  247. self.SERIALIZED_WITH_NON_EXISTANT_PARENT, format)
  248. def runCreateKeyReferenceFromListTest(self, format):
  249. """Tests that a reference specified as a list in json/yaml can be loaded OK."""
  250. self.doLookupDeserialisationReferenceTest(self.SERIALIZED_WITH_KEY_AS_LIST,
  251. format)
  252. def runCreateKeyReferenceFromReprTest(self, format):
  253. """Tests that a reference specified as repr(Key) in can loaded OK."""
  254. self.doLookupDeserialisationReferenceTest(self.SERIALIZED_WITH_KEY_REPR,
  255. format)
  256. def runCreateModelKeyFromListTest(self, format):
  257. """Tests that a model key specified as a list can be loaded OK."""
  258. self.doModelKeyDeserialisationReferenceTest(self.MK_SERIALIZED_WITH_LIST,
  259. format)
  260. def runCreateModelKeyFromReprTest(self, format):
  261. """Tests that a model key specified as a repr(Key) can be loaded OK."""
  262. self.doModelKeyDeserialisationReferenceTest(
  263. self.MK_SERIALIZED_WITH_KEY_REPR, format)
  264. def runCreateModelKeyFromTextTest(self, format):
  265. """Tests that a reference specified as a plain key_name loads OK."""
  266. self.doModelKeyDeserialisationReferenceTest(
  267. self.MK_SERIALIZED_WITH_KEY_AS_TEXT, format)
  268. def runDateTimeTest(self, format):
  269. """Tests that db.DateTimeProperty and related can be correctly handled."""
  270. obj = ModelC()
  271. obj.put()
  272. self.doSerialisationTest(format, obj)
  273. if __name__ == '__main__':
  274. unittest.main()