• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/python2
2#
3#             Perforce Defect Tracking Integration Project
4#              <http://www.ravenbrook.com/project/p4dti/>
5#
6#                   COVERAGE.PY -- COVERAGE TESTING
7#
8#             Gareth Rees, Ravenbrook Limited, 2001-12-04
9#                     Ned Batchelder, 2004-12-12
10#         http://nedbatchelder.com/code/modules/coverage.html
11#
12#
13# 1. INTRODUCTION
14#
15# This module provides coverage testing for Python code.
16#
17# The intended readership is all Python developers.
18#
19# This document is not confidential.
20#
21# See [GDR 2001-12-04a] for the command-line interface, programmatic
22# interface and limitations.  See [GDR 2001-12-04b] for requirements and
23# design.
24
25import pdb
26
27r"""Usage:
28
29coverage.py -x [-p] MODULE.py [ARG1 ARG2 ...]
30    Execute module, passing the given command-line arguments, collecting
31    coverage data. With the -p option, write to a temporary file containing
32    the machine name and process ID.
33
34coverage.py -e
35    Erase collected coverage data.
36
37coverage.py -c
38    Collect data from multiple coverage files (as created by -p option above)
39    and store it into a single file representing the union of the coverage.
40
41coverage.py -r [-m] [-o dir1,dir2,...] FILE1 FILE2 ...
42    Report on the statement coverage for the given files.  With the -m
43    option, show line numbers of the statements that weren't executed.
44
45coverage.py -a [-d dir] [-o dir1,dir2,...] FILE1 FILE2 ...
46    Make annotated copies of the given files, marking statements that
47    are executed with > and statements that are missed with !.  With
48    the -d option, make the copies in that directory.  Without the -d
49    option, make each copy in the same directory as the original.
50
51-o dir,dir2,...
52  Omit reporting or annotating files when their filename path starts with
53  a directory listed in the omit list.
54  e.g. python coverage.py -i -r -o c:\python23,lib\enthought\traits
55
56Coverage data is saved in the file .coverage by default.  Set the
57COVERAGE_FILE environment variable to save it somewhere else."""
58
59__version__ = "2.78.20070930"    # see detailed history at the end of this file.
60
61import compiler
62import compiler.visitor
63import glob
64import os
65import re
66import string
67import symbol
68import sys
69import threading
70import token
71import types
72from socket import gethostname
73
74# Python version compatibility
75try:
76    strclass = basestring   # new to 2.3
77except:
78    strclass = str
79
80# 2. IMPLEMENTATION
81#
82# This uses the "singleton" pattern.
83#
84# The word "morf" means a module object (from which the source file can
85# be deduced by suitable manipulation of the __file__ attribute) or a
86# filename.
87#
88# When we generate a coverage report we have to canonicalize every
89# filename in the coverage dictionary just in case it refers to the
90# module we are reporting on.  It seems a shame to throw away this
91# information so the data in the coverage dictionary is transferred to
92# the 'cexecuted' dictionary under the canonical filenames.
93#
94# The coverage dictionary is called "c" and the trace function "t".  The
95# reason for these short names is that Python looks up variables by name
96# at runtime and so execution time depends on the length of variables!
97# In the bottleneck of this application it's appropriate to abbreviate
98# names to increase speed.
99
100class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
101    """ A visitor for a parsed Abstract Syntax Tree which finds executable
102        statements.
103    """
104    def __init__(self, statements, excluded, suite_spots):
105        compiler.visitor.ASTVisitor.__init__(self)
106        self.statements = statements
107        self.excluded = excluded
108        self.suite_spots = suite_spots
109        self.excluding_suite = 0
110
111    def doRecursive(self, node):
112        for n in node.getChildNodes():
113            self.dispatch(n)
114
115    visitStmt = visitModule = doRecursive
116
117    def doCode(self, node):
118        if hasattr(node, 'decorators') and node.decorators:
119            self.dispatch(node.decorators)
120            self.recordAndDispatch(node.code)
121        else:
122            self.doSuite(node, node.code)
123
124    visitFunction = visitClass = doCode
125
126    def getFirstLine(self, node):
127        # Find the first line in the tree node.
128        lineno = node.lineno
129        for n in node.getChildNodes():
130            f = self.getFirstLine(n)
131            if lineno and f:
132                lineno = min(lineno, f)
133            else:
134                lineno = lineno or f
135        return lineno
136
137    def getLastLine(self, node):
138        # Find the first line in the tree node.
139        lineno = node.lineno
140        for n in node.getChildNodes():
141            lineno = max(lineno, self.getLastLine(n))
142        return lineno
143
144    def doStatement(self, node):
145        self.recordLine(self.getFirstLine(node))
146
147    visitAssert = visitAssign = visitAssTuple = visitPrint = \
148        visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
149        doStatement
150
151    def visitPass(self, node):
152        # Pass statements have weird interactions with docstrings.  If this
153        # pass statement is part of one of those pairs, claim that the statement
154        # is on the later of the two lines.
155        l = node.lineno
156        if l:
157            lines = self.suite_spots.get(l, [l,l])
158            self.statements[lines[1]] = 1
159
160    def visitDiscard(self, node):
161        # Discard nodes are statements that execute an expression, but then
162        # discard the results.  This includes function calls, so we can't
163        # ignore them all.  But if the expression is a constant, the statement
164        # won't be "executed", so don't count it now.
165        if node.expr.__class__.__name__ != 'Const':
166            self.doStatement(node)
167
168    def recordNodeLine(self, node):
169        # Stmt nodes often have None, but shouldn't claim the first line of
170        # their children (because the first child might be an ignorable line
171        # like "global a").
172        if node.__class__.__name__ != 'Stmt':
173            return self.recordLine(self.getFirstLine(node))
174        else:
175            return 0
176
177    def recordLine(self, lineno):
178        # Returns a bool, whether the line is included or excluded.
179        if lineno:
180            # Multi-line tests introducing suites have to get charged to their
181            # keyword.
182            if lineno in self.suite_spots:
183                lineno = self.suite_spots[lineno][0]
184            # If we're inside an excluded suite, record that this line was
185            # excluded.
186            if self.excluding_suite:
187                self.excluded[lineno] = 1
188                return 0
189            # If this line is excluded, or suite_spots maps this line to
190            # another line that is exlcuded, then we're excluded.
191            elif self.excluded.has_key(lineno) or \
192                 self.suite_spots.has_key(lineno) and \
193                 self.excluded.has_key(self.suite_spots[lineno][1]):
194                return 0
195            # Otherwise, this is an executable line.
196            else:
197                self.statements[lineno] = 1
198                return 1
199        return 0
200
201    default = recordNodeLine
202
203    def recordAndDispatch(self, node):
204        self.recordNodeLine(node)
205        self.dispatch(node)
206
207    def doSuite(self, intro, body, exclude=0):
208        exsuite = self.excluding_suite
209        if exclude or (intro and not self.recordNodeLine(intro)):
210            self.excluding_suite = 1
211        self.recordAndDispatch(body)
212        self.excluding_suite = exsuite
213
214    def doPlainWordSuite(self, prevsuite, suite):
215        # Finding the exclude lines for else's is tricky, because they aren't
216        # present in the compiler parse tree.  Look at the previous suite,
217        # and find its last line.  If any line between there and the else's
218        # first line are excluded, then we exclude the else.
219        lastprev = self.getLastLine(prevsuite)
220        firstelse = self.getFirstLine(suite)
221        for l in range(lastprev+1, firstelse):
222            if self.suite_spots.has_key(l):
223                self.doSuite(None, suite, exclude=self.excluded.has_key(l))
224                break
225        else:
226            self.doSuite(None, suite)
227
228    def doElse(self, prevsuite, node):
229        if node.else_:
230            self.doPlainWordSuite(prevsuite, node.else_)
231
232    def visitFor(self, node):
233        self.doSuite(node, node.body)
234        self.doElse(node.body, node)
235
236    visitWhile = visitFor
237
238    def visitIf(self, node):
239        # The first test has to be handled separately from the rest.
240        # The first test is credited to the line with the "if", but the others
241        # are credited to the line with the test for the elif.
242        self.doSuite(node, node.tests[0][1])
243        for t, n in node.tests[1:]:
244            self.doSuite(t, n)
245        self.doElse(node.tests[-1][1], node)
246
247    def visitTryExcept(self, node):
248        self.doSuite(node, node.body)
249        for i in range(len(node.handlers)):
250            a, b, h = node.handlers[i]
251            if not a:
252                # It's a plain "except:".  Find the previous suite.
253                if i > 0:
254                    prev = node.handlers[i-1][2]
255                else:
256                    prev = node.body
257                self.doPlainWordSuite(prev, h)
258            else:
259                self.doSuite(a, h)
260        self.doElse(node.handlers[-1][2], node)
261
262    def visitTryFinally(self, node):
263        self.doSuite(node, node.body)
264        self.doPlainWordSuite(node.body, node.final)
265
266    def visitWith(self, node):
267        self.doSuite(node, node.body)
268
269    def visitGlobal(self, node):
270        # "global" statements don't execute like others (they don't call the
271        # trace function), so don't record their line numbers.
272        pass
273
274the_coverage = None
275
276class CoverageException(Exception): pass
277
278class coverage:
279    # Name of the cache file (unless environment variable is set).
280    cache_default = ".coverage"
281
282    # Environment variable naming the cache file.
283    cache_env = "COVERAGE_FILE"
284
285    # A dictionary with an entry for (Python source file name, line number
286    # in that file) if that line has been executed.
287    c = {}
288
289    # A map from canonical Python source file name to a dictionary in
290    # which there's an entry for each line number that has been
291    # executed.
292    cexecuted = {}
293
294    # Cache of results of calling the analysis2() method, so that you can
295    # specify both -r and -a without doing double work.
296    analysis_cache = {}
297
298    # Cache of results of calling the canonical_filename() method, to
299    # avoid duplicating work.
300    canonical_filename_cache = {}
301
302    def __init__(self):
303        global the_coverage
304        if the_coverage:
305            raise CoverageException("Only one coverage object allowed.")
306        self.usecache = 1
307        self.cache = None
308        self.parallel_mode = False
309        self.exclude_re = ''
310        self.nesting = 0
311        self.cstack = []
312        self.xstack = []
313        self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.sep)
314        self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
315
316    # t(f, x, y).  This method is passed to sys.settrace as a trace function.
317    # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and
318    # the arguments and return value of the trace function.
319    # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
320    # objects.
321
322    def t(self, f, w, unused):                                 #pragma: no cover
323        if w == 'line':
324            #print "Executing %s @ %d" % (f.f_code.co_filename, f.f_lineno)
325            self.c[(f.f_code.co_filename, f.f_lineno)] = 1
326            for c in self.cstack:
327                c[(f.f_code.co_filename, f.f_lineno)] = 1
328        return self.t
329
330    def help(self, error=None):     #pragma: no cover
331        if error:
332            print error
333            print
334        print __doc__
335        sys.exit(1)
336
337    def command_line(self, argv, help_fn=None):
338        import getopt
339        help_fn = help_fn or self.help
340        settings = {}
341        optmap = {
342            '-a': 'annotate',
343            '-c': 'collect',
344            '-d:': 'directory=',
345            '-e': 'erase',
346            '-h': 'help',
347            '-i': 'ignore-errors',
348            '-m': 'show-missing',
349            '-p': 'parallel-mode',
350            '-r': 'report',
351            '-x': 'execute',
352            '-o:': 'omit=',
353            }
354        short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
355        long_opts = optmap.values()
356        options, args = getopt.getopt(argv, short_opts, long_opts)
357
358        for o, a in options:
359            if optmap.has_key(o):
360                settings[optmap[o]] = 1
361            elif optmap.has_key(o + ':'):
362                settings[optmap[o + ':']] = a
363            elif o[2:] in long_opts:
364                settings[o[2:]] = 1
365            elif o[2:] + '=' in long_opts:
366                settings[o[2:]+'='] = a
367            else:       #pragma: no cover
368                pass    # Can't get here, because getopt won't return anything unknown.
369
370        if settings.get('help'):
371            help_fn()
372
373        for i in ['erase', 'execute']:
374            for j in ['annotate', 'report', 'collect']:
375                if settings.get(i) and settings.get(j):
376                    help_fn("You can't specify the '%s' and '%s' "
377                              "options at the same time." % (i, j))
378
379        args_needed = (settings.get('execute')
380                       or settings.get('annotate')
381                       or settings.get('report'))
382        action = (settings.get('erase')
383                  or settings.get('collect')
384                  or args_needed)
385        if not action:
386            help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
387        if not args_needed and args:
388            help_fn("Unexpected arguments: %s" % " ".join(args))
389
390        self.parallel_mode = settings.get('parallel-mode')
391        self.get_ready()
392
393        if settings.get('erase'):
394            self.erase()
395        if settings.get('execute'):
396            if not args:
397                help_fn("Nothing to do.")
398            sys.argv = args
399            self.start()
400            import __main__
401            sys.path[0] = os.path.dirname(sys.argv[0])
402            # the line below is needed since otherwise __file__ gets fucked
403            __main__.__dict__["__file__"] = sys.argv[0]
404            execfile(sys.argv[0], __main__.__dict__)
405        if settings.get('collect'):
406            self.collect()
407        if not args:
408            args = self.cexecuted.keys()
409
410        ignore_errors = settings.get('ignore-errors')
411        show_missing = settings.get('show-missing')
412        directory = settings.get('directory=')
413
414        omit = settings.get('omit=')
415        if omit is not None:
416            omit = omit.split(',')
417        else:
418            omit = []
419
420        if settings.get('report'):
421            self.report(args, show_missing, ignore_errors, omit_prefixes=omit)
422        if settings.get('annotate'):
423            self.annotate(args, directory, ignore_errors, omit_prefixes=omit)
424
425    def use_cache(self, usecache, cache_file=None):
426        self.usecache = usecache
427        if cache_file and not self.cache:
428            self.cache_default = cache_file
429
430    def get_ready(self, parallel_mode=False):
431        if self.usecache and not self.cache:
432            self.cache = os.environ.get(self.cache_env, self.cache_default)
433            if self.parallel_mode:
434                self.cache += "." + gethostname() + "." + str(os.getpid())
435            self.restore()
436        self.analysis_cache = {}
437
438    def start(self, parallel_mode=False):
439        self.get_ready()
440        if self.nesting == 0:                               #pragma: no cover
441            sys.settrace(self.t)
442            if hasattr(threading, 'settrace'):
443                threading.settrace(self.t)
444        self.nesting += 1
445
446    def stop(self):
447        self.nesting -= 1
448        if self.nesting == 0:                               #pragma: no cover
449            sys.settrace(None)
450            if hasattr(threading, 'settrace'):
451                threading.settrace(None)
452
453    def erase(self):
454        self.get_ready()
455        self.c = {}
456        self.analysis_cache = {}
457        self.cexecuted = {}
458        if self.cache and os.path.exists(self.cache):
459            os.remove(self.cache)
460
461    def exclude(self, re):
462        if self.exclude_re:
463            self.exclude_re += "|"
464        self.exclude_re += "(" + re + ")"
465
466    def begin_recursive(self):
467        self.cstack.append(self.c)
468        self.xstack.append(self.exclude_re)
469
470    def end_recursive(self):
471        self.c = self.cstack.pop()
472        self.exclude_re = self.xstack.pop()
473
474    # save().  Save coverage data to the coverage cache.
475
476    def save(self):
477        if self.usecache and self.cache:
478            self.canonicalize_filenames()
479            cache = open(self.cache, 'wb')
480            import marshal
481            marshal.dump(self.cexecuted, cache)
482            cache.close()
483
484    # restore().  Restore coverage data from the coverage cache (if it exists).
485
486    def restore(self):
487        self.c = {}
488        self.cexecuted = {}
489        assert self.usecache
490        if os.path.exists(self.cache):
491            self.cexecuted = self.restore_file(self.cache)
492
493    def restore_file(self, file_name):
494        try:
495            cache = open(file_name, 'rb')
496            import marshal
497            cexecuted = marshal.load(cache)
498            cache.close()
499            if isinstance(cexecuted, types.DictType):
500                return cexecuted
501            else:
502                return {}
503        except:
504            return {}
505
506    # collect(). Collect data in multiple files produced by parallel mode
507
508    def collect(self):
509        cache_dir, local = os.path.split(self.cache)
510        for f in os.listdir(cache_dir or '.'):
511            if not f.startswith(local):
512                continue
513
514            full_path = os.path.join(cache_dir, f)
515            cexecuted = self.restore_file(full_path)
516            self.merge_data(cexecuted)
517
518    def merge_data(self, new_data):
519        for file_name, file_data in new_data.items():
520            if self.cexecuted.has_key(file_name):
521                self.merge_file_data(self.cexecuted[file_name], file_data)
522            else:
523                self.cexecuted[file_name] = file_data
524
525    def merge_file_data(self, cache_data, new_data):
526        for line_number in new_data.keys():
527            if not cache_data.has_key(line_number):
528                cache_data[line_number] = new_data[line_number]
529
530    # canonical_filename(filename).  Return a canonical filename for the
531    # file (that is, an absolute path with no redundant components and
532    # normalized case).  See [GDR 2001-12-04b, 3.3].
533
534    def canonical_filename(self, filename):
535        if not self.canonical_filename_cache.has_key(filename):
536            f = filename
537            if os.path.isabs(f) and not os.path.exists(f):
538                f = os.path.basename(f)
539            if not os.path.isabs(f):
540                for path in [os.curdir] + sys.path:
541                    g = os.path.join(path, f)
542                    if os.path.exists(g):
543                        f = g
544                        break
545            cf = os.path.normcase(os.path.abspath(f))
546            self.canonical_filename_cache[filename] = cf
547        return self.canonical_filename_cache[filename]
548
549    # canonicalize_filenames().  Copy results from "c" to "cexecuted",
550    # canonicalizing filenames on the way.  Clear the "c" map.
551
552    def canonicalize_filenames(self):
553        for filename, lineno in self.c.keys():
554            if filename == '<string>':
555                # Can't do anything useful with exec'd strings, so skip them.
556                continue
557            f = self.canonical_filename(filename)
558            if not self.cexecuted.has_key(f):
559                self.cexecuted[f] = {}
560            self.cexecuted[f][lineno] = 1
561        self.c = {}
562
563    # morf_filename(morf).  Return the filename for a module or file.
564
565    def morf_filename(self, morf):
566        if isinstance(morf, types.ModuleType):
567            if not hasattr(morf, '__file__'):
568                raise CoverageException("Module has no __file__ attribute.")
569            f = morf.__file__
570        else:
571            f = morf
572        return self.canonical_filename(f)
573
574    # analyze_morf(morf).  Analyze the module or filename passed as
575    # the argument.  If the source code can't be found, raise an error.
576    # Otherwise, return a tuple of (1) the canonical filename of the
577    # source code for the module, (2) a list of lines of statements
578    # in the source code, (3) a list of lines of excluded statements,
579    # and (4), a map of line numbers to multi-line line number ranges, for
580    # statements that cross lines.
581
582    def analyze_morf(self, morf):
583        if self.analysis_cache.has_key(morf):
584            return self.analysis_cache[morf]
585        filename = self.morf_filename(morf)
586        ext = os.path.splitext(filename)[1]
587        if ext == '.pyc':
588            if not os.path.exists(filename[:-1]):
589                raise CoverageException(
590                    "No source for compiled code '%s'." % filename
591                    )
592            filename = filename[:-1]
593        source = open(filename, 'r')
594        try:
595            lines, excluded_lines, line_map = self.find_executable_statements(
596                source.read(), exclude=self.exclude_re
597                )
598        except SyntaxError, synerr:
599            raise CoverageException(
600                "Couldn't parse '%s' as Python source: '%s' at line %d" %
601                    (filename, synerr.msg, synerr.lineno)
602                )
603        source.close()
604        result = filename, lines, excluded_lines, line_map
605        self.analysis_cache[morf] = result
606        return result
607
608    def first_line_of_tree(self, tree):
609        while True:
610            if len(tree) == 3 and type(tree[2]) == type(1):
611                return tree[2]
612            tree = tree[1]
613
614    def last_line_of_tree(self, tree):
615        while True:
616            if len(tree) == 3 and type(tree[2]) == type(1):
617                return tree[2]
618            tree = tree[-1]
619
620    def find_docstring_pass_pair(self, tree, spots):
621        for i in range(1, len(tree)):
622            if self.is_string_constant(tree[i]) and self.is_pass_stmt(tree[i+1]):
623                first_line = self.first_line_of_tree(tree[i])
624                last_line = self.last_line_of_tree(tree[i+1])
625                self.record_multiline(spots, first_line, last_line)
626
627    def is_string_constant(self, tree):
628        try:
629            return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
630        except:
631            return False
632
633    def is_pass_stmt(self, tree):
634        try:
635            return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
636        except:
637            return False
638
639    def record_multiline(self, spots, i, j):
640        for l in range(i, j+1):
641            spots[l] = (i, j)
642
643    def get_suite_spots(self, tree, spots):
644        """ Analyze a parse tree to find suite introducers which span a number
645            of lines.
646        """
647        for i in range(1, len(tree)):
648            if type(tree[i]) == type(()):
649                if tree[i][0] == symbol.suite:
650                    # Found a suite, look back for the colon and keyword.
651                    lineno_colon = lineno_word = None
652                    for j in range(i-1, 0, -1):
653                        if tree[j][0] == token.COLON:
654                            # Colons are never executed themselves: we want the
655                            # line number of the last token before the colon.
656                            lineno_colon = self.last_line_of_tree(tree[j-1])
657                        elif tree[j][0] == token.NAME:
658                            if tree[j][1] == 'elif':
659                                # Find the line number of the first non-terminal
660                                # after the keyword.
661                                t = tree[j+1]
662                                while t and token.ISNONTERMINAL(t[0]):
663                                    t = t[1]
664                                if t:
665                                    lineno_word = t[2]
666                            else:
667                                lineno_word = tree[j][2]
668                            break
669                        elif tree[j][0] == symbol.except_clause:
670                            # "except" clauses look like:
671                            # ('except_clause', ('NAME', 'except', lineno), ...)
672                            if tree[j][1][0] == token.NAME:
673                                lineno_word = tree[j][1][2]
674                                break
675                    if lineno_colon and lineno_word:
676                        # Found colon and keyword, mark all the lines
677                        # between the two with the two line numbers.
678                        self.record_multiline(spots, lineno_word, lineno_colon)
679
680                    # "pass" statements are tricky: different versions of Python
681                    # treat them differently, especially in the common case of a
682                    # function with a doc string and a single pass statement.
683                    self.find_docstring_pass_pair(tree[i], spots)
684
685                elif tree[i][0] == symbol.simple_stmt:
686                    first_line = self.first_line_of_tree(tree[i])
687                    last_line = self.last_line_of_tree(tree[i])
688                    if first_line != last_line:
689                        self.record_multiline(spots, first_line, last_line)
690                self.get_suite_spots(tree[i], spots)
691
692    def find_executable_statements(self, text, exclude=None):
693        # Find lines which match an exclusion pattern.
694        excluded = {}
695        suite_spots = {}
696        if exclude:
697            reExclude = re.compile(exclude)
698            lines = text.split('\n')
699            for i in range(len(lines)):
700                if reExclude.search(lines[i]):
701                    excluded[i+1] = 1
702
703        # Parse the code and analyze the parse tree to find out which statements
704        # are multiline, and where suites begin and end.
705        import parser
706        tree = parser.suite(text+'\n\n').totuple(1)
707        self.get_suite_spots(tree, suite_spots)
708        #print "Suite spots:", suite_spots
709
710        # Use the compiler module to parse the text and find the executable
711        # statements.  We add newlines to be impervious to final partial lines.
712        statements = {}
713        ast = compiler.parse(text+'\n\n')
714        visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
715        compiler.walk(ast, visitor, walker=visitor)
716
717        lines = statements.keys()
718        lines.sort()
719        excluded_lines = excluded.keys()
720        excluded_lines.sort()
721        return lines, excluded_lines, suite_spots
722
723    # format_lines(statements, lines).  Format a list of line numbers
724    # for printing by coalescing groups of lines as long as the lines
725    # represent consecutive statements.  This will coalesce even if
726    # there are gaps between statements, so if statements =
727    # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
728    # format_lines will return "1-2, 5-11, 13-14".
729
730    def format_lines(self, statements, lines):
731        pairs = []
732        i = 0
733        j = 0
734        start = None
735        pairs = []
736        while i < len(statements) and j < len(lines):
737            if statements[i] == lines[j]:
738                if start is None:
739                    start = lines[j]
740                end = lines[j]
741                j = j + 1
742            elif start:
743                pairs.append((start, end))
744                start = None
745            i = i + 1
746        if start:
747            pairs.append((start, end))
748        def stringify(pair):
749            start, end = pair
750            if start == end:
751                return "%d" % start
752            else:
753                return "%d-%d" % (start, end)
754        ret = string.join(map(stringify, pairs), ", ")
755        return ret
756
757    # Backward compatibility with version 1.
758    def analysis(self, morf):
759        f, s, _, m, mf = self.analysis2(morf)
760        return f, s, m, mf
761
762    def analysis2(self, morf):
763        filename, statements, excluded, line_map = self.analyze_morf(morf)
764        self.canonicalize_filenames()
765        if not self.cexecuted.has_key(filename):
766            self.cexecuted[filename] = {}
767        missing = []
768        for line in statements:
769            lines = line_map.get(line, [line, line])
770            for l in range(lines[0], lines[1]+1):
771                if self.cexecuted[filename].has_key(l):
772                    break
773            else:
774                missing.append(line)
775        return (filename, statements, excluded, missing,
776                self.format_lines(statements, missing))
777
778    def relative_filename(self, filename):
779        """ Convert filename to relative filename from self.relative_dir.
780        """
781        return filename.replace(self.relative_dir, "")
782
783    def morf_name(self, morf):
784        """ Return the name of morf as used in report.
785        """
786        if isinstance(morf, types.ModuleType):
787            return morf.__name__
788        else:
789            return self.relative_filename(os.path.splitext(morf)[0])
790
791    def filter_by_prefix(self, morfs, omit_prefixes):
792        """ Return list of morfs where the morf name does not begin
793            with any one of the omit_prefixes.
794        """
795        filtered_morfs = []
796        for morf in morfs:
797            for prefix in omit_prefixes:
798                if self.morf_name(morf).startswith(prefix):
799                    break
800            else:
801                filtered_morfs.append(morf)
802
803        return filtered_morfs
804
805    def morf_name_compare(self, x, y):
806        return cmp(self.morf_name(x), self.morf_name(y))
807
808    def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
809        if not isinstance(morfs, types.ListType):
810            morfs = [morfs]
811        # On windows, the shell doesn't expand wildcards.  Do it here.
812        globbed = []
813        for morf in morfs:
814            if isinstance(morf, strclass):
815                globbed.extend(glob.glob(morf))
816            else:
817                globbed.append(morf)
818        morfs = globbed
819
820        morfs = self.filter_by_prefix(morfs, omit_prefixes)
821        morfs.sort(self.morf_name_compare)
822
823        max_name = max([5,] + map(len, map(self.morf_name, morfs)))
824        fmt_name = "%%- %ds  " % max_name
825        fmt_err = fmt_name + "%s: %s"
826        header = fmt_name % "Name" + " Stmts   Exec  Cover"
827        fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
828        if show_missing:
829            header = header + "   Missing"
830            fmt_coverage = fmt_coverage + "   %s"
831        if not file:
832            file = sys.stdout
833        print >>file, header
834        print >>file, "-" * len(header)
835        total_statements = 0
836        total_executed = 0
837        for morf in morfs:
838            name = self.morf_name(morf)
839            try:
840                _, statements, _, missing, readable  = self.analysis2(morf)
841                n = len(statements)
842                m = n - len(missing)
843                if n > 0:
844                    pc = 100.0 * m / n
845                else:
846                    pc = 100.0
847                args = (name, n, m, pc)
848                if show_missing:
849                    args = args + (readable,)
850                print >>file, fmt_coverage % args
851                total_statements = total_statements + n
852                total_executed = total_executed + m
853            except KeyboardInterrupt:                       #pragma: no cover
854                raise
855            except:
856                if not ignore_errors:
857                    typ, msg = sys.exc_info()[:2]
858                    print >>file, fmt_err % (name, typ, msg)
859        if len(morfs) > 1:
860            print >>file, "-" * len(header)
861            if total_statements > 0:
862                pc = 100.0 * total_executed / total_statements
863            else:
864                pc = 100.0
865            args = ("TOTAL", total_statements, total_executed, pc)
866            if show_missing:
867                args = args + ("",)
868            print >>file, fmt_coverage % args
869
870    # annotate(morfs, ignore_errors).
871
872    blank_re = re.compile(r"\s*(#|$)")
873    else_re = re.compile(r"\s*else\s*:\s*(#|$)")
874
875    def annotate(self, morfs, directory=None, ignore_errors=0, omit_prefixes=[]):
876        morfs = self.filter_by_prefix(morfs, omit_prefixes)
877        for morf in morfs:
878            try:
879                filename, statements, excluded, missing, _ = self.analysis2(morf)
880                self.annotate_file(filename, statements, excluded, missing, directory)
881            except KeyboardInterrupt:
882                raise
883            except:
884                if not ignore_errors:
885                    raise
886
887    def annotate_file(self, filename, statements, excluded, missing, directory=None):
888        source = open(filename, 'r')
889        if directory:
890            dest_file = os.path.join(directory,
891                                     os.path.basename(filename)
892                                     + ',cover')
893        else:
894            dest_file = filename + ',cover'
895        dest = open(dest_file, 'w')
896        lineno = 0
897        i = 0
898        j = 0
899        covered = 1
900        while 1:
901            line = source.readline()
902            if line == '':
903                break
904            lineno = lineno + 1
905            while i < len(statements) and statements[i] < lineno:
906                i = i + 1
907            while j < len(missing) and missing[j] < lineno:
908                j = j + 1
909            if i < len(statements) and statements[i] == lineno:
910                covered = j >= len(missing) or missing[j] > lineno
911            if self.blank_re.match(line):
912                dest.write('  ')
913            elif self.else_re.match(line):
914                # Special logic for lines containing only 'else:'.
915                # See [GDR 2001-12-04b, 3.2].
916                if i >= len(statements) and j >= len(missing):
917                    dest.write('! ')
918                elif i >= len(statements) or j >= len(missing):
919                    dest.write('> ')
920                elif statements[i] == missing[j]:
921                    dest.write('! ')
922                else:
923                    dest.write('> ')
924            elif lineno in excluded:
925                dest.write('- ')
926            elif covered:
927                dest.write('> ')
928            else:
929                dest.write('! ')
930            dest.write(line)
931        source.close()
932        dest.close()
933
934# Singleton object.
935the_coverage = coverage()
936
937# Module functions call methods in the singleton object.
938def use_cache(*args, **kw):
939    return the_coverage.use_cache(*args, **kw)
940
941def start(*args, **kw):
942    return the_coverage.start(*args, **kw)
943
944def stop(*args, **kw):
945    return the_coverage.stop(*args, **kw)
946
947def erase(*args, **kw):
948    return the_coverage.erase(*args, **kw)
949
950def begin_recursive(*args, **kw):
951    return the_coverage.begin_recursive(*args, **kw)
952
953def end_recursive(*args, **kw):
954    return the_coverage.end_recursive(*args, **kw)
955
956def exclude(*args, **kw):
957    return the_coverage.exclude(*args, **kw)
958
959def analysis(*args, **kw):
960    return the_coverage.analysis(*args, **kw)
961
962def analysis2(*args, **kw):
963    return the_coverage.analysis2(*args, **kw)
964
965def report(*args, **kw):
966    return the_coverage.report(*args, **kw)
967
968def annotate(*args, **kw):
969    return the_coverage.annotate(*args, **kw)
970
971def annotate_file(*args, **kw):
972    return the_coverage.annotate_file(*args, **kw)
973
974# Save coverage data when Python exits.  (The atexit module wasn't
975# introduced until Python 2.0, so use sys.exitfunc when it's not
976# available.)
977try:
978    import atexit
979    atexit.register(the_coverage.save)
980except ImportError:
981    sys.exitfunc = the_coverage.save
982
983# Command-line interface.
984if __name__ == '__main__':
985    the_coverage.command_line(sys.argv[1:])
986
987
988# A. REFERENCES
989#
990# [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
991# Ravenbrook Limited; 2001-12-04;
992# <http://www.nedbatchelder.com/code/modules/rees-coverage.html>.
993#
994# [GDR 2001-12-04b] "Statement coverage for Python: design and
995# analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
996# <http://www.nedbatchelder.com/code/modules/rees-design.html>.
997#
998# [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
999# Guide van Rossum; 2001-07-20;
1000# <http://www.python.org/doc/2.1.1/ref/ref.html>.
1001#
1002# [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
1003# 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
1004#
1005#
1006# B. DOCUMENT HISTORY
1007#
1008# 2001-12-04 GDR Created.
1009#
1010# 2001-12-06 GDR Added command-line interface and source code
1011# annotation.
1012#
1013# 2001-12-09 GDR Moved design and interface to separate documents.
1014#
1015# 2001-12-10 GDR Open cache file as binary on Windows.  Allow
1016# simultaneous -e and -x, or -a and -r.
1017#
1018# 2001-12-12 GDR Added command-line help.  Cache analysis so that it
1019# only needs to be done once when you specify -a and -r.
1020#
1021# 2001-12-13 GDR Improved speed while recording.  Portable between
1022# Python 1.5.2 and 2.1.1.
1023#
1024# 2002-01-03 GDR Module-level functions work correctly.
1025#
1026# 2002-01-07 GDR Update sys.path when running a file with the -x option,
1027# so that it matches the value the program would get if it were run on
1028# its own.
1029#
1030# 2004-12-12 NMB Significant code changes.
1031# - Finding executable statements has been rewritten so that docstrings and
1032#   other quirks of Python execution aren't mistakenly identified as missing
1033#   lines.
1034# - Lines can be excluded from consideration, even entire suites of lines.
1035# - The filesystem cache of covered lines can be disabled programmatically.
1036# - Modernized the code.
1037#
1038# 2004-12-14 NMB Minor tweaks.  Return 'analysis' to its original behavior
1039# and add 'analysis2'.  Add a global for 'annotate', and factor it, adding
1040# 'annotate_file'.
1041#
1042# 2004-12-31 NMB Allow for keyword arguments in the module global functions.
1043# Thanks, Allen.
1044#
1045# 2005-12-02 NMB Call threading.settrace so that all threads are measured.
1046# Thanks Martin Fuzzey. Add a file argument to report so that reports can be
1047# captured to a different destination.
1048#
1049# 2005-12-03 NMB coverage.py can now measure itself.
1050#
1051# 2005-12-04 NMB Adapted Greg Rogers' patch for using relative filenames,
1052# and sorting and omitting files to report on.
1053#
1054# 2006-07-23 NMB Applied Joseph Tate's patch for function decorators.
1055#
1056# 2006-08-21 NMB Applied Sigve Tjora and Mark van der Wal's fixes for argument
1057# handling.
1058#
1059# 2006-08-22 NMB Applied Geoff Bache's parallel mode patch.
1060#
1061# 2006-08-23 NMB Refactorings to improve testability.  Fixes to command-line
1062# logic for parallel mode and collect.
1063#
1064# 2006-08-25 NMB "#pragma: nocover" is excluded by default.
1065#
1066# 2006-09-10 NMB Properly ignore docstrings and other constant expressions that
1067# appear in the middle of a function, a problem reported by Tim Leslie.
1068# Minor changes to avoid lint warnings.
1069#
1070# 2006-09-17 NMB coverage.erase() shouldn't clobber the exclude regex.
1071# Change how parallel mode is invoked, and fix erase() so that it erases the
1072# cache when called programmatically.
1073#
1074# 2007-07-21 NMB In reports, ignore code executed from strings, since we can't
1075# do anything useful with it anyway.
1076# Better file handling on Linux, thanks Guillaume Chazarain.
1077# Better shell support on Windows, thanks Noel O'Boyle.
1078# Python 2.2 support maintained, thanks Catherine Proulx.
1079#
1080# 2007-07-22 NMB Python 2.5 now fully supported. The method of dealing with
1081# multi-line statements is now less sensitive to the exact line that Python
1082# reports during execution. Pass statements are handled specially so that their
1083# disappearance during execution won't throw off the measurement.
1084#
1085# 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the
1086# new with statement is counted as executable.
1087#
1088# 2007-07-29 NMB Better packaging.
1089#
1090# 2007-09-30 NMB Don't try to predict whether a file is Python source based on
1091# the extension. Extensionless files are often Pythons scripts. Instead, simply
1092# parse the file and catch the syntax errors.  Hat tip to Ben Finney.
1093
1094# C. COPYRIGHT AND LICENCE
1095#
1096# Copyright 2001 Gareth Rees.  All rights reserved.
1097# Copyright 2004-2007 Ned Batchelder.  All rights reserved.
1098#
1099# Redistribution and use in source and binary forms, with or without
1100# modification, are permitted provided that the following conditions are
1101# met:
1102#
1103# 1. Redistributions of source code must retain the above copyright
1104#    notice, this list of conditions and the following disclaimer.
1105#
1106# 2. Redistributions in binary form must reproduce the above copyright
1107#    notice, this list of conditions and the following disclaimer in the
1108#    documentation and/or other materials provided with the
1109#    distribution.
1110#
1111# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
1112# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
1113# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
1114# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
1115# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
1116# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
1117# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
1118# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
1119# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
1120# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
1121# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
1122# DAMAGE.
1123#
1124# $Id: coverage.py 79 2007-10-01 01:01:52Z nedbat $
1125