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

275 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-10 22:22 +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 dictionaries. ''' 

22 

23# mypy: ignore-errors 

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

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

26 

27 

28import pytest 

29 

30from itertools import product 

31from types import MappingProxyType as DictionaryProxy 

32 

33from . import ( 

34 MODULES_QNAMES, 

35 PACKAGE_NAME, 

36 cache_import_module, 

37) 

38 

39 

40THESE_MODULE_QNAMES = tuple( 

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

42INITARGS_NAMES = ( 'Dictionary', ) 

43VALIDATOR_NAMES = ( 'ValidatorDictionary', 'ProducerValidatorDictionary' ) 

44PRODUCER_NAMES = ( 'ProducerDictionary', 'ProducerValidatorDictionary' ) 

45THESE_CLASSES_NAMES = ( *INITARGS_NAMES, *PRODUCER_NAMES, *VALIDATOR_NAMES ) 

46PRODUCER_VALIDATOR_NAMES = tuple( 

47 name for name in THESE_CLASSES_NAMES 

48 if name in PRODUCER_NAMES and name in VALIDATOR_NAMES ) 

49 

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

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

52 

53 

54def select_arguments( class_name ): 

55 ''' Chooses initializer arguments depending on class. ''' 

56 if class_name in PRODUCER_NAMES: 

57 if class_name in VALIDATOR_NAMES: 

58 return ( list, lambda k, v: isinstance( v, list ), ), { } 

59 return ( list, ), { } 

60 if class_name in VALIDATOR_NAMES: 

61 return ( lambda k, v: isinstance( v, int ), ), { } 

62 return ( ), { } 

63 

64 

65def select_simple_arguments( class_name ): 

66 ''' Choose simple test arguments depending on class. ''' 

67 posargs = ( ( ( 'foo', 1 ), ( 'bar', 2 ) ), { 'unicorn': True } ) 

68 nomargs = DictionaryProxy( dict( orb = False ) ) 

69 if class_name in PRODUCER_NAMES: 

70 if class_name in VALIDATOR_NAMES: 

71 posargs = ( 

72 ( ( 'foo', [ 1 ] ), ( 'bar', [ 2 ] ) ), { 'unicorn': [ ] } ) 

73 nomargs = DictionaryProxy( dict( orb = [ ] ) ) 

74 return posargs, nomargs 

75 return posargs, nomargs 

76 if class_name in VALIDATOR_NAMES: 

77 posargs = ( ( ( 'foo', 1 ), ( 'bar', 2 ) ), { 'unicorn': 42 } ) 

78 nomargs = DictionaryProxy( dict( orb = 84 ) ) 

79 return posargs, nomargs 

80 return posargs, nomargs 

81 

82 

83@pytest.mark.parametrize( 

84 'module_qname, class_name', 

85 product( THESE_MODULE_QNAMES, INITARGS_NAMES ) 

86) 

87def test_100_instantiation( module_qname, class_name ): 

88 ''' Class instantiates. ''' 

89 module = cache_import_module( module_qname ) 

90 factory = getattr( module, class_name ) 

91 dct1 = factory( ) 

92 assert isinstance( dct1, factory ) 

93 dct2 = factory( 

94 ( ( 'foo', 1 ), ( 'bar', 2 ) ), { 'unicorn': True }, orb = False ) 

95 assert isinstance( dct2, factory ) 

96 assert 1 == dct2[ 'foo' ] 

97 assert 2 == dct2[ 'bar' ] 

98 assert dct2[ 'unicorn' ] 

99 assert not dct2[ 'orb' ] 

100 assert ( 'foo', 'bar', 'unicorn', 'orb' ) == tuple( dct2.keys( ) ) 

101 assert ( 1, 2, True, False ) == tuple( dct2.values( ) ) 

102 

103 

104@pytest.mark.parametrize( 

105 'module_qname, class_name', 

106 product( THESE_MODULE_QNAMES, PRODUCER_NAMES ) 

107) 

108def test_101_instantiation( module_qname, class_name ): 

109 ''' Producer class instantiates. ''' 

110 module = cache_import_module( module_qname ) 

111 factory = getattr( module, class_name ) 

112 posargs, nomargs = select_arguments( class_name ) 

113 dct = factory( *posargs, **nomargs ) 

114 assert isinstance( dct, factory ) 

115 assert isinstance( dct[ 'a' ], list ) 

116 

117 

118@pytest.mark.parametrize( 

119 'module_qname, class_name', 

120 product( THESE_MODULE_QNAMES, VALIDATOR_NAMES ) 

121) 

122def test_102_instantiation( module_qname, class_name ): 

123 ''' Validator class instantiates. ''' 

124 module = cache_import_module( module_qname ) 

125 factory = getattr( module, class_name ) 

126 posargs, nomargs = select_arguments( class_name ) 

127 dct = factory( *posargs, **nomargs ) 

128 assert isinstance( dct, factory ) 

129 value = [ ] if class_name in PRODUCER_NAMES else 42 

130 dct[ 'a' ] = value 

131 with pytest.raises( exceptions.EntryValidationError ): 

132 dct[ 'b' ] = 'invalid value' 

133 

134 

135@pytest.mark.parametrize( 

136 'module_qname, class_name', 

137 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

138) 

139def test_110_attribute_accretion( module_qname, class_name ): 

140 ''' Dictionary accretes attributes. ''' 

141 module = cache_import_module( module_qname ) 

142 factory = getattr( module, class_name ) 

143 posargs, nomargs = select_arguments( class_name ) 

144 obj = factory( *posargs, **nomargs ) 

145 obj.attr = 42 

146 with pytest.raises( exceptions.IndelibleAttributeError ): 

147 obj.attr = -1 

148 assert 42 == obj.attr 

149 with pytest.raises( exceptions.IndelibleAttributeError ): 

150 del obj.attr 

151 assert 42 == obj.attr 

152 

153 

154@pytest.mark.parametrize( 

155 'module_qname, class_name', 

156 product( THESE_MODULE_QNAMES, INITARGS_NAMES ) 

157) 

158def test_200_dictionary_entry_accretion( module_qname, class_name ): 

159 ''' Dictionary accretes entries. ''' 

160 module = cache_import_module( module_qname ) 

161 factory = getattr( module, class_name ) 

162 simple_posargs, simple_nomargs = select_simple_arguments( class_name ) 

163 dct = factory( *simple_posargs, **simple_nomargs ) 

164 with pytest.raises( exceptions.IndelibleEntryError ): 

165 del dct[ 'foo' ] 

166 with pytest.raises( exceptions.IndelibleEntryError ): 

167 dct[ 'foo' ] = 666 

168 dct[ 'baz' ] = 43 

169 with pytest.raises( exceptions.IndelibleEntryError ): 

170 del dct[ 'baz' ] 

171 with pytest.raises( exceptions.IndelibleEntryError ): 

172 dct[ 'baz' ] = 42 

173 

174 

175@pytest.mark.parametrize( 

176 'module_qname, class_name', 

177 product( THESE_MODULE_QNAMES, PRODUCER_NAMES ) 

178) 

179def test_201_producer_dictionary_entry_accretion( module_qname, class_name ): 

180 ''' Producer dictionary accretes entries. ''' 

181 module = cache_import_module( module_qname ) 

182 factory = getattr( module, class_name ) 

183 posargs, nomargs = select_arguments( class_name ) 

184 dct = factory( *posargs, **nomargs ) 

185 if class_name not in VALIDATOR_NAMES: 

186 dct[ 'baz' ] = 43 

187 else: dct[ 'baz' ] = [ 43 ] 

188 with pytest.raises( exceptions.IndelibleEntryError ): 

189 del dct[ 'baz' ] 

190 with pytest.raises( exceptions.IndelibleEntryError ): 

191 if class_name not in VALIDATOR_NAMES: 

192 dct[ 'baz' ] = 42 

193 else: dct[ 'baz' ] = [ 43 ] 

194 dct[ 'abc' ].append( 12 ) 

195 assert 12 == dct[ 'abc' ][ 0 ] 

196 with pytest.raises( exceptions.IndelibleEntryError ): 

197 del dct[ 'abc' ] 

198 with pytest.raises( exceptions.IndelibleEntryError ): 

199 if class_name not in VALIDATOR_NAMES: 

200 dct[ 'abc' ] = 666 

201 else: dct[ 'abc' ] = [ 666 ] 

202 

203 

204@pytest.mark.parametrize( 

205 'module_qname, class_name', 

206 product( THESE_MODULE_QNAMES, VALIDATOR_NAMES ) 

207) 

208def test_202_validator_dictionary_entry_accretion( module_qname, class_name ): 

209 ''' Validator dictionary accretes valid entries only. ''' 

210 module = cache_import_module( module_qname ) 

211 factory = getattr( module, class_name ) 

212 posargs, nomargs = select_arguments( class_name ) 

213 dct = factory( *posargs, **nomargs ) 

214 value = [ ] if class_name in PRODUCER_NAMES else 42 

215 dct[ 'foo' ] = value 

216 with pytest.raises( exceptions.EntryValidationError ): 

217 dct[ 'bar' ] = 'invalid value' 

218 with pytest.raises( exceptions.IndelibleEntryError ): 

219 dct[ 'foo' ] = value 

220 if class_name in PRODUCER_NAMES: 

221 lst = dct[ 'baz' ] 

222 assert isinstance( lst, list ) 

223 lst.append( 42 ) 

224 assert 42 == dct[ 'baz' ][ 0 ] 

225 

226 

227@pytest.mark.parametrize( 

228 'module_qname, class_name', 

229 product( THESE_MODULE_QNAMES, PRODUCER_VALIDATOR_NAMES ) 

230) 

231def test_205_producer_validator_invalid_production( module_qname, class_name ): 

232 ''' Producer-validator dictionary rejects invalid produced values. ''' 

233 module = cache_import_module( module_qname ) 

234 factory = getattr( module, class_name ) 

235 # Producer that returns invalid values (not a list) 

236 dct = factory( lambda: 42, lambda k, v: isinstance( v, list ) ) 

237 with pytest.raises( exceptions.EntryValidationError ): 

238 _ = dct[ 'foo' ] # Production should fail validation 

239 

240 

241@pytest.mark.parametrize( 

242 'module_qname, class_name', 

243 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

244) 

245def test_210_dictionary_entry_accretion_via_update( module_qname, class_name ): 

246 ''' Dictionary accretes entries via update. ''' 

247 module = cache_import_module( module_qname ) 

248 factory = getattr( module, class_name ) 

249 posargs, nomargs = select_arguments( class_name ) 

250 dct = factory( *posargs, **nomargs ) 

251 simple_posargs, simple_nomargs = select_simple_arguments( class_name ) 

252 dct.update( *simple_posargs, **simple_nomargs ) 

253 with pytest.raises( exceptions.IndelibleEntryError ): 

254 del dct[ 'foo' ] 

255 with pytest.raises( exceptions.IndelibleEntryError ): 

256 if class_name not in PRODUCER_VALIDATOR_NAMES: dct[ 'foo' ] = 666 

257 else: dct[ 'foo' ] = [ 666 ] 

258 if class_name not in PRODUCER_VALIDATOR_NAMES: dct[ 'baz' ] = 43 

259 else: dct[ 'baz' ] = [ 43 ] 

260 with pytest.raises( exceptions.IndelibleEntryError ): 

261 del dct[ 'baz' ] 

262 with pytest.raises( exceptions.IndelibleEntryError ): 

263 if class_name not in PRODUCER_VALIDATOR_NAMES: dct[ 'baz' ] = 42 

264 else: dct[ 'baz' ] = [ 42 ] 

265 

266 

267@pytest.mark.parametrize( 

268 'module_qname, class_name', 

269 product( THESE_MODULE_QNAMES, VALIDATOR_NAMES ) 

270) 

271def test_211_validator_dictionary_entry_accretion_via_update( 

272 module_qname, class_name 

273): 

274 ''' Validator dictionary accretes valid entries via update. ''' 

275 module = cache_import_module( module_qname ) 

276 factory = getattr( module, class_name ) 

277 posargs, nomargs = select_arguments( class_name ) 

278 dct = factory( *posargs, **nomargs ) 

279 value = [ ] if class_name in PRODUCER_NAMES else 42 

280 dct.update( foo = value ) 

281 with pytest.raises( exceptions.EntryValidationError ): 

282 dct.update( bar = 'invalid value' ) 

283 with pytest.raises( exceptions.IndelibleEntryError ): 

284 dct[ 'foo' ] = value 

285 

286 

287@pytest.mark.parametrize( 

288 'module_qname, class_name', 

289 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

290) 

291def test_220_duplication( module_qname, class_name ): 

292 ''' Dictionary is duplicable. ''' 

293 module = cache_import_module( module_qname ) 

294 factory = getattr( module, class_name ) 

295 posargs, nomargs = select_arguments( class_name ) 

296 odct = factory( *posargs, **nomargs ) 

297 ddct = odct.copy( ) 

298 assert odct == ddct 

299 if class_name in PRODUCER_NAMES: 

300 odct[ 'baz' ] = [ 42 ] 

301 else: odct[ 'baz' ] = 42 

302 assert odct != ddct 

303 

304 

305@pytest.mark.parametrize( 

306 'module_qname, class_name', 

307 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

308) 

309def test_221_dictionary_iterability( module_qname, class_name ): 

310 ''' Dictionary is iterable. ''' 

311 module = cache_import_module( module_qname ) 

312 factory = getattr( module, class_name ) 

313 posargs, nomargs = select_arguments( class_name ) 

314 dct = factory( *posargs, **nomargs ) 

315 simple_posargs, simple_nomargs = select_simple_arguments( class_name ) 

316 dct.update( *simple_posargs, **simple_nomargs ) 

317 assert frozenset( dct.keys( ) ) == frozenset( iter( dct ) ) 

318 assert tuple( dct.items( ) ) == tuple( zip( dct.keys( ), dct.values( ) ) ) 

319 

320 

321@pytest.mark.parametrize( 

322 'module_qname, class_name', 

323 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

324) 

325def test_222_dictionary_measurability( module_qname, class_name ): 

326 ''' Dictionary is measurable. ''' 

327 module = cache_import_module( module_qname ) 

328 factory = getattr( module, class_name ) 

329 posargs, nomargs = select_arguments( class_name ) 

330 dct = factory( *posargs, **nomargs ) 

331 simple_posargs, simple_nomargs = select_simple_arguments( class_name ) 

332 dct.update( *simple_posargs, **simple_nomargs ) 

333 assert len( dct.keys( ) ) == len( dct ) 

334 assert len( dct.items( ) ) == len( dct ) 

335 assert len( dct.values( ) ) == len( dct ) 

336 

337 

338@pytest.mark.parametrize( 

339 'module_qname, class_name', 

340 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

341) 

342def test_225_dictionary_equality( # pylint: disable=too-many-locals 

343 module_qname, class_name 

344): 

345 ''' Dictionary is equivalent to another dictionary with same values. ''' 

346 module = cache_import_module( module_qname ) 

347 factory = getattr( module, class_name ) 

348 posargs, nomargs = select_arguments( class_name ) 

349 dct1 = factory( *posargs, **nomargs ) 

350 simple_posargs, simple_nomargs = select_simple_arguments( class_name ) 

351 dct1.update( *simple_posargs, **simple_nomargs ) 

352 dct2 = dct1.copy( ) 

353 dct3 = dict( dct1 ) 

354 assert dct1 == dct2 

355 assert dct2 == dct1 

356 assert dct1 == dct3 

357 assert dct3 == dct1 

358 assert not ( dct1 == -1 ) # pylint: disable=superfluous-parens 

359 assert dct1 != -1 

360 assert dct1 != ( ) 

361 if class_name not in PRODUCER_VALIDATOR_NAMES: dct2[ 'baz' ] = 43 

362 else: dct2[ 'baz' ] = [ 43 ] 

363 assert dct1 != dct2 

364 assert dct2 != dct1 

365 

366 

367@pytest.mark.parametrize( 

368 'module_qname, class_name', 

369 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

370) 

371def test_230_string_representation( module_qname, class_name ): 

372 ''' Dictionary has expected string representations. ''' 

373 module = cache_import_module( module_qname ) 

374 factory = getattr( module, class_name ) 

375 posargs, nomargs = select_arguments( class_name ) 

376 dct = factory( *posargs, **nomargs ) 

377 simple_posargs, simple_nomargs = select_simple_arguments( class_name ) 

378 dct.update( *simple_posargs, **simple_nomargs ) 

379 cdct = dict( dct ) 

380 assert str( cdct ) == str( dct ) 

381 assert str( cdct ) in repr( dct ) 

382 assert base.calculate_fqname( dct ) in repr( dct ) 

383 

384 

385@pytest.mark.parametrize( 

386 'module_qname, class_name', 

387 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

388) 

389def test_240_dictionary_entry_optional_retrieval( module_qname, class_name ): 

390 ''' Default value on optional access of dictionary entry. ''' 

391 module = cache_import_module( module_qname ) 

392 factory = getattr( module, class_name ) 

393 posargs, nomargs = select_arguments( class_name ) 

394 dct = factory( *posargs, **nomargs ) 

395 simple_posargs, simple_nomargs = select_simple_arguments( class_name ) 

396 dct.update( *simple_posargs, **simple_nomargs ) 

397 assert None is dct.get( 'baz' ) 

398 assert -1 == dct.get( 'baz', -1 ) 

399 assert -1 == dct.get( 'baz', default = -1 ) 

400 if class_name not in PRODUCER_VALIDATOR_NAMES: 

401 assert 1 == dct.get( 'foo' ) 

402 assert 1 == dct.get( 'foo', -1 ) 

403 else: 

404 assert [ 1 ] == dct.get( 'foo' ) 

405 assert [ 1 ] == dct.get( 'foo', -1 ) 

406 

407 

408@pytest.mark.parametrize( 

409 'module_qname, class_name', 

410 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

411) 

412def test_250_subclasses_abc_dictionary( module_qname, class_name ): 

413 ''' Subclasses 'collections.abc.Mapping'. ''' 

414 from collections.abc import Mapping as AbstractDictionary 

415 module = cache_import_module( module_qname ) 

416 factory = getattr( module, class_name ) 

417 issubclass( factory, AbstractDictionary ) 

418 

419 

420@pytest.mark.parametrize( 

421 'module_qname, class_name', 

422 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

423) 

424def test_900_docstring_sanity( module_qname, class_name ): 

425 ''' Class has valid docstring. ''' 

426 module = cache_import_module( module_qname ) 

427 factory = getattr( module, class_name ) 

428 assert hasattr( factory, '__doc__' ) 

429 assert isinstance( factory.__doc__, str ) 

430 assert factory.__doc__ 

431 

432 

433# TODO: Dictionary description. 

434 

435 

436@pytest.mark.parametrize( 

437 'module_qname, class_name', 

438 product( THESE_MODULE_QNAMES, THESE_CLASSES_NAMES ) 

439) 

440def test_902_docstring_mentions_accretion( module_qname, class_name ): 

441 ''' Class docstring mentions accretion. ''' 

442 module = cache_import_module( module_qname ) 

443 factory = getattr( module, class_name ) 

444 fragment = base.generate_docstring( 'instance attributes accretion' ) 

445 assert fragment in factory.__doc__