"""$URL: svn+ssh://sting.mems-exchange.org/repos/trunk/DurusWorks/sancho/utest.py $
$Id: utest.py 31037 2008-09-08 10:24:26Z dbinger $

a lean unit test framework
"""

import sys, traceback, inspect, pdb, os

if sys.version < '3.0':
    from cStringIO import StringIO
else:
    from io import StringIO

class UTest:

    def __init__(self, debug=False):
        """
        Run each method of self whose name does not start with an underscore
        as a test.
        Run self._pre() before each test method.
        Run self._post() after each test method.
        Trap all output to stdout, except when an exception is raised
        in the method being called.
        If an exception is raised in a method call, print any generated
        output and a traceback, and proceed to the next method call.
        If debug is true, don't trap output or exceptions.
        """
        if 'DEBUG' in os.environ:
            debug = True
        def run(method, name):
            """(method:MethodType, name:str)
            Run method, label output using name.
            The method runs with stdout redirected to a StringIO.
            If it runs without raising an exception, no output is generated.
            If an exception is generated, it prints the accumulated output
            and a traceback.
            """
            old_stdout = sys.stdout
            if not debug:
                sys.stdout = StringIO()
            try:
                sys.stdout.write('.-- %s' % name)
                try:
                    method(self)
                except KeyboardInterrupt:
                    raise
                except:
                    data = ''.join(traceback.format_exception(*sys.exc_info()))
                    for line in data.rstrip().split('\n'):
                        sys.stdout.write('|' + line + '\n')
                    if hasattr(sys.stdout, 'getvalue'):
                        sys.__stdout__.write(sys.stdout.getvalue())
                    else:
                        sys.__stdout__.write( "\nsys.stdout=%r\n" % sys.stdout)
                    if debug:
                        pdb.post_mortem(sys.exc_info()[-1])
            finally:
                sys.stdout = old_stdout
        names = sorted([name for name in dir(self.__class__)
                 if not name.startswith('_')])
        printed = False
        for name in names:
            check = getattr(self.__class__, name)
            if hasattr(check, '__call__'):
                if not printed:
                    import sys
                    sys.stdout.write("%s: %s:\n" % (
                        inspect.getsourcefile(check),
                        self.__class__.__name__))
                    printed = True
                run(self.__class__._pre, '%s: _pre' % name)
                run(check, name)
                run(self.__class__._post, '%s: _post' % name)

    def _pre(self):
        """
        Executed before each test method.
        Override as needed.
        """

    def _post(self):
        """
        Executed after each test method.  Override as needed.
        This default removes any attributes whose names do not start with '_'
        """
        for name in list(self.__dict__):
            if not name.startswith('_'):
                delattr(self, name)


def raises(exc, func, *args, **kwargs):
    """(exception, func, *args, **kwargs) -> bool
    Call func and catch the exception.
    If the exception is not caught, raise an AssertionError
    """
    try:
       func(*args, **kwargs)
    except exc:
        return True
    raise AssertionError("Expected %s" % exc)



