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