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