summaryrefslogtreecommitdiff
path: root/tests/coveragetest.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/coveragetest.py')
-rw-r--r--tests/coveragetest.py122
1 files changed, 86 insertions, 36 deletions
diff --git a/tests/coveragetest.py b/tests/coveragetest.py
index 47a124c1..c55fffd5 100644
--- a/tests/coveragetest.py
+++ b/tests/coveragetest.py
@@ -15,9 +15,18 @@ import re
import shlex
import sys
+from types import ModuleType
+from typing import (
+ Any, Collection, Dict, Generator, Iterable, List, Mapping, Optional,
+ Tuple, Union,
+)
+
import coverage
+from coverage import Coverage
from coverage.cmdline import CoverageScript
+from coverage.data import CoverageData
from coverage.misc import import_local_file
+from coverage.types import TArc, TLineNo
from tests.helpers import arcs_to_arcz_repr, arcz_to_arcs, assert_count_equal
from tests.helpers import nice_file, run_command
@@ -53,15 +62,20 @@ class CoverageTest(
# Let stderr go to stderr, pytest will capture it for us.
show_stderr = True
- def setUp(self):
+ def setUp(self) -> None:
super().setUp()
# Attributes for getting info about what happened.
- self.last_command_status = None
- self.last_command_output = None
- self.last_module_name = None
-
- def start_import_stop(self, cov, modname, modfile=None):
+ self.last_command_status: Optional[int] = None
+ self.last_command_output: Optional[str] = None
+ self.last_module_name: Optional[str] = None
+
+ def start_import_stop(
+ self,
+ cov: Coverage,
+ modname: str,
+ modfile: Optional[str]=None
+ ) -> ModuleType:
"""Start coverage, import a file, then stop coverage.
`cov` is started and stopped, with an `import_local_file` of
@@ -80,7 +94,7 @@ class CoverageTest(
cov.stop()
return mod
- def get_report(self, cov, squeeze=True, **kwargs):
+ def get_report(self, cov: Coverage, squeeze: bool=True, **kwargs: Any) -> str:
"""Get the report from `cov`, and canonicalize it."""
repout = io.StringIO()
kwargs.setdefault("show_missing", False)
@@ -91,12 +105,17 @@ class CoverageTest(
report = re.sub(r" +", " ", report)
return report
- def get_module_name(self):
+ def get_module_name(self) -> str:
"""Return a random module name to use for this test run."""
self.last_module_name = 'coverage_test_' + str(random.random())[2:]
return self.last_module_name
- def _check_arcs(self, a1, a2, arc_type):
+ def _check_arcs(
+ self,
+ a1: Optional[Iterable[TArc]],
+ a2: Optional[Iterable[TArc]],
+ arc_type: str,
+ ) -> str:
"""Check that the arc lists `a1` and `a2` are equal.
If they are equal, return empty string. If they are unequal, return
@@ -114,11 +133,20 @@ class CoverageTest(
return ""
def check_coverage(
- self, text, lines=None, missing="", report="",
- excludes=None, partials="",
- arcz=None, arcz_missing=None, arcz_unpredicted=None,
- arcs=None, arcs_missing=None, arcs_unpredicted=None,
- ):
+ self,
+ text: str,
+ lines: Optional[Union[List[TLineNo], List[List[TLineNo]]]]=None,
+ missing: Union[str, List[str]]="",
+ report: str="",
+ excludes: Optional[Iterable[str]]=None,
+ partials: Iterable[str]=(),
+ arcz: Optional[str]=None,
+ arcz_missing: Optional[str]=None,
+ arcz_unpredicted: Optional[str]=None,
+ arcs: Optional[Iterable[TArc]]=None,
+ arcs_missing: Optional[Iterable[TArc]]=None,
+ arcs_unpredicted: Optional[Iterable[TArc]]=None,
+ ) -> Coverage:
"""Check the coverage measurement of `text`.
The source `text` is run and measured. `lines` are the line numbers
@@ -216,7 +244,14 @@ class CoverageTest(
return cov
- def make_data_file(self, basename=None, suffix=None, lines=None, arcs=None, file_tracers=None):
+ def make_data_file(
+ self,
+ basename: Optional[str]=None,
+ suffix: Optional[str]=None,
+ lines: Optional[Mapping[str, Collection[TLineNo]]]=None,
+ arcs: Optional[Mapping[str, Collection[TArc]]]=None,
+ file_tracers: Optional[Mapping[str, str]]=None,
+ ) -> CoverageData:
"""Write some data into a coverage data file."""
data = coverage.CoverageData(basename=basename, suffix=suffix)
assert lines is None or arcs is None
@@ -230,7 +265,12 @@ class CoverageTest(
return data
@contextlib.contextmanager
- def assert_warnings(self, cov, warnings, not_warnings=()):
+ def assert_warnings(
+ self,
+ cov: Coverage,
+ warnings: Iterable[str],
+ not_warnings: Iterable[str]=(),
+ ) -> Generator[None, None, None]:
"""A context manager to check that particular warnings happened in `cov`.
`cov` is a Coverage instance. `warnings` is a list of regexes. Every
@@ -248,7 +288,11 @@ class CoverageTest(
"""
__tracebackhide__ = True
saved_warnings = []
- def capture_warning(msg, slug=None, once=False): # pylint: disable=unused-argument
+ def capture_warning(
+ msg: str,
+ slug: Optional[str]=None,
+ once: bool=False, # pylint: disable=unused-argument
+ ) -> None:
"""A fake implementation of Coverage._warn, to capture warnings."""
# NOTE: we don't implement `once`.
if slug:
@@ -256,7 +300,7 @@ class CoverageTest(
saved_warnings.append(msg)
original_warn = cov._warn
- cov._warn = capture_warning
+ cov._warn = capture_warning # type: ignore[assignment]
try:
yield
@@ -281,30 +325,35 @@ class CoverageTest(
if saved_warnings:
assert False, f"Unexpected warnings: {saved_warnings!r}"
finally:
- cov._warn = original_warn
+ cov._warn = original_warn # type: ignore[assignment]
- def assert_same_files(self, flist1, flist2):
+ def assert_same_files(self, flist1: Iterable[str], flist2: Iterable[str]) -> None:
"""Assert that `flist1` and `flist2` are the same set of file names."""
flist1_nice = [nice_file(f) for f in flist1]
flist2_nice = [nice_file(f) for f in flist2]
assert_count_equal(flist1_nice, flist2_nice)
- def assert_exists(self, fname):
+ def assert_exists(self, fname: str) -> None:
"""Assert that `fname` is a file that exists."""
assert os.path.exists(fname), f"File {fname!r} should exist"
- def assert_doesnt_exist(self, fname):
+ def assert_doesnt_exist(self, fname: str) -> None:
"""Assert that `fname` is a file that doesn't exist."""
assert not os.path.exists(fname), f"File {fname!r} shouldn't exist"
- def assert_file_count(self, pattern, count):
+ def assert_file_count(self, pattern: str, count: int) -> None:
"""Assert that there are `count` files matching `pattern`."""
files = sorted(glob.glob(pattern))
msg = "There should be {} files matching {!r}, but there are these: {}"
msg = msg.format(count, pattern, files)
assert len(files) == count, msg
- def assert_recent_datetime(self, dt, seconds=10, msg=None):
+ def assert_recent_datetime(
+ self,
+ dt: datetime.datetime,
+ seconds: int=10,
+ msg: Optional[str]=None,
+ ) -> None:
"""Assert that `dt` marks a time at most `seconds` seconds ago."""
age = datetime.datetime.now() - dt
assert age.total_seconds() >= 0, msg
@@ -331,7 +380,7 @@ class CoverageTest(
# https://salsa.debian.org/debian/pkg-python-coverage/-/blob/master/debian/patches/02.rename-public-programs.patch
coverage_command = "coverage"
- def run_command(self, cmd):
+ def run_command(self, cmd: str) -> str:
"""Run the command-line `cmd` in a sub-process.
`cmd` is the command line to invoke in a sub-process. Returns the
@@ -348,7 +397,7 @@ class CoverageTest(
_, output = self.run_command_status(cmd)
return output
- def run_command_status(self, cmd):
+ def run_command_status(self, cmd: str) -> Tuple[int, str]:
"""Run the command-line `cmd` in a sub-process, and print its output.
Use this when you need to test the process behavior of coverage.
@@ -408,36 +457,36 @@ class CoverageTest(
print(self.last_command_output)
return self.last_command_status, self.last_command_output
- def working_root(self):
+ def working_root(self) -> str:
"""Where is the root of the coverage.py working tree?"""
return os.path.dirname(nice_file(__file__, ".."))
- def report_from_command(self, cmd):
+ def report_from_command(self, cmd: str) -> str:
"""Return the report from the `cmd`, with some convenience added."""
report = self.run_command(cmd).replace('\\', '/')
assert "error" not in report.lower()
return report
- def report_lines(self, report):
+ def report_lines(self, report: str) -> List[str]:
"""Return the lines of the report, as a list."""
lines = report.split('\n')
assert lines[-1] == ""
return lines[:-1]
- def line_count(self, report):
+ def line_count(self, report: str) -> int:
"""How many lines are in `report`?"""
return len(self.report_lines(report))
- def squeezed_lines(self, report):
+ def squeezed_lines(self, report: str) -> List[str]:
"""Return a list of the lines in report, with the spaces squeezed."""
lines = self.report_lines(report)
return [re.sub(r"\s+", " ", l.strip()) for l in lines]
- def last_line_squeezed(self, report):
+ def last_line_squeezed(self, report: str) -> str:
"""Return the last line of `report` with the spaces squeezed down."""
return self.squeezed_lines(report)[-1]
- def get_measured_filenames(self, coverage_data):
+ def get_measured_filenames(self, coverage_data: CoverageData) -> Dict[str, str]:
"""Get paths to measured files.
Returns a dict of {filename: absolute path to file}
@@ -446,9 +495,10 @@ class CoverageTest(
return {os.path.basename(filename): filename
for filename in coverage_data.measured_files()}
- def get_missing_arc_description(self, cov, start, end):
+ def get_missing_arc_description(self, cov: Coverage, start: TLineNo, end: TLineNo) -> str:
"""Get the missing-arc description for a line arc in a coverage run."""
# ugh, unexposed methods??
+ assert self.last_module_name is not None
filename = self.last_module_name + ".py"
fr = cov._get_file_reporter(filename)
arcs_executed = cov._analyze(filename).arcs_executed()
@@ -458,8 +508,8 @@ class CoverageTest(
class UsingModulesMixin:
"""A mixin for importing modules from tests/modules and tests/moremodules."""
- def setUp(self):
- super().setUp()
+ def setUp(self) -> None:
+ super().setUp() # type: ignore[misc]
# Parent class saves and restores sys.path, we can just modify it.
sys.path.append(nice_file(TESTS_DIR, "modules"))