11 """ |
11 """ |
12 |
12 |
13 import glob |
13 import glob |
14 import os.path |
14 import os.path |
15 |
15 |
16 from coverage.exceptions import CoverageException |
16 from coverage.exceptions import CoverageException, NoDataError |
17 from coverage.misc import file_be_gone |
17 from coverage.misc import file_be_gone, human_sorted, plural |
18 from coverage.sqldata import CoverageData |
18 from coverage.sqldata import CoverageData |
19 |
19 |
20 |
20 |
21 def line_counts(data, fullpath=False): |
21 def line_counts(data, fullpath=False): |
22 """Return a dict summarizing the line coverage data. |
22 """Return a dict summarizing the line coverage data. |
51 else: |
51 else: |
52 hasher.update(sorted(data.lines(filename) or [])) |
52 hasher.update(sorted(data.lines(filename) or [])) |
53 hasher.update(data.file_tracer(filename)) |
53 hasher.update(data.file_tracer(filename)) |
54 |
54 |
55 |
55 |
|
56 def combinable_files(data_file, data_paths=None): |
|
57 """Make a list of data files to be combined. |
|
58 |
|
59 `data_file` is a path to a data file. `data_paths` is a list of files or |
|
60 directories of files. |
|
61 |
|
62 Returns a list of absolute file paths. |
|
63 """ |
|
64 data_dir, local = os.path.split(os.path.abspath(data_file)) |
|
65 |
|
66 data_paths = data_paths or [data_dir] |
|
67 files_to_combine = [] |
|
68 for p in data_paths: |
|
69 if os.path.isfile(p): |
|
70 files_to_combine.append(os.path.abspath(p)) |
|
71 elif os.path.isdir(p): |
|
72 pattern = os.path.join(os.path.abspath(p), f"{local}.*") |
|
73 files_to_combine.extend(glob.glob(pattern)) |
|
74 else: |
|
75 raise NoDataError(f"Couldn't combine from non-existent path '{p}'") |
|
76 return files_to_combine |
|
77 |
|
78 |
56 def combine_parallel_data( |
79 def combine_parallel_data( |
57 data, aliases=None, data_paths=None, strict=False, keep=False, message=None, |
80 data, aliases=None, data_paths=None, strict=False, keep=False, message=None, |
58 ): |
81 ): |
59 """Combine a number of data files together. |
82 """Combine a number of data files together. |
|
83 |
|
84 `data` is a CoverageData. |
60 |
85 |
61 Treat `data.filename` as a file prefix, and combine the data from all |
86 Treat `data.filename` as a file prefix, and combine the data from all |
62 of the data files starting with that prefix plus a dot. |
87 of the data files starting with that prefix plus a dot. |
63 |
88 |
64 If `aliases` is provided, it's a `PathAliases` object that is used to |
89 If `aliases` is provided, it's a `PathAliases` object that is used to |
77 |
102 |
78 If `strict` is true, and no files are found to combine, an error is |
103 If `strict` is true, and no files are found to combine, an error is |
79 raised. |
104 raised. |
80 |
105 |
81 """ |
106 """ |
82 # Because of the os.path.abspath in the constructor, data_dir will |
107 files_to_combine = combinable_files(data.base_filename(), data_paths) |
83 # never be an empty string. |
|
84 data_dir, local = os.path.split(data.base_filename()) |
|
85 localdot = local + '.*' |
|
86 |
|
87 data_paths = data_paths or [data_dir] |
|
88 files_to_combine = [] |
|
89 for p in data_paths: |
|
90 if os.path.isfile(p): |
|
91 files_to_combine.append(os.path.abspath(p)) |
|
92 elif os.path.isdir(p): |
|
93 pattern = os.path.join(os.path.abspath(p), localdot) |
|
94 files_to_combine.extend(glob.glob(pattern)) |
|
95 else: |
|
96 raise CoverageException(f"Couldn't combine from non-existent path '{p}'") |
|
97 |
108 |
98 if strict and not files_to_combine: |
109 if strict and not files_to_combine: |
99 raise CoverageException("No data to combine") |
110 raise NoDataError("No data to combine") |
100 |
111 |
101 files_combined = 0 |
112 files_combined = 0 |
102 for f in files_to_combine: |
113 for f in files_to_combine: |
103 if f == data.data_filename(): |
114 if f == data.data_filename(): |
104 # Sometimes we are combining into a file which is one of the |
115 # Sometimes we are combining into a file which is one of the |
125 if data._debug.should('dataio'): |
136 if data._debug.should('dataio'): |
126 data._debug.write(f"Deleting combined data file {f!r}") |
137 data._debug.write(f"Deleting combined data file {f!r}") |
127 file_be_gone(f) |
138 file_be_gone(f) |
128 |
139 |
129 if strict and not files_combined: |
140 if strict and not files_combined: |
130 raise CoverageException("No usable data files") |
141 raise NoDataError("No usable data files") |
|
142 |
|
143 |
|
144 def debug_data_file(filename): |
|
145 """Implementation of 'coverage debug data'.""" |
|
146 data = CoverageData(filename) |
|
147 filename = data.data_filename() |
|
148 print(f"path: {filename}") |
|
149 if not os.path.exists(filename): |
|
150 print("No data collected: file doesn't exist") |
|
151 return |
|
152 data.read() |
|
153 print(f"has_arcs: {data.has_arcs()!r}") |
|
154 summary = line_counts(data, fullpath=True) |
|
155 filenames = human_sorted(summary.keys()) |
|
156 nfiles = len(filenames) |
|
157 print(f"{nfiles} file{plural(nfiles)}:") |
|
158 for f in filenames: |
|
159 line = f"{f}: {summary[f]} line{plural(summary[f])}" |
|
160 plugin = data.file_tracer(f) |
|
161 if plugin: |
|
162 line += f" [{plugin}]" |
|
163 print(line) |