def testFileChangeinfoChangedPathProperty(self): """Test ``changed_file`` property of ``FileChangeInfo``.""" modified_file = FileChangeInfo.Modify('a.cc') self.assertEqual(modified_file.changed_path, 'a.cc') added_file = FileChangeInfo.Modify('a.cc') self.assertEqual(added_file.changed_path, 'a.cc') copied_file = FileChangeInfo.Copy('old.cc', 'new.cc') self.assertEqual(copied_file.changed_path, 'new.cc') deleted_file = FileChangeInfo.Delete('old.cc') self.assertEqual(deleted_file.changed_path, 'old.cc')
def testClassifySuspects(self): """Tests ``ClassifySuspects`` classify a list of ``Suspect``s.""" suspect1 = Suspect(self.GetDummyChangeLog(), 'src/') suspect1.changelog = suspect1.changelog._replace(touched_files=[ FileChangeInfo(ChangeType.MODIFY, 'comp1/a.cc', 'comp1/b.cc') ]) suspect2 = Suspect(self.GetDummyChangeLog(), 'src/') suspect2.changelog = suspect2.changelog._replace(touched_files=[ FileChangeInfo(ChangeType.MODIFY, 'comp2/a.cc', 'comp2/b.cc') ]) self.assertEqual( self.classifier.ClassifySuspects([suspect1, suspect2]), ['Comp1>Dummy', 'Comp2>Dummy'])
def testMinDistanceFeatureInfinityDistance(self): """Test that we return log(0) when the min_distance is infinity. The infinity distance means the touched file get overwritten by other cls, and the change didn't show in the final blame file. """ report = self._GetDummyReport( deps={'src/': Dependency('src/', 'https://repo', '6')}, dep_rolls={'src/': DependencyRoll('src/', 'https://repo', '0', '4')}) suspect = self._GetMockSuspect() crashed = CrashedFile(_MOCK_FRAME) matches = { crashed: CrashMatch(crashed, [FileChangeInfo(ChangeType.MODIFY, 'file', 'file')], [FrameInfo(_MOCK_FRAME, 0)]) } with mock.patch('analysis.linear.changelist_features.min_distance.' 'MinDistanceFeature.' 'DistanceBetweenTouchedFileAndFrameInfos') as mock_distance: mock_distance.return_value = None self.assertEqual( 0.0, min_distance.MinDistanceFeature( self._get_repository, _MAXIMUM)(report)(suspect, matches).value) with mock.patch('analysis.linear.changelist_features.min_distance.' 'MinDistanceFeature.' 'DistanceBetweenTouchedFileAndFrameInfos') as mock_distance: mock_distance.return_value = min_distance.Distance(float('inf'), None) self.assertEqual( 0.0, min_distance.MinDistanceFeature(self._get_repository, 100)(report)( suspect, matches).value)
def testClassifySuspectNoMatch(self): """Tests ``ClassifySuspect`` returns None if there is no file match.""" suspect = Suspect(self.GetDummyChangeLog(), 'dummy') suspect.changelog = suspect.changelog._replace(touched_files=[ FileChangeInfo(ChangeType.MODIFY, 'comp1.cc', 'comp1.cc') ]) self.assertEqual(self.classifier.ClassifySuspect(suspect), [])
def testClassifyTouchedFile(self): """Tests ``ClassifyTouchedFile`` method.""" touched_file = FileChangeInfo(ChangeType.MODIFY, 'comp1/a.cc', 'comp1/b.cc') self.assertEqual( self.classifier.ClassifyTouchedFile('src', touched_file), 'Comp1>Dummy')
def testMinDistanceFeatureMiddling(self): """Test that the feature returns middling scores for middling distances.""" report = self._GetDummyReport( deps={'src/': Dependency('src/', 'https://repo', '6')}, dep_rolls={ 'src/': DependencyRoll('src/', 'https://repo', '0', '4') }) frame = StackFrame(0, 'src/', 'func', 'f.cc', 'f.cc', [232], 'https://repo') distance = 42. crashed = CrashedFile('file') matches = { crashed: CrashMatch(crashed, [FileChangeInfo(ChangeType.MODIFY, 'file', 'file')], [FrameInfo(frame, 0)]) } with mock.patch( 'analysis.linear.changelist_features.' 'min_distance.MinDistanceFeature.' 'DistanceBetweenTouchedFileAndFrameInfos') as mock_distance: mock_distance.return_value = min_distance.Distance(distance, frame) self.assertEqual((_MAXIMUM - distance) / _MAXIMUM, min_distance.MinDistanceFeature( self._get_repository, _MAXIMUM)(report)(self._GetMockSuspect(), matches).value)
def testMinDistanceFeatureIsOverMax(self): """Test that we return log(0) when the min_distance is too large.""" report = self._GetDummyReport( deps={'src/': Dependency('src/', 'https://repo', '6')}, dep_rolls={ 'src/': DependencyRoll('src/', 'https://repo', '0', '4') }) distance = _MAXIMUM + 1 frame = _MOCK_FRAME._replace(file_path='file') crashed = CrashedFile('file') matches = { crashed: CrashMatch(crashed, [FileChangeInfo(ChangeType.MODIFY, 'file', 'file')], [FrameInfo(frame, 0)]) } with mock.patch( 'analysis.linear.changelist_features.' 'min_distance.MinDistanceFeature.' 'DistanceBetweenTouchedFileAndFrameInfos') as mock_distance: mock_distance.return_value = min_distance.Distance(distance, None) self.assertEqual( 0.0, min_distance.MinDistanceFeature( self._get_repository, _MAXIMUM)(report)(self._GetMockSuspect(), matches).value)
def testClassifySuspectNoTouchedFileMatch(self): """Tests ``ClassifySuspect`` returns None if there is no file match.""" suspect = Suspect(self.GetDummyChangeLog(), 'dummy') suspect.touched_files = [ FileChangeInfo(ChangeType.MODIFY, 'a/b.h', 'a/b.h') ] self.assertIsNone(self.classifier.ClassifySuspect(suspect))
def testCheckFileSameLineChanged(self): def MockGetChangedLines(*_): return [1, 3] self.mock(build_failure_analysis, '_GetChangedLinesForChromiumRepo', MockGetChangedLines) touched_file = FileChangeInfo.FromDict({ 'change_type': ChangeType.MODIFY, 'old_path': 'a/b/c.cc', 'new_path': 'a/b/c.cc' }) file_path_in_log = 'a/b/c.cc' justification = build_failure_analysis._Justification() file_name_occurrences = {'c.cc': 1} line_numbers = [1, 3] repo_info = { 'repo_url': 'https://chromium.googlesource.com/chromium/src.git', 'revision': 'dummy_abcd1234' } commit_revision = 'dummy_1' build_failure_analysis._CheckFile(touched_file, file_path_in_log, justification, file_name_occurrences, line_numbers, repo_info, commit_revision) expected_justification = { 'score': 4, 'hints': { 'modified c.cc[1, 3] (and it was in log)': 4 } } self.assertEqual(expected_justification, justification.ToDict())
def testMinDistanceChangedFiles(self): """Tests ``ChangedFile`` method.""" report = self._GetDummyReport( deps={'src/': Dependency('src/', 'https://repo', '6')}, dep_rolls={'src/': DependencyRoll('src/', 'https://repo', '0', '4')}) distance = 42 crashed = CrashedFile(_MOCK_FRAME) matches = { crashed: CrashMatch(crashed, [FileChangeInfo(ChangeType.MODIFY, 'file', 'file')], [FrameInfo(_MOCK_FRAME, 0)]) } frame = StackFrame(0, 'src/', 'func', 'f.cc', 'f.cc', [7], 'https://repo') with mock.patch('analysis.linear.changelist_features.min_distance.' 'MinDistanceFeature.' 'DistanceBetweenTouchedFileAndFrameInfos') as mock_distance: mock_distance.return_value = min_distance.Distance(distance, frame) self.assertEqual( min_distance.MinDistanceFeature( self._get_repository, _MAXIMUM)(report)( self._GetMockSuspect(), matches).changed_files, [ChangedFile(name='file', blame_url=('%s/+blame/%s/f.cc#%d' % (frame.repo_url, report.crashed_version, frame.crashed_line_numbers[0])), reasons=['Distance between touched lines and crashed' ' lines is %d, in frame #%d' % ( distance, frame.index)])])
def testDistanceBetweenTouchedFileAndFrameInfos(self): """Tests ``DistanceBetweenTouchedFileAndFrameInfos`` method.""" feature = min_distance.MinDistanceFeature(self._get_repository, _MAXIMUM) frame1 = StackFrame(0, 'src/', 'func', 'a.cc', 'src/a.cc', [7], repo_url='https://repo_url') frame2 = StackFrame(0, 'src/', 'func', 'a.cc', 'src/a.cc', [17], repo_url='https://repo_url') touched_file = FileChangeInfo(ChangeType.MODIFY, 'file', 'file') blame = Blame('rev', 'src/') blame.AddRegions([Region(0, 10, 'rev', 'a1', 'e1', 't1'), Region(11, 20, 'dummy_rev', 'a2', 'e2', 't2')]) url_to_blame = {'rev/file': blame} def _MockGetBlame(path, revision): revision_path = '%s/%s' % (revision, path) return url_to_blame.get(revision_path) with mock.patch('libs.gitiles.gitiles_repository.GitilesRepository.' 'GetBlame') as mock_get_blame: mock_get_blame.side_effect = _MockGetBlame distance_info = feature.DistanceBetweenTouchedFileAndFrameInfos( 'rev', touched_file, [FrameInfo(frame1, 0), FrameInfo(frame2, 0)], Dependency('src/', 'https://repo', 'rev')) self.assertEqual(distance_info, min_distance.Distance(0, frame1)) distance_info = feature.DistanceBetweenTouchedFileAndFrameInfos( 'wrong_rev', touched_file, [FrameInfo(frame1, 0), FrameInfo(frame2, 0)], Dependency('src/', 'https://repo', 'wrong_rev')) self.assertIsNone(distance_info)
def testClassifySuspect(self): """Tests ``ClassifySuspect`` method.""" suspect = Suspect(self.GetDummyChangeLog(), 'src/') suspect.changelog = suspect.changelog._replace(touched_files=[ FileChangeInfo(ChangeType.MODIFY, 'comp1/a.cc', 'comp1/b.cc') ]) self.assertEqual(self.classifier.ClassifySuspect(suspect), ['Comp1>Dummy'])
def testFileChangeinfo(self): filechange_dict = { 'change_type': 'copy', 'old_path': 'a', 'new_path': 'b' } filechange_info = FileChangeInfo.FromDict(filechange_dict) self.assertEqual(filechange_dict, filechange_info.ToDict())
def testMatchWhenCrashedDirectryIsEmpty(self): """Tests ``Match`` returns False when the crashed directory is empty.""" self.assertFalse( self._feature.Match( None, FileChangeInfo.FromDict({ 'change_type': 'add', 'new_path': 'p/a.cc', 'old_path': None, })))
def testOnlyOneTouchedFilePerMatchedCrashedFile(self): """Test that ``CrashMatch`` can only have 1 touched file.""" report = self._GetDummyReport( deps={'src/': Dependency('src/', 'https://repo', '6')}, dep_rolls={'src/': DependencyRoll('src/', 'https://repo', '0', '4')}) frame = _MOCK_FRAME._replace(file_path='file') crashed = CrashedFile('file') matches = { crashed: CrashMatch(crashed, [FileChangeInfo(ChangeType.MODIFY, 'file', 'file'), FileChangeInfo(ChangeType.MODIFY, 'dummy', 'dummy')], [FrameInfo(frame, 0)]) } self.assertEqual( 0.0, min_distance.MinDistanceFeature(self._get_repository, _MAXIMUM)(report)( self._GetMockSuspect(), matches).value)
def testCall(self): """Tests ``__call__`` method.""" suspect = Suspect(self.GetDummyChangeLog(), 'src/') touched_file = FileChangeInfo.FromDict({'old_path': None, 'new_path': 'a.cc', 'change_type': 'add'}) suspect.changelog = suspect.changelog._replace( touched_files=[touched_file]*15) self.assertEqual(self._feature(None)(suspect).value, 0.0)
def testDistanceBetweenTouchedFileAndFrameInfosWithDeletedFile(self): """Tests that this method returns None when the touched_file is deleted.""" feature = min_distance.MinDistanceFeature(self._get_repository, _MAXIMUM) touched_file = FileChangeInfo(ChangeType.DELETE, 'file', None) distance_info = feature.DistanceBetweenTouchedFileAndFrameInfos( 'rev', touched_file, [FrameInfo(_MOCK_FRAME, 0)], Dependency('src/', 'https://repo', 'rev')) self.assertIsNone(distance_info)
def GetFileChangeInfo(change_type, path1, path2): """Set old/new path and old/new mode.""" change_type = change_type.lower() if change_type == ChangeType.MODIFY: return FileChangeInfo.Modify(path1) if change_type == ChangeType.ADD: return FileChangeInfo.Add(path1) if change_type == ChangeType.DELETE: return FileChangeInfo.Delete(path1) if change_type == ChangeType.RENAME: return FileChangeInfo.Rename(path1, path2) # TODO(http://crbug.com/659346): write coverage test for this branch if change_type.lower() == ChangeType.COPY: # pragma: no cover return FileChangeInfo.Copy(path1, path2) return None
def testFilePathMatchAfterFileRename(self): """Tests the feature can match old file with new file after file name.""" feature = TouchCrashedDirectoryBaseFeature( options={'replace_path': { 'old/dir': 'new/dir' }}) self.assertTrue( feature.Match( CrashedDirectory('new/dir/a'), FileChangeInfo(ChangeType.MODIFY, 'old/dir/a/FileName.cc', 'old/dir/a/FileName.cc')))
def testFeatureValueIsOneWhenThereIsMatchedDirectory(self): """Test that feature value is 1 when there is matched directory.""" changelog = self.GetDummyChangeLog()._replace(touched_files=[ FileChangeInfo.FromDict({ 'change_type': 'add', 'new_path': 'p/a.cc', 'old_path': None, }) ]) suspect = Suspect(changelog, 'src/') feature_value = self._feature(self._report)(suspect) self.assertEqual(1.0, feature_value.value)
def testFilePathMatchAfterReplacePath(self): """Tests the feature can match old file with new file after file move.""" feature = TouchCrashedFileMetaFeature( [TouchCrashedFileFeature()], options={'replace_path': { 'old/dir': 'new/dir' }}) self.assertTrue( feature.Match( CrashedFile('new/dir/a/file.cc'), FileChangeInfo(ChangeType.MODIFY, 'old/dir/a/file.cc', 'old/dir/a/file.cc')))
def testMatchesTouchedFile(self): """Tests that ``MatchesTouchedFile`` matches touched files correctly.""" touched_file = FileChangeInfo(ChangeType.MODIFY, 'a/b.h', 'a/b.h') self.assertFalse( self.chromium_project.MatchesTouchedFile( 'dummy', touched_file.changed_path)) self.assertTrue( self.chromium_project.MatchesTouchedFile( 'src/', touched_file.changed_path)) deleted_file = FileChangeInfo(ChangeType.DELETE, 'a/b.h', None) self.assertTrue( self.chromium_project.MatchesTouchedFile( 'src/', deleted_file.changed_path)) self.assertFalse( self.android_project.MatchesTouchedFile('src/', deleted_file.changed_path)) add_file = FileChangeInfo(ChangeType.ADD, None, 'googleplex-android/b.java') self.assertTrue( self.android_project.MatchesTouchedFile('android_path/', add_file.changed_path))
def testFeatureValueIsZeroWhenAFileIsDeleted(self): """Tests that the feature returns 0 when a file is deleted.""" changelog = self.GetDummyChangeLog()._replace( # File deleted in the same directory: touched_files=[ FileChangeInfo.FromDict({ 'change_type': 'delete', 'new_path': None, 'old_path': 'p/a.cc', }) ]) suspect = Suspect(changelog, 'src/') feature_value = self._feature(self._report)(suspect) self.assertEqual(0.0, feature_value.value)
def testFilePathMatchAfterChangeFileExtension(self): """Tests feature can match old file with new file after extension change.""" feature = TouchCrashedFileMetaFeature( [TouchCrashedFileFeature()], options={'change_file_extension': { 'dir/a': { 'cpp': 'cc' } }}) self.assertTrue( feature.Match( CrashedFile('dir/a/file_name.cc'), FileChangeInfo(ChangeType.MODIFY, 'dir/a/file_name.cpp', 'dir/a/file_name.cpp')))
def testFilePathMatchAfterChangeNamingConvention(self): """Tests the feature can match old file with new file after file name.""" feature = TouchCrashedFileMetaFeature([TouchCrashedFileFeature()], options={ 'change_naming_convention': { 'dir/a': 'capital_to_underscore' } }) self.assertTrue( feature.Match( CrashedFile('dir/a/file_name.cc'), FileChangeInfo(ChangeType.MODIFY, 'dir/a/FileName.cc', 'dir/a/FileName.cc')))
def testReplacePath(self): """Tests feature can still match components with files after file move.""" components = [Component('new_comp', ['src/dep/b/new_dir'], '', 'team')] # Only construct the classifier once, rather than making a new one every # time we call a method on it. classifier = ComponentClassifier(components, 3, _MOCK_REPO_TO_DEP_PATH) feature = TouchCrashedComponentFeature( classifier, options={'replace_path': { 'a/old_dir': 'b/new_dir' }}) match_func = feature.GetMatchFunction('src/dep') self.assertTrue( match_func( CrashedComponent('new_comp'), FileChangeInfo(ChangeType.MODIFY, 'a/old_dir/f.cc', 'a/old_dir/f.cc')))
def testGetChangedLinesNoneBlame(self): repo_info = { 'repo_url': 'https://chromium.googlesource.com/chromium/src.git', 'revision': '10' } touched_file = FileChangeInfo.FromDict({ 'change_type': ChangeType.MODIFY, 'old_path': 'a/b/c.cc', 'new_path': 'a/b/c.cc' }) line_numbers = [2, 7, 8] commit_revision = '7' self.mock(CachedGitilesRepository, 'GetBlame', self._MockGetBlame) changed_line_numbers = ( build_failure_analysis._GetChangedLinesForChromiumRepo( repo_info, touched_file, line_numbers, commit_revision)) self.assertEqual([], changed_line_numbers)
def testFeatureValueIsOneWhenThereIsMatchedDirectory(self): """Test that feature value is 1 when there is matched directory.""" frame1 = StackFrame(0, 'src/', 'func', 'p/f.cc', 'src/p/f.cc', [2, 3], 'h://repo') stack = CallStack(0, frame_list=[frame1]) stack_trace = Stacktrace([stack], stack) deps = {'src/': Dependency('src/', 'h://repo', '8')} dep_rolls = {'src/': DependencyRoll('src/', 'h://repo', '2', '6')} report = CrashReport('8', 'sig', 'linux', stack_trace, ('2', '6'), deps, dep_rolls) changelog = self.GetDummyChangeLog()._replace(touched_files=[ FileChangeInfo.FromDict({ 'change_type': 'add', 'new_path': 'p/a.cc', 'old_path': None, }) ]) suspect = Suspect(changelog, 'src/') feature_value = self._feature(report)(suspect) self.assertEqual(1.0, feature_value.value)
def _ParseChangeLogFromLogData(self, data): change_info = commit_util.ExtractChangeInfo(data['message'], self._ref) touched_files = [] for file_diff in data['tree_diff']: change_type = file_diff['type'].lower() if not diff.IsKnownChangeType(change_type): raise Exception('Unknown change type "%s"' % change_type) touched_files.append( FileChangeInfo(change_type, file_diff['old_path'], file_diff['new_path'])) reverted_revision = commit_util.GetRevertedRevision(data['message']) url = '%s/+/%s' % (self.repo_url, data['commit']) return ChangeLog(self._ContributorFromDict(data['author']), self._ContributorFromDict(data['committer']), data['commit'], change_info.get('commit_position'), data['message'], touched_files, url, change_info.get('code_review_url'), reverted_revision, change_info.get('host'), change_info.get('change_id'))
def ChangeLogFromDict(data): touched_files = [ FileChangeInfo.FromDict(touched_file) for touched_file in data.get('touched_files', []) ] author = Contributor( data.get('author', {}).get('name', 'author'), data.get('author', {}).get('email', 'email'), data.get('author', {}).get('time', 'time')) committer = Contributor( data.get('committer', {}).get('name', 'committer'), data.get('committer', {}).get('email', 'email'), data.get('committer', {}).get('time', 'time')) return ChangeLog(author, committer, data.get('revision'), data.get('commit_position'), data.get('message'), touched_files, data.get('commit_url'), data.get('code_review_url'), data.get('reverted_revision'), data.get('review_server_host'), data.get('review_change_id'))