~ubuntu-branches/ubuntu/maverick/python-coverage/maverick

« back to all changes in this revision

Viewing changes to coverage.py

  • Committer: Bazaar Package Importer
  • Author(s): Ben Finney, Ben Finney
  • Date: 2008-06-30 23:44:57 UTC
  • Revision ID: james.westby@ubuntu.com-20080630234457-mcxukedgt30t85mi
Tags: 2.80-1
[ Ben Finney ]
* New upstream release.
* debian/control
  + Added Ben Finney to Uploaders.
  + Set Standards-Version to 3.8.0 (no changes required).
  + Add Build-Depends-Indep: python-setuptools, as now used by upstream
    packaging.
  + Upgrade Build-Depends-Indep: python-central to version that doesn't
    require 'debian/pycompat'.
* debian/pycompat
  + Remove obsolete file.
* debian/watch
  + Added, pointing to upstream author's site.
* debian/rules
  + Upstream packaging installs program to '/usr/bin/coverage', move to
    '/usr/bin/python-coverage' to conform with existing versions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
#
3
1
#             Perforce Defect Tracking Integration Project
4
2
#              <http://www.ravenbrook.com/project/p4dti/>
5
3
#
54
52
Coverage data is saved in the file .coverage by default.  Set the
55
53
COVERAGE_FILE environment variable to save it somewhere else."""
56
54
 
57
 
__version__ = "2.6.20060823"    # see detailed history at the end of this file.
 
55
__version__ = "2.80.20080525"    # see detailed history at the end of this file.
58
56
 
59
57
import compiler
60
58
import compiler.visitor
 
59
import glob
61
60
import os
62
61
import re
63
62
import string
 
63
import symbol
64
64
import sys
65
65
import threading
 
66
import token
66
67
import types
67
68
from socket import gethostname
68
69
 
 
70
# Python version compatibility
 
71
try:
 
72
    strclass = basestring   # new to 2.3
 
73
except:
 
74
    strclass = str
 
75
 
69
76
# 2. IMPLEMENTATION
70
77
#
71
78
# This uses the "singleton" pattern.
87
94
# names to increase speed.
88
95
 
89
96
class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
 
97
    """ A visitor for a parsed Abstract Syntax Tree which finds executable
 
98
        statements.
 
