Coverage for tests/test_000_frigid/test_200_objects.py: 100%

176 statements  

« prev     ^ index     » next       coverage.py v7.6.8, created at 2024-12-05 03:26 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Assert correct function of objects. ''' 

22 

23# mypy: ignore-errors 

24# pylint: disable=attribute-defined-outside-init 

25# pylint: disable=invalid-name,magic-value-comparison 

26# pylint: disable=missing-class-docstring,protected-access,unused-variable 

27 

28 

29import pytest 

30 

31from dataclasses import dataclass 

32from itertools import product 

33 

34from . import ( 

35 MODULES_QNAMES, 

36 PACKAGE_NAME, 

37 cache_import_module, 

38) 

39 

40 

41THESE_MODULE_QNAMES = tuple( 

42 name for name in MODULES_QNAMES if name.endswith( '.objects' ) ) 

43THESE_CLASSES_NAMES = ( 'Object', ) 

44 

45base = cache_import_module( f"{PACKAGE_NAME}.__" ) 

46exceptions = cache_import_module( f"{PACKAGE_NAME}.exceptions" ) 

47 

48 

49@pytest.mark.parametrize( 

50 'module_qname, class_name', 

51 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

52) 

53def test_100_instantiation( module_qname, class_name ): 

54 ''' Class instantiates. ''' 

55 module = cache_import_module( module_qname ) 

56 Object = getattr( module, class_name ) 

57 obj = Object( ) 

58 assert isinstance( obj, Object ) 

59 

60 

61@pytest.mark.parametrize( 

62 'module_qname, class_name', 

63 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

64) 

65def test_101_immutability( module_qname, class_name ): 

66 ''' Object prevents attribute modification after initialization. ''' 

67 module = cache_import_module( module_qname ) 

68 Object = getattr( module, class_name ) 

69 

70 class Example( Object ): 

71 def __init__( self ): 

72 self.value = 42 

73 super( ).__init__( ) 

74 

75 obj = Example( ) 

76 assert 42 == obj.value 

77 with pytest.raises( exceptions.AttributeImmutabilityError ): 

78 obj.value = -1 

79 assert 42 == obj.value 

80 with pytest.raises( exceptions.AttributeImmutabilityError ): 

81 obj.new_attr = 'test' 

82 with pytest.raises( exceptions.AttributeImmutabilityError ): 

83 del obj.value 

84 

85 

86@pytest.mark.parametrize( 

87 'module_qname, class_name', 

88 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

89) 

90def test_102_string_representation( module_qname, class_name ): 

91 ''' Object has expected string representations. ''' 

92 module = cache_import_module( module_qname ) 

93 factory = getattr( module, class_name ) 

94 obj = factory( ) 

95 assert base.calculate_fqname( obj ) in repr( obj ) 

96 

97 

98def test_200_immutable_decorator_basic( ): 

99 ''' Decorator makes regular class immutable. ''' 

100 module = cache_import_module( f"{PACKAGE_NAME}.objects" ) 

101 

102 @module.immutable 

103 class Example: 

104 def __init__( self ): 

105 self.value = 42 

106 

107 obj = Example( ) 

108 assert 42 == obj.value 

109 with pytest.raises( exceptions.AttributeImmutabilityError ): 

110 obj.value = 24 

111 with pytest.raises( exceptions.AttributeImmutabilityError ): 

112 obj.new_attr = 'test' 

113 with pytest.raises( exceptions.AttributeImmutabilityError ): 

114 del obj.value 

115 

116 

117def test_201_immutable_decorator_with_dataclass( ): 

118 ''' Decorator works with dataclass. ''' 

119 module = cache_import_module( f"{PACKAGE_NAME}.objects" ) 

120 

121 @module.immutable 

122 @dataclass( kw_only = True ) 

123 class Example: 

124 x: int 

125 y: str = 'default' 

126 

127 obj = Example( x = 42 ) 

128 assert 42 == obj.x 

129 assert 'default' == obj.y 

130 with pytest.raises( exceptions.AttributeImmutabilityError ): 

131 obj.x = 24 

132 with pytest.raises( exceptions.AttributeImmutabilityError ): 

133 obj.y = 'changed' 

134 

135 

136def test_202_immutable_decorator_inheritance( ): 

137 ''' Decorator properly handles inheritance. ''' 

138 module = cache_import_module( f"{PACKAGE_NAME}.objects" ) 

139 

140 @module.immutable 

141 class Base: 

142 def __init__( self ): 

143 self.base_attr = 'base' 

144 super( ).__init__( ) 

145 

146 class Derived( Base ): 

147 def __init__( self ): 

148 self.derived_attr = 'derived' 

149 super( ).__init__( ) 

150 

151 obj = Derived( ) 

152 assert 'base' == obj.base_attr 

153 assert 'derived' == obj.derived_attr 

154 with pytest.raises( exceptions.AttributeImmutabilityError ): 

155 obj.base_attr = 'modified' 

156 with pytest.raises( exceptions.AttributeImmutabilityError ): 

157 obj.derived_attr = 'modified' 

158 

159 

160def test_203_immutable_decorator_compatibility( ): 

161 ''' Decorator raises error for incompatible classes. ''' 

162 module = cache_import_module( f"{PACKAGE_NAME}.objects" ) 

163 

164 with pytest.raises( exceptions.DecoratorCompatibilityError ): 

165 @module.immutable 

166 class BadExample: 

167 def __setattr__( self, name, value ): 

168 pass # pragma: no coverage 

169 

170 with pytest.raises( exceptions.DecoratorCompatibilityError ): 

171 @module.immutable 

172 class AnotherBadExample: 

173 def __delattr__( self, name ): 

174 pass # pragma: no coverage 

175 

176 

177def test_204_immutable_decorator_slots( ): 

178 ''' Decorator handles classes with slots. ''' 

179 module = cache_import_module( f"{PACKAGE_NAME}.objects" ) 

180 

181 @module.immutable 

182 class Example: 

183 __slots__ = ( 'x', 'y', '_behaviors_' ) 

184 

185 def __init__( self ): 

186 self.x = 1 

187 self.y = 2 

188 

189 obj = Example( ) 

190 assert 1 == obj.x 

191 assert 2 == obj.y 

192 with pytest.raises( exceptions.AttributeImmutabilityError ): 

193 obj.x = 3 

194 with pytest.raises( exceptions.AttributeImmutabilityError ): 

195 obj.y = 4 

196 with pytest.raises( exceptions.AttributeImmutabilityError ): 

197 del obj.x 

198 

199 

200def test_205_immutable_decorator_existing_behaviors( ): 

201 ''' Decorator handles classes with existing behaviors. ''' 

202 module = cache_import_module( f"{PACKAGE_NAME}.objects" ) 

203 

204 @module.immutable 

205 class DictExample: 

206 def __init__( self ): 

207 self._behaviors_ = { 'existing' } 

208 self.value = 42 

209 

210 obj1 = DictExample( ) 

211 assert 'existing' in obj1._behaviors_ 

212 assert module.__.behavior_label in obj1._behaviors_ 

213 with pytest.raises( exceptions.AttributeImmutabilityError ): 

214 obj1.value = 24 

215 

216 @module.immutable 

217 class SlotsExample: 

218 __slots__ = ( '_behaviors_', 'value' ) 

219 

220 def __init__( self ): 

221 self._behaviors_ = { 'existing' } 

222 self.value = 42 

223 

224 obj2 = SlotsExample( ) 

225 assert 'existing' in obj2._behaviors_ 

226 assert module.__.behavior_label in obj2._behaviors_ 

227 with pytest.raises( exceptions.AttributeImmutabilityError ): 

228 obj2.value = 24 

229 

230 

231def test_206_immutable_decorator_mixed_slots_dict( ): 

232 ''' Decorator handles classes with both slots and dict. ''' 

233 module = cache_import_module( f"{PACKAGE_NAME}.objects" ) 

234 

235 @module.immutable 

236 class Example: 

237 __slots__ = ( 'x', 'y', '__dict__' ) 

238 

239 def __init__( self ): 

240 self.x = 1 

241 self.y = 2 

242 self.z = 3 # Goes to __dict__ 

243 

244 obj = Example( ) 

245 assert 1 == obj.x 

246 assert 2 == obj.y 

247 assert 3 == obj.z # pylint: disable=no-member 

248 with pytest.raises( exceptions.AttributeImmutabilityError ): 

249 obj.x = 4 

250 with pytest.raises( exceptions.AttributeImmutabilityError ): 

251 obj.z = 4 

252 

253 

254def test_207_immutable_decorator_initialization_deletion( ): 

255 ''' Decorator allows deletion during initialization. ''' 

256 module = cache_import_module( f"{PACKAGE_NAME}.objects" ) 

257 

258 @module.immutable 

259 class Example: 

260 __slots__ = ( 'x', '_behaviors_' ) 

261 

262 def __init__( self ): 

263 self.x = 1 

264 del self.x 

265 

266 obj = Example( ) 

267 with pytest.raises( AttributeError ): 

268 _ = obj.x 

269 with pytest.raises( exceptions.AttributeImmutabilityError ): 

270 del obj._behaviors_ # pylint: disable=no-member 

271 

272 

273@pytest.mark.parametrize( 

274 'module_qname, class_name', 

275 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

276) 

277def test_900_docstring_sanity( module_qname, class_name ): 

278 ''' Class has valid docstring. ''' 

279 module = cache_import_module( module_qname ) 

280 Object = getattr( module, class_name ) 

281 assert hasattr( Object, '__doc__' ) 

282 assert isinstance( Object.__doc__, str ) 

283 assert Object.__doc__ 

284 

285 

286@pytest.mark.parametrize( 

287 'module_qname, class_name', 

288 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

289) 

290def test_902_docstring_mentions_immutability( module_qname, class_name ): 

291 ''' Class docstring mentions immutability. ''' 

292 module = cache_import_module( module_qname ) 

293 Object = getattr( module, class_name ) 

294 fragment = base.generate_docstring( 'instance attributes immutability' ) 

295 assert fragment in Object.__doc__