summaryrefslogtreecommitdiff
path: root/coverage
diff options
context:
space:
mode:
Diffstat (limited to 'coverage')
-rw-r--r--coverage/__init__.py2
-rw-r--r--coverage/cmdline.py21
-rw-r--r--coverage/collector.py9
-rw-r--r--coverage/config.py4
-rw-r--r--coverage/control.py106
-rw-r--r--coverage/data.py10
-rw-r--r--coverage/debug.py54
-rw-r--r--coverage/files.py8
-rw-r--r--coverage/html.py29
-rw-r--r--coverage/parser.py5
-rw-r--r--coverage/version.py2
11 files changed, 184 insertions, 66 deletions
diff --git a/coverage/__init__.py b/coverage/__init__.py
index 0ccc699f..193b7a10 100644
--- a/coverage/__init__.py
+++ b/coverage/__init__.py
@@ -92,7 +92,7 @@ except KeyError:
# COPYRIGHT AND LICENSE
#
# Copyright 2001 Gareth Rees. All rights reserved.
-# Copyright 2004-2012 Ned Batchelder. All rights reserved.
+# Copyright 2004-2013 Ned Batchelder. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index ac803109..0881313e 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -5,6 +5,7 @@ import optparse, os, sys, traceback
from coverage.backward import sorted # pylint: disable=W0622
from coverage.execfile import run_python_file, run_python_module
from coverage.misc import CoverageException, ExceptionDuringRun, NoSource
+from coverage.debug import info_formatter
class Opts(object):
@@ -19,6 +20,10 @@ class Opts(object):
'', '--branch', action='store_true',
help="Measure branch coverage in addition to statement coverage."
)
+ debug = optparse.make_option(
+ '', '--debug', action='store', metavar="OPTS",
+ help="Debug options, separated by commas"
+ )
directory = optparse.make_option(
'-d', '--directory', action='store', metavar="DIR",
help="Write the output files to DIR."
@@ -117,6 +122,7 @@ class CoverageOptionParser(optparse.OptionParser, object):
self.set_defaults(
actions=[],
branch=None,
+ debug=None,
directory=None,
fail_under=None,
help=None,
@@ -310,6 +316,7 @@ CMDS = {
[
Opts.append,
Opts.branch,
+ Opts.debug,
Opts.pylib,
Opts.parallel_mode,
Opts.module,
@@ -404,6 +411,7 @@ class CoverageScript(object):
source = unshell_list(options.source)
omit = unshell_list(options.omit)
include = unshell_list(options.include)
+ debug = unshell_list(options.debug)
# Do something.
self.coverage = self.covpkg.coverage(
@@ -415,6 +423,7 @@ class CoverageScript(object):
source = source,
omit = omit,
include = include,
+ debug = debug,
)
if 'debug' in options.actions:
@@ -584,16 +593,8 @@ class CoverageScript(object):
for info in args:
if info == 'sys':
print("-- sys ----------------------------------------")
- for label, info in self.coverage.sysinfo():
- if info == []:
- info = "-none-"
- if isinstance(info, list):
- prefix = "%15s:" % label
- for e in info:
- print("%16s %s" % (prefix, e))
- prefix = ""
- else:
- print("%15s: %s" % (label, info))
+ for line in info_formatter(self.coverage.sysinfo()):
+ print(" %s" % line)
elif info == 'data':
print("-- data ---------------------------------------")
self.coverage.load()
diff --git a/coverage/collector.py b/coverage/collector.py
index 781a0fa6..9a74700d 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -75,10 +75,11 @@ class PyTracer(object):
# in this file.
self.data_stack.append((self.cur_file_data, self.last_line))
filename = frame.f_code.co_filename
- tracename = self.should_trace_cache.get(filename)
- if tracename is None:
+ if filename not in self.should_trace_cache:
tracename = self.should_trace(filename, frame)
self.should_trace_cache[filename] = tracename
+ else:
+ tracename = self.should_trace_cache[filename]
#print("called, stack is %d deep, tracename is %r" % (
# len(self.data_stack), tracename))
if tracename:
@@ -166,7 +167,7 @@ class Collector(object):
"""Create a collector.
`should_trace` is a function, taking a filename, and returning a
- canonicalized filename, or False depending on whether the file should
+ canonicalized filename, or None depending on whether the file should
be traced or not.
If `timid` is true, then a slower simpler trace function will be
@@ -210,7 +211,7 @@ class Collector(object):
# A cache of the results from should_trace, the decision about whether
# to trace execution in a file. A dict of filename to (filename or
- # False).
+ # None).
self.should_trace_cache = {}
# Our active Tracers.
diff --git a/coverage/config.py b/coverage/config.py
index c2ebecb2..87318ff1 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -111,6 +111,7 @@ class CoverageConfig(object):
self.parallel = False
self.timid = False
self.source = None
+ self.debug = []
# Defaults for [report]
self.exclude_list = DEFAULT_EXCLUDE[:]
@@ -142,7 +143,7 @@ class CoverageConfig(object):
if env:
self.timid = ('--timid' in env)
- MUST_BE_LIST = ["omit", "include"]
+ MUST_BE_LIST = ["omit", "include", "debug"]
def from_args(self, **kwargs):
"""Read config values from `kwargs`."""
@@ -178,6 +179,7 @@ class CoverageConfig(object):
('branch', 'run:branch', 'boolean'),
('cover_pylib', 'run:cover_pylib', 'boolean'),
('data_file', 'run:data_file'),
+ ('debug', 'run:debug', 'list'),
('include', 'run:include', 'list'),
('omit', 'run:omit', 'list'),
('parallel', 'run:parallel', 'boolean'),
diff --git a/coverage/control.py b/coverage/control.py
index 8821bfbb..4b76121c 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -3,11 +3,12 @@
import atexit, os, random, socket, sys
from coverage.annotate import AnnotateReporter
-from coverage.backward import string_class, iitems
+from coverage.backward import string_class, iitems, sorted # pylint: disable=W0622
from coverage.codeunit import code_unit_factory, CodeUnit
from coverage.collector import Collector
from coverage.config import CoverageConfig
from coverage.data import CoverageData
+from coverage.debug import DebugControl
from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher
from coverage.files import PathAliases, find_python_files, prep_patterns
from coverage.html import HtmlReporter
@@ -17,6 +18,14 @@ from coverage.results import Analysis, Numbers
from coverage.summary import SummaryReporter
from coverage.xmlreport import XmlReporter
+# Pypy has some unusual stuff in the "stdlib". Consider those locations
+# when deciding where the stdlib is.
+try:
+ import _structseq # pylint: disable=F0401
+except ImportError:
+ _structseq = None
+
+
class coverage(object):
"""Programmatic access to coverage.py.
@@ -33,7 +42,8 @@ class coverage(object):
"""
def __init__(self, data_file=None, data_suffix=None, cover_pylib=None,
auto_data=False, timid=None, branch=None, config_file=True,
- source=None, omit=None, include=None):
+ source=None, omit=None, include=None, debug=None,
+ debug_file=None):
"""
`data_file` is the base name of the data file to use, defaulting to
".coverage". `data_suffix` is appended (with a dot) to `data_file` to
@@ -68,6 +78,10 @@ class coverage(object):
`include` will be measured, files that match `omit` will not. Each
will also accept a single string argument.
+ `debug` is a list of strings indicating what debugging information is
+ desired. `debug_file` is the file to write debug messages to,
+ defaulting to stderr.
+
"""
from coverage import __version__
@@ -100,9 +114,12 @@ class coverage(object):
self.config.from_args(
data_file=data_file, cover_pylib=cover_pylib, timid=timid,
branch=branch, parallel=bool_or_none(data_suffix),
- source=source, omit=omit, include=include
+ source=source, omit=omit, include=include, debug=debug,
)
+ # Create and configure the debugging controller.
+ self.debug = DebugControl(self.config.debug, debug_file or sys.stderr)
+
self.auto_data = auto_data
# _exclude_re is a dict mapping exclusion list names to compiled
@@ -147,7 +164,8 @@ class coverage(object):
# started rather than wherever the process eventually chdir'd to.
self.data = CoverageData(
basename=self.config.data_file,
- collector="coverage v%s" % __version__
+ collector="coverage v%s" % __version__,
+ debug=self.debug,
)
# The dirs for files considered "installed with the interpreter".
@@ -158,8 +176,8 @@ class coverage(object):
# environments (virtualenv, for example), these modules may be
# spread across a few locations. Look at all the candidate modules
# we've imported, and take all the different ones.
- for m in (atexit, os, random, socket):
- if hasattr(m, "__file__"):
+ for m in (atexit, os, random, socket, _structseq):
+ if m is not None and hasattr(m, "__file__"):
m_dir = self._canonical_dir(m)
if m_dir not in self.pylib_dirs:
self.pylib_dirs.append(m_dir)
@@ -168,7 +186,7 @@ class coverage(object):
# where we are.
self.cover_dir = self._canonical_dir(__file__)
- # The matchers for _should_trace, created when tracing starts.
+ # The matchers for _should_trace.
self.source_match = None
self.pylib_match = self.cover_match = None
self.include_match = self.omit_match = None
@@ -201,26 +219,28 @@ class coverage(object):
filename = filename[:-9] + ".py"
return filename
- def _should_trace(self, filename, frame):
- """Decide whether to trace execution in `filename`
+ def _should_trace_with_reason(self, filename, frame):
+ """Decide whether to trace execution in `filename`, with a reason.
This function is called from the trace function. As each new file name
is encountered, this function determines whether it is traced or not.
- Returns a canonicalized filename if it should be traced, False if it
- should not.
+ Returns a pair of values: the first indicates whether the file should
+ be traced: it's a canonicalized filename if it should be traced, None
+ if it should not. The second value is a string, the resason for the
+ decision.
"""
if not filename:
# Empty string is pretty useless
- return False
+ return None, "empty string isn't a filename"
if filename.startswith('<'):
# Lots of non-file execution is represented with artificial
# filenames like "<string>", "<doctest readme.txt[0]>", or
# "<exec_function>". Don't ever trace these executions, since we
# can't do anything with the data later anyway.
- return False
+ return None, "not a real filename"
self._check_for_packages()
@@ -246,35 +266,41 @@ class coverage(object):
# stdlib and coverage.py directories.
if self.source_match:
if not self.source_match.match(canonical):
- return False
+ return None, "falls outside the --source trees"
elif self.include_match:
if not self.include_match.match(canonical):
- return False
+ return None, "falls outside the --include trees"
else:
# If we aren't supposed to trace installed code, then check if this
# is near the Python standard library and skip it if so.
if self.pylib_match and self.pylib_match.match(canonical):
- return False
+ return None, "is in the stdlib"
# We exclude the coverage code itself, since a little of it will be
# measured otherwise.
if self.cover_match and self.cover_match.match(canonical):
- return False
+ return None, "is part of coverage.py"
# Check the file against the omit pattern.
if self.omit_match and self.omit_match.match(canonical):
- return False
+ return None, "is inside an --omit pattern"
- return canonical
+ return canonical, "because we love you"
+
+ def _should_trace(self, filename, frame):
+ """Decide whether to trace execution in `filename`.
- # To log what should_trace returns, change this to "if 1:"
- if 0:
- _real_should_trace = _should_trace
- def _should_trace(self, filename, frame): # pylint: disable=E0102
- """A logging decorator around the real _should_trace function."""
- ret = self._real_should_trace(filename, frame)
- print("should_trace: %r -> %r" % (filename, ret))
- return ret
+ Calls `_should_trace_with_reason`, and returns just the decision.
+
+ """
+ canonical, reason = self._should_trace_with_reason(filename, frame)
+ if self.debug.should('trace'):
+ if not canonical:
+ msg = "Not tracing %r: %s" % (filename, reason)
+ else:
+ msg = "Tracing %r" % (filename,)
+ self.debug.write(msg)
+ return canonical
def _warn(self, msg):
"""Use `msg` as a warning."""
@@ -364,6 +390,16 @@ class coverage(object):
if self.omit:
self.omit_match = FnmatchMatcher(self.omit)
+ # The user may want to debug things, show info if desired.
+ if self.debug.should('config'):
+ self.debug.write("Configuration values:")
+ config_info = sorted(self.config.__dict__.items())
+ self.debug.write_formatted_info(config_info)
+
+ if self.debug.should('sys'):
+ self.debug.write("Debugging info:")
+ self.debug.write_formatted_info(self.sysinfo())
+
self.collector.start()
self._started = True
self._measured = True
@@ -688,11 +724,23 @@ class coverage(object):
('executable', sys.executable),
('cwd', os.getcwd()),
('path', sys.path),
- ('environment', [
+ ('environment', sorted([
("%s = %s" % (k, v)) for k, v in iitems(os.environ)
if re.search(r"^COV|^PY", k)
- ]),
+ ])),
+ ('command_line', " ".join(getattr(sys, 'argv', ['???']))),
]
+ if self.source_match:
+ info.append(('source_match', self.source_match.info()))
+ if self.include_match:
+ info.append(('include_match', self.include_match.info()))
+ if self.omit_match:
+ info.append(('omit_match', self.omit_match.info()))
+ if self.cover_match:
+ info.append(('cover_match', self.cover_match.info()))
+ if self.pylib_match:
+ info.append(('pylib_match', self.pylib_match.info()))
+
return info
diff --git a/coverage/data.py b/coverage/data.py
index c86a77f2..fb88c5b1 100644
--- a/coverage/data.py
+++ b/coverage/data.py
@@ -23,15 +23,18 @@ class CoverageData(object):
"""
- def __init__(self, basename=None, collector=None):
+ def __init__(self, basename=None, collector=None, debug=None):
"""Create a CoverageData.
`basename` is the name of the file to use for storing data.
`collector` is a string describing the coverage measurement software.
+ `debug` is a `DebugControl` object for writing debug messages.
+
"""
self.collector = collector or 'unknown'
+ self.debug = debug
self.use_file = True
@@ -121,6 +124,9 @@ class CoverageData(object):
if self.collector:
data['collector'] = self.collector
+ if self.debug and self.debug.should('dataio'):
+ self.debug.write("Writing data to %r" % (filename,))
+
# Write the pickle to the file.
fdata = open(filename, 'wb')
try:
@@ -134,6 +140,8 @@ class CoverageData(object):
def raw_data(self, filename):
"""Return the raw pickled data from `filename`."""
+ if self.debug and self.debug.should('dataio'):
+ self.debug.write("Reading data from %r" % (filename,))
fdata = open(filename, 'rb')
try:
data = pickle.load(fdata)
diff --git a/coverage/debug.py b/coverage/debug.py
new file mode 100644
index 00000000..104f3b1d
--- /dev/null
+++ b/coverage/debug.py
@@ -0,0 +1,54 @@
+"""Control of and utilities for debugging."""
+
+import os
+
+
+# When debugging, it can be helpful to force some options, especially when
+# debugging the configuration mechanisms you usually use to control debugging!
+# This is a list of forced debugging options.
+FORCED_DEBUG = []
+
+
+class DebugControl(object):
+ """Control and output for debugging."""
+
+ def __init__(self, options, output):
+ """Configure the options and output file for debugging."""
+ self.options = options
+ self.output = output
+
+ def should(self, option):
+ """Decide whether to output debug information in category `option`."""
+ return (option in self.options or option in FORCED_DEBUG)
+
+ def write(self, msg):
+ """Write a line of debug output."""
+ if self.should('pid'):
+ msg = "pid %5d: %s" % (os.getpid(), msg)
+ self.output.write(msg+"\n")
+ self.output.flush()
+
+ def write_formatted_info(self, info):
+ """Write a sequence of (label,data) pairs nicely."""
+ for line in info_formatter(info):
+ self.write(" %s" % line)
+
+
+def info_formatter(info):
+ """Produce a sequence of formatted lines from info.
+
+ `info` is a sequence of pairs (label, data). The produced lines are
+ nicely formatted, ready to print.
+
+ """
+ label_len = max([len(l) for l, _d in info])
+ for label, data in info:
+ if data == []:
+ data = "-none-"
+ if isinstance(data, (list, tuple)):
+ prefix = "%*s:" % (label_len, label)
+ for e in data:
+ yield "%*s %s" % (label_len+1, prefix, e)
+ prefix = ""
+ else:
+ yield "%*s: %s" % (label_len, label, data)
diff --git a/coverage/files.py b/coverage/files.py
index 4c55151e..8d154c6f 100644
--- a/coverage/files.py
+++ b/coverage/files.py
@@ -144,6 +144,10 @@ class TreeMatcher(object):
def __repr__(self):
return "<TreeMatcher %r>" % self.dirs
+ def info(self):
+ """A list of strings for displaying when dumping state."""
+ return self.dirs
+
def add(self, directory):
"""Add another directory to the list we match for."""
self.dirs.append(directory)
@@ -169,6 +173,10 @@ class FnmatchMatcher(object):
def __repr__(self):
return "<FnmatchMatcher %r>" % self.pats
+ def info(self):
+ """A list of strings for displaying when dumping state."""
+ return self.pats
+
def match(self, fpath):
"""Does `fpath` match one of our filename patterns?"""
for pat in self.pats:
diff --git a/coverage/html.py b/coverage/html.py
index ed8920f2..aef43be3 100644
--- a/coverage/html.py
+++ b/coverage/html.py
@@ -10,10 +10,6 @@ from coverage.report import Reporter
from coverage.results import Numbers
from coverage.templite import Templite
-# Disable pylint msg W0612, because a bunch of variables look unused, but
-# they're accessed in a Templite context via locals().
-# pylint: disable=W0612
-
def data_filename(fname):
"""Return the path to a data file of ours."""
return os.path.join(os.path.split(__file__)[0], fname)
@@ -162,7 +158,6 @@ class HtmlReporter(Reporter):
nums = analysis.numbers
missing_branch_arcs = analysis.missing_branch_arcs()
- arcs = self.arcs
# These classes determine which lines are highlighted by default.
c_run = "run hide_run"
@@ -220,13 +215,17 @@ class HtmlReporter(Reporter):
})
# Write the HTML page for this file.
- html_filename = flat_rootname + ".html"
- html_path = os.path.join(self.directory, html_filename)
- extra_css = self.extra_css
+ html = spaceless(self.source_tmpl.render({
+ 'c_exc': c_exc, 'c_mis': c_mis, 'c_par': c_par, 'c_run': c_run,
+ 'arcs': self.arcs, 'extra_css': self.extra_css,
+ 'cu': cu, 'nums': nums, 'lines': lines,
+ }))
- html = spaceless(self.source_tmpl.render(locals()))
if sys.version_info < (3, 0):
html = html.decode(encoding)
+
+ html_filename = flat_rootname + ".html"
+ html_path = os.path.join(self.directory, html_filename)
self.write_html(html_path, html)
# Save this file's information for the index file.
@@ -244,13 +243,15 @@ class HtmlReporter(Reporter):
data("htmlfiles/index.html"), self.template_globals
)
- files = self.files
- arcs = self.arcs
+ self.totals = sum([f['nums'] for f in self.files])
- self.totals = totals = sum([f['nums'] for f in files])
- extra_css = self.extra_css
+ html = index_tmpl.render({
+ 'arcs': self.arcs,
+ 'extra_css': self.extra_css,
+ 'files': self.files,
+ 'totals': self.totals,
+ })
- html = index_tmpl.render(locals())
if sys.version_info < (3, 0):
html = html.decode("utf-8")
self.write_html(
diff --git a/coverage/parser.py b/coverage/parser.py
index 8d6a077b..2d777a5d 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -512,11 +512,6 @@ class ByteParser(object):
chunk.exits.add(block_stack[-1][1])
chunk = None
if bc.op == OP_END_FINALLY:
- if block_stack:
- # A break that goes through a finally will jump to whatever
- # block is on top of the stack.
- # print self._block_stack_repr(block_stack)
- chunk.exits.add(block_stack[-1][1])
# For the finally clause we need to find the closest exception
# block, and use its jump target as an exit.
for block in reversed(block_stack):
diff --git a/coverage/version.py b/coverage/version.py
index 422fbadb..db4bca5d 100644
--- a/coverage/version.py
+++ b/coverage/version.py
@@ -1,7 +1,7 @@
"""The version and URL for coverage.py"""
# This file is exec'ed in setup.py, don't import anything!
-__version__ = "3.6.1a0" # see detailed history in CHANGES.txt
+__version__ = "3.6.1a1" # see detailed history in CHANGES.txt
__url__ = "http://nedbatchelder.com/code/coverage"
if max(__version__).isalpha():