report.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
  2. # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
  3. """Reporter foundation for coverage.py."""
  4. import os
  5. import warnings
  6. from coverage.files import prep_patterns, FnmatchMatcher
  7. from coverage.misc import CoverageException, NoSource, NotPython, isolate_module
  8. os = isolate_module(os)
  9. class Reporter(object):
  10. """A base class for all reporters."""
  11. def __init__(self, coverage, config):
  12. """Create a reporter.
  13. `coverage` is the coverage instance. `config` is an instance of
  14. CoverageConfig, for controlling all sorts of behavior.
  15. """
  16. self.coverage = coverage
  17. self.config = config
  18. # The directory into which to place the report, used by some derived
  19. # classes.
  20. self.directory = None
  21. # Our method find_file_reporters used to set an attribute that other
  22. # code could read. That's been refactored away, but some third parties
  23. # were using that attribute. We'll continue to support it in a noisy
  24. # way for now.
  25. self._file_reporters = []
  26. @property
  27. def file_reporters(self):
  28. """Keep .file_reporters working for private-grabbing tools."""
  29. warnings.warn(
  30. "Report.file_reporters will no longer be available in Coverage.py 4.2",
  31. DeprecationWarning,
  32. )
  33. return self._file_reporters
  34. def find_file_reporters(self, morfs):
  35. """Find the FileReporters we'll report on.
  36. `morfs` is a list of modules or file names.
  37. Returns a list of FileReporters.
  38. """
  39. reporters = self.coverage._get_file_reporters(morfs)
  40. if self.config.include:
  41. matcher = FnmatchMatcher(prep_patterns(self.config.include))
  42. reporters = [fr for fr in reporters if matcher.match(fr.filename)]
  43. if self.config.omit:
  44. matcher = FnmatchMatcher(prep_patterns(self.config.omit))
  45. reporters = [fr for fr in reporters if not matcher.match(fr.filename)]
  46. self._file_reporters = sorted(reporters)
  47. return self._file_reporters
  48. def report_files(self, report_fn, morfs, directory=None):
  49. """Run a reporting function on a number of morfs.
  50. `report_fn` is called for each relative morf in `morfs`. It is called
  51. as::
  52. report_fn(file_reporter, analysis)
  53. where `file_reporter` is the `FileReporter` for the morf, and
  54. `analysis` is the `Analysis` for the morf.
  55. """
  56. file_reporters = self.find_file_reporters(morfs)
  57. if not file_reporters:
  58. raise CoverageException("No data to report.")
  59. self.directory = directory
  60. if self.directory and not os.path.exists(self.directory):
  61. os.makedirs(self.directory)
  62. for fr in file_reporters:
  63. try:
  64. report_fn(fr, self.coverage._analyze(fr))
  65. except NoSource:
  66. if not self.config.ignore_errors:
  67. raise
  68. except NotPython:
  69. # Only report errors for .py files, and only if we didn't
  70. # explicitly suppress those errors.
  71. # NotPython is only raised by PythonFileReporter, which has a
  72. # should_be_python() method.
  73. if fr.should_be_python():
  74. if self.config.ignore_errors:
  75. self.coverage._warn("Could not parse Python file {0}".format(fr.filename))
  76. else:
  77. raise