diff options
-rw-r--r-- | coverage/plugin.py | 6 | ||||
-rw-r--r-- | tests/plugin1.py | 6 | ||||
-rw-r--r-- | tests/plugin2.py | 38 | ||||
-rw-r--r-- | tests/test_plugins.py | 126 |
4 files changed, 109 insertions, 67 deletions
diff --git a/coverage/plugin.py b/coverage/plugin.py index 24a2b9a3..6c7de669 100644 --- a/coverage/plugin.py +++ b/coverage/plugin.py @@ -2,8 +2,6 @@ import sys -from coverage.misc import CoverageException - class CoveragePlugin(object): """Base class for coverage.py plugins.""" @@ -41,7 +39,9 @@ class CoveragePlugin(object): `file_tracer`. It's an error to return None. """ - raise NotImplementedError("Plugin %r needs to implement file_reporter" % self.plugin_name) + raise NotImplementedError( + "Plugin %r needs to implement file_reporter" % self.plugin_name + ) class FileTracer(object): diff --git a/tests/plugin1.py b/tests/plugin1.py index 21e64aeb..c7660024 100644 --- a/tests/plugin1.py +++ b/tests/plugin1.py @@ -19,8 +19,10 @@ class Plugin(coverage.CoveragePlugin): class FileTracer(coverage.plugin.FileTracer): + """A FileTracer emulating a simple static plugin.""" + def __init__(self, filename): - """xyz.py was actually sourced from ABC.zz""" + """Claim that xyz.py was actually sourced from ABC.zz""" self._filename = filename self._source_filename = os.path.join( "/src", @@ -37,9 +39,11 @@ class FileTracer(coverage.plugin.FileTracer): class FileReporter(coverage.plugin.FileReporter): + """Dead-simple FileReporter.""" def get_parser(self, exclude=None): return PluginParser() class PluginParser(CodeParser): + """CodeParser hard-coded for a test in test_plugins.py.""" def parse_source(self): return set([105, 106, 107, 205, 206, 207]), set([]) diff --git a/tests/plugin2.py b/tests/plugin2.py index 1fa66cb2..4fb3d057 100644 --- a/tests/plugin2.py +++ b/tests/plugin2.py @@ -1,24 +1,48 @@ """A plugin for test_plugins.py to import.""" import coverage +from coverage.parser import CodeParser + class Plugin(coverage.CoveragePlugin): def file_tracer(self, filename): if "render.py" in filename: - return RenderFileTracer(filename) + return RenderFileTracer() + + def file_reporter(self, filename): + return FileReporter(filename) class RenderFileTracer(coverage.plugin.FileTracer): - def __init__(self, filename): - pass + """A FileTracer using information from the caller.""" def has_dynamic_source_filename(self): return True def dynamic_source_filename(self, filename, frame): - filename = "fake%d.html" % frame.f_lineno - print("dynamic filename: %r" % filename) - return filename + if frame.f_code.co_name != "render": + return None + return frame.f_locals['filename'] def line_number_range(self, frame): - return 17,19 + lineno = frame.f_locals['linenum'] + return lineno,lineno+1 + + +class FileReporter(coverage.plugin.FileReporter): + # TODO: Why do I have to make a FileReporter just to make a CodeParser?? + def __init__(self, filename): + self.filename = filename + + def get_parser(self, exclude=None): + # Goofy test arrangement: claim that the file has as many lines as the + # number in its name. + num = self.filename.split(".")[0].split("_")[1] + return PluginParser(int(num)) + +class PluginParser(CodeParser): + def __init__(self, num_lines): + self.num_lines = num_lines + + def parse_source(self): + return set(range(1, self.num_lines+1)), set([]) diff --git a/tests/test_plugins.py b/tests/test_plugins.py index f2658998..b94d0776 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -1,6 +1,6 @@ """Tests for plugins.""" -import sys +import os, sys from nose.plugins.skip import SkipTest @@ -11,6 +11,9 @@ import coverage.plugin from tests.coveragetest import CoverageTest +# Are we running with the C tracer or not? +C_TRACER = os.getenv('COVERAGE_TEST_TRACER', 'c') == 'c' + class FakeConfig(object): """A fake config for use in tests.""" @@ -138,62 +141,73 @@ class PluginTest(CoverageTest): cov.stop() -class FileTracerTest(CoverageTest): - """Tests of plugins that implement file_tracer.""" - - def test_plugin1(self): - if sys.platform == 'win32': - raise SkipTest("Plugin stuff is jank on windows.. fixing soon...") - - self.make_file("simple.py", """\ - import try_xyz - a = 1 - b = 2 - """) - self.make_file("try_xyz.py", """\ - c = 3 - d = 4 - """) - - cov = coverage.Coverage() - cov.config["run:plugins"] = ["tests.plugin1"] - - # Import the python file, executing it. - self.start_import_stop(cov, "simple") - - _, statements, missing, _ = cov.analysis("simple.py") - self.assertEqual(statements, [1,2,3]) - self.assertEqual(missing, []) - _, statements, _, _ = cov.analysis("/src/try_ABC.zz") - self.assertEqual(statements, [105, 106, 107, 205, 206, 207]) - - def test_plugin2(self): - self.make_file("render.py", """\ - def render(filename, linenum): - fiddle_around = 1 # vamp until ready - return "[{0} @ {1}]".format(filename, linenum) - """) - self.make_file("caller.py", """\ - from render import render - - assert render("foo.html", 17) == "[foo.html @ 17]" - assert render("bar.html", 23) == "[bar.html @ 23]" - """) +if not C_TRACER: + class FileTracerTest(CoverageTest): + """Tests of plugins that implement file_tracer.""" - cov = coverage.Coverage() - cov.config["run:plugins"] = ["tests.plugin2"] - cov.config["run:debug"] = ["trace"] + def test_plugin1(self): + if sys.platform == 'win32': + raise SkipTest("Plugin stuff is jank on windows.. fixing soon...") - self.start_import_stop(cov, "caller") + self.make_file("simple.py", """\ + import try_xyz + a = 1 + b = 2 + """) + self.make_file("try_xyz.py", """\ + c = 3 + d = 4 + """) - print(self.stderr()) - cov._harvest_data() - print(cov.data.line_data()) - - return # TODO: finish this test + cov = coverage.Coverage() + cov.config["run:plugins"] = ["tests.plugin1"] + + # Import the python file, executing it. + self.start_import_stop(cov, "simple") + + _, statements, missing, _ = cov.analysis("simple.py") + self.assertEqual(statements, [1,2,3]) + self.assertEqual(missing, []) + _, statements, _, _ = cov.analysis("/src/try_ABC.zz") + self.assertEqual(statements, [105, 106, 107, 205, 206, 207]) + + def test_plugin2(self): + # plugin2 emulates a dynamic tracing plugin: the caller's locals + # are examined to determine the source file and line number. + # The plugin is in tests/plugin2.py. + self.make_file("render.py", """\ + def render(filename, linenum): + # This function emulates a template renderer. The plugin + # will examine the `filename` and `linenum` locals to + # determine the source file and line number. + fiddle_around = 1 # not used, just chaff. + return "[{0} @ {1}]".format(filename, linenum) + + def helper(x): + # This function is here just to show that not all code in + # this file will be part of the dynamic tracing. + return x+1 + """) + self.make_file("caller.py", """\ + from render import helper, render + + assert render("foo_7.html", 4) == "[foo_7.html @ 4]" + assert helper(42) == 43 + assert render("bar_4.html", 2) == "[bar_4.html @ 2]" + assert helper(76) == 77 + """) - _, statements, missing, _ = cov.analysis("simple.py") - self.assertEqual(statements, [1,2,3]) - self.assertEqual(missing, []) - _, statements, _, _ = cov.analysis("/src/try_ABC.zz") - self.assertEqual(statements, [105, 106, 107, 205, 206, 207]) + cov = coverage.Coverage() + cov.config["run:plugins"] = ["tests.plugin2"] + + self.start_import_stop(cov, "caller") + + # The way plugin2 works, a file named foo_7.html will be claimed to + # have 7 lines in it. If render() was called with line number 4, + # then the plugin will claim that lines 4 and 5 were executed. + _, statements, missing, _ = cov.analysis("foo_7.html") + self.assertEqual(statements, [1,2,3,4,5,6,7]) + self.assertEqual(missing, [1,2,3,6,7]) + _, statements, missing, _ = cov.analysis("bar_4.html") + self.assertEqual(statements, [1,2,3,4]) + self.assertEqual(missing, [1,4]) |