| OLD | NEW |
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 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 from collections import defaultdict | 5 from collections import defaultdict |
| 6 import copy | 6 import copy |
| 7 from datetime import datetime | 7 from datetime import datetime |
| 8 import logging |
| 8 import os | 9 import os |
| 9 | 10 |
| 10 from google.appengine.api import users | 11 from google.appengine.api import users |
| 11 | 12 |
| 13 from common import constants |
| 12 from common.base_handler import BaseHandler | 14 from common.base_handler import BaseHandler |
| 13 from common.base_handler import Permission | 15 from common.base_handler import Permission |
| 14 from common import constants | 16 from common.waterfall import failure_type |
| 15 from handlers import handlers_util | 17 from handlers import handlers_util |
| 16 from handlers import result_status | 18 from handlers import result_status |
| 17 from handlers.result_status import NO_TRY_JOB_REASON_MAP | 19 from handlers.result_status import NO_TRY_JOB_REASON_MAP |
| 18 from model import analysis_status | 20 from model import analysis_status |
| 19 from model.wf_analysis import WfAnalysis | 21 from model.wf_analysis import WfAnalysis |
| 22 from model.wf_try_job import WfTryJob |
| 20 from model.result_status import RESULT_STATUS_TO_DESCRIPTION | 23 from model.result_status import RESULT_STATUS_TO_DESCRIPTION |
| 21 from waterfall import build_failure_analysis_pipelines | 24 from waterfall import build_failure_analysis_pipelines |
| 22 from waterfall import buildbot | 25 from waterfall import buildbot |
| 23 from waterfall import waterfall_config | 26 from waterfall import waterfall_config |
| 24 | 27 |
| 25 | 28 |
| 26 NON_SWARMING = object() | 29 NON_SWARMING = object() |
| 27 | 30 |
| 28 | 31 |
| 29 def _FormatDatetime(dt): | 32 def _FormatDatetime(dt): |
| (...skipping 231 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 261 return updated_results | 264 return updated_results |
| 262 | 265 |
| 263 | 266 |
| 264 class BuildFailure(BaseHandler): | 267 class BuildFailure(BaseHandler): |
| 265 PERMISSION_LEVEL = Permission.ANYONE | 268 PERMISSION_LEVEL = Permission.ANYONE |
| 266 | 269 |
| 267 def _ShowDebugInfo(self): | 270 def _ShowDebugInfo(self): |
| 268 # Show debug info only if the app is run locally during development, if the | 271 # Show debug info only if the app is run locally during development, if the |
| 269 # currently logged-in user is an admin, or if it is explicitly requested | 272 # currently logged-in user is an admin, or if it is explicitly requested |
| 270 # with parameter 'debug=1'. | 273 # with parameter 'debug=1'. |
| 271 return (os.environ['SERVER_SOFTWARE'].startswith('Development') or | 274 return ( |
| 272 users.is_current_user_admin() or self.request.get('debug') == '1') | 275 users.is_current_user_admin() or self.request.get('debug') == '1') |
| 273 | 276 |
| 274 def _ShowTriageHelpButton(self): | 277 def _ShowTriageHelpButton(self): |
| 275 return users.is_current_user_admin() | 278 return users.is_current_user_admin() |
| 276 | 279 |
| 280 def _PrepareCommonDataForFailure(self, analysis): |
| 281 return { |
| 282 'master_name': analysis.master_name, |
| 283 'builder_name': analysis.builder_name, |
| 284 'build_number': analysis.build_number, |
| 285 'pipeline_status_path': analysis.pipeline_status_path, |
| 286 'show_debug_info': self._ShowDebugInfo(), |
| 287 'analysis_request_time': _FormatDatetime(analysis.request_time), |
| 288 'analysis_start_time': _FormatDatetime(analysis.start_time), |
| 289 'analysis_end_time': _FormatDatetime(analysis.end_time), |
| 290 'analysis_duration': analysis.duration, |
| 291 'analysis_update_time': _FormatDatetime(analysis.updated_time), |
| 292 'analysis_completed': analysis.completed, |
| 293 'analysis_failed': analysis.failed, |
| 294 'analysis_correct': analysis.correct, |
| 295 'triage_history': _GetTriageHistory(analysis), |
| 296 'show_triage_help_button': self._ShowTriageHelpButton(), |
| 297 'status_message_map': result_status.STATUS_MESSAGE_MAP |
| 298 } |
| 299 |
| 300 @staticmethod |
| 301 def _PrepareTryJobDataForCompileFailure(analysis): |
| 302 try_job_data = {} |
| 303 if not (analysis.failure_result_map and # pragma: no branch. |
| 304 constants.COMPILE_STEP_NAME in analysis.failure_result_map): |
| 305 return try_job_data # pragma: no cover. |
| 306 |
| 307 referred_build_keys = analysis.failure_result_map[ |
| 308 constants.COMPILE_STEP_NAME].split('/') |
| 309 try_job = WfTryJob.Get(*referred_build_keys) |
| 310 if not try_job or not try_job.compile_results: |
| 311 return try_job_data # pragma: no cover. |
| 312 result = try_job.compile_results[-1] |
| 313 |
| 314 try_job_data['status'] = analysis_status.STATUS_TO_DESCRIPTION.get( |
| 315 try_job.status, 'unknown').lower() |
| 316 try_job_data['url'] = result.get('url') |
| 317 try_job_data['completed'] = try_job.completed |
| 318 try_job_data['failed'] = try_job.failed |
| 319 try_job_data['culprit'] = result.get( |
| 320 'culprit', {}).get(constants.COMPILE_STEP_NAME) |
| 321 |
| 322 return try_job_data |
| 323 |
| 324 @staticmethod |
| 325 def _PopulateHeuristicDataForCompileFailure(analysis, data): |
| 326 if analysis.result: # pragma: no branch. |
| 327 compile_failure = None |
| 328 for failure in analysis.result.get('failures', []): |
| 329 if failure['step_name'] == constants.COMPILE_STEP_NAME: |
| 330 compile_failure = failure |
| 331 if compile_failure: # pragma: no branch. |
| 332 data['first_failure'] = compile_failure['first_failure'] |
| 333 data['last_pass'] = compile_failure['last_pass'] |
| 334 data['suspected_cls_by_heuristic'] = compile_failure['suspected_cls'] |
| 335 |
| 336 def _PrepareDataForCompileFailure(self, analysis): |
| 337 data = self._PrepareCommonDataForFailure(analysis) |
| 338 |
| 339 # Check result from heuristic analysis. |
| 340 self._PopulateHeuristicDataForCompileFailure(analysis, data) |
| 341 # Check result from try job. |
| 342 data['try_job'] = self._PrepareTryJobDataForCompileFailure(analysis) |
| 343 |
| 344 return data |
| 345 |
| 346 def _PrepareDataForTestFailures(self, analysis, build_info): |
| 347 data = self._PrepareCommonDataForFailure(analysis) |
| 348 |
| 349 organized_results = _GetOrganizedAnalysisResultBySuspectedCL( |
| 350 analysis.result) |
| 351 analysis_result = _GetAnalysisResultWithTryJobInfo( |
| 352 organized_results, *build_info) |
| 353 data['analysis_result'] = analysis_result |
| 354 |
| 355 return data |
| 356 |
| 277 def HandleGet(self): | 357 def HandleGet(self): |
| 278 """Triggers analysis of a build failure on demand and return current result. | 358 """Triggers analysis of a build failure on demand and return current result. |
| 279 | 359 |
| 280 If the final analysis result is available, set cache-control to 1 day to | 360 If the final analysis result is available, set cache-control to 1 day to |
| 281 avoid overload by unnecessary and frequent query from clients; otherwise | 361 avoid overload by unnecessary and frequent query from clients; otherwise |
| 282 set cache-control to 5 seconds to allow repeated query. | 362 set cache-control to 5 seconds to allow repeated query. |
| 283 | 363 |
| 284 Serve HTML page or JSON result as requested. | 364 Serve HTML page or JSON result as requested. |
| 285 """ | 365 """ |
| 286 url = self.request.get('url').strip() | 366 url = self.request.get('url').strip() |
| (...skipping 16 matching lines...) Expand all Loading... |
| 303 # Only allow admin to force a re-run and set the build_completed. | 383 # Only allow admin to force a re-run and set the build_completed. |
| 304 force = (users.is_current_user_admin() and | 384 force = (users.is_current_user_admin() and |
| 305 self.request.get('force') == '1') | 385 self.request.get('force') == '1') |
| 306 build_completed = (users.is_current_user_admin() and | 386 build_completed = (users.is_current_user_admin() and |
| 307 self.request.get('build_completed') == '1') | 387 self.request.get('build_completed') == '1') |
| 308 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded( | 388 analysis = build_failure_analysis_pipelines.ScheduleAnalysisIfNeeded( |
| 309 master_name, builder_name, build_number, | 389 master_name, builder_name, build_number, |
| 310 build_completed=build_completed, force=force, | 390 build_completed=build_completed, force=force, |
| 311 queue_name=constants.WATERFALL_ANALYSIS_QUEUE) | 391 queue_name=constants.WATERFALL_ANALYSIS_QUEUE) |
| 312 | 392 |
| 313 organized_results = _GetOrganizedAnalysisResultBySuspectedCL( | 393 if analysis.failure_type == failure_type.COMPILE: # pragma: no branch |
| 314 analysis.result) | 394 return { |
| 315 analysis_result = _GetAnalysisResultWithTryJobInfo( | 395 'template': 'waterfall/compile_failure.html', |
| 316 organized_results, *build_info) | 396 'data': self._PrepareDataForCompileFailure(analysis), |
| 317 | 397 } |
| 318 data = { | 398 else: |
| 319 'master_name': analysis.master_name, | 399 return { |
| 320 'builder_name': analysis.builder_name, | 400 'template': 'build_failure.html', |
| 321 'build_number': analysis.build_number, | 401 'data': self._PrepareDataForTestFailures(analysis, build_info), |
| 322 'pipeline_status_path': analysis.pipeline_status_path, | 402 } |
| 323 'show_debug_info': self._ShowDebugInfo(), | |
| 324 'analysis_request_time': _FormatDatetime(analysis.request_time), | |
| 325 'analysis_start_time': _FormatDatetime(analysis.start_time), | |
| 326 'analysis_end_time': _FormatDatetime(analysis.end_time), | |
| 327 'analysis_duration': analysis.duration, | |
| 328 'analysis_update_time': _FormatDatetime(analysis.updated_time), | |
| 329 'analysis_completed': analysis.completed, | |
| 330 'analysis_failed': analysis.failed, | |
| 331 'analysis_result': analysis_result, | |
| 332 'analysis_correct': analysis.correct, | |
| 333 'triage_history': _GetTriageHistory(analysis), | |
| 334 'show_triage_help_button': self._ShowTriageHelpButton(), | |
| 335 'status_message_map': result_status.STATUS_MESSAGE_MAP | |
| 336 } | |
| 337 | |
| 338 return {'template': 'build_failure.html', 'data': data} | |
| 339 | 403 |
| 340 def HandlePost(self): # pragma: no cover | 404 def HandlePost(self): # pragma: no cover |
| 341 return self.HandleGet() | 405 return self.HandleGet() |
| OLD | NEW |