]> arthur.barton.de Git - bup.git/blob - wvtest.py
wvtest.py: add a fallback definition of relpath() for Python < 2.6.
[bup.git] / wvtest.py
1 #!/usr/bin/env python
2 #
3 # WvTest:
4 #   Copyright (C)2007-2012 Versabanq Innovations Inc. and contributors.
5 #       Licensed under the GNU Library General Public License, version 2.
6 #       See the included file named LICENSE for license information.
7 #       You can get wvtest from: http://github.com/apenwarr/wvtest
8 #
9 import atexit
10 import inspect
11 import os
12 import re
13 import sys
14 import traceback
15
16 # NOTE
17 # Why do we do we need the "!= main" check?  Because if you run
18 # wvtest.py as a main program and it imports your test files, then
19 # those test files will try to import the wvtest module recursively.
20 # That actually *works* fine, because we don't run this main program
21 # when we're imported as a module.  But you end up with two separate
22 # wvtest modules, the one that gets imported, and the one that's the
23 # main program.  Each of them would have duplicated global variables
24 # (most importantly, wvtest._registered), and so screwy things could
25 # happen.  Thus, we make the main program module *totally* different
26 # from the imported module.  Then we import wvtest (the module) into
27 # wvtest (the main program) here and make sure to refer to the right
28 # versions of global variables.
29 #
30 # All this is done just so that wvtest.py can be a single file that's
31 # easy to import into your own applications.
32 if __name__ != '__main__':   # we're imported as a module
33     _registered = []
34     _tests = 0
35     _fails = 0
36
37     def wvtest(func):
38         """ Use this decorator (@wvtest) in front of any function you want to
39             run as part of the unit test suite.  Then run:
40                 python wvtest.py path/to/yourtest.py [other test.py files...]
41             to run all the @wvtest functions in the given file(s).
42         """
43         _registered.append(func)
44         return func
45
46
47     def _result(msg, tb, code):
48         global _tests, _fails
49         _tests += 1
50         if code != 'ok':
51             _fails += 1
52         (filename, line, func, text) = tb
53         filename = os.path.basename(filename)
54         msg = re.sub(r'\s+', ' ', str(msg))
55         sys.stderr.flush()
56         print '! %-70s %s' % ('%s:%-4d %s' % (filename, line, msg),
57                               code)
58         sys.stdout.flush()
59
60
61     def _check(cond, msg = 'unknown', tb = None):
62         if tb == None: tb = traceback.extract_stack()[-3]
63         if cond:
64             _result(msg, tb, 'ok')
65         else:
66             _result(msg, tb, 'FAILED')
67         return cond
68
69
70     def _code():
71         (filename, line, func, text) = traceback.extract_stack()[-3]
72         text = re.sub(r'^\w+\((.*)\)(\s*#.*)?$', r'\1', text);
73         return text
74
75
76     def WVMSG(message):
77         ''' Issues a notification. '''
78         return _result(message, traceback.extract_stack()[-3], 'ok')
79
80     def WVPASS(cond = True):
81         ''' Counts a test failure unless cond is true. '''
82         return _check(cond, _code())
83
84     def WVFAIL(cond = True):
85         ''' Counts a test failure  unless cond is false. '''
86         return _check(not cond, 'NOT(%s)' % _code())
87
88     def WVPASSEQ(a, b):
89         ''' Counts a test failure unless a == b. '''
90         return _check(a == b, '%s == %s' % (repr(a), repr(b)))
91
92     def WVPASSNE(a, b):
93         ''' Counts a test failure unless a != b. '''
94         return _check(a != b, '%s != %s' % (repr(a), repr(b)))
95
96     def WVPASSLT(a, b):
97         ''' Counts a test failure unless a < b. '''
98         return _check(a < b, '%s < %s' % (repr(a), repr(b)))
99
100     def WVPASSLE(a, b):
101         ''' Counts a test failure unless a <= b. '''
102         return _check(a <= b, '%s <= %s' % (repr(a), repr(b)))
103
104     def WVPASSGT(a, b):
105         ''' Counts a test failure unless a > b. '''
106         return _check(a > b, '%s > %s' % (repr(a), repr(b)))
107
108     def WVPASSGE(a, b):
109         ''' Counts a test failure unless a >= b. '''
110         return _check(a >= b, '%s >= %s' % (repr(a), repr(b)))
111
112     def WVEXCEPT(etype, func, *args, **kwargs):
113         ''' Counts a test failure unless func throws an 'etype' exception.
114             You have to spell out the function name and arguments, rather than
115             calling the function yourself, so that WVEXCEPT can run before
116             your test code throws an exception.
117         '''
118         try:
119             func(*args, **kwargs)
120         except etype, e:
121             return _check(True, 'EXCEPT(%s)' % _code())
122         except:
123             _check(False, 'EXCEPT(%s)' % _code())
124             raise
125         else:
126             return _check(False, 'EXCEPT(%s)' % _code())
127
128
129     def _check_unfinished():
130         if _registered:
131             for func in _registered:
132                 print 'WARNING: not run: %r' % (func,)
133             WVFAIL('wvtest_main() not called')
134         if _fails:
135             sys.exit(1)
136
137     atexit.register(_check_unfinished)
138
139
140 def _run_in_chdir(path, func, *args, **kwargs):
141     oldwd = os.getcwd()
142     oldpath = sys.path
143     try:
144         os.chdir(path)
145         sys.path += [path, os.path.split(path)[0]]
146         return func(*args, **kwargs)
147     finally:
148         os.chdir(oldwd)
149         sys.path = oldpath
150
151
152 if sys.version_info >= (2,6,0):
153     _relpath = os.path.relpath;
154 else:
155     # Implementation for Python 2.5, taken from CPython (tag v2.6,
156     # file Lib/posixpath.py, hg-commit 95fff5a6a276).  Update
157     # ./LICENSE When this code is eventually removed.
158     def _relpath(path, start=os.path.curdir):
159         if not path:
160             raise ValueError("no path specified")
161
162         start_list = os.path.abspath(start).split(os.path.sep)
163         path_list = os.path.abspath(path).split(os.path.sep)
164
165         # Work out how much of the filepath is shared by start and path.
166         i = len(os.path.commonprefix([start_list, path_list]))
167
168         rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
169         if not rel_list:
170             return curdir
171         return os.path.join(*rel_list)
172
173
174 def _runtest(fname, f):
175     mod = inspect.getmodule(f)
176     relpath = _relpath(mod.__file__, os.getcwd()).replace('.pyc', '.py')
177     print
178     print 'Testing "%s" in %s:' % (fname, relpath)
179     sys.stdout.flush()
180     try:
181         _run_in_chdir(os.path.split(mod.__file__)[0], f)
182     except Exception, e:
183         print
184         print traceback.format_exc()
185         tb = sys.exc_info()[2]
186         wvtest._result(e, traceback.extract_tb(tb)[1], 'EXCEPTION')
187
188
189 def _run_registered_tests():
190     import wvtest as _wvtestmod
191     while _wvtestmod._registered:
192         t = _wvtestmod._registered.pop(0)
193         _runtest(t.func_name, t)
194         print
195
196
197 def wvtest_main(extra_testfiles=[]):
198     import wvtest as _wvtestmod
199     _run_registered_tests()
200     for modname in extra_testfiles:
201         if not os.path.exists(modname):
202             print 'Skipping: %s' % modname
203             continue
204         if modname.endswith('.py'):
205             modname = modname[:-3]
206         print 'Importing: %s' % modname
207         path, mod = os.path.split(os.path.abspath(modname))
208         nicename = modname.replace(os.path.sep, '.')
209         while nicename.startswith('.'):
210             nicename = modname[1:]
211         _run_in_chdir(path, __import__, nicename, None, None, [])
212         _run_registered_tests()
213     print
214     print 'WvTest: %d tests, %d failures.' % (_wvtestmod._tests,
215                                               _wvtestmod._fails)
216
217
218 if __name__ == '__main__':
219     import wvtest as _wvtestmod
220     sys.modules['wvtest'] = _wvtestmod
221     sys.modules['wvtest.wvtest'] = _wvtestmod
222     wvtest_main(sys.argv[1:])