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:52 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-10 04:52 +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 class factory classes. '''
23# mypy: ignore-errors
24# pylint: disable=magic-value-comparison,protected-access
27import pytest
29from itertools import product
30from platform import python_implementation
32from . import (
33 MODULES_QNAMES,
34 PACKAGE_NAME,
35 cache_import_module,
36)
39THESE_MODULE_QNAMES = tuple(
40 name for name in MODULES_QNAMES if name.endswith( '.classes' ) )
41THESE_CLASSES_NAMES = ( 'Class', 'ABCFactory', 'ProtocolClass' )
43base = cache_import_module( f"{PACKAGE_NAME}.__" )
44exceptions = cache_import_module( f"{PACKAGE_NAME}.exceptions" )
46pypy_skip_mark = pytest.mark.skipif(
47 'PyPy' == python_implementation( ),
48 reason = "PyPy handles class cell updates differently"
49)
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 )
61 class Object( metaclass = class_factory_class ):
62 ''' test '''
64 assert isinstance( Object, class_factory_class )
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 )
76 class Object( metaclass = class_factory_class ):
77 ''' test '''
78 attr = 42
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
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 = [ ]
106 def test_decorator1( cls ):
107 decorator_calls.append( 'decorator1' )
108 cls.decorator1_attr = 'value1'
109 return cls
111 def test_decorator2( cls ):
112 decorator_calls.append( 'decorator2' )
113 cls.decorator2_attr = 'value2'
114 return cls
116 class Object(
117 metaclass = class_factory_class,
118 decorators = ( test_decorator1, test_decorator2 )
119 ):
120 ''' test '''
121 attr = 42
123 _class_behaviors_ = { 'foo' }
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'
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
145 class Object(
146 metaclass = class_factory_class,
147 decorators = ( dataclass( slots = True ), )
148 ):
149 ''' test '''
150 value: str = 'test'
152 def method_with_super( self ):
153 ''' References class cell on CPython. '''
154 super( ).__init__( )
155 return self.__class__.__name__
157 def other_method_with_super( self ):
158 ''' References class cell on CPython. '''
159 super( ).__init__( )
160 return 'other'
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( )
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
179 class Object(
180 metaclass = class_factory_class,
181 decorators = ( dataclass( slots = True ), )
182 ):
183 ''' test '''
184 value: str = 'test'
186 @property
187 def prop_with_class( self ):
188 ''' References class cell on CPython. '''
189 return self.__class__.__name__
191 @property
192 def other_prop_with_class( self ):
193 ''' References class cell on CPython. '''
194 return f"other_{self.__class__.__name__}"
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
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
213 class Object(
214 metaclass = class_factory_class,
215 decorators = ( dataclass( slots = True ), )
216 ):
217 ''' test '''
218 value: str = 'test'
220 def method_without_cell( self ): # pylint: disable=no-self-use
221 ''' Operates without class cell on CPython. '''
222 return 'no_cell'
224 # Verify class was properly reproduced
225 obj = Object()
226 assert 'no_cell' == obj.method_without_cell( )
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 ) )
239 def failing_decorator( cls ):
240 raise ValueError( "Decorator failure" ) # noqa
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 '''
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 )
259 class Object( metaclass = class_factory_class, docstring = 'dynamic' ):
260 ''' test '''
261 attr = 42
263 assert 'test' != Object.__doc__
264 assert 'dynamic' == Object.__doc__
267# TODO? String representations.
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__
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__
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__