summaryrefslogtreecommitdiffstats
path: root/test/unittest/plugins/funccover.py
diff options
context:
space:
mode:
authorAdrian Likins <alikins@redhat.com>2008-10-15 22:10:06 -0400
committerAdrian Likins <alikins@redhat.com>2008-10-15 22:10:06 -0400
commit98c7dae5c2ae6253d887cfd4fa4fb0b969af0d7c (patch)
treeebea7605b672c0c4a95b74d5eb50ab714bd0817e /test/unittest/plugins/funccover.py
parent9f7531516e5eed3a293ab1e4b225146cd84f6dbf (diff)
downloadfunc-98c7dae5c2ae6253d887cfd4fa4fb0b969af0d7c.tar.gz
func-98c7dae5c2ae6253d887cfd4fa4fb0b969af0d7c.tar.xz
func-98c7dae5c2ae6253d887cfd4fa4fb0b969af0d7c.zip
Add in some support for test coverage.
test/unittest/plugins/*: add a funccover nosetest plugin that can write out code coverage attribution information cover_to_html.py: script to convert coverage information to html output. This isn't integrated into the test scripts yet, but should work for manually ran tests. See plugins/README for more info
Diffstat (limited to 'test/unittest/plugins/funccover.py')
-rw-r--r--test/unittest/plugins/funccover.py159
1 files changed, 159 insertions, 0 deletions
diff --git a/test/unittest/plugins/funccover.py b/test/unittest/plugins/funccover.py
new file mode 100644
index 0000000..a45d9e1
--- /dev/null
+++ b/test/unittest/plugins/funccover.py
@@ -0,0 +1,159 @@
+"""If you have Ned Batchelder's coverage_ module installed, you may activate a
+coverage report with the --with-coverage switch or NOSE_WITH_COVERAGE
+environment variable. The coverage report will cover any python source module
+imported after the start of the test run, excluding modules that match
+testMatch. If you want to include those modules too, use the --cover-tests
+switch, or set the NOSE_COVER_TESTS environment variable to a true value. To
+restrict the coverage report to modules from a particular package or packages,
+use the --cover-packages switch or the NOSE_COVER_PACKAGES environment
+variable.
+
+.. _coverage: http://www.nedbatchelder.com/code/modules/coverage.html
+"""
+import logging
+import os
+import sys
+from nose.plugins.base import Plugin
+from nose.util import tolist
+
+log = logging.getLogger(__name__)
+
+class FuncCoverage(Plugin):
+ """
+ If you have Ned Batchelder's coverage module installed, you may
+ activate a coverage report. The coverage report will cover any
+ python source module imported after the start of the test run, excluding
+ modules that match testMatch. If you want to include those modules too,
+ use the --cover-tests switch, or set the NOSE_COVER_TESTS environment
+ variable to a true value. To restrict the coverage report to modules from
+ a particular package or packages, use the --cover-packages switch or the
+ NOSE_COVER_PACKAGES environment variable.
+ """
+ coverTests = False
+ coverPackages = None
+
+ def options(self, parser, env=os.environ):
+ Plugin.options(self, parser, env)
+ parser.add_option("--func-cover-package", action="append",
+ default=env.get('FUNC_NOSE_COVER_PACKAGE'),
+ dest="cover_packages",
+ help="Restrict coverage output to selected packages "
+ "[FUNC_NOSE_COVER_PACKAGE]")
+ parser.add_option("--func-cover-erase", action="store_true",
+ default=env.get('FUNC_NOSE_COVER_ERASE'),
+ dest="cover_erase",
+ help="Erase previously collected coverage "
+ "statistics before run")
+ parser.add_option("--func-cover-tests", action="store_true",
+ dest="cover_tests",
+ default=env.get('FUNC_NOSE_COVER_TESTS'),
+ help="Include test modules in coverage report "
+ "[FUNC_NOSE_COVER_TESTS]")
+ parser.add_option("--func-cover-annotate", action="store_true",
+ dest="cover_annotate",
+ help="write out annotated files"
+ "[FUNC_NOSE_COVER_ANNOTATE]"),
+ parser.add_option("--func-cover-dir", action="store",
+ dest="cover_dir",
+ help="directory to write data to"
+ "[FUNC_NOSE_COVER_DIR]"),
+
+ parser.add_option("--func-cover-inclusive", action="store_true",
+ dest="cover_inclusive",
+ default=env.get('FUNC_NOSE_COVER_INCLUSIVE'),
+ help="Include all python files under working "
+ "directory in coverage report. Useful for "
+ "discovering holes in test coverage if not all "
+ "files are imported by the test suite. "
+ "[FUNC_NOSE_COVER_INCLUSIVE]")
+
+
+ def configure(self, options, config):
+ 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.conf = config
+ self.coverErase = options.cover_erase
+ self.coverTests = options.cover_tests
+ self.coverPackages = []
+ self.coverDir = options.cover_dir
+ self.coverAnnotate = options.cover_annotate
+ if options.cover_packages:
+ for pkgs in [tolist(x) for x in options.cover_packages]:
+ self.coverPackages.extend(pkgs)
+ self.coverInclusive = options.cover_inclusive
+ if self.coverPackages:
+ log.info("Coverage report will include only packages: %s",
+ self.coverPackages)
+
+ def begin(self):
+ log.debug("Coverage begin")
+ import coverage
+ self.skipModules = sys.modules.keys()[:]
+ if self.coverErase:
+ log.debug("Clearing previously collected coverage statistics")
+ coverage.erase()
+ coverage.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
+ coverage.start()
+
+ def report(self, stream):
+ log.debug("Coverage report")
+ import coverage
+ coverage.stop()
+ modules = [ module
+ for name, module in sys.modules.items()
+ if self.wantModuleCoverage(name, module) ]
+ log.debug("Coverage report will cover modules: %s", modules)
+ if self.coverDir and self.coverAnnotate:
+ coverage.annotate(modules, self.coverDir)
+ fd = open("%s/cover.report" % self.coverDir, "w")
+ coverage.report(modules, file=fd)
+ fd.close()
+
+ def wantModuleCoverage(self, name, module):
+ if not hasattr(module, '__file__'):
+ log.debug("no coverage of %s: no __file__", name)
+ return False
+ root, ext = os.path.splitext(module.__file__)
+ if not ext in ('.py', '.pyc', '.pyo'):
+ log.debug("no coverage of %s: not a python file", name)
+ return False
+ if self.coverPackages:
+ for package in self.coverPackages:
+ if (name.startswith(package)
+ and (self.coverTests
+ or not self.conf.testMatch.search(name))):
+ log.debug("coverage for %s", name)
+ return True
+ if name in self.skipModules:
+ log.debug("no coverage for %s: loaded before coverage start",
+ name)
+ return False
+ if self.conf.testMatch.search(name) and not self.coverTests:
+ log.debug("no coverage for %s: is a test", name)
+ return False
+ # accept any package that passed the previous tests, unless
+ # coverPackages is on -- in that case, if we wanted this
+ # module, we would have already returned True
+ return not self.coverPackages
+
+ def wantFile(self, file, package=None):
+ """If inclusive coverage enabled, return true for all source files
+ in wanted packages.
+ """
+ if self.coverInclusive:
+ if file.endswith(".py"):
+ if package and self.coverPackages:
+ for want in self.coverPackages:
+ if package.startswith(want):
+ return True
+ else:
+ return True
+ return None
+