summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgignore64
-rw-r--r--.travis.yml21
-rw-r--r--CHANGES.txt16
-rw-r--r--MANIFEST.in1
-rw-r--r--Makefile4
-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
-rw-r--r--igor.py8
-rw-r--r--lab/hack_pyc.py164
-rw-r--r--lab/sample.py10
-rw-r--r--lab/show_pyc.py140
-rw-r--r--lab/trace_sample.py114
-rw-r--r--tests/coveragetest.py11
-rw-r--r--tests/test_arcs.py63
-rw-r--r--tests/test_cmdline.py80
-rw-r--r--tests/test_collector.py57
-rw-r--r--tests/test_debug.py113
-rw-r--r--tests/test_farm.py6
-rw-r--r--tests/test_files.py9
-rw-r--r--tests/test_html.py3
-rw-r--r--tests/test_misc.py1
-rw-r--r--tests/test_process.py4
31 files changed, 767 insertions, 372 deletions
diff --git a/.hgignore b/.hgignore
index d8b5935..34fa8a7 100644
--- a/.hgignore
+++ b/.hgignore
@@ -1,32 +1,32 @@
-syntax: glob
-
-# Files that can appear anywhere in the tree.
-*.pyc
-*.pyo
-*$py.class
-*.pyd
-*.so
-*.bak
-.coverage
-.coverage.*
-*.swp
-
-# Stuff generated by editors.
-.idea/
-
-# Stuff in the root.
-build
-*.egg-info
-dist
-htmlcov
-MANIFEST
-setuptools-*.egg
-.tox
-.tox_kits
-
-# Stuff in the test directory.
-zipmods.zip
-
-# Stuff in the doc directory.
-_build
-sample_html_beta
+syntax: glob
+
+# Files that can appear anywhere in the tree.
+*.pyc
+*.pyo
+*$py.class
+*.pyd
+*.so
+*.bak
+.coverage
+.coverage.*
+*.swp
+
+# Stuff generated by editors.
+.idea/
+
+# Stuff in the root.
+build
+*.egg-info
+dist
+htmlcov
+MANIFEST
+setuptools-*.egg
+.tox
+.tox_kits
+
+# Stuff in the test directory.
+zipmods.zip
+
+# Stuff in the doc directory.
+_build
+sample_html_beta
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..2dfd698
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,21 @@
+# Tell Travis what to do
+# https://travis-ci.org/nedbat/coveragepy
+
+language: python
+
+python:
+ - 2.5
+ - 2.6
+ - 2.7
+ - 3.2
+ - 3.3
+ - pypy
+
+install:
+ - python setup.py clean develop
+
+script:
+ - python igor.py zip_mods install_egg
+ - python igor.py test_with_tracer c
+ - python igor.py remove_extension
+ - python igor.py test_with_tracer py
diff --git a/CHANGES.txt b/CHANGES.txt
index 316df0c..47976b3 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -5,7 +5,10 @@ Change history for Coverage.py
3.6.1
-----
-- Improved the branch coverage mechanism, fixing `issue 175`_.
+- 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 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`_.
@@ -14,15 +17,20 @@ 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 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
.. _issue 175: https://bitbucket.org/ned/coveragepy/issue/175/branch-coverage-gets-confused-in-certain
.. _issue 207: https://bitbucket.org/ned/coveragepy/issue/207/run-m-cannot-find-module-or-package-in
.. _issue 242: https://bitbucket.org/ned/coveragepy/issue/242/running-a-two-level-package-doesnt-work
.. _issue 218: https://bitbucket.org/ned/coveragepy/issue/218/run-command-does-not-respect-the-omit-flag
+.. _issue 255: https://bitbucket.org/ned/coveragepy/issue/255/directory-level-__main__py-not-included-in
Version 3.6 --- 5 January 2013
diff --git a/MANIFEST.in b/MANIFEST.in
index d2712d9..7224674 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,6 +4,7 @@ recursive-include coverage/fullcoverage *
include coverage.egg-info/*.*
include setup.py
+include __main__.py
include README.txt
include CHANGES.txt
include AUTHORS.txt
diff --git a/Makefile b/Makefile
index 0588105..1ce7497 100644
--- a/Makefile
+++ b/Makefile
@@ -48,7 +48,9 @@ metahtml:
# Kitting
-SDIST_CMD = python setup.py sdist --keep-temp --formats=gztar fixtar --owner=ned --group=coverage --clean
+# For kitting on Windows:
+# SDIST_CMD = python setup.py sdist --keep-temp --formats=gztar fixtar --owner=ned --group=coverage --clean
+SDIST_CMD = python setup.py sdist --formats=gztar
kit:
$(SDIST_CMD)
diff --git a/coverage/__init__.py b/coverage/__init__.py
index 0ccc699..193b7a1 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 ac80310..0881313 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 781a0fa..9a74700 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 c2ebecb..87318ff 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 8821bfb..4b76121 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 c86a77f..fb88c5b 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 0000000..104f3b1
--- /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 4c55151..8d154c6 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 ed8920f..aef43be 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 8d6a077..2d777a5 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 422fbad..db4bca5 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():
diff --git a/igor.py b/igor.py
index e093d49..12ec6c8 100644
--- a/igor.py
+++ b/igor.py
@@ -15,6 +15,10 @@ import sys
import zipfile
+# Functions named do_* are executable from the command line: do_blah is run
+# by "python igor.py blah".
+
+
def do_remove_extension():
"""Remove the compiled C extension, no matter what its name."""
@@ -195,6 +199,8 @@ def do_check_eol():
check_file("setup.py")
check_file("igor.py")
check_file("Makefile")
+ check_file(".hgignore")
+ check_file(".travis.yml")
check_files("doc", ["*.rst"])
check_files(".", ["*.txt"])
@@ -217,7 +223,7 @@ def print_banner(label):
def do_help():
"""List the available commands"""
- items = globals().items()
+ items = list(globals().items())
items.sort()
for name, value in items:
if name.startswith('do_'):
diff --git a/lab/hack_pyc.py b/lab/hack_pyc.py
index e8992b9..1cdc476 100644
--- a/lab/hack_pyc.py
+++ b/lab/hack_pyc.py
@@ -1,82 +1,82 @@
-""" Wicked hack to get .pyc files to do bytecode tracing instead of
- line tracing.
-"""
-
-import marshal, new, opcode, sys, types
-
-from lnotab import lnotab_numbers, lnotab_string
-
-class PycFile:
- def read(self, f):
- if isinstance(f, basestring):
- f = open(f, "rb")
- self.magic = f.read(4)
- self.modtime = f.read(4)
- self.code = marshal.load(f)
-
- def write(self, f):
- if isinstance(f, basestring):
- f = open(f, "wb")
- f.write(self.magic)
- f.write(self.modtime)
- marshal.dump(self.code, f)
-
- def hack_line_numbers(self):
- self.code = hack_line_numbers(self.code)
-
-def hack_line_numbers(code):
- """ Replace a code object's line number information to claim that every
- byte of the bytecode is a new source line. Returns a new code
- object. Also recurses to hack the line numbers in nested code objects.
- """
-
- # Create a new lnotab table. Each opcode is claimed to be at
- # 1000*lineno + (opcode number within line), so for example, the opcodes on
- # source line 12 will be given new line numbers 12000, 12001, 12002, etc.
- old_num = list(lnotab_numbers(code.co_lnotab, code.co_firstlineno))
- n_bytes = len(code.co_code)
- new_num = []
- line = 0
- opnum_in_line = 0
- i_byte = 0
- while i_byte < n_bytes:
- if old_num and i_byte == old_num[0][0]:
- line = old_num.pop(0)[1]
- opnum_in_line = 0
- new_num.append((i_byte, 100000000 + 1000*line + opnum_in_line))
- if ord(code.co_code[i_byte]) >= opcode.HAVE_ARGUMENT:
- i_byte += 3
- else:
- i_byte += 1
- opnum_in_line += 1
-
- # new_num is a list of pairs, (byteoff, lineoff). Turn it into an lnotab.
- new_firstlineno = new_num[0][1]-1
- new_lnotab = lnotab_string(new_num, new_firstlineno)
-
- # Recurse into code constants in this code object.
- new_consts = []
- for const in code.co_consts:
- if type(const) == types.CodeType:
- new_consts.append(hack_line_numbers(const))
- else:
- new_consts.append(const)
-
- # Create a new code object, just like the old one, except with new
- # line numbers.
- new_code = new.code(
- code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags,
- code.co_code, tuple(new_consts), code.co_names, code.co_varnames,
- code.co_filename, code.co_name, new_firstlineno, new_lnotab
- )
-
- return new_code
-
-def hack_file(f):
- pyc = PycFile()
- pyc.read(f)
- pyc.hack_line_numbers()
- pyc.write(f)
-
-if __name__ == '__main__':
- hack_file(sys.argv[1])
+""" Wicked hack to get .pyc files to do bytecode tracing instead of
+ line tracing.
+"""
+
+import marshal, new, opcode, sys, types
+
+from lnotab import lnotab_numbers, lnotab_string
+
+class PycFile:
+ def read(self, f):
+ if isinstance(f, basestring):
+ f = open(f, "rb")
+ self.magic = f.read(4)
+ self.modtime = f.read(4)
+ self.code = marshal.load(f)
+
+ def write(self, f):
+ if isinstance(f, basestring):
+ f = open(f, "wb")
+ f.write(self.magic)
+ f.write(self.modtime)
+ marshal.dump(self.code, f)
+
+ def hack_line_numbers(self):
+ self.code = hack_line_numbers(self.code)
+
+def hack_line_numbers(code):
+ """ Replace a code object's line number information to claim that every
+ byte of the bytecode is a new source line. Returns a new code
+ object. Also recurses to hack the line numbers in nested code objects.
+ """
+
+ # Create a new lnotab table. Each opcode is claimed to be at
+ # 1000*lineno + (opcode number within line), so for example, the opcodes on
+ # source line 12 will be given new line numbers 12000, 12001, 12002, etc.
+ old_num = list(lnotab_numbers(code.co_lnotab, code.co_firstlineno))
+ n_bytes = len(code.co_code)
+ new_num = []
+ line = 0
+ opnum_in_line = 0
+ i_byte = 0
+ while i_byte < n_bytes:
+ if old_num and i_byte == old_num[0][0]:
+ line = old_num.pop(0)[1]
+ opnum_in_line = 0
+ new_num.append((i_byte, 100000000 + 1000*line + opnum_in_line))
+ if ord(code.co_code[i_byte]) >= opcode.HAVE_ARGUMENT:
+ i_byte += 3
+ else:
+ i_byte += 1
+ opnum_in_line += 1
+
+ # new_num is a list of pairs, (byteoff, lineoff). Turn it into an lnotab.
+ new_firstlineno = new_num[0][1]-1
+ new_lnotab = lnotab_string(new_num, new_firstlineno)
+
+ # Recurse into code constants in this code object.
+ new_consts = []
+ for const in code.co_consts:
+ if type(const) == types.CodeType:
+ new_consts.append(hack_line_numbers(const))
+ else:
+ new_consts.append(const)
+
+ # Create a new code object, just like the old one, except with new
+ # line numbers.
+ new_code = new.code(
+ code.co_argcount, code.co_nlocals, code.co_stacksize, code.co_flags,
+ code.co_code, tuple(new_consts), code.co_names, code.co_varnames,
+ code.co_filename, code.co_name, new_firstlineno, new_lnotab
+ )
+
+ return new_code
+
+def hack_file(f):
+ pyc = PycFile()
+ pyc.read(f)
+ pyc.hack_line_numbers()
+ pyc.write(f)
+
+if __name__ == '__main__':
+ hack_file(sys.argv[1])
diff --git a/lab/sample.py b/lab/sample.py
index cf4f6dc..bb62848 100644
--- a/lab/sample.py
+++ b/lab/sample.py
@@ -1,5 +1,5 @@
-a, b = 1, 0
-if a or b or fn():
- # Hey
- a = 3
-d = 4 \ No newline at end of file
+a, b = 1, 0
+if a or b or fn():
+ # Hey
+ a = 3
+d = 4
diff --git a/lab/show_pyc.py b/lab/show_pyc.py
index 7dacc2b..b2cbb34 100644
--- a/lab/show_pyc.py
+++ b/lab/show_pyc.py
@@ -1,70 +1,70 @@
-import dis, marshal, struct, sys, time, types
-
-def show_pyc_file(fname):
- f = open(fname, "rb")
- magic = f.read(4)
- moddate = f.read(4)
- modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0]))
- print "magic %s" % (magic.encode('hex'))
- print "moddate %s (%s)" % (moddate.encode('hex'), modtime)
- code = marshal.load(f)
- show_code(code)
-
-def show_py_file(fname):
- text = open(fname).read().replace('\r\n', '\n')
- show_py_text(text, fname=fname)
-
-def show_py_text(text, fname="<string>"):
- code = compile(text, fname, "exec")
- show_code(code)
-
-def show_code(code, indent=''):
- print "%scode" % indent
- indent += ' '
- print "%sargcount %d" % (indent, code.co_argcount)
- print "%snlocals %d" % (indent, code.co_nlocals)
- print "%sstacksize %d" % (indent, code.co_stacksize)
- print "%sflags %04x" % (indent, code.co_flags)
- show_hex("code", code.co_code, indent=indent)
- dis.disassemble(code)
- print "%sconsts" % indent
- for const in code.co_consts:
- if type(const) == types.CodeType:
- show_code(const, indent+' ')
- else:
- print " %s%r" % (indent, const)
- print "%snames %r" % (indent, code.co_names)
- print "%svarnames %r" % (indent, code.co_varnames)
- print "%sfreevars %r" % (indent, code.co_freevars)
- print "%scellvars %r" % (indent, code.co_cellvars)
- print "%sfilename %r" % (indent, code.co_filename)
- print "%sname %r" % (indent, code.co_name)
- print "%sfirstlineno %d" % (indent, code.co_firstlineno)
- show_hex("lnotab", code.co_lnotab, indent=indent)
-
-def show_hex(label, h, indent):
- h = h.encode('hex')
- if len(h) < 60:
- print "%s%s %s" % (indent, label, h)
- else:
- print "%s%s" % (indent, label)
- for i in range(0, len(h), 60):
- print "%s %s" % (indent, h[i:i+60])
-
-def show_file(fname):
- if fname.endswith('pyc'):
- show_pyc_file(fname)
- elif fname.endswith('py'):
- show_py_file(fname)
- else:
- print "Odd file:", fname
-
-def main(args):
- if args[0] == '-c':
- show_py_text(" ".join(args[1:]).replace(";", "\n"))
- else:
- for a in args:
- show_file(a)
-
-if __name__ == '__main__':
- main(sys.argv[1:])
+import dis, marshal, struct, sys, time, types
+
+def show_pyc_file(fname):
+ f = open(fname, "rb")
+ magic = f.read(4)
+ moddate = f.read(4)
+ modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0]))
+ print "magic %s" % (magic.encode('hex'))
+ print "moddate %s (%s)" % (moddate.encode('hex'), modtime)
+ code = marshal.load(f)
+ show_code(code)
+
+def show_py_file(fname):
+ text = open(fname).read().replace('\r\n', '\n')
+ show_py_text(text, fname=fname)
+
+def show_py_text(text, fname="<string>"):
+ code = compile(text, fname, "exec")
+ show_code(code)
+
+def show_code(code, indent=''):
+ print "%scode" % indent
+ indent += ' '
+ print "%sargcount %d" % (indent, code.co_argcount)
+ print "%snlocals %d" % (indent, code.co_nlocals)
+ print "%sstacksize %d" % (indent, code.co_stacksize)
+ print "%sflags %04x" % (indent, code.co_flags)
+ show_hex("code", code.co_code, indent=indent)
+ dis.disassemble(code)
+ print "%sconsts" % indent
+ for const in code.co_consts:
+ if type(const) == types.CodeType:
+ show_code(const, indent+' ')
+ else:
+ print " %s%r" % (indent, const)
+ print "%snames %r" % (indent, code.co_names)
+ print "%svarnames %r" % (indent, code.co_varnames)
+ print "%sfreevars %r" % (indent, code.co_freevars)
+ print "%scellvars %r" % (indent, code.co_cellvars)
+ print "%sfilename %r" % (indent, code.co_filename)
+ print "%sname %r" % (indent, code.co_name)
+ print "%sfirstlineno %d" % (indent, code.co_firstlineno)
+ show_hex("lnotab", code.co_lnotab, indent=indent)
+
+def show_hex(label, h, indent):
+ h = h.encode('hex')
+ if len(h) < 60:
+ print "%s%s %s" % (indent, label, h)
+ else:
+ print "%s%s" % (indent, label)
+ for i in range(0, len(h), 60):
+ print "%s %s" % (indent, h[i:i+60])
+
+def show_file(fname):
+ if fname.endswith('pyc'):
+ show_pyc_file(fname)
+ elif fname.endswith('py'):
+ show_py_file(fname)
+ else:
+ print "Odd file:", fname
+
+def main(args):
+ if args[0] == '-c':
+ show_py_text(" ".join(args[1:]).replace(";", "\n"))
+ else:
+ for a in args:
+ show_file(a)
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/lab/trace_sample.py b/lab/trace_sample.py
index 9fa3724..3f81919 100644
--- a/lab/trace_sample.py
+++ b/lab/trace_sample.py
@@ -1,57 +1,57 @@
-import os, sys
-
-global nest
-nest = 0
-
-def trace(frame, event, arg):
- #if event == 'line':
- global nest
-
- print "%s%s %s %d" % (
- " " * nest,
- event,
- os.path.basename(frame.f_code.co_filename),
- frame.f_lineno,
- )
-
- if event == 'call':
- nest += 1
- if event == 'return':
- nest -= 1
-
- return trace
-
-def trace2(frame, event, arg):
- #if event == 'line':
- global nest
-
- print "2: %s%s %s %d" % (
- " " * nest,
- event,
- os.path.basename(frame.f_code.co_filename),
- frame.f_lineno,
- )
-
- if event == 'call':
- nest += 1
- if event == 'return':
- nest -= 1
-
- return trace2
-
-sys.settrace(trace)
-
-def bar():
- print "nar"
-
-a = 26
-def foo(n):
- a = 28
- sys.settrace(sys.gettrace())
- bar()
- a = 30
- return 2*n
-
-print foo(a)
-#import sample
-#import littleclass
+import os, sys
+
+global nest
+nest = 0
+
+def trace(frame, event, arg):
+ #if event == 'line':
+ global nest
+
+ print "%s%s %s %d" % (
+ " " * nest,
+ event,
+ os.path.basename(frame.f_code.co_filename),
+ frame.f_lineno,
+ )
+
+ if event == 'call':
+ nest += 1
+ if event == 'return':
+ nest -= 1
+
+ return trace
+
+def trace2(frame, event, arg):
+ #if event == 'line':
+ global nest
+
+ print "2: %s%s %s %d" % (
+ " " * nest,
+ event,
+ os.path.basename(frame.f_code.co_filename),
+ frame.f_lineno,
+ )
+
+ if event == 'call':
+ nest += 1
+ if event == 'return':
+ nest -= 1
+
+ return trace2
+
+sys.settrace(trace)
+
+def bar():
+ print "nar"
+
+a = 26
+def foo(n):
+ a = 28
+ sys.settrace(sys.gettrace())
+ bar()
+ a = 30
+ return 2*n
+
+print foo(a)
+#import sample
+#import littleclass
diff --git a/tests/coveragetest.py b/tests/coveragetest.py
index 5613fa6..5060b53 100644
--- a/tests/coveragetest.py
+++ b/tests/coveragetest.py
@@ -36,8 +36,12 @@ OK, ERR = 0, 1
class CoverageTest(TestCase):
"""A base class for Coverage test cases."""
+ # Our own setting: most CoverageTests run in their own temp directory.
run_in_temp_dir = True
+ # Standard unittest setting: show me diffs even if they are very long.
+ maxDiff = None
+
def setUp(self):
super(CoverageTest, self).setUp()
@@ -61,8 +65,11 @@ class CoverageTest(TestCase):
self.old_dir = os.getcwd()
os.chdir(self.temp_dir)
- # Modules should be importable from this temp directory.
- sys.path.insert(0, '')
+ # Modules should be importable from this temp directory. We don't
+ # use '' because we make lots of different temp directories and
+ # nose's caching importer can get confused. The full path prevents
+ # problems.
+ sys.path.insert(0, os.getcwd())
# Keep a counter to make every call to check_coverage unique.
self.n = 0
diff --git a/tests/test_arcs.py b/tests/test_arcs.py
index f3c5fc3..6268e28 100644
--- a/tests/test_arcs.py
+++ b/tests/test_arcs.py
@@ -141,7 +141,7 @@ class SimpleArcTest(CoverageTest):
)
if 0: # expected failure
- def test_lambdas_are_confusing_bug_90(self):
+ def test_unused_lambdas_are_confusing_bug_90(self):
self.check_coverage("""\
a = 1
fn = lambda x: x
@@ -172,9 +172,9 @@ if sys.version_info >= (2, 6):
self.check_coverage("""\
for i in range(2):
with open("test", "w") as f:
- print 3
- print 4
- print 5
+ print(3)
+ print(4)
+ print(5)
""",
arcz=".1 12 23 34 41 15 5."
)
@@ -456,8 +456,8 @@ class ExceptionArcTest(CoverageTest):
d = 12 # C
assert a == 5 and c == 10 and d == 12 # D
""",
- arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB AD BC CD D.",
- arcz_missing="3D AD", arcz_unpredicted="7A")
+ arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB BC CD D.",
+ arcz_missing="3D", arcz_unpredicted="7A")
self.check_coverage("""\
a, c, d, i = 1, 1, 1, 99
try:
@@ -473,8 +473,8 @@ class ExceptionArcTest(CoverageTest):
d = 12 # C
assert a == 8 and c == 10 and d == 1 # D
""",
- arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB AD BC CD D.",
- arcz_missing="67 AB AD BC CD", arcz_unpredicted="")
+ arcz=".1 12 23 34 3D 45 56 67 68 8A A3 AB BC CD D.",
+ arcz_missing="67 AB BC CD", arcz_unpredicted="")
def test_break_in_finally(self):
@@ -493,22 +493,45 @@ class ExceptionArcTest(CoverageTest):
d = 12 # C
assert a == 5 and c == 10 and d == 1 # D
""",
- arcz=".1 12 23 34 3D 45 56 67 68 7A 8A A3 AB AD BC CD D.",
- arcz_missing="3D AB BC CD", arcz_unpredicted="")
+ arcz=".1 12 23 34 3D 45 56 67 68 7A 8A A3 AB BC CD D.",
+ arcz_missing="3D AB BC CD", arcz_unpredicted="AD")
- if 0: # expected failure
- def test_finally_in_loop_bug_92(self):
+ def test_finally_in_loop_bug_92(self):
+ self.check_coverage("""\
+ for i in range(5):
+ try:
+ j = 3
+ finally:
+ f = 5
+ g = 6
+ h = 7
+ """,
+ arcz=".1 12 23 35 56 61 17 7.",
+ arcz_missing="", arcz_unpredicted="")
+
+ # Run this test only on 2.6 and 2.7 for now. I hope to fix it on Py3
+ # eventually...
+ if (2, 6) <= sys.version_info < (3,):
+ # "except Exception as e" is crucial here.
+ def test_bug_212(self):
self.check_coverage("""\
- for i in range(5):
+ def b(exc):
try:
- j = 3
- finally:
- f = 5
- g = 6
- h = 7
+ while 1:
+ raise Exception(exc) # 4
+ except Exception as e:
+ if exc != 'expected':
+ raise
+ q = 8
+
+ b('expected')
+ try:
+ b('unexpected') # C
+ except:
+ pass
""",
- arcz=".1 12 23 35 56 61 17 7.",
- arcz_missing="", arcz_unpredicted="")
+ arcz=".1 .2 1A 23 34 56 67 68 8. AB BC C. DE E.",
+ arcz_missing="C.", arcz_unpredicted="45 7. CD")
if sys.version_info >= (2, 5):
# Try-except-finally was new in 2.5
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
index 6b11b3e..493ce18 100644
--- a/tests/test_cmdline.py
+++ b/tests/test_cmdline.py
@@ -15,7 +15,7 @@ class CmdLineTest(CoverageTest):
run_in_temp_dir = False
INIT_LOAD = """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None)
.load()\n"""
def model_object(self):
@@ -104,7 +104,7 @@ class ClassicCmdLineTest(CmdLineTest):
def test_erase(self):
# coverage -e
self.cmd_executes("-e", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None)
.erase()
""")
self.cmd_executes_same("-e", "--erase")
@@ -114,7 +114,7 @@ class ClassicCmdLineTest(CmdLineTest):
# -x calls coverage.load first.
self.cmd_executes("-x foo.py", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None)
.load()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -123,7 +123,7 @@ class ClassicCmdLineTest(CmdLineTest):
""")
# -e -x calls coverage.erase first.
self.cmd_executes("-e -x foo.py", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None)
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -132,7 +132,7 @@ class ClassicCmdLineTest(CmdLineTest):
""")
# --timid sets a flag, and program arguments get passed through.
self.cmd_executes("-x --timid foo.py abc 123", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=True, branch=None, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=True, branch=None, config_file=True, source=None, include=None, omit=None, debug=None)
.load()
.start()
.run_python_file('foo.py', ['foo.py', 'abc', '123'])
@@ -141,7 +141,7 @@ class ClassicCmdLineTest(CmdLineTest):
""")
# -L sets a flag, and flags for the program don't confuse us.
self.cmd_executes("-x -p -L foo.py -a -b", """\
- .coverage(cover_pylib=True, data_suffix=True, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=True, data_suffix=True, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None)
.load()
.start()
.run_python_file('foo.py', ['foo.py', '-a', '-b'])
@@ -158,7 +158,7 @@ class ClassicCmdLineTest(CmdLineTest):
def test_combine(self):
# coverage -c
self.cmd_executes("-c", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None)
.load()
.combine()
.save()
@@ -180,13 +180,13 @@ class ClassicCmdLineTest(CmdLineTest):
show_missing=True)
""")
self.cmd_executes("-r -o fooey", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"])
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None)
.load()
.report(ignore_errors=None, omit=["fooey"], include=None,
morfs=[], show_missing=None)
""")
self.cmd_executes("-r -o fooey,booey", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"])
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None)
.load()
.report(ignore_errors=None, omit=["fooey", "booey"], include=None,
morfs=[], show_missing=None)
@@ -225,13 +225,13 @@ class ClassicCmdLineTest(CmdLineTest):
omit=None, include=None, morfs=[])
""")
self.cmd_executes("-a -o fooey", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"])
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None)
.load()
.annotate(directory=None, ignore_errors=None,
omit=["fooey"], include=None, morfs=[])
""")
self.cmd_executes("-a -o fooey,booey", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"])
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None)
.load()
.annotate(directory=None, ignore_errors=None,
omit=["fooey", "booey"], include=None, morfs=[])
@@ -270,13 +270,13 @@ class ClassicCmdLineTest(CmdLineTest):
omit=None, include=None, morfs=[])
""")
self.cmd_executes("-b -o fooey", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"])
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None)
.load()
.html_report(directory=None, ignore_errors=None, title=None,
omit=["fooey"], include=None, morfs=[])
""")
self.cmd_executes("-b -o fooey,booey", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"])
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None)
.load()
.html_report(directory=None, ignore_errors=None, title=None,
omit=["fooey", "booey"], include=None, morfs=[])
@@ -481,7 +481,7 @@ class NewCmdLineTest(CmdLineTest):
self.cmd_executes_same("run --timid f.py", "-e -x --timid f.py")
self.cmd_executes_same("run", "-x")
self.cmd_executes("run --branch foo.py", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None, debug=None)
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -489,7 +489,7 @@ class NewCmdLineTest(CmdLineTest):
.save()
""")
self.cmd_executes("run --rcfile=myrc.rc foo.py", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file="myrc.rc", source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file="myrc.rc", source=None, include=None, omit=None, debug=None)
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -497,7 +497,7 @@ class NewCmdLineTest(CmdLineTest):
.save()
""")
self.cmd_executes("run --include=pre1,pre2 foo.py", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=["pre1", "pre2"], omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=["pre1", "pre2"], omit=None, debug=None)
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -505,7 +505,7 @@ class NewCmdLineTest(CmdLineTest):
.save()
""")
self.cmd_executes("run --omit=opre1,opre2 foo.py", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["opre1", "opre2"])
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["opre1", "opre2"], debug=None)
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -517,7 +517,9 @@ class NewCmdLineTest(CmdLineTest):
.coverage(cover_pylib=None, data_suffix=None, timid=None,
branch=None, config_file=True, source=None,
include=["pre1", "pre2"],
- omit=["opre1", "opre2"])
+ omit=["opre1", "opre2"],
+ debug=None,
+ )
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -529,7 +531,37 @@ class NewCmdLineTest(CmdLineTest):
.coverage(cover_pylib=None, data_suffix=None, timid=None,
branch=None, config_file=True,
source=["quux", "hi.there", "/home/bar"], include=None,
- omit=None)
+ omit=None,
+ debug=None,
+ )
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+
+ def test_run_debug(self):
+ self.cmd_executes("run --debug=opt1 foo.py", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None,
+ branch=None, config_file=True,
+ source=None, include=None,
+ omit=None,
+ debug=["opt1"],
+ )
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ self.cmd_executes("run --debug=opt1,opt2 foo.py", """\
+ .coverage(cover_pylib=None, data_suffix=None, timid=None,
+ branch=None, config_file=True,
+ source=None, include=None,
+ omit=None,
+ debug=["opt1","opt2"],
+ )
.erase()
.start()
.run_python_file('foo.py', ['foo.py'])
@@ -539,7 +571,7 @@ class NewCmdLineTest(CmdLineTest):
def test_run_module(self):
self.cmd_executes("run -m mymodule", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None)
.erase()
.start()
.run_python_module('mymodule', ['mymodule'])
@@ -547,7 +579,7 @@ class NewCmdLineTest(CmdLineTest):
.save()
""")
self.cmd_executes("run -m mymodule -qq arg1 arg2", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None)
.erase()
.start()
.run_python_module('mymodule', ['mymodule', '-qq', 'arg1', 'arg2'])
@@ -555,7 +587,7 @@ class NewCmdLineTest(CmdLineTest):
.save()
""")
self.cmd_executes("run --branch -m mymodule", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None)
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=True, config_file=True, source=None, include=None, omit=None, debug=None)
.erase()
.start()
.run_python_module('mymodule', ['mymodule'])
@@ -583,13 +615,13 @@ class NewCmdLineTest(CmdLineTest):
outfile="-")
""")
self.cmd_executes("xml --omit fooey", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"])
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey"], debug=None)
.load()
.xml_report(ignore_errors=None, omit=["fooey"], include=None, morfs=[],
outfile=None)
""")
self.cmd_executes("xml --omit fooey,booey", """\
- .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"])
+ .coverage(cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=["fooey", "booey"], debug=None)
.load()
.xml_report(ignore_errors=None, omit=["fooey", "booey"], include=None,
morfs=[], outfile=None)
diff --git a/tests/test_collector.py b/tests/test_collector.py
new file mode 100644
index 0000000..af3814f
--- /dev/null
+++ b/tests/test_collector.py
@@ -0,0 +1,57 @@
+"""Tests of coverage/collector.py and other collectors."""
+
+import re
+
+import coverage
+from coverage.backward import StringIO
+
+from tests.coveragetest import CoverageTest
+
+
+class CollectorTest(CoverageTest):
+ """Test specific aspects of the collection process."""
+
+ def test_should_trace_cache(self):
+ # The tracers should only invoke should_trace once for each file name.
+ # TODO: Might be better to do this with a mocked _should_trace,
+ # rather than by examining debug output.
+
+ # Make some files that invoke each other.
+ self.make_file("f1.py", """\
+ def f1(x, f):
+ return f(x)
+ """)
+
+ self.make_file("f2.py", """\
+ import f1
+
+ def func(x):
+ return f1.f1(x, otherfunc)
+
+ def otherfunc(x):
+ return x*x
+
+ for i in range(10):
+ func(i)
+ """)
+
+ # Trace one file, but not the other, and get the debug output.
+ debug_out = StringIO()
+ cov = coverage.coverage(
+ include=["f1.py"], debug=['trace'], debug_file=debug_out
+ )
+
+ # Import the python file, executing it.
+ self.start_import_stop(cov, "f2")
+
+ # Grab all the filenames mentioned in debug output, there should be no
+ # duplicates.
+ trace_lines = [
+ l for l in debug_out.getvalue().splitlines()
+ if l.startswith("Tracing ") or l.startswith("Not tracing ")
+ ]
+ filenames = [re.search(r"'[^']+'", l).group() for l in trace_lines]
+ self.assertEqual(len(filenames), len(set(filenames)))
+
+ # Double-check that the tracing messages are in there somewhere.
+ self.assertGreater(len(filenames), 5)
diff --git a/tests/test_debug.py b/tests/test_debug.py
new file mode 100644
index 0000000..0132c1c
--- /dev/null
+++ b/tests/test_debug.py
@@ -0,0 +1,113 @@
+"""Tests of coverage/debug.py"""
+
+import os
+import re
+
+import coverage
+from coverage.backward import StringIO
+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-',
+ ])
+
+
+class DebugTraceTest(CoverageTest):
+ """Tests of debug output."""
+
+ def f1_debug_output(self, debug):
+ """Runs some code with `debug` option, returns the debug output."""
+ # Make code to run.
+ self.make_file("f1.py", """\
+ def f1(x):
+ return x+1
+
+ for i in range(5):
+ f1(i)
+ """)
+
+ debug_out = StringIO()
+ cov = coverage.coverage(debug=debug, debug_file=debug_out)
+ self.start_import_stop(cov, "f1")
+
+ out_lines = debug_out.getvalue().splitlines()
+ return out_lines
+
+ def test_debug_no_trace(self):
+ out_lines = self.f1_debug_output([])
+
+ # We should have no output at all.
+ self.assertFalse(out_lines)
+
+ def test_debug_trace(self):
+ out_lines = self.f1_debug_output(["trace"])
+
+ # We should have a line like "Tracing 'f1.py'"
+ self.assertIn("Tracing 'f1.py'", out_lines)
+
+ # We should lines like "Not tracing 'collector.py'..."
+ coverage_lines = lines_matching(
+ out_lines,
+ r"^Not tracing .*: is part of coverage.py$"
+ )
+ self.assertTrue(coverage_lines)
+
+ def test_debug_trace_pid(self):
+ out_lines = self.f1_debug_output(["trace", "pid"])
+
+ # Now our lines are always prefixed with the process id.
+ pid_prefix = "^pid %5d: " % os.getpid()
+ pid_lines = lines_matching(out_lines, pid_prefix)
+ self.assertEqual(pid_lines, out_lines)
+
+ # We still have some tracing, and some not tracing.
+ self.assertTrue(lines_matching(out_lines, pid_prefix + "Tracing "))
+ self.assertTrue(lines_matching(out_lines, pid_prefix + "Not tracing "))
+
+ def test_debug_config(self):
+ out_lines = self.f1_debug_output(["config"])
+
+ labels = """
+ attempted_config_files branch config_files cover_pylib data_file
+ debug exclude_list extra_css html_dir html_title ignore_errors
+ include omit parallel partial_always_list partial_list paths
+ precision show_missing source timid xml_output
+ """.split()
+ for label in labels:
+ label_pat = r"^\s*%s: " % label
+ self.assertEqual(len(lines_matching(out_lines, label_pat)), 1)
+
+ def test_debug_sys(self):
+ out_lines = self.f1_debug_output(["sys"])
+
+ labels = """
+ version coverage cover_dir pylib_dirs tracer config_files
+ configs_read data_path python platform implementation executable
+ cwd path environment command_line cover_match pylib_match
+ """.split()
+ for label in labels:
+ label_pat = r"^\s*%s: " % label
+ self.assertEqual(len(lines_matching(out_lines, label_pat)), 1)
+
+
+def lines_matching(lines, pat):
+ """Gives the list of lines from `lines` that match `pat`."""
+ return [l for l in lines if re.search(pat, l)]
diff --git a/tests/test_farm.py b/tests/test_farm.py
index e514e66..fee2806 100644
--- a/tests/test_farm.py
+++ b/tests/test_farm.py
@@ -195,9 +195,9 @@ class FarmTestCase(object):
"""Compare files matching `file_pattern` in `dir1` and `dir2`.
`dir2` is interpreted as a prefix, with Python version numbers appended
- to find the actual directory to compare with. "foo" will compare against
- "foo_v241", "foo_v24", "foo_v2", or "foo", depending on which directory
- is found first.
+ to find the actual directory to compare with. "foo" will compare
+ against "foo_v241", "foo_v24", "foo_v2", or "foo", depending on which
+ directory is found first.
`size_within` is a percentage delta for the file sizes. If non-zero,
then the file contents are not compared (since they are expected to
diff --git a/tests/test_files.py b/tests/test_files.py
index 509c23b..eeb2264 100644
--- a/tests/test_files.py
+++ b/tests/test_files.py
@@ -58,16 +58,19 @@ class MatcherTest(CoverageTest):
file4 = self.make_file("sub3/file4.py")
file5 = self.make_file("sub3/file5.c")
fl = FileLocator()
- tm = TreeMatcher([
+ trees = [
fl.canonical_filename("sub"),
fl.canonical_filename(file4),
- ])
+ ]
+ tm = TreeMatcher(trees)
self.assertTrue(tm.match(fl.canonical_filename(file1)))
self.assertTrue(tm.match(fl.canonical_filename(file2)))
self.assertFalse(tm.match(fl.canonical_filename(file3)))
self.assertTrue(tm.match(fl.canonical_filename(file4)))
self.assertFalse(tm.match(fl.canonical_filename(file5)))
+ self.assertEqual(tm.info(), trees)
+
def test_fnmatch_matcher(self):
file1 = self.make_file("sub/file1.py")
file2 = self.make_file("sub/file2.c")
@@ -82,6 +85,8 @@ class MatcherTest(CoverageTest):
self.assertTrue(fnm.match(fl.canonical_filename(file4)))
self.assertFalse(fnm.match(fl.canonical_filename(file5)))
+ self.assertEqual(fnm.info(), ["*.py", "*/sub2/*"])
+
class PathAliasesTest(CoverageTest):
"""Tests for coverage/files.py:PathAliases"""
diff --git a/tests/test_html.py b/tests/test_html.py
index 40852ac..c004303 100644
--- a/tests/test_html.py
+++ b/tests/test_html.py
@@ -52,8 +52,6 @@ class HtmlDeltaTest(HtmlTestHelpers, CoverageTest):
# so grab it here to restore it later.
self.real_coverage_version = coverage.__version__
- self.maxDiff = None
-
def tearDown(self):
coverage.__version__ = self.real_coverage_version
super(HtmlDeltaTest, self).tearDown()
@@ -291,6 +289,7 @@ class HtmlTest(CoverageTest):
os.remove("sub/another.py")
missing_file = os.path.join(self.temp_dir, "sub", "another.py")
+ missing_file = os.path.realpath(missing_file)
self.assertRaisesRegexp(NoSource,
"(?i)No source for code: '%s'" % re.escape(missing_file),
cov.html_report
diff --git a/tests/test_misc.py b/tests/test_misc.py
index e7fa436..50ddb17 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -1,4 +1,5 @@
"""Tests of miscellaneous stuff."""
+
import sys
from coverage.misc import Hasher, file_be_gone
diff --git a/tests/test_process.py b/tests/test_process.py
index 8554eb5..97e2f82 100644
--- a/tests/test_process.py
+++ b/tests/test_process.py
@@ -583,7 +583,7 @@ class ProcessStartupTest(CoverageTest):
pass
finally:
pth.close()
- else: # pragma: not covered
+ else: # pragma: not covered
raise Exception("Couldn't find a place for the .pth file")
def tearDown(self):
@@ -615,7 +615,9 @@ class ProcessStartupTest(CoverageTest):
import main # pylint: disable=F0401,W0612
self.assertEqual(open("out.txt").read(), "Hello, world!\n")
+
# Read the data from .coverage
+ self.assert_exists(".mycovdata")
data = coverage.CoverageData()
data.read_file(".mycovdata")
self.assertEqual(data.summary()['sub.py'], 3)