| OLD | NEW |
| 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 copy | 5 import copy |
| 6 import logging | 6 import logging |
| 7 import math | 7 import math |
| 8 import pprint | |
| 9 | 8 |
| 10 from common.dependency import Dependency | 9 from common.dependency import Dependency |
| 11 from common.dependency import DependencyRoll | 10 from common.dependency import DependencyRoll |
| 12 from common.chrome_dependency_fetcher import ChromeDependencyFetcher | |
| 13 import crash.changelist_classifier as scorer_changelist_classifier | |
| 14 from crash.crash_report import CrashReport | 11 from crash.crash_report import CrashReport |
| 15 from crash.loglinear.changelist_classifier import LogLinearChangelistClassifier | 12 from crash.loglinear.changelist_classifier import LogLinearChangelistClassifier |
| 16 from crash.loglinear.changelist_features.touch_crashed_file_meta import ( | 13 from crash.loglinear.changelist_features.touch_crashed_file_meta import ( |
| 17 TouchCrashedFileMetaFeature) | 14 TouchCrashedFileMetaFeature) |
| 15 from crash.loglinear.feature import MetaFeatureValue |
| 18 from crash.loglinear.feature import WrapperMetaFeature | 16 from crash.loglinear.feature import WrapperMetaFeature |
| 19 from crash.loglinear.weight import Weight | 17 from crash.loglinear.weight import Weight |
| 20 from crash.loglinear.weight import MetaWeight | 18 from crash.loglinear.weight import MetaWeight |
| 21 from crash.suspect import AnalysisInfo | |
| 22 from crash.suspect import Suspect | 19 from crash.suspect import Suspect |
| 23 from crash.suspect import StackInfo | |
| 24 from crash.stacktrace import CallStack | 20 from crash.stacktrace import CallStack |
| 25 from crash.stacktrace import StackFrame | 21 from crash.stacktrace import StackFrame |
| 26 from crash.stacktrace import Stacktrace | 22 from crash.stacktrace import Stacktrace |
| 27 from crash.test.crash_test_suite import CrashTestSuite | 23 from crash.test.crash_test_suite import CrashTestSuite |
| 28 from crash.type_enums import CallStackFormatType | 24 from crash.type_enums import CallStackFormatType |
| 29 from crash.type_enums import LanguageType | 25 from crash.type_enums import LanguageType |
| 30 from libs.gitiles.blame import Blame | |
| 31 from libs.gitiles.blame import Region | |
| 32 from libs.gitiles.change_log import ChangeLog | 26 from libs.gitiles.change_log import ChangeLog |
| 33 from libs.gitiles.gitiles_repository import GitilesRepository | 27 from libs.gitiles.gitiles_repository import GitilesRepository |
| 28 import libs.math.logarithms as lmath |
| 34 | 29 |
| 35 DUMMY_CHANGELOG1 = ChangeLog.FromDict({ | 30 DUMMY_CHANGELOG1 = ChangeLog.FromDict({ |
| 36 'author': { | 31 'author': { |
| 37 'name': 'r@chromium.org', | 32 'name': 'r@chromium.org', |
| 38 'email': 'r@chromium.org', | 33 'email': 'r@chromium.org', |
| 39 'time': 'Thu Mar 31 21:24:43 2016', | 34 'time': 'Thu Mar 31 21:24:43 2016', |
| 40 }, | 35 }, |
| 41 'committer': { | 36 'committer': { |
| 42 'email': 'r@chromium.org', | 37 'email': 'r@chromium.org', |
| 43 'time': 'Thu Mar 31 21:28:39 2016', | 38 'time': 'Thu Mar 31 21:28:39 2016', |
| (...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 117 | 112 |
| 118 DUMMY_CALLSTACKS = [ | 113 DUMMY_CALLSTACKS = [ |
| 119 CallStack(0, [], CallStackFormatType.DEFAULT, LanguageType.CPP), | 114 CallStack(0, [], CallStackFormatType.DEFAULT, LanguageType.CPP), |
| 120 CallStack(1, [], CallStackFormatType.DEFAULT, LanguageType.CPP)] | 115 CallStack(1, [], CallStackFormatType.DEFAULT, LanguageType.CPP)] |
| 121 DUMMY_REPORT = CrashReport( | 116 DUMMY_REPORT = CrashReport( |
| 122 None, None, None, Stacktrace(DUMMY_CALLSTACKS, DUMMY_CALLSTACKS[0]), | 117 None, None, None, Stacktrace(DUMMY_CALLSTACKS, DUMMY_CALLSTACKS[0]), |
| 123 (None, None), None, None) | 118 (None, None), None, None) |
| 124 | 119 |
| 125 | 120 |
| 126 class LogLinearChangelistClassifierTest(CrashTestSuite): | 121 class LogLinearChangelistClassifierTest(CrashTestSuite): |
| 122 """Tests ``LogLinearChangelistClassifier`` class.""" |
| 127 | 123 |
| 128 def setUp(self): | 124 def setUp(self): |
| 129 super(LogLinearChangelistClassifierTest, self).setUp() | 125 super(LogLinearChangelistClassifierTest, self).setUp() |
| 130 meta_weight = MetaWeight({ | 126 meta_weight = MetaWeight({ |
| 131 'TouchCrashedFileMeta': MetaWeight({ | 127 'TouchCrashedFileMeta': MetaWeight({ |
| 132 'MinDistance': Weight(1.), | 128 'MinDistance': Weight(1.), |
| 133 'TopFrameIndex': Weight(1.), | 129 'TopFrameIndex': Weight(1.), |
| 134 'TouchCrashedFile': Weight(1.), | 130 'TouchCrashedFile': Weight(1.), |
| 135 }) | 131 }) |
| 136 }) | 132 }) |
| 137 get_repository = GitilesRepository.Factory(self.GetMockHttpClient()) | 133 get_repository = GitilesRepository.Factory(self.GetMockHttpClient()) |
| 138 meta_feature = WrapperMetaFeature( | 134 meta_feature = WrapperMetaFeature( |
| 139 [TouchCrashedFileMetaFeature(get_repository)]) | 135 [TouchCrashedFileMetaFeature(get_repository)]) |
| 140 | 136 |
| 141 self.changelist_classifier = LogLinearChangelistClassifier( | 137 self.changelist_classifier = LogLinearChangelistClassifier( |
| 142 get_repository, meta_feature, meta_weight) | 138 get_repository, meta_feature, meta_weight) |
| 143 | 139 |
| 144 # TODO(http://crbug.com/659346): why do these mocks give coverage | 140 def testCallFailedFindingSuspects(self): |
| 145 # failures? That's almost surely hiding a bug in the tests themselves. | 141 """Tests that ``__call__`` method failed to find suspects.""" |
| 146 def testFindItForCrashNoRegressionRange(self): # pragma: no cover | 142 self.mock(self.changelist_classifier, 'GenerateSuspects', lambda *_: []) |
| 147 # N.B., for this one test we really do want regression_range=None. | 143 suspects = self.changelist_classifier(DUMMY_REPORT) |
| 148 report = CrashReport(None, None, None, Stacktrace(DUMMY_CALLSTACKS, | 144 self.assertListEqual(suspects, []) |
| 149 DUMMY_CALLSTACKS[0]), | |
| 150 None, {}, {}) | |
| 151 self.assertListEqual(self.changelist_classifier(report), []) | |
| 152 | 145 |
| 153 def testFindItForCrashNoMatchFound(self): | 146 def testCallFindsSuspects(self): |
| 154 self.mock(scorer_changelist_classifier, 'FindSuspects', lambda *_: []) | 147 """Tests that ``__call__`` method finds suspects.""" |
| 155 self.assertListEqual(self.changelist_classifier(DUMMY_REPORT), []) | 148 suspect1 = Suspect(DUMMY_CHANGELOG1, 'src/') |
| 149 suspect2 = Suspect(DUMMY_CHANGELOG2, 'src/') |
| 156 | 150 |
| 157 self.mock(scorer_changelist_classifier, 'FindSuspects', lambda *_: None) | 151 self.mock(self.changelist_classifier, 'GenerateSuspects', |
| 158 self.assertListEqual(self.changelist_classifier(DUMMY_REPORT), []) | 152 lambda *_: [suspect1, suspect2]) |
| 153 self.mock(self.changelist_classifier, 'RankSuspects', |
| 154 lambda report, suspects: [suspects[0]]) |
| 155 suspects = self.changelist_classifier(DUMMY_REPORT) |
| 159 | 156 |
| 160 def testFindItForCrash(self): | 157 expected_suspects = [suspect1.ToDict()] |
| 161 suspect1 = Suspect(DUMMY_CHANGELOG1, 'src/') | |
| 162 suspect2 = Suspect(DUMMY_CHANGELOG3, 'src/') | |
| 163 | |
| 164 a_cc_blame = Blame('6', 'src/') | |
| 165 a_cc_blame.AddRegions([Region(0, 10, suspect1.changelog.revision, | |
| 166 suspect1.changelog.author.name, | |
| 167 suspect1.changelog.author.email, | |
| 168 suspect1.changelog.author.time)]) | |
| 169 f_cc_blame = Blame('6', 'src/') | |
| 170 f_cc_blame.AddRegions([Region(21, 10, suspect2.changelog.revision, | |
| 171 suspect2.changelog.author.name, | |
| 172 suspect2.changelog.author.email, | |
| 173 suspect2.changelog.author.time)]) | |
| 174 url_to_blame = {'6/a.cc': a_cc_blame, | |
| 175 '6/f.cc': f_cc_blame} | |
| 176 | |
| 177 def _MockGetBlame(_, path, revision): | |
| 178 revision_path = '%s/%s' % (revision, path) | |
| 179 return url_to_blame.get(revision_path) | |
| 180 | |
| 181 self.mock(GitilesRepository, 'GetBlame', _MockGetBlame) | |
| 182 self.mock(scorer_changelist_classifier, | |
| 183 'GetChangeLogsForFilesGroupedByDeps', | |
| 184 lambda *_: (None, None)) | |
| 185 self.mock(scorer_changelist_classifier, 'FindSuspects', | |
| 186 lambda *_: [suspect1, suspect2]) | |
| 187 frame1 = StackFrame(0, 'src/', 'func', 'a.cc', 'src/a.cc', [1]) | |
| 188 frame2 = StackFrame(1, 'src/', 'func', 'a.cc', 'src/a.cc', [7]) | |
| 189 frame3 = StackFrame(15, 'src/', 'func', 'f.cc', 'src/f.cc', [1]) | |
| 190 frame4 = StackFrame(3, 'src/dep1', 'func', 'f.cc', 'src/dep1/f.cc', [1]) | |
| 191 stacks = [CallStack(0, frame_list=[frame1, frame2, frame3, frame4])] | |
| 192 stacktrace = Stacktrace(stacks, stacks[0]) | |
| 193 report = CrashReport( | |
| 194 '6', 'sig', 'win', stacktrace, ('0', '4'), | |
| 195 {'src/': Dependency('src/', 'https://repo', '6')}, | |
| 196 {'src/': DependencyRoll('src/', 'https://repo', '0', '4')} ) | |
| 197 | |
| 198 suspects = self.changelist_classifier(report) | |
| 199 self.assertTrue(suspects, | |
| 200 'Expected suspects, but the classifier didn\'t return any') | |
| 201 | |
| 202 expected_suspects = [ | |
| 203 { | |
| 204 'author': 'r@chromium.org', | |
| 205 'changed_files': [ | |
| 206 { | |
| 207 'blame_url': None, | |
| 208 'file': 'a.cc', | |
| 209 'info': ('Distance from touched lines and crashed lines is ' | |
| 210 '0, in frame #0') | |
| 211 } | |
| 212 ], | |
| 213 'confidence': 0., | |
| 214 'project_path': 'src/', | |
| 215 'reasons': ('MinDistance: 0.000000 -- Minimum distance is ' | |
| 216 '0\nTopFrameIndex: 0.000000 -- Top frame is #0\n' | |
| 217 'TouchCrashedFile: 0.000000 -- Touched files - a.cc'), | |
| 218 'review_url': 'https://codereview.chromium.org/3281', | |
| 219 'revision': '1', | |
| 220 'time': 'Thu Mar 31 21:24:43 2016', | |
| 221 'url': 'https://repo.test/+/1' | |
| 222 }, | |
| 223 ] | |
| 224 self.assertListEqual([suspect.ToDict() for suspect in suspects], | 158 self.assertListEqual([suspect.ToDict() for suspect in suspects], |
| 225 expected_suspects) | 159 expected_suspects) |
| 160 |
| 161 def testGenerateSuspects(self): |
| 162 """Tests ``GenerateSuspects`` method.""" |
| 163 dep_roll = DependencyRoll('src/', 'https://repo', 'rev1', 'rev5') |
| 164 report = DUMMY_REPORT._replace(dependency_rolls={dep_roll.path: dep_roll}) |
| 165 self.mock(GitilesRepository, 'GetChangeLogs', |
| 166 lambda *_: [DUMMY_CHANGELOG1, DUMMY_CHANGELOG2, DUMMY_CHANGELOG3]) |
| 167 |
| 168 suspects = self.changelist_classifier.GenerateSuspects(report) |
| 169 self.assertListEqual([Suspect(DUMMY_CHANGELOG3, 'src/').ToDict()], |
| 170 [suspect.ToDict() for suspect in suspects]) |
| 171 |
| 172 def testRankSuspectsAllLogZeros(self): |
| 173 """Tests ``RankSuspects`` method.""" |
| 174 self.mock(self.changelist_classifier._model, 'Features', |
| 175 lambda _: lambda _: MetaFeatureValue('dummy', {})) |
| 176 suspect1 = Suspect(DUMMY_CHANGELOG1, 'src/') |
| 177 suspect2 = Suspect(DUMMY_CHANGELOG2, 'src/') |
| 178 |
| 179 self.mock(self.changelist_classifier._model, 'Score', |
| 180 lambda _: lambda _: lmath.LOG_ZERO) |
| 181 suspects = self.changelist_classifier.RankSuspects(DUMMY_REPORT, |
| 182 [suspect1, suspect2]) |
| 183 self.assertEqual(suspects, []) |
| 184 |
| 185 def testRankSuspects(self): |
| 186 """Tests ``RankSuspects`` method.""" |
| 187 self.mock(self.changelist_classifier._model, 'Features', |
| 188 lambda _: lambda _: MetaFeatureValue('dummy', {})) |
| 189 |
| 190 suspect = Suspect(DUMMY_CHANGELOG1, 'src/') |
| 191 self.mock(self.changelist_classifier._model, 'Score', |
| 192 lambda _: lambda _: lmath.LOG_ONE) |
| 193 suspects = self.changelist_classifier.RankSuspects(DUMMY_REPORT, |
| 194 [suspect]) |
| 195 self.assertEqual(suspects[0].ToDict(), suspect.ToDict()) |
| OLD | NEW |