1212import shutil
1313import cStringIO
1414
15+ import warnings
16+ from nose import SkipTest
17+
1518from base import (
1619 maketemp ,
1720 rorepo_dir
1821 )
1922
2023
2124__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'
2428 )
2529
2630
@@ -191,6 +195,27 @@ def remote_repo_creator(self):
191195
192196 return argument_passer
193197
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+
194219#} END decorators
195220
196221#{ Meta Classes
@@ -214,6 +239,71 @@ def __new__(metacls, name, bases, clsdict):
214239 #END skip case that people import our base without actually using it
215240 #END handle deletion
216241 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 )
217307
218308#} END meta classes
219309
0 commit comments