summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--coverage/runner.py138
-rw-r--r--coverage/test_plugins/nose_coverage.py111
-rw-r--r--coverage/test_plugins/pytest_coverage.py69
3 files changed, 318 insertions, 0 deletions
diff --git a/coverage/runner.py b/coverage/runner.py
new file mode 100644
index 00000000..ce81b1c7
--- /dev/null
+++ b/coverage/runner.py
@@ -0,0 +1,138 @@
+import optparse
+
+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) Imporve your code coverage ;)
+ """
+
+ def __init__(self, options, _covpkg=None):
+ self.options = options
+
+ # _covpkg is for dependency injection, so we can test this code.
+ if _covpkg:
+ self.covpkg = _covpkg
+ else:
+ import coverage
+ self.covpkg = coverage
+
+ self.coverage = None
+
+ def start(self):
+ # Set up coverage
+ 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,
+ )
+
+ # Run the script.
+ self.coverage.start()
+
+ def finish(self):
+ # end coverage and save the results
+ self.coverage.stop()
+ self.coverage.save()
+
+ # Remaining actions are reporting, with some common self.options.
+ report_args = {
+ 'morfs': [],
+ 'ignore_errors': self.options.cover_ignore_errors,
+ }
+
+ # Handle any omits
+ # Allow pointing to a file as well
+ try:
+ omit_file = open(self.options.cover_omit)
+ omit_prefixes = [line.strip() for line in omit_file.readlines()]
+ report_args['omit_prefixes'] = omit_prefixes
+ except:
+ 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, **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
+
+class Options(object):
+ """A namespace class for individual options we'll build parsers from."""
+
+ action = optparse.Option('',
+ '--cover-action', action='append', default=None,
+ 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())
+
+ branch = optparse.Option(
+ '--cover-branch', action='store_true',
+ help="Measure branch execution. HIGHLY EXPERIMENTAL!"
+ )
+ directory = optparse.Option(
+ '--cover-directory', action='store',
+ metavar="DIR",
+ help="Write the output files to DIR."
+ )
+ ignore_errors = optparse.Option(
+ '--cover-ignore-errors', action='store_true',
+ help="Ignore errors while reading source files."
+ )
+ pylib = optparse.Option(
+ '--cover-pylib', action='store_true',
+ help="Measure coverage even inside the Python installed library, "
+ "which isn't done by default."
+ )
+ show_missing = optparse.Option(
+ '--cover-show-missing', action='store_true',
+ help="Show line numbers of statements in each module that weren't "
+ "executed."
+ )
+ omit = optparse.Option(
+ '--cover-omit', action='store',
+ metavar="PRE1,PRE2,...",
+ default='',
+ help="Omit files when their filename path starts with one of these "
+ "prefixes."
+ )
+ output_xml = optparse.Option(
+ '--cover-outfile', action='store',
+ metavar="OUTFILE",
+ help="Write the XML report to this file. Defaults to 'coverage.xml'"
+ )
+ parallel_mode = optparse.Option(
+ '--cover-parallel-mode', action='store_true',
+ help="Include the machine name and process id in the .coverage "
+ "data file name."
+ )
+ timid = optparse.Option(
+ '--cover-timid', action='store_true',
+ help="Use a simpler but slower trace method. Try this if you get "
+ "seemingly impossible results!"
+ )
+ append = optparse.Option(
+ '--cover-append', action='store_false',
+ help="Append coverage data to .coverage, otherwise it is started "
+ "clean with each run."
+ )
diff --git a/coverage/test_plugins/nose_coverage.py b/coverage/test_plugins/nose_coverage.py
new file mode 100644
index 00000000..603ce43a
--- /dev/null
+++ b/coverage/test_plugins/nose_coverage.py
@@ -0,0 +1,111 @@
+import logging
+import unittest, os
+from nose.plugins import Plugin, PluginTester
+
+import sys
+import os
+sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '../../')))
+
+log = logging.getLogger(__name__)
+
+
+class Coverage(Plugin):
+ """
+ Activate a coverage report using Ned Batchelder's coverage module.
+ """
+
+ name = "coverage_new"
+ score = 1
+ status = {}
+
+ def options(self, parser, env):
+ """
+ Add options to command line.
+ """
+
+ Plugin.options(self, parser, env)
+
+ from coverage.runner import Options
+ # Loop the coverage options and append them to the plugin options
+ options = [a for a in dir(Options) if not a.startswith('_')]
+ for option in options:
+ opt = getattr(Options, option)
+ parser.add_option(opt)
+
+ def configure(self, options, config):
+ """
+ Configure plugin.
+ """
+ try:
+ self.status.pop('active')
+ except KeyError:
+ pass
+ Plugin.configure(self, options, config)
+ if self.enabled:
+ try:
+ import coverage
+ except ImportError:
+ log.error("Coverage not available: "
+ "unable to import coverage module")
+ self.enabled = False
+ return
+
+ self.config = config
+ self.status['active'] = True
+ self.options = options
+
+ def begin(self):
+ """
+ Begin recording coverage information.
+ """
+ log.debug("Coverage begin")
+ # Load the runner and start it up
+ from coverage.runner import CoverageTestWrapper
+ self.coverage = CoverageTestWrapper(self.options)
+ self.coverage.start()
+
+ def report(self, stream):
+ """
+ Output code coverage report.
+ """
+ log.debug("Coverage report")
+ stream.write("Processing Coverage...")
+ # finish up with coverage
+ self.coverage.finish()
+
+
+# Monkey patch omit_filter to use regex patterns for file omits
+def omit_filter(omit_prefixes, code_units):
+ import re
+ exclude_patterns = [re.compile(line.strip()) for line in omit_prefixes if line and not line.startswith('#')]
+ filtered = []
+ for cu in code_units:
+ skip = False
+ for pattern in exclude_patterns:
+ if pattern.search(cu.filename):
+ skip = True
+ break
+
+ if not skip:
+ filtered.append(cu)
+ return filtered
+
+try:
+ import coverage
+ coverage.codeunit.omit_filter = omit_filter
+except:
+ pass
+
+class TestCoverage(PluginTester, unittest.TestCase):
+ activate = '--with-coverage_new' # enables the plugin
+ plugins = [Coverage()]
+ args = ['--cover-action=report']
+
+ 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()]) \ No newline at end of file
diff --git a/coverage/test_plugins/pytest_coverage.py b/coverage/test_plugins/pytest_coverage.py
new file mode 100644
index 00000000..5b910e94
--- /dev/null
+++ b/coverage/test_plugins/pytest_coverage.py
@@ -0,0 +1,69 @@
+"""
+Write and report coverage data with 'coverage.py'.
+"""
+import py
+
+coverage = py.test.importorskip("coverage")
+
+def pytest_configure(config):
+ # Load the runner and start it up
+ from coverage.runner import CoverageTestWrapper
+
+ config.coverage = CoverageTestWrapper(config.option)
+ config.coverage.start()
+
+def pytest_terminal_summary(terminalreporter):
+ # Finished the tests start processing the coverage
+ config = terminalreporter.config
+ tw = terminalreporter._tw
+ tw.sep('-', 'coverage')
+ tw.line('Processing Coverage...')
+
+ # finish up with coverage
+ config.coverage.finish()
+
+def pytest_addoption(parser):
+ """
+ Get all the options from the coverage.runner and import them
+ """
+ from coverage.runner import Options
+
+ group = parser.getgroup('Coverage options')
+ # Loop the coverage options and append them to the plugin options
+ options = [a for a in dir(Options) if not a.startswith('_')]
+ for option in options:
+ opt = getattr(Options, option)
+ group._addoption_instance(opt, shortupper=True)
+
+# Monkey patch omit_filter to use regex patterns for file omits
+def omit_filter(omit_prefixes, code_units):
+ import re
+ exclude_patterns = [re.compile(line.strip()) for line in omit_prefixes if line and not line.startswith('#')]
+ filtered = []
+ for cu in code_units:
+ skip = False
+ for pattern in exclude_patterns:
+ if pattern.search(cu.filename):
+ skip = True
+ break
+
+ if not skip:
+ filtered.append(cu)
+ return filtered
+
+coverage.codeunit.omit_filter = omit_filter
+
+def test_functional(testdir):
+ py.test.importorskip("coverage")
+ testdir.plugins.append("coverage")
+ testdir.makepyfile("""
+ def f():
+ x = 42
+ def test_whatever():
+ pass
+ """)
+ result = testdir.runpytest()
+ assert result.ret == 0
+ assert result.stdout.fnmatch_lines([
+ '*Processing Coverage*'
+ ]) \ No newline at end of file