root / logilab.pylintinstaller / logilab / common / testlib.py

Revision 202:d67e86292521, 49.6 kB (checked in by tziade@…, 9 months ago)

added logilab.pylintinstaller

Line 
1# modified copy of some functions from test/regrtest.py from PyXml
2"""Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE).
3http://www.logilab.fr/ -- mailto:contact@logilab.fr 
4
5Run tests.
6
7This will find all modules whose name match a given prefix in the test
8directory, and run them.  Various command line options provide
9additional facilities.
10
11Command line options:
12
13-v: verbose -- run tests in verbose mode with output to stdout
14-q: quiet -- don't print anything except if a test fails
15-t: testdir -- directory where the tests will be found
16-x: exclude -- add a test to exclude
17-p: profile -- profiled execution
18-c: capture -- capture standard out/err during tests
19-d: dbc     -- enable design-by-contract
20
21If no non-option arguments are present, prefixes used are 'test',
22'regrtest', 'smoketest' and 'unittest'.
23
24"""
25import sys
26import os, os.path as osp
27import re
28import time
29import getopt
30import traceback
31import unittest
32import difflib
33import types
34from warnings import warn
35from compiler.consts import CO_GENERATOR
36try:
37    import readline
38except ImportError:
39    readline = None
40
41# PRINT_ = file('stdout.txt', 'w').write
42
43try:
44    from test import test_support
45except ImportError:
46    # not always available
47    class TestSupport:
48        def unload(self, test):
49            pass
50    test_support = TestSupport()
51
52from logilab.common.deprecation import class_renamed, deprecated_function, \
53     obsolete
54from logilab.common.compat import set, enumerate, any
55from logilab.common.modutils import load_module_from_name
56from logilab.common.debugger import Debugger
57from logilab.common.decorators import cached
58
59__all__ = ['main', 'unittest_main', 'find_tests', 'run_test', 'spawn']
60
61DEFAULT_PREFIXES = ('test', 'regrtest', 'smoketest', 'unittest',
62                    'func', 'validation')
63
64ENABLE_DBC = False
65
66def main(testdir=None, exitafter=True):
67    """Execute a test suite.
68
69    This also parses command-line options and modifies its behaviour
70    accordingly.
71
72    tests -- a list of strings containing test names (optional)
73    testdir -- the directory in which to look for tests (optional)
74
75    Users other than the Python test suite will certainly want to
76    specify testdir; if it's omitted, the directory containing the
77    Python test suite is searched for.
78
79    If the tests argument is omitted, the tests listed on the
80    command-line will be used.  If that's empty, too, then all *.py
81    files beginning with test_ will be used.
82
83    """
84
85    try:
86        opts, args = getopt.getopt(sys.argv[1:], 'hvqx:t:pcd', ['help'])
87    except getopt.error, msg:
88        print msg
89        print __doc__
90        return 2
91    verbose = 0
92    quiet = False
93    profile = False
94    exclude = []
95    capture = 0
96    for o, a in opts:
97        if o == '-v':
98            verbose += 1
99        elif o == '-q':
100            quiet = True
101            verbose = 0
102        elif o == '-x':
103            exclude.append(a)
104        elif o == '-t':
105            testdir = a
106        elif o == '-p':
107            profile = True
108        elif o == '-c':
109            capture += 1
110        elif o == '-d':
111            global ENABLE_DBC
112            ENABLE_DBC = True
113        elif o in ('-h', '--help'):
114            print __doc__
115            sys.exit(0)
116
117    args = [item.rstrip('.py') for item in args]
118    exclude = [item.rstrip('.py') for item in exclude]
119
120    if testdir is not None:
121        os.chdir(testdir)
122    sys.path.insert(0, '')
123    tests = find_tests('.', args or DEFAULT_PREFIXES, excludes=exclude)
124    # Tell tests to be moderately quiet
125    test_support.verbose = verbose
126    if profile:
127        print >> sys.stderr, '** profiled run'
128        from hotshot import Profile
129        prof = Profile('stones.prof')
130        start_time, start_ctime = time.time(), time.clock()
131        good, bad, skipped, all_result = prof.runcall(run_tests, tests, quiet,
132                                                      verbose, None, capture)
133        end_time, end_ctime = time.time(), time.clock()
134        prof.close()
135    else:
136        start_time, start_ctime = time.time(), time.clock()
137        good, bad, skipped, all_result = run_tests(tests, quiet, verbose, None, capture)
138        end_time, end_ctime = time.time(), time.clock()
139    if not quiet:
140        print '*'*80
141        if all_result:
142            print 'Ran %s test cases in %0.2fs (%0.2fs CPU)' % (all_result.testsRun,
143                                                                end_time - start_time,
144                                                                end_ctime - start_ctime), 
145            if all_result.errors:
146                print ', %s errors' % len(all_result.errors),
147            if all_result.failures:
148                print ', %s failed' % len(all_result.failures),
149            if all_result.skipped:
150                print ', %s skipped' % len(all_result.skipped),
151            print
152        if good:
153            if not bad and not skipped and len(good) > 1:
154                print "All",
155            print _count(len(good), "test"), "OK."
156        if bad:
157            print _count(len(bad), "test"), "failed:",
158            print ', '.join(bad)
159        if skipped:
160            print _count(len(skipped), "test"), "skipped:",
161            print ', '.join(['%s (%s)' % (test, msg) for test, msg in skipped])
162    if profile:
163        from hotshot import stats
164        stats = stats.load('stones.prof')
165        stats.sort_stats('time', 'calls')
166        stats.print_stats(30)
167    if exitafter:
168        sys.exit(len(bad) + len(skipped))
169    else:
170        sys.path.pop(0)
171        return len(bad)
172main = obsolete("testlib.main() is obsolete, use the pytest tool instead")(main)
173
174
175def run_tests(tests, quiet, verbose, runner=None, capture=0):
176    """ execute a list of tests
177    return a 3-uple with :
178       _ the list of passed tests
179       _ the list of failed tests
180       _ the list of skipped tests
181    """
182    good = []
183    bad = []
184    skipped = []
185    all_result = None
186    for test in tests:
187        if not quiet:
188            print 
189            print '-'*80
190            print "Executing", test
191        result = run_test(test, verbose, runner, capture)
192        if type(result) is type(''):
193            # an unexpected error occured
194            skipped.append( (test, result))
195        else:
196            if all_result is None:
197                all_result = result
198            else:
199                all_result.testsRun += result.testsRun
200                all_result.failures += result.failures
201                all_result.errors += result.errors
202                all_result.skipped += result.skipped
203            if result.errors or result.failures:
204                bad.append(test)
205                if verbose:
206                    print "test", test, \
207                          "failed -- %s errors, %s failures" % (
208                        len(result.errors), len(result.failures))
209            else:
210                good.append(test)
211           
212    return good, bad, skipped, all_result
213   
214def find_tests(testdir,
215               prefixes=DEFAULT_PREFIXES, suffix=".py",
216               excludes=(),
217               remove_suffix=True):
218    """
219    Return a list of all applicable test modules.
220    """
221    tests = []
222    for name in os.listdir(testdir):
223        if not suffix or name.endswith(suffix):
224            for prefix in prefixes:
225                if name.startswith(prefix):
226                    if remove_suffix and name.endswith(suffix):
227                        name = name[:-len(suffix)]
228                    if name not in excludes:
229                        tests.append(name)
230    tests.sort()
231    return tests
232
233
234def run_test(test, verbose, runner=None, capture=0):
235    """
236    Run a single test.
237
238    test -- the name of the test
239    verbose -- if true, print more messages
240    """
241    test_support.unload(test)
242    try:
243        m = load_module_from_name(test, path=sys.path)
244#        m = __import__(test, globals(), locals(), sys.path)
245        try:
246            suite = m.suite
247            if callable(suite):
248                suite = suite()
249        except AttributeError:
250            loader = unittest.TestLoader()
251            suite = loader.loadTestsFromModule(m)
252        if runner is None:
253            runner = SkipAwareTextTestRunner(capture=capture) # verbosity=0)
254        return runner.run(suite)
255    except KeyboardInterrupt, v:
256        raise KeyboardInterrupt, v, sys.exc_info()[2]
257    except:
258        # raise
259        type, value = sys.exc_info()[:2]
260        msg = "test %s crashed -- %s : %s" % (test, type, value)
261        if verbose:
262            traceback.print_exc()
263        return msg
264
265def _count(n, word):
266    """format word according to n"""
267    if n == 1:
268        return "%d %s" % (n, word)
269    else:
270        return "%d %ss" % (n, word)
271
272
273   
274
275## PostMortem Debug facilities #####
276def start_interactive_mode(result):
277    """starts an interactive shell so that the user can inspect errors
278    """
279    debuggers = result.debuggers
280    descrs = result.error_descrs + result.fail_descrs
281    if len(debuggers) == 1:
282        # don't ask for test name if there's only one failure
283        debuggers[0].start()
284    else:
285        while True:
286            testindex = 0
287            print "Choose a test to debug:"
288            # order debuggers in the same way than errors were printed
289            print "\n".join(['\t%s : %s' % (i, descr) for i, (_, descr) in enumerate(descrs)])
290            print "Type 'exit' (or ^D) to quit"
291            print
292            try:
293                todebug = raw_input('Enter a test name: ')
294                if todebug.strip().lower() == 'exit':
295                    print
296                    break
297                else:
298                    try:
299                        testindex = int(todebug)
300                        debugger = debuggers[descrs[testindex][0]]
301                    except (ValueError, IndexError):
302                        print "ERROR: invalid test number %r" % (todebug,)
303                    else:
304                        debugger.start()
305            except (EOFError, KeyboardInterrupt):
306                print
307                break
308
309
310# test utils ##################################################################
311from cStringIO import StringIO
312
313class SkipAwareTestResult(unittest._TextTestResult):
314
315    def __init__(self, stream, descriptions, verbosity,
316                 exitfirst=False, capture=0, printonly=None,
317                 pdbmode=False, cvg=None):
318        super(SkipAwareTestResult, self).__init__(stream,
319                                                  descriptions, verbosity)
320        self.skipped = []
321        self.debuggers = []
322        self.fail_descrs = []
323        self.error_descrs = []
324        self.exitfirst = exitfirst
325        self.capture = capture
326        self.printonly = printonly
327        self.pdbmode = pdbmode
328        self.cvg = cvg
329        self.pdbclass = Debugger
330
331    def descrs_for(self, flavour):
332        return getattr(self, '%s_descrs' % flavour.lower())
333   
334    def _create_pdb(self, test_descr, flavour):
335        self.descrs_for(flavour).append( (len(self.debuggers), test_descr) )
336        if self.pdbmode:
337            self.debuggers.append(self.pdbclass(sys.exc_info()[2]))
338       
339    def addError(self, test, err):
340        exc_type, exc, tcbk = err
341        if exc_type == TestSkipped:
342            self.addSkipped(test, exc)
343        else:
344            if self.exitfirst:
345                self.shouldStop = True
346            descr = self.getDescription(test)
347            super(SkipAwareTestResult, self).addError(test, err)
348            self._create_pdb(descr, 'error')
349
350    def addFailure(self, test, err):
351        if self.exitfirst:
352            self.shouldStop = True
353        descr = self.getDescription(test)
354        super(SkipAwareTestResult, self).addFailure(test, err)
355        self._create_pdb(descr, 'fail')
356
357    def addSkipped(self, test, reason):
358        self.skipped.append((test, self.getDescription(test), reason))
359        if self.showAll:
360            self.stream.writeln("SKIPPED")
361        elif self.dots:
362            self.stream.write('S')
363
364    def printErrors(self):
365        super(SkipAwareTestResult, self).printErrors()
366        self.printSkippedList()
367       
368    def printSkippedList(self):
369        for test, descr, err in self.skipped:
370            self.stream.writeln(self.separator1)
371            self.stream.writeln("%s: %s" % ('SKIPPED', descr))
372            self.stream.writeln("\t%s" % err)
373
374    def printErrorList(self, flavour, errors):
375        for (_, descr), (test, err) in zip(self.descrs_for(flavour), errors):
376            self.stream.writeln(self.separator1)
377            self.stream.writeln("%s: %s" % (flavour, descr))
378            self.stream.writeln(self.separator2)
379            self.stream.writeln("%s" % err)
380            try:
381                output, errput = test.captured_output()
382            except AttributeError:
383                pass # original unittest
384            else:
385                if output:
386                    self.stream.writeln(self.separator2)
387                    self.stream.writeln("captured stdout".center(len(self.separator2)))
388                    self.stream.writeln(self.separator2)
389                    self.stream.writeln(output)
390                else:
391                    self.stream.writeln('no stdout'.center(len(self.separator2)))
392                if errput:
393                    self.stream.writeln(self.separator2)
394                    self.stream.writeln("captured stderr".center(len(self.separator2)))
395                    self.stream.writeln(self.separator2)
396                    self.stream.writeln(errput)
397