• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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