Skip to content

Commit a2f248c

Browse files
committed
fix: stdlib might be through a symlink. #2115
1 parent bc52a22 commit a2f248c

4 files changed

Lines changed: 45 additions & 3 deletions

File tree

CHANGES.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@ upgrading your version of coverage.py.
2323
Unreleased
2424
----------
2525

26+
- Fix: when Python is installed via symlinks, for example with Homebrew, the
27+
standard library files could be incorrectly included in coverage reports.
28+
This is now fixed, closing `issue 2115`_.
29+
2630
- Fix: if a data file is created with no read permissions, the combine step
2731
would fail completely. Now a warning is issued and the file is skipped.
2832
Closes `issue 2117`_.
2933

34+
.. _issue 2115: https://github.com/coveragepy/coveragepy/issues/2115
3035
.. _issue 2117: https://github.com/coveragepy/coveragepy/issues/2117
3136

3237

coverage/files.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,14 +264,14 @@ def __init__(
264264
super().__init__(self.original_paths, name=name, caption=caption, debug=debug)
265265
self.paths = []
266266
for p in paths:
267-
ap = os.path.normcase(p)
267+
ap = abs_file(p)
268268
if ap != p and debug:
269269
debug(f" Normalized {p!r} to {ap!r}")
270270
self.paths.append(ap)
271271

272272
def match(self, fpath: str) -> bool: # pylint: disable=arguments-renamed
273273
"""Does `fpath` indicate a file in one of our trees?"""
274-
fpath = os.path.normcase(fpath)
274+
fpath = abs_file(fpath)
275275
for p in self.paths:
276276
if fpath.startswith(p):
277277
if fpath == p:

coverage/inorout.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ def add_sysconfig_paths(paths: set[str], path_names: list[str]) -> None:
129129
for scheme in scheme_names:
130130
config_paths = sysconfig.get_paths(scheme)
131131
for path_name in path_names:
132-
paths.add(config_paths[path_name])
132+
if path_name in config_paths:
133+
paths.add(config_paths[path_name])
133134

134135

135136
def add_stdlib_paths(paths: set[str]) -> None:

tests/test_api.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
from collections.abc import Callable, Iterable
1919
from typing import cast
20+
from unittest import mock
2021

2122
import pytest
2223

@@ -184,6 +185,41 @@ def test_include_can_measure_stdlib(self) -> None:
184185
_, statements, missing, _ = cov1.analysis("random.py")
185186
assert statements == missing
186187

188+
def test_stdlib_symlink(self) -> None:
189+
# Find the stdlib and make a symlink to it.
190+
import colorsys
191+
192+
stdlib_dir = os.path.dirname(colorsys.__file__)
193+
os.symlink(stdlib_dir, "myliblink")
194+
195+
sys.path.insert(0, os.path.abspath("myliblink"))
196+
del sys.modules["colorsys"]
197+
198+
class FakeSysconfig:
199+
"""A fake for the sysconfig module."""
200+
201+
def get_scheme_names(self) -> list[str]: # pylint: disable=missing-function-docstring
202+
return ["xyzzy"]
203+
204+
def get_paths(self, _: str) -> dict[str, str]: # pylint: disable=missing-function-docstring
205+
return {
206+
"stdlib": os.path.abspath("myliblink"),
207+
}
208+
209+
self.make_file(
210+
"mymain.py",
211+
"""\
212+
import colorsys
213+
hls = colorsys.rgb_to_hls(1, 0, 0)
214+
""",
215+
)
216+
217+
with mock.patch.object(coverage.inorout, "sysconfig", FakeSysconfig()):
218+
cov = coverage.Coverage()
219+
self.start_import_stop(cov, "mymain")
220+
files = cov.get_data().measured_files()
221+
assert not any(os.path.basename(f) == "colorsys.py" for f in files)
222+
187223
def test_exclude_list(self) -> None:
188224
cov = coverage.Coverage()
189225
cov.clear_exclude()

0 commit comments

Comments
 (0)