blob: 48c3a24065649eb8e10b5c16c02300b1a11354f3 [file]
# Copyright 2017 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import logging
from decorators import cached_property
class DependencyAnalyzer(object):
def __init__(self, platform, regression_version, regression_range,
dep_fetcher):
"""Data about the dependencies relating to a crash/regression.
Properties:
_platform (str): The platform name; e.g., 'win', 'mac', 'linux',
'android', 'ios', etc.
_regression_version (str): The version of project in which the
crash/regression occurred.
_regression_range (pair or None): a pair of the last-good and first-bad
versions.
_dependencies (dict): A dict from dependency paths to
``Dependency`` objects. The keys are all those deps which are
used by both the ``regression_version`` of the code, and at least
one frame in the relevant ``stacktrace`` stacks.
_dependency_rolls (dict) A dict from dependency
paths to ``DependencyRoll`` objects. The keys are all those
dependencies which (1) occur in the regression range for the
``platform`` where the crash occurred, (2) neither add nor delete
a dependency, and (3) are also keys of ``dependencies``.
_dep_fetcher (ChromeDependencyFetcher): Dependency fetcher that can fetch
all dependencies related to regression_version.
"""
self._platform = platform
self._regression_version = regression_version
self._regression_range = regression_range
self._dep_fetcher = dep_fetcher
def GetDependencies(self, stacks_list):
"""Get all dependencies that are in the given stacks."""
if not stacks_list:
logging.warning('Cannot get dependencies without stacktrace.')
return {}
return {
frame.dep_path: self.regression_version_deps[frame.dep_path]
for stack in stacks_list
for frame in stack.frames
if frame.dep_path and frame.dep_path in self.regression_version_deps
}
def GetDependencyRolls(self, stacks_list):
"""Gets all dependency rolls of ``dependencies`` in regression range."""
# Short-circuit when we know the deprolls must be empty.
if not self._regression_range or not stacks_list:
logging.warning('Cannot get deps and dep rolls for report without '
'regression range or stacktrace.')
return {}
# Get ``DependencyRoll` objects for all dependencies in the regression
# range (for this particular platform).
regression_range_dep_rolls = self._dep_fetcher.GetDependencyRollsDict(
self._regression_range[0], self._regression_range[1], self._platform)
# Filter out the ones which add or delete a dependency, because we
# can't really be sure whether to blame them or not. This rarely
# happens, so our inability to decide shouldn't be too much of a problem.
def HasBothRevisions(dep_path, dep_roll):
has_both_revisions = bool(dep_roll.old_revision) and bool(
dep_roll.new_revision)
if not has_both_revisions:
logging.info(
'Skip %s dependency %s',
'added' if dep_roll.new_revision else 'deleted',
dep_path)
return has_both_revisions
# Apply the above filter, and also filter to only retain those
# which occur in ``GetDependencies``.
return {
dep_path: dep_roll
for dep_path, dep_roll in regression_range_dep_rolls.iteritems()
if HasBothRevisions(dep_path, dep_roll) and dep_path
in self.GetDependencies(stacks_list)
}
@cached_property
def regression_version_deps(self):
"""Gets all dependencies related to regression_version.
N.B. All dependencies will be returned, no matter whether they appeared in
stacktrace or are related to the crash/regression or not.
"""
return self._dep_fetcher.GetDependency(
self._regression_version, self._platform) if self._dep_fetcher else {}