summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/cmdline.py6
-rw-r--r--coverage/codeunit.py70
-rw-r--r--coverage/control.py94
-rw-r--r--coverage/files.py65
-rw-r--r--coverage/morf.py49
5 files changed, 156 insertions, 128 deletions
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index 7e6659a..f13e49e 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -132,10 +132,8 @@ class CoverageScript:
directory = settings.get('directory=')
omit = settings.get('omit=')
- if omit is not None:
- omit = [self.coverage.abs_file(p) for p in omit.split(',')]
- else:
- omit = []
+ if omit:
+ omit = omit.split(',')
if settings.get('report'):
self.coverage.report_engine(args, show_missing, ignore_errors, omit_prefixes=omit)
diff --git a/coverage/codeunit.py b/coverage/codeunit.py
new file mode 100644
index 0000000..ab68101
--- /dev/null
+++ b/coverage/codeunit.py
@@ -0,0 +1,70 @@
+"""Code unit (module) handling for coverage.py"""
+
+import glob, os, types
+
+def code_unit_factory(morfs, file_wrangler, omit_prefixes=None):
+ """Construct a list of CodeUnits from polymorphic inputs.
+
+ `morfs` is a module or a filename, or a list of same.
+ `file_wrangler` is a FileWrangler that can help resolve filenames.
+ `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes
+ will be omitted from the list.
+
+ Returns a list of CodeUnit objects.
+
+ """
+
+ # Be sure we have a list.
+ if not isinstance(morfs, types.ListType):
+ morfs = [morfs]
+
+ # On Windows, the shell doesn't expand wildcards. Do it here.
+ globbed = []
+ for morf in morfs:
+ if isinstance(morf, basestring) and ('?' in morf or '*' in morf):
+ globbed.extend(glob.glob(morf))
+ else:
+ globbed.append(morf)
+ morfs = globbed
+
+ code_units = [CodeUnit(morf, file_wrangler) for morf in morfs]
+
+ if omit_prefixes:
+ prefixes = [file_wrangler.abs_file(p) for p in omit_prefixes]
+ filtered = []
+ for cu in code_units:
+ for prefix in prefixes:
+ if cu.name.startswith(prefix):
+ break
+ else:
+ filtered.append(cu)
+
+ code_units = filtered
+
+ return code_units
+
+
+class CodeUnit:
+ """Code unit: a filename or module.
+
+ `name` is a human-readable name for this code unit.
+ `filename` is the os path from which we can read the source.
+
+ """
+
+ def __init__(self, morf, file_wrangler):
+ if hasattr(morf, '__file__'):
+ f = morf.__file__
+ else:
+ f = morf
+ self.filename = file_wrangler.canonical_filename(f)
+
+ if hasattr(morf, '__name__'):
+ n = morf.__name__
+ else:
+ n = os.path.splitext(morf)[0]
+ n = file_wrangler.relative_filename(n)
+ self.name = n
+
+ def __cmp__(self, other):
+ return cmp(self.name, other.name)
diff --git a/coverage/control.py b/coverage/control.py
index 0c149bb..f452d1a 100644
--- a/coverage/control.py
+++ b/coverage/control.py
@@ -1,11 +1,11 @@
"""Core control stuff for coverage.py"""
-import glob, os, re, sys, types
+import os, re, sys
from coverage.data import CoverageData
from coverage.misc import nice_pair, CoverageException
-from coverage.morf import morf_factory, Morf
-
+from coverage.codeunit import code_unit_factory
+from coverage.files import FileWrangler
class coverage:
def __init__(self):
@@ -16,7 +16,7 @@ class coverage:
self.nesting = 0
self.cstack = []
self.xstack = []
- self.relative_dir = self.abs_file(os.curdir)+os.sep
+ self.file_wrangler = FileWrangler()
self.collector = Collector(self.should_trace)
@@ -26,10 +26,6 @@ class coverage:
# specify both -r and -a without doing double work.
self.analysis_cache = {}
- # Cache of results of calling the canonical_filename() method, to
- # avoid duplicating work.
- self.canonical_filename_cache = {}
-
# The default exclude pattern.
self.exclude('# *pragma[: ]*[nN][oO] *[cC][oO][vV][eE][rR]')
@@ -49,7 +45,7 @@ class coverage:
return False
# TODO: flag: ignore std lib?
# TODO: ignore by module as well as file?
- return self.canonical_filename(filename)
+ return self.file_wrangler.canonical_filename(filename)
def use_cache(self, usecache, cache_file=None):
self.data.usefile(usecache, cache_file)
@@ -97,58 +93,6 @@ class coverage:
"""Entry point for combining together parallel-mode coverage data."""
self.data.combine_parallel_data()
- def get_zip_data(self, filename):
- """ Get data from `filename` if it is a zip file path, or return None
- if it is not.
- """
- import zipimport
- markers = ['.zip'+os.sep, '.egg'+os.sep]
- for marker in markers:
- if marker in filename:
- parts = filename.split(marker)
- try:
- zi = zipimport.zipimporter(parts[0]+marker[:-1])
- except zipimport.ZipImportError:
- continue
- try:
- data = zi.get_data(parts[1])
- except IOError:
- continue
- return data
- return None
-
- def abs_file(self, filename):
- """ Helper function to turn a filename into an absolute normalized
- filename.
- """
- return os.path.normcase(os.path.abspath(os.path.realpath(filename)))
-
- def relative_filename(self, filename):
- """ Convert filename to relative filename from self.relative_dir.
- """
- return filename.replace(self.relative_dir, "")
-
- def canonical_filename(self, filename):
- """Return a canonical filename for `filename`.
-
- An absolute path with no redundant components and normalized case.
-
- """
- if not self.canonical_filename_cache.has_key(filename):
- f = filename
- if os.path.isabs(f) and not os.path.exists(f):
- if not self.get_zip_data(f):
- f = os.path.basename(f)
- if not os.path.isabs(f):
- for path in [os.curdir] + sys.path:
- g = os.path.join(path, f)
- if os.path.exists(g):
- f = g
- break
- cf = self.abs_file(f)
- self.canonical_filename_cache[filename] = cf
- return self.canonical_filename_cache[filename]
-
def group_collected_data(self):
"""Group the collected data by filename and reset the collector."""
self.data.add_raw_data(self.collector.data_points())
@@ -179,7 +123,7 @@ class coverage:
ext = '.py'
if ext == '.py':
if not os.path.exists(filename):
- source = self.get_zip_data(filename)
+ source = self.file_wrangler.get_zip_data(filename)
if not source:
raise CoverageException(
"No source for code '%s'." % morf.filename
@@ -228,8 +172,8 @@ class coverage:
return f, s, m, mf
def analysis2(self, morf):
- morf = Morf(morf)
- return self.analysis_engine(morf)
+ code_units = code_unit_factory(morf, self.file_wrangler)
+ return self.analysis_engine(code_units[0])
def analysis_engine(self, morf):
filename, statements, excluded, line_map = self.analyze_morf(morf)
@@ -258,10 +202,10 @@ class coverage:
self.report_engine(morfs, show_missing=show_missing, ignore_errors=ignore_errors, file=file)
def report_engine(self, morfs, show_missing=True, ignore_errors=False, file=None, omit_prefixes=None):
- morfs = morf_factory(morfs, omit_prefixes)
- morfs.sort()
+ code_units = code_unit_factory(morfs, self.file_wrangler, omit_prefixes)
+ code_units.sort()
- max_name = max(5, max(map(lambda m: len(m.name), morfs)))
+ max_name = max(5, max(map(lambda cu: len(cu.name), code_units)))
fmt_name = "%%- %ds " % max_name
fmt_err = fmt_name + "%s: %s"
header = fmt_name % "Name" + " Stmts Exec Cover"
@@ -275,16 +219,16 @@ class coverage:
print >>file, "-" * len(header)
total_statements = 0
total_executed = 0
- for morf in morfs:
+ for cu in code_units:
try:
- _, statements, _, missing, readable = self.analysis_engine(morf)
+ _, statements, _, missing, readable = self.analysis_engine(cu)
n = len(statements)
m = n - len(missing)
if n > 0:
pc = 100.0 * m / n
else:
pc = 100.0
- args = (morf.name, n, m, pc)
+ args = (cu.name, n, m, pc)
if show_missing:
args = args + (readable,)
print >>file, fmt_coverage % args
@@ -295,8 +239,8 @@ class coverage:
except:
if not ignore_errors:
typ, msg = sys.exc_info()[:2]
- print >>file, fmt_err % (morf.name, typ, msg)
- if len(morfs) > 1:
+ print >>file, fmt_err % (cu.name, typ, msg)
+ if len(code_units) > 1:
print >>file, "-" * len(header)
if total_statements > 0:
pc = 100.0 * total_executed / total_statements
@@ -313,10 +257,10 @@ class coverage:
else_re = re.compile(r"\s*else\s*:\s*(#|$)")
def annotate(self, morfs, directory=None, ignore_errors=False, omit_prefixes=None):
- morfs = morf_factory(morfs, omit_prefixes)
- for morf in morfs:
+ code_units = code_unit_factory(morfs, self.file_wrangler, omit_prefixes)
+ for cu in code_units:
try:
- filename, statements, excluded, missing, _ = self.analysis_engine(morf)
+ filename, statements, excluded, missing, _ = self.analysis_engine(cu)
self.annotate_file(filename, statements, excluded, missing, directory)
except KeyboardInterrupt:
raise
diff --git a/coverage/files.py b/coverage/files.py
new file mode 100644
index 0000000..625357c
--- /dev/null
+++ b/coverage/files.py
@@ -0,0 +1,65 @@
+"""File wrangling."""
+
+import os, sys
+
+class FileWrangler:
+ """Understand how filenames work."""
+
+ def __init__(self):
+ self.relative_dir = self.abs_file(os.curdir) + os.sep
+
+ # Cache of results of calling the canonical_filename() method, to
+ # avoid duplicating work.
+ self.canonical_filename_cache = {}
+
+ def abs_file(self, filename):
+ """ Helper function to turn a filename into an absolute normalized
+ filename.
+ """
+ return os.path.normcase(os.path.abspath(os.path.realpath(filename)))
+
+ def relative_filename(self, filename):
+ """ Convert filename to relative filename from self.relative_dir.
+ """
+ return filename.replace(self.relative_dir, "")
+
+ def canonical_filename(self, filename):
+ """Return a canonical filename for `filename`.
+
+ An absolute path with no redundant components and normalized case.
+
+ """
+ if not self.canonical_filename_cache.has_key(filename):
+ f = filename
+ if os.path.isabs(f) and not os.path.exists(f):
+ if not self.get_zip_data(f):
+ f = os.path.basename(f)
+ if not os.path.isabs(f):
+ for path in [os.curdir] + sys.path:
+ g = os.path.join(path, f)
+ if os.path.exists(g):
+ f = g
+ break
+ cf = self.abs_file(f)
+ self.canonical_filename_cache[filename] = cf
+ return self.canonical_filename_cache[filename]
+
+ def get_zip_data(self, filename):
+ """ Get data from `filename` if it is a zip file path, or return None
+ if it is not.
+ """
+ import zipimport
+ markers = ['.zip'+os.sep, '.egg'+os.sep]
+ for marker in markers:
+ if marker in filename:
+ parts = filename.split(marker)
+ try:
+ zi = zipimport.zipimporter(parts[0]+marker[:-1])
+ except zipimport.ZipImportError:
+ continue
+ try:
+ data = zi.get_data(parts[1])
+ except IOError:
+ continue
+ return data
+ return None
diff --git a/coverage/morf.py b/coverage/morf.py
deleted file mode 100644
index ba97ed0..0000000
--- a/coverage/morf.py
+++ /dev/null
@@ -1,49 +0,0 @@
-"""Module or Filename handling for coverage.py"""
-
-# TODO: Distinguish between morf (input: module or filename), and Morf (class
-# that can represent either).
-
-def morf_factory(morfs, omit_prefixes=None):
- # Be sure we have a list.
- if not isinstance(morfs, types.ListType):
- morfs = [morfs]
-
- # On Windows, the shell doesn't expand wildcards. Do it here.
- globbed = []
- for morf in morfs:
- if isinstance(morf, basestring) and ('?' in morf or '*' in morf):
- globbed.extend(glob.glob(morf))
- else:
- globbed.append(morf)
- morfs = globbed
-
- morfs = map(Morf, morfs)
-
- if omit_prefixes:
- filtered_morfs = []
- for morf in morfs:
- for prefix in omit_prefixes:
- if morf.name.startswith(prefix):
- break
- else:
- filtered_morfs.append(morf)
-
- morfs = filtered_morfs
-
- return morfs
-
-class Morf:
- def __init__(self, morf):
- if hasattr(morf, '__file__'):
- f = morf.__file__
- else:
- f = morf
- self.filename = self.canonical_filename(f)
-
- if hasattr(morf, '__name__'):
- self.name = morf.__name__
- else:
- self.name = self.relative_filename(os.path.splitext(morf)[0])
-
- def __cmp__(self, other):
- return cmp(self.name, other.name)