Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import copy | 5 import copy |
| 6 import json | 6 import json |
| 7 import logging | 7 import logging |
| 8 | 8 |
| 9 from google.appengine.ext import ndb | 9 from google.appengine.ext import ndb |
| 10 | 10 |
| 11 from common import appengine_util | 11 from common import appengine_util |
| 12 from common.chrome_dependency_fetcher import ChromeDependencyFetcher | 12 from common.chrome_dependency_fetcher import ChromeDependencyFetcher |
| 13 from common import constants | 13 from common import constants |
| 14 from crash.component import Component | |
| 15 from crash.component_classifier import ComponentClassifier | |
| 14 from crash.crash_report import CrashReport | 16 from crash.crash_report import CrashReport |
| 17 from crash.project import Project | |
| 18 from crash.project_classifier import ProjectClassifier | |
| 15 from libs import time_util | 19 from libs import time_util |
| 16 from model import analysis_status | 20 from model import analysis_status |
| 17 from model.crash.crash_config import CrashConfig | 21 from model.crash.crash_config import CrashConfig |
| 18 | 22 |
| 19 | 23 |
| 20 # TODO(http://crbug.com/659346): since most of our unit tests are | 24 # TODO(http://crbug.com/659346): since most of our unit tests are |
| 21 # FinditForFracas-specific, wrengr moved them to findit_for_chromecrash_test.py. | 25 # FinditForFracas-specific, wrengr moved them to findit_for_chromecrash_test.py. |
| 22 # However, now we're missing coverage for most of this file (due to the | 26 # However, now we're missing coverage for most of this file (due to the |
| 23 # buggy way coverage is computed). Need to add a bunch of new unittests | 27 # buggy way coverage is computed). Need to add a bunch of new unittests |
| 24 # to get coverage back up. | 28 # to get coverage back up. |
| 25 | 29 |
| 26 # TODO: this class depends on ndb stuff, and should therefore move to | 30 # TODO: this class depends on ndb stuff, and should therefore move to |
| 27 # cr-culprit-finder/service/predator as part of the big reorganization. | 31 # cr-culprit-finder/service/predator as part of the big reorganization. |
| 28 # This class should be renamed to avoid confustion between Findit and Predator. | 32 # This class should be renamed to avoid confustion between Findit and Predator. |
| 29 # Think of a good name (e.g.'PredatorApp') for this class. | 33 # Think of a good name (e.g.'PredatorApp') for this class. |
| 30 class Findit(object): | 34 class Findit(object): |
| 31 | 35 |
| 32 def __init__(self, get_repository): | 36 def __init__(self, get_repository, config): |
| 33 """ | 37 """ |
| 34 Args: | 38 Args: |
| 35 get_repository (callable): a function from DEP urls to ``Repository`` | 39 get_repository (callable): a function from DEP urls to ``Repository`` |
| 36 objects, so we can get changelogs and blame for each dep. Notably, | 40 objects, so we can get changelogs and blame for each dep. Notably, |
| 37 to keep the code here generic, we make no assumptions about | 41 to keep the code here generic, we make no assumptions about |
| 38 which subclass of ``Repository`` this function returns. Thus, | 42 which subclass of ``Repository`` this function returns. Thus, |
| 39 it is up to the caller to decide what class to return and handle | 43 it is up to the caller to decide what class to return and handle |
| 40 any other arguments that class may require (e.g., an http client | 44 any other arguments that class may require (e.g., an http client |
| 41 for ``GitilesRepository``). | 45 for ``GitilesRepository``). |
|
chanli
2017/02/03 23:58:50
nit: add docstring for config
Sharu Jiang
2017/02/10 23:07:38
Done.
| |
| 42 """ | 46 """ |
| 43 self._get_repository = get_repository | 47 self._get_repository = get_repository |
| 44 self._dep_fetcher = ChromeDependencyFetcher(get_repository) | 48 self._dep_fetcher = ChromeDependencyFetcher(get_repository) |
| 49 | |
| 50 project_classifier_config = config.project_classifier | |
| 51 projects = [Project(name, path_regexs, function_regexs, host_directories) | |
| 52 for name, path_regexs, function_regexs, host_directories | |
| 53 in project_classifier_config['project_path_function_hosts']] | |
| 54 self._project_classifier = ProjectClassifier( | |
| 55 projects, project_classifier_config['top_n'], | |
| 56 project_classifier_config['non_chromium_project_rank_priority']) | |
| 57 | |
| 58 component_classifier_config = config.component_classifier | |
| 59 components = [Component(component_name, path_regex, function_regex) | |
| 60 for path_regex, function_regex, component_name | |
| 61 in component_classifier_config['path_function_component']] | |
| 62 self._component_classifier = ComponentClassifier( | |
| 63 components, component_classifier_config['top_n']) | |
| 64 | |
| 65 self._config = config | |
| 66 | |
| 45 # TODO(http://crbug.com/659354): because self.client is volatile, | 67 # TODO(http://crbug.com/659354): because self.client is volatile, |
| 46 # we need some way of updating the Predator instance whenever the | 68 # we need some way of updating the Predator instance whenever the |
| 47 # config changes. How to do that cleanly? | 69 # config changes. How to do that cleanly? |
| 48 self._predator = None | 70 self._predator = None |
| 49 self._stacktrace_parser = None | 71 self._stacktrace_parser = None |
| 50 | 72 |
| 51 # This is a class method because it should be the same for all | 73 # This is a class method because it should be the same for all |
| 52 # instances of this class. We can in fact call class methods on | 74 # instances of this class. We can in fact call class methods on |
| 53 # instances (not just on the class itself), so we could in principle | 75 # instances (not just on the class itself), so we could in principle |
| 54 # get by with just this method. However, a @classmethod is treated | 76 # get by with just this method. However, a @classmethod is treated |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 80 | 102 |
| 81 N.B., this property is static and should not be overridden.""" | 103 N.B., this property is static and should not be overridden.""" |
| 82 return self._ClientID() | 104 return self._ClientID() |
| 83 | 105 |
| 84 # TODO(http://crbug.com/659354): can we remove the dependency on | 106 # TODO(http://crbug.com/659354): can we remove the dependency on |
| 85 # CrashConfig entirely? It'd be better to receive method calls | 107 # CrashConfig entirely? It'd be better to receive method calls |
| 86 # whenever things change, so that we know the change happened (and | 108 # whenever things change, so that we know the change happened (and |
| 87 # what in particular changed) so that we can update our internal state | 109 # what in particular changed) so that we can update our internal state |
| 88 # as appropriate. | 110 # as appropriate. |
| 89 @property | 111 @property |
| 90 def config(self): | 112 def client_config(self): |
| 91 """Get the current value of the client config for the class of this object. | 113 """Get the current value of the client config for the class of this object. |
| 92 | 114 |
| 93 N.B., this property is volatile and may change asynchronously. | 115 N.B., this property is volatile and may change asynchronously. |
| 94 | 116 |
| 95 If the event of an error this method will return ``None``. That we do | 117 If the event of an error this method will return ``None``. That we do |
| 96 not return the empty dict is intentional. All of the circumstances | 118 not return the empty dict is intentional. All of the circumstances |
| 97 which would lead to this method returning ``None`` indicate | 119 which would lead to this method returning ``None`` indicate |
| 98 underlying bugs in code elsewhere. (E.g., creating instances of | 120 underlying bugs in code elsewhere. (E.g., creating instances of |
| 99 abstract classes, implementing a subclass which is not suppported by | 121 abstract classes, implementing a subclass which is not suppported by |
| 100 ``CrashConfig``, etc.) Because we return ``None``, any subsequent | 122 ``CrashConfig``, etc.) Because we return ``None``, any subsequent |
| 101 calls to ``__getitem__`` or ``get`` will raise method-missing errors; | 123 calls to ``__getitem__`` or ``get`` will raise method-missing errors; |
| 102 which serve to highlight the underlying bug. Whereas, if we silently | 124 which serve to highlight the underlying bug. Whereas, if we silently |
| 103 returned the empty dict, then calls to ``get`` would "work" just | 125 returned the empty dict, then calls to ``get`` would "work" just |
| 104 fine; thereby masking the underlying bug! | 126 fine; thereby masking the underlying bug! |
| 105 """ | 127 """ |
| 106 return CrashConfig.Get().GetClientConfig(self.client_id) | 128 return self._config.GetClientConfig(self.client_id) |
| 107 | 129 |
| 108 # TODO(http://crbug.com/644476): rename to CanonicalizePlatform or | 130 # TODO(http://crbug.com/644476): rename to CanonicalizePlatform or |
| 109 # something like that. | 131 # something like that. |
| 110 def RenamePlatform(self, platform): | 132 def RenamePlatform(self, platform): |
| 111 """Remap the platform to a different one, based on the config.""" | 133 """Remap the platform to a different one, based on the config.""" |
| 112 # TODO(katesonia): Remove the default value after adding validity check to | 134 # TODO(katesonia): Remove the default value after adding validity check to |
| 113 # config. | 135 # config. |
| 114 return self.config.get('platform_rename', {}).get(platform, platform) | 136 return self.client_config.get('platform_rename', {}).get(platform, platform) |
| 115 | 137 |
| 116 def CheckPolicy(self, crash_data): # pylint: disable=W0613 | 138 def CheckPolicy(self, crash_data): # pylint: disable=W0613 |
| 117 """Check whether this client supports analyzing the given report. | 139 """Check whether this client supports analyzing the given report. |
| 118 | 140 |
| 119 Some clients only support analysis for crashes on certain platforms | 141 Some clients only support analysis for crashes on certain platforms |
| 120 or channels, etc. This method checks to see whether this client can | 142 or channels, etc. This method checks to see whether this client can |
| 121 analyze the given crash. The default implementation on the Findit | 143 analyze the given crash. The default implementation on the Findit |
| 122 base class returns None for everything, so that unsupported clients | 144 base class returns None for everything, so that unsupported clients |
| 123 reject everything, as expected. | 145 reject everything, as expected. |
| 124 | 146 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 207 def ParseStacktrace(self, model): # pragma: no cover | 229 def ParseStacktrace(self, model): # pragma: no cover |
| 208 """Parse a CrashAnalysis's ``stack_trace`` string into a Stacktrace object. | 230 """Parse a CrashAnalysis's ``stack_trace`` string into a Stacktrace object. |
| 209 | 231 |
| 210 Args: | 232 Args: |
| 211 model (CrashAnalysis): The model containing the stack_trace string | 233 model (CrashAnalysis): The model containing the stack_trace string |
| 212 to be parsed. | 234 to be parsed. |
| 213 | 235 |
| 214 Returns: | 236 Returns: |
| 215 On success, returns a Stacktrace object; on failure, returns None. | 237 On success, returns a Stacktrace object; on failure, returns None. |
| 216 """ | 238 """ |
| 217 # Use up-to-date ``top_n`` in self.config to filter top n frames. | 239 # Use up-to-date ``top_n`` in self.client_config to filter top n frames. |
| 218 stacktrace = self._stacktrace_parser.Parse( | 240 stacktrace = self._stacktrace_parser.Parse( |
| 219 model.stack_trace, | 241 model.stack_trace, |
| 220 self._dep_fetcher.GetDependency(model.crashed_version, model.platform), | 242 self._dep_fetcher.GetDependency(model.crashed_version, model.platform), |
| 221 model.signature, | 243 model.signature, |
| 222 self.config.get('top_n')) | 244 self.client_config.get('top_n')) |
| 223 if not stacktrace: | 245 if not stacktrace: |
| 224 logging.warning('Failed to parse the stacktrace %s', model.stack_trace) | 246 logging.warning('Failed to parse the stacktrace %s', model.stack_trace) |
| 225 return None | 247 return None |
| 226 | 248 |
| 227 return stacktrace | 249 return stacktrace |
| 228 | 250 |
| 229 def ProcessResultForPublishing(self, result, key): | 251 def ProcessResultForPublishing(self, result, key): |
| 230 """Client specific processing of result data for publishing.""" | 252 """Client specific processing of result data for publishing.""" |
| 231 raise NotImplementedError() | 253 raise NotImplementedError() |
| 232 | 254 |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 272 stacktrace = self.ParseStacktrace(model) | 294 stacktrace = self.ParseStacktrace(model) |
| 273 if stacktrace is None: | 295 if stacktrace is None: |
| 274 return None | 296 return None |
| 275 | 297 |
| 276 return self._predator.FindCulprit(CrashReport( | 298 return self._predator.FindCulprit(CrashReport( |
| 277 crashed_version = model.crashed_version, | 299 crashed_version = model.crashed_version, |
| 278 signature = model.signature, | 300 signature = model.signature, |
| 279 platform = model.platform, | 301 platform = model.platform, |
| 280 stacktrace = stacktrace, | 302 stacktrace = stacktrace, |
| 281 regression_range = model.regression_range)) | 303 regression_range = model.regression_range)) |
| OLD | NEW |