diff options
-rw-r--r-- | CHANGES.txt | 17 | ||||
-rw-r--r-- | coverage/cmdline.py | 2 | ||||
-rw-r--r-- | coverage/control.py | 53 | ||||
-rw-r--r-- | coverage/data.py | 10 | ||||
-rw-r--r-- | coverage/debug.py | 54 | ||||
-rw-r--r-- | coverage/misc.py | 20 | ||||
-rw-r--r-- | tests/test_debug.py | 25 | ||||
-rw-r--r-- | tests/test_misc.py | 24 |
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""" |