diff options
Diffstat (limited to 'tests/coveragetest.py')
| -rw-r--r-- | tests/coveragetest.py | 122 |
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")) |
