summaryrefslogtreecommitdiff
path: root/tests/foreign_object/tests.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/foreign_object/tests.py')
-rw-r--r--tests/foreign_object/tests.py103
1 files changed, 103 insertions, 0 deletions
diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py
index 72d50cad6b..8b61c87e5d 100644
--- a/tests/foreign_object/tests.py
+++ b/tests/foreign_object/tests.py
@@ -1,4 +1,6 @@
+import copy
import datetime
+import pickle
from operator import attrgetter
from django.core.exceptions import FieldError
@@ -482,3 +484,104 @@ class TestExtraJoinFilterQ(TestCase):
qs = qs.select_related('active_translation_q')
with self.assertNumQueries(1):
self.assertEqual(qs[0].active_translation_q.title, 'title')
+
+
+class TestCachedPathInfo(TestCase):
+ def test_equality(self):
+ """
+ The path_infos and reverse_path_infos attributes are equivalent to
+ calling the get_<method>() with no arguments.
+ """
+ foreign_object = Membership._meta.get_field('person')
+ self.assertEqual(
+ foreign_object.path_infos,
+ foreign_object.get_path_info(),
+ )
+ self.assertEqual(
+ foreign_object.reverse_path_infos,
+ foreign_object.get_reverse_path_info(),
+ )
+
+ def test_copy_removes_direct_cached_values(self):
+ """
+ Shallow copying a ForeignObject (or a ForeignObjectRel) removes the
+ object's direct cached PathInfo values.
+ """
+ foreign_object = Membership._meta.get_field('person')
+ # Trigger storage of cached_property into ForeignObject's __dict__.
+ foreign_object.path_infos
+ foreign_object.reverse_path_infos
+ # The ForeignObjectRel doesn't have reverse_path_infos.
+ foreign_object.remote_field.path_infos
+ self.assertIn('path_infos', foreign_object.__dict__)
+ self.assertIn('reverse_path_infos', foreign_object.__dict__)
+ self.assertIn('path_infos', foreign_object.remote_field.__dict__)
+ # Cached value is removed via __getstate__() on ForeignObjectRel
+ # because no __copy__() method exists, so __reduce_ex__() is used.
+ remote_field_copy = copy.copy(foreign_object.remote_field)
+ self.assertNotIn('path_infos', remote_field_copy.__dict__)
+ # Cached values are removed via __copy__() on ForeignObject for
+ # consistency of behavior.
+ foreign_object_copy = copy.copy(foreign_object)
+ self.assertNotIn('path_infos', foreign_object_copy.__dict__)
+ self.assertNotIn('reverse_path_infos', foreign_object_copy.__dict__)
+ # ForeignObjectRel's remains because it's part of a shallow copy.
+ self.assertIn('path_infos', foreign_object_copy.remote_field.__dict__)
+
+ def test_deepcopy_removes_cached_values(self):
+ """
+ Deep copying a ForeignObject removes the object's cached PathInfo
+ values, including those of the related ForeignObjectRel.
+ """
+ foreign_object = Membership._meta.get_field('person')
+ # Trigger storage of cached_property into ForeignObject's __dict__.
+ foreign_object.path_infos
+ foreign_object.reverse_path_infos
+ # The ForeignObjectRel doesn't have reverse_path_infos.
+ foreign_object.remote_field.path_infos
+ self.assertIn('path_infos', foreign_object.__dict__)
+ self.assertIn('reverse_path_infos', foreign_object.__dict__)
+ self.assertIn('path_infos', foreign_object.remote_field.__dict__)
+ # Cached value is removed via __getstate__() on ForeignObjectRel
+ # because no __deepcopy__() method exists, so __reduce_ex__() is used.
+ remote_field_copy = copy.deepcopy(foreign_object.remote_field)
+ self.assertNotIn('path_infos', remote_field_copy.__dict__)
+ # Field.__deepcopy__() internally uses __copy__() on both the
+ # ForeignObject and ForeignObjectRel, so all cached values are removed.
+ foreign_object_copy = copy.deepcopy(foreign_object)
+ self.assertNotIn('path_infos', foreign_object_copy.__dict__)
+ self.assertNotIn('reverse_path_infos', foreign_object_copy.__dict__)
+ self.assertNotIn('path_infos', foreign_object_copy.remote_field.__dict__)
+
+ def test_pickling_foreignobjectrel(self):
+ """
+ Pickling a ForeignObjectRel removes the path_infos attribute.
+
+ ForeignObjectRel implements __getstate__(), so copy and pickle modules
+ both use that, but ForeignObject implements __reduce__() and __copy__()
+ separately, so doesn't share the same behaviour.
+ """
+ foreign_object_rel = Membership._meta.get_field('person').remote_field
+ # Trigger storage of cached_property into ForeignObjectRel's __dict__.
+ foreign_object_rel.path_infos
+ self.assertIn('path_infos', foreign_object_rel.__dict__)
+ foreign_object_rel_restored = pickle.loads(pickle.dumps(foreign_object_rel))
+ self.assertNotIn('path_infos', foreign_object_rel_restored.__dict__)
+
+ def test_pickling_foreignobject(self):
+ """
+ Pickling a ForeignObject does not remove the cached PathInfo values.
+
+ ForeignObject will always keep the path_infos and reverse_path_infos
+ attributes within the same process, because of the way
+ Field.__reduce__() is used for restoring values.
+ """
+ foreign_object = Membership._meta.get_field('person')
+ # Trigger storage of cached_property into ForeignObjectRel's __dict__
+ foreign_object.path_infos
+ foreign_object.reverse_path_infos
+ self.assertIn('path_infos', foreign_object.__dict__)
+ self.assertIn('reverse_path_infos', foreign_object.__dict__)
+ foreign_object_restored = pickle.loads(pickle.dumps(foreign_object))
+ self.assertIn('path_infos', foreign_object_restored.__dict__)
+ self.assertIn('reverse_path_infos', foreign_object_restored.__dict__)