DebugClients/Python/coverage/data.py

branch
Py2 comp.
changeset 3495
fac17a82b431
parent 790
2c0ea0163ef4
child 3499
f2d4b02c7e88
equal deleted inserted replaced
3485:f1cbc18f88b2 3495:fac17a82b431
1 """Coverage data for Coverage.""" 1 """Coverage data for Coverage."""
2 2
3 import os 3 import os
4 4
5 from .backward import pickle, sorted # pylint: disable-msg=W0622 5 from .backward import iitems, pickle, sorted # pylint: disable=W0622
6 from .files import PathAliases
7 from .misc import file_be_gone
6 8
7 9
8 class CoverageData(object): 10 class CoverageData(object):
9 """Manages collected coverage data, including file storage. 11 """Manages collected coverage data, including file storage.
10 12
19 * arcs: a dict mapping filenames to sorted lists of line number pairs: 21 * arcs: a dict mapping filenames to sorted lists of line number pairs:
20 { 'file1': [(17,23), (17,25), (25,26)], ... } 22 { 'file1': [(17,23), (17,25), (25,26)], ... }
21 23
22 """ 24 """
23 25
24 # Name of the data file (unless environment variable is set). 26 def __init__(self, basename=None, collector=None, debug=None):
25 filename_default = ".coverage"
26
27 # Environment variable naming the data file.
28 filename_env = "COVERAGE_FILE"
29
30 def __init__(self, basename=None, suffix=None, collector=None):
31 """Create a CoverageData. 27 """Create a CoverageData.
32 28
33 `basename` is the name of the file to use for storing data. 29 `basename` is the name of the file to use for storing data.
34 30
35 `suffix` is a suffix to append to the base file name. This can be used
36 for multiple or parallel execution, so that many coverage data files
37 can exist simultaneously.
38
39 `collector` is a string describing the coverage measurement software. 31 `collector` is a string describing the coverage measurement software.
40 32
41 """ 33 `debug` is a `DebugControl` object for writing debug messages.
42 self.collector = collector 34
35 """
36 self.collector = collector or 'unknown'
37 self.debug = debug
43 38
44 self.use_file = True 39 self.use_file = True
45 40
46 # Construct the filename that will be used for data file storage, if we 41 # Construct the filename that will be used for data file storage, if we
47 # ever do any file storage. 42 # ever do any file storage.
48 self.filename = (basename or 43 self.filename = basename or ".coverage"
49 os.environ.get(self.filename_env, self.filename_default))
50 if suffix:
51 self.filename += suffix
52 self.filename = os.path.abspath(self.filename) 44 self.filename = os.path.abspath(self.filename)
53 45
54 # A map from canonical Python source file name to a dictionary in 46 # A map from canonical Python source file name to a dictionary in
55 # which there's an entry for each line number that has been 47 # which there's an entry for each line number that has been
56 # executed: 48 # executed:
63 self.lines = {} 55 self.lines = {}
64 56
65 # A map from canonical Python source file name to a dictionary with an 57 # A map from canonical Python source file name to a dictionary with an
66 # entry for each pair of line numbers forming an arc: 58 # entry for each pair of line numbers forming an arc:
67 # 59 #
68 # { filename: { (l1,l2): None, ... }, ...} 60 # {
61 # 'filename1.py': { (12,14): None, (47,48): None, ... },
62 # ...
63 # }
69 # 64 #
70 self.arcs = {} 65 self.arcs = {}
71 66
72 def usefile(self, use_file=True): 67 def usefile(self, use_file=True):
73 """Set whether or not to use a disk file for data.""" 68 """Set whether or not to use a disk file for data."""
78 if self.use_file: 73 if self.use_file:
79 self.lines, self.arcs = self._read_file(self.filename) 74 self.lines, self.arcs = self._read_file(self.filename)
80 else: 75 else:
81 self.lines, self.arcs = {}, {} 76 self.lines, self.arcs = {}, {}
82 77
83 def write(self): 78 def write(self, suffix=None):
84 """Write the collected coverage data to a file.""" 79 """Write the collected coverage data to a file.
80
81 `suffix` is a suffix to append to the base file name. This can be used
82 for multiple or parallel execution, so that many coverage data files
83 can exist simultaneously. A dot will be used to join the base name and
84 the suffix.
85
86 """
85 if self.use_file: 87 if self.use_file:
86 self.write_file(self.filename) 88 filename = self.filename
89 if suffix:
90 filename += "." + suffix
91 self.write_file(filename)
87 92
88 def erase(self): 93 def erase(self):
89 """Erase the data, both in this object, and from its file storage.""" 94 """Erase the data, both in this object, and from its file storage."""
90 if self.use_file: 95 if self.use_file:
91 if self.filename and os.path.exists(self.filename): 96 if self.filename:
92 os.remove(self.filename) 97 file_be_gone(self.filename)
93 self.lines = {} 98 self.lines = {}
94 self.arcs = {} 99 self.arcs = {}
95 100
96 def line_data(self): 101 def line_data(self):
97 """Return the map from filenames to lists of line numbers executed.""" 102 """Return the map from filenames to lists of line numbers executed."""
98 return dict( 103 return dict(
99 [(f, sorted(lmap.keys())) for f, lmap in self.lines.items()] 104 [(f, sorted(lmap.keys())) for f, lmap in iitems(self.lines)]
100 ) 105 )
101 106
102 def arc_data(self): 107 def arc_data(self):
103 """Return the map from filenames to lists of line number pairs.""" 108 """Return the map from filenames to lists of line number pairs."""
104 return dict( 109 return dict(
105 [(f, sorted(amap.keys())) for f, amap in self.arcs.items()] 110 [(f, sorted(amap.keys())) for f, amap in iitems(self.arcs)]
106 ) 111 )
107 112
108 def write_file(self, filename): 113 def write_file(self, filename):
109 """Write the coverage data to `filename`.""" 114 """Write the coverage data to `filename`."""
110 115
116 if arcs: 121 if arcs:
117 data['arcs'] = arcs 122 data['arcs'] = arcs
118 123
119 if self.collector: 124 if self.collector:
120 data['collector'] = self.collector 125 data['collector'] = self.collector
126
127 if self.debug and self.debug.should('dataio'):
128 self.debug.write("Writing data to %r" % (filename,))
121 129
122 # Write the pickle to the file. 130 # Write the pickle to the file.
123 fdata = open(filename, 'wb') 131 fdata = open(filename, 'wb')
124 try: 132 try:
125 pickle.dump(data, fdata, 2) 133 pickle.dump(data, fdata, 2)
130 """Read the coverage data from `filename`.""" 138 """Read the coverage data from `filename`."""
131 self.lines, self.arcs = self._read_file(filename) 139 self.lines, self.arcs = self._read_file(filename)
132 140
133 def raw_data(self, filename): 141 def raw_data(self, filename):
134 """Return the raw pickled data from `filename`.""" 142 """Return the raw pickled data from `filename`."""
143 if self.debug and self.debug.should('dataio'):
144 self.debug.write("Reading data from %r" % (filename,))
135 fdata = open(filename, 'rb') 145 fdata = open(filename, 'rb')
136 try: 146 try:
137 data = pickle.load(fdata) 147 data = pickle.load(fdata)
138 finally: 148 finally:
139 fdata.close() 149 fdata.close()
152 data = self.raw_data(filename) 162 data = self.raw_data(filename)
153 if isinstance(data, dict): 163 if isinstance(data, dict):
154 # Unpack the 'lines' item. 164 # Unpack the 'lines' item.
155 lines = dict([ 165 lines = dict([
156 (f, dict.fromkeys(linenos, None)) 166 (f, dict.fromkeys(linenos, None))
157 for f, linenos in data.get('lines', {}).items() 167 for f, linenos in iitems(data.get('lines', {}))
158 ]) 168 ])
159 # Unpack the 'arcs' item. 169 # Unpack the 'arcs' item.
160 arcs = dict([ 170 arcs = dict([
161 (f, dict.fromkeys(arcpairs, None)) 171 (f, dict.fromkeys(arcpairs, None))
162 for f, arcpairs in data.get('arcs', {}).items() 172 for f, arcpairs in iitems(data.get('arcs', {}))
163 ]) 173 ])
164 except Exception: 174 except Exception:
165 pass 175 pass
166 return lines, arcs 176 return lines, arcs
167 177
168 def combine_parallel_data(self): 178 def combine_parallel_data(self, aliases=None):
169 """Combine a number of data files together. 179 """Combine a number of data files together.
170 180
171 Treat `self.filename` as a file prefix, and combine the data from all 181 Treat `self.filename` as a file prefix, and combine the data from all
172 of the data files starting with that prefix. 182 of the data files starting with that prefix plus a dot.
173 183
174 """ 184 If `aliases` is provided, it's a `PathAliases` object that is used to
185 re-map paths to match the local machine's.
186
187 """
188 aliases = aliases or PathAliases()
175 data_dir, local = os.path.split(self.filename) 189 data_dir, local = os.path.split(self.filename)
190 localdot = local + '.'
176 for f in os.listdir(data_dir or '.'): 191 for f in os.listdir(data_dir or '.'):
177 if f.startswith(local): 192 if f.startswith(localdot):
178 full_path = os.path.join(data_dir, f) 193 full_path = os.path.join(data_dir, f)
179 new_lines, new_arcs = self._read_file(full_path) 194 new_lines, new_arcs = self._read_file(full_path)
180 for filename, file_data in new_lines.items(): 195 for filename, file_data in iitems(new_lines):
196 filename = aliases.map(filename)
181 self.lines.setdefault(filename, {}).update(file_data) 197 self.lines.setdefault(filename, {}).update(file_data)
182 for filename, file_data in new_arcs.items(): 198 for filename, file_data in iitems(new_arcs):
199 filename = aliases.map(filename)
183 self.arcs.setdefault(filename, {}).update(file_data) 200 self.arcs.setdefault(filename, {}).update(file_data)
201 if f != local:
202 os.remove(full_path)
184 203
185 def add_line_data(self, line_data): 204 def add_line_data(self, line_data):
186 """Add executed line data. 205 """Add executed line data.
187 206
188 `line_data` is { filename: { lineno: None, ... }, ...} 207 `line_data` is { filename: { lineno: None, ... }, ...}
189 208
190 """ 209 """
191 for filename, linenos in line_data.items(): 210 for filename, linenos in iitems(line_data):
192 self.lines.setdefault(filename, {}).update(linenos) 211 self.lines.setdefault(filename, {}).update(linenos)
193 212
194 def add_arc_data(self, arc_data): 213 def add_arc_data(self, arc_data):
195 """Add measured arc data. 214 """Add measured arc data.
196 215
197 `arc_data` is { filename: { (l1,l2): None, ... }, ...} 216 `arc_data` is { filename: { (l1,l2): None, ... }, ...}
198 217
199 """ 218 """
200 for filename, arcs in arc_data.items(): 219 for filename, arcs in iitems(arc_data):
201 self.arcs.setdefault(filename, {}).update(arcs) 220 self.arcs.setdefault(filename, {}).update(arcs)
202 221
203 def executed_files(self): 222 def touch_file(self, filename):
204 """A list of all files that had been measured as executed.""" 223 """Ensure that `filename` appears in the data, empty if needed."""
224 self.lines.setdefault(filename, {})
225
226 def measured_files(self):
227 """A list of all files that had been measured."""
205 return list(self.lines.keys()) 228 return list(self.lines.keys())
206 229
207 def executed_lines(self, filename): 230 def executed_lines(self, filename):
208 """A map containing all the line numbers executed in `filename`. 231 """A map containing all the line numbers executed in `filename`.
209 232
214 return self.lines.get(filename) or {} 237 return self.lines.get(filename) or {}
215 238
216 def executed_arcs(self, filename): 239 def executed_arcs(self, filename):
217 """A map containing all the arcs executed in `filename`.""" 240 """A map containing all the arcs executed in `filename`."""
218 return self.arcs.get(filename) or {} 241 return self.arcs.get(filename) or {}
242
243 def add_to_hash(self, filename, hasher):
244 """Contribute `filename`'s data to the Md5Hash `hasher`."""
245 hasher.update(self.executed_lines(filename))
246 hasher.update(self.executed_arcs(filename))
219 247
220 def summary(self, fullpath=False): 248 def summary(self, fullpath=False):
221 """Return a dict summarizing the coverage data. 249 """Return a dict summarizing the coverage data.
222 250
223 Keys are based on the filenames, and values are the number of executed 251 Keys are based on the filenames, and values are the number of executed
228 summ = {} 256 summ = {}
229 if fullpath: 257 if fullpath:
230 filename_fn = lambda f: f 258 filename_fn = lambda f: f
231 else: 259 else:
232 filename_fn = os.path.basename 260 filename_fn = os.path.basename
233 for filename, lines in self.lines.items(): 261 for filename, lines in iitems(self.lines):
234 summ[filename_fn(filename)] = len(lines) 262 summ[filename_fn(filename)] = len(lines)
235 return summ 263 return summ
236 264
237 def has_arcs(self): 265 def has_arcs(self):
238 """Does this data have arcs?""" 266 """Does this data have arcs?"""
246 if sys.argv[1:]: 274 if sys.argv[1:]:
247 fname = sys.argv[1] 275 fname = sys.argv[1]
248 else: 276 else:
249 fname = covdata.filename 277 fname = covdata.filename
250 pprint.pprint(covdata.raw_data(fname)) 278 pprint.pprint(covdata.raw_data(fname))
251
252 #
253 # eflag: FileType = Python2

eric ide

mercurial