diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2010-05-29 23:51:53 -0400 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2010-05-29 23:51:53 -0400 |
commit | 2fd1d9b7dd96a64e6920dda369a13d95af728394 (patch) | |
tree | d6532544f433443c1dc826bfdc6598334a19a4b3 | |
parent | 879fe9c4721c557a31cb951ef9e0d7a098de319c (diff) | |
parent | 343f36c3fe555a196ca59268f71ea4c7838f4d1c (diff) | |
download | python-coveragepy-2fd1d9b7dd96a64e6920dda369a13d95af728394.tar.gz |
Merge latest code from main coverage.py repo
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | TODO.txt | 8 | ||||
-rw-r--r-- | coverage/codeunit.py | 1 | ||||
-rw-r--r-- | coverage/noseplugin.py | 49 | ||||
-rw-r--r-- | coverage/testplugin.py | 197 | ||||
-rw-r--r-- | setup.py | 9 | ||||
-rw-r--r-- | test/test_testplugin.py | 36 |
7 files changed, 299 insertions, 3 deletions
@@ -35,7 +35,7 @@ pep8: testready: testdata devinst tests: testready - nosetests + PYTHONPATH=test/eggsrc nosetests testdata: $(TEST_ZIP) $(TEST_EGG) $(TEST_ZIP): test/covmodzip1.py @@ -35,6 +35,14 @@ Coverage TODO +* Test-runner plugin + +- Support distribute/3k installation, register py.test-entrypoint there + as well as py-1.1.1 is 2.4-3.1 compatible. +- Skip registering plugin entrypoints if not running on Jython. +- Update docs to include a section about the plugin. + + * Speed + C extension collector diff --git a/coverage/codeunit.py b/coverage/codeunit.py index cc7c226..96cdb02 100644 --- a/coverage/codeunit.py +++ b/coverage/codeunit.py @@ -61,7 +61,6 @@ def code_unit_factory(morfs, file_locator, omit=None, include=None): return code_units - class CodeUnit(object): """Code unit: a filename or module. diff --git a/coverage/noseplugin.py b/coverage/noseplugin.py new file mode 100644 index 0000000..9e254ea --- /dev/null +++ b/coverage/noseplugin.py @@ -0,0 +1,49 @@ +import logging +from nose.plugins import Plugin + +from coverage.testplugin import CoverageTestWrapper, options as coverage_opts + + +log = logging.getLogger(__name__) + + +class Coverage(Plugin): + """Nose plugin for coverage reporting.""" + + score = 1 + status = {} + + def options(self, parser, env): + """Add command-line options.""" + + super(Coverage, self).options(parser, env) + for opt in coverage_opts: + parser.add_option(opt) + + def configure(self, options, config): + """Configure plugin.""" + + try: + self.status.pop('active') + except KeyError: + pass + + super(Coverage, self).configure(options, config) + + self.config = config + self.status['active'] = True + self.options = options + + def begin(self): + """Begin recording coverage information.""" + + log.debug("Coverage begin") + self.coverage = CoverageTestWrapper(self.options) + self.coverage.start() + + def report(self, stream): + """Output code coverage report.""" + + log.debug("Coverage report") + stream.write("Processing Coverage...") + self.coverage.finish(stream) diff --git a/coverage/testplugin.py b/coverage/testplugin.py new file mode 100644 index 0000000..bb0231a --- /dev/null +++ b/coverage/testplugin.py @@ -0,0 +1,197 @@ +import re +import sys +import optparse + +import coverage + + +class CoverageTestWrapper(object): + """A coverage test wrapper. + + 1) Setup the with the parsed options + 2) Call start() + 3) Run your tests + 4) Call finish() + 5) Improve your code coverage ;) + """ + + coverPackages = None + + def __init__(self, options, _covpkg=coverage): + # _covpkg is for dependency injection, so we can test this code. + + self.options = options + self.covpkg = _covpkg + + self.coverage = None + + self.coverTests = options.cover_tests + self.coverPackages = options.cover_package + + def start(self): + self.coverage = self.covpkg.coverage( + data_suffix = bool(self.options.cover_parallel_mode), + cover_pylib = self.options.cover_pylib, + timid = self.options.cover_timid, + branch = self.options.cover_branch, + ) + + self.skipModules = sys.modules.keys()[:] #TODO: is this necessary?? + + self.coverage.start() + + def finish(self, stream=None): + self.coverage.stop() + self.coverage.save() + + modules = [module for name, module in sys.modules.items() + if self._want_module(name, module)] + + # Remaining actions are reporting, with some common self.options. + report_args = { + 'morfs': modules, + 'ignore_errors': self.options.cover_ignore_errors, + } + + try: # try looking for an omit file + omit_file = open(self.options.cover_omit) + omit_prefixes = [line.strip() for line in omit_file.readlines()] + report_args['omit_prefixes'] = omit_prefixes + except: # assume cover_omit is a ',' separated list if provided + omit = self.options.cover_omit.split(',') + report_args['omit_prefixes'] = omit + + if 'report' in self.options.cover_actions: + self.coverage.report( + show_missing=self.options.cover_show_missing, + file=stream, **report_args) + if 'annotate' in self.options.cover_actions: + self.coverage.annotate( + directory=self.options.cover_directory, **report_args) + if 'html' in self.options.cover_actions: + self.coverage.html_report( + directory=self.options.cover_directory, **report_args) + if 'xml' in self.options.cover_actions: + outfile = self.options.cover_outfile + if outfile == '-': + outfile = None + self.coverage.xml_report(outfile=outfile, **report_args) + + return + + def _want_module(self, name, module): + for package in self.coverPackages: + if module is not None and name.startswith(package): + return True + + return False + + +options = [ + optparse.Option('--cover-action', action='append', default=['report'], + dest='cover_actions', type="choice", + choices=['annotate', 'html', 'report', 'xml'], + help="""\ +annotate Annotate source files with execution information. +html Create an HTML report. +report Report coverage stats on modules. +xml Create an XML report of coverage results. + """.strip()), + + optparse.Option('--cover-package', action='append', default=[], + dest="cover_package", metavar="COVER_PACKAGE", + help=("Restrict coverage output to selected package " + "- can be specified multiple times")), + + optparse.Option("--cover-tests", action="store_true", dest="cover_tests", + metavar="[NOSE_COVER_TESTS]", default=False, + help="Include test modules in coverage report "), + + optparse.Option('--cover-branch', action='store_true', + help="Measure branch execution. HIGHLY EXPERIMENTAL!"), + + optparse.Option('--cover-directory', action='store', metavar="DIR", + help="Write the output files to DIR."), + + optparse.Option('--cover-ignore-errors', action='store_true', + help="Ignore errors while reading source files."), + + optparse.Option('--cover-pylib', action='store_true', + help=("Measure coverage even inside the Python installed " + "library, which isn't done by default.")), + + optparse.Option('--cover-show-missing', action='store_true', + help=("Show line numbers of statements in each module " + "that weren't executed.")), + + optparse.Option('--cover-omit', action='store', + metavar="PRE1,PRE2,...", default='', + help=("Omit files when their filename path starts with " + "one of these prefixes.")), + + optparse.Option('--cover-outfile', action='store', metavar="OUTFILE", + help=("Write the XML report to this file. Defaults to " + "'coverage.xml'")), + + optparse.Option('--cover-parallel-mode', action='store_true', + help=("Include the machine name and process id in the " + ".coverage data file name.")), + + optparse.Option('--cover-timid', action='store_true', + help=("Use a simpler but slower trace method. Try this " + "if you get seemingly impossible results!")), + + optparse.Option('--cover-append', action='store_false', + help=("Append coverage data to .coverage, otherwise it " + "is started clean with each run.")) +] + +# py.test plugin hooks + +def pytest_addoption(parser): + """ + Get all the options from the coverage.runner and import them + """ + group = parser.getgroup('Coverage options') + for opt in options: + group._addoption_instance(opt) + +def pytest_configure(config): + # Load the runner and start it up + if config.getvalue("cover_actions"): + config.pluginmanager.register(DoCover(config), "do_coverage") + +class DoCover: + + def __init__(self, config): + self.config = config + + def pytest_sessionstart(self): + self.coverage = CoverageTestWrapper(self.config.option) + # XXX maybe better to start/suspend/resume coverage + # for each single test item + self.coverage.start() + + def pytest_terminal_summary(self, terminalreporter): + config = terminalreporter.config + tw = terminalreporter._tw + tw.sep('-', 'coverage') + tw.line('Processing Coverage...') + self.coverage.finish() + + +# XXX please make the following unnessary +# Monkey patch omit_filter to use regex patterns for file omits +def omit_filter(omit_prefixes, code_units, file_locator): + exclude_patterns = [re.compile(line.strip()) for line in omit_prefixes + if line and not line.startswith('#')] + filtered = [] + for cu in code_units: + for pattern in exclude_patterns: + if pattern.search(cu.filename): + break + else: + filtered.append(cu) + return filtered + +coverage.codeunit.omit_filter = omit_filter @@ -80,8 +80,15 @@ setup_args = dict( entry_points = { 'console_scripts': [ 'coverage = coverage:main', - ] + ], + 'pytest11': [ + 'coverage = coverage.testplugin', + ], + 'nose.plugins.0.10': [ + 'coverage = coverage.noseplugin:Coverage', + ], }, + # We need to get HTML assets from our htmlfiles dir. zip_safe = False, diff --git a/test/test_testplugin.py b/test/test_testplugin.py new file mode 100644 index 0000000..87e2e4b --- /dev/null +++ b/test/test_testplugin.py @@ -0,0 +1,36 @@ +import py +import unittest +from nose.plugins import PluginTester +from coverage.noseplugin import Coverage + +class TestCoverage(PluginTester, unittest.TestCase): + activate = '--with-coverage' # enables the plugin + plugins = [Coverage()] + args = ['--cover-action=report'] + + @py.test.mark.skipif(True) # "requires nose test runner" + def test_output(self): + assert "Processing Coverage..." in self.output, ( + "got: %s" % self.output) + def makeSuite(self): + class TC(unittest.TestCase): + def runTest(self): + raise ValueError("Coverage down") + return unittest.TestSuite([TC()]) + +pytest_plugins = ['pytester'] +def test_functional(testdir): + testdir.makepyfile(""" + def f(): + x = 42 + def test_whatever(): + pass + """) + result = testdir.runpytest("--cover-action=annotate") + assert result.ret == 0 + assert result.stdout.fnmatch_lines([ + '*Processing Coverage*' + ]) + coveragefile = testdir.tmpdir.join(".coverage") + assert coveragefile.check() + # XXX try loading it? |