diff options
Diffstat (limited to 'tests/foreign_object/tests.py')
| -rw-r--r-- | tests/foreign_object/tests.py | 103 |
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__) |
