summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.hgtags1
-rw-r--r--AUTHORS.txt2
-rw-r--r--CHANGES.txt36
-rw-r--r--coverage/__init__.py16
-rw-r--r--coverage/backward.py2
-rw-r--r--coverage/cmdline.py122
-rw-r--r--coverage/codeunit.py26
-rw-r--r--coverage/collector.py71
-rw-r--r--coverage/config.py49
-rw-r--r--coverage/control.py167
-rw-r--r--coverage/misc.py2
-rw-r--r--coverage/parser.py4
-rw-r--r--coverage/plugin.py40
-rw-r--r--coverage/pytracer.py24
-rw-r--r--coverage/results.py3
-rw-r--r--coverage/test_helpers.py40
-rw-r--r--coverage/tracer.c32
-rw-r--r--coverage/version.py2
-rw-r--r--doc/_ext/px_xlator.py18
-rw-r--r--doc/changes.rst25
-rw-r--r--doc/cmd.rst28
-rw-r--r--doc/conf.py6
-rw-r--r--doc/config.rst18
-rw-r--r--doc/index.rst9
-rw-r--r--doc/install.rst29
-rw-r--r--doc/python-coverage.1.txt10
-rw-r--r--howto.txt3
-rw-r--r--metacov.ini16
-rw-r--r--setup.py3
-rw-r--r--tests/coveragetest.py14
-rw-r--r--tests/farm/run/run_timid.py20
-rw-r--r--tests/test_cmdline.py503
-rw-r--r--tests/test_collector.py5
-rw-r--r--tests/test_concurrency.py (renamed from tests/test_coroutine.py)34
-rw-r--r--tests/test_config.py43
-rw-r--r--tests/test_debug.py3
-rw-r--r--tests/test_execfile.py4
-rw-r--r--tests/test_farm.py7
-rw-r--r--tests/test_oddball.py2
-rw-r--r--tests/test_plugins.py57
-rw-r--r--tests/test_process.py2
-rw-r--r--tests/test_templite.py6
-rw-r--r--tests/test_testing.py4
-rw-r--r--tests/try_execfile.py7
-rw-r--r--tox.ini19
-rw-r--r--tox_winkits.ini2
46 files changed, 806 insertions, 730 deletions
diff --git a/.hgtags b/.hgtags
index 37da691d..ab6d166e 100644
--- a/.hgtags
+++ b/.hgtags
@@ -36,3 +36,4 @@ afc3c2ffdb323bf059f136c4c88199899ed93d36 coverage-3.5.2b1
af20c6543c226fbc9deeba4a6d4114641374734a coverage-3.6
092c0be6d011b6abf85b11476e3b548ed4d5b4c3 coverage-3.7
41932e001f215955dff895a1fd2fe4133abb89dc coverage-3.7.1
+175fd36ea47ea668066965c80343e1de34f2a94a coverage-4.0a1
diff --git a/AUTHORS.txt b/AUTHORS.txt
index d374c2ac..5d40edee 100644
--- a/AUTHORS.txt
+++ b/AUTHORS.txt
@@ -17,6 +17,8 @@ Matthew Desmarais
Danek Duvall
Ben Finney
Martin Fuzzey
+Alex Gaynor
+Carl Gieringer
Imri Goldberg
Bill Hart
Christian Heimes
diff --git a/CHANGES.txt b/CHANGES.txt
index b034744f..4e657c3a 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -2,13 +2,33 @@
Change history for Coverage.py
------------------------------
-4.0
----
+
+Latest
+------
+
+- Officially support PyPy 2.3 and 2.4.
+
+- The original command line switches (`-x` to run a program, etc) are no
+ longer supported.
+
+- The ``COVERAGE_OPTIONS`` environment variable is no longer supported. It was
+ a hack for ``--timid`` before configuration files were available.
+
+- You can now programmatically adjust the configuration of coverage by setting
+ items on `Coverage.config` after construcion.
+
+- Made some PyPy-specific tweaks to improve speed under PyPy. Thanks, Alex
+ Gaynor.
+
+
+Version 4.0a1 --- 27 September 2014
+-----------------------------------
- Python versions supported are now CPython 2.6, 2.7, 3.2, 3.3, and 3.4, and
PyPy 2.2.
-- Gevent, eventlet, and greenlet are now supported, closing `issue 149`_. Huge
+- Gevent, eventlet, and greenlet are now supported, closing `issue 149`_.
+ The ``concurrency`` setting specifies the concurrency library in use. Huge
thanks to Peter Portante for initial implementation, and to Joe Jevnik for
the final insight that completed the work.
@@ -17,7 +37,7 @@ Change history for Coverage.py
``[coverage:run]`` section of setup.cfg. Finishes `issue 304`_.
- The ``report`` command can now show missing branches when reporting on branch
- coverage. Thanks, Steve Leonard. Closes `issue 230`.
+ coverage. Thanks, Steve Leonard. Closes `issue 230`_.
- The XML report now contains a <source> element, fixing `issue 94`_. Thanks
Stan Hu.
@@ -54,8 +74,8 @@ Change history for Coverage.py
.. _issue 305: https://bitbucket.org/ned/coveragepy/issue/305/pendingdeprecationwarning-the-imp-module
-3.7.1 -- 13 December 2013
--------------------------
+Version 3.7.1 --- 13 December 2013
+----------------------------------
- Improved the speed of HTML report generation by about 20%.
@@ -63,8 +83,8 @@ Change history for Coverage.py
so that it will actually find OS-installed static files.
-3.7 --- 6 October 2013
-----------------------
+Version 3.7 --- 6 October 2013
+------------------------------
- Added the ``--debug`` switch to ``coverage run``. It accepts a list of
options indicating the type of internal activity to log to stderr.
diff --git a/coverage/__init__.py b/coverage/__init__.py
index 5ae32aba..67dd6e88 100644
--- a/coverage/__init__.py
+++ b/coverage/__init__.py
@@ -17,9 +17,9 @@ from coverage.plugin import CoveragePlugin
coverage = Coverage
# Module-level functions. The original API to this module was based on
-# functions defined directly in the module, with a singleton of the coverage()
+# functions defined directly in the module, with a singleton of the Coverage()
# class. That design hampered programmability, so the current api uses
-# explicitly-created coverage objects. But for backward compatibility, here we
+# explicitly-created Coverage objects. But for backward compatibility, here we
# define the top-level functions to create the singleton when they are first
# called.
@@ -28,7 +28,7 @@ coverage = Coverage
_the_coverage = None
def _singleton_method(name):
- """Return a function to the `name` method on a singleton `coverage` object.
+ """Return a function to the `name` method on a singleton `Coverage` object.
The singleton object is created the first time one of these functions is
called.
@@ -42,19 +42,19 @@ def _singleton_method(name):
"""Singleton wrapper around a coverage method."""
global _the_coverage
if not _the_coverage:
- _the_coverage = coverage(auto_data=True)
+ _the_coverage = Coverage(auto_data=True)
return getattr(_the_coverage, name)(*args, **kwargs)
import inspect
- meth = getattr(coverage, name)
+ meth = getattr(Coverage, name)
args, varargs, kw, defaults = inspect.getargspec(meth)
argspec = inspect.formatargspec(args[1:], varargs, kw, defaults)
docstring = meth.__doc__
wrapper.__doc__ = ("""\
- A first-use-singleton wrapper around coverage.%(name)s.
+ A first-use-singleton wrapper around Coverage.%(name)s.
This wrapper is provided for backward compatibility with legacy code.
- New code should use coverage.%(name)s directly.
+ New code should use Coverage.%(name)s directly.
%(name)s%(argspec)s:
@@ -96,7 +96,7 @@ except KeyError:
# COPYRIGHT AND LICENSE
#
# Copyright 2001 Gareth Rees. All rights reserved.
-# Copyright 2004-2013 Ned Batchelder. All rights reserved.
+# Copyright 2004-2014 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/backward.py b/coverage/backward.py
index 9597449c..e839f6bd 100644
--- a/coverage/backward.py
+++ b/coverage/backward.py
@@ -148,7 +148,7 @@ def import_local_file(modname):
if SourceFileLoader:
mod = SourceFileLoader(modname, modfile).load_module()
else:
- for suff in imp.get_suffixes():
+ for suff in imp.get_suffixes(): # pragma: part covered
if suff[0] == '.py':
break
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index bd10d5a8..3d1f5f6a 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -19,9 +19,12 @@ class Opts(object):
'', '--branch', action='store_true',
help="Measure branch coverage in addition to statement coverage."
)
- coroutine = optparse.make_option(
- '', '--coroutine', action='store', metavar="LIB",
- help="Properly measure code using coroutines."
+ CONCURRENCY_CHOICES = ["thread", "gevent", "greenlet", "eventlet"]
+ concurrency = optparse.make_option(
+ '', '--concurrency', action='store', metavar="LIB",
+ choices=CONCURRENCY_CHOICES,
+ help="Properly measure code using a concurrency library. "
+ "Valid values are: %s." % ", ".join(CONCURRENCY_CHOICES)
)
debug = optparse.make_option(
'', '--debug', action='store', metavar="OPTS",
@@ -46,8 +49,8 @@ class Opts(object):
include = optparse.make_option(
'', '--include', action='store',
metavar="PAT1,PAT2,...",
- help="Include files only when their filename path matches one of "
- "these patterns. Usually needs quoting on the command line."
+ help="Include only files whose paths match one of these patterns."
+ "Accepts shell-style wildcards, which must be quoted."
)
pylib = optparse.make_option(
'-L', '--pylib', action='store_true',
@@ -59,17 +62,11 @@ class Opts(object):
help="Show line numbers of statements in each module that weren't "
"executed."
)
- old_omit = optparse.make_option(
- '-o', '--omit', action='store',
- metavar="PAT1,PAT2,...",
- help="Omit files when their filename matches one of these patterns. "
- "Usually needs quoting on the command line."
- )
omit = optparse.make_option(
'', '--omit', action='store',
metavar="PAT1,PAT2,...",
- help="Omit files when their filename matches one of these patterns. "
- "Usually needs quoting on the command line."
+ help="Omit files whose paths match one of these patterns. "
+ "Accepts shell-style wildcards, which must be quoted."
)
output_xml = optparse.make_option(
'-o', '', action='store', dest="outfile",
@@ -125,7 +122,7 @@ class CoverageOptionParser(optparse.OptionParser, object):
self.set_defaults(
actions=[],
branch=None,
- coroutine=None,
+ concurrency=None,
debug=None,
directory=None,
fail_under=None,
@@ -175,42 +172,17 @@ class CoverageOptionParser(optparse.OptionParser, object):
raise self.OptionParserError
-class ClassicOptionParser(CoverageOptionParser):
- """Command-line parser for coverage.py classic arguments."""
+class GlobalOptionParser(CoverageOptionParser):
+ """Command-line parser for coverage.py global option arguments."""
def __init__(self):
- super(ClassicOptionParser, self).__init__()
-
- self.add_action('-a', '--annotate', 'annotate')
- self.add_action('-b', '--html', 'html')
- self.add_action('-c', '--combine', 'combine')
- self.add_action('-e', '--erase', 'erase')
- self.add_action('-r', '--report', 'report')
- self.add_action('-x', '--execute', 'execute')
+ super(GlobalOptionParser, self).__init__()
self.add_options([
- Opts.directory,
Opts.help,
- Opts.ignore_errors,
- Opts.pylib,
- Opts.show_missing,
- Opts.old_omit,
- Opts.parallel_mode,
- Opts.timid,
Opts.version,
])
- def add_action(self, dash, dashdash, action_code):
- """Add a specialized option that is the action to execute."""
- option = self.add_option(dash, dashdash, action='callback',
- callback=self._append_action
- )
- option.action_code = action_code
-
- def _append_action(self, option, opt_unused, value_unused, parser):
- """Callback for an option that adds to the `actions` list."""
- parser.values.actions.append(option.action_code)
-
class CmdOptionParser(CoverageOptionParser):
"""Parse one of the new-style commands for coverage.py."""
@@ -320,7 +292,7 @@ CMDS = {
[
Opts.append,
Opts.branch,
- Opts.coroutine,
+ Opts.concurrency,
Opts.debug,
Opts.pylib,
Opts.parallel_mode,
@@ -370,7 +342,7 @@ class CoverageScript(object):
self.run_python_file = _run_python_file or run_python_file
self.run_python_module = _run_python_module or run_python_module
self.help_fn = _help_fn or self.help
- self.classic = False
+ self.global_option = False
self.coverage = None
@@ -387,11 +359,11 @@ class CoverageScript(object):
self.help_fn(topic='minimum_help')
return OK
- # The command syntax we parse depends on the first argument. Classic
- # syntax always starts with an option.
- self.classic = argv[0].startswith('-')
- if self.classic:
- parser = ClassicOptionParser()
+ # The command syntax we parse depends on the first argument. Global
+ # switch syntax always starts with an option.
+ self.global_option = argv[0].startswith('-')
+ if self.global_option:
+ parser = GlobalOptionParser()
else:
parser = CMDS.get(argv[0])
if not parser:
@@ -429,7 +401,7 @@ class CoverageScript(object):
omit = omit,
include = include,
debug = debug,
- coroutine = options.coroutine,
+ concurrency = options.concurrency,
)
if 'debug' in options.actions:
@@ -508,7 +480,7 @@ class CoverageScript(object):
"""
# Handle help.
if options.help:
- if self.classic:
+ if self.global_option:
self.help_fn(topic='help')
else:
self.help_fn(parser=parser)
@@ -656,53 +628,6 @@ def unglob_args(args):
HELP_TOPICS = {
# -------------------------
-'classic':
-r"""Coverage.py version %(__version__)s
-Measure, collect, and report on code coverage in Python programs.
-
-Usage:
-
-coverage -x [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...]
- Execute the module, passing the given command-line arguments, collecting
- coverage data. With the -p option, include the machine name and process
- id in the .coverage file name. With -L, measure coverage even inside the
- Python installed library, which isn't done by default. With --timid, use a
- simpler but slower trace method.
-
-coverage -e
- Erase collected coverage data.
-
-coverage -c
- Combine data from multiple coverage files (as created by -p option above)
- and store it into a single file representing the union of the coverage.
-
-coverage -r [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
- Report on the statement coverage for the given files. With the -m
- option, show line numbers of the statements that weren't executed.
-
-coverage -b -d DIR [-i] [-o DIR,...] [FILE1 FILE2 ...]
- Create an HTML report of the coverage of the given files. Each file gets
- its own page, with the file listing decorated to show executed, excluded,
- and missed lines.
-
-coverage -a [-d DIR] [-i] [-o DIR,...] [FILE1 FILE2 ...]
- Make annotated copies of the given files, marking statements that
- are executed with > and statements that are missed with !.
-
--d DIR
- Write output files for -b or -a to this directory.
-
--i Ignore errors while reporting or annotating.
-
--o DIR,...
- Omit reporting or annotating files when their filename path starts with
- a directory listed in the omit list.
- e.g. coverage -i -r -o c:\python25,lib\enthought\traits
-
-Coverage data is saved in the file .coverage by default. Set the
-COVERAGE_FILE environment variable to save it somewhere else.
-""",
-# -------------------------
'help': """\
Coverage.py, version %(__version__)s
Measure, collect, and report on code coverage in Python programs.
@@ -720,7 +645,6 @@ Commands:
xml Create an XML report of coverage results.
Use "coverage help <command>" for detailed help on any command.
-Use "coverage help classic" for help on older command syntax.
For more information, see %(__url__)s
""",
# -------------------------
diff --git a/coverage/codeunit.py b/coverage/codeunit.py
index c9ab2622..da617913 100644
--- a/coverage/codeunit.py
+++ b/coverage/codeunit.py
@@ -32,24 +32,16 @@ def code_unit_factory(morfs, file_locator, get_plugin=None):
if isinstance(morf, string_class) and get_plugin:
plugin = get_plugin(morf)
if plugin:
- klass = plugin.code_unit_class(morf)
- #klass = DjangoTracer # NOT REALLY! TODO
- # Hacked-in Mako support. Define COVERAGE_MAKO_PATH as a fragment of
- # the path that indicates the Python file is actually a compiled Mako
- # template. THIS IS TEMPORARY!
- #MAKO_PATH = os.environ.get('COVERAGE_MAKO_PATH')
- #if MAKO_PATH and isinstance(morf, string_class) and MAKO_PATH in morf:
- # # Super hack! Do mako both ways!
- # if 0:
- # cu = PythonCodeUnit(morf, file_locator)
- # cu.name += '_fako'
- # code_units.append(cu)
- # klass = MakoCodeUnit
- #elif isinstance(morf, string_class) and morf.endswith(".html"):
- # klass = DjangoCodeUnit
+ file_reporter = plugin.file_reporter(morf)
+ if file_reporter is None:
+ raise CoverageException(
+ "Plugin %r did not provide a file reporter for %r." % (
+ plugin.plugin_name, morf
+ )
+ )
else:
- klass = PythonCodeUnit
- code_units.append(klass(morf, file_locator))
+ file_reporter = PythonCodeUnit(morf, file_locator)
+ code_units.append(file_reporter)
return code_units
diff --git a/coverage/collector.py b/coverage/collector.py
index 85c8dc90..001bc3d3 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -23,6 +23,11 @@ except ImportError:
sys.exit(1)
CTracer = None
+try:
+ import __pypy__ # pylint: disable=import-error
+except ImportError:
+ __pypy__ = None
+
class Collector(object):
"""Collects trace data.
@@ -46,7 +51,7 @@ class Collector(object):
_collectors = []
def __init__(self,
- should_trace, check_include, timid, branch, warn, coroutine,
+ should_trace, check_include, timid, branch, warn, concurrency,
):
"""Create a collector.
@@ -68,7 +73,9 @@ class Collector(object):
`warn` is a warning function, taking a single string message argument,
to be used if a warning needs to be issued.
- TODO: `coroutine`
+ `concurrency` is a string indicating the concurrency library in use.
+ Valid values are "greenlet", "eventlet", "gevent", or "thread" (the
+ default).
"""
self.should_trace = should_trace
@@ -76,21 +83,21 @@ class Collector(object):
self.warn = warn
self.branch = branch
self.threading = None
- self.coroutine = coroutine
+ self.concurrency = concurrency
- self.coroutine_id_func = None
+ self.concur_id_func = None
try:
- if coroutine == "greenlet":
- import greenlet
- self.coroutine_id_func = greenlet.getcurrent
- elif coroutine == "eventlet":
- import eventlet.greenthread
- self.coroutine_id_func = eventlet.greenthread.getcurrent
- elif coroutine == "gevent":
- import gevent
- self.coroutine_id_func = gevent.getcurrent
- elif coroutine == "thread" or not coroutine:
+ if concurrency == "greenlet":
+ import greenlet # pylint: disable=import-error
+ self.concur_id_func = greenlet.getcurrent
+ elif concurrency == "eventlet":
+ import eventlet.greenthread # pylint: disable=import-error
+ self.concur_id_func = eventlet.greenthread.getcurrent
+ elif concurrency == "gevent":
+ import gevent # pylint: disable=import-error
+ self.concur_id_func = gevent.getcurrent
+ elif concurrency == "thread" or not concurrency:
# It's important to import threading only if we need it. If
# it's imported early, and the program being measured uses
# gevent, then gevent's monkey-patching won't work properly.
@@ -98,12 +105,12 @@ class Collector(object):
self.threading = threading
else:
raise CoverageException(
- "Don't understand coroutine=%s" % coroutine
+ "Don't understand concurrency=%s" % concurrency
)
except ImportError:
raise CoverageException(
- "Couldn't trace with coroutine=%s, "
- "the module isn't installed." % coroutine
+ "Couldn't trace with concurrency=%s, "
+ "the module isn't installed." % concurrency
)
self.reset()
@@ -134,7 +141,24 @@ 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
# None).
- self.should_trace_cache = {}
+ if __pypy__ is not None:
+ # Alex Gaynor said:
+ # should_trace_cache is a strictly growing key: once a key is in
+ # it, it never changes. Further, the keys used to access it are
+ # generally constant, given sufficient context. That is to say, at
+ # any given point _trace() is called, pypy is able to know the key.
+ # This is because the key is determined by the physical source code
+ # line, and that's invariant with the call site.
+ #
+ # This property of a dict with immutable keys, combined with
+ # call-site-constant keys is a match for PyPy's module dict,
+ # which is optimized for such workloads.
+ #
+ # This gives a 20% benefit on the workload described at
+ # https://bitbucket.org/pypy/pypy/issue/1871/10x-slower-than-cpython-under-coverage
+ self.should_trace_cache = __pypy__.newdict("module")
+ else:
+ self.should_trace_cache = {}
# Our active Tracers.
self.tracers = []
@@ -148,13 +172,13 @@ class Collector(object):
tracer.should_trace_cache = self.should_trace_cache
tracer.warn = self.warn
- if hasattr(tracer, 'coroutine_id_func'):
- tracer.coroutine_id_func = self.coroutine_id_func
- elif self.coroutine_id_func:
+ if hasattr(tracer, 'concur_id_func'):
+ tracer.concur_id_func = self.concur_id_func
+ elif self.concur_id_func:
raise CoverageException(
- "Can't support coroutine=%s with %s, "
+ "Can't support concurrency=%s with %s, "
"only threads are supported" % (
- self.coroutine, self.tracer_name(),
+ self.concurrency, self.tracer_name(),
)
)
@@ -204,6 +228,7 @@ class Collector(object):
# Install the tracer on this thread.
fn = self._start_tracer()
+ # Replay all the events from fullcoverage into the new trace function.
for args in traces0:
(frame, event, arg), lineno = args
try:
diff --git a/coverage/config.py b/coverage/config.py
index c671ef75..4d599ee7 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -2,6 +2,8 @@
import os, re, sys
from coverage.backward import string_class, iitems
+from coverage.misc import CoverageException
+
# In py3, # ConfigParser was renamed to the more-standard configparser
try:
@@ -140,7 +142,7 @@ class CoverageConfig(object):
# Defaults for [run]
self.branch = False
- self.coroutine = None
+ self.concurrency = None
self.cover_pylib = False
self.data_file = ".coverage"
self.parallel = False
@@ -173,15 +175,6 @@ class CoverageConfig(object):
# Options for plugins
self.plugin_options = {}
- def from_environment(self, env_var):
- """Read configuration from the `env_var` environment variable."""
- # Timidity: for nose users, read an environment variable. This is a
- # cheap hack, since the rest of the command line arguments aren't
- # recognized, but it solves some users' problems.
- env = os.environ.get(env_var, '')
- if env:
- self.timid = ('--timid' in env)
-
MUST_BE_LIST = ["omit", "include", "debug", "plugins"]
def from_args(self, **kwargs):
@@ -235,7 +228,7 @@ class CoverageConfig(object):
# [run]
('branch', 'run:branch', 'boolean'),
- ('coroutine', 'run:coroutine'),
+ ('concurrency', 'run:concurrency'),
('cover_pylib', 'run:cover_pylib', 'boolean'),
('data_file', 'run:data_file'),
('debug', 'run:debug', 'list'),
@@ -275,3 +268,37 @@ class CoverageConfig(object):
def get_plugin_options(self, plugin):
"""Get a dictionary of options for the plugin named `plugin`."""
return self.plugin_options.get(plugin, {})
+
+ # TODO: docs for this.
+ def __setitem__(self, option_name, value):
+ # Check all the hard-coded options.
+ for option_spec in self.CONFIG_FILE_OPTIONS:
+ attr, where = option_spec[:2]
+ if where == option_name:
+ setattr(self, attr, value)
+ return
+
+ # See if it's a plugin option.
+ plugin_name, _, key = option_name.partition(":")
+ if key and plugin_name in self.plugins:
+ self.plugin_options.setdefault(plugin_name, {})[key] = value
+ return
+
+ # If we get here, we didn't find the option.
+ raise CoverageException("No such option: %r" % option_name)
+
+ # TODO: docs for this.
+ def __getitem__(self, option_name):
+ # Check all the hard-coded options.
+ for option_spec in self.CONFIG_FILE_OPTIONS:
+ attr, where = option_spec[:2]
+ if where == option_name:
+ return getattr(self, attr)
+
+ # See if it's a plugin option.
+ plugin_name, _, key = option_name.partition(":")
+ if key and plugin_name in self.plugins:
+ return self.plugin_options.get(plugin_name, {}).get(key)
+
+ # If we get here, we didn't find the option.
+ raise CoverageException("No such option: %r" % option_name)
diff --git a/coverage/control.py b/coverage/control.py
index 86a2ae23..66979c33 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -33,7 +33,7 @@ class Coverage(object):
To use::
- from coverage import coverage
+ from coverage import Coverage
cov = Coverage()
cov.start()
@@ -45,7 +45,7 @@ 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,
- debug_file=None, coroutine=None, plugins=None):
+ concurrency=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
@@ -81,22 +81,14 @@ class Coverage(object):
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.
+ desired.
- `coroutine` is a string indicating the coroutining library being used
+ `concurrency` is a string indicating the concurrency library being used
in the measured code. Without this, coverage.py will get incorrect
- results. Valid strings are "greenlet", "eventlet", or "gevent", which
- are all equivalent. TODO: really?
-
- `plugins` TODO.
+ results. Valid strings are "greenlet", "eventlet", "gevent", or
+ "thread" (the default).
"""
- from coverage import __version__
-
- # A record of all the warnings that have been issued.
- self._warnings = []
-
# Build our configuration from a number of sources:
# 1: defaults:
self.config = CoverageConfig()
@@ -118,7 +110,6 @@ class Coverage(object):
self.config.from_file("setup.cfg", section_prefix="coverage:")
# 3: from environment variables:
- self.config.from_environment('COVERAGE_OPTIONS')
env_data_file = os.environ.get('COVERAGE_FILE')
if env_data_file:
self.config.data_file = env_data_file
@@ -128,22 +119,69 @@ class Coverage(object):
data_file=data_file, cover_pylib=cover_pylib, timid=timid,
branch=branch, parallel=bool_or_none(data_suffix),
source=source, omit=omit, include=include, debug=debug,
- coroutine=coroutine, plugins=plugins,
+ concurrency=concurrency,
)
+ self._debug_file = None
+ self._auto_data = auto_data
+ self._data_suffix = data_suffix
+
+ # The matchers for _should_trace.
+ self.source_match = None
+ self.pylib_match = self.cover_match = None
+ self.include_match = self.omit_match = None
+
+ # Is it ok for no data to be collected?
+ self._warn_no_data = True
+ self._warn_unimported_source = True
+
+ # A record of all the warnings that have been issued.
+ self._warnings = []
+
+ # Other instance attributes, set later.
+ self.omit = self.include = self.source = None
+ self.source_pkgs = self.file_locator = None
+ self.data = self.collector = None
+ self.plugins = self.file_tracers = None
+ self.pylib_dirs = self.cover_dir = None
+ self.data_suffix = self.run_suffix = None
+ self._exclude_re = None
+ self.debug = None
+
+ # State machine variables:
+ # Have we initialized everything?
+ self._inited = False
+ # Have we started collecting and not stopped it?
+ self._started = False
+ # Have we measured some data and not harvested it?
+ self._measured = False
+
+ def _init(self):
+ """Set all the initial state.
+
+ This is called by the public methods to initialize state. This lets us
+ construct a Coverage object, then tweak its state before this function
+ is called.
+
+ """
+ from coverage import __version__
+
+ if self._inited:
+ return
+
# Create and configure the debugging controller.
- self.debug = DebugControl(self.config.debug, debug_file or sys.stderr)
+ if self._debug_file is None:
+ self._debug_file = sys.stderr
+ self.debug = DebugControl(self.config.debug, self._debug_file)
# Load plugins
self.plugins = Plugins.load_plugins(self.config.plugins, self.config)
- self.trace_judges = []
+ self.file_tracers = []
for plugin in self.plugins:
- if plugin_implements(plugin, "trace_judge"):
- self.trace_judges.append(plugin)
- self.trace_judges.append(None) # The Python case.
-
- self.auto_data = auto_data
+ if plugin_implements(plugin, "file_tracer"):
+ self.file_tracers.append(plugin)
+ self.file_tracers.append(None) # The Python case.
# _exclude_re is a dict mapping exclusion list names to compiled
# regexes.
@@ -170,21 +208,21 @@ class Coverage(object):
timid=self.config.timid,
branch=self.config.branch,
warn=self._warn,
- coroutine=self.config.coroutine,
+ concurrency=self.config.concurrency,
)
# Suffixes are a bit tricky. We want to use the data suffix only when
# collecting data, not when combining data. So we save it as
# `self.run_suffix` now, and promote it to `self.data_suffix` if we
# find that we are collecting data later.
- if data_suffix or self.config.parallel:
- if not isinstance(data_suffix, string_class):
+ if self._data_suffix or self.config.parallel:
+ if not isinstance(self._data_suffix, string_class):
# if data_suffix=True, use .machinename.pid.random
- data_suffix = True
+ self._data_suffix = True
else:
- data_suffix = None
+ self._data_suffix = None
self.data_suffix = None
- self.run_suffix = data_suffix
+ self.run_suffix = self._data_suffix
# Create the data file. We do this at construction time so that the
# data file will be written into the directory where the process
@@ -206,31 +244,24 @@ class Coverage(object):
for m in (atexit, os, platform, random, socket, _structseq):
if m is not None and hasattr(m, "__file__"):
self.pylib_dirs.add(self._canonical_dir(m))
+ if _structseq and not hasattr(_structseq, '__file__'):
+ # PyPy 2.4 has no __file__ in the builtin modules, but the code
+ # objects still have the filenames. So dig into one to find
+ # the path to exclude.
+ structseq_file = _structseq.structseq_new.func_code.co_filename
+ self.pylib_dirs.add(self._canonical_dir(structseq_file))
# To avoid tracing the coverage code itself, we skip anything located
# where we are.
self.cover_dir = self._canonical_dir(__file__)
- # The matchers for _should_trace.
- self.source_match = None
- self.pylib_match = self.cover_match = None
- self.include_match = self.omit_match = None
-
# Set the reporting precision.
Numbers.set_precision(self.config.precision)
- # Is it ok for no data to be collected?
- self._warn_no_data = True
- self._warn_unimported_source = True
-
- # State machine variables:
- # Have we started collecting and not stopped it?
- self._started = False
- # Have we measured some data and not harvested it?
- self._measured = False
-
atexit.register(self._atexit)
+ self._inited = True
+
def _canonical_dir(self, morf):
"""Return the canonical directory of the module or file `morf`."""
morf_filename = PythonCodeUnit(morf, self.file_locator).filename
@@ -294,15 +325,27 @@ class Coverage(object):
disp.canonical_filename = canonical
# Try the plugins, see if they have an opinion about the file.
- for plugin in self.trace_judges:
+ for plugin in self.file_tracers:
if plugin:
- plugin.trace_judge(disp)
+ #plugin.trace_judge(disp)
+ file_tracer = plugin.file_tracer(canonical)
+ if file_tracer is not None:
+ file_tracer.plugin_name = plugin.plugin_name
+ disp.trace = True
+ disp.file_tracer = file_tracer
+ disp.source_filename = self.file_locator.canonical_filename(file_tracer.source_filename())
else:
disp.trace = True
disp.source_filename = canonical
+ file_tracer = None
if disp.trace:
- disp.plugin = plugin
-
+ if file_tracer:
+ disp.file_tracer = file_tracer
+ if disp.source_filename is None:
+ raise CoverageException(
+ "Plugin %r didn't set source_filename for %r" %
+ (plugin, disp.original_filename)
+ )
if disp.check_filters:
reason = self._check_include_omit_etc(disp.source_filename)
if reason:
@@ -425,10 +468,12 @@ class Coverage(object):
`usecache` is true or false, whether to read and write data on disk.
"""
+ self._init()
self.data.usefile(usecache)
def load(self):
"""Load previously-collected coverage data from the data file."""
+ self._init()
self.collector.reset()
self.data.read()
@@ -442,11 +487,12 @@ class Coverage(object):
process might not shut down cleanly.
"""
+ self._init()
if self.run_suffix:
# Calling start() means we're running code, so use the run_suffix
# as the data_suffix when we eventually save the data.
self.data_suffix = self.run_suffix
- if self.auto_data:
+ if self._auto_data:
self.load()
# Create the matchers we need for _should_trace
@@ -478,14 +524,15 @@ class Coverage(object):
def stop(self):
"""Stop measuring code coverage."""
+ if self._started:
+ self.collector.stop()
self._started = False
- self.collector.stop()
def _atexit(self):
"""Clean up on process shutdown."""
if self._started:
self.stop()
- if self.auto_data:
+ if self._auto_data:
self.save()
def erase(self):
@@ -495,11 +542,13 @@ class Coverage(object):
discarding the data file.
"""
+ self._init()
self.collector.reset()
self.data.erase()
def clear_exclude(self, which='exclude'):
"""Clear the exclude list."""
+ self._init()
setattr(self.config, which + "_list", [])
self._exclude_regex_stale()
@@ -518,6 +567,7 @@ class Coverage(object):
is marked for special treatment during reporting.
"""
+ self._init()
excl_list = getattr(self.config, which + "_list")
excl_list.append(regex)
self._exclude_regex_stale()
@@ -540,10 +590,12 @@ class Coverage(object):
that are available, and their meaning.
"""
+ self._init()
return getattr(self.config, which + "_list")
def save(self):
"""Save the collected coverage data to the data file."""
+ self._init()
data_suffix = self.data_suffix
if data_suffix is True:
# If data_suffix was a simple true value, then make a suffix with
@@ -551,10 +603,9 @@ class Coverage(object):
# `save()` at the last minute so that the pid will be correct even
# if the process forks.
extra = ""
- if _TEST_NAME_FILE:
- f = open(_TEST_NAME_FILE)
- test_name = f.read()
- f.close()
+ if _TEST_NAME_FILE: # pragma: debugging
+ with open(_TEST_NAME_FILE) as f:
+ test_name = f.read()
extra = "." + test_name
data_suffix = "%s%s.%s.%06d" % (
socket.gethostname(), extra, os.getpid(),
@@ -572,6 +623,7 @@ class Coverage(object):
current measurements.
"""
+ self._init()
aliases = None
if self.config.paths:
aliases = PathAliases(self.file_locator)
@@ -587,6 +639,7 @@ class Coverage(object):
Also warn about various problems collecting data.
"""
+ self._init()
if not self._measured:
return
@@ -644,6 +697,7 @@ class Coverage(object):
coverage data.
"""
+ self._init()
analysis = self._analyze(morf)
return (
analysis.filename,
@@ -794,6 +848,7 @@ class Coverage(object):
import coverage as covmod
+ self._init()
try:
implementation = platform.python_implementation()
except AttributeError:
@@ -843,7 +898,7 @@ class FileDisposition(object):
self.check_filters = True
self.trace = False
self.reason = ""
- self.plugin = None
+ self.file_tracer = None
def debug_message(self):
"""Produce a debugging message explaining the outcome."""
diff --git a/coverage/misc.py b/coverage/misc.py
index 6962ae32..a653bb62 100644
--- a/coverage/misc.py
+++ b/coverage/misc.py
@@ -55,7 +55,7 @@ def format_lines(statements, lines):
return ret
-def short_stack():
+def short_stack(): # pragma: debugging
"""Return a string summarizing the call stack."""
stack = inspect.stack()[:0:-1]
return "\n".join("%30s : %s @%d" % (t[3],t[1],t[2]) for t in stack)
diff --git a/coverage/parser.py b/coverage/parser.py
index c5e95baa..e7b9c029 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -413,7 +413,7 @@ class ByteParser(object):
for _, l in bp._bytes_lines():
yield l
- def _block_stack_repr(self, block_stack):
+ def _block_stack_repr(self, block_stack): # pragma: debugging
"""Get a string version of `block_stack`, for debugging."""
blocks = ", ".join(
"(%s, %r)" % (dis.opname[b[0]], b[1]) for b in block_stack
@@ -552,7 +552,7 @@ class ByteParser(object):
#self.validate_chunks(chunks)
return chunks
- def validate_chunks(self, chunks):
+ def validate_chunks(self, chunks): # pragma: debugging
"""Validate the rule that chunks have a single entrance."""
# starts is the entrances to the chunks
starts = set(ch.byte for ch in chunks)
diff --git a/coverage/plugin.py b/coverage/plugin.py
index 35be41a9..5d1c5306 100644
--- a/coverage/plugin.py
+++ b/coverage/plugin.py
@@ -2,28 +2,33 @@
import sys
+from coverage.misc import CoverageException
+
class CoveragePlugin(object):
"""Base class for coverage.py plugins."""
def __init__(self, options):
self.options = options
- def trace_judge(self, disposition):
- """Decide whether to trace this file with this plugin.
+ def file_tracer(self, filename):
+ """Return a FileTracer object for this file."""
+ return None
+
+ def file_reporter(self, filename):
+ """Return the FileReporter class to use for filename.
- Set disposition.trace to True if this plugin should trace this file.
- May also set other attributes in `disposition`.
+ This will only be invoked if `filename` returns non-None from
+ `file_tracer`. It's an error to return None.
"""
- return None
+ raise Exception("Plugin %r needs to implement file_reporter" % self.plugin_name)
- def source_file_name(self, filename):
- """Return the source name for a given Python filename.
- Can return None if tracing shouldn't continue.
+class FileTracer(object):
+ """Support needed for files during the tracing phase."""
- """
- return filename
+ def source_filename(self):
+ return "xyzzy"
def dynamic_source_file_name(self):
"""Returns a callable that can return a source name for a frame.
@@ -38,9 +43,16 @@ class CoveragePlugin(object):
"""
return None
- def code_unit_class(self, morf):
- """Return the CodeUnit class to use for a module or filename."""
- return None
+ def line_number_range(self, frame):
+ """Given a call frame, return the range of source line numbers."""
+ lineno = frame.f_lineno
+ return lineno, lineno
+
+
+class FileReporter(object):
+ """Support needed for files during the reporting phase."""
+ def __init__(self, filename):
+ self.filename = filename
class Plugins(object):
@@ -67,7 +79,7 @@ class Plugins(object):
if plugin_class:
options = config.get_plugin_options(module)
plugin = plugin_class(options)
- plugin.__name__ = module
+ plugin.plugin_name = module
plugins.order.append(plugin)
plugins.names[module] = plugin
diff --git a/coverage/pytracer.py b/coverage/pytracer.py
index 7563ae11..84071bb1 100644
--- a/coverage/pytracer.py
+++ b/coverage/pytracer.py
@@ -33,7 +33,7 @@ class PyTracer(object):
# The threading module to use, if any.
self.threading = None
- self.plugin = []
+ self.file_tracer = []
self.cur_file_dict = []
self.last_line = [0]
@@ -62,7 +62,7 @@ class PyTracer(object):
if self.arcs and self.cur_file_dict:
pair = (self.last_line, -self.last_exc_firstlineno)
self.cur_file_dict[pair] = None
- self.plugin, self.cur_file_dict, self.last_line = (
+ self.file_tracer, self.cur_file_dict, self.last_line = (
self.data_stack.pop()
)
self.last_exc_back = None
@@ -71,7 +71,7 @@ class PyTracer(object):
# Entering a new function context. Decide if we should trace
# in this file.
self.data_stack.append(
- (self.plugin, self.cur_file_dict, self.last_line)
+ (self.file_tracer, self.cur_file_dict, self.last_line)
)
filename = frame.f_code.co_filename
disp = self.should_trace_cache.get(filename)
@@ -79,12 +79,12 @@ class PyTracer(object):
disp = self.should_trace(filename, frame)
self.should_trace_cache[filename] = disp
- self.plugin = None
+ self.file_tracer = None
self.cur_file_dict = None
if disp.trace:
tracename = disp.source_filename
- if disp.plugin:
- dyn_func = disp.plugin.dynamic_source_file_name()
+ if disp.file_tracer:
+ dyn_func = disp.file_tracer.dynamic_source_file_name()
if dyn_func:
tracename = dyn_func(tracename, frame)
if tracename:
@@ -95,17 +95,17 @@ class PyTracer(object):
if tracename:
if tracename not in self.data:
self.data[tracename] = {}
- if disp.plugin:
- self.plugin_data[tracename] = disp.plugin.__name__
+ if disp.file_tracer:
+ self.plugin_data[tracename] = disp.file_tracer.plugin_name
self.cur_file_dict = self.data[tracename]
- self.plugin = disp.plugin
+ self.file_tracer = disp.file_tracer
# Set the last_line to -1 because the next arc will be entering a
# code block, indicated by (-1, n).
self.last_line = -1
elif event == 'line':
# Record an executed line.
- if self.plugin:
- lineno_from, lineno_to = self.plugin.line_number_range(frame)
+ if self.file_tracer:
+ lineno_from, lineno_to = self.file_tracer.line_number_range(frame)
else:
lineno_from, lineno_to = frame.f_lineno, frame.f_lineno
if lineno_from != -1:
@@ -123,7 +123,7 @@ class PyTracer(object):
first = frame.f_code.co_firstlineno
self.cur_file_dict[(self.last_line, -first)] = None
# Leaving this function, pop the filename stack.
- self.plugin, self.cur_file_dict, self.last_line = (
+ self.file_tracer, self.cur_file_dict, self.last_line = (
self.data_stack.pop()
)
elif event == 'exception':
diff --git a/coverage/results.py b/coverage/results.py
index 6cbcbfc8..4449de56 100644
--- a/coverage/results.py
+++ b/coverage/results.py
@@ -11,9 +11,8 @@ class Analysis(object):
def __init__(self, cov, code_unit):
self.coverage = cov
- self.code_unit = code_unit
- self.filename = self.code_unit.filename
+ self.filename = code_unit.filename
self.parser = code_unit.get_parser(
exclude=self.coverage._exclude_regex('exclude')
)
diff --git a/coverage/test_helpers.py b/coverage/test_helpers.py
index efe68dcd..665593a2 100644
--- a/coverage/test_helpers.py
+++ b/coverage/test_helpers.py
@@ -132,8 +132,13 @@ class StdStreamCapturingMixin(TestCase):
return self.captured_stderr.getvalue()
-class TempDirMixin(TestCase):
- """A test case mixin that creates a temp directory and files in it."""
+class TempDirMixin(SysPathAwareMixin, ModuleAwareMixin, TestCase):
+ """A test case mixin that creates a temp directory and files in it.
+
+ Includes SysPathAwareMixin and ModuleAwareMixin, because making and using
+ temp dirs like this will also need that kind of isolation.
+
+ """
# Our own setting: most of these tests run in their own temp directory.
run_in_temp_dir = True
@@ -143,12 +148,8 @@ class TempDirMixin(TestCase):
if self.run_in_temp_dir:
# Create a temporary directory.
- noise = str(random.random())[2:]
- self.temp_root = os.path.join(tempfile.gettempdir(), 'test_cover')
- self.temp_dir = os.path.join(self.temp_root, noise)
- os.makedirs(self.temp_dir)
- self.old_dir = os.getcwd()
- os.chdir(self.temp_dir)
+ self.temp_dir = self.make_temp_dir("test_cover")
+ self.chdir(self.temp_dir)
# Modules should be importable from this temp directory. We don't
# use '' because we make lots of different temp directories and
@@ -161,15 +162,24 @@ class TempDirMixin(TestCase):
class_behavior.test_method_made_any_files = False
class_behavior.temp_dir = self.run_in_temp_dir
- self.addCleanup(self.cleanup_temp_dir)
+ self.addCleanup(self.check_behavior)
- def cleanup_temp_dir(self):
- """Clean up the temp directories we made."""
+ def make_temp_dir(self, slug="test_cover"):
+ """Make a temp directory that is cleaned up when the test is done."""
+ name = "%s_%08d" % (slug, random.randint(0, 99999999))
+ temp_dir = os.path.join(tempfile.gettempdir(), name)
+ os.makedirs(temp_dir)
+ self.addCleanup(shutil.rmtree, temp_dir)
+ return temp_dir
- if self.run_in_temp_dir:
- # Get rid of the temporary directory.
- os.chdir(self.old_dir)
- shutil.rmtree(self.temp_root)
+ def chdir(self, new_dir):
+ """Change directory, and change back when the test is done."""
+ old_dir = os.getcwd()
+ os.chdir(new_dir)
+ self.addCleanup(os.chdir, old_dir)
+
+ def check_behavior(self):
+ """Check that we did the right things."""
class_behavior = self.class_behavior()
if class_behavior.test_method_made_any_files:
diff --git a/coverage/tracer.c b/coverage/tracer.c
index 5bf5c462..5a463fcb 100644
--- a/coverage/tracer.c
+++ b/coverage/tracer.c
@@ -81,7 +81,7 @@ typedef struct {
/* Python objects manipulated directly by the Collector class. */
PyObject * should_trace;
PyObject * warn;
- PyObject * coroutine_id_func;
+ PyObject * concur_id_func;
PyObject * data;
PyObject * plugin_data;
PyObject * should_trace_cache;
@@ -104,8 +104,8 @@ typedef struct {
(None).
*/
- DataStack data_stack; /* Used if we aren't doing coroutines. */
- PyObject * data_stack_index; /* Used if we are doing coroutines. */
+ DataStack data_stack; /* Used if we aren't doing concurrency. */
+ PyObject * data_stack_index; /* Used if we are doing concurrency. */
DataStack * data_stacks;
int data_stacks_alloc;
int data_stacks_used;
@@ -191,7 +191,7 @@ CTracer_init(CTracer *self, PyObject *args_unused, PyObject *kwds_unused)
self->should_trace = NULL;
self->warn = NULL;
- self->coroutine_id_func = NULL;
+ self->concur_id_func = NULL;
self->data = NULL;
self->plugin_data = NULL;
self->should_trace_cache = NULL;
@@ -234,7 +234,7 @@ CTracer_dealloc(CTracer *self)
Py_XDECREF(self->should_trace);
Py_XDECREF(self->warn);
- Py_XDECREF(self->coroutine_id_func);
+ Py_XDECREF(self->concur_id_func);
Py_XDECREF(self->data);
Py_XDECREF(self->plugin_data);
Py_XDECREF(self->should_trace_cache);
@@ -327,18 +327,18 @@ CTracer_record_pair(CTracer *self, int l1, int l2)
static int
CTracer_set_pdata_stack(CTracer *self)
{
- if (self->coroutine_id_func != Py_None) {
+ if (self->concur_id_func != Py_None) {
PyObject * co_obj = NULL;
PyObject * stack_index = NULL;
long the_index = 0;
- co_obj = PyObject_CallObject(self->coroutine_id_func, NULL);
+ co_obj = PyObject_CallObject(self->concur_id_func, NULL);
if (co_obj == NULL) {
return RET_ERROR;
}
stack_index = PyDict_GetItem(self->data_stack_index, co_obj);
if (stack_index == NULL) {
- /* A new coroutine object. Make a new data stack. */
+ /* A new concurrency object. Make a new data stack. */
the_index = self->data_stacks_used;
stack_index = MyInt_FromLong(the_index);
if (PyDict_SetItem(self->data_stack_index, co_obj, stack_index) < 0) {
@@ -505,7 +505,7 @@ CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unuse
if (MyText_Check(tracename)) {
PyObject * file_data = PyDict_GetItem(self->data, tracename);
- PyObject * disp_plugin = NULL;
+ PyObject * disp_file_tracer = NULL;
PyObject * disp_plugin_name = NULL;
if (file_data == NULL) {
@@ -527,16 +527,16 @@ CTracer_trace(CTracer *self, PyFrameObject *frame, int what, PyObject *arg_unuse
if (self->plugin_data != NULL) {
/* If the disposition mentions a plugin, record that. */
- disp_plugin = PyObject_GetAttrString(disposition, "plugin");
- if (disp_plugin == NULL) {
+ disp_file_tracer = PyObject_GetAttrString(disposition, "file_tracer");
+ if (disp_file_tracer == NULL) {
STATS( self->stats.errors++; )
Py_DECREF(tracename);
Py_DECREF(disposition);
return RET_ERROR;
}
- if (disp_plugin != Py_None) {
- disp_plugin_name = PyObject_GetAttrString(disp_plugin, "__name__");
- Py_DECREF(disp_plugin);
+ if (disp_file_tracer != Py_None) {
+ disp_plugin_name = PyObject_GetAttrString(disp_file_tracer, "plugin_name");
+ Py_DECREF(disp_file_tracer);
if (disp_plugin_name == NULL) {
STATS( self->stats.errors++; )
Py_DECREF(tracename);
@@ -781,8 +781,8 @@ CTracer_members[] = {
{ "warn", T_OBJECT, offsetof(CTracer, warn), 0,
PyDoc_STR("Function for issuing warnings.") },
- { "coroutine_id_func", T_OBJECT, offsetof(CTracer, coroutine_id_func), 0,
- PyDoc_STR("Function for determining coroutine context") },
+ { "concur_id_func", T_OBJECT, offsetof(CTracer, concur_id_func), 0,
+ PyDoc_STR("Function for determining concurrency context") },
{ "data", T_OBJECT, offsetof(CTracer, data), 0,
PyDoc_STR("The raw dictionary of trace data.") },
diff --git a/coverage/version.py b/coverage/version.py
index 27c2f6b1..fe3f44fa 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__ = "4.0a0" # see detailed history in CHANGES.txt
+__version__ = "4.0a1" # see detailed history in CHANGES.txt
__url__ = "http://nedbatchelder.com/code/coverage"
if max(__version__).isalpha():
diff --git a/doc/_ext/px_xlator.py b/doc/_ext/px_xlator.py
index 6085b697..41619119 100644
--- a/doc/_ext/px_xlator.py
+++ b/doc/_ext/px_xlator.py
@@ -1,4 +1,5 @@
from docutils import nodes
+from sphinx import addnodes
from sphinx.writers.html import SmartyPantsHTMLTranslator
from sphinx.builders.html import StandaloneHTMLBuilder
import os
@@ -10,7 +11,7 @@ def setup(app):
BaseHtmlXlator = SmartyPantsHTMLTranslator
class PxTranslator(BaseHtmlXlator):
"""Adjust the HTML translator into a .px translator.
-
+
"""
def __init__(self, *args, **kwargs):
@@ -45,11 +46,11 @@ class PxTranslator(BaseHtmlXlator):
when, what = hist.split(',', 1)
self.body.append("<what when='%s'>%s</what>\n" % (when, self.encode(what.strip())))
self.body.append("</history>\n")
-
+
if "b" in self.builder.config.release:
self.body.append("""
<box>
- These docs are for a beta release, %s.
+ These docs are for a beta release, %s.
For the latest released version, see <a href='/code/coverage'>coverage.py</a>.
</box>
""" % self.builder.config.release)
@@ -76,8 +77,15 @@ class PxTranslator(BaseHtmlXlator):
raise nodes.SkipNode
def visit_desc_parameterlist(self, node):
+ # I'm overriding this method so that the base class doesn't write out
+ # <big>(</big>, but I also have to handle the logic from the base class,
+ # so most of this is just copied from sphinx/writers/html.py...
self.body.append('(')
self.first_param = 1
+ self.optional_param_level = 0
+ # How many required parameters are left.
+ self.required_params_left = sum([isinstance(c, addnodes.desc_parameter)
+ for c in node.children])
self.param_separator = node.child_text_separator
def depart_desc_parameterlist(self, node):
self.body.append(')')
@@ -91,10 +99,10 @@ class PxBuilder(StandaloneHTMLBuilder):
self.config.html_translator_class = "px_xlator.PxTranslator"
super(PxBuilder, self).init()
-
+
self.out_suffix = '.px'
self.link_suffix = '.html'
-
+
if "b" in self.config.release:
self.px_uri = "/code/coverage/beta/"
else:
diff --git a/doc/changes.rst b/doc/changes.rst
index 3ddf8896..b9980af8 100644
--- a/doc/changes.rst
+++ b/doc/changes.rst
@@ -35,6 +35,31 @@ history, see the `CHANGES.txt`_ file in the source tree.
.. _CHANGES.txt: http://bitbucket.org/ned/coveragepy/src/tip/CHANGES.txt
+.. _changes_40:
+
+Version 4.0 pre-release --- 27 September 2014
+---------------------------------------------
+
+- Python versions supported are now CPython 2.6, 2.7, 3.2, 3.3, and 3.4, and
+ PyPy 2.2.
+
+- Gevent, eventlet, and greenlet are now supported, closing `issue 149`_.
+ The ``concurrency`` setting specifies the concurrency library in use. Huge
+ thanks to Peter Portante for initial implementation, and to Joe Jevnik for
+ the final insight that completed the work.
+
+- Options are now also read from a setup.cfg file, if any. Sections are
+ prefixed with "coverage:", so the ``[run]`` options will be read from the
+ ``[coverage:run]`` section of setup.cfg. Finishes `issue 304`_.
+
+- The ``report`` command can now show missing branches when reporting on branch
+ coverage. Thanks, Steve Leonard. Closes `issue 230`_.
+
+.. _issue 149: https://bitbucket.org/ned/coveragepy/issue/149/coverage-gevent-looks-broken
+.. _issue 230: https://bitbucket.org/ned/coveragepy/issue/230/show-line-no-for-missing-branches-in
+.. _issue 304: https://bitbucket.org/ned/coveragepy/issue/304/attempt-to-get-configuration-from-setupcfg
+
+
.. _changes_371:
Version 3.7.1 --- 13 December 2013
diff --git a/doc/cmd.rst b/doc/cmd.rst
index 49062b31..cd6ae955 100644
--- a/doc/cmd.rst
+++ b/doc/cmd.rst
@@ -17,6 +17,7 @@ Coverage command line usage
:history: 20120807T211600, Clarified the combine rules.
:history: 20121003T074600, Fixed an option reference, https://bitbucket.org/ned/coveragepy/issue/200/documentation-mentions-output-xml-instead
:history: 20121117T091000, Added command aliases.
+:history: 20140924T193000, Added --concurrency
.. highlight:: console
@@ -95,6 +96,16 @@ but before the program invocation::
$ coverage run --source=dir1,dir2 my_program.py arg1 arg2
$ coverage run --source=dir1,dir2 -m packagename.modulename arg1 arg2
+Coverage can measure multi-threaded programs by default. If you are using
+more exotic concurrency, with the `greenlet`_, `eventlet`_, or `gevent`_
+libraries, then coverage will get very confused. Use the ``--concurrency``
+switch to properly measure programs using these libraries. Give it a value of
+``greenlet``, ``eventlet``, or ``gevent``.
+
+.. _greenlet: http://greenlet.readthedocs.org/en/latest/
+.. _gevent: http://www.gevent.org/
+.. _eventlet: http://eventlet.net/
+
By default, coverage does not measure code installed with the Python
interpreter, for example, the standard library. If you want to measure that
code as well as your own, add the ``-L`` flag.
@@ -102,9 +113,7 @@ code as well as your own, add the ``-L`` flag.
If your coverage results seem to be overlooking code that you know has been
executed, try running coverage again with the ``--timid`` flag. This uses a
simpler but slower trace method. Projects that use DecoratorTools, including
-TurboGears, will need to use ``--timid`` to get correct results. This option
-can also be enabled by setting the environment variable COVERAGE_OPTIONS to
-``--timid``.
+TurboGears, will need to use ``--timid`` to get correct results.
If you are measuring coverage in a multi-process program, or across a number of
machines, you'll want the ``--parallel-mode`` switch to keep the data separate
@@ -266,6 +275,19 @@ The ``-m`` flag also shows the line numbers of missing statements::
-------------------------------------------------------
TOTAL 91 12 87%
+If you are using branch coverage, then branch statistics will be reported in
+the Branch and BrMiss columns, the Missing column will detail the missed
+branches::
+
+ $ coverage report -m
+ Name Stmts Miss Branch BrMiss Cover Missing
+ ---------------------------------------------------------------------
+ my_program 20 4 10 2 80% 33-35, 36->38, 39
+ my_module 15 2 3 0 86% 8, 12
+ my_other_module 56 6 5 1 89% 17-23, 40->45
+ ---------------------------------------------------------------------
+ TOTAL 91 12 18 3 87%
+
You can restrict the report to only certain files by naming them on the
command line::
diff --git a/doc/conf.py b/doc/conf.py
index 41a00e3e..16f566f0 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -40,16 +40,16 @@ master_doc = 'index'
# General information about the project.
project = u'coverage.py'
-copyright = u'2009\N{EN DASH}2013, Ned Batchelder'
+copyright = u'2009\N{EN DASH}2014, Ned Batchelder'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '3.7.1'
+version = '4.0'
# The full version, including alpha/beta/rc tags.
-release = '3.7.1'
+release = '4.0a1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/doc/config.rst b/doc/config.rst
index 882fc777..cec14e0f 100644
--- a/doc/config.rst
+++ b/doc/config.rst
@@ -10,6 +10,7 @@ Configuration files
:history: 20110604T184400, updated for 3.5.
:history: 20110827T212700, updated for 3.5.1
:history: 20130926T222300, updated for 3.6.1
+:history: 20140925T064700, updated for 4.0a1
Coverage.py options can be specified in a configuration file. This makes it
@@ -26,6 +27,14 @@ configuration file are tied to your source code and how it should be measured,
so it should be stored with your source, and checked into source control,
rather than put in your home directory.
+A different name for the configuration file can be specified with the
+``--rcfile=FILE`` command line option.
+
+Coverage.py will read settings from a ``setup.cfg`` file if no other
+configuration file is used. In this case, the section names have "coverage:"
+prefixed, so the ``[run]`` options described below will be found in the
+``[coverage:run]`` section of ``setup.cfg``.
+
Syntax
------
@@ -90,6 +99,15 @@ to more than one command.
``cover_pylib`` (boolean, default False): whether to measure the Python
standard library.
+``concurrency`` (string, default "thread"): the name of the concurrency
+library in use by the product code. If your program uses `gevent`_,
+`greenlet`_, or `eventlet`_, you must name that library in this option, or
+coverage will produce very wrong results.
+
+.. _greenlet: http://greenlet.readthedocs.org/en/latest/
+.. _gevent: http://www.gevent.org/
+.. _eventlet: http://eventlet.net/
+
``data_file`` (string, default ".coverage"): the name of the data file to use
for storing or reporting coverage.
diff --git a/doc/index.rst b/doc/index.rst
index 3a0d9308..d557665c 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -31,6 +31,7 @@ coverage.py
:history: 20130105T174000, Updated for 3.6
:history: 20131005T210000, Updated for 3.7
:history: 20131212T213300, Updated for 3.7.1
+:history: 20140924T073000, Updated for 4.0a1
Coverage.py is a tool for measuring code coverage of Python programs. It
@@ -44,14 +45,14 @@ not.
.. ifconfig:: not prerelease
The latest version is coverage.py 3.7.1, released 13 December 2013.
- It is supported on Python versions 2.3 through 3.4, and PyPy 2.1.
+ It is supported on Python versions 2.6 through 3.4, and PyPy 2.2.
.. ifconfig:: prerelease
- The latest version is coverage.py 3.6b3, released 29 December 2012.
- It is supported on Python versions 2.3 through 3.3, and PyPy 1.9.
+ The latest version is coverage.py 4.0a1, released 27 September 2014.
+ It is supported on Python versions 2.6 through 3.4, and PyPy 2.2 through 2.4.
**This is a pre-release build. The usual warnings about possible bugs apply.**
- The latest stable version is coverage.py 3.5.3, `described here`_.
+ The latest stable version is coverage.py 3.7.1, `described here`_.
.. _described here: http://nedbatchelder.com/code/coverage
diff --git a/doc/install.rst b/doc/install.rst
index bc8097a2..7dfa5668 100644
--- a/doc/install.rst
+++ b/doc/install.rst
@@ -20,29 +20,36 @@ Installation
:history: 20130105T174400, updated for 3.6.
:history: 20131005T210600, updated for 3.7.
:history: 20131212T213500, updated for 3.7.1.
+:history: 20140927T102700, updated for 4.0a1.
.. highlight:: console
+
.. _coverage_pypi: http://pypi.python.org/pypi/coverage
.. _setuptools: http://pypi.python.org/pypi/setuptools
.. _Distribute: http://packages.python.org/distribute/
-Installing coverage.py is done in the usual ways. You must have `setuptools`_
-or `Distribute`_ installed already, and then you:
+Installing coverage.py is done in the usual ways. The simplest way is with
+pip::
-#. Download the appropriate kit from the
- `coverage page on the Python Package Index`__.
+ $ pip install coverage
-#. Run ``python setup.py install``.
+.. ifconfig:: prerelease
-or, use::
+ To install a pre-release version, you will need to specify ``--pre``::
- $ pip install coverage
+ $ pip install --pre coverage
-or even::
- $ easy_install coverage
+The alternate old-school technique is:
+
+#. Install (or already have installed) `setuptools`_ or `Distribute`_.
+
+#. Download the appropriate kit from the
+ `coverage page on the Python Package Index`__.
+
+#. Run ``python setup.py install``.
.. __: coverage_pypi_
@@ -75,9 +82,9 @@ If all went well, you should be able to open a command prompt, and see coverage
installed properly::
$ coverage --version
- Coverage.py, version 3.7.1. http://nedbatchelder.com/code/coverage
+ Coverage.py, version 4.0a1. http://nedbatchelder.com/code/coverage
You can also invoke coverage as a module::
$ python -m coverage --version
- Coverage.py, version 3.7.1. http://nedbatchelder.com/code/coverage
+ Coverage.py, version 4.0a1. http://nedbatchelder.com/code/coverage
diff --git a/doc/python-coverage.1.txt b/doc/python-coverage.1.txt
index 89c70b53..f79f33d8 100644
--- a/doc/python-coverage.1.txt
+++ b/doc/python-coverage.1.txt
@@ -8,7 +8,7 @@ measure code coverage of Python program execution
:Author: Ned Batchelder <ned@nedbatchelder.com>
:Author: |author|
-:Date: 2013-10-10
+:Date: 2014-09-27
:Copyright: BSD license, attribution and disclaimer required.
:Manual section: 1
:Manual group: Coverage
@@ -76,8 +76,8 @@ GLOBAL OPTIONS
Usually needs quoting on the command line.
**--include** `PATTERN` [ , ... ]
- Include files only when their filename path matches one of these
- PATTERNs. Usually needs quoting on the command line.
+ Include only files whose paths match one of these
+ PATTERNs. Accepts shell-style wildcards, which must be quoted.
COMMAND REFERENCE
@@ -160,6 +160,10 @@ COMMAND REFERENCE
\--branch
Measure branch coverage in addition to statement coverage.
+ \--concurrency `LIB`
+ Properly measure code using a concurrency library. Valid values are:
+ thread, gevent, greenlet, eventlet.
+
\--debug `DEBUGOPT`,...
Debug options `DEBUGOPT`, separated by commas.
diff --git a/howto.txt b/howto.txt
index 3367a063..932a16a3 100644
--- a/howto.txt
+++ b/howto.txt
@@ -7,6 +7,7 @@
- Pythons 2.6, 2.7, 3.2, 3.3, 3.4
- Version number in coverage/version.py
- 3.1a1, 3.1b1, 3.1c1, 3.1
+- Copyright date in coverage/__init__.py
- Update CHANGES.txt, including release date.
- Update docstring in setup.py, including "New in x.y:"
- Update docs
@@ -17,7 +18,7 @@
- Don't forget the man page: doc/python-coverage.1.txt
- Generate new sample_html to get the latest, incl footer version number:
python setup.py develop
- cd C:\ned\cog\trunk
+ cd ~/cog/trunk
rm -rf htmlcov
coverage run --branch --source=cogapp -m cogapp.test_cogapp CogTestsInMemory
coverage html
diff --git a/metacov.ini b/metacov.ini
index f94b834c..3bce01e2 100644
--- a/metacov.ini
+++ b/metacov.ini
@@ -6,17 +6,6 @@ parallel = true
source =
$COVERAGE_HOME/coverage
$COVERAGE_HOME/tests
- $COVERAGE_HOME/.tox/py26/lib/python2.6/site-packages/coverage
- $COVERAGE_HOME/.tox/py27/lib/python2.7/site-packages/coverage
- $COVERAGE_HOME/.tox/py32/lib/python3.2/site-packages/coverage
- $COVERAGE_HOME/.tox/py33/lib/python3.3/site-packages/coverage
- $COVERAGE_HOME/.tox/py34/lib/python3.4/site-packages/coverage
- $COVERAGE_HOME/.tox/pypy/site-packages/coverage
- $COVERAGE_HOME\.tox\py26\Lib\site-packages\coverage
- $COVERAGE_HOME\.tox\py27\Lib\site-packages\coverage
- $COVERAGE_HOME\.tox\py32\Lib\site-packages\coverage
- $COVERAGE_HOME\.tox\py33\Lib\site-packages\coverage
- $COVERAGE_HOME\.tox\py34\Lib\site-packages\coverage
[report]
# We set a different pragma so our code won't be confused with test code.
@@ -26,6 +15,8 @@ exclude_lines =
def __repr__
if __name__ == .__main__.:
raise AssertionError
+ # pragma: debugging
+ # pragma: only failure
partial_branches =
# pragma: part covered
@@ -37,6 +28,5 @@ precision = 1
[paths]
source =
.
- */.tox/*/lib/*/site-packages
- */.tox/pypy/site-packages
+ *\coverage\trunk
*/coverage/trunk
diff --git a/setup.py b/setup.py
index 52eb086f..3f7afae6 100644
--- a/setup.py
+++ b/setup.py
@@ -12,6 +12,9 @@ Documentation is at `nedbatchelder.com <%s>`_. Code repository and issue
tracker are on `Bitbucket <http://bitbucket.org/ned/coveragepy>`_, with a
mirrored repo on `Github <https://github.com/nedbat/coveragepy>`_.
+New in 4.0 alpha: ``--concurrency``, dropped support for older Pythons,
+setup.cfg support.
+
New in 3.7: ``--debug``, and 12 bugs closed.
New in 3.6: ``--fail-under``, and >20 bugs closed.
diff --git a/tests/coveragetest.py b/tests/coveragetest.py
index 4053059f..a680ed46 100644
--- a/tests/coveragetest.py
+++ b/tests/coveragetest.py
@@ -8,8 +8,7 @@ from coverage.backward import StringIO, import_local_file
from coverage.backward import importlib # pylint: disable=unused-import
from coverage.control import _TEST_NAME_FILE
from coverage.test_helpers import (
- ModuleAwareMixin, SysPathAwareMixin, EnvironmentAwareMixin,
- StdStreamCapturingMixin, TempDirMixin,
+ EnvironmentAwareMixin, StdStreamCapturingMixin, TempDirMixin,
)
from tests.backtest import run_command
@@ -19,8 +18,6 @@ from tests.backtest import run_command
OK, ERR = 0, 1
class CoverageTest(
- ModuleAwareMixin,
- SysPathAwareMixin,
EnvironmentAwareMixin,
StdStreamCapturingMixin,
TempDirMixin,
@@ -37,10 +34,11 @@ class CoverageTest(
def setUp(self):
super(CoverageTest, self).setUp()
- if _TEST_NAME_FILE:
- f = open(_TEST_NAME_FILE, "w")
- f.write("%s_%s" % (self.__class__.__name__, self._testMethodName))
- f.close()
+ if _TEST_NAME_FILE: # pragma: debugging
+ with open(_TEST_NAME_FILE, "w") as f:
+ f.write("%s_%s" % (
+ self.__class__.__name__, self._testMethodName,
+ ))
def clean_local_file_imports(self):
"""Clean up the results of calls to `import_local_file`.
diff --git a/tests/farm/run/run_timid.py b/tests/farm/run/run_timid.py
index d4e69a46..99155b85 100644
--- a/tests/farm/run/run_timid.py
+++ b/tests/farm/run/run_timid.py
@@ -37,24 +37,4 @@ else:
# also show the Python function.
contains("out/showtraceout.txt", "regular PyTracer")
-# Try the environment variable.
-old_opts = os.environ.get('COVERAGE_OPTIONS')
-os.environ['COVERAGE_OPTIONS'] = '--timid'
-
-run("""
- coverage run showtrace.py regular
- coverage run --timid showtrace.py timid
- """, rundir="out", outfile="showtraceout.txt")
-
-contains("out/showtraceout.txt",
- "none None",
- "timid PyTracer",
- "regular PyTracer",
- )
-
-if old_opts:
- os.environ['COVERAGE_OPTIONS'] = old_opts
-else:
- del os.environ['COVERAGE_OPTIONS']
-
clean("out")
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
index 08f7937a..695c3bec 100644
--- a/tests/test_cmdline.py
+++ b/tests/test_cmdline.py
@@ -9,7 +9,7 @@ from coverage.misc import ExceptionDuringRun
from tests.coveragetest import CoverageTest, OK, ERR
-class CmdLineTest(CoverageTest):
+class BaseCmdLineTest(CoverageTest):
"""Tests of execution paths through the command line interpreter."""
run_in_temp_dir = False
@@ -20,7 +20,7 @@ class CmdLineTest(CoverageTest):
defaults.coverage(
cover_pylib=None, data_suffix=None, timid=None, branch=None,
config_file=True, source=None, include=None, omit=None, debug=None,
- coroutine=None,
+ concurrency=None,
)
defaults.annotate(
directory=None, ignore_errors=None, include=None, omit=None, morfs=[],
@@ -122,326 +122,92 @@ class CmdLineTest(CoverageTest):
)
-class CmdLineTestTest(CmdLineTest):
- """Tests that our CmdLineTest helpers work."""
+class BaseCmdLineTestTest(BaseCmdLineTest):
+ """Tests that our BaseCmdLineTest helpers work."""
def test_assert_same_method_calls(self):
# All the other tests here use self.cmd_executes_same in successful
# ways, so here we just check that it fails.
with self.assertRaises(AssertionError):
- self.cmd_executes_same("-e", "-c")
+ self.cmd_executes_same("run", "debug")
-class ClassicCmdLineTest(CmdLineTest):
- """Tests of the classic coverage.py command line."""
+class FakeCoverageForDebugData(object):
+ """Just enough of a fake coverage package for the 'debug data' tests."""
+ def __init__(self, summary):
+ self._summary = summary
+ self.filename = "FILENAME"
+ self.data = self
- def test_erase(self):
- # coverage -e
- self.cmd_executes("-e", """\
- .coverage()
- .erase()
- """)
- self.cmd_executes_same("-e", "--erase")
+ # package members
+ def coverage(self, *unused_args, **unused_kwargs):
+ """The coverage class in the package."""
+ return self
- def test_execute(self):
- # coverage -x [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...]
+ # coverage methods
+ def load(self):
+ """Fake coverage().load()"""
+ pass
- # -x calls coverage.load first.
- self.cmd_executes("-x foo.py", """\
- .coverage()
- .load()
- .start()
- .run_python_file('foo.py', ['foo.py'])
- .stop()
- .save()
- """)
- # -e -x calls coverage.erase first.
- self.cmd_executes("-e -x foo.py", """\
- .coverage()
- .erase()
- .start()
- .run_python_file('foo.py', ['foo.py'])
- .stop()
- .save()
- """)
- # --timid sets a flag, and program arguments get passed through.
- self.cmd_executes("-x --timid foo.py abc 123", """\
- .coverage(timid=True)
- .load()
- .start()
- .run_python_file('foo.py', ['foo.py', 'abc', '123'])
- .stop()
- .save()
- """)
- # -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)
- .load()
- .start()
- .run_python_file('foo.py', ['foo.py', '-a', '-b'])
- .stop()
- .save()
- """)
+ # data methods
+ def has_arcs(self):
+ """Fake coverage().data.has_arcs()"""
+ return False
- # Check that long forms of flags do the same thing as short forms.
- self.cmd_executes_same("-x f.py", "--execute f.py")
- self.cmd_executes_same("-e -x f.py", "--erase --execute f.py")
- self.cmd_executes_same("-x -p f.py", "-x --parallel-mode f.py")
- self.cmd_executes_same("-x -L f.py", "-x --pylib f.py")
+ def summary(self, fullpath): # pylint: disable=W0613
+ """Fake coverage().data.summary()"""
+ return self._summary
- def test_combine(self):
- # coverage -c
- self.cmd_executes("-c", """\
- .coverage()
- .load()
- .combine()
- .save()
- """)
- self.cmd_executes_same("-c", "--combine")
- def test_report(self):
- # coverage -r [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
- self.cmd_executes("-r", """\
- .coverage()
- .load()
- .report(show_missing=None)
- """)
- self.cmd_executes("-r -i", """\
- .coverage()
- .load()
- .report(ignore_errors=True)
- """)
- self.cmd_executes("-r -m", """\
- .coverage()
- .load()
- .report(show_missing=True)
- """)
- self.cmd_executes("-r -o fooey", """\
- .coverage(omit=["fooey"])
- .load()
- .report(omit=["fooey"])
- """)
- self.cmd_executes("-r -o fooey,booey", """\
- .coverage(omit=["fooey", "booey"])
- .load()
- .report(omit=["fooey", "booey"])
- """)
- self.cmd_executes("-r mod1", """\
- .coverage()
- .load()
- .report(morfs=["mod1"])
- """)
- self.cmd_executes("-r mod1 mod2 mod3", """\
- .coverage()
- .load()
- .report(morfs=["mod1", "mod2", "mod3"])
- """)
-
- self.cmd_executes_same("-r", "--report")
- self.cmd_executes_same("-r -i", "-r --ignore-errors")
- self.cmd_executes_same("-r -m", "-r --show-missing")
- self.cmd_executes_same("-r -o f", "-r --omit=f")
- self.cmd_executes_same("-r -o f", "-r --omit f")
- self.cmd_executes_same("-r -o f,b", "-r --omit=f,b")
- self.cmd_executes_same("-r -o f,b", "-r --omit f,b")
- self.cmd_executes_same("-r -of", "-r --omit=f")
- self.cmd_executes_same("-r -of,b", "-r --omit=f,b")
+class CmdLineTest(BaseCmdLineTest):
+ """Tests of the coverage.py command line."""
def test_annotate(self):
- # coverage -a [-d DIR] [-i] [-o DIR,...] [FILE1 FILE2 ...]
- self.cmd_executes("-a", """\
+ # coverage annotate [-d DIR] [-i] [--omit DIR,...] [FILE1 FILE2 ...]
+ self.cmd_executes("annotate", """\
.coverage()
.load()
.annotate()
""")
- self.cmd_executes("-a -d dir1", """\
+ self.cmd_executes("annotate -d dir1", """\
.coverage()
.load()
.annotate(directory="dir1")
""")
- self.cmd_executes("-a -i", """\
+ self.cmd_executes("annotate -i", """\
.coverage()
.load()
.annotate(ignore_errors=True)
""")
- self.cmd_executes("-a -o fooey", """\
+ self.cmd_executes("annotate --omit fooey", """\
.coverage(omit=["fooey"])
.load()
.annotate(omit=["fooey"])
""")
- self.cmd_executes("-a -o fooey,booey", """\
+ self.cmd_executes("annotate --omit fooey,booey", """\
.coverage(omit=["fooey", "booey"])
.load()
.annotate(omit=["fooey", "booey"])
""")
- self.cmd_executes("-a mod1", """\
+ self.cmd_executes("annotate mod1", """\
.coverage()
.load()
.annotate(morfs=["mod1"])
""")
- self.cmd_executes("-a mod1 mod2 mod3", """\
+ self.cmd_executes("annotate mod1 mod2 mod3", """\
.coverage()
.load()
.annotate(morfs=["mod1", "mod2", "mod3"])
""")
- self.cmd_executes_same("-a", "--annotate")
- self.cmd_executes_same("-a -d d1", "-a --directory=d1")
- self.cmd_executes_same("-a -i", "-a --ignore-errors")
- self.cmd_executes_same("-a -o f", "-a --omit=f")
- self.cmd_executes_same("-a -o f", "-a --omit f")
- self.cmd_executes_same("-a -o f,b", "-a --omit=f,b")
- self.cmd_executes_same("-a -o f,b", "-a --omit f,b")
- self.cmd_executes_same("-a -of", "-a --omit=f")
- self.cmd_executes_same("-a -of,b", "-a --omit=f,b")
-
- def test_html_report(self):
- # coverage -b -d DIR [-i] [-o DIR,...] [FILE1 FILE2 ...]
- self.cmd_executes("-b", """\
- .coverage()
- .load()
- .html_report()
- """)
- self.cmd_executes("-b -d dir1", """\
- .coverage()
- .load()
- .html_report(directory="dir1")
- """)
- self.cmd_executes("-b -i", """\
- .coverage()
- .load()
- .html_report(ignore_errors=True)
- """)
- self.cmd_executes("-b -o fooey", """\
- .coverage(omit=["fooey"])
- .load()
- .html_report(omit=["fooey"])
- """)
- self.cmd_executes("-b -o fooey,booey", """\
- .coverage(omit=["fooey", "booey"])
- .load()
- .html_report(omit=["fooey", "booey"])
- """)
- self.cmd_executes("-b mod1", """\
- .coverage()
- .load()
- .html_report(morfs=["mod1"])
- """)
- self.cmd_executes("-b mod1 mod2 mod3", """\
+ def test_combine(self):
+ # coverage combine
+ self.cmd_executes("combine", """\
.coverage()
.load()
- .html_report(morfs=["mod1", "mod2", "mod3"])
+ .combine()
+ .save()
""")
- self.cmd_executes_same("-b", "--html")
- self.cmd_executes_same("-b -d d1", "-b --directory=d1")
- self.cmd_executes_same("-b -i", "-b --ignore-errors")
- self.cmd_executes_same("-b -o f", "-b --omit=f")
- self.cmd_executes_same("-b -o f,b", "-b --omit=f,b")
- self.cmd_executes_same("-b -of", "-b --omit=f")
- self.cmd_executes_same("-b -of,b", "-b --omit=f,b")
-
- def test_help(self):
- # coverage -h
- self.cmd_help("-h", topic="help", ret=OK)
- self.cmd_help("--help", topic="help", ret=OK)
-
- def test_version(self):
- # coverage --version
- self.cmd_help("--version", topic="version", ret=OK)
-
- ## Error cases
-
- def test_argless_actions(self):
- self.cmd_help("-e foo bar", "Unexpected arguments: foo bar")
- self.cmd_help("-c baz quux", "Unexpected arguments: baz quux")
-
- def test_need_action(self):
- self.cmd_help("-p", "You must specify at least one of "
- "-e, -x, -c, -r, -a, or -b.")
-
- def test_bad_action_combinations(self):
- self.cmd_help('-e -a',
- "You can't specify the 'erase' and 'annotate' "
- "options at the same time."
- )
- self.cmd_help('-e -r',
- "You can't specify the 'erase' and 'report' "
- "options at the same time."
- )
- self.cmd_help('-e -b',
- "You can't specify the 'erase' and 'html' "
- "options at the same time."
- )
- self.cmd_help('-e -c',
- "You can't specify the 'erase' and 'combine' "
- "options at the same time."
- )
- self.cmd_help('-x -a',
- "You can't specify the 'execute' and 'annotate' "
- "options at the same time."
- )
- self.cmd_help('-x -r',
- "You can't specify the 'execute' and 'report' "
- "options at the same time."
- )
- self.cmd_help('-x -b',
- "You can't specify the 'execute' and 'html' "
- "options at the same time."
- )
- self.cmd_help('-x -c',
- "You can't specify the 'execute' and 'combine' "
- "options at the same time."
- )
-
- def test_nothing_to_do(self):
- self.cmd_help("-x", "Nothing to do.")
-
- def test_unknown_option(self):
- self.cmd_help("-z", "no such option: -z")
-
-
-class FakeCoverageForDebugData(object):
- """Just enough of a fake coverage package for the 'debug data' tests."""
- def __init__(self, summary):
- self._summary = summary
- self.filename = "FILENAME"
- self.data = self
-
- # package members
- def coverage(self, *unused_args, **unused_kwargs):
- """The coverage class in the package."""
- return self
-
- # coverage methods
- def load(self):
- """Fake coverage().load()"""
- pass
-
- # data methods
- def has_arcs(self):
- """Fake coverage().data.has_arcs()"""
- return False
-
- def summary(self, fullpath): # pylint: disable=W0613
- """Fake coverage().data.summary()"""
- return self._summary
-
-
-class NewCmdLineTest(CmdLineTest):
- """Tests of the coverage.py command line."""
-
- def test_annotate(self):
- self.cmd_executes_same("annotate", "-a")
- self.cmd_executes_same("annotate -i", "-a -i")
- self.cmd_executes_same("annotate -d d1", "-a -d d1")
- self.cmd_executes_same("annotate --omit f", "-a --omit f")
- self.cmd_executes_same("annotate --omit f,b", "-a --omit f,b")
- self.cmd_executes_same("annotate m1", "-a m1")
- self.cmd_executes_same("annotate m1 m2 m3", "-a m1 m2 m3")
-
- def test_combine(self):
- self.cmd_executes_same("combine", "-c")
-
def test_debug(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'")
@@ -474,13 +240,26 @@ class NewCmdLineTest(CmdLineTest):
def test_debug_sys(self):
self.command_line("debug sys")
out = self.stdout()
- assert "version:" in out
- assert "data_path:" in out
+ self.assertIn("version:", out)
+ self.assertIn("data_path:", out)
def test_erase(self):
- self.cmd_executes_same("erase", "-e")
+ # coverage erase
+ self.cmd_executes("erase", """\
+ .coverage()
+ .erase()
+ """)
- def test_help(self):
+ def test_version(self):
+ # coverage --version
+ self.cmd_help("--version", topic="version", ret=OK)
+
+ def test_help_option(self):
+ # coverage -h
+ self.cmd_help("-h", topic="help", ret=OK)
+ self.cmd_help("--help", topic="help", ret=OK)
+
+ def test_help_command(self):
self.cmd_executes("help", ".help_fn(topic='help')")
def test_cmd_help(self):
@@ -489,18 +268,42 @@ class NewCmdLineTest(CmdLineTest):
self.cmd_executes_same("help run", "run --help")
def test_html(self):
- self.cmd_executes_same("html", "-b")
- self.cmd_executes_same("html -i", "-b -i")
- self.cmd_executes_same("html -d d1", "-b -d d1")
- self.cmd_executes_same("html --omit f", "-b --omit f")
- self.cmd_executes_same("html --omit f,b", "-b --omit f,b")
- self.cmd_executes_same("html m1", "-b m1")
- self.cmd_executes_same("html m1 m2 m3", "-b m1 m2 m3")
+ # coverage html -d DIR [-i] [--omit DIR,...] [FILE1 FILE2 ...]
self.cmd_executes("html", """\
.coverage()
.load()
.html_report()
""")
+ self.cmd_executes("html -d dir1", """\
+ .coverage()
+ .load()
+ .html_report(directory="dir1")
+ """)
+ self.cmd_executes("html -i", """\
+ .coverage()
+ .load()
+ .html_report(ignore_errors=True)
+ """)
+ self.cmd_executes("html --omit fooey", """\
+ .coverage(omit=["fooey"])
+ .load()
+ .html_report(omit=["fooey"])
+ """)
+ self.cmd_executes("html --omit fooey,booey", """\
+ .coverage(omit=["fooey", "booey"])
+ .load()
+ .html_report(omit=["fooey", "booey"])
+ """)
+ self.cmd_executes("html mod1", """\
+ .coverage()
+ .load()
+ .html_report(morfs=["mod1"])
+ """)
+ self.cmd_executes("html mod1 mod2 mod3", """\
+ .coverage()
+ .load()
+ .html_report(morfs=["mod1", "mod2", "mod3"])
+ """)
self.cmd_executes("html --title=Hello_there", """\
.coverage()
.load()
@@ -508,22 +311,82 @@ class NewCmdLineTest(CmdLineTest):
""")
def test_report(self):
- self.cmd_executes_same("report", "-r")
- self.cmd_executes_same("report -i", "-r -i")
- self.cmd_executes_same("report -m", "-r -m")
- self.cmd_executes_same("report --omit f", "-r --omit f")
- self.cmd_executes_same("report --omit f,b", "-r --omit f,b")
- self.cmd_executes_same("report m1", "-r m1")
- self.cmd_executes_same("report m1 m2 m3", "-r m1 m2 m3")
+ # coverage report [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
+ self.cmd_executes("report", """\
+ .coverage()
+ .load()
+ .report(show_missing=None)
+ """)
+ self.cmd_executes("report -i", """\
+ .coverage()
+ .load()
+ .report(ignore_errors=True)
+ """)
+ self.cmd_executes("report -m", """\
+ .coverage()
+ .load()
+ .report(show_missing=True)
+ """)
+ self.cmd_executes("report --omit fooey", """\
+ .coverage(omit=["fooey"])
+ .load()
+ .report(omit=["fooey"])
+ """)
+ self.cmd_executes("report --omit fooey,booey", """\
+ .coverage(omit=["fooey", "booey"])
+ .load()
+ .report(omit=["fooey", "booey"])
+ """)
+ self.cmd_executes("report mod1", """\
+ .coverage()
+ .load()
+ .report(morfs=["mod1"])
+ """)
+ self.cmd_executes("report mod1 mod2 mod3", """\
+ .coverage()
+ .load()
+ .report(morfs=["mod1", "mod2", "mod3"])
+ """)
def test_run(self):
- self.cmd_executes_same("run f.py", "-e -x f.py")
- self.cmd_executes_same("run f.py -a arg -z", "-e -x f.py -a arg -z")
- self.cmd_executes_same("run -a f.py", "-x f.py")
- self.cmd_executes_same("run -p f.py", "-e -x -p f.py")
- self.cmd_executes_same("run -L f.py", "-e -x -L f.py")
- self.cmd_executes_same("run --timid f.py", "-e -x --timid f.py")
- self.cmd_executes_same("run", "-x")
+ # coverage run [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...]
+
+ # run calls coverage.erase first.
+ self.cmd_executes("run foo.py", """\
+ .coverage()
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ # run -a calls coverage.load first without erasing.
+ self.cmd_executes("run -a foo.py", """\
+ .coverage()
+ .load()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+ # --timid sets a flag, and program arguments get passed through.
+ self.cmd_executes("run --timid foo.py abc 123", """\
+ .coverage(timid=True)
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py', 'abc', '123'])
+ .stop()
+ .save()
+ """)
+ # -L sets a flag, and flags for the program don't confuse us.
+ self.cmd_executes("run -p -L foo.py -a -b", """\
+ .coverage(cover_pylib=True, data_suffix=True)
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py', '-a', '-b'])
+ .stop()
+ .save()
+ """)
self.cmd_executes("run --branch foo.py", """\
.coverage(branch=True)
.erase()
@@ -565,8 +428,7 @@ class NewCmdLineTest(CmdLineTest):
.stop()
.save()
""")
- self.cmd_executes("run --source=quux,hi.there,/home/bar foo.py",
- """\
+ self.cmd_executes("run --source=quux,hi.there,/home/bar foo.py", """\
.coverage(source=["quux", "hi.there", "/home/bar"])
.erase()
.start()
@@ -574,6 +436,19 @@ class NewCmdLineTest(CmdLineTest):
.stop()
.save()
""")
+ self.cmd_executes("run --concurrency=gevent foo.py", """\
+ .coverage(concurrency='gevent')
+ .erase()
+ .start()
+ .run_python_file('foo.py', ['foo.py'])
+ .stop()
+ .save()
+ """)
+
+ def test_bad_concurrency(self):
+ self.command_line("run --concurrency=nothing", ret=ERR)
+ out = self.stdout()
+ self.assertIn("option --concurrency: invalid choice: 'nothing'", out)
def test_run_debug(self):
self.cmd_executes("run --debug=opt1 foo.py", """\
@@ -670,39 +545,39 @@ class NewCmdLineTest(CmdLineTest):
self.cmd_help("xyzzy", "Unknown command: 'xyzzy'")
-class CmdLineStdoutTest(CmdLineTest):
+class CmdLineStdoutTest(BaseCmdLineTest):
"""Test the command line with real stdout output."""
def test_minimum_help(self):
self.command_line("")
out = self.stdout()
- assert "Code coverage for Python." in out
- assert out.count("\n") < 4
+ self.assertIn("Code coverage for Python.", out)
+ self.assertLess(out.count("\n"), 4)
def test_version(self):
self.command_line("--version")
out = self.stdout()
- assert "ersion " in out
- assert out.count("\n") < 4
+ self.assertIn("ersion ", out)
+ self.assertLess(out.count("\n"), 4)
def test_help(self):
self.command_line("help")
out = self.stdout()
- assert "nedbatchelder.com" in out
- assert out.count("\n") > 10
+ self.assertIn("nedbatchelder.com", out)
+ self.assertGreater(out.count("\n"), 10)
def test_cmd_help(self):
self.command_line("help run")
out = self.stdout()
- assert "<pyfile>" in out
- assert "--timid" in out
- assert out.count("\n") > 10
+ self.assertIn("<pyfile>", out)
+ self.assertIn("--timid", out)
+ self.assertGreater(out.count("\n"), 10)
def test_error(self):
self.command_line("fooey kablooey", ret=ERR)
out = self.stdout()
- assert "fooey" in out
- assert "help" in out
+ self.assertIn("fooey", out)
+ self.assertIn("help", out)
class CmdMainTest(CoverageTest):
diff --git a/tests/test_collector.py b/tests/test_collector.py
index 7bd4bebb..5f931385 100644
--- a/tests/test_collector.py
+++ b/tests/test_collector.py
@@ -37,9 +37,8 @@ class CollectorTest(CoverageTest):
# 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
- )
+ cov = coverage.coverage(include=["f1.py"], debug=['trace'])
+ cov._debug_file = debug_out
# Import the python file, executing it.
self.start_import_stop(cov, "f2")
diff --git a/tests/test_coroutine.py b/tests/test_concurrency.py
index 4abdd6f6..5ea756f6 100644
--- a/tests/test_coroutine.py
+++ b/tests/test_concurrency.py
@@ -1,4 +1,4 @@
-"""Tests for coroutining."""
+"""Tests for concurrency libraries."""
import os, os.path, sys, threading
@@ -36,8 +36,8 @@ def line_count(s):
return sum(1 for l in s.splitlines() if code_line(l))
-class CoroutineTest(CoverageTest):
- """Tests of the coroutine support in coverage.py."""
+class ConcurrencyTest(CoverageTest):
+ """Tests of the concurrency support in coverage.py."""
LIMIT = 1000
@@ -103,7 +103,7 @@ class CoroutineTest(CoverageTest):
import gevent.queue as queue
""" + COMMON
- # Uncomplicated code that doesn't use any of the coroutining stuff, to test
+ # Uncomplicated code that doesn't use any of the concurrency stuff, to test
# the simple case under each of the regimes.
SIMPLE = """\
total = 0
@@ -112,32 +112,32 @@ class CoroutineTest(CoverageTest):
print(total)
""".format(LIMIT=LIMIT)
- def try_some_code(self, code, coroutine, the_module, expected_out=None):
- """Run some coroutine testing code and see that it was all covered.
+ def try_some_code(self, code, concurrency, the_module, expected_out=None):
+ """Run some concurrency testing code and see that it was all covered.
- `code` is the Python code to execute. `coroutine` is the name of the
- coroutine regime to test it under. `the_module` is the imported module
- that must be available for this to work at all. `expected_out` is the
- text we expect the code to produce.
+ `code` is the Python code to execute. `concurrency` is the name of
+ the concurrency regime to test it under. `the_module` is the imported
+ module that must be available for this to work at all. `expected_out`
+ is the text we expect the code to produce.
"""
self.make_file("try_it.py", code)
- cmd = "coverage run --coroutine=%s try_it.py" % coroutine
+ cmd = "coverage run --concurrency=%s try_it.py" % concurrency
out = self.run_command(cmd)
if not the_module:
# We don't even have the underlying module installed, we expect
# coverage to alert us to this fact.
expected_out = (
- "Couldn't trace with coroutine=%s, "
- "the module isn't installed.\n" % coroutine
+ "Couldn't trace with concurrency=%s, "
+ "the module isn't installed.\n" % concurrency
)
self.assertEqual(out, expected_out)
- elif C_TRACER or coroutine == "thread":
+ elif C_TRACER or concurrency == "thread":
# We can fully measure the code if we are using the C tracer, which
- # can support all the coroutining, or if we are using threads.
+ # can support all the concurrency, or if we are using threads.
if expected_out is None:
expected_out = "%d\n" % (sum(range(self.LIMIT)))
self.assertEqual(out, expected_out)
@@ -157,8 +157,8 @@ class CoroutineTest(CoverageTest):
self.assertEqual(data.summary()['try_it.py'], lines)
else:
expected_out = (
- "Can't support coroutine=%s with PyTracer, "
- "only threads are supported\n" % coroutine
+ "Can't support concurrency=%s with PyTracer, "
+ "only threads are supported\n" % concurrency
)
self.assertEqual(out, expected_out)
diff --git a/tests/test_config.py b/tests/test_config.py
index bf84423d..232d2289 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -123,6 +123,45 @@ class ConfigTest(CoverageTest):
["the_$one", "anotherZZZ", "xZZZy", "xy", "huh${X}what"]
)
+ def test_tweaks_after_constructor(self):
+ # Arguments to the constructor are applied to the configuation.
+ cov = coverage.coverage(timid=True, data_file="fooey.dat")
+ cov.config["run:timid"] = False
+
+ self.assertFalse(cov.config.timid)
+ self.assertFalse(cov.config.branch)
+ self.assertEqual(cov.config.data_file, "fooey.dat")
+
+ self.assertFalse(cov.config["run:timid"])
+ self.assertFalse(cov.config["run:branch"])
+ self.assertEqual(cov.config["run:data_file"], "fooey.dat")
+
+ def test_tweak_error_checking(self):
+ # Trying to set an unknown config value raises an error.
+ cov = coverage.coverage()
+ with self.assertRaises(CoverageException):
+ cov.config["run:xyzzy"] = 12
+ with self.assertRaises(CoverageException):
+ cov.config["xyzzy:foo"] = 12
+ with self.assertRaises(CoverageException):
+ _ = cov.config["run:xyzzy"]
+ with self.assertRaises(CoverageException):
+ _ = cov.config["xyzzy:foo"]
+
+ def test_tweak_plugin_options(self):
+ # Plugin options have a more flexible syntax.
+ cov = coverage.coverage()
+ cov.config["run:plugins"] = ["fooey.plugin", "xyzzy.coverage.plugin"]
+ cov.config["fooey.plugin:xyzzy"] = 17
+ cov.config["xyzzy.coverage.plugin:plugh"] = ["a", "b"]
+ with self.assertRaises(CoverageException):
+ cov.config["no_such.plugin:foo"] = 23
+
+ self.assertEqual(cov.config["fooey.plugin:xyzzy"], 17)
+ self.assertEqual(cov.config["xyzzy.coverage.plugin:plugh"], ["a", "b"])
+ with self.assertRaises(CoverageException):
+ _ = cov.config["no_such.plugin:foo"]
+
class ConfigFileTest(CoverageTest):
"""Tests of the config file settings in particular."""
@@ -144,6 +183,7 @@ class ConfigFileTest(CoverageTest):
cover_pylib = TRUE
parallel = on
include = a/ , b/
+ concurrency = thread
plugins =
plugins.a_plugin
plugins.another
@@ -210,6 +250,7 @@ class ConfigFileTest(CoverageTest):
self.assertTrue(cov.config.branch)
self.assertTrue(cov.config.cover_pylib)
self.assertTrue(cov.config.parallel)
+ self.assertEqual(cov.config.concurrency, "thread")
self.assertEqual(cov.get_exclude_list(),
["if 0:", r"pragma:?\s+no cover", "another_tab"]
@@ -274,7 +315,7 @@ class ConfigFileTest(CoverageTest):
self.assertEqual(cov.config.omit, None)
self.assertEqual(cov.config.branch, False)
- def test_one(self):
+ def test_non_ascii(self):
self.make_file(".coveragerc", """\
[html]
title = tabblo & «ταБЬℓσ» # numbers
diff --git a/tests/test_debug.py b/tests/test_debug.py
index 985da40f..e8a8e950 100644
--- a/tests/test_debug.py
+++ b/tests/test_debug.py
@@ -47,7 +47,8 @@ class DebugTraceTest(CoverageTest):
""")
debug_out = StringIO()
- cov = coverage.coverage(debug=debug, debug_file=debug_out)
+ cov = coverage.coverage(debug=debug)
+ cov._debug_file = debug_out
self.start_import_stop(cov, "f1")
out_lines = debug_out.getvalue().splitlines()
diff --git a/tests/test_execfile.py b/tests/test_execfile.py
index 69616e84..3a92ff76 100644
--- a/tests/test_execfile.py
+++ b/tests/test_execfile.py
@@ -91,9 +91,9 @@ class RunPycFileTest(CoverageTest):
os.remove("compiled.py")
# Find the .pyc file!
- for there, _, files in os.walk("."):
+ for there, _, files in os.walk("."): # pragma: part covered
for f in files:
- if f.endswith(".pyc"):
+ if f.endswith(".pyc"): # pragma: part covered
return os.path.join(there, f)
def test_running_pyc(self):
diff --git a/tests/test_farm.py b/tests/test_farm.py
index 47f9b7b7..d0f0a72a 100644
--- a/tests/test_farm.py
+++ b/tests/test_farm.py
@@ -70,10 +70,9 @@ class FarmTestCase(object):
"""Execute the test from the run.py file.
"""
- if _TEST_NAME_FILE:
- f = open(_TEST_NAME_FILE, "w")
- f.write(self.description.replace("/", "_"))
- f.close()
+ if _TEST_NAME_FILE: # pragma: debugging
+ with open(_TEST_NAME_FILE, "w") as f:
+ f.write(self.description.replace("/", "_"))
cwd = self.cd(self.dir)
diff --git a/tests/test_oddball.py b/tests/test_oddball.py
index 47f492f6..3a013b04 100644
--- a/tests/test_oddball.py
+++ b/tests/test_oddball.py
@@ -173,7 +173,7 @@ class MemoryLeakTest(CoverageTest):
if ram_growth > 100000:
fails += 1
- if fails > 8:
+ if fails > 8: # pragma: only failure
self.fail("RAM grew by %d" % (ram_growth))
diff --git a/tests/test_plugins.py b/tests/test_plugins.py
index 9c5a037d..5039082e 100644
--- a/tests/test_plugins.py
+++ b/tests/test_plugins.py
@@ -1,12 +1,17 @@
"""Tests for plugins."""
import os.path
+import sys
+
+from nose.plugins.skip import SkipTest
import coverage
from coverage.codeunit import CodeUnit
from coverage.parser import CodeParser
from coverage.plugin import Plugins, overrides
+import coverage.plugin
+
from tests.coveragetest import CoverageTest
@@ -98,7 +103,10 @@ class PluginTest(CoverageTest):
""")
self.assert_doesnt_exist("evidence.out")
- _ = coverage.Coverage(plugins=["my_plugin"])
+ cov = coverage.Coverage()
+ cov.config["run:plugins"] = ["my_plugin"]
+ cov.start()
+ cov.stop()
with open("evidence.out") as f:
self.assertEqual(f.read(), "we are here!")
@@ -106,8 +114,10 @@ class PluginTest(CoverageTest):
def test_missing_plugin_raises_import_error(self):
# Prove that a missing plugin will raise an ImportError.
with self.assertRaises(ImportError):
- cov = coverage.Coverage(plugins=["does_not_exist_woijwoicweo"])
+ cov = coverage.Coverage()
+ cov.config["run:plugins"] = ["does_not_exist_woijwoicweo"]
cov.start()
+ cov.stop()
def test_bad_plugin_isnt_hidden(self):
# Prove that a plugin with an error in it will raise the error.
@@ -115,9 +125,15 @@ class PluginTest(CoverageTest):
1/0
""")
with self.assertRaises(ZeroDivisionError):
- _ = coverage.Coverage(plugins=["plugin_over_zero"])
+ cov = coverage.Coverage()
+ cov.config["run:plugins"] = ["plugin_over_zero"]
+ cov.start()
+ cov.stop()
def test_importing_myself(self):
+ if sys.platform == 'win32':
+ raise SkipTest("Plugin stuff is jank on windows.. fixing soon...")
+
self.make_file("simple.py", """\
import try_xyz
a = 1
@@ -128,7 +144,8 @@ class PluginTest(CoverageTest):
d = 4
""")
- cov = coverage.Coverage(plugins=["tests.test_plugins"])
+ cov = coverage.Coverage()
+ cov.config["run:plugins"] = ["tests.test_plugins"]
# Import the python file, executing it.
self.start_import_stop(cov, "simple")
@@ -141,24 +158,32 @@ class PluginTest(CoverageTest):
class Plugin(coverage.CoveragePlugin):
- def trace_judge(self, disp):
- if "xyz.py" in disp.original_filename:
- disp.trace = True
- disp.source_filename = os.path.join(
- "/src",
- os.path.basename(
- disp.original_filename.replace("xyz.py", "ABC.zz")
- )
- )
+ def file_tracer(self, filename):
+ if "xyz.py" in filename:
+ file_tracer = FileTracer(filename)
+ return file_tracer
+
+ def file_reporter(self, filename):
+ return FileReporter(filename)
+
+
+class FileTracer(coverage.plugin.FileTracer):
+ def __init__(self, filename):
+ self._filename = filename
+ self._source_filename = os.path.join(
+ "/src",
+ os.path.basename(filename.replace("xyz.py", "ABC.zz"))
+ )
+
+ def source_filename(self):
+ return self._source_filename
def line_number_range(self, frame):
lineno = frame.f_lineno
return lineno*100+5, lineno*100+7
- def code_unit_class(self, filename):
- return PluginCodeUnit
-class PluginCodeUnit(CodeUnit):
+class FileReporter(coverage.plugin.FileReporter):
def get_parser(self, exclude=None):
return PluginParser()
diff --git a/tests/test_process.py b/tests/test_process.py
index 3a0980dc..4669a62f 100644
--- a/tests/test_process.py
+++ b/tests/test_process.py
@@ -559,7 +559,7 @@ class PydocTest(CoverageTest):
self.assert_pydoc_ok("coverage", coverage)
def test_pydoc_coverage_coverage(self):
- self.assert_pydoc_ok("coverage.coverage", coverage.coverage)
+ self.assert_pydoc_ok("coverage.Coverage", coverage.Coverage)
class FailUnderTest(CoverageTest):
diff --git a/tests/test_templite.py b/tests/test_templite.py
index a4667a62..aa697b78 100644
--- a/tests/test_templite.py
+++ b/tests/test_templite.py
@@ -31,8 +31,10 @@ class TempliteTest(CoverageTest):
an exception and never get to the result comparison.
"""
actual = Templite(text).render(ctx or {})
- if result:
- self.assertEqual(actual, result)
+ # If result is None, then an exception should have prevented us getting
+ # to here.
+ assert result is not None
+ self.assertEqual(actual, result)
def assertSynErr(self, msg):
"""Assert that a `TempliteSyntaxError` will happen.
diff --git a/tests/test_testing.py b/tests/test_testing.py
index 4a19098f..05db7298 100644
--- a/tests/test_testing.py
+++ b/tests/test_testing.py
@@ -114,7 +114,7 @@ def same_python_executable(e1, e2):
e2 = os.path.abspath(os.path.realpath(e2))
if os.path.dirname(e1) != os.path.dirname(e2):
- return False
+ return False # pragma: only failure
e1 = os.path.basename(e1)
e2 = os.path.basename(e2)
@@ -126,4 +126,4 @@ def same_python_executable(e1, e2):
# python2.3 and python2.3: ok
return True
- return False
+ return False # pragma: only failure
diff --git a/tests/try_execfile.py b/tests/try_execfile.py
index 9bbabd1a..825fdb70 100644
--- a/tests/try_execfile.py
+++ b/tests/try_execfile.py
@@ -12,18 +12,13 @@ def my_function(a):
FN_VAL = my_function("fooey")
-try:
- pkg = __package__
-except NameError:
- pkg = "*No __package__*"
-
globals_to_check = {
'__name__': __name__,
'__file__': __file__,
'__doc__': __doc__,
'__builtins__.has_open': hasattr(__builtins__, 'open'),
'__builtins__.dir': dir(__builtins__),
- '__package__': pkg,
+ '__package__': __package__,
'DATA': DATA,
'FN_VAL': FN_VAL,
'__main__.DATA': getattr(__main__, "DATA", "nothing"),
diff --git a/tox.ini b/tox.ini
index ca0d1721..96f97dcf 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,7 +4,7 @@
# and then run "tox" from this directory.
[tox]
-envlist = py26, py27, py32, py33, py34, pypy
+envlist = py26, py27, py32, py33, py34, pypy22, pypy23, pypy24
[testenv]
commands =
@@ -26,6 +26,8 @@ deps =
nose
mock
+usedevelop = True
+
[testenv:py26]
deps =
{[testenv]deps}
@@ -56,7 +58,20 @@ deps =
{[testenv]deps}
greenlet
-[testenv:pypy]
+[testenv:pypy22]
+basepython = pypy2.2
+# PyPy has no C extensions
+setenv =
+ COVERAGE_NO_EXTENSION=1
+
+[testenv:pypy23]
+basepython = pypy2.3
+# PyPy has no C extensions
+setenv =
+ COVERAGE_NO_EXTENSION=1
+
+[testenv:pypy24]
+basepython = pypy2.4
# PyPy has no C extensions
setenv =
COVERAGE_NO_EXTENSION=1
diff --git a/tox_winkits.ini b/tox_winkits.ini
index 49057d3b..f1941918 100644
--- a/tox_winkits.ini
+++ b/tox_winkits.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py26, py27, py32, py33
+envlist = py26, py27, py32, py33, py34
toxworkdir = {toxinidir}/.tox_kits
[testenv]