Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(187)

Side by Side Diff: appengine/findit/crash/test/findit_for_chromecrash_test.py

Issue 2663063007: [Predator] Switch from anonymous dict to CrashData. (Closed)
Patch Set: Rebase. Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
11 from crash import findit 11 from crash import findit
12 from crash import findit_for_chromecrash 12 from crash import findit_for_chromecrash
13 from crash.loglinear.changelist_classifier import LogLinearChangelistClassifier 13 from crash.loglinear.changelist_classifier import LogLinearChangelistClassifier
14 from crash.chromecrash_parser import ChromeCrashParser 14 from crash.chromecrash_parser import ChromeCrashParser
15 from crash.component_classifier import ComponentClassifier 15 from crash.component_classifier import ComponentClassifier
16 from crash.crash_report import CrashReport 16 from crash.crash_report import CrashReport
17 from crash.culprit import Culprit 17 from crash.culprit import Culprit
18 from crash.findit_for_chromecrash import FinditForChromeCrash 18 from crash.findit_for_chromecrash import FinditForChromeCrash
19 from crash.findit_for_chromecrash import FinditForFracas 19 from crash.findit_for_chromecrash import FinditForFracas
20 from crash.project_classifier import ProjectClassifier 20 from crash.project_classifier import ProjectClassifier
21 from crash.suspect import Suspect 21 from crash.suspect import Suspect
22 from crash.stacktrace import CallStack 22 from crash.stacktrace import CallStack
23 from crash.stacktrace import Stacktrace 23 from crash.stacktrace import Stacktrace
24 from crash.test.predator_testcase import PredatorTestCase 24 from crash.test.predator_testcase import PredatorTestCase
25 from crash.type_enums import CrashClient 25 from crash.type_enums import CrashClient
26 from gae_libs.http.http_client_appengine import HttpClientAppengine 26 from gae_libs.http.http_client_appengine import HttpClientAppengine
27 from libs.gitiles.gitiles_repository import GitilesRepository 27 from libs.gitiles.gitiles_repository import GitilesRepository
28 from model import analysis_status 28 from model import analysis_status
29 from model.crash.crash_analysis import CrashAnalysis 29 from model.crash.crash_analysis import CrashAnalysis
30 from model.crash.crash_config import CrashConfig
30 from model.crash.fracas_crash_analysis import FracasCrashAnalysis 31 from model.crash.fracas_crash_analysis import FracasCrashAnalysis
31 32
32 MOCK_GET_REPOSITORY = lambda _: None # pragma: no cover 33 MOCK_GET_REPOSITORY = lambda _: None # pragma: no cover
33 34
35
34 class _FinditForChromeCrash(FinditForChromeCrash): # pylint: disable = W 36 class _FinditForChromeCrash(FinditForChromeCrash): # pylint: disable = W
35 # We allow overriding the default ``get_repository`` because one unittest 37 # We allow overriding the default ``get_repository`` because one unittest
36 # needs to. 38 # needs to.
37 def __init__(self, get_repository=MOCK_GET_REPOSITORY): 39 def __init__(self, get_repository=MOCK_GET_REPOSITORY, config=None):
38 super(_FinditForChromeCrash, self).__init__(get_repository) 40 super(_FinditForChromeCrash, self).__init__(get_repository, config or {})
39 41
40 @classmethod 42 @classmethod
41 def _ClientID(cls): # pragma: no cover 43 def _ClientID(cls): # pragma: no cover
42 """Avoid throwing a NotImplementedError. 44 """Avoid throwing a NotImplementedError.
43 45
44 Since this method is called from ``FinditForChromeCrash.__init__`` 46 Since this method is called from ``FinditForChromeCrash.__init__``
45 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
46 exceptions since we want to be able to test the FinditForChromeCrash 48 exceptions since we want to be able to test the FinditForChromeCrash
47 class itself. 49 class itself.
48 """ 50 """
49 return '' 51 return 'ChromeCrash'
50
51 @property
52 def config(self):
53 """Avoid returning None.
54
55 The default ``Findit.config`` will return None if the client
56 id is not found in the CrashConfig. This in turn will cause
57 ``FinditForChromeCrash.__init__`` to crash, since NoneType doesn't
58 have a ``get`` method. In general it's fine for things to crash, since
59 noone should make instances of Findit subclasses which don't define
60 ``_clientID``; but for this test suite, we want to permit instances
61 of FinditForChromeCrash, so that we can test that class directly.
62 """
63 return {}
64 52
65 53
66 def _FinditForFracas(): 54 def _FinditForFracas(config=None):
67 """A helper to pass in the standard pipeline class.""" 55 """A helper to pass in the standard pipeline class."""
68 return FinditForFracas(MOCK_GET_REPOSITORY) 56 return FinditForFracas(MOCK_GET_REPOSITORY, config or {})
69 57
70 58
71 class FinditForChromeCrashTest(PredatorTestCase): 59 class FinditForChromeCrashTest(PredatorTestCase):
72 60
73 # 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
74 # 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
75 # 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,
76 # then we shouldn't need to mock FindCulprit... 64 # then we shouldn't need to mock FindCulprit...
77 def testFindCulprit(self): 65 def testFindCulprit(self):
78 self.mock(FinditForChromeCrash, 'FindCulprit', lambda self, *_: None) 66 self.mock(FinditForChromeCrash, 'FindCulprit', lambda self, *_: None)
79 67
80 # TODO(wrengr): would be less fragile to call 68 # TODO(wrengr): would be less fragile to call
81 # FinditForFracas.CreateAnalysis instead; though if I'm right about 69 # FinditForFracas.CreateAnalysis instead; though if I'm right about
82 # 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
83 # places where calling FracasCrashAnalysis directly would actually 71 # places where calling FracasCrashAnalysis directly would actually
84 # make sense. 72 # make sense.
85 analysis = FracasCrashAnalysis.Create({'signature': 'sig'}) 73 analysis = FracasCrashAnalysis.Create({'signature': 'sig'})
86 # TODO(wrengr): shouldn't FracasCrashAnalysis.Create already have set 74 findit_client = _FinditForChromeCrash(
87 # the client_id? 75 GitilesRepository.Factory(HttpClientAppengine()), CrashConfig.Get())
88 analysis.client_id = CrashClient.FRACAS
89
90 findit_client = (
91 _FinditForChromeCrash(GitilesRepository.Factory(HttpClientAppengine())))
92 self.assertIsNone(findit_client.FindCulprit(analysis)) 76 self.assertIsNone(findit_client.FindCulprit(analysis))
93 77
94 78
95 class FinditForFracasTest(PredatorTestCase): 79 class FinditForFracasTest(PredatorTestCase):
96 80
97 def testPlatformRename(self): 81 def setUp(self):
98 self.assertEqual(_FinditForFracas().RenamePlatform('linux'), 'unix') 82 super(FinditForFracasTest, self).setUp()
83 self._client = _FinditForFracas(config=CrashConfig.Get())
84
85 def testCheckPolicyBlacklistSignature(self):
86 crash_data = self.GetDummyCrashData(
87 client_id = CrashClient.FRACAS,
88 signature='Blacklist marker signature')
89 crash_buffer = self._client.GetCrashBuffer(crash_data)
90 self.assertFalse(self._client._CheckPolicy(crash_buffer))
99 91
100 def testCheckPolicyUnsupportedPlatform(self): 92 def testCheckPolicyUnsupportedPlatform(self):
101 self.assertIsNone(_FinditForFracas().CheckPolicy(self.GetDummyCrashData( 93 crash_data = self.GetDummyCrashData(
102 platform = 'unsupported_platform'))) 94 client_id = CrashClient.FRACAS,
95 platform='unsupported_platform')
96 crash_buffer = self._client.GetCrashBuffer(crash_data)
97 self.assertFalse(self._client._CheckPolicy(crash_buffer))
103 98
104 def testCheckPolicyBlacklistedSignature(self): 99 def testScheduleNewAnalysisSkipsUnsupportedChannel(self):
105 self.assertIsNone(_FinditForFracas().CheckPolicy(self.GetDummyCrashData( 100 crash_data = self.GetDummyCrashData(
106 signature = 'Blacklist marker signature'))) 101 client_id = CrashClient.FRACAS,
102 channel='unsupported_channel')
103 crash_buffer = self._client.GetCrashBuffer(crash_data)
104 self.assertFalse(self._client._CheckPolicy(crash_buffer))
107 105
108 def testCheckPolicyPlatformRename(self): 106 def testCheckPolicySuccess(self):
109 new_crash_data = _FinditForFracas().CheckPolicy(self.GetDummyCrashData( 107 crash_buffer = self._client.GetCrashBuffer(self.GetDummyCrashData(
110 platform = 'linux')) 108 client_id = CrashClient.FRACAS))
111 self.assertIsNotNone(new_crash_data, 109 self.assertTrue(self._client._CheckPolicy(crash_buffer))
112 'FinditForFracas.CheckPolicy unexpectedly returned None')
113 self.assertEqual(new_crash_data['platform'], 'unix')
114 110
115 def testCreateAnalysis(self): 111 def testCreateAnalysis(self):
116 self.assertIsNotNone(_FinditForFracas().CreateAnalysis( 112 self.assertIsNotNone(self._client.CreateAnalysis(
117 {'signature': 'sig'})) 113 {'signature': 'sig'}))
118 114
119 def testGetAnalysis(self): 115 def testGetAnalysis(self):
120 crash_identifiers = {'signature': 'sig'} 116 crash_identifiers = {'signature': 'sig'}
121 # TODO(wrengr): would be less fragile to call 117 # TODO(wrengr): would be less fragile to call
122 # FinditForFracas.CreateAnalysis instead. 118 # FinditForFracas.CreateAnalysis instead.
123 analysis = FracasCrashAnalysis.Create(crash_identifiers) 119 analysis = FracasCrashAnalysis.Create(crash_identifiers)
124 analysis.put() 120 analysis.put()
125 self.assertEqual(_FinditForFracas().GetAnalysis(crash_identifiers), 121 self.assertEqual(self._client.GetAnalysis(crash_identifiers), analysis)
126 analysis)
127
128 def testInitializeAnalysisForFracas(self):
129 crash_data = self.GetDummyCrashData(platform = 'linux')
130 crash_identifiers = crash_data['crash_identifiers']
131
132 findit_client = _FinditForFracas()
133 analysis = findit_client.CreateAnalysis(crash_identifiers)
134 findit_client._InitializeAnalysis(analysis, crash_data)
135 analysis.put()
136 analysis = findit_client.GetAnalysis(crash_identifiers)
137 self.assertIsNotNone(analysis,
138 'FinditForFracas.GetAnalysis unexpectedly returned None')
139
140 self.assertEqual(analysis.crashed_version, crash_data['chrome_version'])
141 self.assertEqual(analysis.signature, crash_data['signature'])
142 self.assertEqual(analysis.platform, crash_data['platform'])
143 self.assertEqual(analysis.stack_trace, crash_data['stack_trace'])
144 channel = crash_data['customized_data'].get('channel', None)
145 self.assertIsNotNone(channel,
146 'channel is unexpectedly not defined in crash_data')
147 self.assertEqual(analysis.channel, channel)
148
149 def testNeedsNewAnalysisIsTrueIfNoAnalysisYet(self):
150 self.assertTrue(_FinditForFracas()._NeedsNewAnalysis(
151 self.GetDummyCrashData()))
152
153 def testNeedsNewAnalysisIsTrueIfLastOneFailed(self):
154 findit_client = _FinditForFracas()
155 crash_data = self.GetDummyCrashData()
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 = self.GetDummyCrashData()
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 testFindCulpritForChromeCrashEmptyStacktrace(self):
173 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
174 'GetDependency', lambda *_: {})
175 self.mock(ChromeCrashParser, 'Parse', lambda *_: None)
176
177 analysis = CrashAnalysis()
178 analysis.signature = 'signature'
179 analysis.platform = 'win'
180 analysis.stack_trace = 'frame1\nframe2'
181 analysis.crashed_version = '50.0.1234.0'
182 analysis.historical_metadata = [
183 {'chrome_version': '51.0.1234.0', 'cpm': 0.6}]
184 self.assertIsNone(_FinditForChromeCrash().FindCulprit(analysis))
185
186 # TODO(http://crbug.com/659346): why do these mocks give coverage
187 # failures? That's almost surely hiding a bug in the tests themselves.
188 def testFindCulpritForChromeCrash(self): # pragma: no cover
189 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
190 'GetDependency', lambda *_: {})
191 stack = CallStack(0)
192 self.mock(ChromeCrashParser, 'Parse',
193 lambda *_: Stacktrace([stack], stack))
194 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
195 'GetDependencyRollsDict',
196 lambda *_: {
197 'src/': DependencyRoll('src/', 'https://repo', '1', '2'),
198 'src/add': DependencyRoll('src/add', 'https://repo1', None, '2'),
199 'src/delete': DependencyRoll('src/delete', 'https://repo2', '2',
200 None)
201 })
202
203 dummy_suspect = Suspect(self.GetDummyChangeLog(), 'src/')
204 self.mock(LogLinearChangelistClassifier, '__call__',
205 lambda _self, report:
206 [dummy_suspect] if report.regression_range else [])
207
208 self.mock(ComponentClassifier, 'ClassifySuspects', lambda *_: [])
209 self.mock(ComponentClassifier, 'ClassifyCallStack', lambda *_: [])
210 self.mock(ProjectClassifier, 'ClassifySuspects', lambda *_: '')
211 self.mock(ProjectClassifier, 'ClassifyCallStack', lambda *_: '')
212
213 # TODO(wrengr): for both these tests, we should compare Culprit
214 # objects directly rather than calling ToDicts and comparing the
215 # dictionaries.
216 self._testFindCulpritForChromeCrashSucceeds(dummy_suspect)
217 self._testFindCulpritForChromeCrashFails()
218
219 def _testFindCulpritForChromeCrashSucceeds(self, dummy_suspect):
220 analysis = CrashAnalysis()
221 analysis.signature = 'signature'
222 analysis.platform = 'win'
223 analysis.stack_trace = 'frame1\nframe2'
224 analysis.crashed_version = '50.0.1234.0'
225 dummy_regression_range = ('50.0.1233.0', '50.0.1234.0')
226 analysis.regression_range = dummy_regression_range
227 culprit = _FinditForChromeCrash().FindCulprit(analysis)
228 self.assertIsNotNone(culprit, 'FindCulprit failed unexpectedly')
229 suspects, tag = culprit.ToDicts()
230
231 expected_suspects = {
232 'found': True,
233 'suspected_cls': [dummy_suspect.ToDict()],
234 'regression_range': dummy_regression_range
235 }
236 expected_tag = {
237 'found_suspects': True,
238 'found_project': False,
239 'found_components': False,
240 'has_regression_range': True,
241 'solution': 'core_algorithm',
242 }
243
244 self.assertDictEqual(expected_suspects, suspects)
245 self.assertDictEqual(expected_tag, tag)
246
247 def _testFindCulpritForChromeCrashFails(self):
248 analysis = CrashAnalysis()
249 analysis.signature = 'signature'
250 analysis.platform = 'win'
251 analysis.stack_trace = 'frame1\nframe2'
252 analysis.crashed_version = '50.0.1234.0'
253 # N.B., analysis.regression_range is None
254 suspects, tag = _FinditForChromeCrash().FindCulprit(analysis).ToDicts()
255
256 expected_suspects = {'found': False}
257 expected_tag = {
258 'found_suspects': False,
259 'found_project': False,
260 'found_components': False,
261 'has_regression_range': False,
262 'solution': 'core_algorithm',
263 }
264
265 self.assertDictEqual(expected_suspects, suspects)
266 self.assertDictEqual(expected_tag, tag)
267 122
268 @mock.patch('google.appengine.ext.ndb.Key.urlsafe') 123 @mock.patch('google.appengine.ext.ndb.Key.urlsafe')
269 @mock.patch('common.appengine_util.GetDefaultVersionHostname') 124 @mock.patch('common.appengine_util.GetDefaultVersionHostname')
270 def testProcessResultForPublishing(self, mocked_get_default_host, 125 def testProcessResultForPublishing(self, mocked_get_default_host,
271 mocked_urlsafe): 126 mocked_urlsafe):
272 mocked_host = 'http://host' 127 mocked_host = 'http://host'
273 mocked_get_default_host.return_value = mocked_host 128 mocked_get_default_host.return_value = mocked_host
274 urlsafe_key = 'abcde' 129 urlsafe_key = 'abcde'
275 mocked_urlsafe.return_value = urlsafe_key 130 mocked_urlsafe.return_value = urlsafe_key
276 131
277 crash_identifiers = {'signature': 'sig'} 132 crash_identifiers = {'signature': 'sig'}
278 analysis = FracasCrashAnalysis.Create(crash_identifiers) 133 analysis = FracasCrashAnalysis.Create(crash_identifiers)
279 analysis.result = {'other': 'data'} 134 analysis.result = {'other': 'data'}
280 findit_object = FinditForFracas(MOCK_GET_REPOSITORY)
281 expected_processed_suspect = { 135 expected_processed_suspect = {
282 'client_id': findit_object.client_id, 136 'client_id': self._client.client_id,
283 'crash_identifiers': {'signature': 'sig'}, 137 'crash_identifiers': {'signature': 'sig'},
284 'result': { 138 'result': {
285 'feedback_url': ( 139 'feedback_url': (
286 findit_for_chromecrash._FRACAS_FEEDBACK_URL_TEMPLATE % ( 140 findit_for_chromecrash._FRACAS_FEEDBACK_URL_TEMPLATE % (
287 mocked_host, urlsafe_key)), 141 mocked_host, urlsafe_key)),
288 'other': 'data' 142 'other': 'data'
289 } 143 }
290 } 144 }
291 145
292 self.assertDictEqual(findit_object.GetPublishableResult(crash_identifiers, 146 self.assertDictEqual(self._client.GetPublishableResult(crash_identifiers,
293 analysis), 147 analysis),
294 expected_processed_suspect) 148 expected_processed_suspect)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698