PageRenderTime 41ms CodeModel.GetById 20ms app.highlight 17ms RepoModel.GetById 1ms app.codeStats 0ms

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