Coverage for tests / coveragetest.py: 100.000%
220 statements
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-29 20:34 +0000
« prev ^ index » next coverage.py v7.12.1a0.dev1, created at 2025-11-29 20:34 +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 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
8import collections 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
9import contextlib 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
10import datetime 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
11import glob 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
12import io 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
13import os 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
14import os.path 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
15import random 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
16import re 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
17import shlex 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
18import sys 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
20from collections.abc import Collection, Iterable, Iterator, Mapping, Sequence 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
21from types import ModuleType 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
22from typing import Any 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
24import coverage 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
25from coverage import Coverage 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
26from coverage.cmdline import CoverageScript 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
27from coverage.data import CoverageData 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
28from coverage.misc import import_local_file 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
29from coverage.types import TArc, TLineNo 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
31from tests.helpers import arcz_to_arcs, assert_count_equal 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
32from tests.helpers import nice_file, run_command 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
33from tests.mixins import PytestBase, StdStreamCapturingMixin, RestoreModulesMixin, TempDirMixin 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
36# Status returns for the command line.
37OK, ERR = 0, 1 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
39# The coverage/tests directory, for all sorts of finding test helping things.
40TESTS_DIR = os.path.dirname(__file__) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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, "..")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
48def arcs_to_branches(arcs: Iterable[TArc]) -> dict[TLineNo, list[TLineNo]]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
49 """Convert a list of arcs into a dict showing branches."""
50 arcs_combined = collections.defaultdict(set) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
51 for fromno, tono in arcs: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
52 arcs_combined[fromno].add(tono) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
53 branches = collections.defaultdict(list) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
54 for fromno, tono in arcs: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
55 if len(arcs_combined[fromno]) > 1: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
56 branches[fromno].append(tono) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMl9Yx7Nm$On%Zy5Po!Qp'0zRq(Sr1A8234
57 return branches 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
60def branches_to_arcs(branches: dict[TLineNo, list[TLineNo]]) -> list[TArc]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
65class CoverageTest( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
76 # Tell newer unittest implementations to print long helpful messages.
77 longMessage = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
79 # Let stderr go to stderr, pytest will capture it for us.
80 show_stderr = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
82 def setUp(self) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
83 super().setUp() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
85 # Attributes for getting info about what happened.
86 self.last_command_status: int | None = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
87 self.last_command_output: str | None = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
88 self.last_module_name: str | None = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
90 def start_import_stop( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8
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() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
129 """Get the report from `cov`, and canonicalize it."""
130 repout = io.StringIO() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
131 kwargs.setdefault("show_missing", False) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
132 cov.report(file=repout, **kwargs) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
133 report = repout.getvalue().replace("\\", "/") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
134 print(report) # When tests fail, it's helpful to see the output 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
135 if squeeze: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
136 report = re.sub(r" +", " ", report) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
137 return report 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
139 def get_module_name(self) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
140 """Return a random module name to use for this test run."""
141 self.last_module_name = "coverage_test_" + str(random.random())[2:] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
142 return self.last_module_name 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
144 def check_coverage( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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. 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
177 self.make_file(modname + ".py", text) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
179 branches = branches_missing = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
180 if branchz is not None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
181 branches = arcz_to_arcs(branchz) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
182 if branchz_missing is not None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
183 branches_missing = arcz_to_arcs(branchz_missing) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMl9Yx7NmOnZy5PoQp0z#RqSr1A8234
185 # Start up coverage.py.
186 cov = coverage.Coverage(branch=branch) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
187 cov.erase() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
188 for exc in excludes or []: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
189 cov.exclude(exc) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
190 for par in partials or []: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
191 cov.exclude(par, which="partial") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
193 mod = self.start_import_stop(cov, modname) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8
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) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
242 assert lines is None or arcs is None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
243 if lines: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
244 data.add_lines(lines) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
245 if arcs: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
246 data.add_arcs(arcs) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZy5PoQp0zRqSr1A234
247 if file_tracers: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
248 data.add_file_tracers(file_tracers) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234
249 data.write() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
250 return data 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
252 @contextlib.contextmanager 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
253 def assert_warnings( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8
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 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
275 saved_warnings = [] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
277 def capture_warning( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8
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: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
285 msg = f"{msg} ({slug})" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
286 saved_warnings.append(msg) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
288 original_warn = cov._warn 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
289 cov._warn = capture_warning # type: ignore[method-assign] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
291 try: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
292 yield 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
293 except: # pylint: disable=try-except-raise 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6MlYxNmOnZy5PoQp0zRqSr1A234
294 raise 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6MlYxNmOnZy5PoQp0zRqSr1A234
295 else:
296 if warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
297 for warning_regex in warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
298 for saved in saved_warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
299 if re.search(warning_regex, saved): 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
300 break 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
301 else:
302 msg = f"Didn't find warning {warning_regex!r} in {saved_warnings!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6MlYxNmOnZy5PoQp0zRqSr1A234
303 assert False, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6MlYxNmOnZy5PoQp0zRqSr1A234
304 for warning_regex in not_warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
305 for saved in saved_warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6MlYxNmOnZy5PoQp0zRqSr1A234
306 if re.search(warning_regex, saved): 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6MlYxNmOnZy5PoQp0zRqSr1A234
307 msg = f"Found warning {warning_regex!r} in {saved_warnings!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6MlYxNmOnZy5PoQp0zRqSr1A234
308 assert False, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6MlYxNmOnZy5PoQp0zRqSr1A234
309 else:
310 # No warnings expected. Raise if any warnings happened.
311 if saved_warnings: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
312 assert False, f"Unexpected warnings: {saved_warnings!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6MlYxNmOnZyPo!Qp0zRqSr1A234
313 finally:
314 cov._warn = original_warn # type: ignore[method-assign] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
316 def assert_same_files(self, flist1: Iterable[str], flist2: Iterable[str]) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
317 """Assert that `flist1` and `flist2` are the same set of file names."""
318 flist1_nice = [nice_file(f) for f in flist1] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
319 flist2_nice = [nice_file(f) for f in flist2] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
320 assert_count_equal(flist1_nice, flist2_nice) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
322 def assert_exists(self, fname: str) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
323 """Assert that `fname` is a file that exists."""
324 assert os.path.exists(fname), f"File {fname!r} should exist" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
326 def assert_doesnt_exist(self, fname: str) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
330 def assert_file_count(self, pattern: str, count: int) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
331 """Assert that there are `count` files matching `pattern`."""
332 files = sorted(glob.glob(pattern)) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
333 msg = "There should be {} files matching {!r}, but there are these: {}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
334 msg = msg.format(count, pattern, files) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
335 assert len(files) == count, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
337 def assert_recent_datetime( 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8
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 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
345 assert age.total_seconds() >= 0, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
346 assert age.total_seconds() <= seconds, msg 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
348 def command_line(self, args: str, ret: int = OK) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
361 assert ret_actual == ret, f"{ret_actual!r} != {ret!r}" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
369 def run_command(self, cmd: str, *, status: int = 0) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
386 if status < 0: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
387 # Mac properly returns -signal as the exit status. Linux returns 128 + signal.
388 statuses.append(128 - status) 1BaCbDcEdFeGfHgIhJiKjLkMlNmOnPoQpRqSr23
389 actual_status, output = self.run_command_status(cmd) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
390 assert actual_status in statuses 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
391 return output 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
393 def run_command_status(self, cmd: str) -> tuple[int, str]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
416 command_name = split_commandline[0] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
417 command_args = split_commandline[1:] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
419 if command_name == "python": 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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)] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
427 elif command_name == "coverage": 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
428 # The invocation requests the coverage.py program. Substitute the
429 # actual coverage.py main command name.
430 command_words = [self.coverage_command] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
432 else:
433 command_words = [command_name] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYx7NmOnZy5PoQp0z#RqSr1A8234
435 cmd = " ".join([shlex.quote(w) for w in command_words] + command_args) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
437 self.last_command_status, self.last_command_output = run_command(cmd) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
438 print(self.last_command_output) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
439 return self.last_command_status, self.last_command_output 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
441 def add_test_modules_to_pythonpath(self) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
442 """Add our test modules directory to PYTHONPATH."""
443 # Check that there isn't already a PYTHONPATH.
444 assert os.getenv("PYTHONPATH") is None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
445 testmods = nice_file(self.working_root(), "tests/modules") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
446 zipfile = nice_file(self.working_root(), "tests/zipmods.zip") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
447 self.set_environ("PYTHONPATH", testmods + os.pathsep + zipfile) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
449 def working_root(self) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
450 """Where is the root of the coverage.py working tree?"""
451 return os.path.dirname(nice_file(__file__, "..")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
453 def report_from_command(self, cmd: str) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
454 """Return the report from the `cmd`, with some convenience added."""
455 report = self.run_command(cmd).replace("\\", "/") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
456 assert "error" not in report.lower() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
457 return report 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
459 def report_lines(self, report: str) -> list[str]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
460 """Return the lines of the report, as a list."""
461 lines = report.split("\n") 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
462 assert lines[-1] == "" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
463 return lines[:-1] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
465 def line_count(self, report: str) -> int: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
466 """How many lines are in `report`?"""
467 return len(self.report_lines(report)) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
469 def squeezed_lines(self, report: str) -> list[str]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
470 """Return a list of the lines in report, with the spaces squeezed."""
471 lines = self.report_lines(report) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
472 return [re.sub(r"\s+", " ", l.strip()) for l in lines] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
474 def last_line_squeezed(self, report: str) -> str: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
475 """Return the last line of `report` with the spaces squeezed down."""
476 return self.squeezed_lines(report)[-1] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
478 def get_measured_filenames(self, coverage_data: CoverageData) -> dict[str, str]: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
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 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
490 filename = self.last_module_name + ".py" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
491 fr = cov._get_file_reporter(filename) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
492 arcs_executed = cov._analyze(filename).arcs_executed 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
493 return fr.missing_arc_description(start, end, arcs_executed) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
496class UsingModulesMixin: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
497 """A mixin for importing modules from tests/modules and tests/moremodules."""
499 def setUp(self) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
500 super().setUp() # type: ignore[misc] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
502 # Parent class saves and restores sys.path, we can just modify it.
503 sys.path.append(nice_file(TESTS_DIR, "modules")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
504 sys.path.append(nice_file(TESTS_DIR, "moremodules")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
505 sys.path.append(nice_file(TESTS_DIR, "zipmods.zip")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
508def command_line(args: str) -> int: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
509 """Run `args` through the CoverageScript command line.
511 Returns the return code from CoverageScript.command_line.
513 """
514 script = CoverageScript() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
515 ret = script.command_line(shlex.split(args)) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234
516 return ret 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234