summaryrefslogtreecommitdiff
path: root/coverage
diff options
context:
space:
mode:
Diffstat (limited to 'coverage')
-rw-r--r--coverage/__init__.py4
-rw-r--r--coverage/backunittest.py2
-rw-r--r--coverage/backward.py6
-rw-r--r--coverage/cmdline.py13
-rw-r--r--coverage/codeunit.py59
-rw-r--r--coverage/collector.py6
-rw-r--r--coverage/config.py4
-rw-r--r--coverage/control.py54
-rw-r--r--coverage/django.py61
-rw-r--r--coverage/execfile.py1
-rw-r--r--coverage/files.py5
-rw-r--r--coverage/misc.py24
-rw-r--r--coverage/parser.py6
-rw-r--r--coverage/plugin.py75
-rw-r--r--coverage/pytracer.py13
-rw-r--r--coverage/summary.py13
16 files changed, 142 insertions, 204 deletions
diff --git a/coverage/__init__.py b/coverage/__init__.py
index 67dd6e88..3a52c1d6 100644
--- a/coverage/__init__.py
+++ b/coverage/__init__.py
@@ -34,9 +34,9 @@ def _singleton_method(name):
called.
"""
- # Disable pylint msg W0612, because a bunch of variables look unused, but
+ # Disable pylint message, because a bunch of variables look unused, but
# they're accessed via locals().
- # pylint: disable=W0612
+ # pylint: disable=unused-variable
def wrapper(*args, **kwargs):
"""Singleton wrapper around a coverage method."""
diff --git a/coverage/backunittest.py b/coverage/backunittest.py
index b2b7ca2f..648e74ef 100644
--- a/coverage/backunittest.py
+++ b/coverage/backunittest.py
@@ -3,7 +3,7 @@
# Use unittest2 if it's available, otherwise unittest. This gives us
# backported features for 2.6.
try:
- import unittest2 as unittest # pylint: disable=F0401
+ import unittest2 as unittest
except ImportError:
import unittest
diff --git a/coverage/backward.py b/coverage/backward.py
index e839f6bd..3372a8b3 100644
--- a/coverage/backward.py
+++ b/coverage/backward.py
@@ -2,8 +2,6 @@
# This file does lots of tricky stuff, so disable a bunch of lintisms.
# pylint: disable=redefined-builtin
-# pylint: disable=import-error
-# pylint: disable=no-member
# pylint: disable=unused-import
# pylint: disable=no-name-in-module
@@ -50,7 +48,7 @@ else:
if sys.version_info >= (3, 0):
# Python 3.2 provides `tokenize.open`, the best way to open source files.
import tokenize
- open_python_source = tokenize.open # pylint: disable=E1101
+ open_python_source = tokenize.open
else:
def open_python_source(fname):
"""Open a source file the best way."""
@@ -153,7 +151,7 @@ def import_local_file(modname):
break
with open(modfile, 'r') as f:
- # pylint: disable=W0631
+ # pylint: disable=undefined-loop-variable
# (Using possibly undefined loop variable 'suff')
mod = imp.load_module(modname, f, modfile, suff)
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index e7efe5c4..1b4d322c 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -62,6 +62,10 @@ class Opts(object):
help="Show line numbers of statements in each module that weren't "
"executed."
)
+ skip_covered = optparse.make_option(
+ '--skip-covered', action='store_true',
+ help="Skip files with 100% coverage."
+ )
omit = optparse.make_option(
'', '--omit', action='store',
metavar="PAT1,PAT2,...",
@@ -135,6 +139,7 @@ class CoverageOptionParser(optparse.OptionParser, object):
pylib=None,
rcfile=True,
show_missing=None,
+ skip_covered=None,
source=None,
timid=None,
title=None,
@@ -153,7 +158,7 @@ class CoverageOptionParser(optparse.OptionParser, object):
"""Used to stop the optparse error handler ending the process."""
pass
- def parse_args(self, args=None, options=None):
+ def parse_args_ok(self, args=None, options=None):
"""Call optparse.parse_args, but return a triple:
(ok, options, args)
@@ -283,6 +288,7 @@ CMDS = {
Opts.omit,
Opts.include,
Opts.show_missing,
+ Opts.skip_covered
] + GLOBAL_ARGS,
usage = "[options] [modules]",
description = "Report coverage statistics on modules."
@@ -369,7 +375,7 @@ class CoverageScript(object):
argv = argv[1:]
parser.help_fn = self.help_fn
- ok, options, args = parser.parse_args(argv)
+ ok, options, args = parser.parse_args_ok(argv)
if not ok:
return ERR
@@ -426,7 +432,8 @@ class CoverageScript(object):
if options.action == "report":
total = self.coverage.report(
- show_missing=options.show_missing, **report_args)
+ show_missing=options.show_missing,
+ skip_covered=options.skip_covered, **report_args)
if options.action == "annotate":
self.coverage.annotate(
directory=options.directory, **report_args)
diff --git a/coverage/codeunit.py b/coverage/codeunit.py
index da617913..207383e0 100644
--- a/coverage/codeunit.py
+++ b/coverage/codeunit.py
@@ -243,62 +243,3 @@ class PythonCodeUnit(CodeUnit):
def source_encoding(self):
return source_encoding(self.source())
-
-
-class MakoParser(CodeParser):
- def __init__(self, metadata):
- self.metadata = metadata
-
- def parse_source(self):
- """Returns executable_line_numbers, excluded_line_numbers"""
- executable = set(self.metadata['line_map'].values())
- return executable, set()
-
- def translate_lines(self, lines):
- tlines = set()
- for l in lines:
- try:
- tlines.add(self.metadata['full_line_map'][l])
- except IndexError:
- pass
- return tlines
-
-
-class MakoCodeUnit(CodeUnit):
- def __init__(self, *args, **kwargs):
- super(MakoCodeUnit, self).__init__(*args, **kwargs)
- from mako.template import ModuleInfo
- py_source = open(self.filename).read()
- self.metadata = ModuleInfo.get_module_source_metadata(py_source, full_line_map=True)
-
- def get_source(self):
- return open(self.metadata['filename']).read()
-
- def get_parser(self, exclude=None):
- return MakoParser(self.metadata)
-
- def source_encoding(self):
- return self.metadata['source_encoding']
-
-
-class DjangoCodeUnit(CodeUnit):
- def get_source(self):
- with open(self.filename) as f:
- return f.read()
-
- def get_parser(self, exclude=None):
- return DjangoParser(self.filename)
-
- def source_encoding(self):
- return "utf8"
-
-
-class DjangoParser(CodeParser):
- def __init__(self, filename):
- self.filename = filename
-
- def parse_source(self):
- with open(self.filename) as f:
- source = f.read()
- executable = set(range(1, len(source.splitlines())+1))
- return executable, set()
diff --git a/coverage/collector.py b/coverage/collector.py
index 001bc3d3..6ffdc7c4 100644
--- a/coverage/collector.py
+++ b/coverage/collector.py
@@ -7,7 +7,7 @@ from coverage.pytracer import PyTracer
try:
# Use the C extension code when we can, for speed.
- from coverage.tracer import CTracer # pylint: disable=F0401,E0611
+ from coverage.tracer import CTracer # pylint: disable=no-name-in-module
except ImportError:
# Couldn't import the C extension, maybe it isn't built.
if os.getenv('COVERAGE_TEST_TRACER') == 'c':
@@ -24,7 +24,7 @@ except ImportError:
CTracer = None
try:
- import __pypy__ # pylint: disable=import-error
+ import __pypy__
except ImportError:
__pypy__ = None
@@ -186,6 +186,8 @@ class Collector(object):
tracer.plugin_data = self.plugin_data
if hasattr(tracer, 'threading'):
tracer.threading = self.threading
+ if hasattr(tracer, 'check_include'):
+ tracer.check_include = self.check_include
fn = tracer.start()
self.tracers.append(tracer)
diff --git a/coverage/config.py b/coverage/config.py
index 4d599ee7..9598f74d 100644
--- a/coverage/config.py
+++ b/coverage/config.py
@@ -7,7 +7,7 @@ from coverage.misc import CoverageException
# In py3, # ConfigParser was renamed to the more-standard configparser
try:
- import configparser # pylint: disable=F0401
+ import configparser
except ImportError:
import ConfigParser as configparser
@@ -160,6 +160,7 @@ class CoverageConfig(object):
self.partial_always_list = DEFAULT_PARTIAL_ALWAYS[:]
self.precision = 0
self.show_missing = False
+ self.skip_covered = False
# Defaults for [html]
self.html_dir = "htmlcov"
@@ -248,6 +249,7 @@ class CoverageConfig(object):
('partial_always_list', 'report:partial_branches_always', 'linelist'),
('precision', 'report:precision', 'int'),
('show_missing', 'report:show_missing', 'boolean'),
+ ('skip_covered', 'report:skip_covered', 'boolean'),
# [html]
('html_dir', 'html:directory'),
diff --git a/coverage/control.py b/coverage/control.py
index e568f643..346f655f 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -9,13 +9,13 @@ from coverage.collector import Collector
from coverage.config import CoverageConfig
from coverage.data import CoverageData
from coverage.debug import DebugControl
-from coverage.plugin import CoveragePlugin, Plugins, overrides
+from coverage.plugin import CoveragePlugin, Plugins
from coverage.files import FileLocator, TreeMatcher, FnmatchMatcher
from coverage.files import PathAliases, find_python_files, prep_patterns
from coverage.files import ModuleMatcher
from coverage.html import HtmlReporter
from coverage.misc import CoverageException, bool_or_none, join_regex
-from coverage.misc import file_be_gone
+from coverage.misc import file_be_gone, overrides
from coverage.results import Analysis, Numbers
from coverage.summary import SummaryReporter
from coverage.xmlreport import XmlReporter
@@ -24,7 +24,7 @@ from coverage.xmlreport import XmlReporter
# Pypy has some unusual stuff in the "stdlib". Consider those locations
# when deciding where the stdlib is.
try:
- import _structseq # pylint: disable=F0401
+ import _structseq
except ImportError:
_structseq = None
@@ -353,6 +353,7 @@ class Coverage(object):
"""
disp = FileDisposition(filename)
def nope(disp, reason):
+ """Simple helper to make it easy to return NO."""
disp.trace = False
disp.reason = reason
return disp
@@ -371,8 +372,6 @@ class Coverage(object):
# Empty string is pretty useless
return nope(disp, "empty string isn't a filename")
- modulename = self._name_for_module(frame.f_globals, filename)
-
if filename.startswith('memory:'):
return nope(disp, "memory isn't traceable")
@@ -398,7 +397,10 @@ class Coverage(object):
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())
+ if file_tracer.has_dynamic_source_filename():
+ disp.has_dynamic_filename = True
+ else:
+ disp.source_filename = self.file_locator.canonical_filename(file_tracer.source_filename())
else:
disp.trace = True
disp.source_filename = canonical
@@ -406,29 +408,32 @@ class Coverage(object):
if disp.trace:
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, modulename,
- )
- if reason:
- nope(disp, reason)
+ if not disp.has_dynamic_filename:
+ 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, frame,
+ )
+ if reason:
+ nope(disp, reason)
return disp
return nope(disp, "no plugin found") # TODO: a test that causes this.
- def _check_include_omit_etc(self, filename, modulename):
+ def _check_include_omit_etc(self, filename, frame):
"""Check a filename against the include, omit, etc, rules.
Returns a string or None. String means, don't trace, and is the reason
why. None means no reason found to not trace.
"""
+ modulename = self._name_for_module(frame.f_globals, filename)
+
# If the user specified source or include, then that's authoritative
# about the outer bound of what to measure and we don't have to apply
# any canned exclusions. If they didn't, then we have to exclude the
@@ -473,13 +478,13 @@ class Coverage(object):
self.debug.write(disp.debug_message())
return disp
- def _tracing_check_include_omit_etc(self, filename):
- """Check a filename against the include, omit, etc, rules, and say so.
+ def _tracing_check_include_omit_etc(self, filename, frame):
+ """Check a filename against the include/omit/etc, rules, verbosely.
Returns a boolean: True if the file should be traced, False if not.
"""
- reason = self._check_include_omit_etc(filename)
+ reason = self._check_include_omit_etc(filename, frame)
if self.debug.should('trace'):
if not reason:
msg = "Tracing %r" % (filename,)
@@ -750,8 +755,8 @@ Unexpected third case:
return Analysis(self, it)
def report(self, morfs=None, show_missing=True, ignore_errors=None,
- file=None, # pylint: disable=W0622
- omit=None, include=None
+ file=None, # pylint: disable=redefined-builtin
+ omit=None, include=None, skip_covered=False,
):
"""Write a summary report to `file`.
@@ -768,7 +773,7 @@ Unexpected third case:
self._harvest_data()
self.config.from_args(
ignore_errors=ignore_errors, omit=omit, include=include,
- show_missing=show_missing,
+ show_missing=show_missing, skip_covered=skip_covered,
)
reporter = SummaryReporter(self, self.config)
return reporter.report(morfs, outfile=file)
@@ -921,6 +926,7 @@ class FileDisposition(object):
self.trace = False
self.reason = ""
self.file_tracer = None
+ self.has_dynamic_filename = False
def debug_message(self):
"""Produce a debugging message explaining the outcome."""
diff --git a/coverage/django.py b/coverage/django.py
deleted file mode 100644
index 00f2ed54..00000000
--- a/coverage/django.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import sys
-
-
-ALL_TEMPLATE_MAP = {}
-
-def get_line_map(filename):
- if filename not in ALL_TEMPLATE_MAP:
- with open(filename) as template_file:
- template_source = template_file.read()
- line_lengths = [len(l) for l in template_source.splitlines(True)]
- ALL_TEMPLATE_MAP[filename] = list(running_sum(line_lengths))
- return ALL_TEMPLATE_MAP[filename]
-
-def get_line_number(line_map, offset):
- for lineno, line_offset in enumerate(line_map, start=1):
- if line_offset >= offset:
- return lineno
- return -1
-
-class DjangoTracer(object):
- def should_trace(self, canonical):
- return "/django/template/" in canonical
-
- def source(self, frame):
- if frame.f_code.co_name != 'render':
- return None
- that = frame.f_locals['self']
- return getattr(that, "source", None)
-
- def file_name(self, frame):
- source = self.source(frame)
- if not source:
- return None
- return source[0].name.encode(sys.getfilesystemencoding())
-
- def line_number_range(self, frame):
- source = self.source(frame)
- if not source:
- return -1, -1
- filename = source[0].name
- line_map = get_line_map(filename)
- start = get_line_number(line_map, source[1][0])
- end = get_line_number(line_map, source[1][1])
- if start < 0 or end < 0:
- return -1, -1
- return start, end
-
-def running_sum(seq):
- total = 0
- for num in seq:
- total += num
- yield total
-
-def ppp(obj):
- ret = []
- import inspect
- for name, value in inspect.getmembers(obj):
- if not callable(value):
- ret.append("%s=%r" % (name, value))
- attrs = ", ".join(ret)
- return "%s: %s" % (obj.__class__, attrs)
diff --git a/coverage/execfile.py b/coverage/execfile.py
index 4b5f3af4..8965d207 100644
--- a/coverage/execfile.py
+++ b/coverage/execfile.py
@@ -29,7 +29,6 @@ if importlib_util_find_spec:
Returns the file path of the module, and the name of the enclosing
package.
"""
- # pylint: disable=no-member
try:
spec = importlib_util_find_spec(modulename)
except ImportError as err:
diff --git a/coverage/files.py b/coverage/files.py
index 332c4225..3a298867 100644
--- a/coverage/files.py
+++ b/coverage/files.py
@@ -286,11 +286,10 @@ class PathAliases(object):
pattern = abs_file(pattern)
pattern += pattern_sep
- # Make a regex from the pattern. fnmatch always adds a \Z or $ to
+ # Make a regex from the pattern. fnmatch always adds a \Z to
# match the whole string, which we don't want.
regex_pat = fnmatch.translate(pattern).replace(r'\Z(', '(')
- if regex_pat.endswith("$"):
- regex_pat = regex_pat[:-1]
+
# We want */a/b.py to match on Windows too, so change slash to match
# either separator.
regex_pat = regex_pat.replace(r"\/", r"[\\/]")
diff --git a/coverage/misc.py b/coverage/misc.py
index f9a30bc5..f0e043b9 100644
--- a/coverage/misc.py
+++ b/coverage/misc.py
@@ -4,6 +4,7 @@ import errno
import hashlib
import inspect
import os
+import sys
from coverage.backward import string_class, to_bytes
@@ -135,6 +136,29 @@ class Hasher(object):
return self.md5.hexdigest()
+def overrides(obj, method_name, base_class):
+ """Does `obj` override the `method_name` it got from `base_class`?
+
+ Determine if `obj` implements the method called `method_name`, which it
+ inherited from `base_class`.
+
+ Returns a boolean.
+
+ """
+ klass = obj.__class__
+ klass_func = getattr(klass, method_name)
+ base_func = getattr(base_class, method_name)
+
+ # Python 2/3 compatibility: Python 2 returns an instancemethod object, the
+ # function is the .im_func attribute. Python 3 returns a plain function
+ # object already.
+ if sys.version_info < (3, 0):
+ klass_func = klass_func.im_func
+ base_func = base_func.im_func
+
+ return klass_func is not base_func
+
+
class CoverageException(Exception):
"""An exception specific to Coverage."""
pass
diff --git a/coverage/parser.py b/coverage/parser.py
index e7b9c029..9041e349 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -3,7 +3,7 @@
import collections, dis, re, token, tokenize
from coverage.backward import StringIO
-from coverage.backward import range # pylint: disable=W0622
+from coverage.backward import range # pylint: disable=redefined-builtin
from coverage.backward import bytes_to_ints, open_python_source
from coverage.bytecode import ByteCodes, CodeObjects
from coverage.misc import nice_pair, expensive, join_regex
@@ -216,7 +216,7 @@ class PythonParser(CodeParser):
try:
self._raw_parse()
except (tokenize.TokenError, IndentationError) as tokerr:
- msg, lineno = tokerr.args
+ msg, lineno = tokerr.args # pylint: disable=unpacking-non-sequence
raise NotPython(
"Couldn't parse '%s' as Python source: '%s' at %s" %
(self.filename, msg, lineno)
@@ -545,7 +545,7 @@ class ByteParser(object):
chunks.append(chunk)
# Give all the chunks a length.
- chunks[-1].length = bc.next_offset - chunks[-1].byte # pylint: disable=W0631,C0301
+ chunks[-1].length = bc.next_offset - chunks[-1].byte
for i in range(len(chunks)-1):
chunks[i].length = chunks[i+1].byte - chunks[i].byte
diff --git a/coverage/plugin.py b/coverage/plugin.py
index 3d41aab9..24a2b9a3 100644
--- a/coverage/plugin.py
+++ b/coverage/plugin.py
@@ -41,22 +41,58 @@ class CoveragePlugin(object):
`file_tracer`. It's an error to return None.
"""
- raise Exception("Plugin %r needs to implement file_reporter" % self.plugin_name)
+ raise NotImplementedError("Plugin %r needs to implement file_reporter" % self.plugin_name)
class FileTracer(object):
- """Support needed for files during the tracing phase."""
+ """Support needed for files during the tracing phase.
+
+ You may construct this object from CoveragePlugin.file_tracer any way you
+ like. A natural choice would be to pass the filename given to file_tracer.
+
+ """
def source_filename(self):
- return "xyzzy"
+ """The source filename for this file.
+
+ This may be any filename you like. A key responsibility of a plugin is
+ to own the mapping from Python execution back to whatever source
+ filename was originally the source of the code.
+
+ Returns:
+ The filename to credit with this execution.
- def dynamic_source_file_name(self):
- """Returns a callable that can return a source name for a frame.
+ """
+ return None
+
+ def has_dynamic_source_filename(self):
+ """Does this FileTracer have dynamic source filenames?
- The callable should take a filename and a frame, and return either a
- filename or None:
+ FileTracers can provide dynamically determined filenames by implementing
+ dynamic_source_filename. Invoking that function is expensive. To
+ determine whether it should invoke it, coverage.py uses the result of
+ this function to know if it needs to bother invoking
+ dynamic_source_filename.
- def dynamic_source_filename_func(filename, frame)
+ Returns:
+ A boolean, true if `dynamic_source_filename` should be called to
+ get dynamic source filenames.
+
+ """
+ return False
+
+ def dynamic_source_filename(self, filename, frame):
+ """Returns a dynamically computed source filename.
+
+ Some plugins need to compute the source filename dynamically for each
+ frame.
+
+ This function will not be invoked if `has_dynamic_source_filename`
+ returns False.
+
+ Returns:
+ The source filename for this frame, or None if this frame shouldn't
+ be measured.
Can return None if dynamic filenames aren't needed.
@@ -129,26 +165,3 @@ class Plugins(object):
def get(self, module):
return self.names[module]
-
-
-def overrides(obj, method_name, base_class):
- """Does `obj` override the `method_name` it got from `base_class`?
-
- Determine if `obj` implements the method called `method_name`, which it
- inherited from `base_class`.
-
- Returns a boolean.
-
- """
- klass = obj.__class__
- klass_func = getattr(klass, method_name)
- base_func = getattr(base_class, method_name)
-
- # Python 2/3 compatibility: Python 2 returns an instancemethod object, the
- # function is the .im_func attribute. Python 3 returns a plain function
- # object already.
- if sys.version_info < (3, 0):
- klass_func = klass_func.im_func
- base_func = base_func.im_func
-
- return klass_func is not base_func
diff --git a/coverage/pytracer.py b/coverage/pytracer.py
index 84071bb1..f3f490a0 100644
--- a/coverage/pytracer.py
+++ b/coverage/pytracer.py
@@ -28,6 +28,7 @@ class PyTracer(object):
self.arcs = False
self.should_trace = None
self.should_trace_cache = None
+ self.check_include = None
self.warn = None
self.plugin_data = None
# The threading module to use, if any.
@@ -83,13 +84,11 @@ class PyTracer(object):
self.cur_file_dict = None
if disp.trace:
tracename = disp.source_filename
- if disp.file_tracer:
- dyn_func = disp.file_tracer.dynamic_source_file_name()
- if dyn_func:
- tracename = dyn_func(tracename, frame)
- if tracename:
- if not self.check_include(tracename):
- tracename = None
+ if disp.file_tracer and disp.has_dynamic_filename:
+ tracename = disp.file_tracer.dynamic_source_filename(tracename, frame)
+ if tracename:
+ if not self.check_include(tracename, frame):
+ tracename = None
else:
tracename = None
if tracename:
diff --git a/coverage/summary.py b/coverage/summary.py
index a166ec2c..10ac7e2c 100644
--- a/coverage/summary.py
+++ b/coverage/summary.py
@@ -54,6 +54,17 @@ class SummaryReporter(Reporter):
try:
analysis = self.coverage._analyze(cu)
nums = analysis.numbers
+
+ if self.config.skip_covered:
+ # Don't report on 100% files.
+ no_missing_lines = (nums.n_missing == 0)
+ if self.branches:
+ no_missing_branches = (nums.n_partial_branches == 0)
+ else:
+ no_missing_branches = True
+ if no_missing_lines and no_missing_branches:
+ continue
+
args = (cu.name, nums.n_statements, nums.n_missing)
if self.branches:
args += (nums.n_branches, nums.n_partial_branches)
@@ -69,8 +80,6 @@ class SummaryReporter(Reporter):
args += (missing_fmtd,)
outfile.write(fmt_coverage % args)
total += nums
- except KeyboardInterrupt: # pragma: not covered
- raise
except Exception:
report_it = not self.config.ignore_errors
if report_it: