17757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch"""Coverage data for Coverage."""
27757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
37757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport os
47757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
57757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.backward import pickle, sorted        # pylint: disable=W0622
67757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochfrom coverage.files import PathAliases
77757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
87757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
97757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass CoverageData(object):
107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Manages collected coverage data, including file storage.
117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    The data file format is a pickled dict, with these keys:
137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        * collector: a string identifying the collecting software
157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        * lines: a dict mapping filenames to sorted lists of line numbers
177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch          executed:
187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            { 'file1': [17,23,45],  'file2': [1,2,3], ... }
197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        * arcs: a dict mapping filenames to sorted lists of line number pairs:
217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            { 'file1': [(17,23), (17,25), (25,26)], ... }
227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __init__(self, basename=None, collector=None):
267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Create a CoverageData.
277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        `basename` is the name of the file to use for storing data.
297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        `collector` is a string describing the coverage measurement software.
317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.collector = collector or 'unknown'
347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.use_file = True
367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Construct the filename that will be used for data file storage, if we
387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # ever do any file storage.
397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.filename = basename or ".coverage"
407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.filename = os.path.abspath(self.filename)
417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # A map from canonical Python source file name to a dictionary in
437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # which there's an entry for each line number that has been
447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # executed:
457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #
467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #   {
477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #       'filename1.py': { 12: None, 47: None, ... },
487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #       ...
497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #       }
507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #
517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.lines = {}
527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # A map from canonical Python source file name to a dictionary with an
547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # entry for each pair of line numbers forming an arc:
557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #
567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #   {
577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #       'filename1.py': { (12,14): None, (47,48): None, ... },
587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #       ...
597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #       }
607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        #
617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.arcs = {}
627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.os = os
647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.sorted = sorted
657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.pickle = pickle
667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def usefile(self, use_file=True):
687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Set whether or not to use a disk file for data."""
697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.use_file = use_file
707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def read(self):
727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Read coverage data from the coverage data file (if it exists)."""
737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.use_file:
747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.lines, self.arcs = self._read_file(self.filename)
757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.lines, self.arcs = {}, {}
777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def write(self, suffix=None):
797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Write the collected coverage data to a file.
807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        `suffix` is a suffix to append to the base file name. This can be used
827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for multiple or parallel execution, so that many coverage data files
837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        can exist simultaneously.  A dot will be used to join the base name and
847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        the suffix.
857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.use_file:
887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            filename = self.filename
897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if suffix:
907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                filename += "." + suffix
917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.write_file(filename)
927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def erase(self):
947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Erase the data, both in this object, and from its file storage."""
957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.use_file:
967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if self.filename and os.path.exists(self.filename):
977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                os.remove(self.filename)
987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.lines = {}
997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.arcs = {}
1007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def line_data(self):
1027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Return the map from filenames to lists of line numbers executed."""
1037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return dict(
1047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            [(f, self.sorted(lmap.keys())) for f, lmap in self.lines.items()]
1057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            )
1067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def arc_data(self):
1087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Return the map from filenames to lists of line number pairs."""
1097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return dict(
1107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            [(f, self.sorted(amap.keys())) for f, amap in self.arcs.items()]
1117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            )
1127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def write_file(self, filename):
1147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Write the coverage data to `filename`."""
1157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Create the file data.
1177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        data = {}
1187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        data['lines'] = self.line_data()
1207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        arcs = self.arc_data()
1217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if arcs:
1227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            data['arcs'] = arcs
1237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.collector:
1257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            data['collector'] = self.collector
1267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Write the pickle to the file.
1287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        fdata = open(filename, 'wb')
1297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
1307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.pickle.dump(data, fdata, 2)
1317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        finally:
1327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            fdata.close()
1337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def read_file(self, filename):
1357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Read the coverage data from `filename`."""
1367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.lines, self.arcs = self._read_file(filename)
1377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def raw_data(self, filename):
1397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Return the raw pickled data from `filename`."""
1407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        fdata = open(filename, 'rb')
1417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
1427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            data = pickle.load(fdata)
1437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        finally:
1447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            fdata.close()
1457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return data
1467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _read_file(self, filename):
1487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Return the stored coverage data from the given file.
1497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns two values, suitable for assigning to `self.lines` and
1517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        `self.arcs`.
1527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
1547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        lines = {}
1557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        arcs = {}
1567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
1577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            data = self.raw_data(filename)
1587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if isinstance(data, dict):
1597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                # Unpack the 'lines' item.
1607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                lines = dict([
1617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    (f, dict.fromkeys(linenos, None))
1627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        for f, linenos in data.get('lines', {}).items()
1637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    ])
1647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                # Unpack the 'arcs' item.
1657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                arcs = dict([
1667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    (f, dict.fromkeys(arcpairs, None))
1677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        for f, arcpairs in data.get('arcs', {}).items()
1687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    ])
1697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except Exception:
1707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            pass
1717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return lines, arcs
1727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def combine_parallel_data(self, aliases=None):
1747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Combine a number of data files together.
1757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Treat `self.filename` as a file prefix, and combine the data from all
1777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        of the data files starting with that prefix plus a dot.
1787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        If `aliases` is provided, it's a `PathAliases` object that is used to
1807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        re-map paths to match the local machine's.
1817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
1837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        aliases = aliases or PathAliases()
1847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        data_dir, local = os.path.split(self.filename)
1857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        localdot = local + '.'
1867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for f in os.listdir(data_dir or '.'):
1877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if f.startswith(localdot):
1887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                full_path = os.path.join(data_dir, f)
1897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                new_lines, new_arcs = self._read_file(full_path)
1907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                for filename, file_data in new_lines.items():
1917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    filename = aliases.map(filename)
1927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    self.lines.setdefault(filename, {}).update(file_data)
1937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                for filename, file_data in new_arcs.items():
1947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    filename = aliases.map(filename)
1957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    self.arcs.setdefault(filename, {}).update(file_data)
1967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if f != local:
1977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    os.remove(full_path)
1987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def add_line_data(self, line_data):
2007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Add executed line data.
2017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        `line_data` is { filename: { lineno: None, ... }, ...}
2037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for filename, linenos in line_data.items():
2067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.lines.setdefault(filename, {}).update(linenos)
2077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def add_arc_data(self, arc_data):
2097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Add measured arc data.
2107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        `arc_data` is { filename: { (l1,l2): None, ... }, ...}
2127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for filename, arcs in arc_data.items():
2157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.arcs.setdefault(filename, {}).update(arcs)
2167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def touch_file(self, filename):
2187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Ensure that `filename` appears in the data, empty if needed."""
2197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.lines.setdefault(filename, {})
2207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def measured_files(self):
2227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """A list of all files that had been measured."""
2237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return list(self.lines.keys())
2247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def executed_lines(self, filename):
2267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """A map containing all the line numbers executed in `filename`.
2277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        If `filename` hasn't been collected at all (because it wasn't executed)
2297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        then return an empty map.
2307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self.lines.get(filename) or {}
2337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def executed_arcs(self, filename):
2357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """A map containing all the arcs executed in `filename`."""
2367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self.arcs.get(filename) or {}
2377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def add_to_hash(self, filename, hasher):
2397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Contribute `filename`'s data to the Md5Hash `hasher`."""
2407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        hasher.update(self.executed_lines(filename))
2417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        hasher.update(self.executed_arcs(filename))
2427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def summary(self, fullpath=False):
2447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Return a dict summarizing the coverage data.
2457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Keys are based on the filenames, and values are the number of executed
2477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        lines.  If `fullpath` is true, then the keys are the full pathnames of
2487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        the files, otherwise they are the basenames of the files.
2497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        summ = {}
2527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if fullpath:
2537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            filename_fn = lambda f: f
2547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
2557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            filename_fn = self.os.path.basename
2567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for filename, lines in self.lines.items():
2577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            summ[filename_fn(filename)] = len(lines)
2587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return summ
2597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def has_arcs(self):
2617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Does this data have arcs?"""
2627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return bool(self.arcs)
2637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochif __name__ == '__main__':
2667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    # Ad-hoc: show the raw data in a data file.
2677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    import pprint, sys
2687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    covdata = CoverageData()
2697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    if sys.argv[1:]:
2707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        fname = sys.argv[1]
2717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    else:
2727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        fname = covdata.filename
2737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    pprint.pprint(covdata.raw_data(fname))
274