| 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 mock | 5 import mock |
| 6 | 6 |
| 7 from common import chrome_dependency_fetcher | 7 from common import chrome_dependency_fetcher |
| 8 from common.dependency import DependencyRoll | 8 from common.dependency import DependencyRoll |
| 9 from crash import chromecrash_parser | 9 from crash import chromecrash_parser |
| 10 from crash import detect_regression_range | 10 from crash import detect_regression_range |
| (...skipping 30 matching lines...) Expand all Loading... |
| 41 | 41 |
| 42 @classmethod | 42 @classmethod |
| 43 def _ClientID(cls): # pragma: no cover | 43 def _ClientID(cls): # pragma: no cover |
| 44 """Avoid throwing a NotImplementedError. | 44 """Avoid throwing a NotImplementedError. |
| 45 | 45 |
| 46 Since this method is called from ``FinditForChromeCrash.__init__`` | 46 Since this method is called from ``FinditForChromeCrash.__init__`` |
| 47 in order to construct the Azalea object, we need to not throw | 47 in order to construct the Azalea object, we need to not throw |
| 48 exceptions since we want to be able to test the FinditForChromeCrash | 48 exceptions since we want to be able to test the FinditForChromeCrash |
| 49 class itself. | 49 class itself. |
| 50 """ | 50 """ |
| 51 return 'fracas' | 51 return 'ChromeCrash' |
| 52 | |
| 53 @property | |
| 54 def client_config(self): | |
| 55 """Avoid returning None. | |
| 56 | |
| 57 The default ``Findit.client_config`` will return None if the client | |
| 58 id is not found in the CrashConfig. This in turn will cause | |
| 59 ``FinditForChromeCrash.__init__`` to crash, since NoneType doesn't | |
| 60 have a ``get`` method. In general it's fine for things to crash, since | |
| 61 noone should make instances of Findit subclasses which don't define | |
| 62 ``_clientID``; but for this test suite, we want to permit instances | |
| 63 of FinditForChromeCrash, so that we can test that class directly. | |
| 64 """ | |
| 65 return {} | |
| 66 | 52 |
| 67 | 53 |
| 68 def _FinditForFracas(config=None): | 54 def _FinditForFracas(config=None): |
| 69 """A helper to pass in the standard pipeline class.""" | 55 """A helper to pass in the standard pipeline class.""" |
| 70 return FinditForFracas(MOCK_GET_REPOSITORY, config or {}) | 56 return FinditForFracas(MOCK_GET_REPOSITORY, config or {}) |
| 71 | 57 |
| 72 | 58 |
| 73 class FinditForChromeCrashTest(PredatorTestCase): | 59 class FinditForChromeCrashTest(PredatorTestCase): |
| 74 | 60 |
| 75 # TODO(wrengr): what was the purpose of this test? As written it's | 61 # TODO(wrengr): what was the purpose of this test? As written it's |
| 76 # just testing that mocking works. I'm guessing it was to check that | 62 # just testing that mocking works. I'm guessing it was to check that |
| 77 # we fail when the analysis is for the wrong client_id; but if so, | 63 # we fail when the analysis is for the wrong client_id; but if so, |
| 78 # then we shouldn't need to mock FindCulprit... | 64 # then we shouldn't need to mock FindCulprit... |
| 79 def testFindCulprit(self): | 65 def testFindCulprit(self): |
| 80 self.mock(FinditForChromeCrash, 'FindCulprit', lambda self, *_: None) | 66 self.mock(FinditForChromeCrash, 'FindCulprit', lambda self, *_: None) |
| 81 | 67 |
| 82 # TODO(wrengr): would be less fragile to call | 68 # TODO(wrengr): would be less fragile to call |
| 83 # FinditForFracas.CreateAnalysis instead; though if I'm right about | 69 # FinditForFracas.CreateAnalysis instead; though if I'm right about |
| 84 # the original purpose of this test, then this is one of the few | 70 # the original purpose of this test, then this is one of the few |
| 85 # places where calling FracasCrashAnalysis directly would actually | 71 # places where calling FracasCrashAnalysis directly would actually |
| 86 # make sense. | 72 # make sense. |
| 87 analysis = FracasCrashAnalysis.Create({'signature': 'sig'}) | 73 analysis = FracasCrashAnalysis.Create({'signature': 'sig'}) |
| 88 # TODO(wrengr): shouldn't FracasCrashAnalysis.Create already have set | |
| 89 # the client_id? | |
| 90 analysis.client_id = CrashClient.FRACAS | |
| 91 | |
| 92 findit_client = _FinditForChromeCrash( | 74 findit_client = _FinditForChromeCrash( |
| 93 GitilesRepository.Factory(HttpClientAppengine()), | 75 GitilesRepository.Factory(HttpClientAppengine()), CrashConfig.Get()) |
| 94 config=CrashConfig.Get()) | |
| 95 self.assertIsNone(findit_client.FindCulprit(analysis)) | 76 self.assertIsNone(findit_client.FindCulprit(analysis)) |
| 96 | 77 |
| 97 | 78 |
| 98 class FinditForFracasTest(PredatorTestCase): | 79 class FinditForFracasTest(PredatorTestCase): |
| 99 | 80 |
| 100 def setUp(self): | 81 def setUp(self): |
| 101 super(FinditForFracasTest, self).setUp() | 82 super(FinditForFracasTest, self).setUp() |
| 102 self.findit = _FinditForFracas(config=CrashConfig.Get()) | 83 self._client = _FinditForFracas(config=CrashConfig.Get()) |
| 103 | 84 |
| 104 def testPlatformRename(self): | 85 def testCheckPolicyBlacklistSignature(self): |
| 105 self.assertEqual(self.findit.RenamePlatform('linux'), 'unix') | 86 raw_crash_data = self.GetDummyChromeCrashData( |
| 87 client_id = CrashClient.FRACAS, |
| 88 signature='Blacklist marker signature') |
| 89 crash_data = self._client.GetCrashData(raw_crash_data) |
| 90 self.assertFalse(self._client._CheckPolicy(crash_data)) |
| 106 | 91 |
| 107 def testCheckPolicyUnsupportedPlatform(self): | 92 def testCheckPolicyUnsupportedPlatform(self): |
| 108 self.assertIsNone(self.findit.CheckPolicy(self.GetDummyCrashData( | 93 raw_crash_data = self.GetDummyChromeCrashData( |
| 109 platform = 'unsupported_platform'))) | 94 client_id = CrashClient.FRACAS, |
| 95 platform='unsupported_platform') |
| 96 crash_data = self._client.GetCrashData(raw_crash_data) |
| 97 self.assertFalse(self._client._CheckPolicy(crash_data)) |
| 110 | 98 |
| 111 def testCheckPolicyBlacklistedSignature(self): | 99 def testScheduleNewAnalysisSkipsUnsupportedChannel(self): |
| 112 self.assertIsNone(self.findit.CheckPolicy(self.GetDummyCrashData( | 100 raw_crash_data = self.GetDummyChromeCrashData( |
| 113 signature = 'Blacklist marker signature'))) | 101 client_id = CrashClient.FRACAS, |
| 102 channel='unsupported_channel') |
| 103 crash_data = self._client.GetCrashData(raw_crash_data) |
| 104 self.assertFalse(self._client._CheckPolicy(crash_data)) |
| 114 | 105 |
| 115 def testCheckPolicyPlatformRename(self): | 106 def testCheckPolicySuccess(self): |
| 116 new_crash_data = self.findit.CheckPolicy(self.GetDummyCrashData( | 107 crash_data = self._client.GetCrashData(self.GetDummyChromeCrashData( |
| 117 platform = 'linux')) | 108 client_id = CrashClient.FRACAS)) |
| 118 self.assertIsNotNone(new_crash_data, | 109 self.assertTrue(self._client._CheckPolicy(crash_data)) |
| 119 'FinditForFracas.CheckPolicy unexpectedly returned None') | |
| 120 self.assertEqual(new_crash_data['platform'], 'unix') | |
| 121 | 110 |
| 122 def testCreateAnalysis(self): | 111 def testCreateAnalysis(self): |
| 123 self.assertIsNotNone(self.findit.CreateAnalysis( | 112 self.assertIsNotNone(self._client.CreateAnalysis( |
| 124 {'signature': 'sig'})) | 113 {'signature': 'sig'})) |
| 125 | 114 |
| 126 def testGetAnalysis(self): | 115 def testGetAnalysis(self): |
| 127 crash_identifiers = {'signature': 'sig'} | 116 crash_identifiers = {'signature': 'sig'} |
| 128 # TODO(wrengr): would be less fragile to call | 117 # TODO(wrengr): would be less fragile to call |
| 129 # FinditForFracas.CreateAnalysis instead. | 118 # FinditForFracas.CreateAnalysis instead. |
| 130 analysis = FracasCrashAnalysis.Create(crash_identifiers) | 119 analysis = FracasCrashAnalysis.Create(crash_identifiers) |
| 131 analysis.put() | 120 analysis.put() |
| 132 self.assertEqual(self.findit.GetAnalysis(crash_identifiers), | 121 self.assertEqual(self._client.GetAnalysis(crash_identifiers), analysis) |
| 133 analysis) | |
| 134 | |
| 135 def testInitializeAnalysisForFracas(self): | |
| 136 crash_data = self.GetDummyCrashData(platform = 'linux') | |
| 137 crash_identifiers = crash_data['crash_identifiers'] | |
| 138 | |
| 139 self.findit = self.findit | |
| 140 analysis = self.findit.CreateAnalysis(crash_identifiers) | |
| 141 self.findit._InitializeAnalysis(analysis, crash_data) | |
| 142 analysis.put() | |
| 143 analysis = self.findit.GetAnalysis(crash_identifiers) | |
| 144 self.assertIsNotNone(analysis, | |
| 145 'FinditForFracas.GetAnalysis unexpectedly returned None') | |
| 146 | |
| 147 self.assertEqual(analysis.crashed_version, crash_data['chrome_version']) | |
| 148 self.assertEqual(analysis.signature, crash_data['signature']) | |
| 149 self.assertEqual(analysis.platform, crash_data['platform']) | |
| 150 self.assertEqual(analysis.stack_trace, crash_data['stack_trace']) | |
| 151 channel = crash_data['customized_data'].get('channel', None) | |
| 152 self.assertIsNotNone(channel, | |
| 153 'channel is unexpectedly not defined in crash_data') | |
| 154 self.assertEqual(analysis.channel, channel) | |
| 155 | |
| 156 def testNeedsNewAnalysisIsTrueIfNoAnalysisYet(self): | |
| 157 self.assertTrue(self.findit._NeedsNewAnalysis( | |
| 158 self.GetDummyCrashData())) | |
| 159 | |
| 160 def testNeedsNewAnalysisIsTrueIfLastOneFailed(self): | |
| 161 crash_data = self.GetDummyCrashData() | |
| 162 analysis = self.findit.CreateAnalysis(crash_data['crash_identifiers']) | |
| 163 analysis.status = analysis_status.ERROR | |
| 164 analysis.put() | |
| 165 self.assertTrue(self.findit._NeedsNewAnalysis(crash_data)) | |
| 166 | |
| 167 def testNeedsNewAnalysisIsFalseIfLastOneIsNotFailed(self): | |
| 168 crash_data = self.GetDummyCrashData() | |
| 169 crash_identifiers = crash_data['crash_identifiers'] | |
| 170 for status in (analysis_status.PENDING, analysis_status.RUNNING, | |
| 171 analysis_status.COMPLETED, analysis_status.SKIPPED): | |
| 172 analysis = self.findit.CreateAnalysis(crash_identifiers) | |
| 173 analysis.status = status | |
| 174 analysis.put() | |
| 175 self.assertFalse(self.findit._NeedsNewAnalysis(crash_data)) | |
| 176 | |
| 177 def testFindCulpritForChromeCrashEmptyStacktrace(self): | |
| 178 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher, | |
| 179 'GetDependency', lambda *_: {}) | |
| 180 self.mock(ChromeCrashParser, 'Parse', lambda *_: None) | |
| 181 | |
| 182 analysis = CrashAnalysis() | |
| 183 analysis.signature = 'signature' | |
| 184 analysis.platform = 'win' | |
| 185 analysis.stack_trace = 'frame1\nframe2' | |
| 186 analysis.crashed_version = '50.0.1234.0' | |
| 187 analysis.historical_metadata = [ | |
| 188 {'chrome_version': '51.0.1234.0', 'cpm': 0.6}] | |
| 189 self.assertIsNone(_FinditForChromeCrash( | |
| 190 config=CrashConfig.Get()).FindCulprit(analysis)) | |
| 191 | |
| 192 # TODO(http://crbug.com/659346): why do these mocks give coverage | |
| 193 # failures? That's almost surely hiding a bug in the tests themselves. | |
| 194 def testFindCulpritForChromeCrash(self): # pragma: no cover | |
| 195 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher, | |
| 196 'GetDependency', lambda *_: {}) | |
| 197 stack = CallStack(0) | |
| 198 self.mock(ChromeCrashParser, 'Parse', | |
| 199 lambda *_: Stacktrace([stack], stack)) | |
| 200 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher, | |
| 201 'GetDependencyRollsDict', | |
| 202 lambda *_: { | |
| 203 'src/': DependencyRoll('src/', 'https://repo', '1', '2'), | |
| 204 'src/add': DependencyRoll('src/add', 'https://repo1', None, '2'), | |
| 205 'src/delete': DependencyRoll('src/delete', 'https://repo2', '2', | |
| 206 None) | |
| 207 }) | |
| 208 | |
| 209 dummy_suspect = Suspect(self.GetDummyChangeLog(), 'src/') | |
| 210 self.mock(LogLinearChangelistClassifier, '__call__', | |
| 211 lambda _, report: [dummy_suspect] if report.regression_range else []) | |
| 212 | |
| 213 self.mock(ComponentClassifier, 'ClassifySuspects', lambda *_: []) | |
| 214 self.mock(ComponentClassifier, 'ClassifyCallStack', lambda *_: []) | |
| 215 self.mock(ProjectClassifier, 'ClassifySuspects', lambda *_: '') | |
| 216 self.mock(ProjectClassifier, 'ClassifyCallStack', lambda *_: '') | |
| 217 | |
| 218 # TODO(wrengr): for both these tests, we should compare Culprit | |
| 219 # objects directly rather than calling ToDicts and comparing the | |
| 220 # dictionaries. | |
| 221 self._testFindCulpritForChromeCrashSucceeds(dummy_suspect) | |
| 222 self._testFindCulpritForChromeCrashFails() | |
| 223 | |
| 224 def _testFindCulpritForChromeCrashSucceeds(self, dummy_suspect): | |
| 225 analysis = CrashAnalysis() | |
| 226 analysis.signature = 'signature' | |
| 227 analysis.platform = 'win' | |
| 228 analysis.stack_trace = 'frame1\nframe2' | |
| 229 analysis.crashed_version = '50.0.1234.0' | |
| 230 dummy_regression_range = ('50.0.1233.0', '50.0.1234.0') | |
| 231 analysis.regression_range = dummy_regression_range | |
| 232 culprit = _FinditForChromeCrash(config=CrashConfig.Get()).FindCulprit( | |
| 233 analysis) | |
| 234 self.assertIsNotNone(culprit, 'FindCulprit failed unexpectedly') | |
| 235 suspects, tag = culprit.ToDicts() | |
| 236 | |
| 237 expected_suspects = { | |
| 238 'found': True, | |
| 239 'suspected_cls': [dummy_suspect.ToDict()], | |
| 240 'regression_range': dummy_regression_range | |
| 241 } | |
| 242 expected_tag = { | |
| 243 'found_suspects': True, | |
| 244 'found_project': False, | |
| 245 'found_components': False, | |
| 246 'has_regression_range': True, | |
| 247 'solution': 'core_algorithm', | |
| 248 } | |
| 249 | |
| 250 self.assertDictEqual(expected_suspects, suspects) | |
| 251 self.assertDictEqual(expected_tag, tag) | |
| 252 | |
| 253 def _testFindCulpritForChromeCrashFails(self): | |
| 254 analysis = CrashAnalysis() | |
| 255 analysis.signature = 'signature' | |
| 256 analysis.platform = 'win' | |
| 257 analysis.stack_trace = 'frame1\nframe2' | |
| 258 analysis.crashed_version = '50.0.1234.0' | |
| 259 # N.B., analysis.regression_range is None | |
| 260 suspects, tag = _FinditForChromeCrash(config=CrashConfig.Get()).FindCulprit( | |
| 261 analysis).ToDicts() | |
| 262 | |
| 263 expected_suspects = {'found': False} | |
| 264 expected_tag = { | |
| 265 'found_suspects': False, | |
| 266 'found_project': False, | |
| 267 'found_components': False, | |
| 268 'has_regression_range': False, | |
| 269 'solution': 'core_algorithm', | |
| 270 } | |
| 271 | |
| 272 self.assertDictEqual(expected_suspects, suspects) | |
| 273 self.assertDictEqual(expected_tag, tag) | |
| 274 | 122 |
| 275 @mock.patch('google.appengine.ext.ndb.Key.urlsafe') | 123 @mock.patch('google.appengine.ext.ndb.Key.urlsafe') |
| 276 @mock.patch('common.appengine_util.GetDefaultVersionHostname') | 124 @mock.patch('common.appengine_util.GetDefaultVersionHostname') |
| 277 def testProcessResultForPublishing(self, mocked_get_default_host, | 125 def testProcessResultForPublishing(self, mocked_get_default_host, |
| 278 mocked_urlsafe): | 126 mocked_urlsafe): |
| 279 mocked_host = 'http://host' | 127 mocked_host = 'http://host' |
| 280 mocked_get_default_host.return_value = mocked_host | 128 mocked_get_default_host.return_value = mocked_host |
| 281 urlsafe_key = 'abcde' | 129 urlsafe_key = 'abcde' |
| 282 mocked_urlsafe.return_value = urlsafe_key | 130 mocked_urlsafe.return_value = urlsafe_key |
| 283 | 131 |
| 284 crash_identifiers = {'signature': 'sig'} | 132 crash_identifiers = {'signature': 'sig'} |
| 285 analysis = FracasCrashAnalysis.Create(crash_identifiers) | 133 analysis = FracasCrashAnalysis.Create(crash_identifiers) |
| 286 analysis.result = {'other': 'data'} | 134 analysis.result = {'other': 'data'} |
| 287 findit_object = _FinditForFracas(config=CrashConfig.Get()) | |
| 288 expected_processed_suspect = { | 135 expected_processed_suspect = { |
| 289 'client_id': findit_object.client_id, | 136 'client_id': self._client.client_id, |
| 290 'crash_identifiers': {'signature': 'sig'}, | 137 'crash_identifiers': {'signature': 'sig'}, |
| 291 'result': { | 138 'result': { |
| 292 'feedback_url': ( | 139 'feedback_url': ( |
| 293 findit_for_chromecrash._FRACAS_FEEDBACK_URL_TEMPLATE % ( | 140 findit_for_chromecrash._FRACAS_FEEDBACK_URL_TEMPLATE % ( |
| 294 mocked_host, urlsafe_key)), | 141 mocked_host, urlsafe_key)), |
| 295 'other': 'data' | 142 'other': 'data' |
| 296 } | 143 } |
| 297 } | 144 } |
| 298 | 145 |
| 299 self.assertDictEqual(findit_object.GetPublishableResult(crash_identifiers, | 146 self.assertDictEqual(self._client.GetPublishableResult(crash_identifiers, |
| 300 analysis), | 147 analysis), |
| 301 expected_processed_suspect) | 148 expected_processed_suspect) |
| OLD | NEW |