summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.rst4
-rw-r--r--coverage/pytracer.py38
-rw-r--r--tests/test_context.py21
-rw-r--r--tests/test_plugins.py35
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