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

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

Issue 2560723005: Implementing a new LogLinearModel-based CL classifier (Closed)
Patch Set: rebase Created 4 years 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/loglinear/model.py ('k') | no next file » | 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 import copy
6 import math
7 import pprint
8
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 import crash.loglinear.changelist_classifier as loglinear_changelist_classifier
14 from crash.loglinear.feature import ChangedFile
15 from crash.loglinear.feature import FeatureValue
16 from crash.suspect import AnalysisInfo
17 from crash.suspect import Suspect
18 from crash.suspect import StackInfo
19 from crash.stacktrace import CallStack
20 from crash.stacktrace import StackFrame
21 from crash.stacktrace import Stacktrace
22 from crash.test.crash_test_suite import CrashTestSuite
23 from libs.gitiles.change_log import ChangeLog
24 from libs.gitiles.gitiles_repository import GitilesRepository
25
26 DUMMY_CHANGELOG1 = ChangeLog.FromDict({
27 'author_name': 'r@chromium.org',
28 'message': 'dummy',
29 'committer_email': 'r@chromium.org',
30 'commit_position': 175900,
31 'author_email': 'r@chromium.org',
32 'touched_files': [
33 {
34 'change_type': 'add',
35 'new_path': 'a.cc',
36 'old_path': None,
37 },
38 ],
39 'author_time': 'Thu Mar 31 21:24:43 2016',
40 'committer_time': 'Thu Mar 31 21:28:39 2016',
41 'commit_url':
42 'https://repo.test/+/1',
43 'code_review_url': 'https://codereview.chromium.org/3281',
44 'committer_name': 'example@chromium.org',
45 'revision': '1',
46 'reverted_revision': None
47 })
48
49 DUMMY_CHANGELOG2 = ChangeLog.FromDict({
50 'author_name': 'example@chromium.org',
51 'message': 'dummy',
52 'committer_email': 'example@chromium.org',
53 'commit_position': 175976,
54 'author_email': 'example@chromium.org',
55 'touched_files': [
56 {
57 'change_type': 'add',
58 'new_path': 'f0.cc',
59 'old_path': 'b/f0.cc'
60 },
61 ],
62 'author_time': 'Thu Mar 31 21:24:43 2016',
63 'committer_time': 'Thu Mar 31 21:28:39 2016',
64 'commit_url':
65 'https://repo.test/+/2',
66 'code_review_url': 'https://codereview.chromium.org/3281',
67 'committer_name': 'example@chromium.org',
68 'revision': '2',
69 'reverted_revision': '1'
70 })
71
72 DUMMY_CHANGELOG3 = ChangeLog.FromDict({
73 'author_name': 'e@chromium.org',
74 'message': 'dummy',
75 'committer_email': 'e@chromium.org',
76 'commit_position': 176000,
77 'author_email': 'e@chromium.org',
78 'touched_files': [
79 {
80 'change_type': 'modify',
81 'new_path': 'f.cc',
82 'old_path': 'f.cc'
83 },
84 {
85 'change_type': 'delete',
86 'new_path': None,
87 'old_path': 'f1.cc'
88 },
89 ],
90 'author_time': 'Thu Apr 1 21:24:43 2016',
91 'committer_time': 'Thu Apr 1 21:28:39 2016',
92 'commit_url':
93 'https://repo.test/+/3',
94 'code_review_url': 'https://codereview.chromium.org/3281',
95 'committer_name': 'example@chromium.org',
96 'revision': '3',
97 'reverted_revision': None
98 })
99
100 # TODO(crbug.com/674255): clean up the warning about the empty trace.
101 DUMMY_REPORT = CrashReport(None, None, None, Stacktrace(), (None, None))
102
103
104 class LogLinearChangelistClassifierTest(CrashTestSuite):
105
106 def setUp(self):
107 super(LogLinearChangelistClassifierTest, self).setUp()
108 weights = {
109 'MinDistance': 1.,
110 'TopFrameIndex': 1.,
111 }
112
113 self.changelist_classifier = (
114 loglinear_changelist_classifier.LogLinearChangelistClassifier(
115 GitilesRepository(self.GetMockHttpClient()), weights))
116
117 def testAggregateChangedFilesAggreegates(self):
118 """Test that ``AggregateChangedFiles`` does aggregate reasons per file.
119
120 In the main/inner loop of ``AggregateChangedFiles``: if multiple
121 features all blame the same file change, we try to aggregate those
122 reasons so that we only report the file once (with all reasons). None
123 of the other tests here actually check the case where the same file
124 is blamed multiple times, so we check that here.
125
126 In particular, we provide the same ``FeatureValue`` twice, and
127 hence the same ``ChangedFile`` twice; so we should get back a single
128 ``ChangedFile`` but with the ``reasons`` fields concatenated.
129 """
130 file_reason = 'I blame you!'
131 file_blame = ChangedFile(
132 name = 'a.cc',
133 blame_url = None,
134 reasons = [file_reason]
135 )
136
137 feature_value = FeatureValue(
138 name = 'dummy feature',
139 value = 42,
140 reason = 'dummy reason',
141 changed_files = [file_blame]
142 )
143
144 expected_file_blame = file_blame._replace(reasons = [file_reason] * 2)
145
146 self.assertListEqual(
147 [expected_file_blame],
148 self.changelist_classifier.AggregateChangedFiles(
149 [feature_value] * 2))
150
151 def testSkipAddedAndDeletedRegressionRolls(self):
152 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
153 'GetDependency', lambda *_: {})
154 dep_rolls = {
155 'src/dep': DependencyRoll('src/dep1', 'https://url_dep1', None, '9'),
156 'src/': DependencyRoll('src/', ('https://chromium.googlesource.com/'
157 'chromium/src.git'), '4', '5')
158 }
159 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
160 'GetDependencyRollsDict', lambda *_: dep_rolls)
161
162 passed_in_regression_deps_rolls = []
163 def _MockGetChangeLogsForFilesGroupedByDeps(regression_deps_rolls, *_):
164 passed_in_regression_deps_rolls.append(regression_deps_rolls)
165 return {}, None
166
167 self.mock(changelist_classifier, 'GetChangeLogsForFilesGroupedByDeps',
168 _MockGetChangeLogsForFilesGroupedByDeps)
169 self.mock(changelist_classifier, 'GetStackInfosForFilesGroupedByDeps',
170 lambda *_: {})
171 self.mock(changelist_classifier, 'FindSuspects', lambda *_: None)
172
173 self.changelist_classifier(CrashReport(crashed_version = '5',
174 signature = 'sig',
175 platform = 'canary',
176 stacktrace = Stacktrace([CallStack(0)]),
177 regression_range = ['4', '5']))
178 expected_regression_deps_rolls = copy.deepcopy(dep_rolls)
179
180 # Regression of a dep added/deleted (old_revision/new_revision is None) can
181 # not be known for sure and this case rarely happens, so just filter them
182 # out.
183 del expected_regression_deps_rolls['src/dep']
184 self.assertEqual(passed_in_regression_deps_rolls[0],
185 expected_regression_deps_rolls)
186
187 # TODO(http://crbug.com/659346): why do these mocks give coverage
188 # failures? That's almost surely hiding a bug in the tests themselves.
189 def testFindItForCrashNoRegressionRange(self): # pragma: no cover
190 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
191 'GetDependencyRollsDict', lambda *_: {})
192 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
193 'GetDependency', lambda *_: {})
194 # N.B., for this one test we really do want regression_range=None.
195 report = DUMMY_REPORT._replace(regression_range=None)
196 self.assertListEqual(self.changelist_classifier(report), [])
197
198 def testFindItForCrashNoMatchFound(self):
199 self.mock(changelist_classifier, 'FindSuspects', lambda *_: [])
200 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
201 'GetDependencyRollsDict',
202 lambda *_: {'src/': DependencyRoll('src/', 'https://repo', '1', '2')})
203 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
204 'GetDependency', lambda *_: {})
205 self.assertListEqual(self.changelist_classifier(DUMMY_REPORT), [])
206
207 def testFindItForCrash(self):
208
209 def _MockFindSuspects(*_):
210 suspect1 = Suspect(DUMMY_CHANGELOG1, 'src/')
211 frame1 = StackFrame(0, 'src/', 'func', 'a.cc', 'src/a.cc', [1])
212 frame2 = StackFrame(1, 'src/', 'func', 'a.cc', 'src/a.cc', [7])
213 suspect1.file_to_stack_infos = {
214 'a.cc': [StackInfo(frame1, 0), StackInfo(frame2, 0)]
215 }
216 suspect1.file_to_analysis_info = {
217 'a.cc': AnalysisInfo(min_distance=0, min_distance_frame=frame1)
218 }
219
220 suspect2 = Suspect(DUMMY_CHANGELOG3, 'src/')
221 frame3 = StackFrame(5, 'src/', 'func', 'f.cc', 'src/f.cc', [1])
222 suspect2.file_to_stack_infos = {
223 'f.cc': [StackInfo(frame3, 0)]
224 }
225 suspect2.file_to_analysis_info = {
226 'a.cc': AnalysisInfo(min_distance=20, min_distance_frame=frame3)
227 }
228
229 return [suspect1, suspect2]
230
231 self.mock(changelist_classifier, 'FindSuspects', _MockFindSuspects)
232 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
233 'GetDependencyRollsDict',
234 lambda *_: {'src/': DependencyRoll('src/', 'https://repo', '1', '2')})
235 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
236 'GetDependency', lambda *_: {})
237
238 suspects = self.changelist_classifier(DUMMY_REPORT)
239 self.assertTrue(suspects,
240 "Expected suspects, but the classifier didn't return any")
241
242 expected_suspects = [
243 {
244 'review_url': 'https://codereview.chromium.org/3281',
245 'url': 'https://repo.test/+/3',
246 'author': 'e@chromium.org',
247 'time': 'Thu Apr 1 21:24:43 2016',
248 'project_path': 'src/',
249 'revision': '3',
250 'confidence': math.log(0.2857142857142857 * 0.6),
251 'reasons': [
252 ('MinDistance', math.log(0.6), 'Minimum distance is 20'),
253 ('TopFrameIndex', math.log(0.2857142857142857),
254 'Top frame is #5')],
255 'changed_files': [
256 {
257 'file': 'a.cc',
258 'blame_url': None,
259 'info': 'Minimum distance (LOC) 20, frame #5',
260 }],
261 }, {
262 'review_url': 'https://codereview.chromium.org/3281',
263 'url': 'https://repo.test/+/1',
264 'author': 'r@chromium.org',
265 'time': 'Thu Mar 31 21:24:43 2016',
266 'project_path': 'src/',
267 'revision': '1',
268 'confidence': 0.,
269 'reasons': [
270 ('MinDistance', 0., 'Minimum distance is 0'),
271 ('TopFrameIndex', 0., 'Top frame is #0')],
272 'changed_files': [
273 {
274 'file': 'a.cc',
275 'blame_url': None,
276 'info': 'Minimum distance (LOC) 0, frame #0',
277 }],
278 },
279 ]
280 self.assertListEqual([suspect.ToDict() for suspect in suspects],
281 expected_suspects)
282
283 def testFinditForCrashFilterZeroConfidenceSuspects(self):
284 def _MockFindSuspects(*_):
285 suspect1 = Suspect(DUMMY_CHANGELOG1, 'src/')
286 frame1 = StackFrame(0, 'src/', 'func', 'a.cc', 'src/a.cc', [1])
287 frame2 = StackFrame(1, 'src/', 'func', 'a.cc', 'src/a.cc', [7])
288 suspect1.file_to_stack_infos = {
289 'a.cc': [StackInfo(frame1, 0), StackInfo(frame2, 0)]
290 }
291 suspect1.file_to_analysis_info = {
292 'a.cc': AnalysisInfo(min_distance=1, min_distance_frame=frame1)
293 }
294
295 suspect2 = Suspect(DUMMY_CHANGELOG3, 'src/')
296 frame3 = StackFrame(15, 'src/', 'func', 'f.cc', 'src/f.cc', [1])
297 suspect2.file_to_stack_infos = {
298 'f.cc': [StackInfo(frame3, 0)]
299 }
300 suspect2.file_to_analysis_info = {
301 'f.cc': AnalysisInfo(min_distance=20, min_distance_frame=frame3)
302 }
303
304 suspect3 = Suspect(DUMMY_CHANGELOG3, 'src/')
305 frame4 = StackFrame(3, 'src/', 'func', 'ff.cc', 'src/ff.cc', [1])
306 suspect3.file_to_stack_infos = {
307 'f.cc': [StackInfo(frame4, 0)]
308 }
309 suspect3.file_to_analysis_info = {
310 'f.cc': AnalysisInfo(min_distance=60, min_distance_frame=frame4)
311 }
312
313 return [suspect1, suspect2, suspect3]
314
315 self.mock(changelist_classifier, 'FindSuspects', _MockFindSuspects)
316 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
317 'GetDependencyRollsDict',
318 lambda *_: {'src/': DependencyRoll('src/', 'https://repo', '1', '2')})
319 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
320 'GetDependency', lambda *_: {})
321
322 suspects = self.changelist_classifier(DUMMY_REPORT)
323 self.assertTrue(suspects,
324 "Expected suspects, but the classifier didn't return any")
325
326 expected_suspects = [
327 {
328 'author': 'r@chromium.org',
329 'changed_files': [
330 {
331 'blame_url': None,
332 'file': 'a.cc',
333 'info': 'Minimum distance (LOC) 1, frame #0'
334 }
335 ],
336 'confidence': math.log(0.98),
337 'project_path': 'src/',
338 'reasons': [
339 ('MinDistance', math.log(0.98), 'Minimum distance is 1'),
340 ('TopFrameIndex', 0., 'Top frame is #0'),
341 ],
342 'review_url': 'https://codereview.chromium.org/3281',
343 'revision': '1',
344 'time': 'Thu Mar 31 21:24:43 2016',
345 'url': 'https://repo.test/+/1'
346 },
347 ]
348 self.assertListEqual([suspect.ToDict() for suspect in suspects],
349 expected_suspects)
350
351 def testFinditForCrashAllSuspectsWithZeroConfidences(self):
352 """Test that we filter out suspects with too-large frame indices.
353
354 In the mock suspects below we return frames with indices
355 15, 20, 21 which are all larger than the ``max_top_n`` of
356 ``TopFrameIndexFeature``. Therefore we should get a score of zero
357 for that feature, which should cause the suspects to be filtered out.
358 """
359 def _MockFindSuspects(*_):
360 suspect1 = Suspect(DUMMY_CHANGELOG1, 'src/')
361 frame1 = StackFrame(20, 'src/', '', 'func', 'a.cc', [1])
362 frame2 = StackFrame(21, 'src/', '', 'func', 'a.cc', [7])
363 suspect1.file_to_stack_infos = {
364 'a.cc': [StackInfo(frame1, 0), StackInfo(frame2, 0)]
365 }
366 suspect1.file_to_analysis_info = {
367 'a.cc': AnalysisInfo(min_distance=1, min_distance_frame=frame1)
368 }
369
370 suspect2 = Suspect(DUMMY_CHANGELOG3, 'src/')
371 frame3 = StackFrame(15, 'src/', '', 'func', 'f.cc', [1])
372 suspect2.file_to_stack_infos = {
373 'f.cc': [StackInfo(frame3, 0)]
374 }
375 suspect2.min_distance = 20
376 suspect2.file_to_analysis_info = {
377 'f.cc': AnalysisInfo(min_distance=20, min_distance_frame=frame3)
378 }
379
380 return [suspect1, suspect2]
381
382 self.mock(changelist_classifier, 'FindSuspects', _MockFindSuspects)
383 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
384 'GetDependencyRollsDict',
385 lambda *_: {'src/': DependencyRoll('src/', 'https://repo', '1', '2')})
386 self.mock(chrome_dependency_fetcher.ChromeDependencyFetcher,
387 'GetDependency', lambda *_: {})
388
389 suspects = self.changelist_classifier(DUMMY_REPORT)
390 self.assertFalse(suspects, 'Expected zero suspects, but found some:\n%s'
391 % pprint.pformat([suspect.ToDict() for suspect in suspects]))
OLDNEW
« no previous file with comments | « appengine/findit/crash/loglinear/model.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698