Coverage for tests/test_classes.py: 39%
84 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-31 02:35 -0700
« prev ^ index » next coverage.py v6.5.0, created at 2023-03-31 02:35 -0700
1# This file is part of utils.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12import copy
13import functools
14import logging
15import pickle
16import unittest
18from lsst.utils.classes import Singleton, cached_getter, immutable
20log = logging.getLogger("test_classes")
23class SingletonTestCase(unittest.TestCase):
24 """Tests of the Singleton metaclass"""
26 class IsSingleton(metaclass=Singleton):
27 def __init__(self):
28 self.data = {}
29 self.id = 0
31 class IsBadSingleton(IsSingleton):
32 def __init__(self, arg):
33 """A singleton can not accept any arguments."""
34 self.arg = arg
36 class IsSingletonSubclass(IsSingleton):
37 def __init__(self):
38 super().__init__()
40 def testSingleton(self):
41 one = SingletonTestCase.IsSingleton()
42 two = SingletonTestCase.IsSingleton()
44 # Now update the first one and check the second
45 one.data["test"] = 52
46 self.assertEqual(one.data, two.data)
47 two.id += 1
48 self.assertEqual(one.id, two.id)
50 three = SingletonTestCase.IsSingletonSubclass()
51 self.assertNotEqual(one.id, three.id)
53 with self.assertRaises(TypeError):
54 SingletonTestCase.IsBadSingleton(52)
57class ImmutabilityTestCase(unittest.TestCase):
58 @immutable
59 class Immutable:
60 def __init__(self, name: str, number: int):
61 self.name = name
62 self.number = number
64 def __hash__(self) -> int:
65 return hash((self.name, self.number))
67 def testImmutable(self):
68 im1 = ImmutabilityTestCase.Immutable("name", 42)
69 im2 = ImmutabilityTestCase.Immutable("another", 0)
70 self.assertEqual((im1.name, im1.number), ("name", 42))
71 test_set = {im1, im2}
72 self.assertIn(im2, test_set)
74 with self.assertRaises(AttributeError):
75 im1.name = "no"
77 self.assertIs(copy.copy(im1), im1)
79 # Pickling does not work without help and this tests that it
80 # does not work.
81 pickled = pickle.dumps(im1)
82 im3 = pickle.loads(pickled)
83 self.assertEqual(im3.__dict__, {})
86class CacheTestCase(unittest.TestCase):
87 class Cached1:
88 """Cached getter using cached_getter. This can use slots."""
90 __slots__ = ("value", "_cached_cache_value")
92 def __init__(self, value: int):
93 self.value = value
95 @property
96 @cached_getter
97 def cache_value(self) -> int:
98 log.info("Calculating cached value.")
99 return self.value + 1
101 class Cached2:
102 """Cached getter using functools. This can not use slots."""
104 def __init__(self, value: int):
105 self.value = value
107 @functools.cached_property
108 def cache_value(self) -> int:
109 log.info("Calculating cached value.")
110 return self.value + 1
112 def assertCache(self, cls):
113 v1 = cls(42)
114 self.assertEqual(v1.value, 42)
116 with self.assertLogs(level=logging.INFO) as cm:
117 cached_value = v1.cache_value
118 self.assertEqual(cached_value, 43)
119 self.assertEqual(cm.output, ["INFO:test_classes:Calculating cached value."])
121 v1.value = 50
122 self.assertEqual(v1.value, 50)
123 with self.assertLogs(level=logging.INFO) as cm:
124 cached_value = v1.cache_value
125 log.info("Used cache.")
126 self.assertEqual(cached_value, 43)
127 self.assertEqual(cm.output, ["INFO:test_classes:Used cache."])
129 def testCachedGetter(self):
130 self.assertCache(CacheTestCase.Cached1)
132 def testFunctoolsCachedProperty(self):
133 self.assertCache(CacheTestCase.Cached2)
136if __name__ == "__main__":
137 unittest.main()