summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Stanek <dstanek@dstanek.com>2010-05-21 23:26:51 -0400
committerDavid Stanek <dstanek@dstanek.com>2010-05-21 23:26:51 -0400
commit5b078202a2a80dfb40084a41d97ccd8859750fd1 (patch)
tree330be09dfe7ff46449b9a38879523e07a1fddb02
parent51e42b3e3c0f92568a8e058189585c2baf3ca4ff (diff)
parent7ccdc87d3e4ad3a7933c3fc34e37213f36f3229a (diff)
downloadpython-coveragepy-git-5b078202a2a80dfb40084a41d97ccd8859750fd1.tar.gz
merged in latest changes from Ned's repo
-rw-r--r--TODO.txt8
-rw-r--r--coverage/codeunit.py27
-rw-r--r--coverage/noseplugin.py67
-rw-r--r--coverage/testplugin.py219
-rw-r--r--setup.py9
-rw-r--r--test/test_testplugin.py36
6 files changed, 355 insertions, 11 deletions
diff --git a/TODO.txt b/TODO.txt
index 446fe7cc..84723968 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -47,6 +47,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 a0da1a71..01708957 100644
--- a/coverage/codeunit.py
+++ b/coverage/codeunit.py
@@ -50,19 +50,26 @@ def code_unit_factory(
code_units = filtered
if omit_prefixes:
- assert not isinstance(omit_prefixes, string_class) # common mistake
- prefixes = [file_locator.abs_file(p) for p in omit_prefixes]
- filtered = []
- for cu in code_units:
- for prefix in prefixes:
- if cu.filename.startswith(prefix):
- break
- else:
- filtered.append(cu)
- code_units = filtered
+ code_units = omit_filter(omit_prefixes, code_units)
return code_units
+def omit_filter(omit_prefixes, code_units):
+ """
+ The filtering method removing any unwanted code_units
+
+ Refactored out so you can easily monkeypatch if needs be
+ """
+ prefixes = [file_locator.abs_file(p) for p in omit_prefixes]
+ filtered = []
+ for cu in code_units:
+ for prefix in prefixes:
+ if cu.filename.startswith(prefix):
+ break
+ else:
+ filtered.append(cu)
+
+ return filtered
class CodeUnit(object):
"""Code unit: a filename or module.
diff --git a/coverage/noseplugin.py b/coverage/noseplugin.py
new file mode 100644
index 00000000..b2bf9b5a
--- /dev/null
+++ b/coverage/noseplugin.py
@@ -0,0 +1,67 @@
+import logging
+import unittest, os
+from nose.plugins import Plugin
+import sys
+
+from coverage.testplugin import CoverageTestWrapper, options as coverage_opts
+
+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)
+ for opt in coverage_opts:
+ 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
+ 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(stream)
+
diff --git a/coverage/testplugin.py b/coverage/testplugin.py
new file mode 100644
index 00000000..4ff598a9
--- /dev/null
+++ b/coverage/testplugin.py
@@ -0,0 +1,219 @@
+import os
+import re
+import sys
+import optparse
+from types import ListType
+
+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 ;)
+ """
+
+ coverTests = False
+ coverPackages = None
+
+ 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
+
+ self.coverTests = options.cover_tests
+ self.coverPackage = options.cover_package
+
+ 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,
+ )
+
+ self.skipModules = sys.modules.keys()[:]
+
+ # Run the script.
+ self.coverage.start()
+
+ def finish(self, stream=None):
+ # end coverage and save the results
+ self.coverage.stop()
+ self.coverage.save()
+
+ modules = [module
+ for name, module in sys.modules.items()
+ if module is not None and name.startswith(self.coverPackage)]
+
+ # Remaining actions are reporting, with some common self.options.
+ report_args = {
+ 'morfs': modules,
+ '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,
+ 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
+
+options = [
+ 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()),
+ optparse.Option(
+ '--cover-package', action='store',
+ dest="cover_package",
+ metavar="COVER_PACKAGE",
+ help="Restrict coverage output to selected package"
+ ),
+ 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):
+ # Finished the tests start processing the coverage
+ 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):
+ 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
diff --git a/setup.py b/setup.py
index 822b1da5..e8aa3341 100644
--- a/setup.py
+++ b/setup.py
@@ -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 00000000..c216ead6
--- /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_new' # 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?