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

133 statements  

« prev     ^ index     » next       coverage.py v7.6.8, created at 2024-12-05 01:36 +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 class factory classes. ''' 

22 

23# mypy: ignore-errors 

24# pylint: disable=magic-value-comparison,protected-access 

25 

26 

27import pytest 

28 

29from itertools import product 

30from platform import python_implementation 

31 

32from . import ( 

33 MODULES_QNAMES, 

34 PACKAGE_NAME, 

35 cache_import_module, 

36) 

37 

38 

39THESE_MODULE_QNAMES = tuple( 

40 name for name in MODULES_QNAMES if name.endswith( '.classes' ) ) 

41THESE_CLASSES_NAMES = ( 'Class', 'ABCFactory', 'ProtocolClass' ) 

42 

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

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

45 

46pypy_skip_mark = pytest.mark.skipif( 

47 'PyPy' == python_implementation( ), 

48 reason = "PyPy handles class cell updates differently" 

49) 

50 

51 

52@pytest.mark.parametrize( 

53 'module_qname, class_name', 

54 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

55) 

56def test_100_instantiation( module_qname, class_name ): 

57 ''' Class instantiates. ''' 

58 module = cache_import_module( module_qname ) 

59 class_factory_class = getattr( module, class_name ) 

60 

61 class Object( metaclass = class_factory_class ): 

62 ''' test ''' 

63 

64 assert isinstance( Object, class_factory_class ) 

65 

66 

67@pytest.mark.parametrize( 

68 'module_qname, class_name', 

69 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

70) 

71def test_101_immutability( module_qname, class_name ): 

72 ''' Class attributes are immutable. ''' 

73 module = cache_import_module( module_qname ) 

74 class_factory_class = getattr( module, class_name ) 

75 

76 class Object( metaclass = class_factory_class ): 

77 ''' test ''' 

78 attr = 42 

79 

80 with pytest.raises( exceptions.AttributeImmutabilityError ): 

81 Object.attr = -1 

82 assert 42 == Object.attr 

83 with pytest.raises( exceptions.AttributeImmutabilityError ): 

84 del Object.attr 

85 assert 42 == Object.attr 

86 with pytest.raises( exceptions.AttributeImmutabilityError ): 

87 Object.new_attr = 'foo' 

88 

89 

90@pytest.mark.parametrize( 

91 'module_qname, class_name', 

92 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

93) 

94def test_110_class_decorators( module_qname, class_name ): 

95 ''' Class accepts and applies decorators correctly. ''' 

96 module = cache_import_module( module_qname ) 

97 class_factory_class = getattr( module, class_name ) 

98 decorator_calls = [ ] 

99 

100 def test_decorator1( cls ): 

101 decorator_calls.append( 'decorator1' ) 

102 cls.decorator1_attr = 'value1' 

103 return cls 

104 

105 def test_decorator2( cls ): 

106 decorator_calls.append( 'decorator2' ) 

107 cls.decorator2_attr = 'value2' 

108 return cls 

109 

110 class Object( 

111 metaclass = class_factory_class, 

112 decorators = ( test_decorator1, test_decorator2 ) 

113 ): 

114 ''' test ''' 

115 attr = 42 

116 

117 _class_behaviors_ = { 'foo' } 

118 

119 assert [ 'decorator1', 'decorator2' ] == decorator_calls 

120 assert 'value1' == Object.decorator1_attr 

121 assert 'value2' == Object.decorator2_attr 

122 with pytest.raises( exceptions.AttributeImmutabilityError ): 

123 Object.decorator1_attr = 'new_value' 

124 with pytest.raises( exceptions.AttributeImmutabilityError ): 

125 Object.decorator2_attr = 'new_value' 

126 

127 

128@pypy_skip_mark 

129@pytest.mark.parametrize( 

130 'module_qname, class_name', 

131 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

132) 

133def test_111_class_decorator_reproduction_method( module_qname, class_name ): 

134 ''' Class handles decorator reproduction with super() method. ''' 

135 module = cache_import_module( module_qname ) 

136 class_factory_class = getattr( module, class_name ) 

137 from dataclasses import dataclass 

138 

139 class Object( 

140 metaclass = class_factory_class, 

141 decorators = ( dataclass( slots = True ), ) 

142 ): 

143 ''' test ''' 

144 value: str = 'test' 

145 

146 def method_with_super( self ): 

147 ''' References class cell on CPython. ''' 

148 super( ).__init__( ) 

149 return self.__class__.__name__ 

150 

151 def other_method_with_super( self ): 

152 ''' References class cell on CPython. ''' 

153 super( ).__init__( ) 

154 return 'other' 

155 

156 # Verify class was properly reproduced and both methods work 

157 obj = Object( ) 

158 assert 'Object' == obj.method_with_super( ) 

159 assert 'other' == obj.other_method_with_super( ) 

160 

161 

162@pypy_skip_mark 

163@pytest.mark.parametrize( 

164 'module_qname, class_name', 

165 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

166) 

167def test_112_class_decorator_reproduction_property( module_qname, class_name ): 

168 ''' Class handles decorator reproduction with dotted access property. ''' 

169 module = cache_import_module( module_qname ) 

170 class_factory_class = getattr( module, class_name ) 

171 from dataclasses import dataclass 

172 

173 class Object( 

174 metaclass = class_factory_class, 

175 decorators = ( dataclass( slots = True ), ) 

176 ): 

177 ''' test ''' 

178 value: str = 'test' 

179 

180 @property 

181 def prop_with_class( self ): 

182 ''' References class cell on CPython. ''' 

183 return self.__class__.__name__ 

184 

185 @property 

186 def other_prop_with_class( self ): 

187 ''' References class cell on CPython. ''' 

188 return f"other_{self.__class__.__name__}" 

189 

190 # Verify class was properly reproduced and both properties work 

191 obj = Object( ) 

192 assert 'Object' == obj.prop_with_class 

193 assert 'other_Object' == obj.other_prop_with_class 

194 

195 

196@pypy_skip_mark 

197@pytest.mark.parametrize( 

198 'module_qname, class_name', 

199 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

200) 

201def test_113_class_decorator_reproduction_no_cell( module_qname, class_name ): 

202 ''' Class handles decorator reproduction with no class cell. ''' 

203 module = cache_import_module( module_qname ) 

204 class_factory_class = getattr( module, class_name ) 

205 from dataclasses import dataclass 

206 

207 class Object( 

208 metaclass = class_factory_class, 

209 decorators = ( dataclass( slots = True ), ) 

210 ): 

211 ''' test ''' 

212 value: str = 'test' 

213 

214 def method_without_cell( self ): # pylint: disable=no-self-use 

215 ''' Operates without class cell on CPython. ''' 

216 return 'no_cell' 

217 

218 # Verify class was properly reproduced 

219 obj = Object( ) 

220 assert 'no_cell' == obj.method_without_cell( ) 

221 

222 

223@pytest.mark.parametrize( 

224 'module_qname, class_name', 

225 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

226) 

227def test_114_decorator_error_handling( module_qname, class_name ): 

228 ''' Class handles decorator errors appropriately. ''' 

229 module = cache_import_module( module_qname ) 

230 class_factory_class = ( # pylint: disable=unused-variable 

231 getattr( module, class_name ) ) 

232 

233 def failing_decorator( cls ): 

234 raise ValueError( "Decorator failure" ) # noqa 

235 

236 with pytest.raises( ValueError, match = "Decorator failure" ): 

237 class Object( # pylint: disable=unused-variable 

238 metaclass = class_factory_class, 

239 decorators = ( failing_decorator, ) 

240 ): 

241 ''' test ''' 

242 

243 

244@pytest.mark.parametrize( 

245 'module_qname, class_name', 

246 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

247) 

248def test_120_docstring_assignment( module_qname, class_name ): 

249 ''' Class has dynamically-assigned docstring. ''' 

250 module = cache_import_module( module_qname ) 

251 class_factory_class = getattr( module, class_name ) 

252 

253 class Object( metaclass = class_factory_class, docstring = 'dynamic' ): 

254 ''' test ''' 

255 attr = 42 

256 

257 assert 'test' != Object.__doc__ 

258 assert 'dynamic' == Object.__doc__ 

259 

260 

261@pytest.mark.parametrize( 

262 'module_qname, class_name', 

263 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

264) 

265def test_900_docstring_sanity( module_qname, class_name ): 

266 ''' Class has valid docstring. ''' 

267 module = cache_import_module( module_qname ) 

268 class_factory_class = getattr( module, class_name ) 

269 assert hasattr( class_factory_class, '__doc__' ) 

270 assert isinstance( class_factory_class.__doc__, str ) 

271 assert class_factory_class.__doc__ 

272 

273 

274@pytest.mark.parametrize( 

275 'module_qname, class_name', 

276 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

277) 

278def test_901_docstring_describes_cfc( module_qname, class_name ): 

279 ''' Class docstring describes class factory class. ''' 

280 module = cache_import_module( module_qname ) 

281 class_factory_class = getattr( module, class_name ) 

282 fragment = base.generate_docstring( 'description of class factory class' ) 

283 assert fragment in class_factory_class.__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 class_factory_class = getattr( module, class_name ) 

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

295 assert fragment in class_factory_class.__doc__