diff options
-rw-r--r-- | CHANGES.txt | 3 | ||||
-rw-r--r-- | TODO.txt | 2 | ||||
-rw-r--r-- | coverage/collector.py | 2 | ||||
-rw-r--r-- | coverage/control.py | 9 | ||||
-rw-r--r-- | coverage/pytracer.py | 48 | ||||
-rw-r--r-- | coverage/test_helpers.py | 2 | ||||
-rw-r--r-- | tests/test_plugins.py | 27 |
7 files changed, 50 insertions, 43 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 00ad1a3f..3650a5ae 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -6,7 +6,8 @@ Change history for Coverage.py Latest ------ -- Plugin support is now implemented in the C tracer and the Python tracer. +- Plugin support is now implemented in the C tracer instead of the Python + tracer. - Added 3.5.0a1 to the list of supported versions. @@ -30,7 +30,7 @@ Key: - Plugins - Clean up + implement plugin support in CTracer - - remove plugin support from PyTracer + + remove plugin support from PyTracer - add services: - filelocator - warning diff --git a/coverage/collector.py b/coverage/collector.py index ded6d920..0348bb72 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -119,6 +119,8 @@ class Collector(object): # trace function. self._trace_class = CTracer or PyTracer + self.supports_plugins = self._trace_class is CTracer + def __repr__(self): return "<Collector at 0x%x>" % id(self) diff --git a/coverage/control.py b/coverage/control.py index 1b21c3bd..bb26b785 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -227,6 +227,15 @@ class Coverage(object): concurrency=concurrency, ) + # Early warning if we aren't going to be able to support plugins. + if self.file_tracers and not self.collector.supports_plugins: + raise CoverageException( + "Plugin file tracers (%s) aren't supported with %s" % ( + ", ".join(ft.plugin_name for ft in self.file_tracers), + self.collector.tracer_name(), + ) + ) + # Suffixes are a bit tricky. We want to use the data suffix only when # collecting data, not when combining data. So we save it as # `self.run_suffix` now, and promote it to `self.data_suffix` if we diff --git a/coverage/pytracer.py b/coverage/pytracer.py index 16a51c2b..7029c255 100644 --- a/coverage/pytracer.py +++ b/coverage/pytracer.py @@ -28,13 +28,10 @@ class PyTracer(object): self.arcs = False self.should_trace = None self.should_trace_cache = None - self.check_include = None self.warn = None - self.plugin_data = None # The threading module to use, if any. self.threading = None - self.file_tracer = [] self.cur_file_dict = [] self.last_line = [0] @@ -63,72 +60,43 @@ class PyTracer(object): if self.arcs and self.cur_file_dict: pair = (self.last_line, -self.last_exc_firstlineno) self.cur_file_dict[pair] = None - self.file_tracer, self.cur_file_dict, self.last_line = ( - self.data_stack.pop() - ) + self.cur_file_dict, self.last_line = self.data_stack.pop() self.last_exc_back = None if event == 'call': # Entering a new function context. Decide if we should trace # in this file. - self.data_stack.append( - (self.file_tracer, self.cur_file_dict, self.last_line) - ) + self.data_stack.append((self.cur_file_dict, self.last_line)) filename = frame.f_code.co_filename disp = self.should_trace_cache.get(filename) if disp is None: disp = self.should_trace(filename, frame) self.should_trace_cache[filename] = disp - self.file_tracer = None self.cur_file_dict = None if disp.trace: tracename = disp.source_filename - if disp.file_tracer and disp.has_dynamic_filename: - tracename = disp.file_tracer.dynamic_source_filename(tracename, frame) - if tracename: - included = self.should_trace_cache.get(tracename) - if included is None: - included = self.check_include(tracename, frame) - self.should_trace_cache[tracename] = included - if not included: - tracename = None - else: - tracename = None - if tracename: if tracename not in self.data: self.data[tracename] = {} - if disp.file_tracer: - self.plugin_data[tracename] = disp.file_tracer.plugin_name self.cur_file_dict = self.data[tracename] - self.file_tracer = disp.file_tracer # Set the last_line to -1 because the next arc will be entering a # code block, indicated by (-1, n). self.last_line = -1 elif event == 'line': # Record an executed line. if self.cur_file_dict is not None: - if self.file_tracer: - lineno_from, lineno_to = self.file_tracer.line_number_range(frame) + lineno = frame.f_lineno + if self.arcs: + self.cur_file_dict[(self.last_line, lineno)] = None else: - lineno_from, lineno_to = frame.f_lineno, frame.f_lineno - if lineno_from != -1: - if self.arcs: - self.cur_file_dict[ - (self.last_line, lineno_from) - ] = None - else: - for lineno in range(lineno_from, lineno_to+1): - self.cur_file_dict[lineno] = None - self.last_line = lineno_to + self.cur_file_dict[lineno] = None + self.last_line = lineno elif event == 'return': if self.arcs and self.cur_file_dict: first = frame.f_code.co_firstlineno self.cur_file_dict[(self.last_line, -first)] = None # Leaving this function, pop the filename stack. - self.file_tracer, self.cur_file_dict, self.last_line = ( - self.data_stack.pop() - ) + self.cur_file_dict, self.last_line = self.data_stack.pop() elif event == 'exception': self.last_exc_back = frame.f_back self.last_exc_firstlineno = frame.f_code.co_firstlineno diff --git a/coverage/test_helpers.py b/coverage/test_helpers.py index 65d99779..e9ad377a 100644 --- a/coverage/test_helpers.py +++ b/coverage/test_helpers.py @@ -249,7 +249,7 @@ class TempDirMixin(SysPathAwareMixin, ModuleAwareMixin, TestCase): def report_on_class_behavior(cls): """Called at process exit to report on class behavior.""" for test_class, behavior in cls.class_behaviors.items(): - if behavior.tests == behavior.skipped: + if behavior.tests <= behavior.skipped: bad = "" elif behavior.temp_dir and behavior.tests_making_files == 0: bad = "Inefficient" diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 83dc4a16..2b4b2ee1 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -3,6 +3,7 @@ import os.path import coverage +from coverage import env from coverage.backward import StringIO from coverage.control import Plugins from coverage.misc import CoverageException @@ -198,9 +199,35 @@ class PluginTest(CoverageTest): self.assertEqual(expected_end, out_lines[-len(expected_end):]) +class PluginWarningOnPyTracer(CoverageTest): + """Test that we get a controlled exception with plugins on PyTracer.""" + def setUp(self): + super(PluginWarningOnPyTracer, self).setUp() + if env.C_TRACER: + self.skip("This test is only about PyTracer.") + + def test_exception_if_plugins_on_pytracer(self): + self.make_file("simple.py", """a = 1""") + + cov = coverage.Coverage() + cov.config["run:plugins"] = ["tests.plugin1"] + + msg = ( + r"Plugin file tracers \(tests.plugin1\) " + r"aren't supported with PyTracer" + ) + with self.assertRaisesRegex(CoverageException, msg): + self.start_import_stop(cov, "simple") + + class FileTracerTest(CoverageTest): """Tests of plugins that implement file_tracer.""" + def setUp(self): + super(FileTracerTest, self).setUp() + if not env.C_TRACER: + self.skip("Plugins are only supported with the C tracer.") + def test_plugin1(self): self.make_file("simple.py", """\ import try_xyz |