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