Chromium Code Reviews| 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'): | |
|
stgao
2016/03/15 17:19:40
Note: for some script-based test like checklicense
| |
| 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' | |
|
stgao
2016/03/15 17:19:40
nit: no hard code here. Use the var at the top.
chanli
2016/03/15 17:34:05
Done.
| |
| 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 |