source: TI02-CSML/trunk/services/3rdParty/OWSLib-0.2.0/tests/coverage.py @ 2194

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI02-CSML/trunk/services/3rdParty/OWSLib-0.2.0/tests/coverage.py@2194
Revision 2194, 29.7 KB checked in by lawrence, 13 years ago (diff)

Adding various specs and 3rd party code of interest for the CSML
services development.

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