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

139 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-10 04:11 +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_accretion( module_qname, class_name ): 

72 ''' Class accretes attributes. ''' 

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.IndelibleAttributeError ): 

81 Object.attr = -1 

82 assert 42 == Object.attr 

83 with pytest.raises( exceptions.IndelibleAttributeError ): 

84 del Object.attr 

85 assert 42 == Object.attr 

86 Object.accreted_attr = 'foo' 

87 assert 'foo' == Object.accreted_attr 

88 with pytest.raises( exceptions.IndelibleAttributeError ): 

89 Object.accreted_attr = 'bar' 

90 assert 'foo' == Object.accreted_attr 

91 with pytest.raises( exceptions.IndelibleAttributeError ): 

92 del Object.accreted_attr 

93 assert 'foo' == Object.accreted_attr 

94 

95 

96@pytest.mark.parametrize( 

97 'module_qname, class_name', 

98 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

99) 

100def test_110_class_decorators( module_qname, class_name ): 

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

102 module = cache_import_module( module_qname ) 

103 class_factory_class = getattr( module, class_name ) 

104 decorator_calls = [ ] 

105 

106 def test_decorator1( cls ): 

107 decorator_calls.append( 'decorator1' ) 

108 cls.decorator1_attr = 'value1' 

109 return cls 

110 

111 def test_decorator2( cls ): 

112 decorator_calls.append( 'decorator2' ) 

113 cls.decorator2_attr = 'value2' 

114 return cls 

115 

116 class Object( 

117 metaclass = class_factory_class, 

118 decorators = ( test_decorator1, test_decorator2 ) 

119 ): 

120 ''' test ''' 

121 attr = 42 

122 

123 _class_behaviors_ = { 'foo' } 

124 

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

126 assert 'value1' == Object.decorator1_attr 

127 assert 'value2' == Object.decorator2_attr 

128 with pytest.raises( exceptions.IndelibleAttributeError ): 

129 Object.decorator1_attr = 'new_value' 

130 with pytest.raises( exceptions.IndelibleAttributeError ): 

131 Object.decorator2_attr = 'new_value' 

132 

133 

134@pypy_skip_mark 

135@pytest.mark.parametrize( 

136 'module_qname, class_name', 

137 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

138) 

139def test_111_class_decorator_reproduction_method( module_qname, class_name ): 

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

141 module = cache_import_module( module_qname ) 

142 class_factory_class = getattr( module, class_name ) 

143 from dataclasses import dataclass 

144 

145 class Object( 

146 metaclass = class_factory_class, 

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

148 ): 

149 ''' test ''' 

150 value: str = 'test' 

151 

152 def method_with_super( self ): 

153 ''' References class cell on CPython. ''' 

154 super( ).__init__( ) 

155 return self.__class__.__name__ 

156 

157 def other_method_with_super( self ): 

158 ''' References class cell on CPython. ''' 

159 super( ).__init__( ) 

160 return 'other' 

161 

162 # Verify class was properly reproduced and both methods work 

163 obj = Object( ) 

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

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

166 

167 

168@pypy_skip_mark 

169@pytest.mark.parametrize( 

170 'module_qname, class_name', 

171 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

172) 

173def test_112_class_decorator_reproduction_property( module_qname, class_name ): 

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

175 module = cache_import_module( module_qname ) 

176 class_factory_class = getattr( module, class_name ) 

177 from dataclasses import dataclass 

178 

179 class Object( 

180 metaclass = class_factory_class, 

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

182 ): 

183 ''' test ''' 

184 value: str = 'test' 

185 

186 @property 

187 def prop_with_class( self ): 

188 ''' References class cell on CPython. ''' 

189 return self.__class__.__name__ 

190 

191 @property 

192 def other_prop_with_class( self ): 

193 ''' References class cell on CPython. ''' 

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

195 

196 # Verify class was properly reproduced and both properties work 

197 obj = Object( ) 

198 assert 'Object' == obj.prop_with_class 

199 assert 'other_Object' == obj.other_prop_with_class 

200 

201 

202@pypy_skip_mark 

203@pytest.mark.parametrize( 

204 'module_qname, class_name', 

205 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

206) 

207def test_113_class_decorator_reproduction_no_cell( module_qname, class_name ): 

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

209 module = cache_import_module( module_qname ) 

210 class_factory_class = getattr( module, class_name ) 

211 from dataclasses import dataclass 

212 

213 class Object( 

214 metaclass = class_factory_class, 

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

216 ): 

217 ''' test ''' 

218 value: str = 'test' 

219 

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

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

222 return 'no_cell' 

223 

224 # Verify class was properly reproduced 

225 obj = Object() 

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

227 

228 

229@pytest.mark.parametrize( 

230 'module_qname, class_name', 

231 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

232) 

233def test_114_decorator_error_handling( module_qname, class_name ): 

234 ''' Class handles decorator errors appropriately. ''' 

235 module = cache_import_module( module_qname ) 

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

237 getattr( module, class_name ) ) 

238 

239 def failing_decorator( cls ): 

240 raise ValueError( "Decorator failure" ) # noqa 

241 

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

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

244 metaclass = class_factory_class, 

245 decorators = ( failing_decorator, ) 

246 ): 

247 ''' test ''' 

248 

249 

250@pytest.mark.parametrize( 

251 'module_qname, class_name', 

252 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

253) 

254def test_120_docstring_assignment( module_qname, class_name ): 

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

256 module = cache_import_module( module_qname ) 

257 class_factory_class = getattr( module, class_name ) 

258 

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

260 ''' test ''' 

261 attr = 42 

262 

263 assert 'test' != Object.__doc__ 

264 assert 'dynamic' == Object.__doc__ 

265 

266 

267# TODO? String representations. 

268 

269 

270@pytest.mark.parametrize( 

271 'module_qname, class_name', 

272 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

273) 

274def test_900_docstring_sanity( module_qname, class_name ): 

275 ''' Class has valid docstring. ''' 

276 module = cache_import_module( module_qname ) 

277 class_factory_class = getattr( module, class_name ) 

278 assert hasattr( class_factory_class, '__doc__' ) 

279 assert isinstance( class_factory_class.__doc__, str ) 

280 assert class_factory_class.__doc__ 

281 

282 

283@pytest.mark.parametrize( 

284 'module_qname, class_name', 

285 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

286) 

287def test_901_docstring_describes_cfc( module_qname, class_name ): 

288 ''' Class docstring describes class factory class. ''' 

289 module = cache_import_module( module_qname ) 

290 class_factory_class = getattr( module, class_name ) 

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

292 assert fragment in class_factory_class.__doc__ 

293 

294 

295@pytest.mark.parametrize( 

296 'module_qname, class_name', 

297 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

298) 

299def test_902_docstring_mentions_accretion( module_qname, class_name ): 

300 ''' Class docstring mentions accretion. ''' 

301 module = cache_import_module( module_qname ) 

302 class_factory_class = getattr( module, class_name ) 

303 fragment = base.generate_docstring( 'class attributes accretion' ) 

304 assert fragment in class_factory_class.__doc__