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

Side by Side Diff: appengine/findit/waterfall/build_failure_analysis.py

Issue 838003004: [Findit] Add three sub-pipelines to analyze build failure. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Address comments. Created 5 years, 11 months 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 2015 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 collections
6 import os
7 import re
8
9 from common.diff import ChangeType
10 from waterfall.failure_signal import FailureSignal
11
12
13 def _IsSameFile(src_file, file_path):
14 """Guess if the two files are the same.
qyearsley 2015/01/16 22:55:26 The first line of the docstring should use third-p
stgao 2015/01/21 20:29:22 Done for files in this CL. Will do a cleanup of ot
15
16 File paths from a failure might not be full paths. So match string by postfix.
17 Examples:
18 src/chrome/test/base/chrome_process_util.h <-> base/chrome_process_util.h
19 """
20 if os.path.basename(src_file) != os.path.basename(file_path):
21 return False
22 else:
23 return src_file.endswith(file_path)
24
25
26 def _NormalizeObjectFile(file_path):
27 # During compile, a/b/c/file.cc in TARGET will be compiled into object
28 # file a/b/c/TARGET.file.o, thus TARGET needs removing from path.
29 if file_path.startswith('obj/'):
30 file_path = file_path[4:]
31
32 file_dir = os.path.dirname(file_path)
33
qyearsley 2015/01/16 22:55:25 Probably no need to have visual separation above a
stgao 2015/01/21 20:29:23 Done.
34 file_name = os.path.basename(file_path)
35 parts = file_name.split('.', 1)
36 if len(parts) == 2 and parts[1].endswith('.o'):
37 file_name = parts[1]
38
39 return os.path.join(file_dir, file_name).replace(os.sep, '/')
40
41
42 _COMMON_SUFFIXES= [
qyearsley 2015/01/16 22:55:25 Missing a space after "=".
stgao 2015/01/21 20:29:22 Ooops, done.
43 'impl',
44 'gcc', 'msvc',
45 'arm', 'arm64', 'mips', 'portable', 'x86',
46 'android', 'ios', 'linux', 'mac', 'ozone', 'posix', 'win',
47 'aura', 'x', 'x11',
48 ]
49
50 _COMMON_SUFFIX_PATTERNS = [
51 re.compile('.*(\_[^\_]*test)$'),
qyearsley 2015/01/16 22:55:25 This also matches "foo_blahtest", which means the
stgao 2015/01/21 20:29:22 OK. Restrict this pattern to a list of common suff
52 ] + [
53 re.compile('.*(\_%s)$' % postfix) for postfix in _COMMON_SUFFIXES
54 ]
55
56
57 def _StripExtensionAndCommonPostfix(file_path):
58 """Strip extension and common postfixes from file name to guess correlation.
qyearsley 2015/01/16 22:55:26 Strip -> Strips Postfix -> Suffix
stgao 2015/01/21 20:29:22 Done.
59
60 Examples:
61 file_impl.cc, file_unittest.cc, file_impl_mac.h -> file
62 """
63 file_dir = os.path.dirname(file_path)
64 file_name = os.path.splitext(os.path.basename(file_path))[0]
65 while True:
66 match = None
67 for postfix_patten in _COMMON_SUFFIX_PATTERNS:
qyearsley 2015/01/16 22:55:25 postfix_patten -> suffix_pattern
stgao 2015/01/21 20:29:23 Done.
68 match = postfix_patten.match(file_name)
69 if match:
70 file_name = file_name[:-len(match.group(1))]
71 break
72
73 if not match:
74 break
75
76 return os.path.join(file_dir, file_name).replace(os.sep, '/')
77
78
79 def _IsCorrelated(src_file, file_path):
80 """Check if two files are correlated.
qyearsley 2015/01/16 22:55:25 Maybe just "related", rather than "correlated"?
stgao 2015/01/21 20:29:22 Done.
81
82 Example of correlated files:
83 1. file.h <-> file_impl.cc
84 2. file_impl.cc <-> file_unittest.cc
85 3. file_win.cc <-> file_mac.cc
86 4. a/b/x.cc <-> a/b/y.cc
87 5. x.h <-> x.cc
88 """
89 if file_path.endswith('.o'):
90 file_path = _NormalizeObjectFile(file_path)
91
92 if _IsSameFile(_StripExtensionAndCommonPostfix(src_file),
93 _StripExtensionAndCommonPostfix(file_path)):
94 return True
95
96 # Two file are in the same directory: a/b/x.cc <-> a/b/y.cc
97 # TODO: cause noisy result?
98 src_file_dir = os.path.dirname(src_file)
99 return src_file_dir and src_file_dir == os.path.dirname(file_path)
100
101
102 class _Justification(object):
qyearsley 2015/01/16 22:55:25 What does this class contain/represent? Evidence f
stgao 2015/01/21 20:29:23 Added a docstring to explain its usage, hints, and
103 def __init__(self):
104 self.suspects = 0
105 self.scores = 0
qyearsley 2015/01/16 22:55:26 Using a plural word in a variable name usually sug
stgao 2015/01/21 20:29:22 Done. Explanation of the difference between the s
106 self.hints = list()
qyearsley 2015/01/16 22:55:26 Do these all need to be public? Also, you could us
stgao 2015/01/21 20:29:23 Done.
107
108 def AddFileChange(self, change_action, src_file, file_path, suspect, score):
109 """Add a suspected file change.
110
111 Args:
112 change_action (str): The change type (modify/add/delete) of the file.
qyearsley 2015/01/16 22:55:26 Saying what the arg type is is useful, but args se
stgao 2015/01/21 20:29:22 I also get used to the docstring format as you sug
qyearsley 2015/01/27 00:58:48 Alright, that looks good -- if it's in the style g
113 src_file (str): The file changed by a CL.
114 file_path (str): The file showing in the failure log.
115 suspect (int): Suspect points for the file change.
116 score (int): Score for the file change.
117 """
118 self.suspects += suspect
119 self.scores += score
120
121 # TODO: make hint more descriptive?
122 if src_file != file_path:
123 self.hints.append(
124 '%s %s (%s)' % (change_action, src_file, file_path))
125 else:
126 self.hints.append('%s %s' % (change_action, src_file))
127
128 def ToDict(self):
129 return {
130 'suspects': self.suspects,
131 'scores': self.scores,
132 'hints': self.hints,
133 }
134
135
136 def _CheckFile(
137 change_action, src_file, file_path, suspect, score, justification):
138 """Check if the given files are the same or correlated.
139
140 Args:
141 change_action (str): The change type (modify/add/delete) of the file.
142 src_file (str): The file changed by a CL.
143 file_path (str): The file showing in the failure log.
144 suspect (int): Suspect points if the two files are the same.
145 score (int): Score if the two files are the same.
146 justification (_Justification): An instance of _Justification.
147 """
148 if _IsSameFile(src_file, file_path):
149 justification.AddFileChange(
150 change_action, src_file, file_path, suspect, score)
151 elif _IsCorrelated(src_file, file_path):
152 # For correlated files, do suspect=0 and score=1, because it is not highly
153 # suspected.
154 justification.AddFileChange(
155 change_action, src_file, file_path, 0, 1)
156
157
158 def CheckFiles(failure_signal, change_log):
qyearsley 2015/01/16 22:55:26 Non-trivial public functions (CheckFiles and Analy
stgao 2015/01/21 20:29:22 Done.
159 justification = _Justification()
160
161 for file_path, _ in failure_signal.files.iteritems():
162 # TODO(stgao): remove this hack when DEPS parsing is supported.
163 if file_path.startswith('src/'):
164 file_path = file_path[4:]
165
166 for touched_file in change_log['touched_files']:
167 change_type = touched_file['change_type']
168
169 if change_type == ChangeType.MODIFY:
170 if _IsSameFile(touched_file['new_path'], file_path):
171 # TODO: use line number for git blame.
172 justification.AddFileChange(
173 'modify', touched_file['new_path'], file_path, 0, 1)
174 elif _IsCorrelated(touched_file['new_path'], file_path):
175 justification.AddFileChange(
176 'modify', touched_file['new_path'], file_path, 0, 1)
177
178 if change_type in (ChangeType.ADD, ChangeType.COPY, ChangeType.RENAME):
179 _CheckFile('add', touched_file['new_path'], file_path, 1, 5,
180 justification)
181
182 if change_type in (ChangeType.DELETE, ChangeType.RENAME):
183 _CheckFile('delete', touched_file['old_path'], file_path, 1, 5,
184 justification)
185
186 if not justification.scores:
187 return None
188 else:
189 return justification.ToDict()
190
191
192 def AnalyzeBuildFailure(failure_info, change_logs, failure_signals):
193 analysis_result = {}
194
195 if not failure_info['failed']:
196 return analysis_result
197
198 failed_steps = failure_info['failed_steps']
199 builds = failure_info['builds']
200 for step_name, step_failure_info in failed_steps.iteritems():
201 failure_signal = FailureSignal.FromJson(failure_signals[step_name])
202 failed_build_number = step_failure_info['current_failure']
203 build_number = step_failure_info['first_failure']
204
205 step_analysis_result = {}
206
207 while build_number <= failed_build_number:
208 for revision in builds[str(build_number)]['blame_list']:
209 justification = CheckFiles(failure_signal, change_logs[revision])
qyearsley 2015/01/16 22:55:26 Maybe justification_dict, since this (as is) is ei
stgao 2015/01/21 20:29:22 Done.
210 if justification:
211 step_analysis_result[revision] = justification
212
213 build_number += 1
214
215 if step_analysis_result:
216 # TODO: sorted CLs related to a step failure.
217 analysis_result[step_name] = step_analysis_result
218
219 return analysis_result
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698