]> arthur.barton.de Git - bup.git/blob - wvtest.py
wvtest.py: add WVSTART for standalone python tests
[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(wv_call_depth):
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()[-(wv_call_depth + 2)]
74         finally:
75             os.chdir(orig)
76
77
78     def _check(cond, msg = 'unknown', tb = None):
79         if tb == None: tb = _caller_stack(2)
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(2)[3]
89         return _code_rx.sub(r'\1', text)
90
91     def WVSTART(message):
92         filename = _caller_stack(1)[0]
93         sys.stderr.write('Testing \"' + message + '\" in ' + filename + ':\n')
94
95     def WVMSG(message):
96         ''' Issues a notification. '''
97         return _result(message, _caller_stack(1), 'ok')
98
99     def WVPASS(cond = True):
100         ''' Counts a test failure unless cond is true. '''
101         return _check(cond, _code())
102
103     def WVFAIL(cond = True):
104         ''' Counts a test failure  unless cond is false. '''
105         return _check(not cond, 'NOT(%s)' % _code())
106
107     def WVPASSEQ(a, b):
108         ''' Counts a test failure unless a == b. '''
109         return _check(a == b, '%s == %s' % (repr(a), repr(b)))
110
111     def WVPASSNE(a, b):
112         ''' Counts a test failure unless a != b. '''
113         return _check(a != b, '%s != %s' % (repr(a), repr(b)))
114
115     def WVPASSLT(a, b):
116         ''' Counts a test failure unless a < b. '''
117         return _check(a < b, '%s < %s' % (repr(a), repr(b)))
118
119     def WVPASSLE(a, b):
120         ''' Counts a test failure unless a <= b. '''
121         return _check(a <= b, '%s <= %s' % (repr(a), repr(b)))
122
123     def WVPASSGT(a, b):
124         ''' Counts a test failure unless a > b. '''
125         return _check(a > b, '%s > %s' % (repr(a), repr(b)))
126
127     def WVPASSGE(a, b):
128         ''' Counts a test failure unless a >= b. '''
129         return _check(a >= b, '%s >= %s' % (repr(a), repr(b)))
130
131     def WVEXCEPT(etype, func, *args, **kwargs):
132         ''' Counts a test failure unless func throws an 'etype' exception.
133             You have to spell out the function name and arguments, rather than
134             calling the function yourself, so that WVEXCEPT can run before
135             your test code throws an exception.
136         '''
137         try:
138             func(*args, **kwargs)
139         except etype as e:
140             return _check(True, 'EXCEPT(%s)' % _code())
141         except:
142             _check(False, 'EXCEPT(%s)' % _code())
143             raise
144         else:
145             return _check(False, 'EXCEPT(%s)' % _code())
146
147     def wvfailure_count():
148         return _fails
149
150     def _check_unfinished():
151         if _registered:
152             for func in _registered:
153                 print 'WARNING: not run: %r' % (func,)
154             WVFAIL('wvtest_main() not called')
155         if _fails:
156             sys.exit(1)
157
158     atexit.register(_check_unfinished)
159
160
161 def _run_in_chdir(path, func, *args, **kwargs):
162     oldwd = os.getcwd()
163     oldpath = sys.path
164     try:
165         os.chdir(path)
166         sys.path += [path, os.path.split(path)[0]]
167         return func(*args, **kwargs)
168     finally:
169         os.chdir(oldwd)
170         sys.path = oldpath
171
172
173 if sys.version_info >= (2,6,0):
174     _relpath = os.path.relpath;
175 else:
176     # Implementation for Python 2.5, taken from CPython (tag v2.6,
177     # file Lib/posixpath.py, hg-commit 95fff5a6a276).  Update
178     # ./LICENSE When this code is eventually removed.
179     def _relpath(path, start=os.path.curdir):
180         if not path:
181             raise ValueError("no path specified")
182
183         start_list = os.path.abspath(start).split(os.path.sep)
184         path_list = os.path.abspath(path).split(os.path.sep)
185
186         # Work out how much of the filepath is shared by start and path.
187         i = len(os.path.commonprefix([start_list, path_list]))
188
189         rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
190         if not rel_list:
191             return curdir
192         return os.path.join(*rel_list)
193
194
195 def _runtest(fname, f):
196     mod = inspect.getmodule(f)
197     relpath = _relpath(mod.__file__, os.getcwd()).replace('.pyc', '.py')
198     print
199     print 'Testing "%s" in %s:' % (fname, relpath)
200     sys.stdout.flush()
201     try:
202         _run_in_chdir(os.path.split(mod.__file__)[0], f)
203     except Exception as e:
204         print
205         print traceback.format_exc()
206         tb = sys.exc_info()[2]
207         wvtest._result(e, traceback.extract_tb(tb)[1], 'EXCEPTION')
208
209
210 def _run_registered_tests():
211     import wvtest as _wvtestmod
212     while _wvtestmod._registered:
213         t = _wvtestmod._registered.pop(0)
214         _runtest(t.func_name, t)
215         print
216
217
218 def wvtest_main(extra_testfiles=tuple()):
219     import wvtest as _wvtestmod
220     _run_registered_tests()
221     for modname in extra_testfiles:
222         if not os.path.exists(modname):
223             print 'Skipping: %s' % modname
224             continue
225         if modname.endswith('.py'):
226             modname = modname[:-3]
227         print 'Importing: %s' % modname
228         path, mod = os.path.split(os.path.abspath(modname))
229         nicename = modname.replace(os.path.sep, '.')
230         while nicename.startswith('.'):
231             nicename = modname[1:]
232         _run_in_chdir(path, __import__, nicename, None, None, [])
233         _run_registered_tests()
234     print
235     print 'WvTest: %d tests, %d failures.' % (_wvtestmod._tests,
236                                               _wvtestmod._fails)
237
238
239 if __name__ == '__main__':
240     import wvtest as _wvtestmod
241     sys.modules['wvtest'] = _wvtestmod
242     sys.modules['wvtest.wvtest'] = _wvtestmod
243     wvtest = _wvtestmod
244     wvtest_main(sys.argv[1:])