| 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()
|
|
|