1# Copyright (C) 2013 Google Inc. All rights reserved. 2# 3# Redistribution and use in source and binary forms, with or without 4# modification, are permitted provided that the following conditions are 5# met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above 10# copyright notice, this list of conditions and the following disclaimer 11# in the documentation and/or other materials provided with the 12# distribution. 13# * Neither the name of Google Inc. nor the names of its 14# contributors may be used to endorse or promote products derived from 15# this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29import datetime 30import json 31import logging 32import re 33import sys 34import traceback 35import urllib2 36import webapp2 37 38from google.appengine.api import memcache 39 40MASTERS = [ 41 {'name': 'ChromiumWin', 'url_name': 'chromium.win', 'groups': ['@ToT Chromium']}, 42 {'name': 'ChromiumMac', 'url_name': 'chromium.mac', 'groups': ['@ToT Chromium']}, 43 {'name': 'ChromiumLinux', 'url_name': 'chromium.linux', 'groups': ['@ToT Chromium']}, 44 {'name': 'ChromiumChromiumOS', 'url_name': 'chromium.chromiumos', 'groups': ['@ToT ChromeOS']}, 45 {'name': 'ChromiumGPU', 'url_name': 'chromium.gpu', 'groups': ['@ToT Chromium']}, 46 {'name': 'ChromiumGPUFYI', 'url_name': 'chromium.gpu.fyi', 'groups': ['@ToT Chromium FYI']}, 47 {'name': 'ChromiumWebkit', 'url_name': 'chromium.webkit', 'groups': ['@ToT Chromium', '@ToT Blink']}, 48 {'name': 'ChromiumFYI', 'url_name': 'chromium.fyi', 'groups': ['@ToT Chromium FYI']}, 49 {'name': 'V8', 'url_name': 'client.v8', 'groups': ['@ToT V8']}, 50] 51 52# Buildbot steps that have test in the name, but don't run tests. 53NON_TEST_STEP_NAMES = [ 54 'archive', 55 'Run tests', 56 'find isolated tests', 57 'read test spec', 58 'Download latest chromedriver', 59 'compile tests', 60 'create_coverage_', 61 'update test result log', 62 'memory test:', 63 'install_', 64] 65 66# Buildbot steps that run tests but don't upload results to the flakiness dashboard server. 67# FIXME: These should be fixed to upload and then removed from this list. 68TEST_STEPS_THAT_DO_NOT_UPLOAD_YET = [ 69 'java_tests(chrome', 70 'python_tests(chrome', 71 'run_all_tests.py', 72 'test_report', 73 'test CronetSample', 74 'test_mini_installer', 75 'telemetry_unittests', 76 'webkit_python_tests', 77 'webkit_unit_tests', 78] 79 80BUILDS_URL = 'http://chrome-build-extract.appspot.com/get_builds?builder=%s&master=%s&num_builds=1' 81MASTER_URL = 'http://chrome-build-extract.appspot.com/get_master/%s' 82 83 84class FetchBuildersException(Exception): 85 pass 86 87 88def fetch_json(url): 89 logging.debug('Fetching %s' % url) 90 fetched_json = {} 91 try: 92 resp = urllib2.urlopen(url) 93 except: 94 exc_info = sys.exc_info() 95 logging.warning('Error while fetching %s: %s', url, exc_info[1]) 96 return fetched_json 97 98 try: 99 fetched_json = json.load(resp) 100 except: 101 exc_info = sys.exc_info() 102 logging.warning('Unable to parse JSON response from %s: %s', url, exc_info[1]) 103 104 return fetched_json 105 106 107def dump_json(data): 108 return json.dumps(data, separators=(',', ':'), sort_keys=True) 109 110 111def fetch_buildbot_data(masters): 112 start_time = datetime.datetime.now() 113 master_data = masters[:] 114 for master in master_data: 115 master_url = MASTER_URL % master['url_name'] 116 builders = fetch_json(master_url) 117 if not builders: 118 msg = 'Aborting fetch. Could not fetch builders from master "%s": %s.' % (master['name'], master_url) 119 logging.warning(msg) 120 raise FetchBuildersException(msg) 121 122 tests_object = master.setdefault('tests', {}) 123 124 for builder in builders['builders'].keys(): 125 build = fetch_json(BUILDS_URL % (urllib2.quote(builder), master['url_name'])) 126 if not build: 127 logging.info('Skipping builder %s on master %s due to empty data.', builder, master['url_name']) 128 continue 129 130 if not build['builds']: 131 logging.info('Skipping builder %s on master %s due to empty builds list.', builder, master['url_name']) 132 continue 133 134 for step in build['builds'][0]['steps']: 135 step_name = step['name'] 136 137 if not 'test' in step_name: 138 continue 139 140 if any(name in step_name for name in NON_TEST_STEP_NAMES): 141 continue 142 143 if re.search('_only|_ignore|_perf$', step_name): 144 continue 145 146 if step_name == 'webkit_tests': 147 step_name = 'layout-tests' 148 149 tests_object.setdefault(step_name, {'builders': []}) 150 tests_object[step_name]['builders'].append(builder) 151 152 for builders in tests_object.values(): 153 builders['builders'].sort() 154 155 output_data = {'masters': master_data, 'no_upload_test_types': TEST_STEPS_THAT_DO_NOT_UPLOAD_YET} 156 157 delta = datetime.datetime.now() - start_time 158 159 logging.info('Fetched buildbot data in %s seconds.', delta.seconds) 160 161 return dump_json(output_data) 162 163 164class UpdateBuilders(webapp2.RequestHandler): 165 """Fetch and update the cached buildbot data.""" 166 def get(self): 167 try: 168 buildbot_data = fetch_buildbot_data(MASTERS) 169 memcache.set('buildbot_data', buildbot_data) 170 self.response.set_status(200) 171 self.response.out.write("ok") 172 except FetchBuildersException, ex: 173 logging.error('Not updating builders because fetch failed: %s', str(ex)) 174 self.response.set_status(500) 175 self.response.out.write(ex.message) 176 177 178class GetBuilders(webapp2.RequestHandler): 179 """Return a list of masters mapped to their respective builders, possibly using cached data.""" 180 def get(self): 181 buildbot_data = memcache.get('buildbot_data') 182 183 if not buildbot_data: 184 logging.warning('No buildbot data in memcache. If this message repeats, something is probably wrong with memcache.') 185 try: 186 buildbot_data = fetch_buildbot_data(MASTERS) 187 memcache.set('buildbot_data', buildbot_data) 188 except FetchBuildersException, ex: 189 logging.error('Builders fetch failed: %s', str(ex)) 190 self.response.set_status(500) 191 self.response.out.write(ex.message) 192 return 193 194 callback = self.request.get('callback') 195 if callback: 196 buildbot_data = callback + '(' + buildbot_data + ');' 197 198 self.response.out.write(buildbot_data) 199