Coverage for tests / coveragetest.py: 100.000%
220 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-30 17:57 +0000
1# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
2# For details: https://github.com/coveragepy/coveragepy/blob/main/NOTICE.txt
4"""Base test case class for coverage.py testing."""
6from __future__ import annotations 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
8import collections 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
9import contextlib 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
10import datetime 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
11import glob 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
12import io 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
13import os 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
14import os.path 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
15import random 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
16import re 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
17import shlex 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
18import sys 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
20from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
21from types import ModuleType 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
22from typing import Any 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
24import coverage 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
25from coverage import Coverage 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
26from coverage.cmdline import CoverageScript 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
27from coverage.data import CoverageData 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
28from coverage.misc import import_local_file 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
29from coverage.types import TArc, TLineNo 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
31from tests.helpers import arcz_to_arcs, assert_count_equal 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
32from tests.helpers import nice_file, run_command 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
33from tests.mixins import PytestBase, StdStreamCapturingMixin, RestoreModulesMixin, TempDirMixin 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
36# Status returns for the command line.
37OK, ERR = 0, 1 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
39# The coverage/tests directory, for all sorts of finding test helping things.
40TESTS_DIR = os.path.dirname(__file__) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
42# Install arguments to pass to pip when reinstalling ourselves.
43# Defaults to the top of the source tree, but can be overridden if we need
44# some help on certain platforms.
45COVERAGE_INSTALL_ARGS = os.getenv("COVERAGE_INSTALL_ARGS", nice_file(TESTS_DIR, "..")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
48def arcs_to_branches(arcs: Iterable[TArc]) -> dict[TLineNo, list[TLineNo]]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
49 """Convert a list of arcs into a dict showing branches."""
50 arcs_combined = collections.defaultdict(set) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
51 for fromno, tono in arcs: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
52 arcs_combined[fromno].add(tono) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
53 branches = collections.defaultdict(list) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
54 for fromno, tono in arcs: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
55 if len(arcs_combined[fromno]) > 1: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
56 branches[fromno].append(tono) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYx8NmOn6ZyPoQp0zRqSr1A!234
57 return branches 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
60def branches_to_arcs(branches: dict[TLineNo, list[TLineNo]]) -> list[TArc]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
61 """Convert a dict of branches into a list of arcs."""
62 return [(fromno, tono) for fromno, tonos in branches.items() for tono in tonos] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
65class CoverageTest( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
66 StdStreamCapturingMixin,
67 RestoreModulesMixin,
68 TempDirMixin,
69 PytestBase,
70):
71 """A base class for coverage.py test cases."""
73 # Standard unittest setting: show me diffs even if they are very long.
74 maxDiff = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
76 # Tell newer unittest implementations to print long helpful messages.
77 longMessage = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
79 # Let stderr go to stderr, pytest will capture it for us.
80 show_stderr = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
82 def setUp(self) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
83 super().setUp() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
85 # Attributes for getting info about what happened.
86 self.last_command_status: int | None = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
87 self.last_command_output: str | None = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
88 self.last_module_name: str | None = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
90 def start_import_stop( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!
91 self,
92 cov: Coverage,
93 modname: str,
94 modfile: str | None = None,
95 ) -> ModuleType:
96 """Start coverage, import a file, then stop coverage.
98 `cov` is started and stopped, with an `import_local_file` of
99 `modname` in the middle. `modfile` is the file to import as `modname`
100 if it isn't in the current directory.
102 The imported module is returned.
104 """
105 # Here's something I don't understand. I tried changing the code to use
106 # the handy context manager, like this:
107 #
108 # with cov.collect():
109 # # Import the Python file, executing it.
110 # return import_local_file(modname, modfile)
111 #
112 # That seemed to work, until 7.4.0 when it made metacov fail after
113 # running all the tests. The deep recursion tests in test_oddball.py
114 # seemed to cause something to be off so that a "Trace function
115 # changed" error would happen as pytest was cleaning up, failing the
116 # metacov runs. Putting back the old code below fixes it, but I don't
117 # understand the difference.
119 cov.start() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
120 try: # pragma: nested 1abscdtefughvijwklxmnyopzqrA234
121 # Import the Python file, executing it.
122 mod = import_local_file(modname, modfile) 1abscdtefughvijwklxmnyopzqrA234
123 finally: # pragma: nested
124 # Stop coverage.py.
125 cov.stop() 1abscdtefughvijwklxmnyopzqrA234
126 return mod 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
128 def get_report(self, cov: Coverage, squeeze: bool = True, **kwargs: Any) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
129 """Get the report from `cov`, and canonicalize it."""
130 repout = io.StringIO() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
131 kwargs.setdefault("show_missing", False) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
132 cov.report(file=repout, **kwargs) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
133 report = repout.getvalue().replace("\\", "/") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
134 print(report) # When tests fail, it's helpful to see the output 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
135 if squeeze: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
136 report = re.sub(r" +", " ", report) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
137 return report 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
139 def get_module_name(self) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
140 """Return a random module name to use for this test run."""
141 self.last_module_name = "coverage_test_" + str(random.random())[2:] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
142 return self.last_module_name 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
144 def check_coverage( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
145 self,
146 text: str,
147 *,
148 lines: Sequence[TLineNo] | None = None,
149 missing: str = "",
150 report: str = "",
151 excludes: Iterable[str] | None = None,
152 partials: Iterable[str] = (),
153 branchz: str | None = None,
154 branchz_missing: str | None = None,
155 branch: bool = True,
156 ) -> Coverage:
157 """Check the coverage measurement of `text`.
159 The source `text` is run and measured. `lines` are the line numbers
160 that are executable, `missing` are the lines not executed, `excludes`
161 are regexes to match against for excluding lines, and `report` is the
162 text of the measurement report.
164 For branch measurement, `branchz` is a string that can be decoded into
165 arcs in the code (see `arcz_to_arcs` for the encoding scheme).
166 `branchz_missing` are the arcs that are not executed.
168 Returns the Coverage object, in case you want to poke at it some more.
170 """
171 __tracebackhide__ = True # pytest, please don't show me this function. 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
173 # We write the code into a file so that we can import it.
174 # Coverage.py wants to deal with things as modules with file names.
175 modname = self.get_module_name() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
177 self.make_file(modname + ".py", text) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
179 branches = branches_missing = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
180 if branchz is not None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
181 branches = arcz_to_arcs(branchz) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
182 if branchz_missing is not None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
183 branches_missing = arcz_to_arcs(branchz_missing) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYx8NmOnZy#PoQp0z$RqSr1A!234
185 # Start up coverage.py.
186 cov = coverage.Coverage(branch=branch) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
187 cov.erase() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
188 for exc in excludes or []: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
189 cov.exclude(exc) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
190 for par in partials or []: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
191 cov.exclude(par, which="partial") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
193 mod = self.start_import_stop(cov, modname) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
195 # Clean up our side effects
196 del sys.modules[modname] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
198 # Get the analysis results, and check that they are right.
199 analysis = cov._analyze(mod) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
200 statements = sorted(analysis.statements) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
201 if lines: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
202 # lines is a list of numbers, it must match the statements
203 # found in the code.
204 assert statements == lines, f"lines: {statements!r} != {lines!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
205 missing_formatted = analysis.missing_formatted() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
206 msg = f"missing: {missing_formatted!r} != {missing!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
207 assert missing_formatted == missing, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
209 if branches is not None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
210 trimmed_arcs = branches_to_arcs(arcs_to_branches(analysis.arc_possibilities)) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
211 assert branches == trimmed_arcs, ( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
212 f"Wrong possible branches: {branches} != {trimmed_arcs}"
213 )
214 if branches_missing is not None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
215 assert set(branches_missing) <= set(branches), ( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
216 f"{branches_missing = }, has non-branches in it."
217 )
218 analysis_missing = branches_to_arcs(analysis.missing_branch_arcs()) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
219 assert branches_missing == analysis_missing, ( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
220 f"Wrong missing branches: {branches_missing} != {analysis_missing}"
221 )
223 if report: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
224 frep = io.StringIO() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
225 cov.report(mod, file=frep, show_missing=True) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
226 rep = " ".join(frep.getvalue().split("\n")[2].split()[1:]) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
227 assert report == rep, f"{report!r} != {rep!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
229 return cov 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
231 def make_data_file( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!
232 self,
233 basename: str | None = None,
234 *,
235 suffix: str | None = None,
236 lines: Mapping[str, Collection[TLineNo]] | None = None,
237 arcs: Mapping[str, Collection[TArc]] | None = None,
238 file_tracers: Mapping[str, str] | None = None,
239 ) -> CoverageData:
240 """Write some data into a coverage data file."""
241 data = coverage.CoverageData(basename=basename, suffix=suffix) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
242 assert lines is None or arcs is None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
243 if lines: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
244 data.add_lines(lines) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
245 if arcs: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
246 data.add_arcs(arcs) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
247 if file_tracers: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
248 data.add_file_tracers(file_tracers) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
249 data.write() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
250 return data 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
252 @contextlib.contextmanager 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
253 def assert_warnings( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!
254 self,
255 cov: Coverage,
256 warnings: Iterable[str],
257 not_warnings: Iterable[str] = (),
258 ) -> Iterator[None]:
259 """A context manager to check that particular warnings happened in `cov`.
261 `cov` is a Coverage instance. `warnings` is a list of regexes. Every
262 regex must match a warning that was issued by `cov`. It is OK for
263 extra warnings to be issued by `cov` that are not matched by any regex.
264 Warnings that are disabled are still considered issued by this function.
266 `not_warnings` is a list of regexes that must not appear in the
267 warnings. This is only checked if there are some positive warnings to
268 test for in `warnings`.
270 If `warnings` is empty, then `cov` is not allowed to issue any
271 warnings.
273 """
274 __tracebackhide__ = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
275 saved_warnings = [] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
277 def capture_warning( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!
278 msg: str,
279 slug: str | None = None,
280 once: bool = False, # pylint: disable=unused-argument
281 ) -> None:
282 """A fake implementation of Coverage._warn, to capture warnings."""
283 # NOTE: we don't implement `once`.
284 if slug: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
285 msg = f"{msg} ({slug})" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
286 saved_warnings.append(msg) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
288 original_warn = cov._warn 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
289 cov._warn = capture_warning # type: ignore[method-assign] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
291 try: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
292 yield 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
293 except: # pylint: disable=try-except-raise 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr51A234
294 raise 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr51A234
295 else:
296 if warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
297 for warning_regex in warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
298 for saved in saved_warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
299 if re.search(warning_regex, saved): 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
300 break 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
301 else:
302 msg = f"Didn't find warning {warning_regex!r} in {saved_warnings!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr51A234
303 assert False, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr51A234
304 for warning_regex in not_warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
305 for saved in saved_warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr51A234
306 if re.search(warning_regex, saved): 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr51A234
307 msg = f"Found warning {warning_regex!r} in {saved_warnings!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr51A234
308 assert False, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr51A234
309 else:
310 # No warnings expected. Raise if any warnings happened.
311 if saved_warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
312 assert False, f"Unexpected warnings: {saved_warnings!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOn6ZyPoQp0zRqSr1A234
313 finally:
314 cov._warn = original_warn # type: ignore[method-assign] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
316 def assert_same_files(self, flist1: Iterable[str], flist2: Iterable[str]) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
317 """Assert that `flist1` and `flist2` are the same set of file names."""
318 flist1_nice = [nice_file(f) for f in flist1] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
319 flist2_nice = [nice_file(f) for f in flist2] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
320 assert_count_equal(flist1_nice, flist2_nice) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
322 def assert_exists(self, fname: str) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
323 """Assert that `fname` is a file that exists."""
324 assert os.path.exists(fname), f"File {fname!r} should exist" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
326 def assert_doesnt_exist(self, fname: str) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
327 """Assert that `fname` is a file that doesn't exist."""
328 assert not os.path.exists(fname), f"File {fname!r} shouldn't exist" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
330 def assert_file_count(self, pattern: str, count: int) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
331 """Assert that there are `count` files matching `pattern`."""
332 files = sorted(glob.glob(pattern)) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
333 msg = "There should be {} files matching {!r}, but there are these: {}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
334 msg = msg.format(count, pattern, files) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
335 assert len(files) == count, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
337 def assert_recent_datetime( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!
338 self,
339 dt: datetime.datetime,
340 seconds: int = 10,
341 msg: str | None = None,
342 ) -> None:
343 """Assert that `dt` marks a time at most `seconds` seconds ago."""
344 age = datetime.datetime.now() - dt 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
345 assert age.total_seconds() >= 0, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
346 assert age.total_seconds() <= seconds, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
348 def command_line(self, args: str, ret: int = OK) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
349 """Run `args` through the command line.
351 Use this when you want to run the full coverage machinery, but in the
352 current process. Exceptions may be thrown from deep in the code.
353 Asserts that `ret` is returned by `CoverageScript.command_line`.
355 Compare with `run_command`.
357 Returns None.
359 """
360 ret_actual = command_line(args) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
361 assert ret_actual == ret, f"{ret_actual!r} != {ret!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
363 # Some distros rename the coverage command, and need a way to indicate
364 # their new command name to the tests. This is here for them to override,
365 # for example:
366 # https://salsa.debian.org/debian/pkg-python-coverage/-/blob/master/debian/patches/02.rename-public-programs.patch
367 coverage_command = "coverage" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
369 def run_command(self, cmd: str, *, status: int = 0) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
370 """Run the command-line `cmd` in a subprocess.
372 `cmd` is the command line to invoke in a subprocess. Returns the
373 combined content of `stdout` and `stderr` output streams from the
374 subprocess.
376 Asserts that the exit status is `status` (default 0).
378 See `run_command_status` for complete semantics.
380 Use this when you need to test the process behavior of coverage.
382 Compare with `command_line`.
384 """
385 statuses = [status] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
386 if status < 0: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
387 # Mac properly returns -signal as the exit status. Linux returns 128 + signal.
388 statuses.append(128 - status) 1BaCbDcEdFeGfHgIhJiKjLkMl7NmOn6PoQp9RqSr523
389 actual_status, output = self.run_command_status(cmd) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
390 assert actual_status in statuses 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
391 return output 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
393 def run_command_status(self, cmd: str) -> tuple[int, str]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
394 """Run the command-line `cmd` in a subprocess, and print its output.
396 Use this when you need to test the process behavior of coverage.
398 Compare with `command_line`.
400 Handles the following command names specially:
402 * "python" is replaced with the command name of the current
403 Python interpreter.
405 * "coverage" is replaced with the command name for the main
406 coverage.py program.
408 Returns a pair: the process' exit status and its stdout/stderr text,
409 which are also stored as `self.last_command_status` and
410 `self.last_command_output`.
412 """
413 # Make sure "python" and "coverage" mean specifically what we want
414 # them to mean.
415 split_commandline = cmd.split() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
416 command_name = split_commandline[0] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
417 command_args = split_commandline[1:] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
419 if command_name == "python": 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
420 # Running a Python interpreter in a subprocesses can be tricky.
421 # Use the real name of our own executable. So "python foo.py" might
422 # get executed as "python3.3 foo.py". This is important because
423 # Python 3.x doesn't install as "python", so you might get a Python
424 # 2 executable instead if you don't use the executable's basename.
425 command_words = [os.path.basename(sys.executable)] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
427 elif command_name == "coverage": 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
428 # The invocation requests the coverage.py program. Substitute the
429 # actual coverage.py main command name.
430 command_words = [self.coverage_command] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
432 else:
433 command_words = [command_name] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMl7YxNmOn6ZyPoQp90zRqSr51A234
435 cmd = " ".join([shlex.quote(w) for w in command_words] + command_args) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
437 self.last_command_status, self.last_command_output = run_command(cmd) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
438 print(self.last_command_output) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
439 return self.last_command_status, self.last_command_output 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
441 def add_test_modules_to_pythonpath(self) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
442 """Add our test modules directory to PYTHONPATH."""
443 # Check that there isn't already a PYTHONPATH.
444 assert os.getenv("PYTHONPATH") is None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
445 testmods = nice_file(self.working_root(), "tests/modules") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
446 zipfile = nice_file(self.working_root(), "tests/zipmods.zip") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
447 self.set_environ("PYTHONPATH", testmods + os.pathsep + zipfile) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
449 def working_root(self) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
450 """Where is the root of the coverage.py working tree?"""
451 return os.path.dirname(nice_file(__file__, "..")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
453 def report_from_command(self, cmd: str) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
454 """Return the report from the `cmd`, with some convenience added."""
455 report = self.run_command(cmd).replace("\\", "/") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
456 assert "error" not in report.lower() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
457 return report 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
459 def report_lines(self, report: str) -> list[str]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
460 """Return the lines of the report, as a list."""
461 lines = report.split("\n") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
462 assert lines[-1] == "" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
463 return lines[:-1] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
465 def line_count(self, report: str) -> int: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
466 """How many lines are in `report`?"""
467 return len(self.report_lines(report)) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
469 def squeezed_lines(self, report: str) -> list[str]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
470 """Return a list of the lines in report, with the spaces squeezed."""
471 lines = self.report_lines(report) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
472 return [re.sub(r"\s+", " ", l.strip()) for l in lines] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
474 def last_line_squeezed(self, report: str) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
475 """Return the last line of `report` with the spaces squeezed down."""
476 return self.squeezed_lines(report)[-1] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
478 def get_measured_filenames(self, coverage_data: CoverageData) -> dict[str, str]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
479 """Get paths to measured files.
481 Returns a dict of {filename: absolute path to file}
482 for given CoverageData.
483 """
484 return {os.path.basename(filename): filename for filename in coverage_data.measured_files()} 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
486 def get_missing_arc_description(self, cov: Coverage, start: TLineNo, end: TLineNo) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
487 """Get the missing-arc description for a line arc in a coverage run."""
488 # ugh, unexposed methods??
489 assert self.last_module_name is not None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
490 filename = self.last_module_name + ".py" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
491 fr = cov._get_file_reporter(filename) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
492 arcs_executed = cov._analyze(filename).arcs_executed 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
493 return fr.missing_arc_description(start, end, arcs_executed) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
496class UsingModulesMixin: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
497 """A mixin for importing modules from tests/modules and tests/moremodules."""
499 def setUp(self) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
500 super().setUp() # type: ignore[misc] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
502 # Parent class saves and restores sys.path, we can just modify it.
503 sys.path.append(nice_file(TESTS_DIR, "modules")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
504 sys.path.append(nice_file(TESTS_DIR, "moremodules")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
505 sys.path.append(nice_file(TESTS_DIR, "zipmods.zip")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
508def command_line(args: str) -> int: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
509 """Run `args` through the CoverageScript command line.
511 Returns the return code from CoverageScript.command_line.
513 """
514 script = CoverageScript() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
515 ret = script.command_line(shlex.split(args)) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234
516 return ret 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234