Index: tools/telemetry/third_party/coverage/coverage/results.py |
diff --git a/third_party/pycoverage/coverage/results.py b/tools/telemetry/third_party/coverage/coverage/results.py |
similarity index 62% |
copy from third_party/pycoverage/coverage/results.py |
copy to tools/telemetry/third_party/coverage/coverage/results.py |
index db6df0d30b7e389319de83ebaaf11fa4c7806e22..9627373d253b6fef3f2da3f5e195c107134c7f83 100644 |
--- a/third_party/pycoverage/coverage/results.py |
+++ b/tools/telemetry/third_party/coverage/coverage/results.py |
@@ -1,47 +1,44 @@ |
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 |
+# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt |
+ |
"""Results of coverage measurement.""" |
-import os |
+import collections |
-from coverage.backward import iitems, set, sorted # pylint: disable=W0622 |
-from coverage.misc import format_lines, join_regex, NoSource |
-from coverage.parser import CodeParser |
+from coverage.backward import iitems |
+from coverage.misc import format_lines |
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) |
+ """The results of analyzing a FileReporter.""" |
- self.parser = CodeParser( |
- text=source, filename=actual_filename, |
- exclude=self.coverage._exclude_regex('exclude') |
- ) |
- self.statements, self.excluded = self.parser.parse_source() |
+ def __init__(self, data, file_reporter): |
+ self.data = data |
+ self.file_reporter = file_reporter |
+ self.filename = self.file_reporter.filename |
+ self.statements = self.file_reporter.lines() |
+ self.excluded = self.file_reporter.excluded_lines() |
# 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) |
- ) |
+ executed = self.data.lines(self.filename) or [] |
+ executed = self.file_reporter.translate_lines(executed) |
+ self.missing = self.statements - executed |
+ |
+ if self.data.has_arcs(): |
+ self._arc_possibilities = sorted(self.file_reporter.arcs()) |
+ self.exit_counts = self.file_reporter.exit_counts() |
+ self.no_branch = self.file_reporter.no_branch_lines() |
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] |
+ 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)]) |
+ n_missing_branches = sum(len(v) for k,v in iitems(mba)) |
else: |
- n_branches = n_partial_branches = n_missing_branches = 0 |
+ self._arc_possibilities = [] |
+ self.exit_counts = {} |
self.no_branch = set() |
+ n_branches = n_partial_branches = n_missing_branches = 0 |
self.numbers = Numbers( |
n_files=1, |
@@ -53,44 +50,6 @@ class Analysis(object): |
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. |
@@ -101,31 +60,47 @@ class Analysis(object): |
def has_arcs(self): |
"""Were arcs measured in this result?""" |
- return self.coverage.data.has_arcs() |
+ return self.data.has_arcs() |
def arc_possibilities(self): |
"""Returns a sorted list of the arcs in the code.""" |
- arcs = self.parser.arcs() |
- return arcs |
+ return self._arc_possibilities |
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] |
+ executed = self.data.arcs(self.filename) or [] |
+ executed = self.file_reporter.translate_arcs(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 = [ |
+ missing = ( |
p for p in possible |
if p not in executed |
and p[0] not in self.no_branch |
- ] |
+ ) |
return sorted(missing) |
+ def arcs_missing_formatted(self): |
+ """ The missing branch arcs, formatted nicely. |
+ |
+ Returns a string like "1->2, 1->3, 16->20". Omits any mention of |
+ branches from missing lines, so if line 17 is missing, then 17->18 |
+ won't be included. |
+ |
+ """ |
+ arcs = self.missing_branch_arcs() |
+ missing = self.missing |
+ line_exits = sorted(iitems(arcs)) |
+ pairs = [] |
+ for line, exits in line_exits: |
+ for ex in sorted(exits): |
+ if line not in missing: |
+ pairs.append('%d->%d' % (line, ex)) |
+ return ', '.join(pairs) |
+ |
def arcs_unpredicted(self): |
"""Returns a sorted list of the executed arcs missing from the code.""" |
possible = self.arc_possibilities() |
@@ -133,22 +108,23 @@ class Analysis(object): |
# 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 = [ |
+ # Also, generators can somehow cause arcs from "enter" to "exit", so |
+ # make sure we have at least one positive value. |
+ unpredicted = ( |
e for e in executed |
if e not in possible |
and e[0] != e[1] |
- ] |
+ and (e[0] > 0 or e[1] > 0) |
+ ) |
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] |
+ return [l1 for l1,count in iitems(self.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]) |
+ return sum(count for count in self.exit_counts.values() if count > 1) |
def missing_branch_arcs(self): |
"""Return arcs that weren't executed from branch lines. |
@@ -158,11 +134,9 @@ class Analysis(object): |
""" |
missing = self.arcs_missing() |
branch_lines = set(self.branch_lines()) |
- mba = {} |
+ mba = collections.defaultdict(list) |
for l1, l2 in missing: |
if l1 in branch_lines: |
- if l1 not in mba: |
- mba[l1] = [] |
mba[l1].append(l2) |
return mba |
@@ -173,11 +147,10 @@ class Analysis(object): |
(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] |
+ exits = self.exit_counts[lnum] |
try: |
missing = len(missing_arcs[lnum]) |
except KeyError: |
@@ -210,35 +183,43 @@ class Numbers(object): |
self.n_partial_branches = n_partial_branches |
self.n_missing_branches = n_missing_branches |
+ def init_args(self): |
+ """Return a list for __init__(*args) to recreate this object.""" |
+ return [ |
+ self.n_files, self.n_statements, self.n_excluded, self.n_missing, |
+ self.n_branches, self.n_partial_branches, self.n_missing_branches, |
+ ] |
+ |
+ @classmethod |
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): |
+ @property |
+ def 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): |
+ @property |
+ def 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): |
+ @property |
+ def 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)) |
+ numerator, denominator = self.ratio_covered |
+ pc_cov = (100.0 * numerator) / denominator |
else: |
pc_cov = 100.0 |
return pc_cov |
- pc_covered = property(_get_pc_covered) |
- def _get_pc_covered_str(self): |
+ @property |
+ def 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" |
@@ -254,15 +235,21 @@ class Numbers(object): |
else: |
pc = round(pc, self._precision) |
return "%.*f" % (self._precision, pc) |
- pc_covered_str = property(_get_pc_covered_str) |
+ @classmethod |
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) |
+ |
+ @property |
+ def ratio_covered(self): |
+ """Return a numerator and denominator for the coverage ratio.""" |
+ numerator = self.n_executed + self.n_executed_branches |
+ denominator = self.n_statements + self.n_branches |
+ return numerator, denominator |
def __add__(self, other): |
nums = Numbers() |