DebugClients/Python/coverage/data.py

changeset 6219
d6c795b5ce33
parent 5178
878ce843ca9f
equal deleted inserted replaced
6218:bedab77d0fa3 6219:d6c795b5ce33
414 if not self._runs: 414 if not self._runs:
415 self._runs = [{}] 415 self._runs = [{}]
416 self._runs[0].update(kwargs) 416 self._runs[0].update(kwargs)
417 self._validate() 417 self._validate()
418 418
419 def touch_file(self, filename): 419 def touch_file(self, filename, plugin_name=""):
420 """Ensure that `filename` appears in the data, empty if needed.""" 420 """Ensure that `filename` appears in the data, empty if needed.
421
422 `plugin_name` is the name of the plugin resposible for this file. It is used
423 to associate the right filereporter, etc.
424 """
421 if self._debug and self._debug.should('dataop'): 425 if self._debug and self._debug.should('dataop'):
422 self._debug.write("Touching %r" % (filename,)) 426 self._debug.write("Touching %r" % (filename,))
423 if not self._has_arcs() and not self._has_lines(): 427 if not self._has_arcs() and not self._has_lines():
424 raise CoverageException("Can't touch files in an empty CoverageData") 428 raise CoverageException("Can't touch files in an empty CoverageData")
425 429
426 if self._has_arcs(): 430 if self._has_arcs():
427 where = self._arcs 431 where = self._arcs
428 else: 432 else:
429 where = self._lines 433 where = self._lines
430 where.setdefault(filename, []) 434 where.setdefault(filename, [])
435 if plugin_name:
436 # Set the tracer for this file
437 self._file_tracers[filename] = plugin_name
431 438
432 self._validate() 439 self._validate()
433 440
434 def write_fileobj(self, file_obj): 441 def write_fileobj(self, file_obj):
435 """Write the coverage data to `file_obj`.""" 442 """Write the coverage data to `file_obj`."""
449 if self._runs: 456 if self._runs:
450 file_data['runs'] = self._runs 457 file_data['runs'] = self._runs
451 458
452 # Write the data to the file. 459 # Write the data to the file.
453 file_obj.write(self._GO_AWAY) 460 file_obj.write(self._GO_AWAY)
454 json.dump(file_data, file_obj) 461 json.dump(file_data, file_obj, separators=(',', ':'))
455 462
456 def write_file(self, filename): 463 def write_file(self, filename):
457 """Write the coverage data to `filename`.""" 464 """Write the coverage data to `filename`."""
458 if self._debug and self._debug.should('dataio'): 465 if self._debug and self._debug.should('dataio'):
459 self._debug.write("Writing data to %r" % (filename,)) 466 self._debug.write("Writing data to %r" % (filename,))
603 610
604 611
605 class CoverageDataFiles(object): 612 class CoverageDataFiles(object):
606 """Manage the use of coverage data files.""" 613 """Manage the use of coverage data files."""
607 614
608 def __init__(self, basename=None, warn=None): 615 def __init__(self, basename=None, warn=None, debug=None):
609 """Create a CoverageDataFiles to manage data files. 616 """Create a CoverageDataFiles to manage data files.
610 617
611 `warn` is the warning function to use. 618 `warn` is the warning function to use.
612 619
613 `basename` is the name of the file to use for storing data. 620 `basename` is the name of the file to use for storing data.
614 621
622 `debug` is a `DebugControl` object for writing debug messages.
623
615 """ 624 """
616 self.warn = warn 625 self.warn = warn
626 self.debug = debug
627
617 # Construct the file name that will be used for data storage. 628 # Construct the file name that will be used for data storage.
618 self.filename = os.path.abspath(basename or ".coverage") 629 self.filename = os.path.abspath(basename or ".coverage")
619 630
620 def erase(self, parallel=False): 631 def erase(self, parallel=False):
621 """Erase the data from the file storage. 632 """Erase the data from the file storage.
622 633
623 If `parallel` is true, then also deletes data files created from the 634 If `parallel` is true, then also deletes data files created from the
624 basename by parallel-mode. 635 basename by parallel-mode.
625 636
626 """ 637 """
638 if self.debug and self.debug.should('dataio'):
639 self.debug.write("Erasing data file %r" % (self.filename,))
627 file_be_gone(self.filename) 640 file_be_gone(self.filename)
628 if parallel: 641 if parallel:
629 data_dir, local = os.path.split(self.filename) 642 data_dir, local = os.path.split(self.filename)
630 localdot = local + '.*' 643 localdot = local + '.*'
631 pattern = os.path.join(os.path.abspath(data_dir), localdot) 644 pattern = os.path.join(os.path.abspath(data_dir), localdot)
632 for filename in glob.glob(pattern): 645 for filename in glob.glob(pattern):
646 if self.debug and self.debug.should('dataio'):
647 self.debug.write("Erasing parallel data file %r" % (filename,))
633 file_be_gone(filename) 648 file_be_gone(filename)
634 649
635 def read(self, data): 650 def read(self, data):
636 """Read the coverage data.""" 651 """Read the coverage data."""
637 if os.path.exists(self.filename): 652 if os.path.exists(self.filename):
655 extra = "" 670 extra = ""
656 if _TEST_NAME_FILE: # pragma: debugging 671 if _TEST_NAME_FILE: # pragma: debugging
657 with open(_TEST_NAME_FILE) as f: 672 with open(_TEST_NAME_FILE) as f:
658 test_name = f.read() 673 test_name = f.read()
659 extra = "." + test_name 674 extra = "." + test_name
660 suffix = "%s%s.%s.%06d" % ( 675 dice = random.Random(os.urandom(8)).randint(0, 999999)
661 socket.gethostname(), extra, os.getpid(), 676 suffix = "%s%s.%s.%06d" % (socket.gethostname(), extra, os.getpid(), dice)
662 random.randint(0, 999999)
663 )
664 677
665 if suffix: 678 if suffix:
666 filename += "." + suffix 679 filename += "." + suffix
667 data.write_file(filename) 680 data.write_file(filename)
668 681
669 def combine_parallel_data(self, data, aliases=None, data_paths=None): 682 def combine_parallel_data(self, data, aliases=None, data_paths=None, strict=False):
670 """Combine a number of data files together. 683 """Combine a number of data files together.
671 684
672 Treat `self.filename` as a file prefix, and combine the data from all 685 Treat `self.filename` as a file prefix, and combine the data from all
673 of the data files starting with that prefix plus a dot. 686 of the data files starting with that prefix plus a dot.
674 687
683 `self.filename` is used as the directory to search for data files. 696 `self.filename` is used as the directory to search for data files.
684 697
685 Every data file found and combined is then deleted from disk. If a file 698 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 699 cannot be read, a warning will be issued, and the file will not be
687 deleted. 700 deleted.
701
702 If `strict` is true, and no files are found to combine, an error is
703 raised.
688 704
689 """ 705 """
690 # Because of the os.path.abspath in the constructor, data_dir will 706 # Because of the os.path.abspath in the constructor, data_dir will
691 # never be an empty string. 707 # never be an empty string.
692 data_dir, local = os.path.split(self.filename) 708 data_dir, local = os.path.split(self.filename)
701 pattern = os.path.join(os.path.abspath(p), localdot) 717 pattern = os.path.join(os.path.abspath(p), localdot)
702 files_to_combine.extend(glob.glob(pattern)) 718 files_to_combine.extend(glob.glob(pattern))
703 else: 719 else:
704 raise CoverageException("Couldn't combine from non-existent path '%s'" % (p,)) 720 raise CoverageException("Couldn't combine from non-existent path '%s'" % (p,))
705 721
722 if strict and not files_to_combine:
723 raise CoverageException("No data to combine")
724
725 files_combined = 0
706 for f in files_to_combine: 726 for f in files_to_combine:
707 new_data = CoverageData() 727 new_data = CoverageData(debug=self.debug)
708 try: 728 try:
709 new_data.read_file(f) 729 new_data.read_file(f)
710 except CoverageException as exc: 730 except CoverageException as exc:
711 if self.warn: 731 if self.warn:
712 # The CoverageException has the file name in it, so just 732 # The CoverageException has the file name in it, so just
713 # use the message as the warning. 733 # use the message as the warning.
714 self.warn(str(exc)) 734 self.warn(str(exc))
715 else: 735 else:
716 data.update(new_data, aliases=aliases) 736 data.update(new_data, aliases=aliases)
737 files_combined += 1
738 if self.debug and self.debug.should('dataio'):
739 self.debug.write("Deleting combined data file %r" % (f,))
717 file_be_gone(f) 740 file_be_gone(f)
741
742 if strict and not files_combined:
743 raise CoverageException("No usable data files")
718 744
719 745
720 def canonicalize_json_data(data): 746 def canonicalize_json_data(data):
721 """Canonicalize our JSON data so it can be compared.""" 747 """Canonicalize our JSON data so it can be compared."""
722 for fname, lines in iitems(data.get('lines', {})): 748 for fname, lines in iitems(data.get('lines', {})):

eric ide

mercurial