1"""Tools not exempt from being descended into in tracebacks"""
2
3import time
4
5
6__all__ = ['make_decorator', 'raises', 'set_trace', 'timed', 'with_setup',
7           'TimeExpired', 'istest', 'nottest']
8
9
10class TimeExpired(AssertionError):
11    pass
12
13
14def make_decorator(func):
15    """
16    Wraps a test decorator so as to properly replicate metadata
17    of the decorated function, including nose's additional stuff
18    (namely, setup and teardown).
19    """
20    def decorate(newfunc):
21        if hasattr(func, 'compat_func_name'):
22            name = func.compat_func_name
23        else:
24            name = func.__name__
25        newfunc.__dict__ = func.__dict__
26        newfunc.__doc__ = func.__doc__
27        newfunc.__module__ = func.__module__
28        if not hasattr(newfunc, 'compat_co_firstlineno'):
29            newfunc.compat_co_firstlineno = func.__code__.co_firstlineno
30        try:
31            newfunc.__name__ = name
32        except TypeError:
33            # can't set func name in 2.3
34            newfunc.compat_func_name = name
35        return newfunc
36    return decorate
37
38
39def raises(*exceptions):
40    """Test must raise one of expected exceptions to pass.
41
42    Example use::
43
44      @raises(TypeError, ValueError)
45      def test_raises_type_error():
46          raise TypeError("This test passes")
47
48      @raises(Exception)
49      def test_that_fails_by_passing():
50          pass
51
52    If you want to test many assertions about exceptions in a single test,
53    you may want to use `assert_raises` instead.
54    """
55    valid = ' or '.join([e.__name__ for e in exceptions])
56    def decorate(func):
57        name = func.__name__
58        def newfunc(*arg, **kw):
59            try:
60                func(*arg, **kw)
61            except exceptions:
62                pass
63            except:
64                raise
65            else:
66                message = "%s() did not raise %s" % (name, valid)
67                raise AssertionError(message)
68        newfunc = make_decorator(func)(newfunc)
69        return newfunc
70    return decorate
71
72
73def set_trace():
74    """Call pdb.set_trace in the calling frame, first restoring
75    sys.stdout to the real output stream. Note that sys.stdout is NOT
76    reset to whatever it was before the call once pdb is done!
77    """
78    import pdb
79    import sys
80    stdout = sys.stdout
81    sys.stdout = sys.__stdout__
82    pdb.Pdb().set_trace(sys._getframe().f_back)
83
84
85def timed(limit):
86    """Test must finish within specified time limit to pass.
87
88    Example use::
89
90      @timed(.1)
91      def test_that_fails():
92          time.sleep(.2)
93    """
94    def decorate(func):
95        def newfunc(*arg, **kw):
96            start = time.time()
97            result = func(*arg, **kw)
98            end = time.time()
99            if end - start > limit:
100                raise TimeExpired("Time limit (%s) exceeded" % limit)
101            return result
102        newfunc = make_decorator(func)(newfunc)
103        return newfunc
104    return decorate
105
106
107def with_setup(setup=None, teardown=None):
108    """Decorator to add setup and/or teardown methods to a test function::
109
110      @with_setup(setup, teardown)
111      def test_something():
112          " ... "
113
114    Note that `with_setup` is useful *only* for test functions, not for test
115    methods or inside of TestCase subclasses.
116    """
117    def decorate(func, setup=setup, teardown=teardown):
118        if setup:
119            if hasattr(func, 'setup'):
120                _old_s = func.setup
121                def _s():
122                    setup()
123                    _old_s()
124                func.setup = _s
125            else:
126                func.setup = setup
127        if teardown:
128            if hasattr(func, 'teardown'):
129                _old_t = func.teardown
130                def _t():
131                    _old_t()
132                    teardown()
133                func.teardown = _t
134            else:
135                func.teardown = teardown
136        return func
137    return decorate
138
139
140def istest(func):
141    """Decorator to mark a function or method as a test
142    """
143    func.__test__ = True
144    return func
145
146
147def nottest(func):
148    """Decorator to mark a function or method as *not* a test
149    """
150    func.__test__ = False
151    return func
152