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

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

Issue 2707603002: [Predator] Generate all changelogs in regression ranges instead of only matched changelogs (Closed)
Patch Set: . 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
« no previous file with comments | « appengine/findit/crash/suspect.py ('k') | appengine/findit/crash/test/suspect_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 # found in the LICENSE file.
4
5 from collections import defaultdict
6 import copy
7
8 from common.dependency import Dependency
9 from common.dependency import DependencyRoll
10 from common import chrome_dependency_fetcher
11 from crash import changelist_classifier
12 from crash.crash_report import CrashReport
13 from crash.suspect import AnalysisInfo
14 from crash.suspect import StackInfo
15 from crash.suspect import Suspect
16 from crash.stacktrace import CallStack
17 from crash.stacktrace import StackFrame
18 from crash.stacktrace import Stacktrace
19 from crash.test.crash_test_suite import CrashTestSuite
20 from crash.type_enums import CallStackFormatType
21 from crash.type_enums import LanguageType
22 from libs.gitiles.blame import Blame
23 from libs.gitiles.blame import Region
24 from libs.gitiles.change_log import ChangeLog
25 from libs.gitiles.gitiles_repository import GitilesRepository
26
27 DUMMY_CHANGELOG1 = ChangeLog.FromDict({
28 'author': {
29 'name': 'r@chromium.org',
30 'email': 'r@chromium.org',
31 'time': 'Thu Mar 31 21:24:43 2016',
32 },
33 'committer': {
34 'email': 'r@chromium.org',
35 'time': 'Thu Mar 31 21:28:39 2016',
36 'name': 'example@chromium.org',
37 },
38 'message': 'dummy',
39 'commit_position': 175900,
40 'touched_files': [
41 {
42 'change_type': 'add',
43 'new_path': 'a.cc',
44 'old_path': None,
45 },
46 ],
47 'commit_url':
48 'https://repo.test/+/1',
49 'code_review_url': 'https://codereview.chromium.org/3281',
50 'revision': '1',
51 'reverted_revision': None
52 })
53
54 DUMMY_CHANGELOG2 = ChangeLog.FromDict({
55 'author': {
56 'name': 'example@chromium.org',
57 'email': 'example@chromium.org',
58 'time': 'Thu Mar 31 21:24:43 2016',
59 },
60 'committer': {
61 'name': 'example@chromium.org',
62 'email': 'example@chromium.org',
63 'time': 'Thu Mar 31 21:28:39 2016',
64 },
65 'message': 'dummy',
66 'commit_position': 175976,
67 'touched_files': [
68 {
69 'change_type': 'add',
70 'new_path': 'f0.cc',
71 'old_path': 'b/f0.cc'
72 },
73 ],
74 'commit_url':
75 'https://repo.test/+/2',
76 'code_review_url': 'https://codereview.chromium.org/3281',
77 'revision': '2',
78 'reverted_revision': '1'
79 })
80
81 DUMMY_CHANGELOG3 = ChangeLog.FromDict({
82 'author': {
83 'name': 'e@chromium.org',
84 'email': 'e@chromium.org',
85 'time': 'Thu Apr 1 21:24:43 2016',
86 },
87 'committer': {
88 'name': 'example@chromium.org',
89 'email': 'e@chromium.org',
90 'time': 'Thu Apr 1 21:28:39 2016',
91 },
92 'message': 'dummy',
93 'commit_position': 176000,
94 'touched_files': [
95 {
96 'change_type': 'modify',
97 'new_path': 'f.cc',
98 'old_path': 'f.cc'
99 },
100 {
101 'change_type': 'delete',
102 'new_path': None,
103 'old_path': 'f1.cc'
104 },
105 ],
106 'commit_url':
107 'https://repo.test/+/3',
108 'code_review_url': 'https://codereview.chromium.org/3281',
109 'revision': '3',
110 'reverted_revision': None
111 })
112
113 # TODO(wrengr): re crrev.com/2414523002: we need to have a specified
114 # revision_range (even if the versions therein are None), because
115 # ChangelistClassifier.__call__ will take it apart in order to call
116 # GetDEPSRollsDict; if it can't then it will immediately return the
117 # empty list of suspects, breaking many of the tests here. Of course,
118 # taking revision_range apart isn't actually required for the tests,
119 # since we mock GetDEPSRollsDict. So, really what we ought to do in the
120 # long run is redesign things so that GetDEPSRollsDict takes the
121 # CrashReport directly and pulls out the revision_range and platform
122 # itself; that way ChangelistClassifier.__call__ needn't worry about it,
123 # allowing us to clean up the tests here.
124
125 DUMMY_CALLSTACKS = [
126 CallStack(0, [], CallStackFormatType.DEFAULT, LanguageType.CPP),
127 CallStack(1, [], CallStackFormatType.DEFAULT, LanguageType.CPP)]
128 DUMMY_REPORT = CrashReport(
129 'rev', 'sig', 'win', Stacktrace(DUMMY_CALLSTACKS, DUMMY_CALLSTACKS[0]),
130 (None, None), None, None)
131
132
133 class ChangelistClassifierTest(CrashTestSuite):
134
135 @property
136 def _mock_http_gitiles_repo_factory(self):
137 # N.B., we must return a lambda rather than taking ``repo_url``
138 # as an additional argument to the method/property, to ensure that
139 # things have the right arity when this is eventually called. That
140 # is, we want a function of ``repo_url`` which closes over ``self``,
141 # rather than a function taking two arguments at once.
142 return lambda repo_url: GitilesRepository(
143 self.GetMockHttpClient(), repo_url)
144
145 def setUp(self):
146 super(ChangelistClassifierTest, self).setUp()
147 self.changelist_classifier = changelist_classifier.ChangelistClassifier(
148 self._mock_http_gitiles_repo_factory, 7)
149
150 def testSkipAddedAndDeletedRegressionRolls(self):
151 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
152 'GetDependency', lambda *_: {})
153 dep_rolls = {
154 'src/dep': DependencyRoll('src/dep1', 'https://url_dep1', None, '9'),
155 'src/': DependencyRoll('src/', ('https://chromium.googlesource.com/'
156 'chromium/src.git'), '4', '5')
157 }
158 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
159 'GetDependencyRollsDict', lambda *_: dep_rolls)
160
161 passed_in_regression_deps_rolls = []
162 def _MockGetChangeLogsForFilesGroupedByDeps(regression_deps_rolls, *_):
163 passed_in_regression_deps_rolls.append(regression_deps_rolls)
164 return {}, None
165
166 self.mock(changelist_classifier, 'GetChangeLogsForFilesGroupedByDeps',
167 _MockGetChangeLogsForFilesGroupedByDeps)
168 self.mock(changelist_classifier, 'GetStackInfosForFilesGroupedByDeps',
169 lambda *_: {})
170 self.mock(changelist_classifier, 'FindSuspects', lambda *_: None)
171
172 stack = CallStack(0)
173 self.changelist_classifier(
174 CrashReport(crashed_version = '5',
175 signature = 'sig',
176 platform = 'canary',
177 stacktrace = Stacktrace([stack], stack),
178 regression_range = ('4', '5'),
179 dependencies = {},
180 dependency_rolls = {}))
181 expected_regression_deps_rolls = copy.deepcopy(dep_rolls)
182
183 # Regression of a dep added/deleted (old_revision/new_revision is None) can
184 # not be known for sure and this case rarely happens, so just filter them
185 # out.
186 del expected_regression_deps_rolls['src/dep']
187 self.assertEqual(passed_in_regression_deps_rolls[0],
188 expected_regression_deps_rolls)
189
190 def testGetDepsInCrashStack(self):
191 crash_stack = CallStack(0, frame_list=[
192 StackFrame(0, 'src/', 'func0', 'f0.cc', 'src/f0.cc', [1]),
193 StackFrame(1, 'src/', 'func1', 'f1.cc', 'src/f1.cc', [2, 3]),
194 StackFrame(1, '', 'func2', 'f2.cc', 'src/f2.cc', [2, 3])])
195 crash_deps = {'src/': Dependency('src/', 'https://chromium_repo', '1'),
196 'src/v8/': Dependency('src/v8/', 'https://v8_repo', '2')}
197
198 expected_stack_deps = {'src/': crash_deps['src/']}
199
200 self.assertDictEqual(
201 changelist_classifier.GetDepsInCrashStack(crash_stack, crash_deps),
202 expected_stack_deps)
203
204 def testGetChangeLogsForFilesGroupedByDeps(self):
205 regression_deps_rolls = {
206 'src/dep': DependencyRoll('src/dep1', 'https://url_dep1', '7', '9'),
207 'src/': DependencyRoll('src/', ('https://chromium.googlesource.com/'
208 'chromium/src.git'), '4', '5')
209 }
210
211 stack_deps = {
212 'src/': Dependency('src/', 'https://url_src', 'rev1', 'DEPS'),
213 'src/new': Dependency('src/new', 'https://new', 'rev2', 'DEPS'),
214 'src/dep': Dependency('src/dep', 'https://url_dep', 'rev', 'DEPS'),
215 }
216
217 def _MockGetChangeLogs(_, start_rev, end_rev):
218 if start_rev == '4' and end_rev == '5':
219 return [DUMMY_CHANGELOG1, DUMMY_CHANGELOG2, DUMMY_CHANGELOG3]
220
221 return []
222
223 self.mock(GitilesRepository, 'GetChangeLogs', _MockGetChangeLogs)
224
225 dep_file_to_changelogs, ignore_cls = (
226 changelist_classifier.GetChangeLogsForFilesGroupedByDeps(
227 regression_deps_rolls,
228 stack_deps,
229 self._mock_http_gitiles_repo_factory))
230 dep_file_to_changelogs_json = defaultdict(lambda: defaultdict(list))
231 for dep, file_to_changelogs in dep_file_to_changelogs.iteritems():
232 for file_path, changelogs in file_to_changelogs.iteritems():
233 for changelog in changelogs:
234 dep_file_to_changelogs_json[dep][file_path].append(changelog.ToDict())
235
236 expected_dep_file_to_changelogs_json = {
237 'src/': {
238 'a.cc': [DUMMY_CHANGELOG1.ToDict()],
239 'f.cc': [DUMMY_CHANGELOG3.ToDict()]
240 }
241 }
242 self.assertDictEqual(dep_file_to_changelogs_json,
243 expected_dep_file_to_changelogs_json)
244 self.assertSetEqual(ignore_cls, set(['1']))
245
246 def testGetStackInfosForFilesGroupedByDeps(self):
247 main_stack = CallStack(0, frame_list=[
248 StackFrame(0, 'src/', 'c(p* &d)', 'a.cc', 'src/a.cc', [177]),
249 StackFrame(1, 'src/', 'd(a* c)', 'a.cc', 'src/a.cc', [227, 228, 229]),
250 StackFrame(2, 'src/v8/', 'e(int)', 'b.cc', 'src/v8/b.cc', [89, 90])])
251
252 low_priority_stack = CallStack(1, frame_list=[
253 StackFrame(0, 'src/dummy/', 'c(p* &d)', 'd.cc', 'src/dummy/d.cc',
254 [17])])
255
256 stacktrace = Stacktrace([main_stack, low_priority_stack], main_stack)
257
258 crashed_deps = {'src/': Dependency('src/', 'https//repo', '2'),
259 'src/v8/': Dependency('src/v8', 'https//repo', '1')}
260
261 expected_dep_file_to_stack_infos = {
262 'src/': {
263 'a.cc': [
264 StackInfo(main_stack.frames[0], 0),
265 StackInfo(main_stack.frames[1], 0),
266 ],
267 },
268 'src/v8/': {
269 'b.cc': [
270 StackInfo(main_stack.frames[2], 0),
271 ]
272 }
273 }
274
275 dep_file_to_stack_infos = (
276 changelist_classifier.GetStackInfosForFilesGroupedByDeps(
277 stacktrace, crashed_deps))
278
279 self.assertEqual(len(dep_file_to_stack_infos),
280 len(expected_dep_file_to_stack_infos))
281
282 for dep, file_to_stack_infos in dep_file_to_stack_infos.iteritems():
283 self.assertTrue(dep in expected_dep_file_to_stack_infos)
284 expected_file_to_stack_infos = expected_dep_file_to_stack_infos[dep]
285
286 for file_path, stack_infos in file_to_stack_infos.iteritems():
287 self.assertTrue(file_path in expected_file_to_stack_infos)
288 expected_stack_infos = expected_file_to_stack_infos[file_path]
289
290 self._VerifyTwoStackInfosEqual(stack_infos, expected_stack_infos)
291
292 def testFindSuspects(self):
293 dep_file_to_changelogs = {
294 'src/': {
295 'a.cc': [
296 DUMMY_CHANGELOG1,
297 ]
298 }
299 }
300
301 dep_file_to_stack_infos = {
302 'src/': {
303 'a.cc': [
304 StackInfo(StackFrame(
305 0, 'src/', 'func', 'a.cc', 'src/a.cc', [1]), 0),
306 StackInfo(StackFrame(
307 1, 'src/', 'func', 'a.cc', 'src/a.cc', [7]), 0),
308 ],
309 'b.cc': [
310 StackInfo(StackFrame(
311 2, 'src/', 'func', 'b.cc', 'src/b.cc', [36]), 0),
312 ]
313 }
314 }
315
316 dummy_blame = Blame('9', 'a.cc')
317 dummy_blame.AddRegions([
318 Region(1, 5, '6', 'a', 'a@chromium.org', 'Thu Mar 31 21:24:43 2016'),
319 Region(6, 10, '1', 'b', 'b@chromium.org', 'Thu Jun 19 12:11:40 2015')])
320
321 self.mock(GitilesRepository, 'GetBlame', lambda *_: dummy_blame)
322
323 stack_deps = {
324 'src/': Dependency('src/', 'https://url_src', 'rev1', 'DEPS'),
325 }
326
327 expected_suspects = [{
328 'url': 'https://repo.test/+/1',
329 'review_url': 'https://codereview.chromium.org/3281',
330 'revision': '1',
331 'project_path': 'src/',
332 'author': 'r@chromium.org',
333 'time': 'Thu Mar 31 21:24:43 2016',
334 'reasons': None,
335 'confidence': None,
336 'changed_files': None
337 }]
338
339 suspects = changelist_classifier.FindSuspects(
340 dep_file_to_changelogs, dep_file_to_stack_infos, stack_deps,
341 self._mock_http_gitiles_repo_factory)
342 self.assertListEqual([suspect.ToDict() for suspect in suspects],
343 expected_suspects)
344
345 # TODO(http://crbug.com/659346): why do these mocks give coverage
346 # failures? That's almost surely hiding a bug in the tests themselves.
347 def testFindItForCrashNoRegressionRange(self): # pragma: no cover
348 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
349 'GetDependencyRollsDict', lambda *_: {})
350 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
351 'GetDependency', lambda *_: {})
352 # N.B., for this one test we really do want regression_range=None.
353 report = DUMMY_REPORT._replace(regression_range=None)
354 self.assertListEqual(self.changelist_classifier(report), [])
355
356 def testFindItForCrashNoMatchFound(self):
357 self.mock(changelist_classifier, 'FindSuspects', lambda *_: [])
358 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
359 'GetDependencyRollsDict',
360 lambda *_: {'src/': DependencyRoll('src/', 'https://repo', '1', '2')})
361 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
362 'GetDependency', lambda *_: {})
363 self.assertListEqual(self.changelist_classifier(DUMMY_REPORT), [])
364
365 def testFindItForCrash(self):
366
367 def _MockFindSuspects(*_):
368 suspect1 = Suspect(DUMMY_CHANGELOG1, 'src/', confidence=0.)
369 frame1 = StackFrame(0, 'src/', 'func', 'a.cc', 'src/a.cc', [1])
370 frame2 = StackFrame(1, 'src/', 'func', 'a.cc', 'src/a.cc', [7])
371 suspect1.file_to_stack_infos = {
372 'a.cc': [StackInfo(frame1, 0), StackInfo(frame2, 0)]
373 }
374 suspect1.file_to_analysis_info = {
375 'a.cc': AnalysisInfo(min_distance=0, min_distance_frame=frame1)
376 }
377
378 suspect2 = Suspect(DUMMY_CHANGELOG3, 'src/', confidence=0.)
379 frame3 = StackFrame(5, 'src/', 'func', 'f.cc', 'src/f.cc', [1])
380 suspect2.file_to_stack_infos = {
381 'f.cc': [StackInfo(frame3, 0)]
382 }
383 suspect2.file_to_analysis_info = {
384 'a.cc': AnalysisInfo(min_distance=20, min_distance_frame=frame3)
385 }
386
387 return [suspect1, suspect2]
388
389 self.mock(changelist_classifier, 'FindSuspects', _MockFindSuspects)
390 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
391 'GetDependencyRollsDict',
392 lambda *_: {'src/': DependencyRoll('src/', 'https://repo', '1', '2')})
393 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
394 'GetDependency', lambda *_: {})
395 suspects = self.changelist_classifier(DUMMY_REPORT)
396 expected_suspects = [
397 {
398 'reasons': [('TopFrameIndex', 1.0, 'Top frame is #0'),
399 ('MinDistance', 1, 'Minimum distance is 0')],
400 'changed_files': [{'info': 'Minimum distance (LOC) 0, frame #0',
401 'blame_url': None, 'file': 'a.cc'}],
402 'time': 'Thu Mar 31 21:24:43 2016',
403 'author': 'r@chromium.org',
404 'url': 'https://repo.test/+/1',
405 'project_path': 'src/',
406 'review_url': 'https://codereview.chromium.org/3281',
407 'confidence': 1.0, 'revision': '1'
408 },
409 ]
410 self.assertListEqual([suspect.ToDict() for suspect in suspects],
411 expected_suspects)
412
413 def testFinditForCrashFilterZeroConfidentResults(self):
414 def _MockFindSuspects(*_):
415 suspect1 = Suspect(DUMMY_CHANGELOG1, 'src/', confidence=0.)
416 frame1 = StackFrame(0, 'src/', 'func', 'a.cc', 'src/a.cc', [1])
417 frame2 = StackFrame(1, 'src/', 'func', 'a.cc', 'src/a.cc', [7])
418 suspect1.file_to_stack_infos = {
419 'a.cc': [StackInfo(frame1, 0), StackInfo(frame2, 0)]
420 }
421 suspect1.file_to_analysis_info = {
422 'a.cc': AnalysisInfo(min_distance=1, min_distance_frame=frame1)
423 }
424
425 suspect2 = Suspect(DUMMY_CHANGELOG3, 'src/', confidence=0.)
426 frame3 = StackFrame(15, 'src/', 'func', 'f.cc', 'src/f.cc', [1])
427 suspect2.file_to_stack_infos = {
428 'f.cc': [StackInfo(frame3, 0)]
429 }
430 suspect2.file_to_analysis_info = {
431 'f.cc': AnalysisInfo(min_distance=20, min_distance_frame=frame3)
432 }
433
434 suspect3 = Suspect(DUMMY_CHANGELOG3, 'src/', confidence=0.)
435 frame4 = StackFrame(3, 'src/', 'func', 'ff.cc', 'src/ff.cc', [1])
436 suspect3.file_to_stack_infos = {
437 'f.cc': [StackInfo(frame4, 0)]
438 }
439 suspect3.file_to_analysis_info = {
440 'f.cc': AnalysisInfo(min_distance=60, min_distance_frame=frame4)
441 }
442
443 return [suspect1, suspect2, suspect3]
444
445 self.mock(changelist_classifier, 'FindSuspects', _MockFindSuspects)
446 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
447 'GetDependencyRollsDict',
448 lambda *_: {'src/': DependencyRoll('src/', 'https://repo', '1', '2')})
449 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
450 'GetDependency', lambda *_: {})
451
452 suspects = self.changelist_classifier(DUMMY_REPORT)
453 expected_suspects = [
454 {
455 'author': 'r@chromium.org',
456 'changed_files': [
457 {
458 'blame_url': None,
459 'file': 'a.cc',
460 'info': 'Minimum distance (LOC) 1, frame #0'
461 }
462 ],
463 'confidence': 0.8,
464 'project_path': 'src/',
465 'reasons': [
466 ('TopFrameIndex', 1.0, 'Top frame is #0'),
467 ('MinDistance', 0.8, 'Minimum distance is 1')
468 ],
469 'review_url': 'https://codereview.chromium.org/3281',
470 'revision': '1',
471 'time': 'Thu Mar 31 21:24:43 2016',
472 'url': 'https://repo.test/+/1'
473 }
474 ]
475 self.assertListEqual([suspect.ToDict() for suspect in suspects],
476 expected_suspects)
477
478 def testFinditForCrashAllSuspectsWithZeroConfidences(self):
479 """Test that we filter out suspects with too-large frame indices.
480
481 In the mock suspects below we return frames with indices
482 15, 20, 21 which are all larger than the ``max_top_n`` of
483 ``TopFrameIndexScorer``. Therefore we should get a score of zero
484 for that feature, which causes the total score to be zero, and so
485 we should not return these suspects.
486 """
487 def _MockFindSuspects(*_):
488 suspect1 = Suspect(DUMMY_CHANGELOG1, 'src/', confidence=0.)
489 frame1 = StackFrame(20, 'src/', '', 'func', 'a.cc', [1])
490 frame2 = StackFrame(21, 'src/', '', 'func', 'a.cc', [7])
491 suspect1.file_to_stack_infos = {
492 'a.cc': [StackInfo(frame1, 0), StackInfo(frame2, 0)]
493 }
494 suspect1.file_to_analysis_info = {
495 'a.cc': AnalysisInfo(min_distance=1, min_distance_frame=frame1)
496 }
497
498 suspect2 = Suspect(DUMMY_CHANGELOG3, 'src/', confidence=0.)
499 frame3 = StackFrame(15, 'src/', '', 'func', 'f.cc', [1])
500 suspect2.file_to_stack_infos = {
501 'f.cc': [StackInfo(frame3, 0)]
502 }
503 suspect2.min_distance = 20
504 suspect2.file_to_analysis_info = {
505 'f.cc': AnalysisInfo(min_distance=20, min_distance_frame=frame3)
506 }
507
508 return [suspect1, suspect2]
509
510 self.mock(changelist_classifier, 'FindSuspects', _MockFindSuspects)
511 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
512 'GetDependencyRollsDict',
513 lambda *_: {'src/': DependencyRoll('src/', 'https://repo', '1', '2')})
514 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
515 'GetDependency', lambda *_: {})
516
517 self.assertListEqual(self.changelist_classifier(DUMMY_REPORT), [])
OLDNEW
« no previous file with comments | « appengine/findit/crash/suspect.py ('k') | appengine/findit/crash/test/suspect_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698