summaryrefslogtreecommitdiff
path: root/coverage/control.py
diff options
context:
space:
mode:
Diffstat (limited to 'coverage/control.py')
-rw-r--r--coverage/control.py106
1 files changed, 77 insertions, 29 deletions
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