blob: 5da31ce47a9038c2b0e903c91462d6d6b8b0ae1f [file] [edit]
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
"""Tests for the sys.monitoring core in coverage/sysmon.py."""
from __future__ import annotations
from unittest import mock
import pytest
from coverage import env
from coverage.sysmon import SysMonitor, compute_multiline_map
from tests.coveragetest import CoverageTest
MULTI_PY = "x = (\n 1 +\n 2\n)\ny = 5\n"
MULTI_MAP = {1: 1, 2: 1, 3: 1, 4: 1}
class ComputeMultilineMapTest(CoverageTest):
"""Tests of compute_multiline_map."""
def test_python_file(self) -> None:
self.make_file("multi.py", MULTI_PY)
assert compute_multiline_map("multi.py") == MULTI_MAP
def test_missing_file(self) -> None:
# A code object can name a file that doesn't exist on disk.
assert compute_multiline_map("no_such_file.py") == {}
def test_non_python_file(self) -> None:
# A code object can point at non-Python source, as with compiled
# template files. Tokenizing fails, and the map is empty.
self.make_file(
"widget.html",
# The unbalanced paren makes this untokenizable as Python.
"<div class=(widget>\n {% if widget.name %}\n</div>\n",
)
assert compute_multiline_map("widget.html") == {}
def test_indentation_error(self) -> None:
self.make_file("bad.py", "def f():\n pass\n huh = 3\n")
assert compute_multiline_map("bad.py") == {}
@pytest.mark.skipif(not env.PYBEHAVIOR.pep669, reason="SysMonitor needs sys.monitoring")
class MultilineMapCacheTest(CoverageTest):
"""Tests of SysMonitor's per-tracer multiline map cache."""
def test_each_file_computed_at_most_once(self) -> None:
self.make_file("multi.py", MULTI_PY)
self.make_file("other.py", "a = [1,\n 2]\n")
tracer = SysMonitor()
with mock.patch(
"coverage.sysmon.compute_multiline_map",
side_effect=compute_multiline_map,
) as computer:
map1 = tracer.get_multiline_map("multi.py")
map2 = tracer.get_multiline_map("multi.py")
other = tracer.get_multiline_map("other.py")
assert computer.call_count == 2
assert map1 is map2
assert map1 == MULTI_MAP
assert other == {1: 1, 2: 1}
def test_cache_dies_with_the_tracer(self) -> None:
# The cache is per-instance: a new tracer re-reads the file, so a
# source file changed between runs can't serve a stale map, the way
# a module-level cache could.
self.make_file("multi.py", MULTI_PY)
tracer = SysMonitor()
assert tracer.get_multiline_map("multi.py") == MULTI_MAP
self.make_file("multi.py", "x = 1\ny = (2 +\n 3)\n")
assert tracer.get_multiline_map("multi.py") == MULTI_MAP # cached
assert SysMonitor().get_multiline_map("multi.py") == {2: 2, 3: 2}