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