99
    """
90
100
    def __init__(self, statements, excluded, suite_spots):
91
101
        compiler.visitor.ASTVisitor.__init__(self)
92
102
        self.statements = statements
95
105
        self.excluding_suite = 0
96
106
        
97
107
    def doRecursive(self, node):
98
 
        self.recordNodeLine(node)
99
108
        for n in node.getChildNodes():
100
109
            self.dispatch(n)
101
110
 
131
140
    def doStatement(self, node):
132
141
        self.recordLine(self.getFirstLine(node))
133
142
 
134
 
    visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
 
143
    visitAssert = visitAssign = visitAssTuple = visitPrint = \
135
144
        visitPrintnl = visitRaise = visitSubscript = visitDecorators = \
136
145
        doStatement
137
146
    
 
147
    def visitPass(self, node):
 
148
        # Pass statements have weird interactions with docstrings.  If this
 
149
        # pass statement is part of one of those pairs, claim that the statement
 
150
        # is on the later of the two lines.
 
151
        l = node.lineno
 
152
        if l:
 
153
            lines = self.suite_spots.get(l, [l,l])
 
154
            self.statements[lines[1]] = 1
 
155
        
 
156
    def visitDiscard(self, node):
 
157
        # Discard nodes are statements that execute an expression, but then
 
158
        # discard the results.  This includes function calls, so we can't 
 
159
        # ignore them all.  But if the expression is a constant, the statement
 
160
        # won't be "executed", so don't count it now.
 
161
        if node.expr.__class__.__name__ != 'Const':
 
162
            self.doStatement(node)
 
163
 
138
164
    def recordNodeLine(self, node):
139
 
        return self.recordLine(node.lineno)
 
165
        # Stmt nodes often have None, but shouldn't claim the first line of
 
166
        # their children (because the first child might be an ignorable line
 
167
        # like "global a").
 
168
        if node.__class__.__name__ != 'Stmt':
 
169
            return self.recordLine(self.getFirstLine(node))
 
170
        else:
 
171
            return 0
140
172
    
141
173
    def recordLine(self, lineno):
142
174
        # Returns a bool, whether the line is included or excluded.
145
177
            # keyword.
146
178
            if lineno in self.suite_spots:
147
179
                lineno = self.suite_spots[lineno][0]
148
 
            # If we're inside an exluded suite, record that this line was
 
180
            # If we're inside an excluded suite, record that this line was
149
181
            # excluded.
150
182
            if self.excluding_suite:
151
183
                self.excluded[lineno] = 1
197
229
        self.doSuite(node, node.body)
198
230
        self.doElse(node.body, node)
199
231
 
 
232
    visitWhile = visitFor
 
233
 
200
234
    def visitIf(self, node):
201
235
        # The first test has to be handled separately from the rest.
202
236
        # The first test is credited to the line with the "if", but the others
206
240
            self.doSuite(t, n)
207
241
        self.doElse(node.tests[-1][1], node)
208
242
 
209
 
    def visitWhile(self, node):
210
 
        self.doSuite(node, node.body)
211
 
        self.doElse(node.body, node)
212
 
 
213
243
    def visitTryExcept(self, node):
214
244
        self.doSuite(node, node.body)
215
245
        for i in range(len(node.handlers)):
229
259
        self.doSuite(node, node.body)
230
260
        self.doPlainWordSuite(node.body, node.final)
231
261
        
 
262
    def visitWith(self, node):
 
263
        self.doSuite(node, node.body)
 
264
        
232
265
    def visitGlobal(self, node):
233
266
        # "global" statements don't execute like others (they don't call the
234
267
        # trace function), so don't record their line numbers.
265
298
    def __init__(self):
266
299
        global the_coverage
267
300
        if the_coverage:
268
 
            raise CoverageException, "Only one coverage object allowed."
 
301
            raise CoverageException("Only one coverage object allowed.")
269
302
        self.usecache = 1
270
303
        self.cache = None
 
304
        self.parallel_mode = False
271
305
        self.exclude_re = ''
272
306
        self.nesting = 0
273
307
        self.cstack = []
274
308
        self.xstack = []
275
 
        self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.path.sep)
 
309
        self.relative_dir = os.path.normcase(os.path.abspath(os.curdir)+os.sep)
 
310
        self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
276
311
 
277
312
    # t(f, x, y).  This method is passed to sys.settrace as a trace function.  
278
313
    # See [van Rossum 2001-07-20b, 9.2] for an explanation of sys.settrace and 
280
315
    # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
281
316
    # objects.
282
317
    
283
 
    def t(self, f, w, a):                                   #pragma: no cover
 
318
    def t(self, f, w, unused):                                 #pragma: no cover
284
319
        if w == 'line':
285
320
            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
 
321
            #-for c in self.cstack:
 
322
            #-    c[(f.f_code.co_filename, f.f_lineno)] = 1
288
323
        return self.t
289
324
    
290
 
    def help(self, error=None):
 
325
    def help(self, error=None):     #pragma: no cover
291
326
        if error:
292
327
            print error
293
328
            print
294
329
        print __doc__
295
330
        sys.exit(1)
296
331
 
297
 
    def command_line(self, argv, help=None):
 
332
    def command_line(self, argv, help_fn=None):
298
333
        import getopt
299
 
        help = help or self.help
 
334
        help_fn = help_fn or self.help
300
335
        settings = {}
301
336
        optmap = {
302
337
            '-a': 'annotate',
327
362
                pass    # Can't get here, because getopt won't return anything unknown.
328
363
 
329
364
        if settings.get('help'):
330
 
            help()
 
365
            help_fn()
331
366
 
332
367
        for i in ['erase', 'execute']:
333
368
            for j in ['annotate', 'report', 'collect']:
334
369
                if settings.get(i) and settings.get(j):
335
 
                    help("You can't specify the '%s' and '%s' "
 
370
                    help_fn("You can't specify the '%s' and '%s' "
336
371
                              "options at the same time." % (i, j))
337
372
 
338
373
        args_needed = (settings.get('execute')
342
377
                  or settings.get('collect')
343
378
                  or args_needed)
344
379
        if not action:
345
 
            help("You must specify at least one of -e, -x, -c, -r, or -a.")
 
380
            help_fn("You must specify at least one of -e, -x, -c, -r, or -a.")
346
381
        if not args_needed and args:
347
 
            help("Unexpected arguments: %s" % " ".join(args))
 
382
            help_fn("Unexpected arguments: %s" % " ".join(args))
348
383
        
349
 
        self.get_ready(settings.get('parallel-mode'))
350
 
        self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
 
384
        self.parallel_mode = settings.get('parallel-mode')
 
385
        self.get_ready()
351
386
 
352
387
        if settings.get('erase'):
353
388
            self.erase()
354
389
        if settings.get('execute'):
355
390
            if not args:
356
 
                help("Nothing to do.")
 
391
                help_fn("Nothing to do.")
357
392
            sys.argv = args
358
393
            self.start()
359
394
            import __main__
387
422
    def get_ready(self, parallel_mode=False):
388
423
        if self.usecache and not self.cache:
389
424
            self.cache = os.environ.get(self.cache_env, self.cache_default)
390
 
            if parallel_mode:
 
425
            if self.parallel_mode:
391
426
                self.cache += "." + gethostname() + "." + str(os.getpid())
392
427
            self.restore()
393
428
        self.analysis_cache = {}
394
429
        
395
430
    def start(self, parallel_mode=False):
396
 
        self.get_ready(parallel_mode)
 
431
        self.get_ready()
397
432
        if self.nesting == 0:                               #pragma: no cover
398
433
            sys.settrace(self.t)
399
434
            if hasattr(threading, 'settrace'):
408
443
                threading.settrace(None)
409
444
 
410
445
    def erase(self):
 
446
        self.get_ready()
411
447
        self.c = {}
412
448
        self.analysis_cache = {}
413
449
        self.cexecuted = {}
414
450
        if self.cache and os.path.exists(self.cache):
415
451
            os.remove(self.cache)
416
 
        self.exclude_re = ""
417
452
 
418
453
    def exclude(self, re):
419
454
        if self.exclude_re:
464
499
 
465
500
    def collect(self):
466
501
        cache_dir, local = os.path.split(self.cache)
467
 
        for file in os.listdir(cache_dir):
468
 
            if not file.startswith(local):
 
502
        for f in os.listdir(cache_dir or '.'):
 
503
            if not f.startswith(local):
469
504
                continue
470
505
 
471
 
            full_path = os.path.join(cache_dir, file)
 
506
            full_path = os.path.join(cache_dir, f)
472
507
            cexecuted = self.restore_file(full_path)
473
508
            self.merge_data(cexecuted)
474
509
 
508
543
 
509
544
    def canonicalize_filenames(self):
510
545
        for filename, lineno in self.c.keys():
 
546
            if filename == '<string>':
 
547
                # Can't do anything useful with exec'd strings, so skip them.
 
548
                continue
511
549
            f = self.canonical_filename(filename)
512
550
            if not self.cexecuted.has_key(f):
513
551
                self.cexecuted[f] = {}
519
557
    def morf_filename(self, morf):
520
558
        if isinstance(morf, types.ModuleType):
521
559
            if not hasattr(morf, '__file__'):
522
 
                raise CoverageException, "Module has no __file__ attribute."
523
 
            file = morf.__file__
 
560
                raise CoverageException("Module has no __file__ attribute.")
 
561
            f = morf.__file__
524
562
        else:
525
 
            file = morf
526
 
        return self.canonical_filename(file)
 
563
            f = morf
 
564
        return self.canonical_filename(f)
527
565
 
528
566
    # analyze_morf(morf).  Analyze the module or filename passed as
529
567
    # the argument.  If the source code can't be found, raise an error.
530
568
    # Otherwise, return a tuple of (1) the canonical filename of the
531
569
    # 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
 
 
 
570
    # in the source code, (3) a list of lines of excluded statements,
 
571
    # and (4), a map of line numbers to multi-line line number ranges, for
 
572
    # statements that cross lines.
 
573
    
534
574
    def analyze_morf(self, morf):
535
575
        if self.analysis_cache.has_key(morf):
536
576
            return self.analysis_cache[morf]
537
577
        filename = self.morf_filename(morf)
538
578
        ext = os.path.splitext(filename)[1]
539
579
        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
 
            )
 
580
            if not os.path.exists(filename[:-1]):
 
581
                raise CoverageException(
 
582
                    "No source for compiled code '%s'." % filename
 
583
                    )
 
584
            filename = filename[:-1]
 
585
        source = open(filename, 'rU')
 
586
        try:
 
587
            lines, excluded_lines, line_map = self.find_executable_statements(
 
588
                source.read(), exclude=self.exclude_re
 
589
                )
 
590
        except SyntaxError, synerr:
 
591
            raise CoverageException(
 
592
                "Couldn't parse '%s' as Python source: '%s' at line %d" %
 
593
                    (filename, synerr.msg, synerr.lineno)
 
594
                )            
550
595
        source.close()
551
 
        result = filename, lines, excluded_lines
 
596
        result = filename, lines, excluded_lines, line_map
552
597
        self.analysis_cache[morf] = result
553
598
        return result
554
599
 
 
600
    def first_line_of_tree(self, tree):
 
601
        while True:
 
602
            if len(tree) == 3 and type(tree[2]) == type(1):
 
603
                return tree[2]
 
604
            tree = tree[1]
 
605
    
 
606
    def last_line_of_tree(self, tree):
 
607
        while True:
 
608
            if len(tree) == 3 and type(tree[2]) == type(1):
 
609
                return tree[2]
 
610
            tree = tree[-1]
 
611
    
 
612
    def find_docstring_pass_pair(self, tree, spots):
 
613
        for i in range(1, len(tree)):
 
614
            if self.is_string_constant(tree[i]) and self.is_pass_stmt(tree[i+1]):
 
615
                first_line = self.first_line_of_tree(tree[i])
 
616
                last_line = self.last_line_of_tree(tree[i+1])
 
617
                self.record_multiline(spots, first_line, last_line)
 
618
        
 
619
    def is_string_constant(self, tree):
 
620
        try:
 
621
            return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.expr_stmt
 
622
        except:
 
623
            return False
 
624
        
 
625
    def is_pass_stmt(self, tree):
 
626
        try:
 
627
            return tree[0] == symbol.stmt and tree[1][1][1][0] == symbol.pass_stmt
 
628
        except:
 
629
            return False
 
630
 
 
631
    def record_multiline(self, spots, i, j):
 
632
        for l in range(i, j+1):
 
633
            spots[l] = (i, j)
 
634
            
555
635
    def get_suite_spots(self, tree, spots):
556
 
        import symbol, token
 
636
        """ Analyze a parse tree to find suite introducers which span a number
 
