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