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

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 

3 

4"""Base test case class for coverage.py testing.""" 

5 

6from __future__ import annotations 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

7 

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

19 

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

23 

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

30 

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

34 

35 

36# Status returns for the command line. 

37OK, ERR = 0, 1 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

38 

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

41 

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

46 

47 

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

58 

59 

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

63 

64 

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.""" 

72 

73 # Standard unittest setting: show me diffs even if they are very long. 

74 maxDiff = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

75 

76 # Tell newer unittest implementations to print long helpful messages. 

77 longMessage = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

78 

79 # Let stderr go to stderr, pytest will capture it for us. 

80 show_stderr = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

81 

82 def setUp(self) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

83 super().setUp() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

84 

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

89 

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. 

97 

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. 

101 

102 The imported module is returned. 

103 

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. 

118 

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

127 

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

138 

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

143 

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`. 

158 

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. 

163 

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. 

167 

168 Returns the Coverage object, in case you want to poke at it some more. 

169 

170 """ 

171 __tracebackhide__ = True # pytest, please don't show me this function. 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

172 

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

176 

177 self.make_file(modname + ".py", text) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

178 

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

184 

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

192 

193 mod = self.start_import_stop(cov, modname) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

194 

195 # Clean up our side effects 

196 del sys.modules[modname] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234

197 

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

208 

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 ) 

222 

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

228 

229 return cov 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYxNmOnZyPoQp0zRqSr1A234

230 

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

251 

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`. 

260 

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. 

265 

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`. 

269 

270 If `warnings` is empty, then `cov` is not allowed to issue any 

271 warnings. 

272 

273 """ 

274 __tracebackhide__ = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

275 saved_warnings = [] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

276 

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

287 

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

290 

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

315 

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

321 

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

325 

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

329 

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

336 

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

347 

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. 

350 

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`. 

354 

355 Compare with `run_command`. 

356 

357 Returns None. 

358 

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

362 

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

368 

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. 

371 

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. 

375 

376 Asserts that the exit status is `status` (default 0). 

377 

378 See `run_command_status` for complete semantics. 

379 

380 Use this when you need to test the process behavior of coverage. 

381 

382 Compare with `command_line`. 

383 

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

392 

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. 

395 

396 Use this when you need to test the process behavior of coverage. 

397 

398 Compare with `command_line`. 

399 

400 Handles the following command names specially: 

401 

402 * "python" is replaced with the command name of the current 

403 Python interpreter. 

404 

405 * "coverage" is replaced with the command name for the main 

406 coverage.py program. 

407 

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`. 

411 

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

418 

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

426 

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

431 

432 else: 

433 command_words = [command_name] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMl7YxNmOn6ZyPoQp90zRqSr51A234

434 

435 cmd = " ".join([shlex.quote(w) for w in command_words] + command_args) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

436 

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

440 

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

448 

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

452 

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

458 

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

464 

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

468 

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

473 

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

477 

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. 

480 

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

485 

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

494 

495 

496class UsingModulesMixin: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

497 """A mixin for importing modules from tests/modules and tests/moremodules.""" 

498 

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

501 

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

506 

507 

508def command_line(args: str) -> int: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk%Ml7Yx8Nm'On6Zy#Po(Qp90z$Rq)Sr51A!234

509 """Run `args` through the CoverageScript command line. 

510 

511 Returns the return code from CoverageScript.command_line. 

512 

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