637
            of lines.
 
638
        """
557
639
        for i in range(1, len(tree)):
558
640
            if type(tree[i]) == type(()):
559
641
                if tree[i][0] == symbol.suite:
561
643
                    lineno_colon = lineno_word = None
562
644
                    for j in range(i-1, 0, -1):
563
645
                        if tree[j][0] == token.COLON:
564
 
                            lineno_colon = tree[j][2]
 
646
                            # Colons are never executed themselves: we want the
 
647
                            # line number of the last token before the colon.
 
648
                            lineno_colon = self.last_line_of_tree(tree[j-1])
565
649
                        elif tree[j][0] == token.NAME:
566
650
                            if tree[j][1] == 'elif':
567
651
                                # Find the line number of the first non-terminal
583
667
                    if lineno_colon and lineno_word:
584
668
                        # Found colon and keyword, mark all the lines
585
669
                        # 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)
 
670
                        self.record_multiline(spots, lineno_word, lineno_colon)
 
671
 
 
672
                    # "pass" statements are tricky: different versions of Python
 
673
                    # treat them differently, especially in the common case of a
 
674
                    # function with a doc string and a single pass statement.
 
675
                    self.find_docstring_pass_pair(tree[i], spots)
 
676
                    
 
677
                elif tree[i][0] == symbol.simple_stmt:
 
678
                    first_line = self.first_line_of_tree(tree[i])
 
679
                    last_line = self.last_line_of_tree(tree[i])
 
680
                    if first_line != last_line:
 
681
                        self.record_multiline(spots, first_line, last_line)
588
682
                self.get_suite_spots(tree[i], spots)
589
683
 
590
684
    def find_executable_statements(self, text, exclude=None):
598
692
                if reExclude.search(lines[i]):
599
693
                    excluded[i+1] = 1
600
694
 
 
695
        # Parse the code and analyze the parse tree to find out which statements
 
696
        # are multiline, and where suites begin and end.
601
697
        import parser
602
698
        tree = parser.suite(text+'\n\n').totuple(1)
603
699
        self.get_suite_spots(tree, suite_spots)
604
 
            
 
700
        #print "Suite spots:", suite_spots
 
701
        
605
702
        # Use the compiler module to parse the text and find the executable
606
703
        # statements.  We add newlines to be impervious to final partial lines.
607
704
        statements = {}
613
710
        lines.sort()
614
711
        excluded_lines = excluded.keys()
615
712
        excluded_lines.sort()
616
 
        return lines, excluded_lines
 
713
        return lines, excluded_lines, suite_spots
617
714
 
618
715
    # format_lines(statements, lines).  Format a list of line numbers
619
716
    # for printing by coalescing groups of lines as long as the lines
646
743
                return "%d" % start
647
744
            else:
648
745
                return "%d-%d" % (start, end)
649
 
        return string.join(map(stringify, pairs), ", ")
 
746
        ret = string.join(map(stringify, pairs), ", ")
 
747
        return ret
650
748
 
651
749
    # Backward compatibility with version 1.
652
750
    def analysis(self, morf):
654
752
        return f, s, m, mf
655
753
 
656
754
    def analysis2(self, morf):
657
 
        filename, statements, excluded = self.analyze_morf(morf)
 
755
        filename, statements, excluded, line_map = self.analyze_morf(morf)
658
756
        self.canonicalize_filenames()
659
757
        if not self.cexecuted.has_key(filename):
660
758
            self.cexecuted[filename] = {}
661
759
        missing = []
662
760
        for line in statements:
663
 
            if not self.cexecuted[filename].has_key(line):
 
761
            lines = line_map.get(line, [line, line])
 
762
            for l in range(lines[0], lines[1]+1):
 
763
                if self.cexecuted[filename].has_key(l):
 
764
                    break
 
765
            else:
664
766
                missing.append(line)
665
767
        return (filename, statements, excluded, missing,
666
768
                self.format_lines(statements, missing))
698
800
    def report(self, morfs, show_missing=1, ignore_errors=0, file=None, omit_prefixes=[]):
699
801
        if not isinstance(morfs, types.ListType):
700
802
            morfs = [morfs]
 
803
        # On windows, the shell doesn't expand wildcards.  Do it here.
 
804
        globbed = []
 
805
        for morf in morfs:
 
806
            if isinstance(morf, strclass):
 
807
                globbed.extend(glob.glob(morf))
 
808
            else:
 
809
                globbed.append(morf)
 
810
        morfs = globbed
 
811
        
701
812
        morfs = self.filter_by_prefix(morfs, omit_prefixes)
702
813
        morfs.sort(self.morf_name_compare)
703
814
 
735
846
                raise
736
847
            except:
737
848
                if not ignore_errors:
738
 
                    type, msg = sys.exc_info()[0:2]
739
 
                    print >>file, fmt_err % (name, type, msg)
 
849
                    typ, msg = sys.exc_info()[:2]
 
850
                    print >>file, fmt_err % (name, typ, msg)
740
851
        if len(morfs) > 1:
741
852
            print >>file, "-" * len(header)
742
853
            if total_statements > 0:
816
927
the_coverage = coverage()
817
928
 
818
929
# 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)
 
930
def use_cache(*args, **kw): 
 
931
    return the_coverage.use_cache(*args, **kw)
 
932
 
 
933
def start(*args, **kw): 
 
934
    return the_coverage.start(*args, **kw)
 
935
 
 
936
def stop(*args, **kw): 
 
937
    return the_coverage.stop(*args, **kw)
 
938
 
 
939
def erase(*args, **kw): 
 
940
    return the_coverage.erase(*args, **kw)
 
941
 
 
942
def begin_recursive(*args, **kw): 
 
943
    return the_coverage.begin_recursive(*args, **kw)
 
944
 
 
945
def end_recursive(*args, **kw): 
 
946
    return the_coverage.end_recursive(*args, **kw)
 
947
 
 
948
def exclude(*args, **kw): 
 
949
    return the_coverage.exclude(*args, **kw)
 
950
 
 
951
def analysis(*args, **kw): 
 
952
    return the_coverage.analysis(*args, **kw)
 
953
 
 
954
def analysis2(*args, **kw): 
 
955
    return the_coverage.analysis2(*args, **kw)
 
956
 
 
957
def report(*args, **kw): 
 
958
    return the_coverage.report(*args, **kw)
 
959
 
 
960
def annotate(*args, **kw): 
 
961
    return the_coverage.annotate(*args, **kw)
 
962
 
 
963
def annotate_file(*args, **kw): 
 
964
    return the_coverage.annotate_file(*args, **kw)
831
965
 
832
966
# Save coverage data when Python exits.  (The atexit module wasn't
833
967
# introduced until Python 2.0, so use sys.exitfunc when it's not
838
972
except ImportError:
839
973
    sys.exitfunc = the_coverage.save
840
974
 
 
975
def main():
 
976
    the_coverage.command_line(sys.argv[1:])
 
977
    
841
978
# Command-line interface.
842
979
if __name__ == '__main__':
843
 
    the_coverage.command_line(sys.argv[1:])
 
980
    main()
844
981
 
845
982
 
846
983
# A. REFERENCES
918
1055
#
919
1056
# 2006-08-23 NMB Refactorings to improve testability.  Fixes to command-line
920
1057
# logic for parallel mode and collect.
921
 
 
 
1058
#
 
1059
# 2006-08-25 NMB "#pragma: nocover" is excluded by default.
 
1060
#
 
1061
# 2006-09-10 NMB Properly ignore docstrings and other constant expressions that
 
1062
# appear in the middle of a function, a problem reported by Tim Leslie.
 
1063
# Minor changes to avoid lint warnings.
 
1064
#
 
1065
# 2006-09-17 NMB coverage.erase() shouldn't clobber the exclude regex.
 
1066
# Change how parallel mode is invoked, and fix erase() so that it erases the
 
1067
# cache when called programmatically.
 
1068
#
 
1069
# 2007-07-21 NMB In reports, ignore code executed from strings, since we can't
 
1070
# do anything useful with it anyway.
 
1071
# Better file handling on Linux, thanks Guillaume Chazarain.
 
1072
# Better shell support on Windows, thanks Noel O'Boyle.
 
1073
# Python 2.2 support maintained, thanks Catherine Proulx.
 
1074
#
 
1075
# 2007-07-22 NMB Python 2.5 now fully supported. The method of dealing with
 
1076
# multi-line statements is now less sensitive to the exact line that Python
 
1077
# reports during execution. Pass statements are handled specially so that their
 
1078
# disappearance during execution won't throw off the measurement.
 
1079
#
 
1080
# 2007-07-23 NMB Now Python 2.5 is *really* fully supported: the body of the
 
1081
# new with statement is counted as executable.
 
1082
#
 
1083
# 2007-07-29 NMB Better packaging.
 
1084
#
 
1085
# 2007-09-30 NMB Don't try to predict whether a file is Python source based on
 
1086
# the extension. Extensionless files are often Pythons scripts. Instead, simply
 
1087
# parse the file and catch the syntax errors.  Hat tip to Ben Finney.
 
1088
#
 
1089
# 2008-05-25 NMB Open files in rU mode to avoid line ending craziness.
 
1090
# Thanks, Edward Loper.
 
1091
#
922
1092
# C. COPYRIGHT AND LICENCE
923
1093
#
924
1094
# Copyright 2001 Gareth Rees.  All rights reserved.
925
 
# Copyright 2004-2006 Ned Batchelder.  All rights reserved.
 
1095
# Copyright 2004-2008 Ned Batchelder.  All rights reserved.
926
1096
#
927
1097
# Redistribution and use in source and binary forms, with or without
928
1098
# modification, are permitted provided that the following conditions are
949
1119
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
950
1120
# DAMAGE.
951
1121
#
952
 
# $Id: coverage.py 47 2006-08-24 01:08:48Z Ned $
 
1122
# $Id: coverage.py 93 2008-05-25 21:32:09Z nedbat $