| 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 | 5 |
| 6 from collections import namedtuple |
| 7 |
| 6 # TODO(http://crbug.com/644476): this class needs a better name. | 8 # TODO(http://crbug.com/644476): this class needs a better name. |
| 9 class AnalysisInfo(namedtuple('AnalysisInfo', |
| 10 ['min_distance', 'min_distance_frame'])): |
| 11 __slots__ = () |
| 12 |
| 13 |
| 14 # TODO(http://crbug.com/644476): this class needs a better name. |
| 15 # TODO(wrengr): subclass namedtuple, so most things are immutable. |
| 7 class Result(object): | 16 class Result(object): |
| 8 """Represents findit culprit result.""" | 17 """Represents findit culprit result.""" |
| 9 | 18 |
| 10 def __init__(self, changelog, dep_path, | 19 def __init__(self, changelog, dep_path, |
| 11 confidence=None, reasons=None, changed_files=None): | 20 confidence=None, reasons=None, changed_files=None): |
| 12 self.changelog = changelog | 21 self.changelog = changelog |
| 13 self.dep_path = dep_path | 22 self.dep_path = dep_path |
| 14 self.confidence = confidence | 23 self.confidence = confidence |
| 15 self.reasons = reasons | 24 self.reasons = reasons |
| 16 self.changed_files = changed_files | 25 self.changed_files = changed_files |
| 17 | 26 |
| 27 # TODO(wrengr): (a) make these two fields private/readonly |
| 28 # TODO(wrengr): (b) zip them together. |
| 29 # TODO(wrengr): replace "stack_info" pair with a namedtuple. |
| 18 self.file_to_stack_infos = {} | 30 self.file_to_stack_infos = {} |
| 31 # TODO(wrengr): replace "analysis_info" dict with a namedtuple. |
| 19 self.file_to_analysis_info = {} | 32 self.file_to_analysis_info = {} |
| 20 | 33 |
| 21 def ToDict(self): | 34 def ToDict(self): |
| 22 return { | 35 return { |
| 23 'url': self.changelog.commit_url, | 36 'url': self.changelog.commit_url, |
| 24 'review_url': self.changelog.code_review_url, | 37 'review_url': self.changelog.code_review_url, |
| 25 'revision': self.changelog.revision, | 38 'revision': self.changelog.revision, |
| 26 'project_path': self.dep_path, | 39 'project_path': self.dep_path, |
| 27 'author': self.changelog.author_email, | 40 'author': self.changelog.author_email, |
| 28 'time': str(self.changelog.author_time), | 41 'time': str(self.changelog.author_time), |
| 29 'reasons': self.reasons, | 42 'reasons': self.reasons, |
| 30 'changed_files': self.changed_files, | 43 'changed_files': self.changed_files, |
| 31 'confidence': self.confidence, | 44 'confidence': self.confidence, |
| 32 } | 45 } |
| 33 | 46 |
| 47 # TODO(katesonia): This is unusable for logging because in all the |
| 48 # cases that need logging it returns the empty string! We should print |
| 49 # this out in a more useful way (e.g., how CrashConfig is printed) |
| 50 # so that callers don't have to use |str(result.ToDict())| instead. If |
| 51 # we want a method that does what this one does, we should give it a |
| 52 # different name that indicates what it's actually printing out. |
| 34 def ToString(self): | 53 def ToString(self): |
| 35 if not self.file_to_stack_infos: | 54 if not self.file_to_stack_infos: |
| 36 return '' | 55 return '' |
| 37 | 56 |
| 38 lines = [] | 57 lines = [] |
| 39 for file_path, stack_infos in self.file_to_stack_infos.iteritems(): | 58 for file_path, stack_infos in self.file_to_stack_infos.iteritems(): |
| 40 line_parts = [] | 59 line_parts = [] |
| 41 for frame, _ in stack_infos: | 60 for frame, _ in stack_infos: |
| 42 line_parts.append('frame #%d' % frame.index) | 61 line_parts.append('frame #%d' % frame.index) |
| 43 | 62 |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 85 region_end = region_start + region.count - 1 | 104 region_end = region_start + region.count - 1 |
| 86 for frame, _ in stack_infos: | 105 for frame, _ in stack_infos: |
| 87 frame_start = frame.crashed_line_numbers[0] | 106 frame_start = frame.crashed_line_numbers[0] |
| 88 frame_end = frame.crashed_line_numbers[-1] | 107 frame_end = frame.crashed_line_numbers[-1] |
| 89 distance = _DistanceBetweenLineRanges((frame_start, frame_end), | 108 distance = _DistanceBetweenLineRanges((frame_start, frame_end), |
| 90 (region_start, region_end)) | 109 (region_start, region_end)) |
| 91 if distance < min_distance: | 110 if distance < min_distance: |
| 92 min_distance = distance | 111 min_distance = distance |
| 93 min_distance_frame = frame | 112 min_distance_frame = frame |
| 94 | 113 |
| 95 self.file_to_analysis_info[file_path] = { | 114 self.file_to_analysis_info[file_path] = AnalysisInfo( |
| 96 'min_distance': min_distance, | 115 min_distance = min_distance, |
| 97 'min_distance_frame': min_distance_frame, | 116 min_distance_frame = min_distance_frame, |
| 98 } | 117 ) |
| 99 | 118 |
| 100 | 119 |
| 101 def _DistanceBetweenLineRanges((start1, end1), (start2, end2)): | 120 def _DistanceBetweenLineRanges((start1, end1), (start2, end2)): |
| 102 """Given two ranges, compute the (unsigned) distance between them. | 121 """Given two ranges, compute the (unsigned) distance between them. |
| 103 | 122 |
| 104 Args: | 123 Args: |
| 105 start1: the start of the first range | 124 start1: the start of the first range |
| 106 end1: the end of the first range. Must be greater than start1. | 125 end1: the end of the first range. Must be greater than start1. |
| 107 start2: the start of the second range | 126 start2: the start of the second range |
| 108 end2: the end of the second range. Must be greater than start2. | 127 end2: the end of the second range. Must be greater than start2. |
| 109 | 128 |
| 110 Returns: | 129 Returns: |
| 111 If the end of the earlier range comes before the start of the later | 130 If the end of the earlier range comes before the start of the later |
| 112 range, then the difference between those points. Otherwise, returns | 131 range, then the difference between those points. Otherwise, returns |
| 113 zero (because the ranges overlap).""" | 132 zero (because the ranges overlap).""" |
| 114 assert end1 >= start1 | 133 assert end1 >= start1 |
| 115 assert end2 >= start2 | 134 assert end2 >= start2 |
| 116 # There are six possible cases, but in all the cases where the two | 135 # There are six possible cases, but in all the cases where the two |
| 117 # ranges overlap, the latter two differences will be negative. | 136 # ranges overlap, the latter two differences will be negative. |
| 118 return max(0, start2 - end1, start1 - end2) | 137 return max(0, start2 - end1, start1 - end2) |
| 119 | 138 |
| 120 | 139 |
| 121 class MatchResults(dict): | 140 class MatchResults(dict): |
| 122 """A dict indexing MatchResult with its revision.""" | 141 """A dict indexing MatchResult with its revision.""" |
| 123 | 142 |
| 124 def __init__(self, ignore_cls=None): | 143 def __init__(self, ignore_cls=None): |
| 125 super(MatchResults, self).__init__() | 144 super(MatchResults, self).__init__() |
| 126 self.ignore_cls = ignore_cls | 145 self._ignore_cls = ignore_cls |
| 127 | 146 |
| 128 def GenerateMatchResults(self, file_path, dep_path, | 147 def GenerateMatchResults(self, file_path, dep_path, |
| 129 stack_infos, changelogs, blame): | 148 stack_infos, changelogs, blame): |
| 130 """Generates match results. | 149 """Generates match results. |
| 131 | 150 |
| 132 Match results are generated based on newly found file path, its stack_infos, | 151 Match results are generated based on newly found file path, its stack_infos, |
| 133 and all the changelogs that touched this file in the dep in regression | 152 and all the changelogs that touched this file in the dep in regression |
| 134 ranges, those reverted changelogs should be ignored. | 153 ranges, those reverted changelogs should be ignored. |
| 135 | 154 |
| 136 Args: | 155 Args: |
| 137 file_path (str): File path of the crashed file. | 156 file_path (str): File path of the crashed file. |
| 138 dep_path (str): Path of the dependency of the file. | 157 dep_path (str): Path of the dependency of the file. |
| 139 stack_infos (list): List of stack_info dicts, represents frames of this | 158 stack_infos (list): List of stack_info dicts, represents frames of this |
| 140 file and the callstack priorities of those frames. | 159 file and the callstack priorities of those frames. |
| 141 changelogs (list): List of Changelog objects in the dep in regression | 160 changelogs (list): List of Changelog objects in the dep in regression |
| 142 range which touched the file. | 161 range which touched the file. |
| 143 blame (Blame): Blame of the file. | 162 blame (Blame): Blame of the file. |
| 144 """ | 163 """ |
| 145 for changelog in changelogs: | 164 for changelog in changelogs: |
| 146 if self.ignore_cls and changelog.revision in self.ignore_cls: | 165 if self._ignore_cls and changelog.revision in self._ignore_cls: |
| 147 continue | 166 continue |
| 148 | 167 |
| 149 if changelog.revision not in self: | 168 if changelog.revision not in self: |
| 150 self[changelog.revision] = MatchResult(changelog, dep_path) | 169 self[changelog.revision] = MatchResult(changelog, dep_path) |
| 151 | 170 |
| 152 match_result = self[changelog.revision] | 171 match_result = self[changelog.revision] |
| 153 match_result.Update(file_path, stack_infos, blame) | 172 match_result.Update(file_path, stack_infos, blame) |
| OLD | NEW |