From b61370af5615d8aa781f1d311fe408e9a036b6da 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. --HG-- rename : coverage/runner.py => coverage/testplugin.py --- 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 6ba0a104..907e6803 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 094be44a..00000000 --- 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 ef4c1f21..00000000 --- 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 00000000..a979ad2b --- /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 7ad92f3e..38678d15 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 00000000..d102a4f4 --- /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