test_lazyobject.py 15 KB

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