From b47f561ba2c2eb922921064bc347441322379059 Mon Sep 17 00:00:00 2001 From: "rozza@x103086.gcapmedia.com" Date: Fri, 20 Nov 2009 10:08:52 +0000 Subject: Moving file filter logic out so it can be monkey patched easily --- coverage/codeunit.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/coverage/codeunit.py b/coverage/codeunit.py index e310705..28fa055 100644 --- a/coverage/codeunit.py +++ b/coverage/codeunit.py @@ -34,19 +34,26 @@ def code_unit_factory(morfs, file_locator, omit_prefixes=None): code_units = [CodeUnit(morf, file_locator) for morf in morfs] if omit_prefixes: - 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. -- cgit v1.2.1 From 8f0cddc85173002e3113bd3fb0b4cee6eeb8a56a Mon Sep 17 00:00:00 2001 From: "rozza@x103086.gcapmedia.com" Date: Fri, 20 Nov 2009 11:27:52 +0000 Subject: Adding a CoverageTestWrapper to be used by test runners / plugins. Added example plugins for pytest and nose Warning: I haven't tested the nose plugin against a test suite Warning: I use a file listing files / paths to omit from code coverage and use regexes to filter them out To ignore that remove the monkey patch.. --- coverage/runner.py | 138 +++++++++++++++++++++++++++++++ coverage/test_plugins/nose_coverage.py | 111 +++++++++++++++++++++++++ coverage/test_plugins/pytest_coverage.py | 69 ++++++++++++++++ 3 files changed, 318 insertions(+) create mode 100644 coverage/runner.py create mode 100644 coverage/test_plugins/nose_coverage.py create mode 100644 coverage/test_plugins/pytest_coverage.py diff --git a/coverage/runner.py b/coverage/runner.py new file mode 100644 index 0000000..ce81b1c --- /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 0000000..603ce43 --- /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 0000000..5b910e9 --- /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 -- cgit v1.2.1 From e813a7992e9697a34cb08033862c90a22cb0050a Mon Sep 17 00:00:00 2001 From: "rozza@x103086.gcapmedia.com" Date: Fri, 20 Nov 2009 11:54:30 +0000 Subject: Typo --- coverage/runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage/runner.py b/coverage/runner.py index ce81b1c..ba4861a 100644 --- a/coverage/runner.py +++ b/coverage/runner.py @@ -8,7 +8,7 @@ class CoverageTestWrapper(object): 2) Call start() 3) Run your tests 4) Call finish() - 5) Imporve your code coverage ;) + 5) Improve your code coverage ;) """ def __init__(self, options, _covpkg=None): -- cgit v1.2.1 From 49c15be1d5606d1255d2384f1f4876bd1a66a634 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 25 Nov 2009 18:08:57 +0100 Subject: * move pytest_coverage/nose_plugin to become directly importable * fixing order of coverage options * pytest: only do coverage-processing if cover-action is defined --- coverage/nose_coverage.py | 106 +++++++++++++++++++++++++++++ coverage/pytest_coverage.py | 74 +++++++++++++++++++++ coverage/runner.py | 60 ++++++++--------- coverage/test_plugins/nose_coverage.py | 111 ------------------------------- coverage/test_plugins/pytest_coverage.py | 69 ------------------- setup.py | 5 +- 6 files changed, 214 insertions(+), 211 deletions(-) create mode 100644 coverage/nose_coverage.py create mode 100644 coverage/pytest_coverage.py delete mode 100644 coverage/test_plugins/nose_coverage.py delete mode 100644 coverage/test_plugins/pytest_coverage.py diff --git a/coverage/nose_coverage.py b/coverage/nose_coverage.py new file mode 100644 index 0000000..6ba0a10 --- /dev/null +++ b/coverage/nose_coverage.py @@ -0,0 +1,106 @@ +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 + for opt in options: + 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()]) diff --git a/coverage/pytest_coverage.py b/coverage/pytest_coverage.py new file mode 100644 index 0000000..094be44 --- /dev/null +++ b/coverage/pytest_coverage.py @@ -0,0 +1,74 @@ +""" +Write and report coverage data with 'coverage.py'. +""" +import py +import coverage + +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') + 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): + from coverage.runner import CoverageTestWrapper + 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() + + +# 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 + +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? diff --git a/coverage/runner.py b/coverage/runner.py index ba4861a..ef4c1f2 100644 --- a/coverage/runner.py +++ b/coverage/runner.py @@ -73,66 +73,66 @@ class CoverageTestWrapper(object): return -class Options(object): - """A namespace class for individual options we'll build parsers from.""" - - action = optparse.Option('', +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()) - - branch = optparse.Option( + 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-branch', action='store_true', help="Measure branch execution. HIGHLY EXPERIMENTAL!" - ) - directory = optparse.Option( + ), + optparse.Option( '--cover-directory', action='store', metavar="DIR", help="Write the output files to DIR." - ) - ignore_errors = optparse.Option( + ), + optparse.Option( '--cover-ignore-errors', action='store_true', help="Ignore errors while reading source files." - ) - pylib = optparse.Option( + ), + 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( + ), + optparse.Option( '--cover-show-missing', action='store_true', help="Show line numbers of statements in each module that weren't " "executed." - ) - omit = optparse.Option( + ), + 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( + ), + optparse.Option( '--cover-outfile', action='store', metavar="OUTFILE", help="Write the XML report to this file. Defaults to 'coverage.xml'" - ) - parallel_mode = optparse.Option( + ), + 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( + ), + 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( + ), + 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 deleted file mode 100644 index 603ce43..0000000 --- a/coverage/test_plugins/nose_coverage.py +++ /dev/null @@ -1,111 +0,0 @@ -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 deleted file mode 100644 index 5b910e9..0000000 --- a/coverage/test_plugins/pytest_coverage.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -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 diff --git a/setup.py b/setup.py index 4a5aba1..7ad92f3 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,10 @@ if sys.hexversion < 0x03000000: entry_points = { 'console_scripts': [ 'coverage = coverage:main', - ] + ], + 'pytest11': [ + 'coverage = coverage.pytest_coverage', + ], }, # We need to get HTML assets from our htmlfiles dir. -- cgit v1.2.1 From 1a6b591c080c84932ba27990d95cbbd356fbacc9 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 25 Nov 2009 18:56:24 +0100 Subject: merging/unifying test plugin code * coverage/testplugin.py contains common test plugin options and pytest hooks (which don't induce "import py") * coverage/nose_coverage.py contains a basic Nose Plugin * test/test_testplugin.py contains a pytest-functional test and a nose-skeleton one. skipped as appropriate. --- coverage/nose_coverage.py | 44 +--------- coverage/pytest_coverage.py | 74 ----------------- coverage/runner.py | 138 -------------------------------- coverage/testplugin.py | 190 ++++++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- test/test_testplugin.py | 36 +++++++++ 6 files changed, 230 insertions(+), 254 deletions(-) delete mode 100644 coverage/pytest_coverage.py delete mode 100644 coverage/runner.py create mode 100644 coverage/testplugin.py create mode 100644 test/test_testplugin.py diff --git a/coverage/nose_coverage.py b/coverage/nose_coverage.py index 6ba0a10..907e680 100644 --- a/coverage/nose_coverage.py +++ b/coverage/nose_coverage.py @@ -1,10 +1,9 @@ import logging import unittest, os -from nose.plugins import Plugin, PluginTester - +from nose.plugins import Plugin import sys -import os -sys.path.insert(0, os.path.realpath(os.path.join(os.path.dirname(__file__), '../../'))) + +from coverage.testplugin import CoverageTestWrapper log = logging.getLogger(__name__) @@ -55,7 +54,6 @@ class Coverage(Plugin): """ log.debug("Coverage begin") # Load the runner and start it up - from coverage.runner import CoverageTestWrapper self.coverage = CoverageTestWrapper(self.options) self.coverage.start() @@ -68,39 +66,3 @@ class Coverage(Plugin): # 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()]) diff --git a/coverage/pytest_coverage.py b/coverage/pytest_coverage.py deleted file mode 100644 index 094be44..0000000 --- a/coverage/pytest_coverage.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Write and report coverage data with 'coverage.py'. -""" -import py -import coverage - -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') - 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): - from coverage.runner import CoverageTestWrapper - 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() - - -# 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 - -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? diff --git a/coverage/runner.py b/coverage/runner.py deleted file mode 100644 index ef4c1f2..0000000 --- a/coverage/runner.py +++ /dev/null @@ -1,138 +0,0 @@ -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) Improve 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 - -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-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." - ) -] - diff --git a/coverage/testplugin.py b/coverage/testplugin.py new file mode 100644 index 0000000..a979ad2 --- /dev/null +++ b/coverage/testplugin.py @@ -0,0 +1,190 @@ +import coverage, 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) Improve 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 + +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-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 7ad92f3..38678d1 100644 --- a/setup.py +++ b/setup.py @@ -43,7 +43,7 @@ if sys.hexversion < 0x03000000: 'coverage = coverage:main', ], 'pytest11': [ - 'coverage = coverage.pytest_coverage', + 'coverage = coverage.testplugin', ], }, diff --git a/test/test_testplugin.py b/test/test_testplugin.py new file mode 100644 index 0000000..d102a4f --- /dev/null +++ b/test/test_testplugin.py @@ -0,0 +1,36 @@ +import py +import unittest +from nose.plugins import PluginTester +from coverage.nose_coverage 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? -- cgit v1.2.1 From ebb5ca5f5ba29d3389f2b57e49f0a3d9648938a7 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 25 Nov 2009 19:37:34 +0100 Subject: add python3/jython compat TODO --- TODO.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/TODO.txt b/TODO.txt index 01e886c..bf4e3e5 100644 --- a/TODO.txt +++ b/TODO.txt @@ -59,6 +59,9 @@ x Tricky swapping of collector like figleaf, pycov, et al. (Don't need to do * Convenience +- 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 - Command line modules should also be directories, meaning all the modules in that directory. - Why can't a morf also be a string, the name of a module? -- cgit v1.2.1 From a62bd09903e9ab0482c400ac7fdc38e414f3419d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Fri, 27 Nov 2009 16:07:19 -0500 Subject: Move todo's to a distinct plugin section. --- TODO.txt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/TODO.txt b/TODO.txt index bf4e3e5..14eac01 100644 --- a/TODO.txt +++ b/TODO.txt @@ -16,6 +16,14 @@ Coverage TODO - XML report needs to get branch information. +* 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 @@ -59,9 +67,6 @@ x Tricky swapping of collector like figleaf, pycov, et al. (Don't need to do * Convenience -- 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 - Command line modules should also be directories, meaning all the modules in that directory. - Why can't a morf also be a string, the name of a module? -- cgit v1.2.1 From ab747f7c2f3bb2e9ca5d88e78b9bac5284466187 Mon Sep 17 00:00:00 2001 From: holger krekel Date: Wed, 10 Feb 2010 18:23:18 +0100 Subject: integrate a patch by prologic (James Mills) to add some options. --- coverage/testplugin.py | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/coverage/testplugin.py b/coverage/testplugin.py index a979ad2..7b8c0f4 100644 --- a/coverage/testplugin.py +++ b/coverage/testplugin.py @@ -1,4 +1,10 @@ -import coverage, optparse +import os +import re +import sys +import optparse +from types import ListType + +import coverage class CoverageTestWrapper(object): """ @@ -11,6 +17,9 @@ class CoverageTestWrapper(object): 5) Improve your code coverage ;) """ + coverTests = False + coverPackages = None + def __init__(self, options, _covpkg=None): self.options = options @@ -22,6 +31,9 @@ class CoverageTestWrapper(object): self.covpkg = coverage self.coverage = None + + self.coverTests = options.cover_tests + self.coverPackage = options.cover_package def start(self): # Set up coverage @@ -31,6 +43,8 @@ class CoverageTestWrapper(object): timid = self.options.cover_timid, branch = self.options.cover_branch, ) + + self.skipModules = sys.modules.keys()[:] # Run the script. self.coverage.start() @@ -39,10 +53,14 @@ class CoverageTestWrapper(object): # 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': [], + 'morfs': modules, 'ignore_errors': self.options.cover_ignore_errors, } @@ -55,7 +73,7 @@ class CoverageTestWrapper(object): 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) @@ -83,7 +101,17 @@ 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!" -- cgit v1.2.1 From db9a63f43c8d0c2aa83c64c6e5b314797e9486c8 Mon Sep 17 00:00:00 2001 From: David Stanek Date: Sat, 20 Feb 2010 18:36:25 -0500 Subject: fixed busted nose integration --- coverage/nose_coverage.py | 7 +++---- coverage/testplugin.py | 5 +++-- setup.py | 3 +++ 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/coverage/nose_coverage.py b/coverage/nose_coverage.py index 907e680..b2bf9b5 100644 --- a/coverage/nose_coverage.py +++ b/coverage/nose_coverage.py @@ -3,7 +3,7 @@ import unittest, os from nose.plugins import Plugin import sys -from coverage.testplugin import CoverageTestWrapper +from coverage.testplugin import CoverageTestWrapper, options as coverage_opts log = logging.getLogger(__name__) @@ -22,8 +22,7 @@ class Coverage(Plugin): Add options to command line. """ Plugin.options(self, parser, env) - from coverage.runner import options - for opt in options: + for opt in coverage_opts: parser.add_option(opt) def configure(self, options, config): @@ -64,5 +63,5 @@ class Coverage(Plugin): log.debug("Coverage report") stream.write("Processing Coverage...") # finish up with coverage - self.coverage.finish() + self.coverage.finish(stream) diff --git a/coverage/testplugin.py b/coverage/testplugin.py index 7b8c0f4..4ff598a 100644 --- a/coverage/testplugin.py +++ b/coverage/testplugin.py @@ -49,7 +49,7 @@ class CoverageTestWrapper(object): # Run the script. self.coverage.start() - def finish(self): + def finish(self, stream=None): # end coverage and save the results self.coverage.stop() self.coverage.save() @@ -76,7 +76,8 @@ class CoverageTestWrapper(object): if 'report' in self.options.cover_actions: self.coverage.report( - show_missing=self.options.cover_show_missing, **report_args) + 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) diff --git a/setup.py b/setup.py index 38678d1..37f9681 100644 --- a/setup.py +++ b/setup.py @@ -45,6 +45,9 @@ if sys.hexversion < 0x03000000: 'pytest11': [ 'coverage = coverage.testplugin', ], + 'nose.plugins.0.10': [ + 'coverage = coverage.nose_coverage:Coverage', + ], }, # We need to get HTML assets from our htmlfiles dir. -- cgit v1.2.1 From 07cc3b6945f600ef84d83367cf9c7a034d2e9865 Mon Sep 17 00:00:00 2001 From: David Stanek Date: Sat, 20 Feb 2010 19:36:21 -0500 Subject: moved the nose plugin to a better name --- coverage/nose_coverage.py | 67 ----------------------------------------------- coverage/noseplugin.py | 67 +++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 2 +- test/test_testplugin.py | 2 +- 4 files changed, 69 insertions(+), 69 deletions(-) delete mode 100644 coverage/nose_coverage.py create mode 100644 coverage/noseplugin.py diff --git a/coverage/nose_coverage.py b/coverage/nose_coverage.py deleted file mode 100644 index b2bf9b5..0000000 --- a/coverage/nose_coverage.py +++ /dev/null @@ -1,67 +0,0 @@ -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/noseplugin.py b/coverage/noseplugin.py new file mode 100644 index 0000000..b2bf9b5 --- /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/setup.py b/setup.py index 37f9681..6a607d4 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ if sys.hexversion < 0x03000000: 'coverage = coverage.testplugin', ], 'nose.plugins.0.10': [ - 'coverage = coverage.nose_coverage:Coverage', + 'coverage = coverage.noseplugin:Coverage', ], }, diff --git a/test/test_testplugin.py b/test/test_testplugin.py index d102a4f..c216ead 100644 --- a/test/test_testplugin.py +++ b/test/test_testplugin.py @@ -1,7 +1,7 @@ import py import unittest from nose.plugins import PluginTester -from coverage.nose_coverage import Coverage +from coverage.noseplugin import Coverage class TestCoverage(PluginTester, unittest.TestCase): activate = '--with-coverage_new' # enables the plugin -- cgit v1.2.1 From 6ea6858226eebbecf5196a78b216c588dd8a9098 Mon Sep 17 00:00:00 2001 From: David Stanek Date: Fri, 21 May 2010 23:39:40 -0400 Subject: added the test egg's path to PYTHONPATH when running the tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index bf79408..6e27ef0 100644 --- a/Makefile +++ b/Makefile @@ -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 -- cgit v1.2.1 From f1935555fb148d173b9adf2e35f687573c929370 Mon Sep 17 00:00:00 2001 From: David Stanek Date: Fri, 21 May 2010 23:40:04 -0400 Subject: modified to use report as the default action - the way the old nose plugin worked --- coverage/testplugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage/testplugin.py b/coverage/testplugin.py index 4ff598a..89334d7 100644 --- a/coverage/testplugin.py +++ b/coverage/testplugin.py @@ -94,7 +94,7 @@ class CoverageTestWrapper(object): options = [ optparse.Option('', - '--cover-action', action='append', default=None, + '--cover-action', action='append', default=['report'], dest='cover_actions', type="choice", choices=['annotate', 'html', 'report', 'xml'], help="""\ annotate Annotate source files with execution information. -- cgit v1.2.1 From 8713c466aeb25a736e7b095682b8dc6500e3d382 Mon Sep 17 00:00:00 2001 From: David Stanek Date: Fri, 21 May 2010 23:40:04 -0400 Subject: fixed an error where not specifying the cover package would cause a traceback --- coverage/testplugin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/coverage/testplugin.py b/coverage/testplugin.py index 89334d7..3cef47c 100644 --- a/coverage/testplugin.py +++ b/coverage/testplugin.py @@ -54,10 +54,12 @@ class CoverageTestWrapper(object): 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)] - + modules = [] + if self.coverPackage: + for name, module in sys.modules.items(): + if module is not None and name.startswith(self.coverPackage): + modules.append(module) + # Remaining actions are reporting, with some common self.options. report_args = { 'morfs': modules, -- cgit v1.2.1 From 8550826b49fa6f229098601c9236e9895ab312bf Mon Sep 17 00:00:00 2001 From: David Stanek Date: Fri, 21 May 2010 23:40:04 -0400 Subject: fixed the --cover-packages option --- coverage/testplugin.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/coverage/testplugin.py b/coverage/testplugin.py index 3cef47c..f685758 100644 --- a/coverage/testplugin.py +++ b/coverage/testplugin.py @@ -33,7 +33,7 @@ class CoverageTestWrapper(object): self.coverage = None self.coverTests = options.cover_tests - self.coverPackage = options.cover_package + self.coverPackages = options.cover_package def start(self): # Set up coverage @@ -55,10 +55,11 @@ class CoverageTestWrapper(object): self.coverage.save() modules = [] - if self.coverPackage: + if self.coverPackages: for name, module in sys.modules.items(): - if module is not None and name.startswith(self.coverPackage): - modules.append(module) + for package in self.coverPackages: + if module is not None and name.startswith(package): + modules.append(module) # Remaining actions are reporting, with some common self.options. report_args = { @@ -105,10 +106,11 @@ report Report coverage stats on modules. xml Create an XML report of coverage results. """.strip()), optparse.Option( - '--cover-package', action='store', + '--cover-package', action='append', default=[], dest="cover_package", metavar="COVER_PACKAGE", - help="Restrict coverage output to selected package" + help=("Restrict coverage output to selected package " + "- can be specified multiple times") ), optparse.Option("--cover-tests", action="store_true", dest="cover_tests", @@ -205,16 +207,14 @@ class DoCover: # 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('#')] + 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: + else: filtered.append(cu) return filtered -- cgit v1.2.1 From 4f3a0803c488997fee42b8f8781d67b959f81eae Mon Sep 17 00:00:00 2001 From: David Stanek Date: Fri, 21 May 2010 23:40:04 -0400 Subject: refactored the plugin's finish method to better enable filtering modules from a report --- coverage/testplugin.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/coverage/testplugin.py b/coverage/testplugin.py index f685758..c93b859 100644 --- a/coverage/testplugin.py +++ b/coverage/testplugin.py @@ -54,12 +54,8 @@ class CoverageTestWrapper(object): self.coverage.stop() self.coverage.save() - modules = [] - if self.coverPackages: - for name, module in sys.modules.items(): - for package in self.coverPackages: - if module is not None and name.startswith(package): - modules.append(module) + 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 = { @@ -95,6 +91,14 @@ class CoverageTestWrapper(object): 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'], -- cgit v1.2.1 From 4749c14e6dd316b710419b276c09a90662ed2712 Mon Sep 17 00:00:00 2001 From: David Stanek Date: Fri, 21 May 2010 23:40:04 -0400 Subject: cleaned up the code for the nose plugin --- coverage/noseplugin.py | 56 +++++++++++++++++-------------------------------- test/test_testplugin.py | 4 ++-- 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/coverage/noseplugin.py b/coverage/noseplugin.py index b2bf9b5..9e254ea 100644 --- a/coverage/noseplugin.py +++ b/coverage/noseplugin.py @@ -1,67 +1,49 @@ 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" + """Nose plugin for coverage reporting.""" + score = 1 status = {} - + def options(self, parser, env): - """ - Add options to command line. - """ - Plugin.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. - """ + """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 - + + super(Coverage, self).configure(options, config) + self.config = config self.status['active'] = True self.options = options - + def begin(self): - """ - Begin recording coverage information. - """ + """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. - """ + """Output code coverage report.""" + log.debug("Coverage report") stream.write("Processing Coverage...") - # finish up with coverage self.coverage.finish(stream) - diff --git a/test/test_testplugin.py b/test/test_testplugin.py index c216ead..87e2e4b 100644 --- a/test/test_testplugin.py +++ b/test/test_testplugin.py @@ -4,10 +4,10 @@ from nose.plugins import PluginTester from coverage.noseplugin import Coverage class TestCoverage(PluginTester, unittest.TestCase): - activate = '--with-coverage_new' # enables the plugin + 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, ( -- cgit v1.2.1 From 0f53d84d463713f40dea772a9b3f75bf6f035f96 Mon Sep 17 00:00:00 2001 From: David Stanek Date: Fri, 21 May 2010 23:40:04 -0400 Subject: cleaned up the testplugin module --- coverage/testplugin.py | 178 +++++++++++++++++++++---------------------------- 1 file changed, 75 insertions(+), 103 deletions(-) diff --git a/coverage/testplugin.py b/coverage/testplugin.py index c93b859..75c5b33 100644 --- a/coverage/testplugin.py +++ b/coverage/testplugin.py @@ -1,42 +1,34 @@ -import os import re import sys import optparse -from types import ListType import coverage + class CoverageTestWrapper(object): - """ - A Coverage Test Wrapper. - + """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 - + def __init__(self, options, _covpkg=coverage): # _covpkg is for dependency injection, so we can test this code. - if _covpkg: - self.covpkg = _covpkg - else: - import coverage - self.covpkg = coverage - + + self.options = options + self.covpkg = _covpkg + self.coverage = None self.coverTests = options.cover_tests self.coverPackages = 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, @@ -44,13 +36,11 @@ class CoverageTestWrapper(object): branch = self.options.cover_branch, ) - self.skipModules = sys.modules.keys()[:] - - # Run the script. + self.skipModules = sys.modules.keys()[:] #TODO: is this necessary?? + self.coverage.start() - + def finish(self, stream=None): - # end coverage and save the results self.coverage.stop() self.coverage.save() @@ -62,33 +52,31 @@ class CoverageTestWrapper(object): 'morfs': modules, 'ignore_errors': self.options.cover_ignore_errors, } - - # Handle any omits - # Allow pointing to a file as well - try: + + 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: + 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) + 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) + 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) + 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): @@ -100,77 +88,62 @@ class CoverageTestWrapper(object): options = [ - optparse.Option('', - '--cover-action', action='append', default=['report'], - dest='cover_actions', type="choice", choices=['annotate', 'html', 'report', 'xml'], - help="""\ + 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." - ) + """.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 @@ -189,6 +162,7 @@ def pytest_configure(config): config.pluginmanager.register(DoCover(config), "do_coverage") class DoCover: + def __init__(self, config): self.config = config @@ -199,18 +173,16 @@ class DoCover: 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 = [] -- cgit v1.2.1 From 343f36c3fe555a196ca59268f71ea4c7838f4d1c Mon Sep 17 00:00:00 2001 From: David Stanek Date: Sat, 22 May 2010 08:33:18 -0400 Subject: fixed a bug caused in 8e1995c8156a - refactoring out omit_filter never really worked --- coverage/codeunit.py | 4 ++-- coverage/testplugin.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/coverage/codeunit.py b/coverage/codeunit.py index 0170895..bb680e9 100644 --- a/coverage/codeunit.py +++ b/coverage/codeunit.py @@ -50,11 +50,11 @@ def code_unit_factory( code_units = filtered if omit_prefixes: - code_units = omit_filter(omit_prefixes, code_units) + code_units = omit_filter(omit_prefixes, code_units, file_locator) return code_units -def omit_filter(omit_prefixes, code_units): +def omit_filter(omit_prefixes, code_units, file_locator): """ The filtering method removing any unwanted code_units diff --git a/coverage/testplugin.py b/coverage/testplugin.py index 75c5b33..bb0231a 100644 --- a/coverage/testplugin.py +++ b/coverage/testplugin.py @@ -182,7 +182,7 @@ class DoCover: # XXX please make the following unnessary # Monkey patch omit_filter to use regex patterns for file omits -def omit_filter(omit_prefixes, code_units): +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 = [] -- cgit v1.2.1