DebugClients/Python/coverage/data.py

changeset 31
744cd0b4b8cd
parent 0
de9c2efb9d02
child 32
01f04fbc1842
equal deleted inserted replaced
30:9513afbd57f1 31:744cd0b4b8cd
1 """Coverage data for Coverage.""" 1 """Coverage data for Coverage."""
2 2
3 import os 3 import os
4 import cPickle as pickle 4
5 5 from coverage.backward import pickle, sorted # pylint: disable-msg=W0622
6 from backward import sorted # pylint: disable-msg=W0622 6
7 7
8 8 class CoverageData(object):
9 class CoverageData:
10 """Manages collected coverage data, including file storage. 9 """Manages collected coverage data, including file storage.
11 10
12 The data file format is a pickled dict, with these keys: 11 The data file format is a pickled dict, with these keys:
13 12
14 * collector: a string identifying the collecting software 13 * collector: a string identifying the collecting software
15 14
16 * lines: a dict mapping filenames to sorted lists of line numbers 15 * lines: a dict mapping filenames to sorted lists of line numbers
17 executed: 16 executed:
18 { 'file1': [17,23,45], 'file2': [1,2,3], ... } 17 { 'file1': [17,23,45], 'file2': [1,2,3], ... }
19 18
19 * arcs: a dict mapping filenames to sorted lists of line number pairs:
20 { 'file1': [(17,23), (17,25), (25,26)], ... }
21
20 """ 22 """
21 23
22 # Name of the data file (unless environment variable is set). 24 # Name of the data file (unless environment variable is set).
23 filename_default = ".coverage" 25 filename_default = ".coverage"
24 26
25 # Environment variable naming the data file. 27 # Environment variable naming the data file.
26 filename_env = "COVERAGE_FILE" 28 filename_env = "COVERAGE_FILE"
27 29
28 def __init__(self, basename=None, suffix=None, collector=None): 30 def __init__(self, basename=None, suffix=None, collector=None):
29 """Create a CoverageData. 31 """Create a CoverageData.
30 32
31 `basename` is the name of the file to use for storing data. 33 `basename` is the name of the file to use for storing data.
32 34
33 `suffix` is a suffix to append to the base file name. This can be used 35 `suffix` is a suffix to append to the base file name. This can be used
34 for multiple or parallel execution, so that many coverage data files 36 for multiple or parallel execution, so that many coverage data files
35 can exist simultaneously. 37 can exist simultaneously.
36 38
37 `collector` is a string describing the coverage measurement software. 39 `collector` is a string describing the coverage measurement software.
38 40
39 """ 41 """
40 self.basename = basename
41 self.collector = collector 42 self.collector = collector
42 self.suffix = suffix 43
43
44 self.use_file = True 44 self.use_file = True
45 self.filename = None 45
46 # Construct the filename that will be used for data file storage, if we
47 # ever do any file storage.
48 self.filename = (basename or
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)
46 53
47 # A map from canonical Python source file name to a dictionary in 54 # A map from canonical Python source file name to a dictionary in
48 # which there's an entry for each line number that has been 55 # which there's an entry for each line number that has been
49 # executed: 56 # executed:
50 # 57 #
51 # { 58 # {
52 # 'filename1.py': { 12: True, 47: True, ... }, 59 # 'filename1.py': { 12: None, 47: None, ... },
53 # ... 60 # ...
54 # } 61 # }
55 # 62 #
56 self.lines = {} 63 self.lines = {}
57 64
65 # A map from canonical Python source file name to a dictionary with an
66 # entry for each pair of line numbers forming an arc:
67 #
68 # { filename: { (l1,l2): None, ... }, ...}
69 #
70 self.arcs = {}
71
58 def usefile(self, use_file=True): 72 def usefile(self, use_file=True):
59 """Set whether or not to use a disk file for data.""" 73 """Set whether or not to use a disk file for data."""
60 self.use_file = use_file 74 self.use_file = use_file
61 75
62 def _make_filename(self):
63 """Construct the filename that will be used for data file storage."""
64 assert self.use_file
65 if not self.filename:
66 self.filename = (self.basename or
67 os.environ.get(self.filename_env, self.filename_default))
68
69 if self.suffix:
70 self.filename += self.suffix
71
72 def read(self): 76 def read(self):
73 """Read coverage data from the coverage data file (if it exists).""" 77 """Read coverage data from the coverage data file (if it exists)."""
74 data = {}
75 if self.use_file: 78 if self.use_file:
76 self._make_filename() 79 self.lines, self.arcs = self._read_file(self.filename)
77 data = self._read_file(self.filename) 80 else:
78 self.lines = data 81 self.lines, self.arcs = {}, {}
79 82
80 def write(self): 83 def write(self):
81 """Write the collected coverage data to a file.""" 84 """Write the collected coverage data to a file."""
82 if self.use_file: 85 if self.use_file:
83 self._make_filename()
84 self.write_file(self.filename) 86 self.write_file(self.filename)
85 87
86 def erase(self): 88 def erase(self):
87 """Erase the data, both in this object, and from its file storage.""" 89 """Erase the data, both in this object, and from its file storage."""
88 if self.use_file: 90 if self.use_file:
89 self._make_filename()
90 if self.filename and os.path.exists(self.filename): 91 if self.filename and os.path.exists(self.filename):
91 os.remove(self.filename) 92 os.remove(self.filename)
92 self.lines = {} 93 self.lines = {}
93 94 self.arcs = {}
95
94 def line_data(self): 96 def line_data(self):
95 """Return the map from filenames to lists of line numbers executed.""" 97 """Return the map from filenames to lists of line numbers executed."""
96 return dict( 98 return dict(
97 [(f, sorted(linemap.keys())) for f, linemap in self.lines.items()] 99 [(f, sorted(lmap.keys())) for f, lmap in self.lines.items()]
100 )
101
102 def arc_data(self):
103 """Return the map from filenames to lists of line number pairs."""
104 return dict(
105 [(f, sorted(amap.keys())) for f, amap in self.arcs.items()]
98 ) 106 )
99 107
100 def write_file(self, filename): 108 def write_file(self, filename):
101 """Write the coverage data to `filename`.""" 109 """Write the coverage data to `filename`."""
102 110
103 # Create the file data. 111 # Create the file data.
104 data = {} 112 data = {}
105 113
106 data['lines'] = self.line_data() 114 data['lines'] = self.line_data()
115 arcs = self.arc_data()
116 if arcs:
117 data['arcs'] = arcs
107 118
108 if self.collector: 119 if self.collector:
109 data['collector'] = self.collector 120 data['collector'] = self.collector
110 121
111 # Write the pickle to the file. 122 # Write the pickle to the file.
115 finally: 126 finally:
116 fdata.close() 127 fdata.close()
117 128
118 def read_file(self, filename): 129 def read_file(self, filename):
119 """Read the coverage data from `filename`.""" 130 """Read the coverage data from `filename`."""
120 self.lines = self._read_file(filename) 131 self.lines, self.arcs = self._read_file(filename)
132
133 def raw_data(self, filename):
134 """Return the raw pickled data from `filename`."""
135 fdata = open(filename, 'rb')
136 try:
137 data = pickle.load(fdata)
138 finally:
139 fdata.close()
140 return data
121 141
122 def _read_file(self, filename): 142 def _read_file(self, filename):
123 """Return the stored coverage data from the given file.""" 143 """Return the stored coverage data from the given file.
144
145 Returns two values, suitable for assigning to `self.lines` and
146 `self.arcs`.
147
148 """
149 lines = {}
150 arcs = {}
124 try: 151 try:
125 fdata = open(filename, 'rb') 152 data = self.raw_data(filename)
126 try:
127 data = pickle.load(fdata)
128 finally:
129 fdata.close()
130 if isinstance(data, dict): 153 if isinstance(data, dict):
131 # Unpack the 'lines' item. 154 # Unpack the 'lines' item.
132 lines = dict([ 155 lines = dict([
133 (f, dict([(l, True) for l in linenos])) 156 (f, dict.fromkeys(linenos, None))
134 for f,linenos in data['lines'].items() 157 for f, linenos in data.get('lines', {}).items()
135 ]) 158 ])
136 return lines 159 # Unpack the 'arcs' item.
137 else: 160 arcs = dict([
138 return {} 161 (f, dict.fromkeys(arcpairs, None))
162 for f, arcpairs in data.get('arcs', {}).items()
163 ])
139 except Exception: 164 except Exception:
140 return {} 165 pass
166 return lines, arcs
141 167
142 def combine_parallel_data(self): 168 def combine_parallel_data(self):
143 """ Treat self.filename as a file prefix, and combine the data from all 169 """Combine a number of data files together.
144 of the files starting with that prefix. 170
145 """ 171 Treat `self.filename` as a file prefix, and combine the data from all
146 self._make_filename() 172 of the data files starting with that prefix.
173
174 """
147 data_dir, local = os.path.split(self.filename) 175 data_dir, local = os.path.split(self.filename)
148 for f in os.listdir(data_dir or '.'): 176 for f in os.listdir(data_dir or '.'):
149 if f.startswith(local): 177 if f.startswith(local):
150 full_path = os.path.join(data_dir, f) 178 full_path = os.path.join(data_dir, f)
151 new_data = self._read_file(full_path) 179 new_lines, new_arcs = self._read_file(full_path)
152 for filename, file_data in new_data.items(): 180 for filename, file_data in new_lines.items():
153 self.lines.setdefault(filename, {}).update(file_data) 181 self.lines.setdefault(filename, {}).update(file_data)
154 182 for filename, file_data in new_arcs.items():
155 def add_line_data(self, data_points): 183 self.arcs.setdefault(filename, {}).update(file_data)
184
185 def add_line_data(self, line_data):
156 """Add executed line data. 186 """Add executed line data.
157 187
158 `data_points` is (filename, lineno) pairs. 188 `line_data` is { filename: { lineno: None, ... }, ...}
159 189
160 """ 190 """
161 for filename, lineno in data_points: 191 for filename, linenos in line_data.items():
162 self.lines.setdefault(filename, {})[lineno] = True 192 self.lines.setdefault(filename, {}).update(linenos)
193
194 def add_arc_data(self, arc_data):
195 """Add measured arc data.
196
197 `arc_data` is { filename: { (l1,l2): None, ... }, ...}
198
199 """
200 for filename, arcs in arc_data.items():
201 self.arcs.setdefault(filename, {}).update(arcs)
163 202
164 def executed_files(self): 203 def executed_files(self):
165 """A list of all files that had been measured as executed.""" 204 """A list of all files that had been measured as executed."""
166 return self.lines.keys() 205 return list(self.lines.keys())
167 206
168 def executed_lines(self, filename): 207 def executed_lines(self, filename):
169 """A map containing all the line numbers executed in `filename`. 208 """A map containing all the line numbers executed in `filename`.
170 209
171 If `filename` hasn't been collected at all (because it wasn't executed) 210 If `filename` hasn't been collected at all (because it wasn't executed)
172 then return an empty map. 211 then return an empty map.
173 212
174 """ 213 """
175 return self.lines.get(filename) or {} 214 return self.lines.get(filename) or {}
176 215
177 def summary(self): 216 def executed_arcs(self, filename):
217 """A map containing all the arcs executed in `filename`."""
218 return self.arcs.get(filename) or {}
219
220 def summary(self, fullpath=False):
178 """Return a dict summarizing the coverage data. 221 """Return a dict summarizing the coverage data.
179 222
180 Keys are the basename of the filenames, and values are the number of 223 Keys are based on the filenames, and values are the number of executed
181 executed lines. This is useful in the unit tests. 224 lines. If `fullpath` is true, then the keys are the full pathnames of
182 225 the files, otherwise they are the basenames of the files.
226
183 """ 227 """
184 summ = {} 228 summ = {}
229 if fullpath:
230 filename_fn = lambda f: f
231 else:
232 filename_fn = os.path.basename
185 for filename, lines in self.lines.items(): 233 for filename, lines in self.lines.items():
186 summ[os.path.basename(filename)] = len(lines) 234 summ[filename_fn(filename)] = len(lines)
187 return summ 235 return summ
236
237 def has_arcs(self):
238 """Does this data have arcs?"""
239 return bool(self.arcs)
240
241
242 if __name__ == '__main__':
243 # Ad-hoc: show the raw data in a data file.
244 import pprint, sys
245 covdata = CoverageData()
246 if sys.argv[1:]:
247 fname = sys.argv[1]
248 else:
249 fname = covdata.filename
250 pprint.pprint(covdata.raw_data(fname))

eric ide

mercurial