From 690828ce2e03ce32c5a66186c543d7c5050287e4 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 6 Jul 2011 22:11:48 +0200 Subject: Added basis for initial dulwich integration. Many basic issues should surface while integrating this --- git/test/lib/helper.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'git/test/lib/helper.py') diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index d9a92a52..ef2d3280 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -207,7 +207,11 @@ class GlobalsItemDeletorMetaCls(type): new_type = super(GlobalsItemDeletorMetaCls, metacls).__new__(metacls, name, bases, clsdict) if name != metacls.ModuleToDelete: mod = __import__(new_type.__module__, globals(), locals(), new_type.__module__) - delattr(mod, metacls.ModuleToDelete) + try: + delattr(mod, metacls.ModuleToDelete) + except AttributeError: + pass + #END skip case that people import our base without actually using it #END handle deletion return new_type -- cgit v1.2.1 From 80aa40537a3596f24593b5a67adb6d635fe4fa22 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2011 12:55:03 +0200 Subject: Added auto-skip mixin metacls, some serious brainfuck, if the required module was not found. Its actually a nice mix between decorators which are class types, and a mixin as a metaclass, which applies said decorator. The InstanceDecorator wouldn't actually be needed, but it adds flexibility. Maybe it should be removed ... --- git/test/lib/helper.py | 94 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 2 deletions(-) (limited to 'git/test/lib/helper.py') diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index ef2d3280..87600489 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -12,6 +12,9 @@ import tempfile import shutil import cStringIO +import warnings +from nose import SkipTest + from base import ( maketemp, rorepo_dir @@ -19,8 +22,9 @@ from base import ( __all__ = ( - 'StringProcessAdapter', 'GlobalsItemDeletorMetaCls', - 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', + 'StringProcessAdapter', 'GlobalsItemDeletorMetaCls', 'InheritedTestMethodsOverrideWrapperInstanceDecorator', + 'InheritedTestMethodsOverrideWrapperMetaClsAutoMixin', + 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', 'needs_module_or_skip' ) @@ -191,6 +195,27 @@ def with_rw_and_rw_remote_repo(working_tree_ref): return argument_passer +def needs_module_or_skip(module): + """Decorator to be used for test cases only. + Print a warning if the given module could not be imported, and skip the test. + Otherwise run the test as usual + :param module: the name of the module to skip""" + def argpasser(func): + def wrapper(self, *args, **kwargs): + try: + __import__(module) + except ImportError: + msg = "Module %r is required to run this test - skipping" % module + warnings.warn(msg) + raise SkipTest(msg) + #END check import + return func(self, *args, **kwargs) + #END wrapper + wrapper.__name__ = func.__name__ + return wrapper + #END argpasser + return argpasser + #} END decorators #{ Meta Classes @@ -214,6 +239,71 @@ class GlobalsItemDeletorMetaCls(type): #END skip case that people import our base without actually using it #END handle deletion return new_type + + +class InheritedTestMethodsOverrideWrapperInstanceDecorator(object): + """Utility to wrap all inherited methods into a given decorator and set up new + overridden methods on our actual type. This allows to adjust tests which are inherited + by our parent type, automatically. The decorator set in a derived type should + do what it has to do, possibly skipping the test if some prerequesites are not met. + + To use it, instatiate it and use it as a wrapper for the __new__ function of your metacls, as in + + __new__ = @InheritedTestMethodsOverrideWrapperInstanceDecorator(mydecorator)(MyMetaclsBase.__new__)""" + + + def __init__(self, decorator): + self.decorator = decorator + + def _patch_methods_recursive(self, bases, clsdict): + """depth-first patching of methods""" + for base in bases: + self._patch_methods_recursive(base.__bases__, clsdict) + for name, item in base.__dict__.iteritems(): + if not name.startswith('test_'): + continue + #END skip non-tests + clsdict[name] = self.decorator(item) + #END for each item + #END for each base + + def __call__(self, func): + def wrapper(metacls, name, bases, clsdict): + self._patch_methods_recursive(bases, clsdict) + return func(metacls, name, bases, clsdict) + #END wrapper + assert func.__name__ == '__new__', "Can only wrap __new__ function of metaclasses" + wrapper.__name__ = func.__name__ + return wrapper + + + +class InheritedTestMethodsOverrideWrapperMetaClsAutoMixin(object): + """Automatically picks up the actual metaclass of the the type to be created, + that is the one inherited by one of the bases, and patch up its __new__ to use + the InheritedTestMethodsOverrideWrapperInstanceDecorator with our configured decorator""" + + #{ Configuration + # decorator function to use when wrapping the inherited methods. Put it into a list as first member + # to hide it from being created as class method + decorator = [] + #}END configuration + + @classmethod + def _find_metacls(metacls, bases): + """emulate pythons lookup""" + mcls_attr = '__metaclass__' + for base in bases: + if hasattr(base, mcls_attr): + return getattr(base, mcls_attr) + return metacls._find_metacls(base.__bases__) + #END for each base + raise AssertionError("base class had not metaclass attached") + + def __new__(metacls, name, bases, clsdict): + assert metacls.decorator, "'decorator' member needs to be set in subclass" + base_metacls = metacls._find_metacls(bases) + return InheritedTestMethodsOverrideWrapperInstanceDecorator(metacls.decorator[0])(base_metacls.__new__)(base_metacls, name, bases, clsdict) #} END meta classes -- cgit v1.2.1 From d5038ebadc190753c67c02c9f5930a14ca2dc1e7 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Thu, 7 Jul 2011 12:57:26 +0200 Subject: removed now superfluous InstanceDecorator, as it was just complicating things after all --- git/test/lib/helper.py | 58 +++++++++++++++----------------------------------- 1 file changed, 17 insertions(+), 41 deletions(-) (limited to 'git/test/lib/helper.py') diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py index 87600489..2045f9d3 100644 --- a/git/test/lib/helper.py +++ b/git/test/lib/helper.py @@ -22,8 +22,7 @@ from base import ( __all__ = ( - 'StringProcessAdapter', 'GlobalsItemDeletorMetaCls', 'InheritedTestMethodsOverrideWrapperInstanceDecorator', - 'InheritedTestMethodsOverrideWrapperMetaClsAutoMixin', + 'StringProcessAdapter', 'GlobalsItemDeletorMetaCls', 'InheritedTestMethodsOverrideWrapperMetaClsAutoMixin', 'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', 'needs_module_or_skip' ) @@ -239,44 +238,7 @@ class GlobalsItemDeletorMetaCls(type): #END skip case that people import our base without actually using it #END handle deletion return new_type - - -class InheritedTestMethodsOverrideWrapperInstanceDecorator(object): - """Utility to wrap all inherited methods into a given decorator and set up new - overridden methods on our actual type. This allows to adjust tests which are inherited - by our parent type, automatically. The decorator set in a derived type should - do what it has to do, possibly skipping the test if some prerequesites are not met. - - To use it, instatiate it and use it as a wrapper for the __new__ function of your metacls, as in - - __new__ = @InheritedTestMethodsOverrideWrapperInstanceDecorator(mydecorator)(MyMetaclsBase.__new__)""" - - def __init__(self, decorator): - self.decorator = decorator - - def _patch_methods_recursive(self, bases, clsdict): - """depth-first patching of methods""" - for base in bases: - self._patch_methods_recursive(base.__bases__, clsdict) - for name, item in base.__dict__.iteritems(): - if not name.startswith('test_'): - continue - #END skip non-tests - clsdict[name] = self.decorator(item) - #END for each item - #END for each base - - def __call__(self, func): - def wrapper(metacls, name, bases, clsdict): - self._patch_methods_recursive(bases, clsdict) - return func(metacls, name, bases, clsdict) - #END wrapper - assert func.__name__ == '__new__', "Can only wrap __new__ function of metaclasses" - wrapper.__name__ = func.__name__ - return wrapper - - class InheritedTestMethodsOverrideWrapperMetaClsAutoMixin(object): """Automatically picks up the actual metaclass of the the type to be created, @@ -299,11 +261,25 @@ class InheritedTestMethodsOverrideWrapperMetaClsAutoMixin(object): return metacls._find_metacls(base.__bases__) #END for each base raise AssertionError("base class had not metaclass attached") - + + @classmethod + def _patch_methods_recursive(metacls, bases, clsdict): + """depth-first patching of methods""" + for base in bases: + metacls._patch_methods_recursive(base.__bases__, clsdict) + for name, item in base.__dict__.iteritems(): + if not name.startswith('test_'): + continue + #END skip non-tests + clsdict[name] = metacls.decorator[0](item) + #END for each item + #END for each base + def __new__(metacls, name, bases, clsdict): assert metacls.decorator, "'decorator' member needs to be set in subclass" base_metacls = metacls._find_metacls(bases) - return InheritedTestMethodsOverrideWrapperInstanceDecorator(metacls.decorator[0])(base_metacls.__new__)(base_metacls, name, bases, clsdict) + metacls._patch_methods_recursive(bases, clsdict) + return base_metacls.__new__(base_metacls, name, bases, clsdict) #} END meta classes -- cgit v1.2.1