diff options
author | Zooko Ofsimplegeo <zooko@simplegeo.com> | 2010-04-19 16:08:37 -0600 |
---|---|---|
committer | Zooko Ofsimplegeo <zooko@simplegeo.com> | 2010-04-19 16:08:37 -0600 |
commit | ce4dbf3b92dcccf146cdf8c0bfb2f6da0a7c5dd5 (patch) | |
tree | c1841e353e5968604551161e2afbd4ebad38fe75 | |
parent | 6638c632a10af6477a712f1a86af6d9e9ab3a3e3 (diff) | |
download | python-coveragepy-git-ce4dbf3b92dcccf146cdf8c0bfb2f6da0a7c5dd5.tar.gz |
add a --require option to specify directories which are required to be at the beginning of the path for any file that is going to be included in code coverage
make the --omit and --require options apply to code coverage generation as well as to reporting; This speeds up tests from 6 seconds to 1 second on my system, as well as making the resulting .coverage db include *only* the code that I care about, which helps with my code coverage progression/regression tool.
--HG--
extra : transplant_source : %1F.4%81%E8%DA%0B%D0%D5%9D%89%DE%E1vY%E6%CD%1A%EB%C9
-rw-r--r-- | coverage/__init__.py | 2 | ||||
-rw-r--r-- | coverage/annotate.py | 12 | ||||
-rw-r--r-- | coverage/cmdline.py | 17 | ||||
-rw-r--r-- | coverage/codeunit.py | 18 | ||||
-rw-r--r-- | coverage/config.py | 31 | ||||
-rw-r--r-- | coverage/control.py | 76 | ||||
-rw-r--r-- | coverage/html.py | 9 | ||||
-rw-r--r-- | coverage/report.py | 19 | ||||
-rw-r--r-- | coverage/summary.py | 12 |
9 files changed, 168 insertions, 28 deletions
diff --git a/coverage/__init__.py b/coverage/__init__.py index 9fd4a8d2..3741805b 100644 --- a/coverage/__init__.py +++ b/coverage/__init__.py @@ -5,7 +5,7 @@ http://nedbatchelder.com/code/coverage """ -__version__ = "3.3.2a1" # see detailed history in CHANGES.txt +__version__ = "3.3.2a1z8" # see detailed history in CHANGES.txt __url__ = "http://nedbatchelder.com/code/coverage" diff --git a/coverage/annotate.py b/coverage/annotate.py index 2117b657..9c2c7a0a 100644 --- a/coverage/annotate.py +++ b/coverage/annotate.py @@ -33,9 +33,15 @@ class AnnotateReporter(Reporter): blank_re = re.compile(r"\s*(#|$)") else_re = re.compile(r"\s*else\s*:\s*(#|$)") - def report(self, morfs, directory=None, omit_prefixes=None): - """Run the report.""" - self.report_files(self.annotate_file, morfs, directory, omit_prefixes) + def report(self, morfs, directory=None, omit_prefixes=None, require_prefixes=None): + """Run the report. + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. + """ + self.report_files(self.annotate_file, morfs, directory, omit_prefixes, require_prefixes) def annotate_file(self, cu, analysis): """Annotate a single file. diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 9e15074b..acf8903d 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -54,6 +54,12 @@ class Opts(object): help="Omit files when their filename path starts with one of these " "prefixes." ) + require = optparse.Option( + '', '--require', action='store', + metavar="PRE1,PRE2,...", + help="Include files only when their filename path starts with one of these " + "prefixes." + ) output_xml = optparse.Option( '-o', '', action='store', dest="outfile", metavar="OUTFILE", @@ -99,6 +105,7 @@ class CoverageOptionParser(optparse.OptionParser, object): help=None, ignore_errors=None, omit=None, + require=None, parallel_mode=None, pylib=None, rcfile=True, @@ -219,6 +226,7 @@ CMDS = { Opts.directory, Opts.ignore_errors, Opts.omit, + Opts.require, ] + GLOBAL_ARGS, usage = "[options] [modules]", description = "Make annotated copies of the given files, marking " @@ -256,6 +264,7 @@ CMDS = { Opts.directory, Opts.ignore_errors, Opts.omit, + Opts.require, ] + GLOBAL_ARGS, usage = "[options] [modules]", description = "Create an HTML report of the coverage of the files. " @@ -267,6 +276,7 @@ CMDS = { [ Opts.ignore_errors, Opts.omit, + Opts.require, Opts.show_missing, ] + GLOBAL_ARGS, usage = "[options] [modules]", @@ -280,6 +290,8 @@ CMDS = { Opts.pylib, Opts.parallel_mode, Opts.timid, + Opts.omit, + Opts.require, ] + GLOBAL_ARGS, defaults = {'erase_first': True}, cmd = "run", @@ -291,6 +303,7 @@ CMDS = { [ Opts.ignore_errors, Opts.omit, + Opts.require, Opts.output_xml, ] + GLOBAL_ARGS, cmd = "xml", @@ -495,6 +508,10 @@ class CoverageScript(object): if options.omit: omit = options.omit.split(',') report_args['omit_prefixes'] = omit + require = None + if options.require: + require = options.require.split(',') + report_args['require_prefixes'] = require if 'report' in options.actions: self.coverage.report( diff --git a/coverage/codeunit.py b/coverage/codeunit.py index 9bf6dc97..51640b80 100644 --- a/coverage/codeunit.py +++ b/coverage/codeunit.py @@ -6,13 +6,16 @@ from coverage.backward import string_class, StringIO from coverage.misc import CoverageException -def code_unit_factory(morfs, file_locator, omit_prefixes=None): +def code_unit_factory(morfs, file_locator, omit_prefixes=None, require_prefixes=None): """Construct a list of CodeUnits from polymorphic inputs. `morfs` is a module or a filename, or a list of same. `file_locator` is a FileLocator that can help resolve filenames. `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. Returns a list of CodeUnit objects. @@ -33,7 +36,18 @@ def code_unit_factory(morfs, file_locator, omit_prefixes=None): code_units = [CodeUnit(morf, file_locator) for morf in morfs] - if omit_prefixes: + if require_prefixes: + assert not isinstance(require_prefixes, string_class) # common mistake + prefixes = [file_locator.abs_file(p) for p in require_prefixes] + filtered = [] + for cu in code_units: + for prefix in prefixes: + if cu.filename.startswith(prefix): + filtered.append(cu) + break + + code_units = filtered + elif omit_prefixes: assert not isinstance(omit_prefixes, string_class) # common mistake prefixes = [file_locator.abs_file(p) for p in omit_prefixes] filtered = [] diff --git a/coverage/config.py b/coverage/config.py index 8f508f28..92d83016 100644 --- a/coverage/config.py +++ b/coverage/config.py @@ -25,6 +25,7 @@ class CoverageConfig(object): self.exclude_list = ['(?i)# *pragma[: ]*no *cover'] self.ignore_errors = False self.omit_prefixes = None + self.require_prefixes = None # Defaults for [html] self.html_dir = "htmlcov" @@ -67,6 +68,26 @@ class CoverageConfig(object): self.parallel = cp.getboolean('run', 'parallel') if cp.has_option('run', 'timid'): self.timid = cp.getboolean('run', 'timid') + if cp.has_option('run', 'omit'): + # omit is a list of prefixes, on separate lines, or separated by + # commas. + omit_list = cp.get('run', 'omit') + self.omit_prefixes = [] + for omit_line in omit_list.split('\n'): + for omit in omit_line.split(','): + omit = omit.strip() + if omit: + self.omit_prefixes.append(omit) + if cp.has_option('run', 'require'): + # require is a list of prefixes, on separate lines, or separated by + # commas. + require_list = cp.get('run', 'require') + self.require_prefixes = [] + for require_line in require_list.split('\n'): + for require in require_line.split(','): + require = require.strip() + if require: + self.require_prefixes.append(require) # [report] if cp.has_option('report', 'exclude_lines'): @@ -85,6 +106,16 @@ class CoverageConfig(object): omit = omit.strip() if omit: self.omit_prefixes.append(omit) + if cp.has_option('report', 'require'): + # require is a list of prefixes, on separate lines, or separated by + # commas. + require_list = cp.get('report', 'require') + self.require_prefixes = [] + for require_line in require_list.split('\n'): + for require in require_line.split(','): + require = require.strip() + if require: + self.require_prefixes.append(require) # [html] if cp.has_option('html', 'directory'): diff --git a/coverage/control.py b/coverage/control.py index e4f5fae7..a337d6bd 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -31,7 +31,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): + auto_data=False, timid=None, branch=None, config_file=True, omit_prefixes=None, require_prefixes=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 @@ -58,6 +58,13 @@ class coverage(object): standard file is read (".coveragerc"). If it is False, then no file is read. + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. """ from coverage import __version__ @@ -80,7 +87,9 @@ class coverage(object): # 4: from constructor arguments: self.config.from_args( data_file=data_file, cover_pylib=cover_pylib, timid=timid, - branch=branch, parallel=bool_or_none(data_suffix) + branch=branch, parallel=bool_or_none(data_suffix), + omit_prefixes=omit_prefixes, + require_prefixes=require_prefixes ) self.auto_data = auto_data @@ -91,6 +100,11 @@ class coverage(object): self.file_locator = FileLocator() + if self.config.omit_prefixes: + self.omit_prefixes = [self.file_locator.abs_file(p) for p in self.config.omit_prefixes] + if self.config.require_prefixes: + self.require_prefixes = [self.file_locator.abs_file(p) for p in self.config.require_prefixes] + self.collector = Collector( self._should_trace, timid=self.config.timid, branch=self.config.branch @@ -172,6 +186,19 @@ class coverage(object): if canonical.startswith(self.cover_prefix): return False + if self.require_prefixes: + for prefix in self.require_prefixes: + if canonical.startswith(prefix): + return canonical + else: + return False + elif omit_prefixes: + for prefix in prefixes: + if canonical.startswith(prefix): + return False + + code_units = filtered + return canonical # To log what should_trace returns, change this to "if 1:" @@ -322,26 +349,32 @@ class coverage(object): return Analysis(self, it) def report(self, morfs=None, show_missing=True, ignore_errors=None, - file=None, omit_prefixes=None): # pylint: disable-msg=W0622 + file=None, omit_prefixes=None, require_prefixes=None): # pylint: disable-msg=W0622 """Write a summary report to `file`. Each module in `morfs` is listed, with counts of statements, executed statements, missing statements, and a list of lines missed. + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. """ self.config.from_args( ignore_errors=ignore_errors, - omit_prefixes=omit_prefixes + omit_prefixes=omit_prefixes, + require_prefixes=require_prefixes ) reporter = SummaryReporter( self, show_missing, self.config.ignore_errors ) reporter.report( - morfs, outfile=file, omit_prefixes=self.config.omit_prefixes + morfs, outfile=file, omit_prefixes=self.config.omit_prefixes, require_prefixes=self.config.require_prefixes ) def annotate(self, morfs=None, directory=None, ignore_errors=None, - omit_prefixes=None): + omit_prefixes=None, require_prefixes=None): """Annotate a list of modules. Each module in `morfs` is annotated. The source is written to a new @@ -349,34 +382,47 @@ class coverage(object): marker to indicate the coverage of the line. Covered lines have ">", excluded lines have "-", and missing lines have "!". + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. """ self.config.from_args( ignore_errors=ignore_errors, - omit_prefixes=omit_prefixes + omit_prefixes=omit_prefixes, + require_prefixes=require_prefixes ) reporter = AnnotateReporter(self, self.config.ignore_errors) reporter.report( - morfs, directory=directory, omit_prefixes=self.config.omit_prefixes + morfs, directory=directory, omit_prefixes=self.config.omit_prefixes, require_prefixes=self.config.require_prefixes ) def html_report(self, morfs=None, directory=None, ignore_errors=None, - omit_prefixes=None): + omit_prefixes=None, require_prefixes=None): """Generate an HTML report. + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. """ self.config.from_args( ignore_errors=ignore_errors, omit_prefixes=omit_prefixes, + require_prefixes=require_prefixes, html_dir=directory, ) reporter = HtmlReporter(self, self.config.ignore_errors) reporter.report( morfs, directory=self.config.html_dir, - omit_prefixes=self.config.omit_prefixes + omit_prefixes=self.config.omit_prefixes, + require_prefixes=self.config.require_prefixes ) def xml_report(self, morfs=None, outfile=None, ignore_errors=None, - omit_prefixes=None): + omit_prefixes=None, require_prefixes=None): """Generate an XML report of coverage results. The report is compatible with Cobertura reports. @@ -384,10 +430,16 @@ class coverage(object): Each module in `morfs` is included in the report. `outfile` is the path to write the file to, "-" will write to stdout. + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. """ self.config.from_args( ignore_errors=ignore_errors, omit_prefixes=omit_prefixes, + require_prefixes=require_prefixes, xml_output=outfile, ) file_to_close = None @@ -400,7 +452,7 @@ class coverage(object): try: reporter = XmlReporter(self, self.config.ignore_errors) reporter.report( - morfs, omit_prefixes=self.config.omit_prefixes, outfile=outfile + morfs, omit_prefixes=self.config.omit_prefixes, require_prefixes=self.config.require_prefixes, outfile=outfile ) finally: if file_to_close: diff --git a/coverage/html.py b/coverage/html.py index 7d0d064b..61712f36 100644 --- a/coverage/html.py +++ b/coverage/html.py @@ -31,18 +31,23 @@ class HtmlReporter(Reporter): self.files = [] self.arcs = coverage.data.has_arcs() - def report(self, morfs, directory, omit_prefixes=None): + def report(self, morfs, directory, omit_prefixes=None, require_prefixes=None): """Generate an HTML report for `morfs`. `morfs` is a list of modules or filenames. `directory` is where to put the HTML files. `omit_prefixes` is a list of strings, prefixes of modules to omit from the report. + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. """ assert directory, "must provide a directory for html reporting" # Process all the files. - self.report_files(self.html_file, morfs, directory, omit_prefixes) + self.report_files(self.html_file, morfs, directory, omit_prefixes, require_prefixes) # Write the index file. self.index_file() diff --git a/coverage/report.py b/coverage/report.py index 5b66f999..e2a09fbf 100644 --- a/coverage/report.py +++ b/coverage/report.py @@ -24,26 +24,35 @@ class Reporter(object): # classes. self.directory = None - def find_code_units(self, morfs, omit_prefixes): + def find_code_units(self, morfs, omit_prefixes, require_prefixes): """Find the code units we'll report on. - `morfs` is a list of modules or filenames. `omit_prefixes` is a list of prefixes to leave out of the list. + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. """ morfs = morfs or self.coverage.data.executed_files() self.code_units = code_unit_factory( - morfs, self.coverage.file_locator, omit_prefixes) + morfs, self.coverage.file_locator, omit_prefixes, require_prefixes) self.code_units.sort() def report_files(self, report_fn, morfs, directory=None, - omit_prefixes=None): + omit_prefixes=None, require_prefixes=None): """Run a reporting function on a number of morfs. `report_fn` is called for each relative morf in `morfs`. + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. """ - self.find_code_units(morfs, omit_prefixes) + self.find_code_units(morfs, omit_prefixes, require_prefixes) if not self.code_units: raise CoverageException("No data to report.") diff --git a/coverage/summary.py b/coverage/summary.py index 7348acab..696f6b36 100644 --- a/coverage/summary.py +++ b/coverage/summary.py @@ -14,10 +14,16 @@ class SummaryReporter(Reporter): self.show_missing = show_missing self.branches = coverage.data.has_arcs() - def report(self, morfs, omit_prefixes=None, outfile=None): - """Writes a report summarizing coverage statistics per module.""" + def report(self, morfs, omit_prefixes=None, outfile=None, require_prefixes=None): + """Writes a report summarizing coverage statistics per module. - self.find_code_units(morfs, omit_prefixes) + `omit_prefixes` is a list of prefixes. CodeUnits that match those prefixes + will be omitted from the list. + `require_prefixes` is a list of prefixes. Only CodeUnits that match those prefixes + will be included in the list. + You are required to pass at most one of `omit_prefixes` and `require_prefixes`. + """ + self.find_code_units(morfs, omit_prefixes, require_prefixes) # Prepare the formatting strings max_name = max([len(cu.name) for cu in self.code_units] + [5]) |