14 |
15 |
15 from coverage import env |
16 from coverage import env |
16 from coverage.backward import iitems, string_class |
17 from coverage.backward import iitems, string_class |
17 from coverage.debug import _TEST_NAME_FILE |
18 from coverage.debug import _TEST_NAME_FILE |
18 from coverage.files import PathAliases |
19 from coverage.files import PathAliases |
19 from coverage.misc import CoverageException, file_be_gone |
20 from coverage.misc import CoverageException, file_be_gone, isolate_module |
|
21 |
|
22 os = isolate_module(os) |
20 |
23 |
21 |
24 |
22 class CoverageData(object): |
25 class CoverageData(object): |
23 """Manages collected coverage data, including file storage. |
26 """Manages collected coverage data, including file storage. |
24 |
27 |
174 If the file was executed, returns a list of integers, the line numbers |
177 If the file was executed, returns a list of integers, the line numbers |
175 executed in the file. The list is in no particular order. |
178 executed in the file. The list is in no particular order. |
176 |
179 |
177 """ |
180 """ |
178 if self._arcs is not None: |
181 if self._arcs is not None: |
179 if filename in self._arcs: |
182 arcs = self._arcs.get(filename) |
180 return [s for s, __ in self._arcs[filename] if s > 0] |
183 if arcs is not None: |
|
184 all_lines = itertools.chain.from_iterable(arcs) |
|
185 return list(set(l for l in all_lines if l > 0)) |
181 elif self._lines is not None: |
186 elif self._lines is not None: |
182 if filename in self._lines: |
187 return self._lines.get(filename) |
183 return self._lines[filename] |
|
184 return None |
188 return None |
185 |
189 |
186 def arcs(self, filename): |
190 def arcs(self, filename): |
187 """Get the list of arcs executed for a file. |
191 """Get the list of arcs executed for a file. |
188 |
192 |
572 assert isinstance(key, string_class), "Key in _runs shouldn't be %r" % (key,) |
576 assert isinstance(key, string_class), "Key in _runs shouldn't be %r" % (key,) |
573 |
577 |
574 def add_to_hash(self, filename, hasher): |
578 def add_to_hash(self, filename, hasher): |
575 """Contribute `filename`'s data to the `hasher`. |
579 """Contribute `filename`'s data to the `hasher`. |
576 |
580 |
577 `hasher` is a :class:`coverage.misc.Hasher` instance to be updated with |
581 `hasher` is a `coverage.misc.Hasher` instance to be updated with |
578 the file's data. It should only get the results data, not the run |
582 the file's data. It should only get the results data, not the run |
579 data. |
583 data. |
580 |
584 |
581 """ |
585 """ |
582 if self._has_arcs(): |
586 if self._has_arcs(): |
599 |
603 |
600 |
604 |
601 class CoverageDataFiles(object): |
605 class CoverageDataFiles(object): |
602 """Manage the use of coverage data files.""" |
606 """Manage the use of coverage data files.""" |
603 |
607 |
604 def __init__(self, basename=None): |
608 def __init__(self, basename=None, warn=None): |
605 """Create a CoverageDataFiles to manage data files. |
609 """Create a CoverageDataFiles to manage data files. |
606 |
610 |
|
611 `warn` is the warning function to use. |
|
612 |
607 `basename` is the name of the file to use for storing data. |
613 `basename` is the name of the file to use for storing data. |
608 |
614 |
609 """ |
615 """ |
|
616 self.warn = warn |
610 # Construct the file name that will be used for data storage. |
617 # Construct the file name that will be used for data storage. |
611 self.filename = os.path.abspath(basename or ".coverage") |
618 self.filename = os.path.abspath(basename or ".coverage") |
612 |
619 |
613 def erase(self, parallel=False): |
620 def erase(self, parallel=False): |
614 """Erase the data from the file storage. |
621 """Erase the data from the file storage. |
673 `self.filename` plus dot as a prefix, and those files are combined. |
680 `self.filename` plus dot as a prefix, and those files are combined. |
674 |
681 |
675 If `data_paths` is not provided, then the directory portion of |
682 If `data_paths` is not provided, then the directory portion of |
676 `self.filename` is used as the directory to search for data files. |
683 `self.filename` is used as the directory to search for data files. |
677 |
684 |
678 Every data file found and combined is then deleted from disk. |
685 Every data file found and combined is then deleted from disk. If a file |
|
686 cannot be read, a warning will be issued, and the file will not be |
|
687 deleted. |
679 |
688 |
680 """ |
689 """ |
681 # Because of the os.path.abspath in the constructor, data_dir will |
690 # Because of the os.path.abspath in the constructor, data_dir will |
682 # never be an empty string. |
691 # never be an empty string. |
683 data_dir, local = os.path.split(self.filename) |
692 data_dir, local = os.path.split(self.filename) |
694 else: |
703 else: |
695 raise CoverageException("Couldn't combine from non-existent path '%s'" % (p,)) |
704 raise CoverageException("Couldn't combine from non-existent path '%s'" % (p,)) |
696 |
705 |
697 for f in files_to_combine: |
706 for f in files_to_combine: |
698 new_data = CoverageData() |
707 new_data = CoverageData() |
699 new_data.read_file(f) |
708 try: |
700 data.update(new_data, aliases=aliases) |
709 new_data.read_file(f) |
701 file_be_gone(f) |
710 except CoverageException as exc: |
|
711 if self.warn: |
|
712 # The CoverageException has the file name in it, so just |
|
713 # use the message as the warning. |
|
714 self.warn(str(exc)) |
|
715 else: |
|
716 data.update(new_data, aliases=aliases) |
|
717 file_be_gone(f) |
702 |
718 |
703 |
719 |
704 def canonicalize_json_data(data): |
720 def canonicalize_json_data(data): |
705 """Canonicalize our JSON data so it can be compared.""" |
721 """Canonicalize our JSON data so it can be compared.""" |
706 for fname, lines in iitems(data.get('lines', {})): |
722 for fname, lines in iitems(data.get('lines', {})): |