~sambuddhabasu1/mailman/fix_mailman_run_error

« back to all changes in this revision

Viewing changes to misc/coverage.py

  • Committer: Barry Warsaw
  • Date: 2007-07-02 01:49:34 UTC
  • Revision ID: barry@python.org-20070702014934-4dej6tvbh64ypxo7
Add support for code coverage with 'testall --coverage'.  However, I'm not
convinced this is totally accurate as a full test run shows almost no coverage
in the Mailman.database.model modules even though I /know/ they're getting
executed.

I'll need to figure this out, but eventually we'll convert fully to setuptools
and then we'll use the nosetests to do testing and coverage.

Show diffs side-by-side

added added

removed removed

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