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

Side by Side Diff: appengine/findit/handlers/crash/test/crash_handler_test.py

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

Powered by Google App Engine
This is Rietveld 408576698