| OLD | NEW |
| (Empty) | |
| 1 # Copyright 2016 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 from collections import defaultdict |
| 6 |
| 7 from model import wf_analysis_status |
| 8 from model.wf_analysis import WfAnalysis |
| 9 from model.wf_swarming_task import WfSwarmingTask |
| 10 from model.wf_try_job import WfTryJob |
| 11 from waterfall import buildbot |
| 12 from waterfall import waterfall_config |
| 13 |
| 14 |
| 15 FLAKY = 'Flaky' |
| 16 |
| 17 |
| 18 def GenerateSwarmingTasksData(master_name, builder_name, build_number): |
| 19 """Collects info for all related swarming tasks. |
| 20 |
| 21 Returns: A dict as below: |
| 22 { |
| 23 'step1': { |
| 24 'swarming_tasks': [ |
| 25 { |
| 26 'status': 'Completed', |
| 27 'task_id': 'task1', |
| 28 'task_url': ( |
| 29 'https://chromium-swarm.appspot.com/user/task/task1') |
| 30 }, |
| 31 { |
| 32 'status': 'Completed', |
| 33 'task_id': 'task0', |
| 34 'task_url': ( |
| 35 'https://chromium-swarm.appspot.com/user/task/task0') |
| 36 } |
| 37 ], |
| 38 'tests': { |
| 39 'test1': { |
| 40 'status': 'Completed', |
| 41 'task_id': 'task0', |
| 42 'task_url': ( |
| 43 'https://chromium-swarm.appspot.com/user/task/task0') |
| 44 }, |
| 45 'test2': { |
| 46 'status': 'Completed', |
| 47 'task_id': 'task1', |
| 48 'task_url': ( |
| 49 'https://chromium-swarm.appspot.com/user/task/task1') |
| 50 } |
| 51 } |
| 52 }, |
| 53 'step2': { |
| 54 'swarming_tasks': [ |
| 55 { |
| 56 'status': 'Pending' |
| 57 } |
| 58 ], |
| 59 'tests': { |
| 60 'test1': { |
| 61 'status': 'Pending' |
| 62 } |
| 63 } |
| 64 } |
| 65 } |
| 66 """ |
| 67 tasks_info = defaultdict(dict) |
| 68 |
| 69 analysis = WfAnalysis.Get(master_name, builder_name, build_number) |
| 70 if not analysis: |
| 71 return tasks_info |
| 72 |
| 73 failure_result_map = analysis.failure_result_map |
| 74 if failure_result_map: |
| 75 for step_name, failure in failure_result_map.iteritems(): |
| 76 if isinstance(failure, dict): |
| 77 # Only trigger swarming task for swarming test failures. |
| 78 key_test_map = defaultdict(list) |
| 79 for test_name, first_failure_key in failure.iteritems(): |
| 80 key_test_map[first_failure_key].append(test_name) |
| 81 |
| 82 tasks_info[step_name]['swarming_tasks'] = [] |
| 83 tasks_info[step_name]['tests'] = defaultdict(dict) |
| 84 step_tasks_info = tasks_info[step_name]['swarming_tasks'] |
| 85 tests = tasks_info[step_name]['tests'] |
| 86 for key in key_test_map: |
| 87 referred_build_keys = key.split('/') |
| 88 task = WfSwarmingTask.Get(*referred_build_keys, step_name=step_name) |
| 89 if not task: |
| 90 continue |
| 91 task_info = { |
| 92 'status': wf_analysis_status.SWARMING_STATUS_TO_DESCRIPTION.get( |
| 93 task.status) |
| 94 } |
| 95 if task.task_id: |
| 96 task_info['task_id'] = task.task_id |
| 97 task_info['task_url'] = 'https://%s/user/task/%s' % ( |
| 98 waterfall_config.GetSwarmingSettings()['server_host'], |
| 99 task.task_id) |
| 100 |
| 101 step_tasks_info.append(task_info) |
| 102 for test_name in key_test_map[key]: |
| 103 tests[test_name] = task_info |
| 104 |
| 105 return tasks_info |
| 106 |
| 107 |
| 108 def _GetTryJobBuildNumber(url): |
| 109 build_keys = buildbot.ParseBuildUrl(url) |
| 110 return build_keys[2] |
| 111 |
| 112 |
| 113 def _GetCulpritInfoForTryJobResult(try_job_key, culprits_info): |
| 114 referred_build_keys = try_job_key.split('/') |
| 115 try_job = WfTryJob.Get(*referred_build_keys) |
| 116 if not try_job: |
| 117 return |
| 118 |
| 119 if try_job.compile_results: |
| 120 try_job_result = try_job.compile_results[-1] |
| 121 elif try_job.test_results: |
| 122 try_job_result = try_job.test_results[-1] |
| 123 else: |
| 124 try_job_result = None |
| 125 |
| 126 additional_tests_culprit_info = {} |
| 127 for culprit_info in culprits_info.values(): |
| 128 if culprit_info['try_job_key'] != try_job_key: |
| 129 continue |
| 130 |
| 131 # Only include try job result for reliable tests. |
| 132 # Flaky tests have been marked as 'Flaky'. |
| 133 culprit_info['status'] = ( |
| 134 wf_analysis_status.TRY_JOB_STATUS_TO_DESCRIPTION[try_job.status] |
| 135 if not culprit_info.get('status') else culprit_info['status']) |
| 136 |
| 137 if try_job_result and culprit_info['status'] != FLAKY: |
| 138 if try_job_result.get('url'): |
| 139 culprit_info['try_job_url'] = try_job_result['url'] |
| 140 culprit_info['try_job_build_number'] = ( |
| 141 _GetTryJobBuildNumber(try_job_result['url'])) |
| 142 if try_job_result.get('culprit'): |
| 143 try_job_culprits = try_job_result['culprit'] |
| 144 step = culprit_info['step'] |
| 145 test = culprit_info['test'] |
| 146 |
| 147 if test == 'N/A': # Only step level. |
| 148 if try_job_culprits.get(step, {}).get('tests'): |
| 149 # try job results has specified tests. |
| 150 step_culprits = try_job_culprits[step]['tests'] |
| 151 for test_name, try_job_culprit in step_culprits.iteritems(): |
| 152 additional_test_key = '%s-%s' % (step, test_name) |
| 153 additional_tests_culprit_info[additional_test_key] = { |
| 154 'step': step, |
| 155 'test': test_name, |
| 156 'try_job_key': try_job_key, |
| 157 'status': culprit_info['status'], |
| 158 'try_job_url': culprit_info['try_job_url'], |
| 159 'try_job_build_number': culprit_info['try_job_build_number'], |
| 160 'revision': try_job_culprit.get('revision'), |
| 161 'commit_position': try_job_culprit.get('commit_position'), |
| 162 'review_url': try_job_culprit.get('review_url') |
| 163 } |
| 164 continue |
| 165 else: |
| 166 # For historical culprit found by try job for compile, |
| 167 # step name is not recorded. |
| 168 culprit = try_job_culprits.get(step) or try_job_culprits |
| 169 elif test in try_job_culprits.get(step, {}).get('tests'): |
| 170 culprit = try_job_culprits[step]['tests'][test] |
| 171 else: # pragma: no cover |
| 172 continue # No culprit for test found. |
| 173 |
| 174 culprit_info['revision'] = culprit.get('revision') |
| 175 culprit_info['commit_position'] = culprit.get('commit_position') |
| 176 culprit_info['review_url'] = culprit.get('review_url') |
| 177 |
| 178 if additional_tests_culprit_info: |
| 179 for key, test_culprit_info in additional_tests_culprit_info.iteritems(): |
| 180 culprits_info.pop(test_culprit_info['step'], None) |
| 181 culprits_info[key] = test_culprit_info |
| 182 |
| 183 |
| 184 def _UpdateFlakiness(step_name, failure_key_set, culprits_info): |
| 185 for failure_key in failure_key_set: |
| 186 build_keys = failure_key.split('/') |
| 187 task = WfSwarmingTask.Get(*build_keys, step_name=step_name) |
| 188 if not task: |
| 189 continue |
| 190 classified_tests = task.classified_tests |
| 191 for culprit_info in culprits_info.values(): |
| 192 if (culprit_info['try_job_key'] == failure_key and |
| 193 culprit_info['test'] in classified_tests.get('flaky_tests', [])): |
| 194 culprit_info['status'] = FLAKY |
| 195 |
| 196 |
| 197 def GetAllTryJobResults(master_name, builder_name, build_number): |
| 198 culprits_info = {} |
| 199 try_job_keys = set() |
| 200 |
| 201 analysis = WfAnalysis.Get(master_name, builder_name, build_number) |
| 202 if not analysis: |
| 203 return culprits_info |
| 204 |
| 205 failure_result_map = analysis.failure_result_map |
| 206 if failure_result_map: |
| 207 # failure_result_map uses step_names as keys and saves referred try_job_keys |
| 208 # If non-swarming, step_name and referred_try_job_key match directly as: |
| 209 # step_name: try_job_key |
| 210 # If swarming, add one more layer of tests, so the format would be: |
| 211 # step_name: { |
| 212 # test_name1: try_job_key1, |
| 213 # test_name2: try_job_key2, |
| 214 # ... |
| 215 # } |
| 216 for step_name, step_failure_result_map in failure_result_map.iteritems(): |
| 217 if isinstance(step_failure_result_map, dict): |
| 218 step_refering_keys = set() |
| 219 for failed_test, try_job_key in step_failure_result_map.iteritems(): |
| 220 step_test_key = '%s-%s' % (step_name, failed_test) |
| 221 culprits_info[step_test_key] = { |
| 222 'step': step_name, |
| 223 'test': failed_test, |
| 224 'try_job_key': try_job_key |
| 225 } |
| 226 step_refering_keys.add(try_job_key) |
| 227 |
| 228 _UpdateFlakiness(step_name, step_refering_keys, culprits_info) |
| 229 try_job_keys.update(step_refering_keys) |
| 230 else: |
| 231 culprits_info[step_name] = { |
| 232 'step': step_name, |
| 233 'test': 'N/A', |
| 234 'try_job_key': step_failure_result_map |
| 235 } |
| 236 try_job_keys.add(step_failure_result_map) |
| 237 |
| 238 for try_job_key in try_job_keys: |
| 239 _GetCulpritInfoForTryJobResult(try_job_key, culprits_info) |
| 240 |
| 241 return culprits_info |
| OLD | NEW |