Coverage for tests/test_classes.py: 39%
84 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-08 09:53 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-08 09:53 +0000
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 """A singleton."""
29 def __init__(self):
30 self.data = {}
31 self.id = 0
33 class IsBadSingleton(IsSingleton):
34 """A single that can not accept any arguments."""
36 def __init__(self, arg):
37 self.arg = arg
39 class IsSingletonSubclass(IsSingleton):
40 """A subclass of a singleton."""
42 def __init__(self):
43 super().__init__()
45 def testSingleton(self):
46 one = SingletonTestCase.IsSingleton()
47 two = SingletonTestCase.IsSingleton()
49 # Now update the first one and check the second
50 one.data["test"] = 52
51 self.assertEqual(one.data, two.data)
52 two.id += 1
53 self.assertEqual(one.id, two.id)
55 three = SingletonTestCase.IsSingletonSubclass()
56 self.assertNotEqual(one.id, three.id)
58 with self.assertRaises(TypeError):
59 SingletonTestCase.IsBadSingleton(52)
62class ImmutabilityTestCase(unittest.TestCase):
63 """Test immutable classes."""
65 @immutable
66 class Immutable:
67 """An immutable test class."""
69 def __init__(self, name: str, number: int):
70 self.name = name
71 self.number = number
73 def __hash__(self) -> int:
74 return hash((self.name, self.number))
76 def testImmutable(self):
77 im1 = ImmutabilityTestCase.Immutable("name", 42)
78 im2 = ImmutabilityTestCase.Immutable("another", 0)
79 self.assertEqual((im1.name, im1.number), ("name", 42))
80 test_set = {im1, im2}
81 self.assertIn(im2, test_set)
83 with self.assertRaises(AttributeError):
84 im1.name = "no"
86 self.assertIs(copy.copy(im1), im1)
88 # Pickling does not work without help and this tests that it
89 # does not work.
90 pickled = pickle.dumps(im1)
91 im3 = pickle.loads(pickled)
92 self.assertEqual(im3.__dict__, {})
95class CacheTestCase(unittest.TestCase):
96 """Test the caching code."""
98 class Cached1:
99 """Cached getter using cached_getter. This can use slots."""
101 __slots__ = ("value", "_cached_cache_value")
103 def __init__(self, value: int):
104 self.value = value
106 @property
107 @cached_getter
108 def cache_value(self) -> int:
109 log.info("Calculating cached value.")
110 return self.value + 1
112 class Cached2:
113 """Cached getter using functools. This can not use slots."""
115 def __init__(self, value: int):
116 self.value = value
118 @functools.cached_property
119 def cache_value(self) -> int:
120 log.info("Calculating cached value.")
121 return self.value + 1
123 def assertCache(self, cls):
124 v1 = cls(42)
125 self.assertEqual(v1.value, 42)
127 with self.assertLogs(level=logging.INFO) as cm:
128 cached_value = v1.cache_value
129 self.assertEqual(cached_value, 43)
130 self.assertEqual(cm.output, ["INFO:test_classes:Calculating cached value."])
132 v1.value = 50
133 self.assertEqual(v1.value, 50)
134 with self.assertLogs(level=logging.INFO) as cm:
135 cached_value = v1.cache_value
136 log.info("Used cache.")
137 self.assertEqual(cached_value, 43)
138 self.assertEqual(cm.output, ["INFO:test_classes:Used cache."])
140 def testCachedGetter(self):
141 self.assertCache(CacheTestCase.Cached1)
143 def testFunctoolsCachedProperty(self):
144 self.assertCache(CacheTestCase.Cached2)
147if __name__ == "__main__":
148 unittest.main()