ยปCore Development>Code coverage>Lib/trace.py

Python code coverage for Lib/trace.py

#countcontent
1n/a#!/usr/bin/env python3
2n/a
3n/a# portions copyright 2001, Autonomous Zones Industries, Inc., all rights...
4n/a# err... reserved and offered to the public under the terms of the
5n/a# Python 2.2 license.
6n/a# Author: Zooko O'Whielacronx
7n/a# http://zooko.com/
8n/a# mailto:zooko@zooko.com
9n/a#
10n/a# Copyright 2000, Mojam Media, Inc., all rights reserved.
11n/a# Author: Skip Montanaro
12n/a#
13n/a# Copyright 1999, Bioreason, Inc., all rights reserved.
14n/a# Author: Andrew Dalke
15n/a#
16n/a# Copyright 1995-1997, Automatrix, Inc., all rights reserved.
17n/a# Author: Skip Montanaro
18n/a#
19n/a# Copyright 1991-1995, Stichting Mathematisch Centrum, all rights reserved.
20n/a#
21n/a#
22n/a# Permission to use, copy, modify, and distribute this Python software and
23n/a# its associated documentation for any purpose without fee is hereby
24n/a# granted, provided that the above copyright notice appears in all copies,
25n/a# and that both that copyright notice and this permission notice appear in
26n/a# supporting documentation, and that the name of neither Automatrix,
27n/a# Bioreason or Mojam Media be used in advertising or publicity pertaining to
28n/a# distribution of the software without specific, written prior permission.
29n/a#
30n/a"""program/module to trace Python program or function execution
31n/a
32n/aSample use, command line:
33n/a trace.py -c -f counts --ignore-dir '$prefix' spam.py eggs
34n/a trace.py -t --ignore-dir '$prefix' spam.py eggs
35n/a trace.py --trackcalls spam.py eggs
36n/a
37n/aSample use, programmatically
38n/a import sys
39n/a
40n/a # create a Trace object, telling it what to ignore, and whether to
41n/a # do tracing or line-counting or both.
42n/a tracer = trace.Trace(ignoredirs=[sys.base_prefix, sys.base_exec_prefix,],
43n/a trace=0, count=1)
44n/a # run the new command using the given tracer
45n/a tracer.run('main()')
46n/a # make a report, placing output in /tmp
47n/a r = tracer.results()
48n/a r.write_results(show_missing=True, coverdir="/tmp")
49n/a"""
50n/a__all__ = ['Trace', 'CoverageResults']
51n/aimport argparse
52n/aimport linecache
53n/aimport os
54n/aimport re
55n/aimport sys
56n/aimport token
57n/aimport tokenize
58n/aimport inspect
59n/aimport gc
60n/aimport dis
61n/aimport pickle
62n/afrom time import monotonic as _time
63n/a
64n/atry:
65n/a import threading
66n/aexcept ImportError:
67n/a _settrace = sys.settrace
68n/a
69n/a def _unsettrace():
70n/a sys.settrace(None)
71n/aelse:
72n/a def _settrace(func):
73n/a threading.settrace(func)
74n/a sys.settrace(func)
75n/a
76n/a def _unsettrace():
77n/a sys.settrace(None)
78n/a threading.settrace(None)
79n/a
80n/aPRAGMA_NOCOVER = "#pragma NO COVER"
81n/a
82n/a# Simple rx to find lines with no code.
83n/arx_blank = re.compile(r'^\s*(#.*)?$')
84n/a
85n/aclass _Ignore:
86n/a def __init__(self, modules=None, dirs=None):
87n/a self._mods = set() if not modules else set(modules)
88n/a self._dirs = [] if not dirs else [os.path.normpath(d)
89n/a for d in dirs]
90n/a self._ignore = { '<string>': 1 }
91n/a
92n/a def names(self, filename, modulename):
93n/a if modulename in self._ignore:
94n/a return self._ignore[modulename]
95n/a
96n/a # haven't seen this one before, so see if the module name is
97n/a # on the ignore list.
98n/a if modulename in self._mods: # Identical names, so ignore
99n/a self._ignore[modulename] = 1
100n/a return 1
101n/a
102n/a # check if the module is a proper submodule of something on
103n/a # the ignore list
104n/a for mod in self._mods:
105n/a # Need to take some care since ignoring
106n/a # "cmp" mustn't mean ignoring "cmpcache" but ignoring
107n/a # "Spam" must also mean ignoring "Spam.Eggs".
108n/a if modulename.startswith(mod + '.'):
109n/a self._ignore[modulename] = 1
110n/a return 1
111n/a
112n/a # Now check that filename isn't in one of the directories
113n/a if filename is None:
114n/a # must be a built-in, so we must ignore
115n/a self._ignore[modulename] = 1
116n/a return 1
117n/a
118n/a # Ignore a file when it contains one of the ignorable paths
119n/a for d in self._dirs:
120n/a # The '+ os.sep' is to ensure that d is a parent directory,
121n/a # as compared to cases like:
122n/a # d = "/usr/local"
123n/a # filename = "/usr/local.py"
124n/a # or
125n/a # d = "/usr/local.py"
126n/a # filename = "/usr/local.py"
127n/a if filename.startswith(d + os.sep):
128n/a self._ignore[modulename] = 1
129n/a return 1
130n/a
131n/a # Tried the different ways, so we don't ignore this module
132n/a self._ignore[modulename] = 0
133n/a return 0
134n/a
135n/adef _modname(path):
136n/a """Return a plausible module name for the patch."""
137n/a
138n/a base = os.path.basename(path)
139n/a filename, ext = os.path.splitext(base)
140n/a return filename
141n/a
142n/adef _fullmodname(path):
143n/a """Return a plausible module name for the path."""
144n/a
145n/a # If the file 'path' is part of a package, then the filename isn't
146n/a # enough to uniquely identify it. Try to do the right thing by
147n/a # looking in sys.path for the longest matching prefix. We'll
148n/a # assume that the rest is the package name.
149n/a
150n/a comparepath = os.path.normcase(path)
151n/a longest = ""
152n/a for dir in sys.path:
153n/a dir = os.path.normcase(dir)
154n/a if comparepath.startswith(dir) and comparepath[len(dir)] == os.sep:
155n/a if len(dir) > len(longest):
156n/a longest = dir
157n/a
158n/a if longest:
159n/a base = path[len(longest) + 1:]
160n/a else:
161n/a base = path
162n/a # the drive letter is never part of the module name
163n/a drive, base = os.path.splitdrive(base)
164n/a base = base.replace(os.sep, ".")
165n/a if os.altsep:
166n/a base = base.replace(os.altsep, ".")
167n/a filename, ext = os.path.splitext(base)
168n/a return filename.lstrip(".")
169n/a
170n/aclass CoverageResults:
171n/a def __init__(self, counts=None, calledfuncs=None, infile=None,
172n/a callers=None, outfile=None):
173n/a self.counts = counts
174n/a if self.counts is None:
175n/a self.counts = {}
176n/a self.counter = self.counts.copy() # map (filename, lineno) to count
177n/a self.calledfuncs = calledfuncs
178n/a if self.calledfuncs is None:
179n/a self.calledfuncs = {}
180n/a self.calledfuncs = self.calledfuncs.copy()
181n/a self.callers = callers
182n/a if self.callers is None:
183n/a self.callers = {}
184n/a self.callers = self.callers.copy()
185n/a self.infile = infile
186n/a self.outfile = outfile
187n/a if self.infile:
188n/a # Try to merge existing counts file.
189n/a try:
190n/a with open(self.infile, 'rb') as f:
191n/a counts, calledfuncs, callers = pickle.load(f)
192n/a self.update(self.__class__(counts, calledfuncs, callers))
193n/a except (OSError, EOFError, ValueError) as err:
194n/a print(("Skipping counts file %r: %s"
195n/a % (self.infile, err)), file=sys.stderr)
196n/a
197n/a def is_ignored_filename(self, filename):
198n/a """Return True if the filename does not refer to a file
199n/a we want to have reported.
200n/a """
201n/a return filename.startswith('<') and filename.endswith('>')
202n/a
203n/a def update(self, other):
204n/a """Merge in the data from another CoverageResults"""
205n/a counts = self.counts
206n/a calledfuncs = self.calledfuncs
207n/a callers = self.callers
208n/a other_counts = other.counts
209n/a other_calledfuncs = other.calledfuncs
210n/a other_callers = other.callers
211n/a
212n/a for key in other_counts:
213n/a counts[key] = counts.get(key, 0) + other_counts[key]
214n/a
215n/a for key in other_calledfuncs:
216n/a calledfuncs[key] = 1
217n/a
218n/a for key in other_callers:
219n/a callers[key] = 1
220n/a
221n/a def write_results(self, show_missing=True, summary=False, coverdir=None):
222n/a """
223n/a Write the coverage results.
224n/a
225n/a :param show_missing: Show lines that had no hits.
226n/a :param summary: Include coverage summary per module.
227n/a :param coverdir: If None, the results of each module are placed in its
228n/a directory, otherwise it is included in the directory
229n/a specified.
230n/a """
231n/a if self.calledfuncs:
232n/a print()
233n/a print("functions called:")
234n/a calls = self.calledfuncs
235n/a for filename, modulename, funcname in sorted(calls):
236n/a print(("filename: %s, modulename: %s, funcname: %s"
237n/a % (filename, modulename, funcname)))
238n/a
239n/a if self.callers:
240n/a print()
241n/a print("calling relationships:")
242n/a lastfile = lastcfile = ""
243n/a for ((pfile, pmod, pfunc), (cfile, cmod, cfunc)) \
244n/a in sorted(self.callers):
245n/a if pfile != lastfile:
246n/a print()
247n/a print("***", pfile, "***")
248n/a lastfile = pfile
249n/a lastcfile = ""
250n/a if cfile != pfile and lastcfile != cfile:
251n/a print(" -->", cfile)
252n/a lastcfile = cfile
253n/a print(" %s.%s -> %s.%s" % (pmod, pfunc, cmod, cfunc))
254n/a
255n/a # turn the counts data ("(filename, lineno) = count") into something
256n/a # accessible on a per-file basis
257n/a per_file = {}
258n/a for filename, lineno in self.counts:
259n/a lines_hit = per_file[filename] = per_file.get(filename, {})
260n/a lines_hit[lineno] = self.counts[(filename, lineno)]
261n/a
262n/a # accumulate summary info, if needed
263n/a sums = {}
264n/a
265n/a for filename, count in per_file.items():
266n/a if self.is_ignored_filename(filename):
267n/a continue
268n/a
269n/a if filename.endswith(".pyc"):
270n/a filename = filename[:-1]
271n/a
272n/a if coverdir is None:
273n/a dir = os.path.dirname(os.path.abspath(filename))
274n/a modulename = _modname(filename)
275n/a else:
276n/a dir = coverdir
277n/a if not os.path.exists(dir):
278n/a os.makedirs(dir)
279n/a modulename = _fullmodname(filename)
280n/a
281n/a # If desired, get a list of the line numbers which represent
282n/a # executable content (returned as a dict for better lookup speed)
283n/a if show_missing:
284n/a lnotab = _find_executable_linenos(filename)
285n/a else:
286n/a lnotab = {}
287n/a if lnotab:
288n/a source = linecache.getlines(filename)
289n/a coverpath = os.path.join(dir, modulename + ".cover")
290n/a with open(filename, 'rb') as fp:
291n/a encoding, _ = tokenize.detect_encoding(fp.readline)
292n/a n_hits, n_lines = self.write_results_file(coverpath, source,
293n/a lnotab, count, encoding)
294n/a if summary and n_lines:
295n/a percent = int(100 * n_hits / n_lines)
296n/a sums[modulename] = n_lines, percent, modulename, filename
297n/a
298n/a
299n/a if summary and sums:
300n/a print("lines cov% module (path)")
301n/a for m in sorted(sums):
302n/a n_lines, percent, modulename, filename = sums[m]
303n/a print("%5d %3d%% %s (%s)" % sums[m])
304n/a
305n/a if self.outfile:
306n/a # try and store counts and module info into self.outfile
307n/a try:
308n/a pickle.dump((self.counts, self.calledfuncs, self.callers),
309n/a open(self.outfile, 'wb'), 1)
310n/a except OSError as err:
311n/a print("Can't save counts files because %s" % err, file=sys.stderr)
312n/a
313n/a def write_results_file(self, path, lines, lnotab, lines_hit, encoding=None):
314n/a """Return a coverage results file in path."""
315n/a
316n/a try:
317n/a outfile = open(path, "w", encoding=encoding)
318n/a except OSError as err:
319n/a print(("trace: Could not open %r for writing: %s"
320n/a "- skipping" % (path, err)), file=sys.stderr)
321n/a return 0, 0
322n/a
323n/a n_lines = 0
324n/a n_hits = 0
325n/a with outfile:
326n/a for lineno, line in enumerate(lines, 1):
327n/a # do the blank/comment match to try to mark more lines
328n/a # (help the reader find stuff that hasn't been covered)
329n/a if lineno in lines_hit:
330n/a outfile.write("%5d: " % lines_hit[lineno])
331n/a n_hits += 1
332n/a n_lines += 1
333n/a elif rx_blank.match(line):
334n/a outfile.write(" ")
335n/a else:
336n/a # lines preceded by no marks weren't hit
337n/a # Highlight them if so indicated, unless the line contains
338n/a # #pragma: NO COVER
339n/a if lineno in lnotab and not PRAGMA_NOCOVER in line:
340n/a outfile.write(">>>>>> ")
341n/a n_lines += 1
342n/a else:
343n/a outfile.write(" ")
344n/a outfile.write(line.expandtabs(8))
345n/a
346n/a return n_hits, n_lines
347n/a
348n/adef _find_lines_from_code(code, strs):
349n/a """Return dict where keys are lines in the line number table."""
350n/a linenos = {}
351n/a
352n/a for _, lineno in dis.findlinestarts(code):
353n/a if lineno not in strs:
354n/a linenos[lineno] = 1
355n/a
356n/a return linenos
357n/a
358n/adef _find_lines(code, strs):
359n/a """Return lineno dict for all code objects reachable from code."""
360n/a # get all of the lineno information from the code of this scope level
361n/a linenos = _find_lines_from_code(code, strs)
362n/a
363n/a # and check the constants for references to other code objects
364n/a for c in code.co_consts:
365n/a if inspect.iscode(c):
366n/a # find another code object, so recurse into it
367n/a linenos.update(_find_lines(c, strs))
368n/a return linenos
369n/a
370n/adef _find_strings(filename, encoding=None):
371n/a """Return a dict of possible docstring positions.
372n/a
373n/a The dict maps line numbers to strings. There is an entry for
374n/a line that contains only a string or a part of a triple-quoted
375n/a string.
376n/a """
377n/a d = {}
378n/a # If the first token is a string, then it's the module docstring.
379n/a # Add this special case so that the test in the loop passes.
380n/a prev_ttype = token.INDENT
381n/a with open(filename, encoding=encoding) as f:
382n/a tok = tokenize.generate_tokens(f.readline)
383n/a for ttype, tstr, start, end, line in tok:
384n/a if ttype == token.STRING:
385n/a if prev_ttype == token.INDENT:
386n/a sline, scol = start
387n/a eline, ecol = end
388n/a for i in range(sline, eline + 1):
389n/a d[i] = 1
390n/a prev_ttype = ttype
391n/a return d
392n/a
393n/adef _find_executable_linenos(filename):
394n/a """Return dict where keys are line numbers in the line number table."""
395n/a try:
396n/a with tokenize.open(filename) as f:
397n/a prog = f.read()
398n/a encoding = f.encoding
399n/a except OSError as err:
400n/a print(("Not printing coverage data for %r: %s"
401n/a % (filename, err)), file=sys.stderr)
402n/a return {}
403n/a code = compile(prog, filename, "exec")
404n/a strs = _find_strings(filename, encoding)
405n/a return _find_lines(code, strs)
406n/a
407n/aclass Trace:
408n/a def __init__(self, count=1, trace=1, countfuncs=0, countcallers=0,
409n/a ignoremods=(), ignoredirs=(), infile=None, outfile=None,
410n/a timing=False):
411n/a """
412n/a @param count true iff it should count number of times each
413n/a line is executed
414n/a @param trace true iff it should print out each line that is
415n/a being counted
416n/a @param countfuncs true iff it should just output a list of
417n/a (filename, modulename, funcname,) for functions
418n/a that were called at least once; This overrides
419n/a `count' and `trace'
420n/a @param ignoremods a list of the names of modules to ignore
421n/a @param ignoredirs a list of the names of directories to ignore
422n/a all of the (recursive) contents of
423n/a @param infile file from which to read stored counts to be
424n/a added into the results
425n/a @param outfile file in which to write the results
426n/a @param timing true iff timing information be displayed
427n/a """
428n/a self.infile = infile
429n/a self.outfile = outfile
430n/a self.ignore = _Ignore(ignoremods, ignoredirs)
431n/a self.counts = {} # keys are (filename, linenumber)
432n/a self.pathtobasename = {} # for memoizing os.path.basename
433n/a self.donothing = 0
434n/a self.trace = trace
435n/a self._calledfuncs = {}
436n/a self._callers = {}
437n/a self._caller_cache = {}
438n/a self.start_time = None
439n/a if timing:
440n/a self.start_time = _time()
441n/a if countcallers:
442n/a self.globaltrace = self.globaltrace_trackcallers
443n/a elif countfuncs:
444n/a self.globaltrace = self.globaltrace_countfuncs
445n/a elif trace and count:
446n/a self.globaltrace = self.globaltrace_lt
447n/a self.localtrace = self.localtrace_trace_and_count
448n/a elif trace:
449n/a self.globaltrace = self.globaltrace_lt
450n/a self.localtrace = self.localtrace_trace
451n/a elif count:
452n/a self.globaltrace = self.globaltrace_lt
453n/a self.localtrace = self.localtrace_count
454n/a else:
455n/a # Ahem -- do nothing? Okay.
456n/a self.donothing = 1
457n/a
458n/a def run(self, cmd):
459n/a import __main__
460n/a dict = __main__.__dict__
461n/a self.runctx(cmd, dict, dict)
462n/a
463n/a def runctx(self, cmd, globals=None, locals=None):
464n/a if globals is None: globals = {}
465n/a if locals is None: locals = {}
466n/a if not self.donothing:
467n/a _settrace(self.globaltrace)
468n/a try:
469n/a exec(cmd, globals, locals)
470n/a finally:
471n/a if not self.donothing:
472n/a _unsettrace()
473n/a
474n/a def runfunc(self, func, *args, **kw):
475n/a result = None
476n/a if not self.donothing:
477n/a sys.settrace(self.globaltrace)
478n/a try:
479n/a result = func(*args, **kw)
480n/a finally:
481n/a if not self.donothing:
482n/a sys.settrace(None)
483n/a return result
484n/a
485n/a def file_module_function_of(self, frame):
486n/a code = frame.f_code
487n/a filename = code.co_filename
488n/a if filename:
489n/a modulename = _modname(filename)
490n/a else:
491n/a modulename = None
492n/a
493n/a funcname = code.co_name
494n/a clsname = None
495n/a if code in self._caller_cache:
496n/a if self._caller_cache[code] is not None:
497n/a clsname = self._caller_cache[code]
498n/a else:
499n/a self._caller_cache[code] = None
500n/a ## use of gc.get_referrers() was suggested by Michael Hudson
501n/a # all functions which refer to this code object
502n/a funcs = [f for f in gc.get_referrers(code)
503n/a if inspect.isfunction(f)]
504n/a # require len(func) == 1 to avoid ambiguity caused by calls to
505n/a # new.function(): "In the face of ambiguity, refuse the
506n/a # temptation to guess."
507n/a if len(funcs) == 1:
508n/a dicts = [d for d in gc.get_referrers(funcs[0])
509n/a if isinstance(d, dict)]
510n/a if len(dicts) == 1:
511n/a classes = [c for c in gc.get_referrers(dicts[0])
512n/a if hasattr(c, "__bases__")]
513n/a if len(classes) == 1:
514n/a # ditto for new.classobj()
515n/a clsname = classes[0].__name__
516n/a # cache the result - assumption is that new.* is
517n/a # not called later to disturb this relationship
518n/a # _caller_cache could be flushed if functions in
519n/a # the new module get called.
520n/a self._caller_cache[code] = clsname
521n/a if clsname is not None:
522n/a funcname = "%s.%s" % (clsname, funcname)
523n/a
524n/a return filename, modulename, funcname
525n/a
526n/a def globaltrace_trackcallers(self, frame, why, arg):
527n/a """Handler for call events.
528n/a
529n/a Adds information about who called who to the self._callers dict.
530n/a """
531n/a if why == 'call':
532n/a # XXX Should do a better job of identifying methods
533n/a this_func = self.file_module_function_of(frame)
534n/a parent_func = self.file_module_function_of(frame.f_back)
535n/a self._callers[(parent_func, this_func)] = 1
536n/a
537n/a def globaltrace_countfuncs(self, frame, why, arg):
538n/a """Handler for call events.
539n/a
540n/a Adds (filename, modulename, funcname) to the self._calledfuncs dict.
541n/a """
542n/a if why == 'call':
543n/a this_func = self.file_module_function_of(frame)
544n/a self._calledfuncs[this_func] = 1
545n/a
546n/a def globaltrace_lt(self, frame, why, arg):
547n/a """Handler for call events.
548n/a
549n/a If the code block being entered is to be ignored, returns `None',
550n/a else returns self.localtrace.
551n/a """
552n/a if why == 'call':
553n/a code = frame.f_code
554n/a filename = frame.f_globals.get('__file__', None)
555n/a if filename:
556n/a # XXX _modname() doesn't work right for packages, so
557n/a # the ignore support won't work right for packages
558n/a modulename = _modname(filename)
559n/a if modulename is not None:
560n/a ignore_it = self.ignore.names(filename, modulename)
561n/a if not ignore_it:
562n/a if self.trace:
563n/a print((" --- modulename: %s, funcname: %s"
564n/a % (modulename, code.co_name)))
565n/a return self.localtrace
566n/a else:
567n/a return None
568n/a
569n/a def localtrace_trace_and_count(self, frame, why, arg):
570n/a if why == "line":
571n/a # record the file name and line number of every trace
572n/a filename = frame.f_code.co_filename
573n/a lineno = frame.f_lineno
574n/a key = filename, lineno
575n/a self.counts[key] = self.counts.get(key, 0) + 1
576n/a
577n/a if self.start_time:
578n/a print('%.2f' % (_time() - self.start_time), end=' ')
579n/a bname = os.path.basename(filename)
580n/a print("%s(%d): %s" % (bname, lineno,
581n/a linecache.getline(filename, lineno)), end='')
582n/a return self.localtrace
583n/a
584n/a def localtrace_trace(self, frame, why, arg):
585n/a if why == "line":
586n/a # record the file name and line number of every trace
587n/a filename = frame.f_code.co_filename
588n/a lineno = frame.f_lineno
589n/a
590n/a if self.start_time:
591n/a print('%.2f' % (_time() - self.start_time), end=' ')
592n/a bname = os.path.basename(filename)
593n/a print("%s(%d): %s" % (bname, lineno,
594n/a linecache.getline(filename, lineno)), end='')
595n/a return self.localtrace
596n/a
597n/a def localtrace_count(self, frame, why, arg):
598n/a if why == "line":
599n/a filename = frame.f_code.co_filename
600n/a lineno = frame.f_lineno
601n/a key = filename, lineno
602n/a self.counts[key] = self.counts.get(key, 0) + 1
603n/a return self.localtrace
604n/a
605n/a def results(self):
606n/a return CoverageResults(self.counts, infile=self.infile,
607n/a outfile=self.outfile,
608n/a calledfuncs=self._calledfuncs,
609n/a callers=self._callers)
610n/a
611n/adef main():
612n/a
613n/a parser = argparse.ArgumentParser()
614n/a parser.add_argument('--version', action='version', version='trace 2.0')
615n/a
616n/a grp = parser.add_argument_group('Main options',
617n/a 'One of these (or --report) must be given')
618n/a
619n/a grp.add_argument('-c', '--count', action='store_true',
620n/a help='Count the number of times each line is executed and write '
621n/a 'the counts to <module>.cover for each module executed, in '
622n/a 'the module\'s directory. See also --coverdir, --file, '
623n/a '--no-report below.')
624n/a grp.add_argument('-t', '--trace', action='store_true',
625n/a help='Print each line to sys.stdout before it is executed')
626n/a grp.add_argument('-l', '--listfuncs', action='store_true',
627n/a help='Keep track of which functions are executed at least once '
628n/a 'and write the results to sys.stdout after the program exits. '
629n/a 'Cannot be specified alongside --trace or --count.')
630n/a grp.add_argument('-T', '--trackcalls', action='store_true',
631n/a help='Keep track of caller/called pairs and write the results to '
632n/a 'sys.stdout after the program exits.')
633n/a
634n/a grp = parser.add_argument_group('Modifiers')
635n/a
636n/a _grp = grp.add_mutually_exclusive_group()
637n/a _grp.add_argument('-r', '--report', action='store_true',
638n/a help='Generate a report from a counts file; does not execute any '
639n/a 'code. --file must specify the results file to read, which '
640n/a 'must have been created in a previous run with --count '
641n/a '--file=FILE')
642n/a _grp.add_argument('-R', '--no-report', action='store_true',
643n/a help='Do not generate the coverage report files. '
644n/a 'Useful if you want to accumulate over several runs.')
645n/a
646n/a grp.add_argument('-f', '--file',
647n/a help='File to accumulate counts over several runs')
648n/a grp.add_argument('-C', '--coverdir',
649n/a help='Directory where the report files go. The coverage report '
650n/a 'for <package>.<module> will be written to file '
651n/a '<dir>/<package>/<module>.cover')
652n/a grp.add_argument('-m', '--missing', action='store_true',
653n/a help='Annotate executable lines that were not executed with '
654n/a '">>>>>> "')
655n/a grp.add_argument('-s', '--summary', action='store_true',
656n/a help='Write a brief summary for each file to sys.stdout. '
657n/a 'Can only be used with --count or --report')
658n/a grp.add_argument('-g', '--timing', action='store_true',
659n/a help='Prefix each line with the time since the program started. '
660n/a 'Only used while tracing')
661n/a
662n/a grp = parser.add_argument_group('Filters',
663n/a 'Can be specified multiple times')
664n/a grp.add_argument('--ignore-module', action='append', default=[],
665n/a help='Ignore the given module(s) and its submodules'
666n/a '(if it is a package). Accepts comma separated list of '
667n/a 'module names.')
668n/a grp.add_argument('--ignore-dir', action='append', default=[],
669n/a help='Ignore files in the given directory '
670n/a '(multiple directories can be joined by os.pathsep).')
671n/a
672n/a parser.add_argument('filename', nargs='?',
673n/a help='file to run as main program')
674n/a parser.add_argument('arguments', nargs=argparse.REMAINDER,
675n/a help='arguments to the program')
676n/a
677n/a opts = parser.parse_args()
678n/a
679n/a if opts.ignore_dir:
680n/a rel_path = 'lib', 'python{0.major}.{0.minor}'.format(sys.version_info)
681n/a _prefix = os.path.join(sys.base_prefix, *rel_path)
682n/a _exec_prefix = os.path.join(sys.base_exec_prefix, *rel_path)
683n/a
684n/a def parse_ignore_dir(s):
685n/a s = os.path.expanduser(os.path.expandvars(s))
686n/a s = s.replace('$prefix', _prefix).replace('$exec_prefix', _exec_prefix)
687n/a return os.path.normpath(s)
688n/a
689n/a opts.ignore_module = [mod.strip()
690n/a for i in opts.ignore_module for mod in i.split(',')]
691n/a opts.ignore_dir = [parse_ignore_dir(s)
692n/a for i in opts.ignore_dir for s in i.split(os.pathsep)]
693n/a
694n/a if opts.report:
695n/a if not opts.file:
696n/a parser.error('-r/--report requires -f/--file')
697n/a results = CoverageResults(infile=opts.file, outfile=opts.file)
698n/a return results.write_results(opts.missing, opts.summary, opts.coverdir)
699n/a
700n/a if not any([opts.trace, opts.count, opts.listfuncs, opts.trackcalls]):
701n/a parser.error('must specify one of --trace, --count, --report, '
702n/a '--listfuncs, or --trackcalls')
703n/a
704n/a if opts.listfuncs and (opts.count or opts.trace):
705n/a parser.error('cannot specify both --listfuncs and (--trace or --count)')
706n/a
707n/a if opts.summary and not opts.count:
708n/a parser.error('--summary can only be used with --count or --report')
709n/a
710n/a if opts.filename is None:
711n/a parser.error('filename is missing: required with the main options')
712n/a
713n/a sys.argv = opts.filename, *opts.arguments
714n/a sys.path[0] = os.path.dirname(opts.filename)
715n/a
716n/a t = Trace(opts.count, opts.trace, countfuncs=opts.listfuncs,
717n/a countcallers=opts.trackcalls, ignoremods=opts.ignore_module,
718n/a ignoredirs=opts.ignore_dir, infile=opts.file,
719n/a outfile=opts.file, timing=opts.timing)
720n/a try:
721n/a with open(opts.filename) as fp:
722n/a code = compile(fp.read(), opts.filename, 'exec')
723n/a # try to emulate __main__ namespace as much as possible
724n/a globs = {
725n/a '__file__': opts.filename,
726n/a '__name__': '__main__',
727n/a '__package__': None,
728n/a '__cached__': None,
729n/a }
730n/a t.runctx(code, globs, globs)
731n/a except OSError as err:
732n/a sys.exit("Cannot run file %r because: %s" % (sys.argv[0], err))
733n/a except SystemExit:
734n/a pass
735n/a
736n/a results = t.results()
737n/a
738n/a if not opts.no_report:
739n/a results.write_results(opts.missing, opts.summary, opts.coverdir)
740n/a
741n/aif __name__=='__main__':
742n/a main()