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