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

Side by Side Diff: appengine/findit/handlers/crash/test/crash_handler_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 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 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 base64 5 import base64
6 import copy 6 import copy
7 import json 7 import json
8 import logging 8 import logging
9 9
10 from google.appengine.api import app_identity 10 from google.appengine.api import app_identity
11 from google.appengine.ext import ndb 11 from google.appengine.ext import ndb
12 import webapp2 12 import webapp2
13 from webtest.app import AppError 13 from webtest.app import AppError
14 14
15 from common import chrome_dependency_fetcher 15 from common import chrome_dependency_fetcher
16 from crash import crash_pipeline 16 from crash import crash_pipeline
17 from crash.crash_buffer import CrashBuffer
18 from crash.crash_pipeline import CrashWrapperPipeline
17 from crash.findit import Findit 19 from crash.findit import Findit
18 from crash.findit_for_chromecrash import FinditForFracas 20 from crash.findit_for_chromecrash import FinditForFracas
19 from crash.test.predator_testcase import PredatorTestCase 21 from crash.test.predator_testcase import PredatorTestCase
20 from crash.type_enums import CrashClient 22 from crash.type_enums import CrashClient
21 from handlers.crash import crash_handler 23 from handlers.crash import crash_handler
22 from libs.gitiles import gitiles_repository 24 from libs.gitiles import gitiles_repository
23 from model import analysis_status 25 from model import analysis_status
24 from model.crash.fracas_crash_analysis import FracasCrashAnalysis 26 from model.crash.crash_analysis import CrashAnalysis
25 27 from model.crash.crash_config import CrashConfig
26
27 MOCK_GET_REPOSITORY = lambda _: None # pragma: no cover
28
29
30 class MockCulprit(object):
31 """Construct a fake culprit where ``ToDicts`` returns whatever we please."""
32
33 def __init__(self, mock_result, mock_tags):
34 self._result = mock_result
35 self._tags = mock_tags
36
37 def ToDicts(self): # pragma: no cover
38 return self._result, self._tags
39 28
40 29
41 class CrashHandlerTest(PredatorTestCase): 30 class CrashHandlerTest(PredatorTestCase):
42 app_module = webapp2.WSGIApplication([ 31 app_module = webapp2.WSGIApplication([
43 ('/_ah/push-handlers/crash/fracas', crash_handler.CrashHandler), 32 ('/_ah/push-handlers/crash/fracas', crash_handler.CrashHandler),
44 ], debug=True) 33 ], debug=True)
45 34
46 def testScheduleNewAnalysisWithFailingPolicy(self): 35 def testDoNotScheduleNewAnalysisIfNeedsNewAnalysisReturnsFalse(self):
47 class _MockFindit(Findit): # pylint: disable=W0223 36 mock_findit = self.GetMockFindit()
48 def __init__(self): 37 self.mock(mock_findit, 'NeedsNewAnalysis', lambda _: False)
49 super(_MockFindit, self).__init__(MOCK_GET_REPOSITORY) 38 self.mock(crash_pipeline, 'FinditForClientID', lambda *_: mock_findit)
39 # Check policy failed due to empty client config.
40 self.assertFalse(crash_handler.ScheduleNewAnalysis(
41 self.GetDummyCrashData()))
50 42
51 def CheckPolicy(self, crash_data): 43 def testScheduleNewAnalysisIfNeedsNewAnalysisReturnsTrue(self):
52 """This is the same as inherited, but just to be explicit.""" 44 mock_findit = self.GetMockFindit(client_id=CrashClient.FRACAS)
53 return None 45 self.mock(mock_findit, 'NeedsNewAnalysis', lambda _: True)
46 self.mock(crash_pipeline, 'FinditForClientID', lambda *_: mock_findit)
47 self.assertTrue(crash_handler.ScheduleNewAnalysis(self.GetDummyCrashData(
48 client_id=CrashClient.FRACAS)))
54 49
55 def _NeedsNewAnalysis(self, _crash_data): 50 def testHandlePostScheduleNewAnalysis(self):
56 raise AssertionError('testScheduleNewAnalysisWithFailingPolicy: ' 51 chrome_version = '50.2500.0.0'
57 "called _MockFindit._NeedsNewAnalysis, when it shouldn't.")
58
59 self.mock(crash_pipeline, 'FinditForClientID', lambda *_: _MockFindit())
60 self.assertFalse(crash_handler.ScheduleNewAnalysis(self.GetDummyCrashData(
61 client_id = 'MOCK_CLIENT')))
62
63 def testScheduleNewAnalysisWithPlatformRename(self):
64 original_crash_data = self.GetDummyCrashData(
65 client_id = 'MOCK_CLIENT',
66 version = None,
67 platform = 'unix',
68 crash_identifiers = {})
69 renamed_crash_data = copy.deepcopy(original_crash_data)
70 renamed_crash_data['platform'] = 'linux'
71
72 testcase = self
73 class _MockFindit(Findit): # pylint: disable=W0223
74 def __init__(self):
75 super(_MockFindit, self).__init__(MOCK_GET_REPOSITORY)
76
77 @property
78 def config(self):
79 """Make PlatformRename work as expected."""
80 return {'platform_rename': {'unix': 'linux'}}
81
82 def CheckPolicy(self, crash_data):
83 """Call PlatformRename, and return successfully.
84
85 N.B., if we did not override this method, then our overridden
86 ``_NeedsNewAnalysis`` would never be called either."""
87 # TODO(wrengr): should we clone ``crash_data`` rather than mutating it?
88 crash_data['platform'] = self.RenamePlatform(crash_data['platform'])
89 return crash_data
90
91 def _NeedsNewAnalysis(self, new_crash_data):
92 logging.debug('Called _MockFindit._NeedsNewAnalysis, as desired')
93 testcase.assertDictEqual(new_crash_data, renamed_crash_data)
94 return False
95
96 self.mock(crash_pipeline, 'FinditForClientID',
97 lambda _client_id, repository: _MockFindit())
98 self.assertFalse(crash_handler.ScheduleNewAnalysis(original_crash_data))
99
100 def testScheduleNewAnalysisSkipsUnsupportedChannel(self):
101 self.assertFalse(crash_handler.ScheduleNewAnalysis(self.GetDummyCrashData(
102 client_id = CrashClient.FRACAS,
103 version = None,
104 signature = None,
105 crash_identifiers = {},
106 channel = 'unsupported_channel')))
107
108 def testScheduleNewAnalysisSkipsUnsupportedPlatform(self):
109 self.assertFalse(crash_handler.ScheduleNewAnalysis(self.GetDummyCrashData(
110 client_id = CrashClient.FRACAS,
111 version = None,
112 signature = None,
113 platform = 'unsupported_platform',
114 crash_identifiers = {})))
115
116 def testScheduleNewAnalysisSkipsBlackListSignature(self):
117 self.assertFalse(crash_handler.ScheduleNewAnalysis(self.GetDummyCrashData(
118 client_id = CrashClient.FRACAS,
119 version = None,
120 signature = 'Blacklist marker signature',
121 crash_identifiers = {})))
122
123 def testScheduleNewAnalysisSkipsIfAlreadyCompleted(self):
124 findit_client = FinditForFracas(MOCK_GET_REPOSITORY)
125 crash_data = self.GetDummyCrashData(client_id = findit_client.client_id)
126 crash_identifiers = crash_data['crash_identifiers']
127 analysis = findit_client.CreateAnalysis(crash_identifiers)
128 analysis.status = analysis_status.COMPLETED
129 analysis.put()
130 self.assertFalse(crash_handler.ScheduleNewAnalysis(crash_data))
131
132 def testAnalysisScheduled(self):
133 # We need to mock out the method on Findit itself (rather than using a
134 # subclass), since this method only gets called on objects we
135 # ourselves don't construct.
136 requested_crashes = []
137 def _MockScheduleNewAnalysis(crash_data):
138 requested_crashes.append(crash_data)
139 self.mock(crash_handler, 'ScheduleNewAnalysis', _MockScheduleNewAnalysis)
140
141 self.mock_current_user(user_email='test@chromium.org', is_admin=True)
142
143 channel = 'supported_channel'
144 platform = 'supported_platform'
145 signature = 'signature/here' 52 signature = 'signature/here'
146 chrome_version = '50.2500.0.0' 53 channel = 'canary'
147 crash_data = { 54 platform = 'mac'
148 'client_id': 'fracas', 55 crash_data = self.GetDummyCrashData(
149 'platform': platform, 56 client_id=CrashClient.FRACAS,
150 'signature': signature, 57 channel=channel, platform=platform,
151 'stack_trace': 'frame1\nframe2\nframe3', 58 signature=signature, version=chrome_version,
152 'chrome_version': chrome_version, 59 crash_identifiers={'chrome_version': chrome_version,
153 'crash_identifiers': { 60 'signature': signature,
154 'chrome_version': chrome_version, 61 'channel': channel,
155 'signature': signature, 62 'platform': platform,
156 'channel': channel, 63 'process_type': 'renderer'})
157 'platform': platform,
158 'process_type': 'renderer',
159 },
160 'customized_data': {
161 'channel': channel,
162 'historical_metadata':
163 [{'chrome_version': chrome_version, 'cpm': 0.6}],
164 },
165 }
166 64
167 request_json_data = { 65 request_json_data = {
168 'message': { 66 'message': {
169 'data': base64.b64encode(json.dumps(crash_data)), 67 'data': base64.b64encode(json.dumps(crash_data)),
170 'message_id': 'id', 68 'message_id': 'id',
171 }, 69 },
172 'subscription': 'subscription', 70 'subscription': 'subscription',
173 } 71 }
174 72
73 self.MockPipeline(
74 CrashWrapperPipeline, True,
75 (crash_data['client_id'], crash_data['crash_identifiers']))
76 self.mock(CrashAnalysis, 'Initialize', lambda *_: None)
77
175 self.test_app.post_json('/_ah/push-handlers/crash/fracas', 78 self.test_app.post_json('/_ah/push-handlers/crash/fracas',
176 request_json_data) 79 request_json_data)
177
178 self.assertEqual(1, len(requested_crashes))
179 self.assertEqual(crash_data, requested_crashes[0])
180
181 # TODO: this function is a gross hack. We should figure out what the
182 # semantic goal really is here, so we can avoid doing such intricate
183 # and fragile mocking.
184 def _TestRunningAnalysisForResult(self, analysis_result, analysis_tags):
185
186 # Mock out the part of PublishResultPipeline that would go over the wire.
187 pubsub_publish_requests = []
188 def Mocked_PublishMessagesToTopic(messages_data, topic):
189 pubsub_publish_requests.append((messages_data, topic))
190 self.mock(crash_pipeline.pubsub_util, 'PublishMessagesToTopic',
191 Mocked_PublishMessagesToTopic)
192
193 MOCK_HOST = 'host.com'
194 self.mock(app_identity, 'get_default_version_hostname', lambda: MOCK_HOST)
195
196 testcase = self
197 MOCK_KEY = 'MOCK_KEY'
198
199 # Mock out the wrapper pipeline, so call the other pipelines directly
200 # instead of doing the yielding loop and spawning off processes.
201 def mock_start_pipeline(self, **kwargs):
202 logging.info('Mock running on queue %s', kwargs['queue_name'])
203 analysis_pipeline = crash_pipeline.CrashAnalysisPipeline(
204 self._client_id, self._crash_identifiers)
205 analysis_pipeline.run()
206 analysis_pipeline.finalized()
207
208 testcase.mock(ndb.Key, 'urlsafe', lambda _self: MOCK_KEY)
209 publish_pipeline = crash_pipeline.PublishResultPipeline(
210 self._client_id, self._crash_identifiers)
211 publish_pipeline.run()
212 publish_pipeline.finalized()
213 self.mock(crash_pipeline.CrashWrapperPipeline, 'start', mock_start_pipeline)
214
215 # Mock out FindCulprit to track the number of times it's called and
216 # with which arguments. N.B., the pipeline will reconstruct Findit
217 # objects form their client_id, so we can't mock via subclassing,
218 # we must mock via ``self.mock``.
219 mock_culprit = MockCulprit(analysis_result, analysis_tags)
220 analyzed_crashes = []
221 def _MockFindCulprit(_self, model):
222 analyzed_crashes.append(model)
223 return mock_culprit
224 self.mock(FinditForFracas, 'FindCulprit', _MockFindCulprit)
225
226 # The real ``ParseStacktrace`` calls ``GetChromeDependency``,
227 # which eventually calls ``GitRepository.GetSource`` and hence
228 # goes over the wire. Since we mocked out ``FindCulprit`` to no
229 # longer call ``ParseStacktrace``, it shouldn't matter what the real
230 # ``ParseStacktrace`` does. However, since mocking is fragile and it's
231 # hard to triage what actually went wrong if we do end up going over
232 # the wire, we mock this out too just to be safe.
233 def _MockParseStacktrace(_self, _model):
234 raise AssertionError("ParseStacktrace shouldn't ever be called. "
235 'That it was indicates some sort of problem with our mocking code.')
236 self.mock(FinditForFracas, 'ParseStacktrace', _MockParseStacktrace)
237
238 # More directly address the issue about ``GetChromeDependency`` going
239 # over the wire.
240 def _MockGetChromeDependency(_self, _revision, _platform):
241 raise AssertionError("GetChromeDependency shouldn't ever be called. "
242 'That it was indicates some sort of problem with our mocking code.')
243 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
244 'GetDependency', _MockGetChromeDependency)
245
246 crash_data = self.GetDummyCrashData(
247 client_id = CrashClient.FRACAS,
248 version = '50.2500.0.1',
249 stack_trace = 'frame1\nframe2\nframe3')
250 self.assertTrue(crash_handler.ScheduleNewAnalysis(crash_data))
251
252 # The catch/re-raise is to clean up the callstack that's reported
253 # when things acciddentally go over the wire (and subsequently fail).
254 try:
255 self.execute_queued_tasks()
256 except AppError, e: # pragma: no cover
257 raise e
258
259 self.assertEqual(1, len(pubsub_publish_requests))
260
261 processed_analysis_result = copy.deepcopy(analysis_result)
262 processed_analysis_result['feedback_url'] = (
263 'https://%s/crash/fracas-result-feedback?key=%s' % (MOCK_HOST,
264 MOCK_KEY))
265
266 for cl in processed_analysis_result.get('suspected_cls', []):
267 cl['confidence'] = round(cl['confidence'], 2)
268 cl.pop('reasons', None)
269
270 expected_messages_data = [json.dumps({
271 'crash_identifiers': crash_data['crash_identifiers'],
272 'client_id': CrashClient.FRACAS,
273 'result': processed_analysis_result,
274 }, sort_keys=True)]
275 self.assertListEqual(expected_messages_data, pubsub_publish_requests[0][0])
276 self.assertEqual(1, len(analyzed_crashes))
277 analysis = analyzed_crashes[0]
278 self.assertTrue(isinstance(analysis, FracasCrashAnalysis))
279 self.assertEqual(crash_data['signature'], analysis.signature)
280 self.assertEqual(crash_data['platform'], analysis.platform)
281 self.assertEqual(crash_data['stack_trace'], analysis.stack_trace)
282 self.assertEqual(crash_data['chrome_version'], analysis.crashed_version)
283 self.assertEqual(crash_data['regression_range'], analysis.regression_range)
284
285 analysis = FracasCrashAnalysis.Get(crash_data['crash_identifiers'])
286 self.assertEqual(analysis_result, analysis.result)
287 return analysis
288
289 def testRunningAnalysis(self):
290 analysis_result = {
291 'found': True,
292 'suspected_cls': [],
293 'other_data': 'data',
294 }
295 analysis_tags = {
296 'found_suspects': True,
297 'has_regression_range': True,
298 'solution': 'core',
299 'unsupported_tag': '',
300 }
301
302 analysis = self._TestRunningAnalysisForResult(
303 analysis_result, analysis_tags)
304 self.assertTrue(analysis.has_regression_range)
305 self.assertTrue(analysis.found_suspects)
306 self.assertEqual('core', analysis.solution)
307
308 def testRunningAnalysisNoSuspectsFound(self):
309 analysis_result = {
310 'found': False
311 }
312 analysis_tags = {
313 'found_suspects': False,
314 'has_regression_range': False,
315 'solution': 'core',
316 'unsupported_tag': '',
317 }
318
319 analysis = self._TestRunningAnalysisForResult(
320 analysis_result, analysis_tags)
321 self.assertFalse(analysis.has_regression_range)
322 self.assertFalse(analysis.found_suspects)
323 self.assertEqual('core', analysis.solution)
324
325 def testRunningAnalysisWithSuspectsCls(self):
326 analysis_result = {
327 'found': True,
328 'suspected_cls': [
329 {'confidence': 0.21434,
330 'reasons': ['reason1', 'reason2'],
331 'other': 'data'}
332 ],
333 'other_data': 'data',
334 }
335 analysis_tags = {
336 'found_suspects': True,
337 'has_regression_range': True,
338 'solution': 'core',
339 'unsupported_tag': '',
340 }
341
342 analysis = self._TestRunningAnalysisForResult(
343 analysis_result, analysis_tags)
344 self.assertTrue(analysis.has_regression_range)
345 self.assertTrue(analysis.found_suspects)
346 self.assertEqual('core', analysis.solution)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698