| 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 from common import chromium_deps | 5 from common import chromium_deps |
| 6 from common.dependency import DependencyRoll | 6 from common.dependency import DependencyRoll |
| 7 from crash import detect_regression_range | 7 from crash.changelist_classifier import ChangelistClassifier |
| 8 from crash import findit_for_chromecrash | 8 from crash.chromecrash_parser import ChromeCrashParser |
| 9 from crash import chromecrash_parser | |
| 10 from crash import findit_for_crash | |
| 11 from crash.component_classifier import ComponentClassifier | 9 from crash.component_classifier import ComponentClassifier |
| 10 from crash.crash_report import CrashReport |
| 11 from crash.culprit import Culprit |
| 12 from crash.culprit import NullCulprit |
| 13 from crash.findit_for_chromecrash import FinditForChromeCrash |
| 14 from crash.findit_for_chromecrash import FinditForFracas |
| 15 from crash.findit import Findit |
| 12 from crash.project_classifier import ProjectClassifier | 16 from crash.project_classifier import ProjectClassifier |
| 13 from crash.results import MatchResult | 17 from crash.results import MatchResult |
| 14 from crash.stacktrace import CallStack | 18 from crash.stacktrace import CallStack |
| 15 from crash.stacktrace import Stacktrace | 19 from crash.stacktrace import Stacktrace |
| 20 from crash.type_enums import CrashClient |
| 21 from crash.test.crash_pipeline_test import DummyCrashData |
| 16 from crash.test.crash_testcase import CrashTestCase | 22 from crash.test.crash_testcase import CrashTestCase |
| 23 from model import analysis_status |
| 24 from model.crash.crash_analysis import CrashAnalysis |
| 25 from model.crash.fracas_crash_analysis import FracasCrashAnalysis |
| 26 |
| 27 # In production we'd use CrashWrapperPipeline. And that'd work fine here, |
| 28 # since we never actually call the method that uses it. But just to be |
| 29 # absolutely sure we don't go over the wire due to some mocking failure, |
| 30 # we'll use this dummy class instead. (In fact, since it's never used, |
| 31 # we don't even need to give a real class; |None| works just fine.) |
| 32 MOCK_PIPELINE_CLS = None |
| 33 |
| 34 class _FinditForChromeCrash(FinditForChromeCrash): |
| 35 def __init__(self): |
| 36 super(_FinditForChromeCrash, self).__init__(MOCK_PIPELINE_CLS) |
| 37 |
| 38 @classmethod |
| 39 def _ClientID(cls): |
| 40 """Avoid throwing a NotImplementedError. |
| 41 |
| 42 Since this method is called from |FinditForChromeCrash.__init__| |
| 43 in order to construct the Azalea object, we need to not throw |
| 44 exceptions since we want to be able to test the FinditForChromeCrash |
| 45 class itself. |
| 46 """ |
| 47 return '' |
| 48 |
| 49 @property |
| 50 def config(self): |
| 51 """Avoid returning None. |
| 52 |
| 53 The default |Findit.config| will return None if the client |
| 54 id is not found in the CrashConfig. This in turn will cause |
| 55 |FinditForChromeCrash.__init__| to crash, since NoneType doesn't |
| 56 have a |get| method. In general it's fine for things to crash, since |
| 57 noone should make instances of Findit subclasses which don't define |
| 58 |_clientID|; but for this test suite, we want to permit instances |
| 59 of FinditForChromeCrash, so that we can test that class directly. |
| 60 """ |
| 61 return {} |
| 62 |
| 63 def _FinditForFracas(): |
| 64 """A helper to pass in the standard pipeline class.""" |
| 65 return FinditForFracas(MOCK_PIPELINE_CLS) |
| 17 | 66 |
| 18 | 67 |
| 19 class FinditForChromeCrashTest(CrashTestCase): | 68 class FinditForChromeCrashTest(CrashTestCase): |
| 20 | 69 |
| 70 # TODO(wrengr): what was the purpose of this test? As written it's |
| 71 # just testing that mocking works. I'm guessing it was to check that |
| 72 # we fail when the analysis is for the wrong client_id; but if so, |
| 73 # then we shouldn't need to mock FindCulprit... |
| 74 def testFindCulprit(self): |
| 75 self.mock(FinditForChromeCrash, 'FindCulprit', |
| 76 lambda self, *_: NullCulprit()) |
| 77 |
| 78 # TODO(wrengr): would be less fragile to call |
| 79 # FinditForFracas.CreateAnalysis instead; though if I'm right about |
| 80 # the original purpose of this test, then this is one of the few |
| 81 # places where calling FracasCrashAnalysis directly would actually |
| 82 # make sense. |
| 83 analysis = FracasCrashAnalysis.Create({'signature': 'sig'}) |
| 84 # TODO(wrengr): shouldn't FracasCrashAnalysis.Create already have set |
| 85 # the client_id? |
| 86 analysis.client_id = CrashClient.FRACAS |
| 87 |
| 88 # TODO(wrengr): just test for the NullCulprit directly; instead of |
| 89 # going through |ToDicts|. |
| 90 result, tags = _FinditForChromeCrash().FindCulprit(analysis).ToDicts() |
| 91 expected_result, expected_tags = NullCulprit().ToDicts() |
| 92 self.assertDictEqual(result, expected_result) |
| 93 self.assertDictEqual(tags, expected_tags) |
| 94 |
| 95 |
| 96 class FinditForFracasTest(CrashTestCase): |
| 97 |
| 98 def testPlatformRename(self): |
| 99 self.assertEqual(_FinditForFracas().RenamePlatform('linux'), 'unix') |
| 100 |
| 101 def testCheckPolicyUnsupportedPlatform(self): |
| 102 self.assertIsNone(_FinditForFracas().CheckPolicy(DummyCrashData( |
| 103 platform = 'unsupported_platform'))) |
| 104 |
| 105 def testCheckPolicyBlacklistedSignature(self): |
| 106 self.assertIsNone(_FinditForFracas().CheckPolicy(DummyCrashData( |
| 107 signature = 'Blacklist marker signature'))) |
| 108 |
| 109 def testCheckPolicyPlatformRename(self): |
| 110 new_crash_data = _FinditForFracas().CheckPolicy(DummyCrashData( |
| 111 platform = 'linux')) |
| 112 self.assertIsNotNone(new_crash_data, |
| 113 'FinditForFracas.CheckPolicy unexpectedly returned None') |
| 114 self.assertEqual(new_crash_data['platform'], 'unix') |
| 115 |
| 116 def testCreateAnalysis(self): |
| 117 self.assertIsNotNone(_FinditForFracas().CreateAnalysis( |
| 118 {'signature': 'sig'})) |
| 119 |
| 120 def testGetAnalysis(self): |
| 121 crash_identifiers = {'signature': 'sig'} |
| 122 # TODO(wrengr): would be less fragile to call |
| 123 # FinditForFracas.CreateAnalysis instead. |
| 124 analysis = FracasCrashAnalysis.Create(crash_identifiers) |
| 125 analysis.put() |
| 126 self.assertEqual(_FinditForFracas().GetAnalysis(crash_identifiers), |
| 127 analysis) |
| 128 |
| 129 def testInitializeAnalysisForFracas(self): |
| 130 crash_data = DummyCrashData(platform = 'linux') |
| 131 crash_identifiers = crash_data['crash_identifiers'] |
| 132 |
| 133 findit_client = _FinditForFracas() |
| 134 analysis = findit_client.CreateAnalysis(crash_identifiers) |
| 135 findit_client._InitializeAnalysis(analysis, crash_data) |
| 136 analysis.put() |
| 137 analysis = findit_client.GetAnalysis(crash_identifiers) |
| 138 self.assertIsNotNone(analysis, |
| 139 'FinditForFracas.GetAnalysis unexpectedly returned None') |
| 140 |
| 141 self.assertEqual(analysis.crashed_version, crash_data['crashed_version']) |
| 142 self.assertEqual(analysis.signature, crash_data['signature']) |
| 143 self.assertEqual(analysis.platform, crash_data['platform']) |
| 144 self.assertEqual(analysis.stack_trace, crash_data['stack_trace']) |
| 145 channel = crash_data['customized_data'].get('channel', None) |
| 146 self.assertIsNotNone(channel, |
| 147 'channel is unexpectedly not defined in crash_data') |
| 148 self.assertEqual(analysis.channel, channel) |
| 149 |
| 150 def testNeedsNewAnalysisIsTrueIfNoAnalysisYet(self): |
| 151 self.assertTrue(_FinditForFracas()._NeedsNewAnalysis(DummyCrashData())) |
| 152 |
| 153 def testNeedsNewAnalysisIsTrueIfLastOneFailed(self): |
| 154 findit_client = _FinditForFracas() |
| 155 crash_data = DummyCrashData() |
| 156 analysis = findit_client.CreateAnalysis(crash_data['crash_identifiers']) |
| 157 analysis.status = analysis_status.ERROR |
| 158 analysis.put() |
| 159 self.assertTrue(findit_client._NeedsNewAnalysis(crash_data)) |
| 160 |
| 161 def testNeedsNewAnalysisIsFalseIfLastOneIsNotFailed(self): |
| 162 findit_client = _FinditForFracas() |
| 163 crash_data = DummyCrashData() |
| 164 crash_identifiers = crash_data['crash_identifiers'] |
| 165 for status in (analysis_status.PENDING, analysis_status.RUNNING, |
| 166 analysis_status.COMPLETED, analysis_status.SKIPPED): |
| 167 analysis = findit_client.CreateAnalysis(crash_identifiers) |
| 168 analysis.status = status |
| 169 analysis.put() |
| 170 self.assertFalse(findit_client._NeedsNewAnalysis(crash_data)) |
| 171 |
| 172 def testScheduleNewAnalysisSkipsUnsupportedChannel(self): |
| 173 self.assertFalse(_FinditForFracas().ScheduleNewAnalysis(DummyCrashData( |
| 174 version = None, |
| 175 signature = None, |
| 176 crash_identifiers = {}, |
| 177 channel = 'unsupported_channel'))) |
| 178 |
| 179 def testScheduleNewAnalysisSkipsUnsupportedPlatform(self): |
| 180 self.assertFalse(_FinditForFracas().ScheduleNewAnalysis(DummyCrashData( |
| 181 version = None, |
| 182 signature = None, |
| 183 platform = 'unsupported_platform', |
| 184 crash_identifiers = {}))) |
| 185 |
| 186 def testScheduleNewAnalysisSkipsBlackListSignature(self): |
| 187 self.assertFalse(_FinditForFracas().ScheduleNewAnalysis(DummyCrashData( |
| 188 version = None, |
| 189 signature = 'Blacklist marker signature', |
| 190 crash_identifiers = {}))) |
| 191 |
| 192 def testScheduleNewAnalysisSkipsIfAlreadyCompleted(self): |
| 193 findit_client = _FinditForFracas() |
| 194 crash_data = DummyCrashData() |
| 195 crash_identifiers = crash_data['crash_identifiers'] |
| 196 analysis = findit_client.CreateAnalysis(crash_identifiers) |
| 197 analysis.status = analysis_status.COMPLETED |
| 198 analysis.put() |
| 199 self.assertFalse(findit_client.ScheduleNewAnalysis(crash_data)) |
| 200 |
| 21 def testFindCulpritForChromeCrashEmptyStacktrace(self): | 201 def testFindCulpritForChromeCrashEmptyStacktrace(self): |
| 22 def _MockGetChromeDependency(*_): | 202 self.mock(chromium_deps, 'GetChromeDependency', lambda *_: {}) |
| 23 return {} | 203 self.mock(ChromeCrashParser, 'Parse', lambda *_: Stacktrace()) |
| 24 | 204 |
| 25 def _MockParse(*_): | 205 # TODO(wrengr): use NullCulprit instead |
| 26 return Stacktrace() | |
| 27 | |
| 28 self.mock(chromium_deps, 'GetChromeDependency', _MockGetChromeDependency) | |
| 29 self.mock(chromecrash_parser.ChromeCrashParser, 'Parse', _MockParse) | |
| 30 | |
| 31 expected_results = {'found': False} | 206 expected_results = {'found': False} |
| 32 expected_tag = {'found_suspects': False, | 207 expected_tag = {'found_suspects': False, |
| 33 'has_regression_range': False} | 208 'has_regression_range': False} |
| 34 | 209 |
| 35 results, tag = findit_for_chromecrash.FinditForChromeCrash().FindCulprit( | 210 analysis = CrashAnalysis() |
| 36 'signature', 'win', 'frame1\nframe2', '50.0.1234.0', | 211 analysis.signature = 'signature' |
| 37 [{'chrome_version': '50.0.1234.0', 'cpm': 0.6}]).ToDicts() | 212 analysis.platform = 'win' |
| 38 | 213 analysis.stack_trace = 'frame1\nframe2' |
| 39 self.assertEqual(expected_results, results) | 214 analysis.crashed_version = '50.0.1234.0' |
| 40 self.assertEqual(expected_tag, tag) | 215 analysis.historical_metadata = [ |
| 216 {'chrome_version': '51.0.1234.0', 'cpm': 0.6}] |
| 217 results, tag = _FinditForChromeCrash().FindCulprit(analysis).ToDicts() |
| 218 |
| 219 self.assertDictEqual(expected_results, results) |
| 220 self.assertDictEqual(expected_tag, tag) |
| 41 | 221 |
| 42 def testFindCulpritForChromeCrash(self): | 222 def testFindCulpritForChromeCrash(self): |
| 43 def _MockGetChromeDependency(*_): | 223 self.mock(chromium_deps, 'GetChromeDependency', lambda *_: {}) |
| 44 return {} | 224 self.mock(ChromeCrashParser, 'Parse', lambda *_: Stacktrace([CallStack(0)])) |
| 45 | 225 self.mock(chromium_deps, 'GetDEPSRollsDict', lambda *_: { |
| 46 def _MockParse(*_): | 226 'src/': DependencyRoll('src/', 'https://repo', '1', '2'), |
| 47 stack = Stacktrace() | 227 'src/add': DependencyRoll('src/add', 'https://repo1', None, '2'), |
| 48 stack.append(CallStack(0)) | 228 'src/delete': DependencyRoll('src/delete', 'https://repo2', '2', None) |
| 49 return stack | 229 }) |
| 50 | |
| 51 def _MockGetDEPSRollsDict(*_): | |
| 52 return {'src/': DependencyRoll('src/', 'https://repo', '1', '2'), | |
| 53 'src/add': DependencyRoll('src/add', 'https://repo1', None, '2'), | |
| 54 'src/delete': DependencyRoll('src/delete', 'https://repo2', | |
| 55 '2', None)} | |
| 56 | 230 |
| 57 dummy_match_result = MatchResult(self.GetDummyChangeLog(), 'src/') | 231 dummy_match_result = MatchResult(self.GetDummyChangeLog(), 'src/') |
| 58 def _MockFindItForCrash(*args): | 232 self.mock(ChangelistClassifier, '__call__', |
| 59 regression_deps_rolls = args[1] | 233 lambda _self, report: |
| 60 if regression_deps_rolls: | 234 [dummy_match_result] if report.regression_range else []) |
| 61 return [dummy_match_result] | 235 |
| 62 | 236 self.mock(ComponentClassifier, 'Classify', lambda *_: []) |
| 63 return [] | 237 self.mock(ProjectClassifier, 'Classify', lambda *_: '') |
| 64 | 238 |
| 65 def _MockComponentClassify(*_): | 239 # TODO(wrengr): for both these tests, we should compare Culprit |
| 66 return [] | 240 # objects directly rather than calling ToDicts and comparing the |
| 67 | 241 # dictionaries. |
| 68 def _MockProjectClassify(*_): | 242 self._testFindCulpritForChromeCrashSucceeds(dummy_match_result) |
| 69 return '' | 243 self._testFindCulpritForChromeCrashFails() |
| 70 | 244 |
| 71 self.mock(chromium_deps, 'GetChromeDependency', _MockGetChromeDependency) | 245 def _testFindCulpritForChromeCrashSucceeds(self, dummy_match_result): |
| 72 self.mock(chromecrash_parser.ChromeCrashParser, 'Parse', _MockParse) | 246 analysis = CrashAnalysis() |
| 73 self.mock(chromium_deps, 'GetDEPSRollsDict', _MockGetDEPSRollsDict) | 247 analysis.signature = 'signature' |
| 74 self.mock(findit_for_crash, 'FindItForCrash', _MockFindItForCrash) | 248 analysis.platform = 'win' |
| 75 | 249 analysis.stack_trace = 'frame1\nframe2' |
| 76 self.mock(ComponentClassifier, 'Classify', _MockComponentClassify) | 250 analysis.crashed_version = '50.0.1234.0' |
| 77 self.mock(ProjectClassifier, 'Classify', _MockProjectClassify) | 251 dummy_regression_range = ['50.0.1233.0', '50.0.1234.0'] |
| 78 | 252 analysis.regression_range = dummy_regression_range |
| 79 expected_results = {'found': False} | 253 results, tag = _FinditForChromeCrash().FindCulprit(analysis).ToDicts() |
| 80 expected_tag = {'found_suspects': False} | 254 |
| 81 | |
| 82 results, tag = findit_for_chromecrash.FinditForChromeCrash().FindCulprit( | |
| 83 'signature', 'win', 'frame1\nframe2', '50.0.1234.0', | |
| 84 ['50.0.1233.0', '50.0.1234.0']).ToDicts() | |
| 85 | |
| 86 # TODO(wrengr): compare the Culprit object directly to these values, | |
| 87 # rather than converting to dicts first. We can make a different | |
| 88 # unit test for comparing the dicts, if we actually need/want to. | |
| 89 expected_results = { | 255 expected_results = { |
| 90 'found': True, | 256 'found': True, |
| 91 'suspected_project': '', | 257 'suspected_project': '', |
| 92 'suspected_components': [], | 258 'suspected_components': [], |
| 93 'suspected_cls': [dummy_match_result.ToDict()], | 259 'suspected_cls': [dummy_match_result.ToDict()], |
| 94 'regression_range': ['50.0.1233.0', '50.0.1234.0'] | 260 'regression_range': dummy_regression_range |
| 95 } | 261 } |
| 96 expected_tag = { | 262 expected_tag = { |
| 97 'found_suspects': True, | 263 'found_suspects': True, |
| 98 'found_project': False, | 264 'found_project': False, |
| 99 'found_components': False, | 265 'found_components': False, |
| 100 'has_regression_range': True, | 266 'has_regression_range': True, |
| 101 'solution': 'core_algorithm', | 267 'solution': 'core_algorithm', |
| 102 } | 268 } |
| 103 | 269 |
| 104 self.assertEqual(expected_results, results) | 270 self.assertDictEqual(expected_results, results) |
| 105 self.assertEqual(expected_tag, tag) | 271 self.assertDictEqual(expected_tag, tag) |
| 106 | 272 |
| 107 results, tag = findit_for_chromecrash.FinditForChromeCrash().FindCulprit( | 273 def _testFindCulpritForChromeCrashFails(self): |
| 108 'signature', 'win', 'frame1\nframe2', '50.0.1234.0', None).ToDicts() | 274 analysis = CrashAnalysis() |
| 275 analysis.signature = 'signature' |
| 276 analysis.platform = 'win' |
| 277 analysis.stack_trace = 'frame1\nframe2' |
| 278 analysis.crashed_version = '50.0.1234.0' |
| 279 results, tag = _FinditForChromeCrash().FindCulprit(analysis).ToDicts() |
| 109 | 280 |
| 110 expected_results = { | 281 expected_results = { |
| 111 'found': False, | 282 'found': False, |
| 112 'suspected_project': '', | 283 'suspected_project': '', |
| 113 'suspected_components': [], | 284 'suspected_components': [], |
| 114 'suspected_cls': [], | 285 'suspected_cls': [], |
| 115 'regression_range': None | 286 'regression_range': None |
| 116 } | 287 } |
| 117 expected_tag = { | 288 expected_tag = { |
| 118 'found_suspects': False, | 289 'found_suspects': False, |
| 119 'found_project': False, | 290 'found_project': False, |
| 120 'found_components': False, | 291 'found_components': False, |
| 121 'has_regression_range': False, | 292 'has_regression_range': False, |
| 122 'solution': 'core_algorithm', | 293 'solution': 'core_algorithm', |
| 123 } | 294 } |
| 124 | 295 |
| 125 self.assertEqual(expected_results, results) | 296 self.assertDictEqual(expected_results, results) |
| 126 self.assertEqual(expected_tag, tag) | 297 self.assertDictEqual(expected_tag, tag) |
| 127 | |
| 128 def testFieldsProperty(self): | |
| 129 culprit = findit_for_chromecrash.Culprit('proj', ['Blink>API'], [], | |
| 130 ['50.0.1234.0', '50.0.1234.1']) | |
| 131 self.assertEqual(culprit.fields, | |
| 132 ('project', 'components', 'cls', 'regression_range')) | |
| OLD | NEW |