Skip to content

Commit 66d6910

Browse files
committed
fix: make source paths absolute where they exist. #1499
1 parent bb3382f commit 66d6910

File tree

3 files changed

+65
-8
lines changed

3 files changed

+65
-8
lines changed

CHANGES.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ upgrading your version of coverage.py.
2323
Unreleased
2424
----------
2525

26-
Nothing yet.
26+
- Fix: ``source`` directories were not properly communicated to subprocesses
27+
that ran in different directories, as reported in `issue 1499`_. This is now
28+
fixed.
29+
30+
.. _issue 1499: https://github.com/nedbat/coveragepy/issues/1499
2731

2832

2933
.. start-releases

coverage/config.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -274,10 +274,15 @@ def __init__(self) -> None:
274274
"patch",
275275
}
276276

277+
# File paths to make absolute during serialization.
278+
# The pairs are (config_key, must_exist).
277279
SERIALIZE_ABSPATH = {
278-
"data_file",
279-
"debug_file",
280-
"source_dirs",
280+
("data_file", False),
281+
("debug_file", False),
282+
# `source` can be directories or modules, so don't abspath it if it
283+
# doesn't exist.
284+
("source", True),
285+
("source_dirs", False),
281286
}
282287

283288
def from_args(self, **kwargs: TConfigValueIn) -> None:
@@ -569,12 +574,13 @@ def serialize(self) -> str:
569574
deserialized config will refer to the same files.
570575
"""
571576
data = {k: v for k, v in self.__dict__.items() if not k.startswith("_")}
572-
for k in self.SERIALIZE_ABSPATH:
577+
for k, must_exist in self.SERIALIZE_ABSPATH:
578+
abs_fn = abs_path_if_exists if must_exist else os.path.abspath
573579
v = data[k]
574580
if isinstance(v, list):
575-
v = list(map(os.path.abspath, v))
581+
v = list(map(abs_fn, v))
576582
elif isinstance(v, str):
577-
v = os.path.abspath(v)
583+
v = abs_fn(v)
578584
data[k] = v
579585
return base64.b64encode(json.dumps(data).encode()).decode()
580586

@@ -584,6 +590,14 @@ def process_file_value(path: str) -> str:
584590
return os.path.expanduser(path)
585591

586592

593+
def abs_path_if_exists(path: str) -> str:
594+
"""os.path.abspath, but only if the path exists."""
595+
if os.path.exists(path):
596+
return os.path.abspath(path)
597+
else:
598+
return path
599+
600+
587601
def process_regexlist(name: str, option: str, values: list[str]) -> list[str]:
588602
"""Check the values in a regex list and keep the non-blank ones."""
589603
value_list = []

tests/test_process.py

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from tests import testenv
3232
from tests.coveragetest import CoverageTest, TESTS_DIR
33-
from tests.helpers import re_line, re_lines, re_lines_text
33+
from tests.helpers import change_dir, re_line, re_lines, re_lines_text
3434

3535

3636
class ProcessTest(CoverageTest):
@@ -1627,6 +1627,45 @@ def f2():
16271627
data.read()
16281628
assert line_counts(data)["subfunctions.py"] == 11
16291629

1630+
def test_subprocess_dir_with_source(self) -> None:
1631+
# https://github.com/nedbat/coveragepy/issues/1499
1632+
self.make_file("main/d/README", "A sub-directory")
1633+
self.make_file(
1634+
"main/main.py",
1635+
"""\
1636+
import os, subprocess, sys
1637+
orig = os.getcwd()
1638+
os.chdir("./d")
1639+
subprocess.run([sys.executable, f"{orig}/sub.py"])
1640+
os.chdir(orig)
1641+
""",
1642+
)
1643+
self.make_file("lib/other.py", "print('Other', flush=True)")
1644+
self.make_file(
1645+
"main/sub.py",
1646+
"""
1647+
import other
1648+
print("Hello, world!", flush=True)
1649+
""",
1650+
)
1651+
self.make_file(
1652+
"main/pyproject.toml",
1653+
"""\
1654+
[tool.coverage.run]
1655+
patch = ["subprocess"]
1656+
source = [".", "other"]
1657+
disable_warnings = ["module-not-imported"]
1658+
""",
1659+
)
1660+
self.set_environ("PYTHONPATH", os.path.abspath("lib"))
1661+
with change_dir("main"):
1662+
out = self.run_command("coverage run main.py")
1663+
assert out == "Other\nHello, world!\n"
1664+
self.run_command("coverage combine")
1665+
data = coverage.CoverageData()
1666+
data.read()
1667+
assert line_counts(data) == {"main.py": 5, "sub.py": 2, "other.py": 1}
1668+
16301669

16311670
@pytest.fixture
16321671
def _clean_pth_files() -> Iterable[None]:

0 commit comments

Comments
 (0)