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

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 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

7 

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

19 

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

23 

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

30 

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

34 

35 

36# Status returns for the command line. 

37OK, ERR = 0, 1 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

38 

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

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, "..")) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

46 

47 

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

58 

59 

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

63 

64 

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

72 

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

74 maxDiff = None 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

75 

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

77 longMessage = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

78 

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

80 show_stderr = True 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

81 

82 def setUp(self) -> None: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

83 super().setUp() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

84 

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

89 

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. 

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() 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

127 

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

138 

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

143 

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

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. 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

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() 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

176 

177 self.make_file(modname + ".py", text) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

178 

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

184 

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

192 

193 mod = self.start_import_stop(cov, modname) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

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( 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

251 

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

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 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

275 saved_warnings = [] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

276 

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

287 

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

290 

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

315 

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

321 

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

325 

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

329 

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

336 

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

347 

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. 

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) 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

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" 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

368 

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. 

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] 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

392 

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. 

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() 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

418 

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

426 

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

431 

432 else: 

433 command_words = [command_name] 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLkMlYx7NmOnZy5PoQp0z#RqSr1A8234

434 

435 cmd = " ".join([shlex.quote(w) for w in command_words] + command_args) 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

436 

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

440 

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

448 

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

452 

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

458 

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

464 

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

468 

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

473 

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

477 

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. 

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: 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

494 

495 

496class UsingModulesMixin: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

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

498 

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

501 

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

506 

507 

508def command_line(args: str) -> int: 1BaCbTsDcEdUtFeGfVuHgIhWvJiKjXwLk6Ml9Yx7Nm$On%Zy5Po!Qp'0z#Rq(Sr)1A8234

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

510 

511 Returns the return code from CoverageScript.command_line. 

512 

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