12
12
import shutil
13
13
import cStringIO
14
14
15
+ import warnings
16
+ from nose import SkipTest
17
+
15
18
from base import (
16
19
maketemp ,
17
20
rorepo_dir
18
21
)
19
22
20
23
21
24
__all__ = (
22
- 'StringProcessAdapter' , 'GlobalsItemDeletorMetaCls' ,
23
- 'with_rw_repo' , 'with_rw_and_rw_remote_repo' , 'TestBase' , 'TestCase' ,
25
+ 'StringProcessAdapter' , 'GlobalsItemDeletorMetaCls' , 'InheritedTestMethodsOverrideWrapperInstanceDecorator' ,
26
+ 'InheritedTestMethodsOverrideWrapperMetaClsAutoMixin' ,
27
+ 'with_rw_repo' , 'with_rw_and_rw_remote_repo' , 'TestBase' , 'TestCase' , 'needs_module_or_skip'
24
28
)
25
29
26
30
@@ -191,6 +195,27 @@ def remote_repo_creator(self):
191
195
192
196
return argument_passer
193
197
198
+ def needs_module_or_skip (module ):
199
+ """Decorator to be used for test cases only.
200
+ Print a warning if the given module could not be imported, and skip the test.
201
+ Otherwise run the test as usual
202
+ :param module: the name of the module to skip"""
203
+ def argpasser (func ):
204
+ def wrapper (self , * args , ** kwargs ):
205
+ try :
206
+ __import__ (module )
207
+ except ImportError :
208
+ msg = "Module %r is required to run this test - skipping" % module
209
+ warnings .warn (msg )
210
+ raise SkipTest (msg )
211
+ #END check import
212
+ return func (self , * args , ** kwargs )
213
+ #END wrapper
214
+ wrapper .__name__ = func .__name__
215
+ return wrapper
216
+ #END argpasser
217
+ return argpasser
218
+
194
219
#} END decorators
195
220
196
221
#{ Meta Classes
@@ -214,6 +239,71 @@ def __new__(metacls, name, bases, clsdict):
214
239
#END skip case that people import our base without actually using it
215
240
#END handle deletion
216
241
return new_type
242
+
243
+
244
+ class InheritedTestMethodsOverrideWrapperInstanceDecorator (object ):
245
+ """Utility to wrap all inherited methods into a given decorator and set up new
246
+ overridden methods on our actual type. This allows to adjust tests which are inherited
247
+ by our parent type, automatically. The decorator set in a derived type should
248
+ do what it has to do, possibly skipping the test if some prerequesites are not met.
249
+
250
+ To use it, instatiate it and use it as a wrapper for the __new__ function of your metacls, as in
251
+
252
+ __new__ = @InheritedTestMethodsOverrideWrapperInstanceDecorator(mydecorator)(MyMetaclsBase.__new__)"""
253
+
254
+
255
+ def __init__ (self , decorator ):
256
+ self .decorator = decorator
257
+
258
+ def _patch_methods_recursive (self , bases , clsdict ):
259
+ """depth-first patching of methods"""
260
+ for base in bases :
261
+ self ._patch_methods_recursive (base .__bases__ , clsdict )
262
+ for name , item in base .__dict__ .iteritems ():
263
+ if not name .startswith ('test_' ):
264
+ continue
265
+ #END skip non-tests
266
+ clsdict [name ] = self .decorator (item )
267
+ #END for each item
268
+ #END for each base
269
+
270
+ def __call__ (self , func ):
271
+ def wrapper (metacls , name , bases , clsdict ):
272
+ self ._patch_methods_recursive (bases , clsdict )
273
+ return func (metacls , name , bases , clsdict )
274
+ #END wrapper
275
+ assert func .__name__ == '__new__' , "Can only wrap __new__ function of metaclasses"
276
+ wrapper .__name__ = func .__name__
277
+ return wrapper
278
+
279
+
280
+
281
+ class InheritedTestMethodsOverrideWrapperMetaClsAutoMixin (object ):
282
+ """Automatically picks up the actual metaclass of the the type to be created,
283
+ that is the one inherited by one of the bases, and patch up its __new__ to use
284
+ the InheritedTestMethodsOverrideWrapperInstanceDecorator with our configured decorator"""
285
+
286
+ #{ Configuration
287
+ # decorator function to use when wrapping the inherited methods. Put it into a list as first member
288
+ # to hide it from being created as class method
289
+ decorator = []
290
+ #}END configuration
291
+
292
+ @classmethod
293
+ def _find_metacls (metacls , bases ):
294
+ """emulate pythons lookup"""
295
+ mcls_attr = '__metaclass__'
296
+ for base in bases :
297
+ if hasattr (base , mcls_attr ):
298
+ return getattr (base , mcls_attr )
299
+ return metacls ._find_metacls (base .__bases__ )
300
+ #END for each base
301
+ raise AssertionError ("base class had not metaclass attached" )
302
+
303
+ def __new__ (metacls , name , bases , clsdict ):
304
+ assert metacls .decorator , "'decorator' member needs to be set in subclass"
305
+ base_metacls = metacls ._find_metacls (bases )
306
+ return InheritedTestMethodsOverrideWrapperInstanceDecorator (metacls .decorator [0 ])(base_metacls .__new__ )(base_metacls , name , bases , clsdict )
217
307
218
308
#} END meta classes
219
309
0 commit comments