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

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: . 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.
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 JoinAsFilePath(file_dir, file_name):
qyearsley 2015/01/15 19:10:28 Should this function be marked as private? (Same q
stgao 2015/01/16 20:21:38 Done.
27 if file_dir:
28 return '%s/%s' % (file_dir, file_name)
29 else:
30 return file_name
31
32
33 def NormalizeObjectFile(file_path):
34 # During compile, a/b/c/file.cc in TARGET will be compiled into object
35 # file a/b/c/TARGET.file.o, thus TARGET needs removing from path.
36 if file_path.startswith('obj/'):
37 file_path = file_path[4:]
38
39 file_dir = os.path.dirname(file_path)
40
41 file_name = os.path.basename(file_path)
42 parts = file_name.split('.', 1)
43 if len(parts) == 2 and parts[1].endswith('.o'):
44 file_name = parts[1]
45
46 return JoinAsFilePath(file_dir, file_name)
qyearsley 2015/01/15 21:15:03 For both uses of JoinAsFilePath in this file, you
stgao 2015/01/16 20:21:38 Done. Switch to os.path.join, but need to do a re
qyearsley 2015/01/16 22:55:25 Ah, very observant. Sounds good.
47
48
49 COMMON_POSTFIXES = [
qyearsley 2015/01/15 19:10:28 1) s/POSTFIX/SUFFIX/ 2) If this is only used in th
stgao 2015/01/16 20:21:39 Done.
50 'impl',
51 'gcc', 'msvc',
52 'arm', 'arm64', 'mips', 'portable', 'x86',
53 'android', 'ios', 'linux', 'mac', 'ozone', 'posix', 'win',
54 'aura', 'x', 'x11']
qyearsley 2015/01/15 19:10:28 Style nit: Closing parenthesis goes on its own lin
stgao 2015/01/16 20:21:38 Done.
55
56 COMMON_POSTFIX_PATTERNS = [
57 re.compile('.*(\_[^\_]*test)$'),
58 ] + [
59 re.compile('.*(\_%s)$' % postfix) for postfix in COMMON_POSTFIXES
60 ]
qyearsley 2015/01/15 19:10:28 It seems like these four lines should be un-indent
stgao 2015/01/16 20:21:38 Done.
61
62
63 def StripExtensionAndCommonPostfix(file_path):
64 """Strip extension and common postfixes from file name to guess correlation.
65
66 Examples:
67 file_impl.cc, file_unittest.cc, file_impl_mac.h -> file
68 """
69 file_dir = os.path.dirname(file_path)
70 file_name = os.path.splitext(os.path.basename(file_path))[0]
71 while True:
72 match = None
73 for postfix_patten in COMMON_POSTFIX_PATTERNS:
74 match = postfix_patten.match(file_name)
75 if match:
76 file_name = file_name[:-len(match.group(1))]
77 break
78
79 if not match:
80 break
81
82 return JoinAsFilePath(file_dir, file_name)
83
84
85 def IsCorrelated(src_file, file_path):
86 """Check if two files are correlated.
87
88 Examples:
89 1. file.h <-> file_impl.cc
90 2. file_impl.cc <-> file_unittest.cc
91 3. file_win.cc <-> file_mac.cc
92 4. a/b/x.cc <-> a/b/y.cc
93 5. x.h <-> x.cc
94 """
95 if file_path.endswith('.o'):
96 file_path = NormalizeObjectFile(file_path)
97
98 # Example: a/b/file_impl.cc <-> a/b/file_unittest.cc
99 if IsSameFile(StripExtensionAndCommonPostfix(src_file),
100 StripExtensionAndCommonPostfix(file_path)):
101 return True
102
103 # Two file are in the same directory: a/b/x.cc <-> a/b/y.cc
104 # TODO: cause noisy result?
105 src_file_dir = os.path.dirname(src_file)
106 return src_file_dir and src_file_dir == os.path.dirname(file_path)
107
108
109 def CheckFiles(failure_signal, change_log):
110 result = {
111 'suspects' : 0,
112 'scores' : 0,
113 'hints' : []
114 }
115
116 def _AddHint(change_action, src_file, file_path):
qyearsley 2015/01/15 19:10:28 Nested functions don't actually need to be marked
stgao 2015/01/16 20:21:38 Done. Moved _AddHint to a new class _Justificatio
117 # TODO: make hint more descriptive?
118 if src_file != file_path:
119 result['hints'].append(
120 '%s %s (%s)' % (change_action, src_file, file_path))
121 else:
122 result['hints'].append('%s %s' % (change_action, src_file))
123
124 def _CheckFile(change_action, src_file, file_path, suspect, score):
125 # 'suspects' and 'scores' already defined - pylint: disable=E0602, W0612
126 if IsSameFile(src_file, file_path):
127 result['suspects'] += suspect
128 result['scores'] += score
129 _AddHint(change_action, src_file, file_path)
130 elif IsCorrelated(src_file, file_path):
131 result['scores'] += 1
132 _AddHint(change_action, src_file, file_path)
133
134 for file_path, _ in failure_signal.files.iteritems():
135 # TODO(stgao): remove this hack when DEPS parsing is supported.
136 if file_path.startswith('src/'):
137 file_path = file_path[4:]
138
139 for touched_file in change_log['touched_files']:
140 change_type = touched_file['change_type']
141
142 if change_type in (ChangeType.ADD, ChangeType.COPY, ChangeType.RENAME):
143 _CheckFile('add', touched_file['new_path'], file_path, 1, 5)
144
145 if change_type == ChangeType.MODIFY:
146 if IsSameFile(touched_file['new_path'], file_path):
147 # TODO: use line number for git blame.
148 result['scores'] += 1
149 _AddHint('modify', touched_file['new_path'], file_path)
150 continue
151 elif IsCorrelated(touched_file['new_path'], file_path):
152 result['scores'] += 1
153 _AddHint('modify', touched_file['new_path'], file_path)
154 continue
155
156 if change_type in (ChangeType.DELETE, ChangeType.RENAME):
157 _CheckFile('delete', touched_file['old_path'], file_path, 1, 5)
158
159 if not result['scores']:
160 return None
161 else:
162 return result
163
164
165 def AnalyzeBuildFailure(failure_info, change_logs, failure_signals):
166 analysis_result = {}
167
168 if not failure_info['failed']:
169 return analysis_result
170
171 failed_steps = failure_info['failed_steps']
172 builds = failure_info['builds']
173 for step_name, step_failure_info in failed_steps.iteritems():
174 failure_signal = FailureSignal.FromJson(failure_signals[step_name])
175 failed_build_number = step_failure_info['current_failure']
176 build_number = step_failure_info['first_failure']
177
178 step_analysis_result = {}
179
180 while build_number <= failed_build_number:
181 for revision in builds[str(build_number)]['blame_list']:
182 justification = CheckFiles(failure_signal, change_logs[revision])
183 if justification:
184 step_analysis_result[revision] = justification
185
186 build_number += 1
187
188 if step_analysis_result:
189 # TODO: sorted CLs related to a step failure.
190 analysis_result[step_name] = step_analysis_result
191
192 return analysis_result
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698