Convert display_unittests to run exclusively on Swarming
[chromium-blink-merge.git] / tools / findit / blame.py
blob5e5494e6cd52d4682ebd4c59a86d64f508d2274a
1 # Copyright (c) 2014 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.
5 from threading import Lock
7 from common import utils
8 import crash_utils
11 class Blame(object):
12 """Represents a blame object.
14 The object contains blame information for one line of stack, and this
15 information is shown when there are no CLs that change the crashing files.
16 Attributes:
17 line_content: The content of the line to find the blame for.
18 component_name: The name of the component for this line.
19 stack_frame_index: The stack frame index of this file.
20 file_name: The name of the file.
21 line_number: The line that caused a crash.
22 author: The author of this line on the latest revision.
23 revision: The latest revision of this line before the crash revision.
24 message: The commit message for the revision.
25 time: When the revision was committed.
26 url: The url of the change for the revision.
27 range_start: The starting range of the regression for this component.
28 range_end: The ending range of the regression.
30 """
32 def __init__(self, line_content, component_name, stack_frame_index,
33 file_name, line_number, author, revision, message, time,
34 url, range_start, range_end):
35 # Set all the variables from the arguments.
36 self.line_content = line_content
37 self.component_name = component_name
38 self.stack_frame_index = stack_frame_index
39 self.file = file_name
40 self.line_number = line_number
41 self.author = author
42 self.revision = revision
43 self.message = message
44 self.time = time
45 self.url = url
46 self.range_start = range_start
47 self.range_end = range_end
50 class BlameList(object):
51 """Represents a list of blame objects.
53 Thread-safe.
54 """
56 def __init__(self):
57 self.blame_list = []
58 self.blame_list_lock = Lock()
60 def __getitem__(self, index):
61 return self.blame_list[index]
63 def FindBlame(self, callstack, component_to_crash_revision_dict,
64 component_to_regression_dict, parsers,
65 top_n_frames=10):
66 """Given a stack within a stacktrace, retrieves blame information.
68 Only either first 'top_n_frames' or the length of stack, whichever is
69 shorter, results are returned. The default value of 'top_n_frames' is 10.
71 Args:
72 callstack: The list of stack frames.
73 component_to_crash_revision_dict: A dictionary that maps component to its
74 crash revision.
75 component_to_regression_dict: A dictionary that maps component to its
76 revision range.
77 parsers: A list of two parsers, svn_parser and git_parser
78 top_n_frames: A number of stack frames to show the blame result for.
79 """
80 # Only return blame information for first 'top_n_frames' frames.
81 stack_frames = callstack.GetTopNFrames(top_n_frames)
82 tasks = []
83 # Iterate through frames in stack.
84 for stack_frame in stack_frames:
85 # If the component this line is from does not have a crash revision,
86 # it is not possible to get blame information, so ignore this line.
87 component_path = stack_frame.component_path
88 if component_path not in component_to_crash_revision_dict:
89 continue
91 crash_revision = component_to_crash_revision_dict[
92 component_path]['revision']
93 range_start = None
94 range_end = None
95 repository_type = crash_utils.GetRepositoryType(crash_revision)
96 repository_parser = parsers[repository_type]
98 # If the revision is in SVN, and if regression information is available,
99 # get it. For Git, we cannot know the ordering between hash numbers.
100 if repository_type == 'svn':
101 if component_to_regression_dict and \
102 component_path in component_to_regression_dict:
103 component_object = component_to_regression_dict[component_path]
104 range_start = int(component_object['old_revision'])
105 range_end = int(component_object['new_revision'])
107 # Create a task to generate blame entry.
108 tasks.append({
109 'function': self.__GenerateBlameEntry,
110 'args': [repository_parser, stack_frame, crash_revision,
111 range_start, range_end]})
113 # Run all the tasks.
114 crash_utils.RunTasks(tasks)
116 def __GenerateBlameEntry(self, repository_parser, stack_frame,
117 crash_revision, range_start, range_end):
118 """Generates blame list from the arguments."""
119 stack_frame_index = stack_frame.index
120 component_path = stack_frame.component_path
121 component_name = stack_frame.component_name
122 file_name = stack_frame.file_name
123 file_path = stack_frame.file_path
124 crashed_line_number = stack_frame.crashed_line_range[0]
126 if file_path.startswith(component_path):
127 file_path = file_path[len(component_path):]
129 # Parse blame information.
130 parsed_blame_info = repository_parser.ParseBlameInfo(
131 component_path, file_path, crashed_line_number, crash_revision)
133 # If it fails to retrieve information, do not do anything.
134 if not parsed_blame_info:
135 return
137 # Create blame object from the parsed info and add it to the list.
138 (line_content, revision, author, url, message, time) = parsed_blame_info
139 blame = Blame(line_content, component_name, stack_frame_index, file_name,
140 crashed_line_number, author, revision, message, time, url,
141 range_start, range_end)
143 with self.blame_list_lock:
144 self.blame_list.append(blame)
146 def FilterAndSortBlameList(self):
147 """Filters and sorts the blame list."""
148 # Sort the blame list by its position in stack.
149 self.blame_list.sort(key=lambda blame: blame.stack_frame_index)
151 filtered_blame_list = []
153 for blame in self.blame_list:
154 # If regression information is available, check if it needs to be
155 # filtered.
156 if blame.range_start and blame.range_end:
158 # Discards results that are after the end of regression.
159 if not utils.IsGitHash(blame.revision) and (
160 int(blame.range_end) <= int(blame.revision)):
161 continue
163 filtered_blame_list.append(blame)
165 self.blame_list = filtered_blame_list