]> arthur.barton.de Git - bup.git/blob - wvtest.py
wvtest: chdir to start dir before extracting stack
[bup.git] / wvtest.py
1 #!/bin/sh
2 """": # -*-python-*-
3 bup_python="$(dirname "$0")/cmd/bup-python"
4 exec "$bup_python" "$0" ${1+"$@"}
5 """
6 # end of bup preamble
7 #
8 # WvTest:
9 #   Copyright (C)2007-2012 Versabanq Innovations Inc. and contributors.
10 #       Licensed under the GNU Library General Public License, version 2.
11 #       See the included file named LICENSE for license information.
12 #       You can get wvtest from: http://github.com/apenwarr/wvtest
13 #
14 import atexit
15 import inspect
16 import os
17 import re
18 import sys
19 import traceback
20
21 _start_dir = os.getcwd()
22
23 # NOTE
24 # Why do we do we need the "!= main" check?  Because if you run
25 # wvtest.py as a main program and it imports your test files, then
26 # those test files will try to import the wvtest module recursively.
27 # That actually *works* fine, because we don't run this main program
28 # when we're imported as a module.  But you end up with two separate
29 # wvtest modules, the one that gets imported, and the one that's the
30 # main program.  Each of them would have duplicated global variables
31 # (most importantly, wvtest._registered), and so screwy things could
32 # happen.  Thus, we make the main program module *totally* different
33 # from the imported module.  Then we import wvtest (the module) into
34 # wvtest (the main program) here and make sure to refer to the right
35 # versions of global variables.
36 #
37 # All this is done just so that wvtest.py can be a single file that's
38 # easy to import into your own applications.
39 if __name__ != '__main__':   # we're imported as a module
40     _registered = []
41     _tests = 0
42     _fails = 0
43
44     def wvtest(func):
45         """ Use this decorator (@wvtest) in front of any function you want to
46             run as part of the unit test suite.  Then run:
47                 python wvtest.py path/to/yourtest.py [other test.py files...]
48             to run all the @wvtest functions in the given file(s).
49         """
50         _registered.append(func)
51         return func
52
53
54     def _result(msg, tb, code):
55         global _tests, _fails
56         _tests += 1
57         if code != 'ok':
58             _fails += 1
59         (filename, line, func, text) = tb
60         filename = os.path.basename(filename)
61         msg = re.sub(r'\s+', ' ', str(msg))
62         sys.stderr.flush()
63         print '! %-70s %s' % ('%s:%-4d %s' % (filename, line, msg),
64                               code)
65         sys.stdout.flush()
66
67
68     def _caller_stack():
69         # Without the chdir, the source text lookup may fail
70         orig = os.getcwd()
71         os.chdir(_start_dir)
72         try:
73             return traceback.extract_stack()[-4]
74         finally:
75             os.chdir(orig)
76
77
78     def _check(cond, msg = 'unknown', tb = None):
79         if tb == None: tb = _caller_stack()
80         if cond:
81             _result(msg, tb, 'ok')
82         else:
83             _result(msg, tb, 'FAILED')
84         return cond
85
86     _code_rx = re.compile(r'^\w+\((.*)\)(\s*#.*)?$')
87     def _code():
88         text = _caller_stack()[3]
89         return _code_rx.sub(r'\1', text)
90
91     def WVMSG(message):
92         ''' Issues a notification. '''
93         return _result(message, _caller_stack(), 'ok')
94
95     def WVPASS(cond = True):
96         ''' Counts a test failure unless cond is true. '''
97         return _check(cond, _code())
98
99     def WVFAIL(cond = True):
100         ''' Counts a test failure  unless cond is false. '''
101         return _check(not cond, 'NOT(%s)' % _code())
102
103     def WVPASSEQ(a, b):
104         ''' Counts a test failure unless a == b. '''
105         return _check(a == b, '%s == %s' % (repr(a), repr(b)))
106
107     def WVPASSNE(a, b):
108         ''' Counts a test failure unless a != b. '''
109         return _check(a != b, '%s != %s' % (repr(a), repr(b)))
110
111     def WVPASSLT(a, b):
112         ''' Counts a test failure unless a < b. '''
113         return _check(a < b, '%s < %s' % (repr(a), repr(b)))
114
115     def WVPASSLE(a, b):
116         ''' Counts a test failure unless a <= b. '''
117         return _check(a <= b, '%s <= %s' % (repr(a), repr(b)))
118
119     def WVPASSGT(a, b):
120         ''' Counts a test failure unless a > b. '''
121         return _check(a > b, '%s > %s' % (repr(a), repr(b)))
122
123     def WVPASSGE(a, b):
124         ''' Counts a test failure unless a >= b. '''
125         return _check(a >= b, '%s >= %s' % (repr(a), repr(b)))
126
127     def WVEXCEPT(etype, func, *args, **kwargs):
128         ''' Counts a test failure unless func throws an 'etype' exception.
129             You have to spell out the function name and arguments, rather than
130             calling the function yourself, so that WVEXCEPT can run before
131             your test code throws an exception.
132         '''
133         try:
134             func(*args, **kwargs)
135         except etype as e:
136             return _check(True, 'EXCEPT(%s)' % _code())
137         except:
138             _check(False, 'EXCEPT(%s)' % _code())
139             raise
140         else:
141             return _check(False, 'EXCEPT(%s)' % _code())
142
143     def wvfailure_count():
144         return _fails
145
146     def _check_unfinished():
147         if _registered:
148             for func in _registered:
149                 print 'WARNING: not run: %r' % (func,)
150             WVFAIL('wvtest_main() not called')
151         if _fails:
152             sys.exit(1)
153
154     atexit.register(_check_unfinished)
155
156
157 def _run_in_chdir(path, func, *args, **kwargs):
158     oldwd = os.getcwd()
159     oldpath = sys.path
160     try:
161         os.chdir(path)
162         sys.path += [path, os.path.split(path)[0]]
163         return func(*args, **kwargs)
164     finally:
165         os.chdir(oldwd)
166         sys.path = oldpath
167
168
169 if sys.version_info >= (2,6,0):
170     _relpath = os.path.relpath;
171 else:
172     # Implementation for Python 2.5, taken from CPython (tag v2.6,
173     # file Lib/posixpath.py, hg-commit 95fff5a6a276).  Update
174     # ./LICENSE When this code is eventually removed.
175     def _relpath(path, start=os.path.curdir):
176         if not path:
177             raise ValueError("no path specified")
178
179         start_list = os.path.abspath(start).split(os.path.sep)
180         path_list = os.path.abspath(path).split(os.path.sep)
181
182         # Work out how much of the filepath is shared by start and path.
183         i = len(os.path.commonprefix([start_list, path_list]))
184
185         rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
186         if not rel_list:
187             return curdir
188         return os.path.join(*rel_list)
189
190
191 def _runtest(fname, f):
192     mod = inspect.getmodule(f)
193     relpath = _relpath(mod.__file__, os.getcwd()).replace('.pyc', '.py')
194     print
195     print 'Testing "%s" in %s:' % (fname, relpath)
196     sys.stdout.flush()
197     try:
198         _run_in_chdir(os.path.split(mod.__file__)[0], f)
199     except Exception as e:
200         print
201         print traceback.format_exc()
202         tb = sys.exc_info()[2]
203         wvtest._result(e, traceback.extract_tb(tb)[1], 'EXCEPTION')
204
205
206 def _run_registered_tests():
207     import wvtest as _wvtestmod
208     while _wvtestmod._registered:
209         t = _wvtestmod._registered.pop(0)
210         _runtest(t.func_name, t)
211         print
212
213
214 def wvtest_main(extra_testfiles=tuple()):
215     import wvtest as _wvtestmod
216     _run_registered_tests()
217     for modname in extra_testfiles:
218         if not os.path.exists(modname):
219             print 'Skipping: %s' % modname
220             continue
221         if modname.endswith('.py'):
222             modname = modname[:-3]
223         print 'Importing: %s' % modname
224         path, mod = os.path.split(os.path.abspath(modname))
225         nicename = modname.replace(os.path.sep, '.')
226         while nicename.startswith('.'):
227             nicename = modname[1:]
228         _run_in_chdir(path, __import__, nicename, None, None, [])
229         _run_registered_tests()
230     print
231     print 'WvTest: %d tests, %d failures.' % (_wvtestmod._tests,
232                                               _wvtestmod._fails)
233
234
235 if __name__ == '__main__':
236     import wvtest as _wvtestmod
237     sys.modules['wvtest'] = _wvtestmod
238     sys.modules['wvtest.wvtest'] = _wvtestmod
239     wvtest = _wvtestmod
240     wvtest_main(sys.argv[1:])