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