| 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 from common.pipeline_wrapper import pipeline_handlers | 5 from common.pipeline_wrapper import pipeline_handlers |
| 6 from crash.stacktrace import StackFrame | 6 from crash.stacktrace import StackFrame |
| 7 from crash.stacktrace import CallStack | 7 from crash.stacktrace import CallStack |
| 8 from crash.component_classifier import Component | 8 from crash.component_classifier import Component |
| 9 from crash.component_classifier import ComponentClassifier | 9 from crash.component_classifier import ComponentClassifier |
| 10 from crash.suspect import Suspect | 10 from crash.suspect import Suspect |
| 11 from crash.test.predator_testcase import PredatorTestCase | 11 from crash.test.predator_testcase import PredatorTestCase |
| 12 from model.crash.crash_config import CrashConfig | 12 from model.crash.crash_config import CrashConfig |
| 13 from libs.gitiles.change_log import ChangeLog | 13 from libs.gitiles.change_log import ChangeLog |
| 14 from libs.gitiles.change_log import FileChangeInfo | 14 from libs.gitiles.change_log import FileChangeInfo |
| 15 | 15 from libs.gitiles.diff import ChangeType |
| 16 | |
| 17 # N.B., the call to Get() in CrashConfigComponentClassifier.__init__ | |
| 18 # must only be executed from within the testFoo methods of | |
| 19 # ComponentClassifierTest. That is, we can't just do this once and for all | |
| 20 # when doing ComponentClassifierTest.__init__, because that'll cause some | |
| 21 # strange issues in mocking. But factoring it out like this so it gets | |
| 22 # (re)called ever time a testFoo is run, that works. | |
| 23 class CrashConfigComponentClassifier(ComponentClassifier): | |
| 24 """A ComponentClassifier which gets its components from CrashConfig.""" | |
| 25 def __init__(self): | |
| 26 config = CrashConfig.Get().component_classifier | |
| 27 super(CrashConfigComponentClassifier, self).__init__( | |
| 28 [Component(name, path, function) | |
| 29 for path, function, name | |
| 30 in config.get('path_function_component', [])], | |
| 31 config.get('top_n', 0)) | |
| 32 | 16 |
| 33 | 17 |
| 34 class ComponentClassifierTest(PredatorTestCase): | 18 class ComponentClassifierTest(PredatorTestCase): |
| 19 """Tests ``ComponentClassifier`` class.""" |
| 35 | 20 |
| 36 def setUp(self): | 21 def setUp(self): |
| 37 super(ComponentClassifierTest, self).setUp() | 22 super(ComponentClassifierTest, self).setUp() |
| 38 # Only construct the cccc once, rather than making a new one every | 23 config = CrashConfig.Get().component_classifier |
| 24 components = [Component(component_name, path_regex, function_regex) |
| 25 for path_regex, function_regex, component_name |
| 26 in config['path_function_component']] |
| 27 # Only construct the classifier once, rather than making a new one every |
| 39 # time we call a method on it. | 28 # time we call a method on it. |
| 40 self.cccc = CrashConfigComponentClassifier() | 29 self.classifier = ComponentClassifier(components, config['top_n']) |
| 41 | 30 |
| 42 def testGetClassFromStackFrame(self): | 31 def testClassifyCallStack(self): |
| 43 frame = StackFrame(0, 'src/', 'func', 'comp1.cc', 'src/comp1.cc', [2]) | 32 """Tests ``ClassifyCallStack`` method.""" |
| 44 self.assertEqual(self.cccc.GetClassFromStackFrame(frame), 'Comp1>Dummy') | 33 callstack = CallStack( |
| 45 | 34 0, [StackFrame(0, 'src/', 'func', 'comp1.cc', 'src/comp1.cc', [2])]) |
| 46 frame = StackFrame(0, 'src/', 'func2', 'comp2.cc', 'src/comp2.cc', [32]) | 35 self.assertEqual(self.classifier.ClassifyCallStack(callstack), |
| 47 self.assertEqual(self.cccc.GetClassFromStackFrame(frame), 'Comp2>Dummy') | |
| 48 | |
| 49 frame = StackFrame(0, 'src/', 'no_func', 'comp2.cc', 'src/comp2.cc', [32]) | |
| 50 self.assertEqual(self.cccc.GetClassFromStackFrame(frame), '') | |
| 51 | |
| 52 frame = StackFrame(0, 'src/', 'func2', 'a.cc', 'src/a.cc', [6]) | |
| 53 self.assertEqual(self.cccc.GetClassFromStackFrame(frame), '') | |
| 54 | |
| 55 def testGetClassFromSuspect(self): | |
| 56 suspect = Suspect(self.GetDummyChangeLog(), 'src/') | |
| 57 self.assertEqual(self.cccc.GetClassFromSuspect(suspect), '') | |
| 58 | |
| 59 suspect.file_to_stack_infos = { | |
| 60 'comp1.cc': [ | |
| 61 (StackFrame(0, 'src/', 'func', 'comp1.cc', 'src/comp1.cc', [2]), 0) | |
| 62 ] | |
| 63 } | |
| 64 self.assertEqual(self.cccc.GetClassFromSuspect(suspect), 'Comp1>Dummy') | |
| 65 | |
| 66 def testClassifyCrashStack(self): | |
| 67 crash_stack = CallStack(0, frame_list=[ | |
| 68 StackFrame(0, 'src/', 'func', 'comp1.cc', 'src/comp1.cc', [2]), | |
| 69 StackFrame(1, 'src/', 'ff', 'comp1.cc', 'src/comp1.cc', [21]), | |
| 70 StackFrame(2, 'src/', 'func2', 'comp2.cc', 'src/comp2.cc', [8])]) | |
| 71 | |
| 72 self.assertEqual(self.cccc.Classify([], crash_stack), | |
| 73 ['Comp1>Dummy', 'Comp2>Dummy']) | |
| 74 | |
| 75 def testClassifySuspects(self): | |
| 76 suspect = Suspect(self.GetDummyChangeLog(), 'src/') | |
| 77 suspect.file_to_stack_infos = { | |
| 78 'comp1.cc': [ | |
| 79 (StackFrame(0, 'src/', 'func', 'comp1.cc', 'src/comp1.cc', [2]), 0) | |
| 80 ] | |
| 81 } | |
| 82 | |
| 83 self.assertEqual(self.cccc.Classify([suspect], CallStack(0)), | |
| 84 ['Comp1>Dummy']) | 36 ['Comp1>Dummy']) |
| 85 | 37 |
| 86 def testClassifierDoNotHaveConfig(self): | 38 callstack = CallStack( |
| 87 crash_config = CrashConfig.Get() | 39 0, [StackFrame(0, 'dummy/', 'no_func', 'comp2.cc', |
| 88 crash_config.component_classifier = {} | 40 'dummy/comp2.cc', [32])]) |
| 89 # N.B., we must construct a new cccc here, becasue we changed CrashConfig. | 41 self.assertEqual(self.classifier.ClassifyCallStack(callstack), []) |
| 90 self.cccc = CrashConfigComponentClassifier() | |
| 91 | 42 |
| 92 crash_stack = CallStack(0, frame_list=[ | 43 crash_stack = CallStack(0, frame_list=[ |
| 93 StackFrame(0, 'src/', 'func', 'comp1.cc', 'src/comp1.cc', [2]), | 44 StackFrame(0, 'src/', 'func', 'comp1.cc', 'src/comp1.cc', [2]), |
| 94 StackFrame(1, 'src/', 'ff', 'comp1.cc', 'src/comp1.cc', [21]), | 45 StackFrame(1, 'src/', 'ff', 'comp1.cc', 'src/comp1.cc', [21]), |
| 95 StackFrame(2, 'src/', 'func2', 'comp2.cc', 'src/comp2.cc', [8])]) | 46 StackFrame(2, 'src/', 'func2', 'comp2.cc', 'src/comp2.cc', [8])]) |
| 96 | 47 |
| 48 self.assertEqual(self.classifier.ClassifyCallStack(crash_stack), |
| 49 ['Comp1>Dummy', 'Comp2>Dummy']) |
| 50 |
| 51 def testClassifySuspect(self): |
| 52 """Tests ``ClassifySuspect`` method.""" |
| 97 suspect = Suspect(self.GetDummyChangeLog(), 'src/') | 53 suspect = Suspect(self.GetDummyChangeLog(), 'src/') |
| 98 suspect.file_to_stack_infos = { | 54 suspect.changelog = suspect.changelog._replace( |
| 99 'comp1.cc': [ | 55 touched_files = [FileChangeInfo(ChangeType.MODIFY, |
| 100 (StackFrame(0, 'src/', 'func', 'comp1.cc', 'src/comp1.cc', [2]), 0) | 56 'comp1.cc', 'comp1.cc')]) |
| 101 ] | 57 self.assertEqual(self.classifier.ClassifySuspect(suspect), ['Comp1>Dummy']) |
| 102 } | |
| 103 | 58 |
| 104 self.assertEqual(self.cccc.Classify([suspect], crash_stack), []) | 59 def testClassifyEmptySuspect(self): |
| 60 """Tests ``ClassifySuspect`` returns None for empty suspect.""" |
| 61 self.assertIsNone(self.classifier.ClassifySuspect(None)) |
| 105 | 62 |
| 106 def testGetClassFromFileChangeInfo(self): | 63 def testClassifySuspectNoMatch(self): |
| 107 self.assertEqual( | 64 """Tests ``ClassifySuspect`` returns None if there is no file match.""" |
| 108 CrashConfigComponentClassifier().GetClassFromFileChangeInfo( | 65 suspect = Suspect(self.GetDummyChangeLog(), 'dummy') |
| 109 FileChangeInfo.FromDict({'change_type': 'modify', | 66 suspect.changelog = suspect.changelog._replace( |
| 110 'old_path': 'src/comp1.cc', | 67 touched_files = [FileChangeInfo(ChangeType.MODIFY, |
| 111 'new_path': 'src/comp1.cc'})), | 68 'comp1.cc', 'comp1.cc')]) |
| 112 'Comp1>Dummy') | 69 self.assertEqual(self.classifier.ClassifySuspect(suspect), []) |
| 113 | 70 |
| 114 def testGetClassFromFileChangeInfoOldPath(self): | 71 def testClassifySuspects(self): |
| 115 self.assertEqual( | 72 """Tests ``ClassifySuspects`` classify a list of ``Suspect``s.""" |
| 116 CrashConfigComponentClassifier().GetClassFromFileChangeInfo( | 73 suspect1 = Suspect(self.GetDummyChangeLog(), 'src/') |
| 117 FileChangeInfo.FromDict({'change_type': 'delete', | 74 suspect1.changelog = suspect1.changelog._replace( |
| 118 'old_path': 'src/comp1.cc', | 75 touched_files = [FileChangeInfo(ChangeType.MODIFY, |
| 119 'new_path': ''})), | 76 'comp1.cc', 'comp1.cc')]) |
| 120 'Comp1>Dummy') | 77 suspect2 = Suspect(self.GetDummyChangeLog(), 'src/') |
| 78 suspect2.changelog = suspect2.changelog._replace( |
| 79 touched_files = [FileChangeInfo(ChangeType.MODIFY, |
| 80 'comp2.cc', 'comp2.cc')]) |
| 121 | 81 |
| 122 | 82 self.assertEqual(self.classifier.ClassifySuspects([suspect1, suspect2]), |
| 123 def testGetClassFromNoneFileChangeInfo(self): | 83 ['Comp1>Dummy', 'Comp2>Dummy']) |
| 124 self.assertEqual( | |
| 125 CrashConfigComponentClassifier().GetClassFromFileChangeInfo(None), | |
| 126 None) | |
| 127 | |
| 128 def testGetClassFromChangeFileInfoNoMapping(self): | |
| 129 self.assertEqual( | |
| 130 CrashConfigComponentClassifier().GetClassFromFileChangeInfo( | |
| 131 FileChangeInfo.FromDict({'change_type':'modify', | |
| 132 'old_path':'file', | |
| 133 'new_path':'file'})),'') | |
| 134 | |
| 135 def testGetClassFromChangeFileInfoNoMappingOldPath(self): | |
| 136 self.assertEqual( | |
| 137 CrashConfigComponentClassifier().GetClassFromFileChangeInfo( | |
| 138 FileChangeInfo.FromDict({'change_type':'rename', | |
| 139 'old_path':'old_file', | |
| 140 'new_path':'new_file'})),'') | |
| 141 | |
| 142 def testClassifyChangeLog(self): | |
| 143 change_log = ChangeLog.FromDict({ | |
| 144 'author': { | |
| 145 'name': 'a', | |
| 146 'email': 'b@email.com', | |
| 147 'time': '2014-08-13 00:53:12', | |
| 148 }, | |
| 149 'committer': { | |
| 150 'name': 'c', | |
| 151 'email': 'd@email.com', | |
| 152 'time': '2014-08-14 00:53:12', | |
| 153 }, | |
| 154 'revision': 'aaaa', | |
| 155 'commit_position': 1111, | |
| 156 'touched_files': [ | |
| 157 { | |
| 158 'change_type': 'copy', | |
| 159 'old_path': 'file', | |
| 160 'new_path': 'src/comp2.cc' | |
| 161 }, | |
| 162 { | |
| 163 'change_type': 'modify', | |
| 164 'old_path': 'src/comp2.cc', | |
| 165 'new_path': 'src/comp2.cc' | |
| 166 }, | |
| 167 { | |
| 168 'change_type': 'modify', | |
| 169 'old_path': 'src/comp1.cc', | |
| 170 'new_path': 'src/comp1.cc' | |
| 171 } | |
| 172 ], | |
| 173 'message': 'blabla...', | |
| 174 'commit_url': | |
| 175 'https://chromium.googlesource.com/chromium/src/+/git_hash', | |
| 176 'code_review_url': 'https://codereview.chromium.org/2222', | |
| 177 'reverted_revision': '8d4a4fa6s18raf3re12tg6r'}) | |
| 178 self.assertEqual( | |
| 179 CrashConfigComponentClassifier().ClassifyChangeLog(change_log), | |
| 180 ['Comp2>Dummy', 'Comp1>Dummy']) | |
| 181 | |
| 182 def testClassifyNoneChangeLog(self): | |
| 183 change_log = None | |
| 184 self.assertEqual( | |
| 185 CrashConfigComponentClassifier().ClassifyChangeLog(change_log), | |
| 186 None) | |
| 187 | |
| 188 def testClassifyChangeLogNoMapping(self): | |
| 189 change_log = ChangeLog.FromDict({ | |
| 190 'author': { | |
| 191 'name': 'a', | |
| 192 'email': 'b@email.com', | |
| 193 'time': '2014-08-13 00:53:12', | |
| 194 }, | |
| 195 'committer': { | |
| 196 'name': 'c', | |
| 197 'email': 'd@email.com', | |
| 198 'time': '2014-08-14 00:53:12', | |
| 199 }, | |
| 200 'revision': 'aaaa', | |
| 201 'commit_position': 1111, | |
| 202 'touched_files': [ | |
| 203 { | |
| 204 'change_type': 'rename', | |
| 205 'old_path': 'old_file', | |
| 206 'new_path': 'new_file' | |
| 207 }, | |
| 208 { | |
| 209 'change_type': 'delete', | |
| 210 'old_path': 'file', | |
| 211 'new_path': 'file' | |
| 212 } | |
| 213 ], | |
| 214 'message': 'blabla...', | |
| 215 'commit_url': | |
| 216 'https://chromium.googlesource.com/chromium/src/+/git_hash', | |
| 217 'code_review_url': 'https://codereview.chromium.org/2222', | |
| 218 'reverted_revision': '8d4a4fa6s18raf3re12tg6r'}) | |
| 219 self.assertEqual( | |
| 220 CrashConfigComponentClassifier().ClassifyChangeLog(change_log), | |
| 221 []) | |
| OLD | NEW |