Coverage for tests/test_classes.py: 44%

84 statements  

« prev     ^ index     » next       coverage.py v7.4.3, created at 2024-02-27 11:49 +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# This program is free software: you can redistribute it and/or modify 

10# it under the terms of the GNU General Public License as published by 

11# the Free Software Foundation, either version 3 of the License, or 

12# (at your option) any later version. 

13# 

14# This program is distributed in the hope that it will be useful, 

15# but WITHOUT ANY WARRANTY; without even the implied warranty of 

16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

17# GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License 

20# along with this program. If not, see <https://www.gnu.org/licenses/>. 

21 

22import copy 

23import functools 

24import logging 

25import pickle 

26import unittest 

27 

28from lsst.utils.classes import Singleton, cached_getter, immutable 

29 

30log = logging.getLogger("test_classes") 

31 

32 

33class SingletonTestCase(unittest.TestCase): 

34 """Tests of the Singleton metaclass.""" 

35 

36 class IsSingleton(metaclass=Singleton): 

37 """A singleton.""" 

38 

39 def __init__(self): 

40 self.data = {} 

41 self.id = 0 

42 

43 class IsBadSingleton(IsSingleton): 

44 """A single that can not accept any arguments.""" 

45 

46 def __init__(self, arg): 

47 self.arg = arg 

48 

49 class IsSingletonSubclass(IsSingleton): 

50 """A subclass of a singleton.""" 

51 

52 def __init__(self): 

53 super().__init__() 

54 

55 def testSingleton(self): 

56 one = SingletonTestCase.IsSingleton() 

57 two = SingletonTestCase.IsSingleton() 

58 

59 # Now update the first one and check the second 

60 one.data["test"] = 52 

61 self.assertEqual(one.data, two.data) 

62 two.id += 1 

63 self.assertEqual(one.id, two.id) 

64 

65 three = SingletonTestCase.IsSingletonSubclass() 

66 self.assertNotEqual(one.id, three.id) 

67 

68 with self.assertRaises(TypeError): 

69 SingletonTestCase.IsBadSingleton(52) 

70 

71 

72class ImmutabilityTestCase(unittest.TestCase): 

73 """Test immutable classes.""" 

74 

75 @immutable 

76 class Immutable: 

77 """An immutable test class.""" 

78 

79 def __init__(self, name: str, number: int): 

80 self.name = name 

81 self.number = number 

82 

83 def __hash__(self) -> int: 

84 return hash((self.name, self.number)) 

85 

86 def testImmutable(self): 

87 im1 = ImmutabilityTestCase.Immutable("name", 42) 

88 im2 = ImmutabilityTestCase.Immutable("another", 0) 

89 self.assertEqual((im1.name, im1.number), ("name", 42)) 

90 test_set = {im1, im2} 

91 self.assertIn(im2, test_set) 

92 

93 with self.assertRaises(AttributeError): 

94 im1.name = "no" 

95 

96 self.assertIs(copy.copy(im1), im1) 

97 

98 # Pickling does not work without help and this tests that it 

99 # does not work. 

100 pickled = pickle.dumps(im1) 

101 im3 = pickle.loads(pickled) 

102 self.assertEqual(im3.__dict__, {}) 

103 

104 

105class CacheTestCase(unittest.TestCase): 

106 """Test the caching code.""" 

107 

108 class Cached1: 

109 """Cached getter using cached_getter. This can use slots.""" 

110 

111 __slots__ = ("value", "_cached_cache_value") 

112 

113 def __init__(self, value: int): 

114 self.value = value 

115 

116 @property 

117 @cached_getter 

118 def cache_value(self) -> int: 

119 log.info("Calculating cached value.") 

120 return self.value + 1 

121 

122 class Cached2: 

123 """Cached getter using functools. This can not use slots.""" 

124 

125 def __init__(self, value: int): 

126 self.value = value 

127 

128 @functools.cached_property 

129 def cache_value(self) -> int: 

130 log.info("Calculating cached value.") 

131 return self.value + 1 

132 

133 def assertCache(self, cls): 

134 v1 = cls(42) 

135 self.assertEqual(v1.value, 42) 

136 

137 with self.assertLogs(level=logging.INFO) as cm: 

138 cached_value = v1.cache_value 

139 self.assertEqual(cached_value, 43) 

140 self.assertEqual(cm.output, ["INFO:test_classes:Calculating cached value."]) 

141 

142 v1.value = 50 

143 self.assertEqual(v1.value, 50) 

144 with self.assertLogs(level=logging.INFO) as cm: 

145 cached_value = v1.cache_value 

146 log.info("Used cache.") 

147 self.assertEqual(cached_value, 43) 

148 self.assertEqual(cm.output, ["INFO:test_classes:Used cache."]) 

149 

150 def testCachedGetter(self): 

151 self.assertCache(CacheTestCase.Cached1) 

152 

153 def testFunctoolsCachedProperty(self): 

154 self.assertCache(CacheTestCase.Cached2) 

155 

156 

157if __name__ == "__main__": 

158 unittest.main()