1
0
mirror of https://github.com/django/django.git synced 2024-12-29 12:36:08 +00:00
django/tests/utils_tests/test_lazyobject.py
Ben Kraft 13023ba867 Fixed #26122 -- Fixed copying a LazyObject
Shallow copying of `django.utils.functional.LazyObject` or its subclasses has
been broken in a couple of different ways in the past, most recently due to
35355a4.
2016-01-26 06:56:21 -05:00

450 lines
14 KiB
Python

from __future__ import unicode_literals
import copy
import pickle
import sys
import warnings
from unittest import TestCase
from django.utils import six
from django.utils.functional import LazyObject, SimpleLazyObject, empty
from .models import Category, CategoryInfo
class Foo(object):
"""
A simple class with just one attribute.
"""
foo = 'bar'
def __eq__(self, other):
return self.foo == other.foo
class LazyObjectTestCase(TestCase):
def lazy_wrap(self, wrapped_object):
"""
Wrap the given object into a LazyObject
"""
class AdHocLazyObject(LazyObject):
def _setup(self):
self._wrapped = wrapped_object
return AdHocLazyObject()
def test_getattr(self):
obj = self.lazy_wrap(Foo())
self.assertEqual(obj.foo, 'bar')
def test_setattr(self):
obj = self.lazy_wrap(Foo())
obj.foo = 'BAR'
obj.bar = 'baz'
self.assertEqual(obj.foo, 'BAR')
self.assertEqual(obj.bar, 'baz')
def test_setattr2(self):
# Same as test_setattr but in reversed order
obj = self.lazy_wrap(Foo())
obj.bar = 'baz'
obj.foo = 'BAR'
self.assertEqual(obj.foo, 'BAR')
self.assertEqual(obj.bar, 'baz')
def test_delattr(self):
obj = self.lazy_wrap(Foo())
obj.bar = 'baz'
self.assertEqual(obj.bar, 'baz')
del obj.bar
with self.assertRaises(AttributeError):
obj.bar
def test_cmp(self):
obj1 = self.lazy_wrap('foo')
obj2 = self.lazy_wrap('bar')
obj3 = self.lazy_wrap('foo')
self.assertEqual(obj1, 'foo')
self.assertEqual(obj1, obj3)
self.assertNotEqual(obj1, obj2)
self.assertNotEqual(obj1, 'bar')
def test_bytes(self):
obj = self.lazy_wrap(b'foo')
self.assertEqual(bytes(obj), b'foo')
def test_text(self):
obj = self.lazy_wrap('foo')
self.assertEqual(six.text_type(obj), 'foo')
def test_bool(self):
# Refs #21840
for f in [False, 0, (), {}, [], None, set()]:
self.assertFalse(self.lazy_wrap(f))
for t in [True, 1, (1,), {1: 2}, [1], object(), {1}]:
self.assertTrue(t)
def test_dir(self):
obj = self.lazy_wrap('foo')
self.assertEqual(dir(obj), dir('foo'))
def test_len(self):
for seq in ['asd', [1, 2, 3], {'a': 1, 'b': 2, 'c': 3}]:
obj = self.lazy_wrap(seq)
self.assertEqual(len(obj), 3)
def test_class(self):
self.assertIsInstance(self.lazy_wrap(42), int)
class Bar(Foo):
pass
self.assertIsInstance(self.lazy_wrap(Bar()), Foo)
def test_hash(self):
obj = self.lazy_wrap('foo')
d = {}
d[obj] = 'bar'
self.assertIn('foo', d)
self.assertEqual(d['foo'], 'bar')
def test_contains(self):
test_data = [
('c', 'abcde'),
(2, [1, 2, 3]),
('a', {'a': 1, 'b': 2, 'c': 3}),
(2, {1, 2, 3}),
]
for needle, haystack in test_data:
self.assertIn(needle, self.lazy_wrap(haystack))
# __contains__ doesn't work when the haystack is a string and the needle a LazyObject
for needle_haystack in test_data[1:]:
self.assertIn(self.lazy_wrap(needle), haystack)
self.assertIn(self.lazy_wrap(needle), self.lazy_wrap(haystack))
def test_getitem(self):
obj_list = self.lazy_wrap([1, 2, 3])
obj_dict = self.lazy_wrap({'a': 1, 'b': 2, 'c': 3})
self.assertEqual(obj_list[0], 1)
self.assertEqual(obj_list[-1], 3)
self.assertEqual(obj_list[1:2], [2])
self.assertEqual(obj_dict['b'], 2)
with self.assertRaises(IndexError):
obj_list[3]
with self.assertRaises(KeyError):
obj_dict['f']
def test_setitem(self):
obj_list = self.lazy_wrap([1, 2, 3])
obj_dict = self.lazy_wrap({'a': 1, 'b': 2, 'c': 3})
obj_list[0] = 100
self.assertEqual(obj_list, [100, 2, 3])
obj_list[1:2] = [200, 300, 400]
self.assertEqual(obj_list, [100, 200, 300, 400, 3])
obj_dict['a'] = 100
obj_dict['d'] = 400
self.assertEqual(obj_dict, {'a': 100, 'b': 2, 'c': 3, 'd': 400})
def test_delitem(self):
obj_list = self.lazy_wrap([1, 2, 3])
obj_dict = self.lazy_wrap({'a': 1, 'b': 2, 'c': 3})
del obj_list[-1]
del obj_dict['c']
self.assertEqual(obj_list, [1, 2])
self.assertEqual(obj_dict, {'a': 1, 'b': 2})
with self.assertRaises(IndexError):
del obj_list[3]
with self.assertRaises(KeyError):
del obj_dict['f']
def test_iter(self):
# Tests whether an object's custom `__iter__` method is being
# used when iterating over it.
class IterObject(object):
def __init__(self, values):
self.values = values
def __iter__(self):
return iter(self.values)
original_list = ['test', '123']
self.assertEqual(
list(self.lazy_wrap(IterObject(original_list))),
original_list
)
def test_pickle(self):
# See ticket #16563
obj = self.lazy_wrap(Foo())
pickled = pickle.dumps(obj)
unpickled = pickle.loads(pickled)
self.assertIsInstance(unpickled, Foo)
self.assertEqual(unpickled, obj)
self.assertEqual(unpickled.foo, obj.foo)
# Test copying lazy objects wrapping both builtin types and user-defined
# classes since a lot of the relevant code does __dict__ manipulation and
# builtin types don't have __dict__.
def test_copy_list(self):
# Copying a list works and returns the correct objects.
l = [1, 2, 3]
obj = self.lazy_wrap(l)
len(l) # forces evaluation
obj2 = copy.copy(obj)
self.assertIsNot(obj, obj2)
self.assertIsInstance(obj2, list)
self.assertEqual(obj2, [1, 2, 3])
def test_copy_list_no_evaluation(self):
# Copying a list doesn't force evaluation.
l = [1, 2, 3]
obj = self.lazy_wrap(l)
obj2 = copy.copy(obj)
self.assertIsNot(obj, obj2)
self.assertIs(obj._wrapped, empty)
self.assertIs(obj2._wrapped, empty)
def test_copy_class(self):
# Copying a class works and returns the correct objects.
foo = Foo()
obj = self.lazy_wrap(foo)
str(foo) # forces evaluation
obj2 = copy.copy(obj)
self.assertIsNot(obj, obj2)
self.assertIsInstance(obj2, Foo)
self.assertEqual(obj2, Foo())
def test_copy_class_no_evaluation(self):
# Copying a class doesn't force evaluation.
foo = Foo()
obj = self.lazy_wrap(foo)
obj2 = copy.copy(obj)
self.assertIsNot(obj, obj2)
self.assertIs(obj._wrapped, empty)
self.assertIs(obj2._wrapped, empty)
def test_deepcopy_list(self):
# Deep copying a list works and returns the correct objects.
l = [1, 2, 3]
obj = self.lazy_wrap(l)
len(l) # forces evaluation
obj2 = copy.deepcopy(obj)
self.assertIsNot(obj, obj2)
self.assertIsInstance(obj2, list)
self.assertEqual(obj2, [1, 2, 3])
def test_deepcopy_list_no_evaluation(self):
# Deep copying doesn't force evaluation.
l = [1, 2, 3]
obj = self.lazy_wrap(l)
obj2 = copy.deepcopy(obj)
self.assertIsNot(obj, obj2)
self.assertIs(obj._wrapped, empty)
self.assertIs(obj2._wrapped, empty)
def test_deepcopy_class(self):
# Deep copying a class works and returns the correct objects.
foo = Foo()
obj = self.lazy_wrap(foo)
str(foo) # forces evaluation
obj2 = copy.deepcopy(obj)
self.assertIsNot(obj, obj2)
self.assertIsInstance(obj2, Foo)
self.assertEqual(obj2, Foo())
def test_deepcopy_class_no_evaluation(self):
# Deep copying doesn't force evaluation.
foo = Foo()
obj = self.lazy_wrap(foo)
obj2 = copy.deepcopy(obj)
self.assertIsNot(obj, obj2)
self.assertIs(obj._wrapped, empty)
self.assertIs(obj2._wrapped, empty)
class SimpleLazyObjectTestCase(LazyObjectTestCase):
# By inheriting from LazyObjectTestCase and redefining the lazy_wrap()
# method which all testcases use, we get to make sure all behaviors
# tested in the parent testcase also apply to SimpleLazyObject.
def lazy_wrap(self, wrapped_object):
return SimpleLazyObject(lambda: wrapped_object)
def test_repr(self):
# First, for an unevaluated SimpleLazyObject
obj = self.lazy_wrap(42)
# __repr__ contains __repr__ of setup function and does not evaluate
# the SimpleLazyObject
six.assertRegex(self, repr(obj), '^<SimpleLazyObject:')
self.assertIs(obj._wrapped, empty) # make sure evaluation hasn't been triggered
self.assertEqual(obj, 42) # evaluate the lazy object
self.assertIsInstance(obj._wrapped, int)
self.assertEqual(repr(obj), '<SimpleLazyObject: 42>')
def test_trace(self):
# See ticket #19456
old_trace_func = sys.gettrace()
try:
def trace_func(frame, event, arg):
frame.f_locals['self'].__class__
if old_trace_func is not None:
old_trace_func(frame, event, arg)
sys.settrace(trace_func)
self.lazy_wrap(None)
finally:
sys.settrace(old_trace_func)
def test_none(self):
i = [0]
def f():
i[0] += 1
return None
x = SimpleLazyObject(f)
self.assertEqual(str(x), "None")
self.assertEqual(i, [1])
self.assertEqual(str(x), "None")
self.assertEqual(i, [1])
def test_dict(self):
# See ticket #18447
lazydict = SimpleLazyObject(lambda: {'one': 1})
self.assertEqual(lazydict['one'], 1)
lazydict['one'] = -1
self.assertEqual(lazydict['one'], -1)
self.assertIn('one', lazydict)
self.assertNotIn('two', lazydict)
self.assertEqual(len(lazydict), 1)
del lazydict['one']
with self.assertRaises(KeyError):
lazydict['one']
def test_list_set(self):
lazy_list = SimpleLazyObject(lambda: [1, 2, 3, 4, 5])
lazy_set = SimpleLazyObject(lambda: {1, 2, 3, 4})
self.assertIn(1, lazy_list)
self.assertIn(1, lazy_set)
self.assertNotIn(6, lazy_list)
self.assertNotIn(6, lazy_set)
self.assertEqual(len(lazy_list), 5)
self.assertEqual(len(lazy_set), 4)
class BaseBaz(object):
"""
A base class with a funky __reduce__ method, meant to simulate the
__reduce__ method of Model, which sets self._django_version.
"""
def __init__(self):
self.baz = 'wrong'
def __reduce__(self):
self.baz = 'right'
return super(BaseBaz, self).__reduce__()
def __eq__(self, other):
if self.__class__ != other.__class__:
return False
for attr in ['bar', 'baz', 'quux']:
if hasattr(self, attr) != hasattr(other, attr):
return False
elif getattr(self, attr, None) != getattr(other, attr, None):
return False
return True
class Baz(BaseBaz):
"""
A class that inherits from BaseBaz and has its own __reduce_ex__ method.
"""
def __init__(self, bar):
self.bar = bar
super(Baz, self).__init__()
def __reduce_ex__(self, proto):
self.quux = 'quux'
return super(Baz, self).__reduce_ex__(proto)
class BazProxy(Baz):
"""
A class that acts as a proxy for Baz. It does some scary mucking about with
dicts, which simulates some crazy things that people might do with
e.g. proxy models.
"""
def __init__(self, baz):
self.__dict__ = baz.__dict__
self._baz = baz
super(BaseBaz, self).__init__()
class SimpleLazyObjectPickleTestCase(TestCase):
"""
Regression test for pickling a SimpleLazyObject wrapping a model (#25389).
Also covers other classes with a custom __reduce__ method.
"""
def test_pickle_with_reduce(self):
"""
Test in a fairly synthetic setting.
"""
# Test every pickle protocol available
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
lazy_objs = [
SimpleLazyObject(lambda: BaseBaz()),
SimpleLazyObject(lambda: Baz(1)),
SimpleLazyObject(lambda: BazProxy(Baz(2))),
]
for obj in lazy_objs:
pickled = pickle.dumps(obj, protocol)
unpickled = pickle.loads(pickled)
self.assertEqual(unpickled, obj)
self.assertEqual(unpickled.baz, 'right')
def test_pickle_model(self):
"""
Test on an actual model, based on the report in #25426.
"""
category = Category.objects.create(name="thing1")
CategoryInfo.objects.create(category=category)
# Test every pickle protocol available
for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
lazy_category = SimpleLazyObject(lambda: category)
# Test both if we accessed a field on the model and if we didn't.
lazy_category.categoryinfo
lazy_category_2 = SimpleLazyObject(lambda: category)
with warnings.catch_warnings(record=True) as recorded:
self.assertEqual(pickle.loads(pickle.dumps(lazy_category, protocol)), category)
self.assertEqual(pickle.loads(pickle.dumps(lazy_category_2, protocol)), category)
# Assert that there were no warnings.
self.assertEqual(len(recorded), 0)