diff options
-rw-r--r-- | CHANGES.rst | 4 | ||||
-rw-r--r-- | coverage/pytracer.py | 38 | ||||
-rw-r--r-- | tests/test_context.py | 21 | ||||
-rw-r--r-- | tests/test_plugins.py | 35 |
4 files changed, 38 insertions, 60 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index ec79353f..a97f2f27 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -24,6 +24,9 @@ Unreleased in `issue 720`_. The new ``coverage json`` command writes raw and summarized data to a JSON file. Thanks, Matt Bachmann. +- Dynamic contexts are now supported in the Python tracer, which is important + for PyPy users. Closes `issue 846`_. + - The compact line number representation introduced in 5.0a6 is called a "numbits." The :mod:`coverage.numbits` module provides functions for working with them. @@ -46,6 +49,7 @@ Unreleased .. _issue 822: https://github.com/nedbat/coveragepy/issues/822 .. _issue 834: https://github.com/nedbat/coveragepy/issues/834 .. _issue 829: https://github.com/nedbat/coveragepy/issues/829 +.. _issue 846: https://github.com/nedbat/coveragepy/issues/846 .. _changes_50a6: diff --git a/coverage/pytracer.py b/coverage/pytracer.py index e64d7f55..1b9e4d71 100644 --- a/coverage/pytracer.py +++ b/coverage/pytracer.py @@ -40,6 +40,7 @@ class PyTracer(object): self.trace_arcs = False self.should_trace = None self.should_trace_cache = None + self.should_start_context = None self.warn = None # The threading module to use, if any. self.threading = None @@ -47,6 +48,8 @@ class PyTracer(object): self.cur_file_dict = None self.last_line = 0 # int, but uninitialized. self.cur_file_name = None + self.context = None + self.started_context = False self.data_stack = [] self.last_exc_back = None @@ -96,14 +99,35 @@ class PyTracer(object): if self.trace_arcs and self.cur_file_dict: pair = (self.last_line, -self.last_exc_firstlineno) self.cur_file_dict[pair] = None - self.cur_file_dict, self.cur_file_name, self.last_line = self.data_stack.pop() + self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = ( + self.data_stack.pop() + ) self.last_exc_back = None if event == 'call': - # Entering a new function context. Decide if we should trace + # Should we start a new context? + if self.should_start_context and self.context is None: + context_maybe = self.should_start_context(frame) + if context_maybe is not None: + self.context = context_maybe + self.started_context = True + self.switch_context(self.context) + else: + self.started_context = False + else: + self.started_context = False + + # Entering a new frame. Decide if we should trace # in this file. self._activity = True - self.data_stack.append((self.cur_file_dict, self.cur_file_name, self.last_line)) + self.data_stack.append( + ( + self.cur_file_dict, + self.cur_file_name, + self.last_line, + self.started_context, + ) + ) filename = frame.f_code.co_filename self.cur_file_name = filename disp = self.should_trace_cache.get(filename) @@ -146,7 +170,13 @@ class PyTracer(object): first = frame.f_code.co_firstlineno self.cur_file_dict[(self.last_line, -first)] = None # Leaving this function, pop the filename stack. - self.cur_file_dict, self.cur_file_name, self.last_line = self.data_stack.pop() + self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = ( + self.data_stack.pop() + ) + # Leaving a context? + if self.started_context: + self.context = None + self.switch_context(None) elif event == 'exception': self.last_exc_back = frame.f_back self.last_exc_firstlineno = frame.f_code.co_firstlineno diff --git a/tests/test_context.py b/tests/test_context.py index 9c8d4605..eb8ee9c5 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -10,7 +10,6 @@ import coverage from coverage import env from coverage.context import qualname_from_frame from coverage.data import CoverageData -from coverage.misc import CoverageException from tests.coveragetest import CoverageTest @@ -107,11 +106,6 @@ class StaticContextTest(CoverageTest): class DynamicContextTest(CoverageTest): """Tests of dynamically changing contexts.""" - def setUp(self): - if not env.C_TRACER: - self.skipTest("Only the C tracer supports dynamic contexts") - super(DynamicContextTest, self).setUp() - SOURCE = """\ def helper(lineno): x = 2 @@ -178,21 +172,6 @@ class DynamicContextTest(CoverageTest): data.lines(fname, ["stat|two_tests.test_two"]), self.TEST_TWO_LINES) -class DynamicContextWithPythonTracerTest(CoverageTest): - """The Python tracer doesn't do dynamic contexts at all.""" - - run_in_temp_dir = False - - def test_python_tracer_fails_properly(self): - if env.C_TRACER: - self.skipTest("This test is specifically about the Python tracer.") - cov = coverage.Coverage() - cov.set_option("run:dynamic_context", "test_function") - msg = r"Can't support dynamic contexts with PyTracer" - with self.assertRaisesRegex(CoverageException, msg): - cov.start() - - def get_qualname(): """Helper to return qualname_from_frame for the caller.""" stack = inspect.stack()[1:] diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 416f05a2..eb51c1b9 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -887,11 +887,6 @@ class ConfigurerPluginTest(CoverageTest): class DynamicContextPluginTest(CoverageTest): """Tests of plugins that implement `dynamic_context`.""" - def setUp(self): - if not env.C_TRACER: - self.skipTest("Plugins are only supported with the C tracer.") - super(DynamicContextPluginTest, self).setUp() - def make_plugin_capitalized_testnames(self, filename): """Create a dynamic context plugin that capitalizes the part after 'test_'.""" self.make_file(filename, """\ @@ -1112,33 +1107,3 @@ class DynamicContextPluginTest(CoverageTest): [2, 8], data.lines(filenames['rendering.py'], contexts=["renderer:span"]), ) - - -class DynamicContextPluginOtherTracersTest(CoverageTest): - """Tests of plugins that implement `dynamic_context`.""" - - def setUp(self): - if env.C_TRACER: - self.skipTest("These tests are for tracers not implemented in C.") - super(DynamicContextPluginOtherTracersTest, self).setUp() - - def test_other_tracer_support(self): - self.make_file("context_plugin.py", """\ - from coverage import CoveragePlugin - - class Plugin(CoveragePlugin): - def dynamic_context(self, frame): - return frame.f_code.co_name - - def coverage_init(reg, options): - reg.add_dynamic_context(Plugin()) - """) - - cov = coverage.Coverage() - cov.set_option("run:plugins", ['context_plugin']) - - msg = "Can't support dynamic contexts with PyTracer" - with self.assertRaisesRegex(CoverageException, msg): - cov.start() - - cov.stop() # pragma: nested |