Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(37)

Unified Diff: third_party/pycoverage/coverage/results.py

Issue 727003004: Add python coverage module to third_party (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/pycoverage/coverage/report.py ('k') | third_party/pycoverage/coverage/summary.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/pycoverage/coverage/results.py
diff --git a/third_party/pycoverage/coverage/results.py b/third_party/pycoverage/coverage/results.py
new file mode 100644
index 0000000000000000000000000000000000000000..db6df0d30b7e389319de83ebaaf11fa4c7806e22
--- /dev/null
+++ b/third_party/pycoverage/coverage/results.py
@@ -0,0 +1,286 @@
+"""Results of coverage measurement."""
+
+import os
+
+from coverage.backward import iitems, set, sorted # pylint: disable=W0622
+from coverage.misc import format_lines, join_regex, NoSource
+from coverage.parser import CodeParser
+
+
+class Analysis(object):
+ """The results of analyzing a code unit."""
+
+ def __init__(self, cov, code_unit):
+ self.coverage = cov
+ self.code_unit = code_unit
+
+ self.filename = self.code_unit.filename
+ actual_filename, source = self.find_source(self.filename)
+
+ self.parser = CodeParser(
+ text=source, filename=actual_filename,
+ exclude=self.coverage._exclude_regex('exclude')
+ )
+ self.statements, self.excluded = self.parser.parse_source()
+
+ # Identify missing statements.
+ executed = self.coverage.data.executed_lines(self.filename)
+ exec1 = self.parser.first_lines(executed)
+ self.missing = self.statements - exec1
+
+ if self.coverage.data.has_arcs():
+ self.no_branch = self.parser.lines_matching(
+ join_regex(self.coverage.config.partial_list),
+ join_regex(self.coverage.config.partial_always_list)
+ )
+ n_branches = self.total_branches()
+ mba = self.missing_branch_arcs()
+ n_partial_branches = sum(
+ [len(v) for k,v in iitems(mba) if k not in self.missing]
+ )
+ n_missing_branches = sum([len(v) for k,v in iitems(mba)])
+ else:
+ n_branches = n_partial_branches = n_missing_branches = 0
+ self.no_branch = set()
+
+ self.numbers = Numbers(
+ n_files=1,
+ n_statements=len(self.statements),
+ n_excluded=len(self.excluded),
+ n_missing=len(self.missing),
+ n_branches=n_branches,
+ n_partial_branches=n_partial_branches,
+ n_missing_branches=n_missing_branches,
+ )
+
+ def find_source(self, filename):
+ """Find the source for `filename`.
+
+ Returns two values: the actual filename, and the source.
+
+ The source returned depends on which of these cases holds:
+
+ * The filename seems to be a non-source file: returns None
+
+ * The filename is a source file, and actually exists: returns None.
+
+ * The filename is a source file, and is in a zip file or egg:
+ returns the source.
+
+ * The filename is a source file, but couldn't be found: raises
+ `NoSource`.
+
+ """
+ source = None
+
+ base, ext = os.path.splitext(filename)
+ TRY_EXTS = {
+ '.py': ['.py', '.pyw'],
+ '.pyw': ['.pyw'],
+ }
+ try_exts = TRY_EXTS.get(ext)
+ if not try_exts:
+ return filename, None
+
+ for try_ext in try_exts:
+ try_filename = base + try_ext
+ if os.path.exists(try_filename):
+ return try_filename, None
+ source = self.coverage.file_locator.get_zip_data(try_filename)
+ if source:
+ return try_filename, source
+ raise NoSource("No source for code: '%s'" % filename)
+
+ def missing_formatted(self):
+ """The missing line numbers, formatted nicely.
+
+ Returns a string like "1-2, 5-11, 13-14".
+
+ """
+ return format_lines(self.statements, self.missing)
+
+ def has_arcs(self):
+ """Were arcs measured in this result?"""
+ return self.coverage.data.has_arcs()
+
+ def arc_possibilities(self):
+ """Returns a sorted list of the arcs in the code."""
+ arcs = self.parser.arcs()
+ return arcs
+
+ def arcs_executed(self):
+ """Returns a sorted list of the arcs actually executed in the code."""
+ executed = self.coverage.data.executed_arcs(self.filename)
+ m2fl = self.parser.first_line
+ executed = [(m2fl(l1), m2fl(l2)) for (l1,l2) in executed]
+ return sorted(executed)
+
+ def arcs_missing(self):
+ """Returns a sorted list of the arcs in the code not executed."""
+ possible = self.arc_possibilities()
+ executed = self.arcs_executed()
+ missing = [
+ p for p in possible
+ if p not in executed
+ and p[0] not in self.no_branch
+ ]
+ return sorted(missing)
+
+ def arcs_unpredicted(self):
+ """Returns a sorted list of the executed arcs missing from the code."""
+ possible = self.arc_possibilities()
+ executed = self.arcs_executed()
+ # Exclude arcs here which connect a line to itself. They can occur
+ # in executed data in some cases. This is where they can cause
+ # trouble, and here is where it's the least burden to remove them.
+ unpredicted = [
+ e for e in executed
+ if e not in possible
+ and e[0] != e[1]
+ ]
+ return sorted(unpredicted)
+
+ def branch_lines(self):
+ """Returns a list of line numbers that have more than one exit."""
+ exit_counts = self.parser.exit_counts()
+ return [l1 for l1,count in iitems(exit_counts) if count > 1]
+
+ def total_branches(self):
+ """How many total branches are there?"""
+ exit_counts = self.parser.exit_counts()
+ return sum([count for count in exit_counts.values() if count > 1])
+
+ def missing_branch_arcs(self):
+ """Return arcs that weren't executed from branch lines.
+
+ Returns {l1:[l2a,l2b,...], ...}
+
+ """
+ missing = self.arcs_missing()
+ branch_lines = set(self.branch_lines())
+ mba = {}
+ for l1, l2 in missing:
+ if l1 in branch_lines:
+ if l1 not in mba:
+ mba[l1] = []
+ mba[l1].append(l2)
+ return mba
+
+ def branch_stats(self):
+ """Get stats about branches.
+
+ Returns a dict mapping line numbers to a tuple:
+ (total_exits, taken_exits).
+ """
+
+ exit_counts = self.parser.exit_counts()
+ missing_arcs = self.missing_branch_arcs()
+ stats = {}
+ for lnum in self.branch_lines():
+ exits = exit_counts[lnum]
+ try:
+ missing = len(missing_arcs[lnum])
+ except KeyError:
+ missing = 0
+ stats[lnum] = (exits, exits - missing)
+ return stats
+
+
+class Numbers(object):
+ """The numerical results of measuring coverage.
+
+ This holds the basic statistics from `Analysis`, and is used to roll
+ up statistics across files.
+
+ """
+ # A global to determine the precision on coverage percentages, the number
+ # of decimal places.
+ _precision = 0
+ _near0 = 1.0 # These will change when _precision is changed.
+ _near100 = 99.0
+
+ def __init__(self, n_files=0, n_statements=0, n_excluded=0, n_missing=0,
+ n_branches=0, n_partial_branches=0, n_missing_branches=0
+ ):
+ self.n_files = n_files
+ self.n_statements = n_statements
+ self.n_excluded = n_excluded
+ self.n_missing = n_missing
+ self.n_branches = n_branches
+ self.n_partial_branches = n_partial_branches
+ self.n_missing_branches = n_missing_branches
+
+ def set_precision(cls, precision):
+ """Set the number of decimal places used to report percentages."""
+ assert 0 <= precision < 10
+ cls._precision = precision
+ cls._near0 = 1.0 / 10**precision
+ cls._near100 = 100.0 - cls._near0
+ set_precision = classmethod(set_precision)
+
+ def _get_n_executed(self):
+ """Returns the number of executed statements."""
+ return self.n_statements - self.n_missing
+ n_executed = property(_get_n_executed)
+
+ def _get_n_executed_branches(self):
+ """Returns the number of executed branches."""
+ return self.n_branches - self.n_missing_branches
+ n_executed_branches = property(_get_n_executed_branches)
+
+ def _get_pc_covered(self):
+ """Returns a single percentage value for coverage."""
+ if self.n_statements > 0:
+ pc_cov = (100.0 * (self.n_executed + self.n_executed_branches) /
+ (self.n_statements + self.n_branches))
+ else:
+ pc_cov = 100.0
+ return pc_cov
+ pc_covered = property(_get_pc_covered)
+
+ def _get_pc_covered_str(self):
+ """Returns the percent covered, as a string, without a percent sign.
+
+ Note that "0" is only returned when the value is truly zero, and "100"
+ is only returned when the value is truly 100. Rounding can never
+ result in either "0" or "100".
+
+ """
+ pc = self.pc_covered
+ if 0 < pc < self._near0:
+ pc = self._near0
+ elif self._near100 < pc < 100:
+ pc = self._near100
+ else:
+ pc = round(pc, self._precision)
+ return "%.*f" % (self._precision, pc)
+ pc_covered_str = property(_get_pc_covered_str)
+
+ def pc_str_width(cls):
+ """How many characters wide can pc_covered_str be?"""
+ width = 3 # "100"
+ if cls._precision > 0:
+ width += 1 + cls._precision
+ return width
+ pc_str_width = classmethod(pc_str_width)
+
+ def __add__(self, other):
+ nums = Numbers()
+ nums.n_files = self.n_files + other.n_files
+ nums.n_statements = self.n_statements + other.n_statements
+ nums.n_excluded = self.n_excluded + other.n_excluded
+ nums.n_missing = self.n_missing + other.n_missing
+ nums.n_branches = self.n_branches + other.n_branches
+ nums.n_partial_branches = (
+ self.n_partial_branches + other.n_partial_branches
+ )
+ nums.n_missing_branches = (
+ self.n_missing_branches + other.n_missing_branches
+ )
+ return nums
+
+ def __radd__(self, other):
+ # Implementing 0+Numbers allows us to sum() a list of Numbers.
+ if other == 0:
+ return self
+ return NotImplemented
« no previous file with comments | « third_party/pycoverage/coverage/report.py ('k') | third_party/pycoverage/coverage/summary.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698