diff options
-rw-r--r-- | CHANGES.txt | 5 | ||||
-rw-r--r-- | coverage/cmdline.py | 44 | ||||
-rw-r--r-- | coverage/collector.py | 4 | ||||
-rw-r--r-- | coverage/control.py | 24 | ||||
-rw-r--r-- | coverage/data.py | 13 | ||||
-rw-r--r-- | test/test_cmdline.py | 25 |
6 files changed, 102 insertions, 13 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 7165ec9f..d9e50f1f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -25,7 +25,10 @@ Version 3.1, unreleased - Programs that change directory will still write .coverage files in the
directory where execution started. Fixed `issue 24`_.
-
+
+- Added a "coverage info" command for getting diagnostic information about the
+ coverage.py installation.
+
.. _issue 11: http://bitbucket.org/ned/coveragepy/issue/11
.. _issue 12: http://bitbucket.org/ned/coveragepy/issue/12
.. _issue 13: http://bitbucket.org/ned/coveragepy/issue/13
diff --git a/coverage/cmdline.py b/coverage/cmdline.py index accade5b..d421aabe 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -199,7 +199,7 @@ CMDS = { "missed with !." ), - 'help': CmdOptionParser("help", + 'help': CmdOptionParser("help", [Opts.help], usage = "[command]", description = "Describe how to use coverage.py" ), @@ -217,14 +217,22 @@ CMDS = { "executed, excluded, and missed lines." ), - 'combine': CmdOptionParser("combine", + 'combine': CmdOptionParser("combine", [Opts.help], usage = " ", description = "Combine data from multiple coverage files collected " "with 'run -p'. The combined results are stored into a single " "file representing the union of the coverage." ), - 'erase': CmdOptionParser("erase", + 'debug': CmdOptionParser("debug", [Opts.help], + usage = "<topic>", + description = "Display information the internals of coverage.py, " + "for diagnosing problems. " + "Topics are 'data' to show a summary of the data collected in " + ".coverage, or 'sys' to show installation information." + ), + + 'erase': CmdOptionParser("erase", [Opts.help], usage = " ", description = "Erase previously collected coverage data." ), @@ -371,6 +379,7 @@ class CoverageScript: 'execute' in options.actions or 'annotate' in options.actions or 'html' in options.actions or + 'debug' in options.actions or 'report' in options.actions or 'xml' in options.actions ) @@ -389,6 +398,35 @@ class CoverageScript: timid = options.timid, ) + if 'debug' in options.actions: + if not args: + self.help_fn("What information would you like: data, sys?") + return ERR + for info in args: + if info == 'sys': + print("-- sys ----------------------------------------") + for label, info in self.coverage.sysinfo(): + if isinstance(info, list): + print("%15s:" % label) + for e in info: + print("%15s %s" % ("", e)) + else: + print("%15s: %s" % (label, info)) + elif info == 'data': + print("-- data ---------------------------------------") + self.coverage.load() + summary = self.coverage.data.summary(fullpath=True) + if summary: + filenames = sorted(summary.keys()) + for f in filenames: + print("%s: %d lines" % (f, summary[f])) + else: + print("No data collected") + else: + self.help_fn("Don't know what you mean by %r" % info) + return ERR + return OK + if 'erase' in options.actions or options.erase_first: self.coverage.erase() else: diff --git a/coverage/collector.py b/coverage/collector.py index a121193d..3b56d241 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -118,6 +118,10 @@ class Collector: # trace function. self._trace_class = Tracer or PyTracer + def tracer_name(self): + """Return the class name of the tracer we're using.""" + return self._trace_class.__name__ + def reset(self): """Clear collected data, and prepare to collect more.""" # A dictionary with an entry for (Python source file name, line number diff --git a/coverage/control.py b/coverage/control.py index 4fdec930..d51dd294 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -332,3 +332,27 @@ class coverage: outfile = open(outfile, "w") reporter = XmlReporter(self, ignore_errors) reporter.report(morfs, omit_prefixes=omit_prefixes, outfile=outfile) + + def sysinfo(self): + """Return a list of key,value pairs showing internal information.""" + + import coverage as covmod + import platform, re, sys + + info = [ + ('version', covmod.__version__), + ('coverage', covmod.__file__), + ('cover_prefix', self.cover_prefix), + ('pylib_prefix', self.pylib_prefix), + ('tracer', self.collector.tracer_name()), + ('data_file', self.data.filename), + ('python', sys.version), + ('platform', platform.platform()), + ('cwd', os.getcwd()), + ('path', sys.path), + ('environment', [ + ("%s = %s" % (k, v)) for k, v in os.environ.items() + if re.search("^COV|^PY", k) + ]), + ] + return info diff --git a/coverage/data.py b/coverage/data.py index 4d368f55..54979658 100644 --- a/coverage/data.py +++ b/coverage/data.py @@ -164,14 +164,19 @@ class CoverageData: """ return self.lines.get(filename) or {} - def summary(self): + def summary(self, fullpath=False): """Return a dict summarizing the coverage data. - Keys are the basename of the filenames, and values are the number of - executed lines. This is useful in the unit tests. + Keys are based on the filenames, and values are the number of executed + lines. If `fullpath` is true, then the keys are the full pathnames of + the files, otherwise they are the basenames of the files. """ summ = {} + if fullpath: + filename_fn = lambda f: f + else: + filename_fn = os.path.basename for filename, lines in self.lines.items(): - summ[os.path.basename(filename)] = len(lines) + summ[filename_fn(filename)] = len(lines) return summ diff --git a/test/test_cmdline.py b/test/test_cmdline.py index d2413712..7e7cba2a 100644 --- a/test/test_cmdline.py +++ b/test/test_cmdline.py @@ -66,18 +66,23 @@ class CmdLineTest(CoverageTest): self.assertEqual(m1.method_calls, m2.method_calls) def cmd_help(self, args, help_msg=None, topic=None, ret=ERR): - """Run a command line, and check that it prints the right help.""" + """Run a command line, and check that it prints the right help. + + Only the last function call in the mock is checked, which should be the + help message that we want to see. + + """ m, r = self.mock_command_line(args) self.assertEqual(r, ret, "Wrong status: got %s, wanted %s" % (r, ret) ) if help_msg: - self.assertEqual(m.method_calls, - [('help_fn', (help_msg,), {})] + self.assertEqual(m.method_calls[-1], + ('help_fn', (help_msg,), {}) ) else: - self.assertEqual(m.method_calls, - [('help_fn', (), {'topic':topic})] + self.assertEqual(m.method_calls[-1], + ('help_fn', (), {'topic':topic}) ) @@ -340,6 +345,16 @@ class NewCmdLineTest(CmdLineTest): def testCombine(self): self.cmd_executes_same("combine", "-c") + def testDebug(self): + self.cmd_help("debug", "What information would you like: data, sys?") + self.cmd_help("debug foo", "Don't know what you mean by 'foo'") + + def testDebugSys(self): + self.command_line("debug sys") + out = self.stdout() + assert "version:" in out + assert "data_file:" in out + def testErase(self): self.cmd_executes_same("erase", "-e") |