summary.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  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. """Summary reporting"""
  4. import sys
  5. from coverage import env
  6. from coverage.report import Reporter
  7. from coverage.results import Numbers
  8. from coverage.misc import NotPython, CoverageException, output_encoding
  9. class SummaryReporter(Reporter):
  10. """A reporter for writing the summary report."""
  11. def __init__(self, coverage, config):
  12. super(SummaryReporter, self).__init__(coverage, config)
  13. self.branches = coverage.data.has_arcs()
  14. def report(self, morfs, outfile=None):
  15. """Writes a report summarizing coverage statistics per module.
  16. `outfile` is a file object to write the summary to. It must be opened
  17. for native strings (bytes on Python 2, Unicode on Python 3).
  18. """
  19. file_reporters = self.find_file_reporters(morfs)
  20. # Prepare the formatting strings, header, and column sorting.
  21. max_name = max([len(fr.relative_filename()) for fr in file_reporters] + [5])
  22. fmt_name = u"%%- %ds " % max_name
  23. fmt_err = u"%s %s: %s"
  24. fmt_skip_covered = u"\n%s file%s skipped due to complete coverage."
  25. header = (fmt_name % "Name") + u" Stmts Miss"
  26. fmt_coverage = fmt_name + u"%6d %6d"
  27. if self.branches:
  28. header += u" Branch BrPart"
  29. fmt_coverage += u" %6d %6d"
  30. width100 = Numbers.pc_str_width()
  31. header += u"%*s" % (width100+4, "Cover")
  32. fmt_coverage += u"%%%ds%%%%" % (width100+3,)
  33. if self.config.show_missing:
  34. header += u" Missing"
  35. fmt_coverage += u" %s"
  36. rule = u"-" * len(header)
  37. column_order = dict(name=0, stmts=1, miss=2, cover=-1)
  38. if self.branches:
  39. column_order.update(dict(branch=3, brpart=4))
  40. if outfile is None:
  41. outfile = sys.stdout
  42. def writeout(line):
  43. """Write a line to the output, adding a newline."""
  44. if env.PY2:
  45. line = line.encode(output_encoding())
  46. outfile.write(line.rstrip())
  47. outfile.write("\n")
  48. # Write the header
  49. writeout(header)
  50. writeout(rule)
  51. # `lines` is a list of pairs, (line text, line values). The line text
  52. # is a string that will be printed, and line values is a tuple of
  53. # sortable values.
  54. lines = []
  55. total = Numbers()
  56. skipped_count = 0
  57. for fr in file_reporters:
  58. try:
  59. analysis = self.coverage._analyze(fr)
  60. nums = analysis.numbers
  61. total += nums
  62. if self.config.skip_covered:
  63. # Don't report on 100% files.
  64. no_missing_lines = (nums.n_missing == 0)
  65. no_missing_branches = (nums.n_partial_branches == 0)
  66. if no_missing_lines and no_missing_branches:
  67. skipped_count += 1
  68. continue
  69. args = (fr.relative_filename(), nums.n_statements, nums.n_missing)
  70. if self.branches:
  71. args += (nums.n_branches, nums.n_partial_branches)
  72. args += (nums.pc_covered_str,)
  73. if self.config.show_missing:
  74. missing_fmtd = analysis.missing_formatted()
  75. if self.branches:
  76. branches_fmtd = analysis.arcs_missing_formatted()
  77. if branches_fmtd:
  78. if missing_fmtd:
  79. missing_fmtd += ", "
  80. missing_fmtd += branches_fmtd
  81. args += (missing_fmtd,)
  82. text = fmt_coverage % args
  83. # Add numeric percent coverage so that sorting makes sense.
  84. args += (nums.pc_covered,)
  85. lines.append((text, args))
  86. except Exception:
  87. report_it = not self.config.ignore_errors
  88. if report_it:
  89. typ, msg = sys.exc_info()[:2]
  90. # NotPython is only raised by PythonFileReporter, which has a
  91. # should_be_python() method.
  92. if typ is NotPython and not fr.should_be_python():
  93. report_it = False
  94. if report_it:
  95. writeout(fmt_err % (fr.relative_filename(), typ.__name__, msg))
  96. # Sort the lines and write them out.
  97. if getattr(self.config, 'sort', None):
  98. position = column_order.get(self.config.sort.lower())
  99. if position is None:
  100. raise CoverageException("Invalid sorting option: {0!r}".format(self.config.sort))
  101. lines.sort(key=lambda l: (l[1][position], l[0]))
  102. for line in lines:
  103. writeout(line[0])
  104. # Write a TOTAl line if we had more than one file.
  105. if total.n_files > 1:
  106. writeout(rule)
  107. args = ("TOTAL", total.n_statements, total.n_missing)
  108. if self.branches:
  109. args += (total.n_branches, total.n_partial_branches)
  110. args += (total.pc_covered_str,)
  111. if self.config.show_missing:
  112. args += ("",)
  113. writeout(fmt_coverage % args)
  114. # Write other final lines.
  115. if not total.n_files and not skipped_count:
  116. raise CoverageException("No data to report.")
  117. if self.config.skip_covered and skipped_count:
  118. writeout(fmt_skip_covered % (skipped_count, 's' if skipped_count > 1 else ''))
  119. return total.n_statements and total.pc_covered