]> arthur.barton.de Git - bup.git/blob - wvtest.py
_helpers: fix module init, and get it at least building with py3
[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     wvstart = WVSTART
148     wvmsg = WVMSG
149     wvpass = WVPASS
150     wvfail = WVFAIL
151     wvpasseq = WVPASSEQ
152     wvpassne = WVPASSNE
153     wvpaslt = WVPASSLT
154     wvpassle = WVPASSLE
155     wvpassgt = WVPASSGT
156     wvpassge = WVPASSGE
157     wvexcept = WVEXCEPT
158
159     def wvfailure_count():
160         return _fails
161
162     def _check_unfinished():
163         if _registered:
164             for func in _registered:
165                 print 'WARNING: not run: %r' % (func,)
166             WVFAIL('wvtest_main() not called')
167         if _fails:
168             sys.exit(1)
169
170     atexit.register(_check_unfinished)
171
172
173 def _run_in_chdir(path, func, *args, **kwargs):
174     oldwd = os.getcwd()
175     oldpath = sys.path
176     try:
177         os.chdir(path)
178         sys.path += [path, os.path.split(path)[0]]
179         return func(*args, **kwargs)
180     finally:
181         os.chdir(oldwd)
182         sys.path = oldpath
183
184
185 if sys.version_info >= (2,6,0):
186     _relpath = os.path.relpath;
187 else:
188     # Implementation for Python 2.5, taken from CPython (tag v2.6,
189     # file Lib/posixpath.py, hg-commit 95fff5a6a276).  Update
190     # ./LICENSE When this code is eventually removed.
191     def _relpath(path, start=os.path.curdir):
192         if not path:
193             raise ValueError("no path specified")
194
195         start_list = os.path.abspath(start).split(os.path.sep)
196         path_list = os.path.abspath(path).split(os.path.sep)
197
198         # Work out how much of the filepath is shared by start and path.
199         i = len(os.path.commonprefix([start_list, path_list]))
200
201         rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
202         if not rel_list:
203             return curdir
204         return os.path.join(*rel_list)
205
206
207 def _runtest(fname, f):
208     mod = inspect.getmodule(f)
209     relpath = _relpath(mod.__file__, os.getcwd()).replace('.pyc', '.py')
210     print
211     print 'Testing "%s" in %s:' % (fname, relpath)
212     sys.stdout.flush()
213     try:
214         _run_in_chdir(os.path.split(mod.__file__)[0], f)
215     except Exception as e:
216         print
217         print traceback.format_exc()
218         tb = sys.exc_info()[2]
219         wvtest._result(e, traceback.extract_tb(tb)[1], 'EXCEPTION')
220
221
222 def _run_registered_tests():
223     import wvtest as _wvtestmod
224     while _wvtestmod._registered:
225         t = _wvtestmod._registered.pop(0)
226         _runtest(t.func_name, t)
227         print
228
229
230 def wvtest_main(extra_testfiles=tuple()):
231     import wvtest as _wvtestmod
232     _run_registered_tests()
233     for modname in extra_testfiles:
234         if not os.path.exists(modname):
235             print 'Skipping: %s' % modname
236             continue
237         if modname.endswith('.py'):
238             modname = modname[:-3]
239         print 'Importing: %s' % modname
240         path, mod = os.path.split(os.path.abspath(modname))
241         nicename = modname.replace(os.path.sep, '.')
242         while nicename.startswith('.'):
243             nicename = modname[1:]
244         _run_in_chdir(path, __import__, nicename, None, None, [])
245         _run_registered_tests()
246     print
247     print 'WvTest: %d tests, %d failures.' % (_wvtestmod._tests,
248                                               _wvtestmod._fails)
249
250
251 if __name__ == '__main__':
252     import wvtest as _wvtestmod
253     sys.modules['wvtest'] = _wvtestmod
254     sys.modules['wvtest.wvtest'] = _wvtestmod
255     wvtest = _wvtestmod
256     wvtest_main(sys.argv[1:])