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

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: more breaking apart of the CL 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
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.results import AnalysisInfo
17 from crash.results import MatchResult
18 from crash.results 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, 'FindMatchResults', 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, 'FindMatchResults', 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 _MockFindMatchResults(*_):
210 match_result1 = MatchResult(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 match_result1.file_to_stack_infos = {
214 'a.cc': [StackInfo(frame1, 0), StackInfo(frame2, 0)]
215 }
216 match_result1.file_to_analysis_info = {
217 'a.cc': AnalysisInfo(min_distance=0, min_distance_frame=frame1)
218 }
219
220 match_result2 = MatchResult(DUMMY_CHANGELOG3, 'src/')
221 frame3 = StackFrame(5, 'src/', 'func', 'f.cc', 'src/f.cc', [1])
222 match_result2.file_to_stack_infos = {
223 'f.cc': [StackInfo(frame3, 0)]
224 }
225 match_result2.file_to_analysis_info = {
226 'a.cc': AnalysisInfo(min_distance=20, min_distance_frame=frame3)
227 }
228
229 return [match_result1, match_result2]
230
231 self.mock(changelist_classifier, 'FindMatchResults', _MockFindMatchResults)
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 results = self.changelist_classifier(DUMMY_REPORT)
239 self.assertTrue(results,
240 "Expected results, but the classifier didn't return any")
241
242 expected_match_results = [
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([result.ToDict() for result in results],
281 expected_match_results)
282
283 def testFinditForCrashFilterZeroConfidentResults(self):
284 def _MockFindMatchResults(*_):
285 match_result1 = MatchResult(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 match_result1.file_to_stack_infos = {
289 'a.cc': [StackInfo(frame1, 0), StackInfo(frame2, 0)]
290 }
291 match_result1.file_to_analysis_info = {
292 'a.cc': AnalysisInfo(min_distance=1, min_distance_frame=frame1)
293 }
294
295 match_result2 = MatchResult(DUMMY_CHANGELOG3, 'src/')
296 frame3 = StackFrame(15, 'src/', 'func', 'f.cc', 'src/f.cc', [1])
297 match_result2.file_to_stack_infos = {
298 'f.cc': [StackInfo(frame3, 0)]
299 }
300 match_result2.file_to_analysis_info = {
301 'f.cc': AnalysisInfo(min_distance=20, min_distance_frame=frame3)
302 }
303
304 match_result3 = MatchResult(DUMMY_CHANGELOG3, 'src/')
305 frame4 = StackFrame(3, 'src/', 'func', 'ff.cc', 'src/ff.cc', [1])
306 match_result3.file_to_stack_infos = {
307 'f.cc': [StackInfo(frame4, 0)]
308 }
309 match_result3.file_to_analysis_info = {
310 'f.cc': AnalysisInfo(min_distance=60, min_distance_frame=frame4)
311 }
312
313 return [match_result1, match_result2, match_result3]
314
315 self.mock(changelist_classifier, 'FindMatchResults', _MockFindMatchResults)
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 results = self.changelist_classifier(DUMMY_REPORT)
323 self.assertTrue(results,
324 "Expected results, but the classifier didn't return any")
325
326 expected_match_results = [
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([result.ToDict() for result in results],
349 expected_match_results)
350
351 def testFinditForCrashAllMatchResultsWithZeroConfidences(self):
352 """Test that we filter out results with too-large frame indices.
353
354 In the mock results 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 results to be filtered out.
358 """
359 def _MockFindMatchResults(*_):
360 match_result1 = MatchResult(DUMMY_CHANGELOG1, 'src/')
361 frame1 = StackFrame(20, 'src/', '', 'func', 'a.cc', [1])
362 frame2 = StackFrame(21, 'src/', '', 'func', 'a.cc', [7])
363 match_result1.file_to_stack_infos = {
364 'a.cc': [StackInfo(frame1, 0), StackInfo(frame2, 0)]
365 }
366 match_result1.file_to_analysis_info = {
367 'a.cc': AnalysisInfo(min_distance=1, min_distance_frame=frame1)
368 }
369
370 match_result2 = MatchResult(DUMMY_CHANGELOG3, 'src/')
371 frame3 = StackFrame(15, 'src/', '', 'func', 'f.cc', [1])
372 match_result2.file_to_stack_infos = {
373 'f.cc': [StackInfo(frame3, 0)]
374 }
375 match_result2.min_distance = 20
376 match_result2.file_to_analysis_info = {
377 'f.cc': AnalysisInfo(min_distance=20, min_distance_frame=frame3)
378 }
379
380 return [match_result1, match_result2]
381
382 self.mock(changelist_classifier, 'FindMatchResults', _MockFindMatchResults)
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 results = self.changelist_classifier(DUMMY_REPORT)
390 self.assertFalse(results, 'Expected zero results, but found some:\n%s'
391 % pprint.pformat([result.ToDict() for result in results]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698