123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- import copy
- import pickle
- import sys
- import warnings
- from unittest import TestCase
- from django.utils.functional import LazyObject, SimpleLazyObject, empty
- from .models import Category, CategoryInfo
- class Foo:
- """
- 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_getattribute(self):
- """
- Proxy methods don't exist on wrapped objects unless they're set.
- """
- attrs = [
- "__getitem__",
- "__setitem__",
- "__delitem__",
- "__iter__",
- "__len__",
- "__contains__",
- ]
- foo = Foo()
- obj = self.lazy_wrap(foo)
- for attr in attrs:
- with self.subTest(attr):
- self.assertFalse(hasattr(obj, attr))
- setattr(foo, attr, attr)
- obj_with_attr = self.lazy_wrap(foo)
- self.assertTrue(hasattr(obj_with_attr, attr))
- self.assertEqual(getattr(obj_with_attr, attr), attr)
- def test_getattr(self):
- obj = self.lazy_wrap(Foo())
- self.assertEqual(obj.foo, "bar")
- def test_getattr_falsey(self):
- class Thing:
- def __getattr__(self, key):
- return []
- obj = self.lazy_wrap(Thing())
- self.assertEqual(obj.main, [])
- 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_lt(self):
- obj1 = self.lazy_wrap(1)
- obj2 = self.lazy_wrap(2)
- self.assertLess(obj1, obj2)
- def test_gt(self):
- obj1 = self.lazy_wrap(1)
- obj2 = self.lazy_wrap(2)
- self.assertGreater(obj2, obj1)
- 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(str(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 = {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:
- 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())
- obj.bar = "baz"
- pickled = pickle.dumps(obj)
- unpickled = pickle.loads(pickled)
- self.assertIsInstance(unpickled, Foo)
- self.assertEqual(unpickled, obj)
- self.assertEqual(unpickled.foo, obj.foo)
- self.assertEqual(unpickled.bar, obj.bar)
- # 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.
- lst = [1, 2, 3]
- obj = self.lazy_wrap(lst)
- len(lst) # 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.
- lst = [1, 2, 3]
- obj = self.lazy_wrap(lst)
- 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.
- lst = [1, 2, 3]
- obj = self.lazy_wrap(lst)
- len(lst) # 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.
- lst = [1, 2, 3]
- obj = self.lazy_wrap(lst)
- 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
- self.assertRegex(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_add(self):
- obj1 = self.lazy_wrap(1)
- self.assertEqual(obj1 + 1, 2)
- obj2 = self.lazy_wrap(2)
- self.assertEqual(obj2 + obj1, 3)
- self.assertEqual(obj1 + obj2, 3)
- def test_radd(self):
- obj1 = self.lazy_wrap(1)
- self.assertEqual(1 + obj1, 2)
- 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:
- """
- 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().__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().__init__()
- def __reduce_ex__(self, proto):
- self.quux = "quux"
- return super().__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
- # Grandparent super
- 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)
|