Skip to content

Commit 4c63e61

Browse files
committed
Report data file errors in more detail: include file and directory paths, and make helpful suggestions
1 parent 5c70761 commit 4c63e61

File tree

5 files changed

+44
-13
lines changed

5 files changed

+44
-13
lines changed

coverage/cmdline.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import sys
1414
import textwrap
1515
import traceback
16+
from contextlib import suppress
1617

1718
from typing import cast, Any, NoReturn
1819

@@ -24,7 +25,8 @@
2425
from coverage.control import DEFAULT_DATAFILE
2526
from coverage.data import combinable_files, debug_data_file
2627
from coverage.debug import info_header, short_stack, write_formatted_info
27-
from coverage.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource
28+
from coverage.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource, \
29+
NoDataFilesFoundError
2830
from coverage.execfile import PyRunner
2931
from coverage.results import display_covered, should_fail_under
3032
from coverage.version import __url__
@@ -882,9 +884,10 @@ def do_debug(self, args: list[str]) -> int:
882884
print(info_header("data"))
883885
data_file = self.coverage.config.data_file
884886
debug_data_file(data_file)
885-
for filename in combinable_files(data_file):
886-
print("-----")
887-
debug_data_file(filename)
887+
with suppress(NoDataFilesFoundError):
888+
for filename in combinable_files(data_file):
889+
print("-----")
890+
debug_data_file(filename)
888891
elif args[0] == "config":
889892
write_formatted_info(print, "config", self.coverage.config.debug_info())
890893
elif args[0] == "premain":

coverage/data.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
from typing import Callable, Iterable
2020

21-
from coverage.exceptions import CoverageException, NoDataError
21+
from coverage.exceptions import CoverageException, NoDataError, DataFileNotFoundError, \
22+
NoDataFilesFoundError
2223
from coverage.files import PathAliases
2324
from coverage.misc import Hasher, file_be_gone, human_sorted, plural
2425
from coverage.sqldata import CoverageData
@@ -82,12 +83,15 @@ def combinable_files(data_file: str, data_paths: Iterable[str] | None = None) ->
8283
pattern = glob.escape(os.path.join(os.path.abspath(p), local)) +".*"
8384
files_to_combine.extend(glob.glob(pattern))
8485
else:
85-
raise NoDataError(f"Couldn't combine from non-existent path '{p}'")
86+
raise DataFileNotFoundError.new_for_data_file(p, True)
8687

8788
# SQLite might have made journal files alongside our database files.
8889
# We never want to combine those.
8990
files_to_combine = [fnm for fnm in files_to_combine if not fnm.endswith("-journal")]
9091

92+
if not files_to_combine:
93+
raise NoDataFilesFoundError.new_for_data_directory(data_dir)
94+
9195
# Sorting isn't usually needed, since it shouldn't matter what order files
9296
# are combined, but sorting makes tests more predictable, and makes
9397
# debugging more understandable when things go wrong.
@@ -129,10 +133,12 @@ def combine_parallel_data(
129133
`message` is a function to use for printing messages to the user.
130134
131135
"""
132-
files_to_combine = combinable_files(data.base_filename(), data_paths)
133-
134-
if strict and not files_to_combine:
135-
raise NoDataError("No data to combine")
136+
try:
137+
files_to_combine = combinable_files(data.base_filename(), data_paths)
138+
except NoDataFilesFoundError:
139+
if strict:
140+
raise
141+
return None
136142

137143
file_hashes = set()
138144
combined_any = False
@@ -190,6 +196,7 @@ def combine_parallel_data(
190196
file_be_gone(f)
191197

192198
if strict and not combined_any:
199+
# @todo Files found but no usable data in them. Parse error?
193200
raise NoDataError("No usable data files")
194201

195202

coverage/exceptions.py

+21
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
from __future__ import annotations
77

8+
import os.path
9+
10+
811
class _BaseCoverageException(Exception):
912
"""The base-base of all Coverage exceptions."""
1013
pass
@@ -24,11 +27,29 @@ class DataError(CoverageException):
2427
"""An error in using a data file."""
2528
pass
2629

30+
2731
class NoDataError(CoverageException):
2832
"""We didn't have data to work with."""
2933
pass
3034

3135

36+
class DataFileNotFoundError(NoDataError):
37+
"""A data file or data directory could be found."""
38+
@classmethod
39+
def new_for_data_file(cls, data_file_path: str, combined: bool = False) -> 'Self':
40+
message = f"The data file or directory `{os.path.abspath(data_file_path)}` could not be found."
41+
if not combined:
42+
message += " Perhaps `coverage combine` must be run first."
43+
return cls(message)
44+
45+
46+
class NoDataFilesFoundError(NoDataError):
47+
"""No data files could be found in a data directory."""
48+
@classmethod
49+
def new_for_data_directory(cls, data_directory_path: str) -> 'Self':
50+
return cls(f"The data directory `{os.path.abspath(data_directory_path)}` does not contain any data files.")
51+
52+
3253
class NoSource(CoverageException):
3354
"""We couldn't find the source for a module."""
3455
pass

tests/test_api.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ def test_combining_twice(self) -> None:
446446
self.assert_exists(".coverage")
447447

448448
cov2 = coverage.Coverage()
449-
with pytest.raises(NoDataError, match=r"No data to combine"):
449+
with pytest.raises(NoDataError, match=r"^The data directory `(.+?)` does not contain any data files.$"):
450450
cov2.combine(strict=True, keep=False)
451451

452452
cov3 = coverage.Coverage()
@@ -1326,7 +1326,7 @@ def test_combine_parallel_data(self) -> None:
13261326
# Running combine again should fail, because there are no parallel data
13271327
# files to combine.
13281328
cov = coverage.Coverage()
1329-
with pytest.raises(NoDataError, match=r"No data to combine"):
1329+
with pytest.raises(NoDataError):
13301330
cov.combine(strict=True)
13311331

13321332
# And the originally combined data is still there.

tests/test_data.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -915,7 +915,7 @@ def test_combining_from_files(self) -> None:
915915

916916
def test_combining_from_nonexistent_directories(self) -> None:
917917
covdata = DebugCoverageData()
918-
msg = "Couldn't combine from non-existent path 'xyzzy'"
918+
msg = r"^The data file or directory `(.+?)` could not be found.$"
919919
with pytest.raises(NoDataError, match=msg):
920920
combine_parallel_data(covdata, data_paths=['xyzzy'])
921921

0 commit comments

Comments
 (0)