| 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[:12], dep_new_revision[:12]) |
| 166 hint = ('Rolled %s with changes %s (and %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 |