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 04:11 +0000
« 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 -*-
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#============================================================================#
21''' Assert correct function of dictionaries. '''
23# mypy: ignore-errors
24# pylint: disable=attribute-defined-outside-init
25# pylint: disable=invalid-name,magic-value-comparison,protected-access
28import pytest
30from itertools import product
31from types import MappingProxyType as DictionaryProxy
33from . import (
34 MODULES_QNAMES,
35 PACKAGE_NAME,
36 cache_import_module,
37)
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 )
50base = cache_import_module( f"{PACKAGE_NAME}.__" )
51exceptions = cache_import_module( f"{PACKAGE_NAME}.exceptions" )
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 ( ), { }
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
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( ) )
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 )
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'
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
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
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 ]
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 ]
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
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 ]
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
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
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( ) ) )
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 )
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
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 )
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 )
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 )
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__
433# TODO: Dictionary description.
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__