Coverage for tests / test_process.py: 100.000%
620 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"""Tests for process behavior of coverage.py."""
6from __future__ import annotations 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
8import csv 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
9import glob 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
10import itertools 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
11import os 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
12import os.path 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
13import platform 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
14import re 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
15import signal 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
16import site 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
17import stat 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
18import sys 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
19import textwrap 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
21from pathlib import Path 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
22from typing import Any, Iterable 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
24import pytest 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
26import coverage 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
27from coverage import env 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
28from coverage.data import line_counts 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
29from coverage.files import abs_file, python_reported_file 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
31from tests import testenv 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
32from tests.coveragetest import CoverageTest, TESTS_DIR 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
33from tests.helpers import change_dir, re_lines, re_lines_text 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
36class ProcessTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
37 """Tests of the per-process behavior of coverage.py."""
39 def test_save_on_exit(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
40 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
41 "mycode.py",
42 """\
43 h = "Hello"
44 w = "world"
45 """,
46 )
48 self.assert_doesnt_exist(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
49 self.run_command("coverage run mycode.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
50 self.assert_exists(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
52 def test_tests_dir_is_importable(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
53 # Checks that we can import modules from the tests directory at all!
54 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
55 "mycode.py",
56 """\
57 import covmod1
58 import covmodzip1
59 a = 1
60 print('done')
61 """,
62 )
64 self.assert_doesnt_exist(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
65 self.add_test_modules_to_pythonpath() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
66 out = self.run_command("coverage run mycode.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
67 self.assert_exists(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
68 assert out == "done\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
70 def test_coverage_run_envvar_is_in_coveragerun(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
71 # Test that we are setting COVERAGE_RUN when we run.
72 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
73 "envornot.py",
74 """\
75 import os
76 print(os.getenv("COVERAGE_RUN", "nope"))
77 """,
78 )
79 self.del_environ("COVERAGE_RUN") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
80 # Regular Python doesn't have the environment variable.
81 out = self.run_command("python envornot.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
82 assert out == "nope\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
83 self.del_environ("COVERAGE_RUN") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
84 # But `coverage run` does have it.
85 out = self.run_command("coverage run envornot.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
86 assert out == "true\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
88 def make_b_or_c_py(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
89 """Create b_or_c.py, used in a few of these tests."""
90 # "b_or_c.py b" will run 6 lines.
91 # "b_or_c.py c" will run 7 lines.
92 # Together, they run 8 lines.
93 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
94 "b_or_c.py",
95 """\
96 import sys
97 a = 2
98 if sys.argv[1] == 'b':
99 b = 4
100 else:
101 c = 6
102 c2 = 7
103 d = 8
104 print('done')
105 """,
106 )
108 def test_append_data(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
109 self.make_b_or_c_py() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
111 out = self.run_command("coverage run b_or_c.py b") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
112 assert out == "done\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
113 self.assert_exists(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
114 self.assert_file_count(".coverage.*", 0) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
116 out = self.run_command("coverage run --append b_or_c.py c") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
117 assert out == "done\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
118 self.assert_exists(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
119 self.assert_file_count(".coverage.*", 0) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
121 # Read the coverage file and see that b_or_c.py has all 8 lines
122 # executed.
123 data = coverage.CoverageData() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
124 data.read() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
125 assert line_counts(data)["b_or_c.py"] == 8 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
127 def test_append_data_with_different_file(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
128 self.make_b_or_c_py() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
130 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
131 ".coveragerc",
132 """\
133 [run]
134 data_file = .mycovdata
135 """,
136 )
138 out = self.run_command("coverage run b_or_c.py b") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
139 assert out == "done\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
140 self.assert_doesnt_exist(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
141 self.assert_exists(".mycovdata") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
143 out = self.run_command("coverage run --append b_or_c.py c") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
144 assert out == "done\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
145 self.assert_doesnt_exist(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
146 self.assert_exists(".mycovdata") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
148 # Read the coverage file and see that b_or_c.py has all 8 lines
149 # executed.
150 data = coverage.CoverageData(".mycovdata") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
151 data.read() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
152 assert line_counts(data)["b_or_c.py"] == 8 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
154 def test_append_can_create_a_data_file(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
155 self.make_b_or_c_py() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
157 out = self.run_command("coverage run --append b_or_c.py b") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
158 assert out == "done\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
159 self.assert_exists(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
160 self.assert_file_count(".coverage.*", 0) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
162 # Read the coverage file and see that b_or_c.py has only 6 lines
163 # executed.
164 data = coverage.CoverageData() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
165 data.read() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
166 assert line_counts(data)["b_or_c.py"] == 6 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
168 def test_combine_with_rc(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
169 self.make_b_or_c_py() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
171 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
172 ".coveragerc",
173 """\
174 [run]
175 source = .
176 parallel = true
177 """,
178 )
180 out = self.run_command("coverage run b_or_c.py b") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
181 assert out == "done\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
182 self.assert_doesnt_exist(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
184 out = self.run_command("coverage run b_or_c.py c") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
185 assert out == "done\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
186 self.assert_doesnt_exist(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
188 # After two runs, there should be two .coverage.machine.123 files.
189 self.assert_file_count(".coverage.*", 2) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
191 # Combine the parallel coverage data files into .coverage .
192 self.run_command("coverage combine") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
193 self.assert_exists(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
194 self.assert_exists(".coveragerc") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
196 # After combining, there should be only the .coverage file.
197 self.assert_file_count(".coverage.*", 0) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
199 # Read the coverage file and see that b_or_c.py has all 8 lines
200 # executed.
201 data = coverage.CoverageData() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
202 data.read() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
203 assert line_counts(data)["b_or_c.py"] == 8 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
205 # Reporting should still work even with the .rc file
206 out = self.run_command("coverage report") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
207 assert out == textwrap.dedent("""\ 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
208 Name Stmts Miss Cover
209 -------------------------------
210 b_or_c.py 8 0 100%
211 -------------------------------
212 TOTAL 8 0 100%
213 """)
215 def test_combine_with_aliases(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
216 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
217 "d1/x.py",
218 """\
219 a = 1
220 b = 2
221 print(f"{a} {b}")
222 """,
223 )
225 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
226 "d2/x.py",
227 """\
228 # 1
229 # 2
230 # 3
231 c = 4
232 d = 5
233 print(f"{c} {d}")
234 """,
235 )
237 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
238 ".coveragerc",
239 """\
240 [run]
241 source = .
242 parallel = True
244 [paths]
245 source =
246 src
247 */d1
248 */d2
249 """,
250 )
252 out = self.run_command("coverage run " + os.path.normpath("d1/x.py")) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
253 assert out == "1 2\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
254 out = self.run_command("coverage run " + os.path.normpath("d2/x.py")) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
255 assert out == "4 5\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
257 self.assert_file_count(".coverage.*", 2) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
259 self.make_file("src/x.py", "") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
261 self.run_command("coverage combine") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
262 self.assert_exists(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
264 # After combining, there should be only the .coverage file.
265 self.assert_file_count(".coverage.*", 0) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
267 # Read the coverage data file and see that the two different x.py
268 # files have been combined together.
269 data = coverage.CoverageData() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
270 data.read() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
271 summary = line_counts(data, fullpath=True) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
272 assert len(summary) == 1 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
273 actual = abs_file(list(summary.keys())[0]) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
274 expected = abs_file("src/x.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
275 assert expected == actual 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
276 assert list(summary.values())[0] == 6 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
278 def test_erase_parallel(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
279 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
280 ".coveragerc",
281 """\
282 [run]
283 data_file = data.dat
284 parallel = True
285 """,
286 )
287 self.make_file("data.dat") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
288 self.make_file("data.dat.fooey") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
289 self.make_file("data.dat.gooey") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
290 self.make_file(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
292 self.run_command("coverage erase") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
293 self.assert_doesnt_exist("data.dat") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
294 self.assert_doesnt_exist("data.dat.fooey") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
295 self.assert_doesnt_exist("data.dat.gooey") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
296 self.assert_exists(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
298 def test_missing_source_file(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
299 # Check what happens if the source is missing when reporting happens.
300 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
301 "fleeting.py",
302 """\
303 s = 'goodbye, cruel world!'
304 """,
305 )
307 self.run_command("coverage run fleeting.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
308 os.remove("fleeting.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
309 out = self.run_command("coverage html -d htmlcov", status=1) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
310 assert re.search("No source for code: '.*fleeting.py'", out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
311 assert "Traceback" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
313 # It happens that the code paths are different for *.py and other
314 # files, so try again with no extension.
315 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
316 "fleeting",
317 """\
318 s = 'goodbye, cruel world!'
319 """,
320 )
322 self.run_command("coverage run fleeting") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
323 os.remove("fleeting") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
324 out = self.run_command("coverage html -d htmlcov", status=1) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
325 assert re.search(r"No source for code: '.*fleeting'", out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
326 assert re.search( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
327 r"; see https://coverage.readthedocs.io/en/[^/]+/messages.html#error-no-source",
328 out,
329 )
330 assert "Traceback" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
332 def test_running_missing_file(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
333 status, out = self.run_command_status("coverage run xyzzy.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
334 assert re.search("No file to run: .*xyzzy.py", out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
335 assert "raceback" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
336 assert "rror" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
337 assert status == 1 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
339 def test_code_throws(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
340 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
341 "throw.py",
342 """\
343 class MyException(Exception):
344 pass
346 def f1():
347 raise MyException("hey!")
349 def f2():
350 f1()
352 f2()
353 """,
354 )
356 # The important thing is for "coverage run" and "python" to report the
357 # same traceback.
358 out = self.run_command("coverage run throw.py", status=1) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
359 out2 = self.run_command("python throw.py", status=1) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
360 if env.PYPY: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
361 # PyPy has an extra frame in the traceback for some reason
362 out2 = re_lines_text("toplevel", out2, match=False) 1TO)
363 assert out == out2 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
365 # But also make sure that the output is what we expect.
366 path = python_reported_file("throw.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
367 msg = f'File "{re.escape(path)}", line 8, in f2' 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
368 assert re.search(msg, out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
369 assert 'raise MyException("hey!")' in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
371 def test_code_exits(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
372 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
373 "exit.py",
374 """\
375 import sys
376 def f1():
377 print("about to exit..")
378 sys.exit(17)
380 def f2():
381 f1()
383 f2()
384 """,
385 )
387 # The important thing is for "coverage run" and "python" to have the
388 # same output. No traceback.
389 status, out = self.run_command_status("coverage run exit.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
390 status2, out2 = self.run_command_status("python exit.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
391 assert out == out2 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
392 assert out == "about to exit..\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
393 assert status == status2 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
394 assert status == 17 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
396 def test_code_exits_no_arg(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
397 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
398 "exit_none.py",
399 """\
400 import sys
401 def f1():
402 print("about to exit quietly..")
403 sys.exit()
405 f1()
406 """,
407 )
408 status, out = self.run_command_status("coverage run exit_none.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
409 status2, out2 = self.run_command_status("python exit_none.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
410 assert out == out2 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
411 assert out == "about to exit quietly..\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
412 assert status == status2 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
413 assert status == 0 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
415 @pytest.mark.skipif(not hasattr(os, "fork"), reason="Can't test os.fork, it doesn't exist.") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
416 def test_fork(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
417 self.make_file( 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
418 "fork.py",
419 """\
420 import os
422 print(f"parent,{os.getpid()}", flush=True)
423 ret = os.fork()
425 if ret == 0:
426 print(f"child,{os.getpid()}", flush=True)
427 else:
428 os.waitpid(ret, 0)
429 """,
430 )
431 total_lines = 6 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
433 self.set_environ("COVERAGE_DEBUG_FILE", "debug.out") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
434 out = self.run_command("coverage run --debug=pid,process,trace -p fork.py") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
435 pids = {key: int(pid) for key, pid in csv.reader(out.splitlines())} 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
436 assert set(pids) == {"parent", "child"} 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
437 self.assert_doesnt_exist(".coverage") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
439 # After running the forking program, there should be two
440 # .coverage.machine.pid.randomword files. The pids should match our
441 # processes, and the files should have different random words at the
442 # end of the file name.
443 self.assert_file_count(".coverage.*", 2) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
444 data_files = glob.glob(".coverage.*") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
445 filepids = {int(name.split(".")[-2]) for name in data_files} 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
446 assert filepids == set(pids.values()) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
447 suffixes = {name.split(".")[-1] for name in data_files} 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
448 assert len(suffixes) == 2, f"Same random suffix: {data_files}" 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
450 # Each data file should have a subset of the lines.
451 for data_file in data_files: 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
452 data = coverage.CoverageData(data_file) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
453 data.read() 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
454 assert line_counts(data)["fork.py"] < total_lines 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
456 # Combine the parallel coverage data files into a .coverage file.
457 # After combining, there should be only the .coverage file.
458 self.run_command("coverage combine") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
459 self.assert_exists(".coverage") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
460 self.assert_file_count(".coverage.*", 0) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
462 data = coverage.CoverageData() 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
463 data.read() 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
464 assert line_counts(data)["fork.py"] == total_lines 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
466 debug_text = Path("debug.out").read_text(encoding="utf-8") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
467 ppid = pids["parent"] 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
468 cpid = pids["child"] 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
469 assert ppid != cpid 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
470 plines = re_lines(rf"{ppid}\.[0-9a-f]+: New process: pid={ppid}, executable", debug_text) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
471 assert len(plines) == 1 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
472 clines = re_lines(rf"{cpid}\.[0-9a-f]+: New process: forked {ppid} -> {cpid}", debug_text) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
473 assert len(clines) == 1 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
474 reported_pids = {line.split(".")[0] for line in debug_text.splitlines()} 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
475 assert len(reported_pids) == 2 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
477 @pytest.mark.skipif(not hasattr(os, "fork"), reason="Can't test os.fork, it doesn't exist.") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
478 @pytest.mark.parametrize("patch", [False, True]) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
479 def test_os_exit(self, patch: bool) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
480 self.make_file( 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
481 "forky.py",
482 """\
483 import os
484 import tempfile
485 import time
487 complete_file = tempfile.mkstemp()[1]
488 pid = os.fork()
489 if pid:
490 while True:
491 with open(complete_file, encoding="ascii") as f:
492 data = f.read()
493 if "Complete" in data:
494 break
495 time.sleep(.02)
496 os.remove(complete_file)
497 else:
498 time.sleep(.1)
499 with open(complete_file, mode="w", encoding="ascii") as f:
500 f.write("Complete")
501 os._exit(0)
502 # if this exists, then we broke os._exit completely
503 open("impossible.txt", mode="w")
504 """,
505 )
506 total_lines = 17 # don't count the last impossible.txt line 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
507 if patch: 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
508 self.make_file(".coveragerc", "[run]\npatch = _exit\n") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
509 self.run_command("coverage run -p forky.py") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
510 self.run_command("coverage combine") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
511 data = coverage.CoverageData() 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
512 data.read() 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
513 seen = line_counts(data)["forky.py"] 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
514 if patch: 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
515 assert seen == total_lines 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
516 else:
517 assert seen < total_lines 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
518 self.assert_doesnt_exist("impossible.txt") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
520 def test_warnings_during_reporting(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
521 # While fixing issue #224, the warnings were being printed far too
522 # often. Make sure they're not any more.
523 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
524 "hello.py",
525 """\
526 import sys, os, the_other
527 print("Hello")
528 """,
529 )
530 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
531 "the_other.py",
532 """\
533 print("What?")
534 """,
535 )
536 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
537 ".coveragerc",
538 """\
539 [run]
540 source =
541 .
542 xyzzy
543 """,
544 )
546 self.run_command("coverage run hello.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
547 out = self.run_command("coverage html") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
548 assert out.count("Module xyzzy was never imported.") == 0 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
550 def test_warns_if_never_run(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
551 # Note: the name of the function can't have "warning" in it, or the
552 # absolute path of the file will have "warning" in it, and an assertion
553 # will fail.
554 out = self.run_command("coverage run i_dont_exist.py", status=1) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
555 path = python_reported_file("i_dont_exist.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
556 assert f"No file to run: '{path}'" in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
557 assert "warning" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
558 assert "Exception" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
560 out = self.run_command("coverage run -m no_such_module", status=1) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
561 assert ("No module named no_such_module" in out) or ( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
562 "No module named 'no_such_module'" in out
563 )
564 assert "warning" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
565 assert "Exception" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
567 @pytest.mark.skipif(env.METACOV, reason="Can't test tracers changing during metacoverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
568 def test_warnings_trace_function_changed_with_threads(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
569 # https://github.com/coveragepy/coveragepy/issues/164
571 self.make_file(
572 "bug164.py",
573 """\
574 import threading
575 import time
577 class MyThread (threading.Thread):
578 def run(self):
579 print("Hello")
581 thr = MyThread()
582 thr.start()
583 thr.join()
584 """,
585 )
586 out = self.run_command("coverage run --timid bug164.py")
588 assert "Hello\n" in out
589 assert "warning" not in out
591 @pytest.mark.skipif(env.METACOV, reason="Can't test tracers changing during metacoverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
592 def test_warning_trace_function_changed(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
593 self.make_file(
594 "settrace.py",
595 """\
596 import sys
597 print("Hello")
598 sys.settrace(None)
599 print("Goodbye")
600 """,
601 )
602 out = self.run_command("coverage run --timid settrace.py")
603 assert "Hello\n" in out
604 assert "Goodbye\n" in out
606 assert "Trace function changed" in out
608 # When meta-coverage testing, this test doesn't work, because it finds
609 # coverage.py's own trace function.
610 @pytest.mark.skipif(env.METACOV, reason="Can't test timid during coverage measurement.") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
611 def test_timid(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
612 # Test that the --timid command line argument properly swaps the tracer
613 # function for a simpler one.
614 #
615 # This is complicated by the fact that the tests are run twice for each
616 # version: once with a compiled C-based trace function, and once without
617 # it, to also test the Python trace function. So this test has to examine
618 # an environment variable set in igor.py to know whether to expect to see
619 # the C trace function or not.
621 self.make_file(
622 "showtrace.py",
623 """\
624 # Show the current frame's trace function, so that we can test what the
625 # command-line options do to the trace function used.
627 import inspect
629 # Show what the trace function is. If a C-based function is used, then f_trace
630 # may be None.
631 trace_fn = inspect.currentframe().f_trace
632 if trace_fn is None:
633 trace_name = "None"
634 else:
635 # Get the name of the tracer class.
636 try:
637 trace_name = trace_fn.__self__.__class__.__name__
638 except AttributeError:
639 # A C-based function could also manifest as an f_trace value
640 # which doesn't have __self__.
641 trace_name = trace_fn.__class__.__name__
643 print(trace_name)
644 """,
645 )
647 # When running without coverage, no trace function
648 py_out = self.run_command("python showtrace.py")
649 assert py_out == "None\n"
651 cov_out = self.run_command("coverage run showtrace.py")
652 if testenv.C_TRACER:
653 # If the C trace function is being tested, then regular running should have
654 # the C function, which registers itself as f_trace.
655 assert cov_out == "CTracer\n"
656 elif testenv.SYS_MON:
657 assert cov_out == "None\n"
658 else:
659 # If the Python trace function is being tested, then regular running will
660 # also show the Python function.
661 assert cov_out == "PyTracer\n"
663 # When running timidly, the trace function is always Python.
664 timid_out = self.run_command("coverage run --timid showtrace.py")
665 assert timid_out == "PyTracer\n"
667 def test_warn_preimported(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
668 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
669 "hello.py",
670 """\
671 import goodbye
672 import coverage
673 cov = coverage.Coverage(include=["good*"], check_preimported=True)
674 cov.start()
675 print(goodbye.f())
676 cov.stop()
677 """,
678 )
679 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
680 "goodbye.py",
681 """\
682 def f():
683 return "Goodbye!"
684 """,
685 )
686 goodbye_path = os.path.abspath("goodbye.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
688 out = self.run_command("python hello.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
689 assert "Goodbye!" in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
691 msg = ( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
692 f"CoverageWarning: Already imported a file that will be measured: {goodbye_path} "
693 + "(already-imported)"
694 )
695 assert msg in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
697 # Pypy passes locally, but fails in CI? Perhaps the version of macOS is
698 # significant? https://foss.heptapod.net/pypy/pypy/-/issues/3074
699 @pytest.mark.skipif(env.PYPY, reason="PyPy is unreliable with this test") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
700 def test_lang_c(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
701 # LANG=C forces getfilesystemencoding on Linux to 'ascii', which causes
702 # failures with non-ascii file names. We don't want to make a real file
703 # with strange characters, though, because that gets the test runners
704 # tangled up. This will isolate the concerns to the coverage.py code.
705 # https://github.com/coveragepy/coveragepy/issues/533
706 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
707 "weird_file.py",
708 r"""
709 globs = {}
710 code = "a = 1\nb = 2\n"
711 exec(compile(code, "wut\xe9\xea\xeb\xec\x01\x02.py", 'exec'), globs)
712 print(globs['a'])
713 print(globs['b'])
714 """,
715 )
716 self.set_environ("LANG", "C") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
717 out = self.run_command("coverage run weird_file.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
718 assert out == "1\n2\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
720 def test_deprecation_warnings(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
721 # Test that coverage doesn't trigger deprecation warnings.
722 # https://github.com/coveragepy/coveragepy/issues/305
723 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
724 "allok.py",
725 """\
726 import warnings
727 warnings.simplefilter('default')
728 import coverage
729 print("No warnings!")
730 """,
731 )
733 # Some of our testing infrastructure can issue warnings.
734 # Turn it all off for the subprocess.
735 self.del_environ("COVERAGE_TESTING") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
737 out = self.run_command("python allok.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
738 assert out == "No warnings!\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
740 def test_run_twice(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
741 # https://github.com/coveragepy/coveragepy/issues/353
742 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
743 "foo.py",
744 """\
745 def foo():
746 pass
747 """,
748 )
749 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
750 "run_twice.py",
751 """\
752 import sys
753 import coverage
755 for i in [1, 2]:
756 sys.stderr.write(f"Run {i}\\n")
757 inst = coverage.Coverage(source=['foo'])
758 inst.load()
759 inst.start()
760 import foo
761 inst.stop()
762 inst.save()
763 """,
764 )
765 out = self.run_command("python run_twice.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
766 # Remove the file location and source line from the warning.
767 out = re.sub(r"(?m)^[\\/\w.:~_-]+:\d+: CoverageWarning: ", "f:d: CoverageWarning: ", out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
768 out = re.sub(r"(?m)^\s+self.warn.*$\n", "", out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
769 out = re.sub(r"; see https://.*$", "", out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
770 expected = ( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
771 "Run 1\n"
772 + "Run 2\n"
773 + "f:d: CoverageWarning: Module foo was previously imported, but not measured "
774 + "(module-not-measured)\n"
775 )
776 assert out == expected 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
778 def test_module_name(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
779 # https://github.com/coveragepy/coveragepy/issues/478
780 # Make sure help doesn't show a silly command name when run as a
781 # module, like it used to:
782 # $ python -m coverage
783 # Code coverage for Python. Use '__main__.py help' for help.
784 out = self.run_command("python -m coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
785 assert "Use 'coverage help' for help" in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
787 @pytest.mark.skipif(env.WINDOWS, reason="This test is not for Windows") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
788 def test_save_signal_usr1(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
789 self.assert_doesnt_exist(".coverage") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
790 self.make_file( 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
791 "dummy_hello.py",
792 """\
793 import os
794 import signal
796 print(f"Sending SIGUSR1 to myself")
797 os.kill(os.getpid(), signal.SIGUSR1)
798 os.kill(os.getpid(), signal.SIGKILL)
800 print("Done and goodbye")
801 """,
802 )
803 out = self.run_command( 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
804 "coverage run --save-signal=USR1 dummy_hello.py",
805 status=-signal.SIGKILL,
806 )
807 # `startswith` because on Linux it also prints "Killed"
808 assert out.startswith("Sending SIGUSR1 to myself\nSaving coverage data...\n") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
809 self.assert_exists(".coverage") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
810 out = self.run_command("coverage report -m") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
811 assert out == textwrap.dedent("""\ 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
812 Name Stmts Miss Cover Missing
813 ----------------------------------------------
814 dummy_hello.py 6 2 67% 6-8
815 ----------------------------------------------
816 TOTAL 6 2 67%
817 """)
820TRY_EXECFILE = os.path.join(os.path.dirname(__file__), "modules/process_test/try_execfile.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
823class EnvironmentTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
824 """Tests using try_execfile.py to test the execution environment."""
826 def assert_tryexecfile_output(self, expected: str, actual: str) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
827 """Assert that the output we got is a successful run of try_execfile.py.
829 `expected` and `actual` must be the same.
831 """
832 # First, is this even credible try_execfile.py output?
833 assert '"DATA": "xyzzy"' in actual 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
834 # If this fails, "+" is actual, and "-" is expected
835 assert actual == expected 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
837 def test_coverage_run_is_like_python(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
838 with open(TRY_EXECFILE, encoding="utf-8") as f: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
839 self.make_file("run_me.py", f.read()) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
840 expected = self.run_command("python run_me.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
841 actual = self.run_command("coverage run run_me.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
842 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
844 def test_coverage_run_far_away_is_like_python(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
845 with open(TRY_EXECFILE, encoding="utf-8") as f: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
846 self.make_file("sub/overthere/prog.py", f.read()) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
847 expected = self.run_command("python sub/overthere/prog.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
848 actual = self.run_command("coverage run sub/overthere/prog.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
849 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
851 @pytest.mark.skipif(not env.WINDOWS, reason="This is about Windows paths") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
852 def test_coverage_run_far_away_is_like_python_windows(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
853 with open(TRY_EXECFILE, encoding="utf-8") as f: 1'(UVWXYZ0123456789!#$%)
854 self.make_file("sub/overthere/prog.py", f.read()) 1'(UVWXYZ0123456789!#$%)
855 expected = self.run_command("python sub\\overthere\\prog.py") 1'(UVWXYZ0123456789!#$%)
856 actual = self.run_command("coverage run sub\\overthere\\prog.py") 1'(UVWXYZ0123456789!#$%)
857 self.assert_tryexecfile_output(expected, actual) 1'(UVWXYZ0123456789!#$%)
859 def test_coverage_run_dashm_is_like_python_dashm(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
860 self.add_test_modules_to_pythonpath() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
861 expected = self.run_command("python -m process_test.try_execfile") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
862 actual = self.run_command("coverage run -m process_test.try_execfile") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
863 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
865 def test_coverage_run_dir_is_like_python_dir(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
866 with open(TRY_EXECFILE, encoding="utf-8") as f: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
867 self.make_file("with_main/__main__.py", f.read()) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
869 expected = self.run_command("python with_main") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
870 actual = self.run_command("coverage run with_main") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
871 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
873 def test_coverage_run_dashm_dir_no_init_is_like_python(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
874 with open(TRY_EXECFILE, encoding="utf-8") as f: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
875 self.make_file("with_main/__main__.py", f.read()) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
877 expected = self.run_command("python -m with_main") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
878 actual = self.run_command("coverage run -m with_main") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
879 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
881 def test_coverage_run_dashm_dir_with_init_is_like_python(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
882 with open(TRY_EXECFILE, encoding="utf-8") as f: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
883 self.make_file("with_main/__main__.py", f.read()) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
884 self.make_file("with_main/__init__.py", "") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
886 expected = self.run_command("python -m with_main") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
887 actual = self.run_command("coverage run -m with_main") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
888 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
890 def test_coverage_run_dashm_equal_to_doubledashsource(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
891 """regression test for #328
893 When imported by -m, a module's __name__ is __main__, but we need the
894 --source machinery to know and respect the original name.
895 """
896 self.add_test_modules_to_pythonpath() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
897 expected = self.run_command("python -m process_test.try_execfile") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
898 actual = self.run_command( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
899 "coverage run --source process_test.try_execfile -m process_test.try_execfile",
900 )
901 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
903 def test_coverage_run_dashm_superset_of_doubledashsource(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
904 """Edge case: --source foo -m foo.bar"""
905 # Ugh: without this config file, we'll get a warning about
906 # CoverageWarning: Module process_test was previously imported,
907 # but not measured (module-not-measured)
908 #
909 # This is because process_test/__init__.py is imported while looking
910 # for process_test.try_execfile. That import happens while setting
911 # sys.path before start() is called.
912 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
913 ".coveragerc",
914 """\
915 [run]
916 disable_warnings = module-not-measured
917 """,
918 )
919 self.add_test_modules_to_pythonpath() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
920 expected = self.run_command("python -m process_test.try_execfile") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
921 actual = self.run_command( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
922 "coverage run --source process_test -m process_test.try_execfile",
923 )
924 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
926 st, out = self.run_command_status("coverage report") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
927 assert st == 0 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
928 assert self.line_count(out) == 6, out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
930 def test_coverage_run_script_imports_doubledashsource(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
931 # This file imports try_execfile, which compiles it to .pyc, so the
932 # first run will have __file__ == "try_execfile.py" and the second will
933 # have __file__ == "try_execfile.pyc", which throws off the comparison.
934 # Setting dont_write_bytecode True stops the compilation to .pyc and
935 # keeps the test working.
936 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
937 "myscript",
938 """\
939 import sys; sys.dont_write_bytecode = True
940 import process_test.try_execfile
941 """,
942 )
944 self.add_test_modules_to_pythonpath() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
945 expected = self.run_command("python myscript") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
946 actual = self.run_command("coverage run --source process_test myscript") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
947 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
949 st, out = self.run_command_status("coverage report") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
950 assert st == 0 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
951 assert self.line_count(out) == 6, out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
953 def test_coverage_run_dashm_is_like_python_dashm_off_path(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
954 # https://github.com/coveragepy/coveragepy/issues/242
955 self.make_file("sub/__init__.py", "") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
956 with open(TRY_EXECFILE, encoding="utf-8") as f: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
957 self.make_file("sub/run_me.py", f.read()) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
959 expected = self.run_command("python -m sub.run_me") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
960 actual = self.run_command("coverage run -m sub.run_me") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
961 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
963 def test_coverage_run_dashm_is_like_python_dashm_with__main__207(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
964 # https://github.com/coveragepy/coveragepy/issues/207
965 self.make_file("package/__init__.py", "print('init')") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
966 self.make_file("package/__main__.py", "print('main')") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
967 expected = self.run_command("python -m package") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
968 actual = self.run_command("coverage run -m package") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
969 assert expected == actual 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
971 def test_coverage_zip_is_like_python(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
972 # Test running coverage from a zip file itself. Some environments
973 # (windows?) zip up the coverage main to be used as the coverage
974 # command.
975 with open(TRY_EXECFILE, encoding="utf-8") as f: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
976 self.make_file("run_me.py", f.read()) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
977 expected = self.run_command("python run_me.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
978 cov_main = os.path.join(TESTS_DIR, "covmain.zip") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
979 actual = self.run_command(f"python {cov_main} run run_me.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
980 self.assert_tryexecfile_output(expected, actual) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
982 @pytest.mark.skipif(env.PYVERSION < (3, 11), reason="PYTHONSAFEPATH is new in 3.11") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
983 @pytest.mark.skipif( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
984 env.WINDOWS,
985 reason="Windows gets this wrong: https://github.com/python/cpython/issues/131484",
986 )
987 def test_pythonsafepath(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
988 with open(TRY_EXECFILE, encoding="utf-8") as f: 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO
989 self.make_file("run_me.py", f.read()) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO
990 self.set_environ("PYTHONSAFEPATH", "1") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO
991 expected = self.run_command("python run_me.py") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO
992 actual = self.run_command("coverage run run_me.py") 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO
993 self.assert_tryexecfile_output(expected, actual) 1abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO
995 @pytest.mark.skipif(env.PYVERSION < (3, 11), reason="PYTHONSAFEPATH is new in 3.11") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
996 def test_pythonsafepath_dashm_runme(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
997 with open(TRY_EXECFILE, encoding="utf-8") as f: 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
998 self.make_file("run_me.py", f.read()) 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
999 self.set_environ("PYTHONSAFEPATH", "1") 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1000 expected = self.run_command("python run_me.py") 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1001 actual = self.run_command("python -m coverage run run_me.py") 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1002 self.assert_tryexecfile_output(expected, actual) 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1004 @pytest.mark.skipif(env.PYVERSION < (3, 11), reason="PYTHONSAFEPATH is new in 3.11") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1005 def test_pythonsafepath_dashm(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1006 with open(TRY_EXECFILE, encoding="utf-8") as f: 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1007 self.make_file("with_main/__main__.py", f.read()) 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1009 self.set_environ("PYTHONSAFEPATH", "1") 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1010 expected = self.run_command("python -m with_main", status=1) 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1011 actual = self.run_command("coverage run -m with_main", status=1) 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1012 assert re.search("No module named '?with_main'?", actual) 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1013 assert re.search("No module named '?with_main'?", expected) 1abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%O)
1015 def test_coverage_custom_script(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1016 # https://github.com/coveragepy/coveragepy/issues/678
1017 # If sys.path[0] isn't the Python default, then coverage.py won't
1018 # fiddle with it.
1019 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1020 "a/b/c/thing.py",
1021 """\
1022 SOMETHING = "hello-xyzzy"
1023 """,
1024 )
1025 abc = os.path.abspath("a/b/c") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1026 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1027 "run_coverage.py",
1028 f"""\
1029 import sys
1030 sys.path[0:0] = [
1031 r'{abc}',
1032 '/Users/somebody/temp/something/eggs/something-4.5.1-py2.7-xxx-10.13-x86_64.egg',
1033 ]
1035 import coverage.cmdline
1037 if __name__ == '__main__':
1038 sys.exit(coverage.cmdline.main())
1039 """,
1040 )
1041 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1042 "how_is_it.py",
1043 """\
1044 import pprint, sys
1045 pprint.pprint(sys.path)
1046 import thing
1047 print(thing.SOMETHING)
1048 """,
1049 )
1050 # If this test fails, it will be with "can't import thing".
1051 out = self.run_command("python run_coverage.py run how_is_it.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1052 assert "hello-xyzzy" in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1054 out = self.run_command("python -m run_coverage run how_is_it.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1055 assert "hello-xyzzy" in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1057 @pytest.mark.skipif(env.WINDOWS, reason="Windows can't make symlinks") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1058 @pytest.mark.skipif( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1059 platform.python_version().endswith("+"),
1060 reason="setuptools barfs on dev versions: https://github.com/pypa/packaging/issues/678",
1061 # https://github.com/coveragepy/coveragepy/issues/1556
1062 )
1063 def test_bug_862(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1064 # This used to simulate how pyenv and pyenv-virtualenv create the
1065 # coverage executable. Now the code shows how venv does it.
1066 self.make_file( 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1067 "elsewhere/bin/fake-coverage",
1068 f"""\
1069 #!{sys.executable}
1070 import re
1071 import sys
1072 from coverage.cmdline import main
1073 if __name__ == '__main__':
1074 sys.argv[0] = re.sub(r'(-script\\.pyw|\\.exe)?$', '', sys.argv[0])
1075 sys.exit(main())
1076 """,
1077 )
1078 os.chmod("elsewhere/bin/fake-coverage", stat.S_IREAD | stat.S_IEXEC) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1079 os.symlink("elsewhere", "somewhere") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1080 self.make_file("foo.py", "print('inside foo')") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1081 self.make_file("bar.py", "import foo") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1082 out = self.run_command("somewhere/bin/fake-coverage run bar.py") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1083 assert "inside foo\n" == out 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1085 def test_bug_909(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1086 # https://github.com/coveragepy/coveragepy/issues/909
1087 # The __init__ files were being imported before measurement started,
1088 # so the line in __init__.py was being marked as missed, and there were
1089 # warnings about measured files being imported before start.
1090 self.make_file("proj/__init__.py", "print('Init')") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1091 self.make_file("proj/thecode.py", "print('The code')") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1092 self.make_file("proj/tests/__init__.py", "") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1093 self.make_file("proj/tests/test_it.py", "import proj.thecode") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1095 expected = "Init\nThe code\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1096 actual = self.run_command("coverage run --source=proj -m proj.tests.test_it") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1097 assert expected == actual 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1099 report = self.run_command("coverage report -m") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1101 # Name Stmts Miss Cover Missing
1102 # ------------------------------------------------------
1103 # proj/__init__.py 1 0 100%
1104 # proj/tests/__init__.py 0 0 100%
1105 # proj/tests/test_it.py 1 0 100%
1106 # proj/thecode.py 1 0 100%
1107 # ------------------------------------------------------
1108 # TOTAL 3 0 100%
1110 squeezed = self.squeezed_lines(report) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1111 assert squeezed[2].replace("\\", "/") == "proj/__init__.py 1 0 100%" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1114class ExcepthookTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1115 """Tests of sys.excepthook support."""
1117 # TODO: do we need these as process tests if we have test_execfile.py:RunFileTest?
1119 def test_excepthook(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1120 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1121 "excepthook.py",
1122 """\
1123 import sys
1125 def excepthook(*args):
1126 print('in excepthook')
1127 if maybe == 2:
1128 print('definitely')
1130 sys.excepthook = excepthook
1132 maybe = 1
1133 raise RuntimeError('Error Outside')
1134 """,
1135 )
1136 cov_st, cov_out = self.run_command_status("coverage run excepthook.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1137 py_st, py_out = self.run_command_status("python excepthook.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1138 assert cov_st == py_st 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1139 assert cov_st == 1 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1140 assert "in excepthook" in py_out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1141 assert cov_out == py_out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1143 # Read the coverage file and see that excepthook.py has 7 lines
1144 # executed.
1145 data = coverage.CoverageData() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1146 data.read() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1147 print(f"{line_counts(data) = }") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1148 print(f"{data = }") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1149 print("data.lines excepthook.py:", data.lines(os.path.abspath("excepthook.py"))) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1150 assert line_counts(data)["excepthook.py"] == 7 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1152 @pytest.mark.skipif( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1153 not env.CPYTHON,
1154 reason="non-CPython handles excepthook exits differently, punt for now.",
1155 )
1156 def test_excepthook_exit(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1157 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1158 "excepthook_exit.py",
1159 """\
1160 import sys
1162 def excepthook(*args):
1163 print('in excepthook')
1164 sys.exit(0)
1166 sys.excepthook = excepthook
1168 raise RuntimeError('Error Outside')
1169 """,
1170 )
1171 cov_st, cov_out = self.run_command_status("coverage run excepthook_exit.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1172 py_st, py_out = self.run_command_status("python excepthook_exit.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1173 assert cov_st == py_st 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1174 assert cov_st == 0 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1176 assert py_out == "in excepthook\n" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1177 assert cov_out == py_out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1179 @pytest.mark.skipif(env.PYPY, reason="PyPy handles excepthook throws differently.") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1180 def test_excepthook_throw(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1181 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1182 "excepthook_throw.py",
1183 """\
1184 import sys
1186 def excepthook(*args):
1187 # Write this message to stderr so that we don't have to deal
1188 # with interleaved stdout/stderr comparisons in the assertions
1189 # in the test.
1190 sys.stderr.write('in excepthook\\n')
1191 raise RuntimeError('Error Inside')
1193 sys.excepthook = excepthook
1195 raise RuntimeError('Error Outside')
1196 """,
1197 )
1198 cov_out = self.run_command("coverage run excepthook_throw.py", status=1) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1199 py_out = self.run_command("python excepthook_throw.py", status=1) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1200 assert "in excepthook" in py_out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1201 assert cov_out == py_out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%
1204class AliasedCommandTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1205 """Tests of the version-specific command aliases."""
1207 run_in_temp_dir = False 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1209 def test_major_version_works(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1210 # "coverage3" works on py3
1211 cmd = "coverage%d" % sys.version_info[0] 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1212 out = self.run_command(cmd) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1213 assert "Code coverage for Python" in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1215 def test_wrong_alias_doesnt_work(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1216 # "coverage2" doesn't work on py3
1217 assert sys.version_info[0] == 3 # Let us know when Python 4 is out... 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1218 badcmd = "coverage2" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1219 status, out = self.run_command_status(badcmd) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1220 assert "Code coverage for Python" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1221 assert status != 0 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1223 def test_specific_alias_works(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1224 # "coverage-3.9" works on py3.9
1225 cmd = "coverage-%d.%d" % sys.version_info[:2] 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1226 out = self.run_command(cmd) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1227 assert "Code coverage for Python" in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1229 @pytest.mark.parametrize( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1230 "cmd",
1231 [
1232 "coverage",
1233 "coverage%d" % sys.version_info[0],
1234 "coverage-%d.%d" % sys.version_info[:2],
1235 ],
1236 )
1237 def test_aliases_used_in_messages(self, cmd: str) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1238 out = self.run_command(f"{cmd} foobar", status=1) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1239 assert "Unknown command: 'foobar'" in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1240 assert f"Use '{cmd} help' for help" in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1243class PydocTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1244 """Test that pydoc can get our information."""
1246 run_in_temp_dir = False 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1248 def assert_pydoc_ok(self, name: str, thing: Any) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1249 """Check that pydoc of `name` finds the docstring from `thing`."""
1250 # Run pydoc.
1251 out = self.run_command("python -m pydoc " + name) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1252 # It should say "Help on..", and not have a traceback
1253 assert out.startswith("Help on ") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1254 assert "Traceback" not in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1256 # All of the lines in the docstring should be there somewhere.
1257 for line in thing.__doc__.splitlines(): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1258 assert line.strip() in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1260 def test_pydoc_coverage(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1261 self.assert_pydoc_ok("coverage", coverage) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1263 def test_pydoc_coverage_coverage(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1264 self.assert_pydoc_ok("coverage.Coverage", coverage.Coverage) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1267class FailUnderTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1268 """Tests of the --fail-under switch."""
1270 def setUp(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1271 super().setUp() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1272 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1273 "forty_two_plus.py",
1274 """\
1275 # I have 42.857% (3/7) coverage!
1276 a = 1
1277 b = 2
1278 if a > 3:
1279 b = 4
1280 c = 5
1281 d = 6
1282 e = 7
1283 """,
1284 )
1285 self.make_data_file(lines={abs_file("forty_two_plus.py"): [2, 3, 4]}) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1287 def test_report_43_is_ok(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1288 st, out = self.run_command_status("coverage report --fail-under=43") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1289 assert st == 0 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1290 assert self.last_line_squeezed(out) == "TOTAL 7 4 43%" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1292 def test_report_43_is_not_ok(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1293 st, out = self.run_command_status("coverage report --fail-under=44") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1294 assert st == 2 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1295 expected = "Coverage failure: total of 43 is less than fail-under=44" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1296 assert expected == self.last_line_squeezed(out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1298 def test_report_42p86_is_not_ok(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1299 self.make_file(".coveragerc", "[report]\nprecision = 2") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1300 st, out = self.run_command_status("coverage report --fail-under=42.88") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1301 assert st == 2 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1302 expected = "Coverage failure: total of 42.86 is less than fail-under=42.88" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1303 assert expected == self.last_line_squeezed(out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1305 def test_report_99p9_is_not_ok(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1306 # A file with 99.9% coverage:
1307 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1308 "ninety_nine_plus.py",
1309 "a = 1\n" + "b = 2\n" * 2000 + "if a > 3:\n" + " c = 4\n",
1310 )
1311 self.make_data_file(lines={abs_file("ninety_nine_plus.py"): range(1, 2002)}) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1312 st, out = self.run_command_status("coverage report --fail-under=100") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1313 assert st == 2 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1314 expected = "Coverage failure: total of 99 is less than fail-under=100" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1315 assert expected == self.last_line_squeezed(out) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1318class FailUnderNoFilesTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1319 """Test that nothing to report results in an error exit status."""
1321 def test_report(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1322 self.make_file(".coveragerc", "[report]\nfail_under = 99\n") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1323 st, out = self.run_command_status("coverage report") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1324 assert "No data to report." in out 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1325 assert st == 1 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1328class FailUnderEmptyFilesTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1329 """Test that empty files produce the proper fail_under exit status."""
1331 def test_report(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1332 self.make_file(".coveragerc", "[report]\nfail_under = 99\n") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1333 self.make_file("empty.py", "") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1334 st, _ = self.run_command_status("coverage run empty.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1335 assert st == 0 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1336 st, _ = self.run_command_status("coverage report") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1337 # An empty file is marked as 100% covered, so this is ok.
1338 assert st == 0 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1341@pytest.mark.skipif(env.WINDOWS, reason="Windows can't delete the directory in use.") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1342class YankedDirectoryTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1343 """Tests of what happens when the current directory is deleted."""
1345 BUG_806 = """\ 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1346 import os
1347 import sys
1348 import tempfile
1350 tmpdir = tempfile.mkdtemp()
1351 os.chdir(tmpdir)
1352 os.rmdir(tmpdir)
1353 print(sys.argv[1])
1354 """
1356 def test_removing_directory(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1357 self.make_file("bug806.py", self.BUG_806) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1358 out = self.run_command("coverage run bug806.py noerror") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1359 assert out == "noerror\n" 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1361 def test_removing_directory_with_error(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1362 self.make_file("bug806.py", self.BUG_806) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1363 out = self.run_command("coverage run bug806.py", status=1) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1364 path = python_reported_file("bug806.py") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1365 # Python 3.11 adds an extra line to the traceback.
1366 # Check that the lines we expect are there.
1367 lines = textwrap.dedent(f"""\ 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1368 Traceback (most recent call last):
1369 File "{path}", line 8, in <module>
1370 print(sys.argv[1])
1371 IndexError: list index out of range
1372 """).splitlines(keepends=True)
1373 assert all(line in out for line in lines) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1376@pytest.mark.skipif(env.METACOV, reason="Can't test subprocess pth file during metacoverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1377@pytest.mark.xdist_group(name="needs_pth") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1378class ProcessStartupTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1379 """Test that we can measure coverage in subprocesses.""" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1381 def make_main_and_sub(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1382 """Create main.py and sub.py."""
1383 # Main will run sub.py
1384 self.make_file(
1385 "main.py",
1386 """\
1387 import os, os.path, sys
1388 ex = os.path.basename(sys.executable)
1389 os.system(ex + " sub.py")
1390 """,
1391 )
1392 # sub.py will write a few lines.
1393 self.make_file(
1394 "sub.py",
1395 """\
1396 with open("out.txt", "w", encoding="utf-8") as f:
1397 f.write("Hello, world!\\n")
1398 a = 3
1399 """,
1400 )
1402 def test_patch_subprocess(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1403 self.make_main_and_sub()
1404 self.make_file(
1405 ".coveragerc",
1406 """\
1407 [run]
1408 patch = subprocess
1409 """,
1410 )
1411 self.run_command("coverage run main.py")
1412 self.run_command("coverage combine")
1413 self.assert_exists(".coverage")
1414 data = coverage.CoverageData()
1415 data.read()
1416 assert line_counts(data)["main.py"] == 3
1417 assert line_counts(data)["sub.py"] == 3
1419 def test_subprocess_with_pth_files(self, _create_pth_file: None) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1420 # An existing data file should not be read when a subprocess gets
1421 # measured automatically. Create the data file here with bogus data in
1422 # it.
1423 self.make_main_and_sub()
1424 data = coverage.CoverageData(".mycovdata")
1425 data.add_lines({os.path.abspath("sub.py"): range(100)})
1426 data.write()
1428 self.make_file(
1429 "coverage.ini",
1430 """\
1431 [run]
1432 data_file = .mycovdata
1433 """,
1434 )
1435 self.set_environ("COVERAGE_PROCESS_START", "coverage.ini")
1436 import main # pylint: disable=unused-import, import-error
1438 with open("out.txt", encoding="utf-8") as f:
1439 assert f.read() == "Hello, world!\n"
1441 # Read the data from .coverage
1442 self.assert_exists(".mycovdata")
1443 data = coverage.CoverageData(".mycovdata")
1444 data.read()
1445 assert line_counts(data)["sub.py"] == 3
1447 def test_subprocess_with_pth_files_and_parallel(self, _create_pth_file: None) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1448 # https://github.com/coveragepy/coveragepy/issues/492
1449 self.make_main_and_sub()
1450 self.make_file(
1451 "coverage.ini",
1452 """\
1453 [run]
1454 parallel = true
1455 """,
1456 )
1458 self.set_environ("COVERAGE_PROCESS_START", "coverage.ini")
1459 self.run_command("coverage run main.py")
1461 with open("out.txt", encoding="utf-8") as f:
1462 assert f.read() == "Hello, world!\n"
1464 self.run_command("coverage combine")
1466 # assert that the combined .coverage data file is correct
1467 self.assert_exists(".coverage")
1468 data = coverage.CoverageData()
1469 data.read()
1470 assert line_counts(data)["sub.py"] == 3
1472 # assert that there are *no* extra data files left over after a combine
1473 data_files = glob.glob(os.getcwd() + "/.coverage*")
1474 msg = (
1475 "Expected only .coverage after combine, looks like there are "
1476 + f"extra data files that were not cleaned up: {data_files!r}"
1477 )
1478 assert len(data_files) == 1, msg
1480 def test_subprocess_in_directories(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1481 # Bug 2025: patch=subprocess didn't find data files from subdirectory
1482 # subprocesses.
1483 self.make_file(
1484 "main.py",
1485 """\
1486 import subprocess
1487 import sys
1488 print(subprocess.check_output(
1489 [sys.executable, "subproc.py"],
1490 cwd="subdir",
1491 encoding="utf-8",
1492 ))
1493 """,
1494 )
1495 self.make_file(
1496 "subdir/subproc.py",
1497 """\
1498 with open("readme.txt", encoding="utf-8") as f:
1499 print(f.read(), end="")
1500 """,
1501 )
1502 self.make_file(
1503 ".coveragerc",
1504 """\
1505 [run]
1506 patch = subprocess
1507 data_file = .covdata
1508 """,
1509 )
1510 self.make_file("subdir/readme.txt", "hello")
1511 out = self.run_command("coverage run main.py")
1512 assert out == "hello\n"
1513 self.run_command("coverage combine")
1514 data = coverage.CoverageData(".covdata")
1515 data.read()
1516 assert line_counts(data)["main.py"] == 6
1517 assert line_counts(data)["subproc.py"] == 2
1519 @pytest.mark.skipif( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1520 not testenv.CAN_MEASURE_BRANCHES, reason="Can't measure branches with this core" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1521 )
1522 def test_subprocess_gets_nonfile_config(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1523 # https://github.com/coveragepy/coveragepy/issues/2021
1524 self.make_file(
1525 "subfunctions.py",
1526 """\
1527 import subprocess, sys
1529 def f1():
1530 print("function 1")
1531 def f2():
1532 print("function 2")
1534 functions = [f1, f2]
1536 cases = sys.argv[1:]
1537 if len(cases) > 1:
1538 for c in cases:
1539 subprocess.call([sys.executable, __file__, c])
1540 else:
1541 functions[int(cases[0])]()
1542 """,
1543 )
1544 self.make_file(
1545 ".coveragerc",
1546 """\
1547 [run]
1548 patch = subprocess
1549 """,
1550 )
1551 out = self.run_command("coverage run --branch subfunctions.py 0 1")
1552 assert out.endswith("function 1\nfunction 2\n")
1553 self.run_command("coverage combine")
1554 data = coverage.CoverageData()
1555 data.read()
1556 assert line_counts(data)["subfunctions.py"] == 11
1558 def test_subprocess_dir_with_source(self) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1559 # https://github.com/coveragepy/coveragepy/issues/1499
1560 self.make_file("main/d/README", "A sub-directory")
1561 self.make_file(
1562 "main/main.py",
1563 """\
1564 import os, subprocess, sys
1565 orig = os.getcwd()
1566 os.chdir("./d")
1567 subprocess.run([sys.executable, f"{orig}/sub.py"])
1568 os.chdir(orig)
1569 """,
1570 )
1571 self.make_file("lib/other.py", "print('Other', flush=True)")
1572 self.make_file(
1573 "main/sub.py",
1574 """
1575 import other
1576 print("Hello, world!", flush=True)
1577 """,
1578 )
1579 self.make_file(
1580 "main/pyproject.toml",
1581 """\
1582 [tool.coverage.run]
1583 patch = ["subprocess"]
1584 source = [".", "other"]
1585 disable_warnings = ["module-not-imported"]
1586 """,
1587 )
1588 self.set_environ("PYTHONPATH", os.path.abspath("lib"))
1589 with change_dir("main"):
1590 out = self.run_command("coverage run main.py")
1591 assert out == "Other\nHello, world!\n"
1592 self.run_command("coverage combine")
1593 data = coverage.CoverageData()
1594 data.read()
1595 assert line_counts(data) == {"main.py": 5, "sub.py": 2, "other.py": 1}
1598@pytest.fixture 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1599def _clean_pth_files() -> Iterable[None]: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1600 """A fixture to clean up any .pth files we created during the test."""
1601 # The execv test needs to make .pth files so that subprocesses will get
1602 # measured. But there's no way for coverage to remove those files because
1603 # they need to exist when the new program starts, and there's no
1604 # information carried over to clean them automatically.
1605 #
1606 # So for these tests, we clean them as part of the test suite.
1607 pth_files: set[Path] = set() 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1608 for d in site.getsitepackages(): 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1609 pth_files.update(Path(d).glob("*.pth")) 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1611 try: 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1612 yield 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1613 finally:
1614 for d in site.getsitepackages(): 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1615 for pth in Path(d).glob("*.pth"): 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1616 if pth not in pth_files: 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1617 pth.unlink() 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1620@pytest.mark.skipif(env.WINDOWS, reason="patch=execv isn't supported on Windows") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1621@pytest.mark.xdist_group(name="needs_pth") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1622class ExecvTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1623 """Test that we can measure coverage in subprocesses."""
1625 @pytest.mark.parametrize( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1626 "fname",
1627 [
1628 base + suffix
1629 for base, suffix in itertools.product(
1630 ["exec", "spawn"],
1631 ["l", "le", "lp", "lpe", "v", "ve", "vp", "vpe"],
1632 )
1633 ],
1634 )
1635 def test_execv_patch(self, fname: str, _clean_pth_files: None) -> None: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1636 self.make_file( 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1637 ".coveragerc",
1638 """\
1639 [run]
1640 patch = subprocess, execv
1641 """,
1642 )
1643 self.make_file( 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1644 "main.py",
1645 f"""\
1646 import os, sys
1647 print("In main")
1648 args = []
1649 if "spawn" in {fname!r}:
1650 args.append(os.P_WAIT)
1651 args.append(sys.executable)
1652 prog_args = ["python", {os.path.abspath("other.py")!r}, "cat", "dog"]
1653 if "l" in {fname!r}:
1654 args.extend(prog_args)
1655 else:
1656 args.append(prog_args)
1657 if {fname!r}.endswith("e"):
1658 args.append({{"SUBVAR": "the-sub-var"}})
1659 os.environ["MAINVAR"] = "the-main-var"
1660 sys.stdout.flush()
1661 os.{fname}(*args)
1662 """,
1663 )
1664 self.make_file( 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1665 "other.py",
1666 """\
1667 import os, sys
1668 print(f"MAINVAR = {os.getenv('MAINVAR', 'none')}")
1669 print(f"SUBVAR = {os.getenv('SUBVAR', 'none')}")
1670 print(f"{sys.argv[1:] = }")
1671 """,
1672 )
1674 out = self.run_command("coverage run main.py") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1675 expected = "In main\n" 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1676 if fname.endswith("e"): 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1677 expected += "MAINVAR = none\n" 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1678 expected += "SUBVAR = the-sub-var\n" 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1679 else:
1680 expected += "MAINVAR = the-main-var\n" 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1681 expected += "SUBVAR = none\n" 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1682 expected += "sys.argv[1:] = ['cat', 'dog']\n" 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1683 assert out == expected 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1685 self.run_command("coverage combine") 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1686 data = coverage.CoverageData() 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1687 data.read() 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1689 main_lines = 12 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1690 if "spawn" in fname: 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1691 main_lines += 1 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1692 if fname.endswith("e"): 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1693 main_lines += 1 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1695 assert line_counts(data)["main.py"] == main_lines 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1696 assert line_counts(data)["other.py"] == 4 1PQRSabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNTO
1699class ProcessStartupWithSourceTest(CoverageTest): 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1700 """Show that we can configure {[run]source} during process-level coverage.
1702 There are three interesting variables, for a total of eight tests:
1704 1. -m versus a simple script argument (for example, `python myscript`),
1706 2. filtering for the top-level (main.py) or second-level (sub.py)
1707 module, and
1709 3. whether the files are in a package or not.
1711 """
1713 @pytest.mark.parametrize("dashm", ["-m", ""]) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1714 @pytest.mark.parametrize("package", ["pkg", ""]) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1715 @pytest.mark.parametrize("source", ["main", "sub"]) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1716 @pytest.mark.xdist_group(name="needs_pth") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1717 def test_pth_and_source_work_together( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1718 self,
1719 dashm: str,
1720 package: str,
1721 source: str,
1722 _create_pth_file: None,
1723 ) -> None:
1724 """Run the test for a particular combination of factors.
1726 The arguments are all strings:
1728 * `dashm`: Either "" (run the program as a file) or "-m" (run the
1729 program as a module).
1731 * `package`: Either "" (put the source at the top level) or a
1732 package name to use to hold the source.
1734 * `source`: Either "main" or "sub", which file to use as the
1735 ``--source`` argument.
1737 """
1739 def fullname(modname: str) -> str: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1740 """What is the full module name for `modname` for this test?"""
1741 if package and dashm: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1742 return ".".join((package, modname)) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1743 else:
1744 return modname 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1746 def path(basename: str) -> str: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1747 """Where should `basename` be created for this test?"""
1748 return os.path.join(package, basename) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1750 # Main will run sub.py.
1751 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1752 path("main.py"),
1753 """\
1754 import %s
1755 a = 2
1756 b = 3
1757 """
1758 % fullname("sub"),
1759 )
1760 if package: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1761 self.make_file(path("__init__.py"), "") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1762 # sub.py will write a few lines.
1763 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1764 path("sub.py"),
1765 """\
1766 with open("out.txt", "w", encoding="utf-8") as f:
1767 f.write("Hello, world!")
1768 a = 3
1769 """,
1770 )
1771 self.make_file( 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1772 "coverage.ini",
1773 """\
1774 [run]
1775 source = %s
1776 """
1777 % fullname(source),
1778 )
1780 self.set_environ("COVERAGE_PROCESS_START", "coverage.ini") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1782 if dashm: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1783 cmd = "python -m %s" % fullname("main") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1784 else:
1785 cmd = "python %s" % path("main.py") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1787 self.run_command(cmd) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1789 with open("out.txt", encoding="utf-8") as f: 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1790 assert f.read() == "Hello, world!" 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1792 # Read the data from .coverage
1793 self.assert_exists(".coverage") 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1794 data = coverage.CoverageData() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1795 data.read() 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1796 summary = line_counts(data) 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1797 assert summary[source + ".py"] == 3 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)
1798 assert len(summary) == 1 1PQRS'(abcdUVefghWXijklYZmnop01qrstuv234wxyzAB567CDEFGH89!IJKLMN#$%TO)