summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2021-11-13 08:09:19 -0500
committerNed Batchelder <ned@nedbatchelder.com>2021-11-13 12:33:11 -0500
commit5ca980dabd71aa74d90351a6d4e88cf101d65ded (patch)
treefbc48740076572aaf3a55c1405d40665ead83434
parent9a72bf4bc964bc8062627d6ecd6fb8c8d1e5dec6 (diff)
downloadpython-coveragepy-git-5ca980dabd71aa74d90351a6d4e88cf101d65ded.tar.gz
feat: 'debug data' now enumerates combinable files also
-rw-r--r--CHANGES.rst3
-rw-r--r--coverage/cmdline.py48
-rw-r--r--coverage/data.py41
-rw-r--r--tests/test_cmdline.py40
4 files changed, 89 insertions, 43 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 5aaa0c4a..65f0c21a 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -28,6 +28,9 @@ Unreleased
- Fix: The HTML report now will not overwrite a .gitignore file that already
exists in the HTML output directory (follow-on for `issue 1244`_).
+- Debug: The `coverage debug data` command will now sniff out combinable data
+ files, and report on all of them.
+
.. _changes_612:
diff --git a/coverage/cmdline.py b/coverage/cmdline.py
index e996ffff..f3a466e3 100644
--- a/coverage/cmdline.py
+++ b/coverage/cmdline.py
@@ -17,7 +17,7 @@ import coverage
from coverage import Coverage
from coverage import env
from coverage.collector import CTracer
-from coverage.data import line_counts
+from coverage.data import CoverageData, combinable_files, line_counts
from coverage.debug import info_formatter, info_header, short_stack
from coverage.exceptions import BaseCoverageException, ExceptionDuringRun, NoSource
from coverage.execfile import PyRunner
@@ -601,8 +601,8 @@ class CoverageScript:
elif options.action == "combine":
if options.append:
self.coverage.load()
- data_dirs = args or None
- self.coverage.combine(data_dirs, strict=True, keep=bool(options.keep))
+ data_paths = args or None
+ self.coverage.combine(data_paths, strict=True, keep=bool(options.keep))
self.coverage.save()
return OK
@@ -786,23 +786,12 @@ class CoverageScript:
for line in info_formatter(sys_info):
print(f" {line}")
elif info == 'data':
- self.coverage.load()
- data = self.coverage.get_data()
print(info_header("data"))
- print(f"path: {data.data_filename()}")
- if data:
- print(f"has_arcs: {data.has_arcs()!r}")
- summary = line_counts(data, fullpath=True)
- filenames = human_sorted(summary.keys())
- print(f"\n{len(filenames)} files:")
- for f in filenames:
- line = f"{f}: {summary[f]} lines"
- plugin = data.file_tracer(f)
- if plugin:
- line += f" [{plugin}]"
- print(line)
- else:
- print("No data collected")
+ data_file = self.coverage.config.data_file
+ self.do_debug_data_file(data_file)
+ for filename in combinable_files(data_file):
+ print("-----")
+ self.do_debug_data_file(filename)
elif info == 'config':
print(info_header("config"))
config_info = self.coverage.config.__dict__.items()
@@ -817,6 +806,27 @@ class CoverageScript:
return OK
+ def do_debug_data_file(self, filename):
+ """Implementation of 'coverage debug data'."""
+ data = CoverageData(filename)
+ filename = data.data_filename()
+ print(f"path: {filename}")
+ if not os.path.exists(filename):
+ print("No data collected: file doesn't exist")
+ return
+ data.read()
+ print(f"has_arcs: {data.has_arcs()!r}")
+ summary = line_counts(data, fullpath=True)
+ filenames = human_sorted(summary.keys())
+ nfiles = len(filenames)
+ print(f"{nfiles} file{'' if nfiles == 1 else 's'}:")
+ for f in filenames:
+ line = f"{f}: {summary[f]} line{'' if summary[f] == 1 else 's'}"
+ plugin = data.file_tracer(f)
+ if plugin:
+ line += f" [{plugin}]"
+ print(line)
+
def unshell_list(s):
"""Turn a command-line argument into a list."""
diff --git a/coverage/data.py b/coverage/data.py
index 68ba7ec3..e7c94b4f 100644
--- a/coverage/data.py
+++ b/coverage/data.py
@@ -53,11 +53,36 @@ def add_data_to_hash(data, filename, hasher):
hasher.update(data.file_tracer(filename))
+def combinable_files(data_file, data_paths=None):
+ """Make a list of data files to be combined.
+
+ `data_file` is a path to a data file. `data_paths` is a list of files or
+ directories of files.
+
+ Returns a list of absolute file paths.
+ """
+ data_dir, local = os.path.split(os.path.abspath(data_file))
+
+ data_paths = data_paths or [data_dir]
+ files_to_combine = []
+ for p in data_paths:
+ if os.path.isfile(p):
+ files_to_combine.append(os.path.abspath(p))
+ elif os.path.isdir(p):
+ pattern = os.path.join(os.path.abspath(p), f"{local}.*")
+ files_to_combine.extend(glob.glob(pattern))
+ else:
+ raise CoverageException(f"Couldn't combine from non-existent path '{p}'")
+ return files_to_combine
+
+
def combine_parallel_data(
data, aliases=None, data_paths=None, strict=False, keep=False, message=None,
):
"""Combine a number of data files together.
+ `data` is a CoverageData.
+
Treat `data.filename` as a file prefix, and combine the data from all
of the data files starting with that prefix plus a dot.
@@ -79,21 +104,7 @@ def combine_parallel_data(
raised.
"""
- # Because of the os.path.abspath in the constructor, data_dir will
- # never be an empty string.
- data_dir, local = os.path.split(data.base_filename())
- localdot = local + '.*'
-
- data_paths = data_paths or [data_dir]
- files_to_combine = []
- for p in data_paths:
- if os.path.isfile(p):
- files_to_combine.append(os.path.abspath(p))
- elif os.path.isdir(p):
- pattern = os.path.join(os.path.abspath(p), localdot)
- files_to_combine.extend(glob.glob(pattern))
- else:
- raise CoverageException(f"Couldn't combine from non-existent path '{p}'")
+ files_to_combine = combinable_files(data.base_filename(), data_paths)
if strict and not files_to_combine:
raise CoverageException("No data to combine")
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
index 1d87c19a..112e5d68 100644
--- a/tests/test_cmdline.py
+++ b/tests/test_cmdline.py
@@ -817,24 +817,46 @@ class CmdLineWithFilesTest(BaseCmdLineTest):
data.write()
self.command_line("debug data")
- assert self.stdout() == textwrap.dedent("""\
+ assert self.stdout() == textwrap.dedent(f"""\
-- data ------------------------------------------------------
- path: FILENAME
+ path: {data.data_filename()}
has_arcs: False
-
2 files:
file1.py: 17 lines [a_plugin]
file2.py: 23 lines
- """).replace("FILENAME", data.data_filename())
+ """)
- def test_debug_data_with_no_data(self):
+ def test_debug_data_with_no_data_file(self):
data = CoverageData()
self.command_line("debug data")
- assert self.stdout() == textwrap.dedent("""\
+ assert self.stdout() == textwrap.dedent(f"""\
-- data ------------------------------------------------------
- path: FILENAME
- No data collected
- """).replace("FILENAME", data.data_filename())
+ path: {data.data_filename()}
+ No data collected: file doesn't exist
+ """)
+
+ def test_debug_combinable_data(self):
+ data1 = CoverageData()
+ data1.add_lines({"file1.py": range(1, 18), "file2.py": [1]})
+ data1.write()
+ data2 = CoverageData(suffix="123")
+ data2.add_lines({"file2.py": range(1, 10)})
+ data2.write()
+
+ self.command_line("debug data")
+ assert self.stdout() == textwrap.dedent(f"""\
+ -- data ------------------------------------------------------
+ path: {data1.data_filename()}
+ has_arcs: False
+ 2 files:
+ file1.py: 17 lines
+ file2.py: 1 line
+ -----
+ path: {data2.data_filename()}
+ has_arcs: False
+ 1 file:
+ file2.py: 9 lines
+ """)
class CmdLineStdoutTest(BaseCmdLineTest):