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

176 statements  

« prev     ^ index     » next       coverage.py v7.5.4, created at 2024-07-06 17:17 +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 

30 

31from . import ( 

32 CONCEALMENT_PACKAGES_NAMES, 

33 MODULES_QNAMES, 

34 PACKAGE_NAME, 

35 PROTECTION_PACKAGES_NAMES, 

36 cache_import_module, 

37) 

38 

39 

40THESE_MODULE_QNAMES = tuple( 

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

42THESE_CONCEALMENT_MODULE_QNAMES = tuple( 

43 name for name in THESE_MODULE_QNAMES 

44 if name.startswith( CONCEALMENT_PACKAGES_NAMES ) ) 

45THESE_NONCONCEALMENT_MODULE_QNAMES = tuple( 

46 name for name in THESE_MODULE_QNAMES 

47 if not name.startswith( CONCEALMENT_PACKAGES_NAMES ) ) 

48THESE_PROTECTION_MODULE_QNAMES = tuple( 

49 name for name in THESE_MODULE_QNAMES 

50 if name.startswith( PROTECTION_PACKAGES_NAMES ) ) 

51THESE_NONPROTECTION_MODULE_QNAMES = tuple( 

52 name for name in THESE_MODULE_QNAMES 

53 if not name.startswith( PROTECTION_PACKAGES_NAMES ) ) 

54ABC_FACTORIES_NAMES = ( 'ABCFactory', ) 

55THESE_CLASSES_NAMES = ( 'Class', *ABC_FACTORIES_NAMES, ) 

56NONABC_FACTORIES_NAMES = tuple( 

57 name for name in THESE_CLASSES_NAMES 

58 if name not in ABC_FACTORIES_NAMES ) 

59 

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

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

62 

63 

64@pytest.mark.parametrize( 

65 'module_qname, class_name', 

66 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

67) 

68def test_100_instantiation( module_qname, class_name ): 

69 ''' Class instantiates. ''' 

70 module = cache_import_module( module_qname ) 

71 class_factory_class = getattr( module, class_name ) 

72 

73 class Object( metaclass = class_factory_class ): 

74 ''' test ''' 

75 

76 assert isinstance( Object, class_factory_class ) 

77 

78 

79@pytest.mark.parametrize( 

80 'module_qname, class_name', 

81 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

82) 

83def test_101_accretion( module_qname, class_name ): 

84 ''' Class accretes attributes. ''' 

85 module = cache_import_module( module_qname ) 

86 class_factory_class = getattr( module, class_name ) 

87 

88 class Object( metaclass = class_factory_class ): 

89 ''' test ''' 

90 attr = 42 

91 

92 with pytest.raises( exceptions.IndelibleAttributeError ): 

93 Object.attr = -1 

94 assert 42 == Object.attr 

95 with pytest.raises( exceptions.IndelibleAttributeError ): 

96 del Object.attr 

97 assert 42 == Object.attr 

98 Object.accreted_attr = 'foo' 

99 assert 'foo' == Object.accreted_attr 

100 with pytest.raises( exceptions.IndelibleAttributeError ): 

101 Object.accreted_attr = 'bar' 

102 assert 'foo' == Object.accreted_attr 

103 with pytest.raises( exceptions.IndelibleAttributeError ): 

104 del Object.accreted_attr 

105 assert 'foo' == Object.accreted_attr 

106 

107 

108@pytest.mark.parametrize( 

109 'module_qname, class_name', 

110 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

111) 

112def test_102_docstring_assignment( module_qname, class_name ): 

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

114 module = cache_import_module( module_qname ) 

115 class_factory_class = getattr( module, class_name ) 

116 

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

118 ''' test ''' 

119 attr = 42 

120 

121 assert 'test' != Object.__doc__ 

122 assert 'dynamic' == Object.__doc__ 

123 

124 

125@pytest.mark.parametrize( 

126 'module_qname, class_name', 

127 product( THESE_CONCEALMENT_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

128) 

129def test_110_attribute_concealment( module_qname, class_name ): 

130 ''' Class conceals attributes. ''' 

131 module = cache_import_module( module_qname ) 

132 class_factory_class = getattr( module, class_name ) 

133 

134 class Object( metaclass = class_factory_class ): 

135 ''' test ''' 

136 _class_attribute_visibility_includes_ = frozenset( ( '_private', ) ) 

137 

138 assert not dir( Object ) 

139 Object.public = 42 

140 assert 'public' in dir( Object ) 

141 Object._nonpublic = 3.1415926535 

142 assert '_nonpublic' not in dir( Object ) 

143 assert '_private' not in dir( Object ) 

144 Object._private = 'foo' 

145 assert '_private' in dir( Object ) 

146 

147 

148@pytest.mark.parametrize( 

149 'module_qname, class_name', 

150 product( THESE_NONCONCEALMENT_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

151) 

152def test_111_attribute_nonconcealment( module_qname, class_name ): 

153 ''' Class does not conceal attributes. ''' 

154 module = cache_import_module( module_qname ) 

155 class_factory_class = getattr( module, class_name ) 

156 

157 class Object( metaclass = class_factory_class ): 

158 ''' test ''' 

159 _class_attribute_visibility_includes_ = frozenset( ( '_private', ) ) 

160 

161 assert '_class_attribute_visibility_includes_' in dir( Object ) 

162 Object.public = 42 

163 assert 'public' in dir( Object ) 

164 Object._nonpublic = 3.1415926535 

165 assert '_nonpublic' in dir( Object ) 

166 assert '_private' not in dir( Object ) 

167 Object._private = 'foo' 

168 assert '_private' in dir( Object ) 

169 

170 

171@pytest.mark.parametrize( 

172 'module_qname, class_name', 

173 product( THESE_PROTECTION_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

174) 

175def test_150_class_attribute_protection( module_qname, class_name ): 

176 ''' Class attributes are protected. ''' 

177 module = cache_import_module( module_qname ) 

178 class_factory_class = getattr( module, class_name ) 

179 with pytest.raises( exceptions.IndelibleAttributeError ): 

180 class_factory_class.__setattr__ = None 

181 with pytest.raises( exceptions.IndelibleAttributeError ): 

182 del class_factory_class.__setattr__ 

183 class_factory_class.foo = 42 

184 with pytest.raises( exceptions.IndelibleAttributeError ): 

185 class_factory_class.foo = -1 

186 with pytest.raises( exceptions.IndelibleAttributeError ): 

187 del class_factory_class.foo 

188 # Cleanup. 

189 type.__delattr__( class_factory_class, 'foo' ) 

190 

191 

192@pytest.mark.parametrize( 

193 'module_qname, class_name', 

194 product( THESE_NONPROTECTION_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

195) 

196def test_151_class_attribute_nonprotection( module_qname, class_name ): 

197 ''' Class attributes are not protected. ''' 

198 module = cache_import_module( module_qname ) 

199 class_factory_class = getattr( module, class_name ) 

200 # Note: Do not mess with '__delattr__' or '__setattr__' as part of testing. 

201 # The functions on the class are restored as descriptor wrappers. 

202 # This breaks resolution of these class methods. 

203 class_factory_class.foo = 42 

204 assert 42 == class_factory_class.foo 

205 class_factory_class.foo = -1 

206 assert -1 == class_factory_class.foo 

207 del class_factory_class.foo 

208 assert not hasattr( class_factory_class, 'foo' ) 

209 

210 

211@pytest.mark.parametrize( 

212 'module_qname, class_name', 

213 product( THESE_MODULE_QNAMES, ABC_FACTORIES_NAMES ) 

214) 

215def test_200_abc_mutation_allowance( module_qname, class_name ): 

216 ''' Class allows mutation of ABC machinery. ''' 

217 from collections.abc import Mapping 

218 from platform import python_implementation 

219 module = cache_import_module( module_qname ) 

220 class_factory_class = getattr( module, class_name ) 

221 

222 class Object( metaclass = class_factory_class ): 

223 ''' test ''' 

224 

225 class Dict( # pylint: disable=abstract-method,unused-variable 

226 Object, Mapping 

227 ): 

228 ''' test ''' 

229 

230 python_kind = python_implementation( ) 

231 assert hasattr( Object, '__abstractmethods__' ) 

232 if python_kind in ( 'CPython', ): 

233 assert hasattr( Object, '_abc_impl' ) 

234 

235 

236@pytest.mark.parametrize( 

237 'module_qname, class_name', 

238 product( THESE_MODULE_QNAMES, NONABC_FACTORIES_NAMES ) 

239) 

240def test_201_abc_mutation_prevention( module_qname, class_name ): 

241 ''' Class prevents mutation of ABC machinery. ''' 

242 from abc import ABCMeta 

243 from collections.abc import Mapping 

244 module = cache_import_module( module_qname ) 

245 class_factory_class = getattr( module, class_name ) 

246 

247 class Class( class_factory_class, ABCMeta ): 

248 ''' test ''' 

249 

250 class Object( metaclass = Class ): 

251 ''' test ''' 

252 

253 with pytest.raises( exceptions.IndelibleAttributeError ): 

254 

255 class Dict( # pylint: disable=abstract-method,unused-variable 

256 Object, Mapping 

257 ): 

258 ''' test ''' 

259 

260 

261# TODO? String representations. 

262 

263 

264@pytest.mark.parametrize( 

265 'module_qname, class_name', 

266 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

267) 

268def test_900_docstring_sanity( module_qname, class_name ): 

269 ''' Class has valid docstring. ''' 

270 module = cache_import_module( module_qname ) 

271 class_factory_class = getattr( module, class_name ) 

272 assert hasattr( class_factory_class, '__doc__' ) 

273 assert isinstance( class_factory_class.__doc__, str ) 

274 assert class_factory_class.__doc__ 

275 

276 

277@pytest.mark.parametrize( 

278 'module_qname, class_name', 

279 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

280) 

281def test_901_docstring_describes_cfc( module_qname, class_name ): 

282 ''' Class docstring describes class factory class. ''' 

283 module = cache_import_module( module_qname ) 

284 class_factory_class = getattr( module, class_name ) 

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

286 assert fragment in class_factory_class.__doc__ 

287 

288 

289@pytest.mark.parametrize( 

290 'module_qname, class_name', 

291 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

292) 

293def test_902_docstring_mentions_accretion( module_qname, class_name ): 

294 ''' Class docstring mentions accretion. ''' 

295 module = cache_import_module( module_qname ) 

296 class_factory_class = getattr( module, class_name ) 

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

298 assert fragment in class_factory_class.__doc__ 

299 

300 

301@pytest.mark.parametrize( 

302 'module_qname, class_name', 

303 product( THESE_CONCEALMENT_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

304) 

305def test_910_docstring_mentions_concealment( module_qname, class_name ): 

306 ''' Class docstring mentions concealment. ''' 

307 module = cache_import_module( module_qname ) 

308 class_factory_class = getattr( module, class_name ) 

309 fragment = base.generate_docstring( 'class attributes concealment' ) 

310 assert fragment in class_factory_class.__doc__ 

311 

312 

313@pytest.mark.parametrize( 

314 'module_qname, class_name', 

315 product( THESE_NONCONCEALMENT_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

316) 

317def test_911_docstring_not_mentions_concealment( module_qname, class_name ): 

318 ''' Class docstring does not mention concealment. ''' 

319 module = cache_import_module( module_qname ) 

320 class_factory_class = getattr( module, class_name ) 

321 fragment = base.generate_docstring( 'class attributes concealment' ) 

322 assert fragment not in class_factory_class.__doc__ 

323 

324 

325@pytest.mark.parametrize( 

326 'module_qname, class_name', 

327 product( THESE_PROTECTION_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

328) 

329def test_930_docstring_mentions_protection( module_qname, class_name ): 

330 ''' Class docstring mentions protection. ''' 

331 module = cache_import_module( module_qname ) 

332 class_factory_class = getattr( module, class_name ) 

333 fragment = base.generate_docstring( 'protection of class factory class' ) 

334 assert fragment in class_factory_class.__doc__ 

335 

336 

337@pytest.mark.parametrize( 

338 'module_qname, class_name', 

339 product( THESE_NONPROTECTION_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

340) 

341def test_931_docstring_not_mentions_protection( module_qname, class_name ): 

342 ''' Class docstring does not mention protection. ''' 

343 module = cache_import_module( module_qname ) 

344 class_factory_class = getattr( module, class_name ) 

345 fragment = base.generate_docstring( 'protection of class factory class' ) 

346 assert fragment not in class_factory_class.__doc__ 

347 

348 

349@pytest.mark.parametrize( 

350 'module_qname, class_name', 

351 product( THESE_MODULE_QNAMES, ABC_FACTORIES_NAMES ) 

352) 

353def test_950_docstring_mentions_abc_exemption( module_qname, class_name ): 

354 ''' Class docstring mentions ABC attributes exemption. ''' 

355 module = cache_import_module( module_qname ) 

356 class_factory_class = getattr( module, class_name ) 

357 fragment = base.generate_docstring( 'abc attributes exemption' ) 

358 assert fragment in class_factory_class.__doc__ 

359 

360 

361@pytest.mark.parametrize( 

362 'module_qname, class_name', 

363 product( THESE_MODULE_QNAMES, NONABC_FACTORIES_NAMES ) 

364) 

365def test_951_docstring_not_mentions_abc_exemption( module_qname, class_name ): 

366 ''' Class docstring does not mention ABC attributes exemption. ''' 

367 module = cache_import_module( module_qname ) 

368 class_factory_class = getattr( module, class_name ) 

369 fragment = base.generate_docstring( 'abc attributes exemption' ) 

370 assert fragment not in class_factory_class.__doc__