summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt17
-rw-r--r--coverage/cmdline.py2
-rw-r--r--coverage/control.py53
-rw-r--r--coverage/data.py10
-rw-r--r--coverage/debug.py54
-rw-r--r--coverage/misc.py20
-rw-r--r--tests/test_debug.py25
-rw-r--r--tests/test_misc.py24
8 files changed, 140 insertions, 65 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index b88b2ebc..47976b33 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -5,11 +5,10 @@ Change history for Coverage.py
3.6.1
-----
-- Added the ``--debug`` switch to ``coverage run``. It accepts one option now,
- ``trace``, to log decisions whether to trace files. I'm hoping this will
- help people diagnose why their code isn't being traced.
+- Added the ``--debug`` switch to ``coverage run``. It accepts a list of
+ options indicating the type of internal activity to log to stderr.
-- Improved the branch coverage facility, fixing `issue 90`_ and `issue 175`_.
+- Improved the branch coverage facility, fixing `issue 92`_ and `issue 175`_.
- Running code with ``coverage run -m`` now behaves more like Python does,
setting sys.path properly, which fixes `issue 207`_ and `issue 242`_.
@@ -18,12 +17,12 @@ Change history for Coverage.py
cause them to be incorrectly marked as unexecuted, as described in
`issue 218`_. This is now fixed.
-- When running a threaded program under the Python tracer, coverage would issue
- a spurious warning about the trace function changing: "Trace function
- changed, measurement is likely wrong: None." This fixes `issue 164`_.
+- When running a threaded program under the Python tracer, coverage no longer
+ issues a spurious warning about the trace function changing: "Trace function
+ changed, measurement is likely wrong: None." This fixes `issue 164`_.
-- The source kit omitted the __main__.py file in the root coverage directory.
- It's now included, fixing `issue 255`_.
+- The source kit now includes the `__main__.py` file in the root coverage
+ directory, fixing `issue 255`_.
.. _issue 92: https://bitbucket.org/ned/coveragepy/issue/92/finally-clauses-arent-treated-properly-in
.. _issue 164: https://bitbucket.org/ned/coveragepy/issue/164/trace-function-changed-warning-when-using
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index 2d820bb7..0881313e 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -5,7 +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.misc import info_formatter
+from coverage.debug import info_formatter
class Opts(object):
diff --git a/coverage/control.py b/coverage/control.py
index a767a264..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
@@ -41,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, debug=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
@@ -76,8 +78,9 @@ class coverage(object):
`include` will be measured, files that match `omit` will not. Each
will also accept a single string argument.
- `debug` is a sequence of strings indicating what debugging information
- is desired.
+ `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__
@@ -114,6 +117,9 @@ class coverage(object):
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
@@ -158,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".
@@ -179,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
@@ -287,12 +294,12 @@ class coverage(object):
"""
canonical, reason = self._should_trace_with_reason(filename, frame)
- if 'trace' in self.config.debug:
+ if self.debug.should('trace'):
if not canonical:
- msg = "Not tracing %r: %s\n" % (filename, reason)
+ msg = "Not tracing %r: %s" % (filename, reason)
else:
- msg = "Tracing %r\n" % (filename,)
- sys.stderr.write(msg)
+ msg = "Tracing %r" % (filename,)
+ self.debug.write(msg)
return canonical
def _warn(self, msg):
@@ -383,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
@@ -707,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/misc.py b/coverage/misc.py
index 295bd2cb..473d7d43 100644
--- a/coverage/misc.py
+++ b/coverage/misc.py
@@ -138,26 +138,6 @@ class Hasher(object):
return self.md5.digest()
-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)
-
-
class CoverageException(Exception):
"""An exception specific to Coverage."""
pass
diff --git a/tests/test_debug.py b/tests/test_debug.py
new file mode 100644
index 00000000..f3ae31fb
--- /dev/null
+++ b/tests/test_debug.py
@@ -0,0 +1,25 @@
+"""Tests of coverage/debug.py"""
+
+from coverage.debug import info_formatter
+from tests.coveragetest import CoverageTest
+
+
+class InfoFormatterTest(CoverageTest):
+ """Tests of misc.info_formatter."""
+
+ def test_info_formatter(self):
+ lines = list(info_formatter([
+ ('x', 'hello there'),
+ ('very long label', ['one element']),
+ ('regular', ['abc', 'def', 'ghi', 'jkl']),
+ ('nothing', []),
+ ]))
+ self.assertEqual(lines, [
+ ' x: hello there',
+ 'very long label: one element',
+ ' regular: abc',
+ ' def',
+ ' ghi',
+ ' jkl',
+ ' nothing: -none-',
+ ])
diff --git a/tests/test_misc.py b/tests/test_misc.py
index 32243edb..50ddb17b 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -1,7 +1,8 @@
"""Tests of miscellaneous stuff."""
+
import sys
-from coverage.misc import Hasher, file_be_gone, info_formatter
+from coverage.misc import Hasher, file_be_gone
from coverage import __version__, __url__
from tests.coveragetest import CoverageTest
@@ -45,27 +46,6 @@ class RemoveFileTest(CoverageTest):
self.assertRaises(OSError, file_be_gone, ".")
-class InfoFormatterTest(CoverageTest):
- """Tests of misc.info_formatter."""
-
- def test_info_formatter(self):
- lines = list(info_formatter([
- ('x', 'hello there'),
- ('very long label', ['one element']),
- ('regular', ['abc', 'def', 'ghi', 'jkl']),
- ('nothing', []),
- ]))
- self.assertEqual(lines, [
- ' x: hello there',
- 'very long label: one element',
- ' regular: abc',
- ' def',
- ' ghi',
- ' jkl',
- ' nothing: -none-',
- ])
-
-
class SetupPyTest(CoverageTest):
"""Tests of setup.py"""