test_lazyobject.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. import copy
  2. import pickle
  3. import sys
  4. import warnings
  5. from unittest import TestCase
  6. from django.utils.functional import LazyObject, SimpleLazyObject, empty
  7. from .models import Category, CategoryInfo
  8. class Foo:
  9. """
  10. A simple class with just one attribute.
  11. """
  12. foo = "bar"
  13. def __eq__(self, other):
  14. return self.foo == other.foo
  15. class LazyObjectTestCase(TestCase):
  16. def lazy_wrap(self, wrapped_object):
  17. """
  18. Wrap the given object into a LazyObject
  19. """
  20. class AdHocLazyObject(LazyObject):
  21. def _setup(self):
  22. self._wrapped = wrapped_object
  23. return AdHocLazyObject()
  24. def test_getattr(self):
  25. obj = self.lazy_wrap(Foo())
  26. self.assertEqual(obj.foo, "bar")
  27. def test_setattr(self):
  28. obj = self.lazy_wrap(Foo())
  29. obj.foo = "BAR"
  30. obj.bar = "baz"
  31. self.assertEqual(obj.foo, "BAR")
  32. self.assertEqual(obj.bar, "baz")
  33. def test_setattr2(self):
  34. # Same as test_setattr but in reversed order
  35. obj = self.lazy_wrap(Foo())
  36. obj.bar = "baz"
  37. obj.foo = "BAR"
  38. self.assertEqual(obj.foo, "BAR")
  39. self.assertEqual(obj.bar, "baz")
  40. def test_delattr(self):
  41. obj = self.lazy_wrap(Foo())
  42. obj.bar = "baz"
  43. self.assertEqual(obj.bar, "baz")
  44. del obj.bar
  45. with self.assertRaises(AttributeError):
  46. obj.bar
  47. def test_cmp(self):
  48. obj1 = self.lazy_wrap("foo")
  49. obj2 = self.lazy_wrap("bar")
  50. obj3 = self.lazy_wrap("foo")
  51. self.assertEqual(obj1, "foo")
  52. self.assertEqual(obj1, obj3)
  53. self.assertNotEqual(obj1, obj2)
  54. self.assertNotEqual(obj1, "bar")
  55. def test_lt(self):
  56. obj1 = self.lazy_wrap(1)
  57. obj2 = self.lazy_wrap(2)
  58. self.assertLess(obj1, obj2)
  59. def test_gt(self):
  60. obj1 = self.lazy_wrap(1)
  61. obj2 = self.lazy_wrap(2)
  62. self.assertGreater(obj2, obj1)
  63. def test_bytes(self):
  64. obj = self.lazy_wrap(b"foo")
  65. self.assertEqual(bytes(obj), b"foo")
  66. def test_text(self):
  67. obj = self.lazy_wrap("foo")
  68. self.assertEqual(str(obj), "foo")
  69. def test_bool(self):
  70. # Refs #21840
  71. for f in [False, 0, (), {}, [], None, set()]:
  72. self.assertFalse(self.lazy_wrap(f))
  73. for t in [True, 1, (1,), {1: 2}, [1], object(), {1}]:
  74. self.assertTrue(t)
  75. def test_dir(self):
  76. obj = self.lazy_wrap("foo")
  77. self.assertEqual(dir(obj), dir("foo"))
  78. def test_len(self):
  79. for seq in ["asd", [1, 2, 3], {"a": 1, "b": 2, "c": 3}]:
  80. obj = self.lazy_wrap(seq)
  81. self.assertEqual(len(obj), 3)
  82. def test_class(self):
  83. self.assertIsInstance(self.lazy_wrap(42), int)
  84. class Bar(Foo):
  85. pass
  86. self.assertIsInstance(self.lazy_wrap(Bar()), Foo)
  87. def test_hash(self):
  88. obj = self.lazy_wrap("foo")
  89. d = {obj: "bar"}
  90. self.assertIn("foo", d)
  91. self.assertEqual(d["foo"], "bar")
  92. def test_contains(self):
  93. test_data = [
  94. ("c", "abcde"),
  95. (2, [1, 2, 3]),
  96. ("a", {"a": 1, "b": 2, "c": 3}),
  97. (2, {1, 2, 3}),
  98. ]
  99. for needle, haystack in test_data:
  100. self.assertIn(needle, self.lazy_wrap(haystack))
  101. # __contains__ doesn't work when the haystack is a string and the
  102. # needle a LazyObject.
  103. for needle_haystack in test_data[1:]:
  104. self.assertIn(self.lazy_wrap(needle), haystack)
  105. self.assertIn(self.lazy_wrap(needle), self.lazy_wrap(haystack))
  106. def test_getitem(self):
  107. obj_list = self.lazy_wrap([1, 2, 3])
  108. obj_dict = self.lazy_wrap({"a": 1, "b": 2, "c": 3})
  109. self.assertEqual(obj_list[0], 1)
  110. self.assertEqual(obj_list[-1], 3)
  111. self.assertEqual(obj_list[1:2], [2])
  112. self.assertEqual(obj_dict["b"], 2)
  113. with self.assertRaises(IndexError):
  114. obj_list[3]
  115. with self.assertRaises(KeyError):
  116. obj_dict["f"]
  117. def test_setitem(self):
  118. obj_list = self.lazy_wrap([1, 2, 3])
  119. obj_dict = self.lazy_wrap({"a": 1, "b": 2, "c": 3})
  120. obj_list[0] = 100
  121. self.assertEqual(obj_list, [100, 2, 3])
  122. obj_list[1:2] = [200, 300, 400]
  123. self.assertEqual(obj_list, [100, 200, 300, 400, 3])
  124. obj_dict["a"] = 100
  125. obj_dict["d"] = 400
  126. self.assertEqual(obj_dict, {"a": 100, "b": 2, "c": 3, "d": 400})
  127. def test_delitem(self):
  128. obj_list = self.lazy_wrap([1, 2, 3])
  129. obj_dict = self.lazy_wrap({"a": 1, "b": 2, "c": 3})
  130. del obj_list[-1]
  131. del obj_dict["c"]
  132. self.assertEqual(obj_list, [1, 2])
  133. self.assertEqual(obj_dict, {"a": 1, "b": 2})
  134. with self.assertRaises(IndexError):
  135. del obj_list[3]
  136. with self.assertRaises(KeyError):
  137. del obj_dict["f"]
  138. def test_iter(self):
  139. # Tests whether an object's custom `__iter__` method is being
  140. # used when iterating over it.
  141. class IterObject:
  142. def __init__(self, values):
  143. self.values = values
  144. def __iter__(self):
  145. return iter(self.values)
  146. original_list = ["test", "123"]
  147. self.assertEqual(list(self.lazy_wrap(IterObject(original_list))), original_list)
  148. def test_pickle(self):
  149. # See ticket #16563
  150. obj = self.lazy_wrap(Foo())
  151. obj.bar = "baz"
  152. pickled = pickle.dumps(obj)
  153. unpickled = pickle.loads(pickled)
  154. self.assertIsInstance(unpickled, Foo)
  155. self.assertEqual(unpickled, obj)
  156. self.assertEqual(unpickled.foo, obj.foo)
  157. self.assertEqual(unpickled.bar, obj.bar)
  158. # Test copying lazy objects wrapping both builtin types and user-defined
  159. # classes since a lot of the relevant code does __dict__ manipulation and
  160. # builtin types don't have __dict__.
  161. def test_copy_list(self):
  162. # Copying a list works and returns the correct objects.
  163. lst = [1, 2, 3]
  164. obj = self.lazy_wrap(lst)
  165. len(lst) # forces evaluation
  166. obj2 = copy.copy(obj)
  167. self.assertIsNot(obj, obj2)
  168. self.assertIsInstance(obj2, list)
  169. self.assertEqual(obj2, [1, 2, 3])
  170. def test_copy_list_no_evaluation(self):
  171. # Copying a list doesn't force evaluation.
  172. lst = [1, 2, 3]
  173. obj = self.lazy_wrap(lst)
  174. obj2 = copy.copy(obj)
  175. self.assertIsNot(obj, obj2)
  176. self.assertIs(obj._wrapped, empty)
  177. self.assertIs(obj2._wrapped, empty)
  178. def test_copy_class(self):
  179. # Copying a class works and returns the correct objects.
  180. foo = Foo()
  181. obj = self.lazy_wrap(foo)
  182. str(foo) # forces evaluation
  183. obj2 = copy.copy(obj)
  184. self.assertIsNot(obj, obj2)
  185. self.assertIsInstance(obj2, Foo)
  186. self.assertEqual(obj2, Foo())
  187. def test_copy_class_no_evaluation(self):
  188. # Copying a class doesn't force evaluation.
  189. foo = Foo()
  190. obj = self.lazy_wrap(foo)
  191. obj2 = copy.copy(obj)
  192. self.assertIsNot(obj, obj2)
  193. self.assertIs(obj._wrapped, empty)
  194. self.assertIs(obj2._wrapped, empty)
  195. def test_deepcopy_list(self):
  196. # Deep copying a list works and returns the correct objects.
  197. lst = [1, 2, 3]
  198. obj = self.lazy_wrap(lst)
  199. len(lst) # forces evaluation
  200. obj2 = copy.deepcopy(obj)
  201. self.assertIsNot(obj, obj2)
  202. self.assertIsInstance(obj2, list)
  203. self.assertEqual(obj2, [1, 2, 3])
  204. def test_deepcopy_list_no_evaluation(self):
  205. # Deep copying doesn't force evaluation.
  206. lst = [1, 2, 3]
  207. obj = self.lazy_wrap(lst)
  208. obj2 = copy.deepcopy(obj)
  209. self.assertIsNot(obj, obj2)
  210. self.assertIs(obj._wrapped, empty)
  211. self.assertIs(obj2._wrapped, empty)
  212. def test_deepcopy_class(self):
  213. # Deep copying a class works and returns the correct objects.
  214. foo = Foo()
  215. obj = self.lazy_wrap(foo)
  216. str(foo) # forces evaluation
  217. obj2 = copy.deepcopy(obj)
  218. self.assertIsNot(obj, obj2)
  219. self.assertIsInstance(obj2, Foo)
  220. self.assertEqual(obj2, Foo())
  221. def test_deepcopy_class_no_evaluation(self):
  222. # Deep copying doesn't force evaluation.
  223. foo = Foo()
  224. obj = self.lazy_wrap(foo)
  225. obj2 = copy.deepcopy(obj)
  226. self.assertIsNot(obj, obj2)
  227. self.assertIs(obj._wrapped, empty)
  228. self.assertIs(obj2._wrapped, empty)
  229. class SimpleLazyObjectTestCase(LazyObjectTestCase):
  230. # By inheriting from LazyObjectTestCase and redefining the lazy_wrap()
  231. # method which all testcases use, we get to make sure all behaviors
  232. # tested in the parent testcase also apply to SimpleLazyObject.
  233. def lazy_wrap(self, wrapped_object):
  234. return SimpleLazyObject(lambda: wrapped_object)
  235. def test_repr(self):
  236. # First, for an unevaluated SimpleLazyObject
  237. obj = self.lazy_wrap(42)
  238. # __repr__ contains __repr__ of setup function and does not evaluate
  239. # the SimpleLazyObject
  240. self.assertRegex(repr(obj), "^<SimpleLazyObject:")
  241. self.assertIs(obj._wrapped, empty) # make sure evaluation hasn't been triggered
  242. self.assertEqual(obj, 42) # evaluate the lazy object
  243. self.assertIsInstance(obj._wrapped, int)
  244. self.assertEqual(repr(obj), "<SimpleLazyObject: 42>")
  245. def test_add(self):
  246. obj1 = self.lazy_wrap(1)
  247. self.assertEqual(obj1 + 1, 2)
  248. obj2 = self.lazy_wrap(2)
  249. self.assertEqual(obj2 + obj1, 3)
  250. self.assertEqual(obj1 + obj2, 3)
  251. def test_radd(self):
  252. obj1 = self.lazy_wrap(1)
  253. self.assertEqual(1 + obj1, 2)
  254. def test_trace(self):
  255. # See ticket #19456
  256. old_trace_func = sys.gettrace()
  257. try:
  258. def trace_func(frame, event, arg):
  259. frame.f_locals["self"].__class__
  260. if old_trace_func is not None:
  261. old_trace_func(frame, event, arg)
  262. sys.settrace(trace_func)
  263. self.lazy_wrap(None)
  264. finally:
  265. sys.settrace(old_trace_func)
  266. def test_none(self):
  267. i = [0]
  268. def f():
  269. i[0] += 1
  270. return None
  271. x = SimpleLazyObject(f)
  272. self.assertEqual(str(x), "None")
  273. self.assertEqual(i, [1])
  274. self.assertEqual(str(x), "None")
  275. self.assertEqual(i, [1])
  276. def test_dict(self):
  277. # See ticket #18447
  278. lazydict = SimpleLazyObject(lambda: {"one": 1})
  279. self.assertEqual(lazydict["one"], 1)
  280. lazydict["one"] = -1
  281. self.assertEqual(lazydict["one"], -1)
  282. self.assertIn("one", lazydict)
  283. self.assertNotIn("two", lazydict)
  284. self.assertEqual(len(lazydict), 1)
  285. del lazydict["one"]
  286. with self.assertRaises(KeyError):
  287. lazydict["one"]
  288. def test_list_set(self):
  289. lazy_list = SimpleLazyObject(lambda: [1, 2, 3, 4, 5])
  290. lazy_set = SimpleLazyObject(lambda: {1, 2, 3, 4})
  291. self.assertIn(1, lazy_list)
  292. self.assertIn(1, lazy_set)
  293. self.assertNotIn(6, lazy_list)
  294. self.assertNotIn(6, lazy_set)
  295. self.assertEqual(len(lazy_list), 5)
  296. self.assertEqual(len(lazy_set), 4)
  297. class BaseBaz:
  298. """
  299. A base class with a funky __reduce__ method, meant to simulate the
  300. __reduce__ method of Model, which sets self._django_version.
  301. """
  302. def __init__(self):
  303. self.baz = "wrong"
  304. def __reduce__(self):
  305. self.baz = "right"
  306. return super().__reduce__()
  307. def __eq__(self, other):
  308. if self.__class__ != other.__class__:
  309. return False
  310. for attr in ["bar", "baz", "quux"]:
  311. if hasattr(self, attr) != hasattr(other, attr):
  312. return False
  313. elif getattr(self, attr, None) != getattr(other, attr, None):
  314. return False
  315. return True
  316. class Baz(BaseBaz):
  317. """
  318. A class that inherits from BaseBaz and has its own __reduce_ex__ method.
  319. """
  320. def __init__(self, bar):
  321. self.bar = bar
  322. super().__init__()
  323. def __reduce_ex__(self, proto):
  324. self.quux = "quux"
  325. return super().__reduce_ex__(proto)
  326. class BazProxy(Baz):
  327. """
  328. A class that acts as a proxy for Baz. It does some scary mucking about with
  329. dicts, which simulates some crazy things that people might do with
  330. e.g. proxy models.
  331. """
  332. def __init__(self, baz):
  333. self.__dict__ = baz.__dict__
  334. self._baz = baz
  335. # Grandparent super
  336. super(BaseBaz, self).__init__()
  337. class SimpleLazyObjectPickleTestCase(TestCase):
  338. """
  339. Regression test for pickling a SimpleLazyObject wrapping a model (#25389).
  340. Also covers other classes with a custom __reduce__ method.
  341. """
  342. def test_pickle_with_reduce(self):
  343. """
  344. Test in a fairly synthetic setting.
  345. """
  346. # Test every pickle protocol available
  347. for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
  348. lazy_objs = [
  349. SimpleLazyObject(lambda: BaseBaz()),
  350. SimpleLazyObject(lambda: Baz(1)),
  351. SimpleLazyObject(lambda: BazProxy(Baz(2))),
  352. ]
  353. for obj in lazy_objs:
  354. pickled = pickle.dumps(obj, protocol)
  355. unpickled = pickle.loads(pickled)
  356. self.assertEqual(unpickled, obj)
  357. self.assertEqual(unpickled.baz, "right")
  358. def test_pickle_model(self):
  359. """
  360. Test on an actual model, based on the report in #25426.
  361. """
  362. category = Category.objects.create(name="thing1")
  363. CategoryInfo.objects.create(category=category)
  364. # Test every pickle protocol available
  365. for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
  366. lazy_category = SimpleLazyObject(lambda: category)
  367. # Test both if we accessed a field on the model and if we didn't.
  368. lazy_category.categoryinfo
  369. lazy_category_2 = SimpleLazyObject(lambda: category)
  370. with warnings.catch_warnings(record=True) as recorded:
  371. self.assertEqual(
  372. pickle.loads(pickle.dumps(lazy_category, protocol)), category
  373. )
  374. self.assertEqual(
  375. pickle.loads(pickle.dumps(lazy_category_2, protocol)), category
  376. )
  377. # Assert that there were no warnings.
  378. self.assertEqual(len(recorded), 0)