OLD | NEW |
1 # Copyright 2015 The Chromium Authors. All rights reserved. | 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 | 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 import collections | 5 import collections |
6 import os | 6 import os |
7 import re | 7 import re |
8 | 8 |
9 from common.diff import ChangeType | 9 from common.diff import ChangeType |
10 from waterfall.failure_signal import FailureSignal | 10 from waterfall.failure_signal import FailureSignal |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
152 if changed_src_file_path != file_path_in_log: | 152 if changed_src_file_path != file_path_in_log: |
153 hint = '%s %s (%s was in log)' % ( | 153 hint = '%s %s (%s was in log)' % ( |
154 change_action, changed_src_file_path, file_path_in_log) | 154 change_action, changed_src_file_path, file_path_in_log) |
155 else: | 155 else: |
156 hint = '%s %s (and it was in log)' % ( | 156 hint = '%s %s (and it was in log)' % ( |
157 change_action, changed_src_file_path) | 157 change_action, changed_src_file_path) |
158 | 158 |
159 self._hints[hint] += score | 159 self._hints[hint] += score |
160 self._score += score | 160 self._score += score |
161 | 161 |
| 162 def AddDEPSRoll(self, dep_path, dep_repo_url, dep_new_revision, |
| 163 dep_old_revision, file_path_in_log, score): |
| 164 url_to_changes_in_roll = '%s/+log/%s..%s?pretty=fuller' % ( |
| 165 dep_repo_url, dep_old_revision[:6], dep_new_revision[:6]) |
| 166 hint = ('Rolled %s %s (%s was in log)' % ( |
| 167 dep_path, url_to_changes_in_roll, file_path_in_log)) |
| 168 self._hints[hint] = score |
| 169 self._score += score |
| 170 |
162 def ToDict(self): | 171 def ToDict(self): |
163 return { | 172 return { |
164 'score': self._score, | 173 'score': self._score, |
165 'hints': self._hints, | 174 'hints': self._hints, |
166 } | 175 } |
167 | 176 |
168 | 177 |
169 def _CheckFile(touched_file, | 178 def _CheckFile(touched_file, |
170 file_path_in_log, | 179 file_path_in_log, |
171 justification, | 180 justification, |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
227 elif _IsRelated(changed_src_file_path, file_path_in_log): | 236 elif _IsRelated(changed_src_file_path, file_path_in_log): |
228 score = 1 | 237 score = 1 |
229 | 238 |
230 if score: | 239 if score: |
231 justification.AddFileChange('deleted', | 240 justification.AddFileChange('deleted', |
232 changed_src_file_path, | 241 changed_src_file_path, |
233 file_path_in_log, | 242 file_path_in_log, |
234 score, | 243 score, |
235 file_name_occurrences.get(file_name)) | 244 file_name_occurrences.get(file_name)) |
236 | 245 |
237 def _CheckFiles(failure_signal, change_log): | 246 |
| 247 def _CheckFiles(failure_signal, change_log, deps_info): |
238 """Check files in the given change log of a CL against the failure signal. | 248 """Check files in the given change log of a CL against the failure signal. |
239 | 249 |
240 Args: | 250 Args: |
241 failure_signal (FailureSignal): The failure signal of a failed step or test. | 251 failure_signal (FailureSignal): The failure signal of a failed step or test. |
242 change_log (dict): The change log of a CL as returned by | 252 change_log (dict): The change log of a CL as returned by |
243 common.change_log.ChangeLog.ToDict(). | 253 common.change_log.ChangeLog.ToDict(). |
| 254 deps_info (dict): Output of pipeline ExtractDEPSInfoPipeline. |
244 | 255 |
245 Returns: | 256 Returns: |
246 A dict as returned by _Justification.ToDict() if the CL is suspected for the | 257 A dict as returned by _Justification.ToDict() if the CL is suspected for the |
247 failure; otherwise None. | 258 failure; otherwise None. |
248 """ | 259 """ |
249 # Use a dict to map each file name of the touched files to their occurrences. | 260 # Use a dict to map each file name of the touched files to their occurrences. |
250 file_name_occurrences = collections.defaultdict(int) | 261 file_name_occurrences = collections.defaultdict(int) |
251 for touched_file in change_log['touched_files']: | 262 for touched_file in change_log['touched_files']: |
252 change_type = touched_file['change_type'] | 263 change_type = touched_file['change_type'] |
253 if (change_type in (ChangeType.ADD, ChangeType.COPY, | 264 if (change_type in (ChangeType.ADD, ChangeType.COPY, |
254 ChangeType.RENAME, ChangeType.MODIFY)): | 265 ChangeType.RENAME, ChangeType.MODIFY)): |
255 file_name = os.path.basename(touched_file['new_path']) | 266 file_name = os.path.basename(touched_file['new_path']) |
256 file_name_occurrences[file_name] += 1 | 267 file_name_occurrences[file_name] += 1 |
257 | 268 |
258 if change_type in (ChangeType.DELETE, ChangeType.RENAME): | 269 if change_type in (ChangeType.DELETE, ChangeType.RENAME): |
259 file_name = os.path.basename(touched_file['old_path']) | 270 file_name = os.path.basename(touched_file['old_path']) |
260 file_name_occurrences[file_name] += 1 | 271 file_name_occurrences[file_name] += 1 |
261 | 272 |
262 justification = _Justification() | 273 justification = _Justification() |
263 | 274 |
264 for file_path_in_log, _ in failure_signal.files.iteritems(): | 275 for file_path_in_log, _ in failure_signal.files.iteritems(): |
265 # TODO(stgao): remove this hack when DEPS parsing is supported. | 276 # Strip src/ from file path to make all files relative to the chromium root |
266 if file_path_in_log.startswith('src/'): | 277 # directory. |
267 file_path_in_log = file_path_in_log[4:] | 278 file_path_in_log = file_path_in_log.lstrip('src/') |
268 | 279 |
269 for touched_file in change_log['touched_files']: | 280 for touched_file in change_log['touched_files']: |
270 _CheckFile( | 281 _CheckFile( |
271 touched_file, file_path_in_log, justification, file_name_occurrences) | 282 touched_file, file_path_in_log, justification, file_name_occurrences) |
272 | 283 |
| 284 for roll in deps_info.get('deps_rolls', {}).get(change_log['revision'], []): |
| 285 dep_path = roll['path'].lstrip('src/') |
| 286 if file_path_in_log.startswith(dep_path): |
| 287 justification.AddDEPSRoll( |
| 288 dep_path, roll['repo_url'], roll['new_revision'], |
| 289 roll['old_revision'], file_path_in_log[len(dep_path):], 2) |
| 290 |
273 if not justification.score: | 291 if not justification.score: |
274 return None | 292 return None |
275 else: | 293 else: |
276 return justification.ToDict() | 294 return justification.ToDict() |
277 | 295 |
278 | 296 |
279 def AnalyzeBuildFailure(failure_info, change_logs, failure_signals): | 297 def AnalyzeBuildFailure( |
| 298 failure_info, change_logs, deps_info, failure_signals): |
280 """Analyze the given failure signals, and figure out culprit CLs. | 299 """Analyze the given failure signals, and figure out culprit CLs. |
281 | 300 |
282 Args: | 301 Args: |
283 failure_info (dict): Output of pipeline DetectFirstFailurePipeline. | 302 failure_info (dict): Output of pipeline DetectFirstFailurePipeline. |
284 change_logs (dict): Output of pipeline PullChangelogPipeline. | 303 change_logs (dict): Output of pipeline PullChangelogPipeline. |
| 304 deps_info (dict): Output of pipeline ExtractDEPSInfoPipeline. |
285 failure_signals (dict): Output of pipeline ExtractSignalPipeline. | 305 failure_signals (dict): Output of pipeline ExtractSignalPipeline. |
286 | 306 |
287 Returns: | 307 Returns: |
288 A dict with the following form: | 308 A dict with the following form: |
289 { | 309 { |
290 'failures': [ | 310 'failures': [ |
291 { | 311 { |
292 'step_name': 'compile', | 312 'step_name': 'compile', |
293 'first_failure': 230, | 313 'first_failure': 230, |
294 'last_pass': 229, | 314 'last_pass': 229, |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
348 | 368 |
349 step_analysis_result = { | 369 step_analysis_result = { |
350 'step_name': step_name, | 370 'step_name': step_name, |
351 'first_failure': step_failure_info['first_failure'], | 371 'first_failure': step_failure_info['first_failure'], |
352 'last_pass': step_failure_info.get('last_pass'), | 372 'last_pass': step_failure_info.get('last_pass'), |
353 'suspected_cls': [], | 373 'suspected_cls': [], |
354 } | 374 } |
355 | 375 |
356 while build_number <= failed_build_number: | 376 while build_number <= failed_build_number: |
357 for revision in builds[str(build_number)]['blame_list']: | 377 for revision in builds[str(build_number)]['blame_list']: |
358 justification_dict = _CheckFiles(failure_signal, change_logs[revision]) | 378 justification_dict = _CheckFiles( |
| 379 failure_signal, change_logs[revision], deps_info) |
359 | 380 |
360 if not justification_dict: | 381 if not justification_dict: |
361 continue | 382 continue |
362 | 383 |
363 step_analysis_result['suspected_cls'].append( | 384 step_analysis_result['suspected_cls'].append( |
364 CreateCLInfoDict(justification_dict, build_number, | 385 CreateCLInfoDict(justification_dict, build_number, |
365 change_logs[revision])) | 386 change_logs[revision])) |
366 | 387 |
367 build_number += 1 | 388 build_number += 1 |
368 | 389 |
369 # TODO(stgao): sort CLs by score. | 390 # TODO(stgao): sort CLs by score. |
370 analysis_result['failures'].append(step_analysis_result) | 391 analysis_result['failures'].append(step_analysis_result) |
371 | 392 |
372 return analysis_result | 393 return analysis_result |
OLD | NEW |