diff options
-rw-r--r-- | coverage/cmdline.py | 6 | ||||
-rw-r--r-- | coverage/codeunit.py | 70 | ||||
-rw-r--r-- | coverage/control.py | 94 | ||||
-rw-r--r-- | coverage/files.py | 65 | ||||
-rw-r--r-- | coverage/morf.py | 49 |
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)
|