"""Tests for plugins.""" import os, sys from nose.plugins.skip import SkipTest import coverage from coverage.plugin import Plugins 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.""" def __init__(self, plugin, options): self.plugin = plugin self.options = options self.asked_for = [] def get_plugin_options(self, module): """Just return the options for `module` if this is the right module.""" self.asked_for.append(module) if module == self.plugin: return self.options else: return {} class LoadPluginsTest(CoverageTest): """Test Plugins.load_plugins directly.""" def test_importing_and_configuring(self): self.make_file("plugin1.py", """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): def __init__(self, options): super(Plugin, self).__init__(options) self.this_is = "me" """) config = FakeConfig("plugin1", {'a':'hello'}) plugins = list(Plugins.load_plugins(["plugin1"], config)) self.assertEqual(len(plugins), 1) self.assertEqual(plugins[0].this_is, "me") self.assertEqual(plugins[0].options, {'a':'hello'}) self.assertEqual(config.asked_for, ['plugin1']) def test_importing_and_configuring_more_than_one(self): self.make_file("plugin1.py", """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): def __init__(self, options): super(Plugin, self).__init__(options) self.this_is = "me" """) self.make_file("plugin2.py", """\ from coverage import CoveragePlugin class Plugin(CoveragePlugin): pass """) config = FakeConfig("plugin1", {'a':'hello'}) plugins = list(Plugins.load_plugins(["plugin1", "plugin2"], config)) self.assertEqual(len(plugins), 2) self.assertEqual(plugins[0].this_is, "me") self.assertEqual(plugins[0].options, {'a':'hello'}) self.assertEqual(plugins[1].options, {}) self.assertEqual(config.asked_for, ['plugin1', 'plugin2']) # The order matters... config = FakeConfig("plugin1", {'a':'second'}) plugins = list(Plugins.load_plugins(["plugin2", "plugin1"], config)) self.assertEqual(len(plugins), 2) self.assertEqual(plugins[0].options, {}) self.assertEqual(plugins[1].this_is, "me") self.assertEqual(plugins[1].options, {'a':'second'}) def test_cant_import(self): with self.assertRaises(ImportError): _ = Plugins.load_plugins(["plugin_not_there"], None) def test_ok_to_not_define_plugin(self): # TODO: should this actually be an error or warning? self.make_file("plugin2.py", """\ from coverage import CoveragePlugin Nothing = 0 """) plugins = list(Plugins.load_plugins(["plugin2"], None)) self.assertEqual(plugins, []) class PluginTest(CoverageTest): """Test plugins through the Coverage class.""" def test_plugin_imported(self): # Prove that a plugin will be imported. self.make_file("my_plugin.py", """\ with open("evidence.out", "w") as f: f.write("we are here!") """) self.assert_doesnt_exist("evidence.out") cov = coverage.Coverage() cov.config["run:plugins"] = ["my_plugin"] cov.start() cov.stop() with open("evidence.out") as f: self.assertEqual(f.read(), "we are here!") def test_missing_plugin_raises_import_error(self): # Prove that a missing plugin will raise an ImportError. with self.assertRaises(ImportError): cov = coverage.Coverage() cov.config["run:plugins"] = ["does_not_exist_woijwoicweo"] cov.start() cov.stop() def test_bad_plugin_isnt_hidden(self): # Prove that a plugin with an error in it will raise the error. self.make_file("plugin_over_zero.py", """\ 1/0 """) with self.assertRaises(ZeroDivisionError): cov = coverage.Coverage() cov.config["run:plugins"] = ["plugin_over_zero"] cov.start() cov.stop() if not C_TRACER: 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() snoop_on_callbacks(cov) 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]" # Render foo_7.html again to trigger the callback snoopers. render("foo_7.html", 4) assert helper(42) == 43 assert render("bar_4.html", 2) == "[bar_4.html @ 2]" assert helper(76) == 77 """) cov = coverage.Coverage() snoop_on_callbacks(cov) 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]) def snoop_on_callbacks(cov): cov_should_trace = cov._should_trace should_trace_filenames = set() def snoop_should_trace(filename, frame): assert filename not in should_trace_filenames should_trace_filenames.add(filename) return cov_should_trace(filename, frame) cov._should_trace = snoop_should_trace cov_check_include = cov._check_include_omit_etc check_include_filenames = set() def snoop_check_include_filenames(filename, frame): assert filename not in check_include_filenames check_include_filenames.add(filename) return cov_check_include(filename, frame) cov._check_include_omit_etc = snoop_check_include_filenames