| OLD | NEW |
| (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), []) | |
| OLD | NEW |