Removed build status tracking, refreshed front page.

BUG=
R=kjellander@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/2106004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@4613 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
phoglund@webrtc.org 2013-08-26 08:45:22 +00:00
parent f1fd9d0c5c
commit c9fa0fede5
16 changed files with 28 additions and 1246 deletions

View File

@ -23,13 +23,8 @@ REQUEST_TOKEN_URL = DASHBOARD_SERVER_HTTP + '/_ah/OAuthGetRequestToken'
AUTHORIZE_TOKEN_URL = DASHBOARD_SERVER_HTTP + '/_ah/OAuthAuthorizeToken'
ACCESS_TOKEN_URL = DASHBOARD_SERVER_HTTP + '/_ah/OAuthGetAccessToken'
# The build master URL.
BUILD_MASTER_SERVER = 'webrtc-cb-linux-master.cbf.corp.google.com:8010'
BUILD_MASTER_TRANSPOSED_GRID_URL = '/tgrid'
# Build bot constants.
BUILD_BOT_COVERAGE_WWW_DIRECTORY = '/var/www/coverage'
# Dashboard data input URLs.
ADD_COVERAGE_DATA_URL = DASHBOARD_SERVER_HTTP + '/add_coverage_data'
ADD_BUILD_STATUS_DATA_URL = DASHBOARD_SERVER_HTTP + '/add_build_status_data'

View File

@ -1,168 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
"""Implements a handler for adding build status data."""
import datetime
import logging
from google.appengine.ext import db
import oauth_post_request_handler
VALID_STATUSES = ['OK', 'failed', 'building', 'warnings']
class OrphanedBuildStatusesExistException(Exception):
pass
class BuildStatusRoot(db.Model):
"""Exists solely to be the root parent for all build status data and to keep
track of when the last update was made.
Since all build status data will refer to this as their parent,
we can run transactions on the build status data as a whole.
"""
last_updated_at = db.DateTimeProperty()
class BuildStatusData(db.Model):
"""This represents one build status report from the build bot."""
bot_name = db.StringProperty(required=True)
revision = db.IntegerProperty(required=True)
build_number = db.IntegerProperty(required=True)
status = db.StringProperty(required=True)
def _ensure_build_status_root_exists():
root = db.GqlQuery('SELECT * FROM BuildStatusRoot').get()
if not root:
# Create a new root, but ensure we don't have any orphaned build statuses
# (in that case, we would not have a single entity group as we desire).
orphans = db.GqlQuery('SELECT * FROM BuildStatusData').get()
if orphans:
raise OrphanedBuildStatusesExistException('Parent is gone and there are '
'orphaned build statuses in '
'the database!')
root = BuildStatusRoot()
root.put()
return root
def _filter_oauth_parameters(post_keys):
return filter(lambda post_key: not post_key.startswith('oauth_'),
post_keys)
def _parse_status(build_number_and_status):
parsed_status = build_number_and_status.split('--')
if len(parsed_status) != 2:
raise ValueError('Malformed status string %s.' % build_number_and_status)
parsed_build_number = int(parsed_status[0])
status = parsed_status[1]
if status not in VALID_STATUSES:
raise ValueError('Invalid status in %s.' % build_number_and_status)
return (parsed_build_number, status)
def _parse_name(revision_and_bot_name):
parsed_name = revision_and_bot_name.split('--')
if len(parsed_name) != 2:
raise ValueError('Malformed name string %s.' % revision_and_bot_name)
revision = parsed_name[0]
bot_name = parsed_name[1]
if '\n' in bot_name:
raise ValueError('Bot name %s can not contain newlines.' % bot_name)
return (int(revision), bot_name)
def _delete_all_with_revision(revision, build_status_root):
query_result = db.GqlQuery('SELECT * FROM BuildStatusData '
'WHERE revision = :1 AND ANCESTOR IS :2',
revision, build_status_root)
for entry in query_result:
entry.delete()
class AddBuildStatusData(oauth_post_request_handler.OAuthPostRequestHandler):
"""Used to report build status data.
Build status data is reported as a POST request. The POST request, aside
from the required oauth_* parameters should contain name-value entries that
abide by the following rules:
1) The name should be on the form <revision>--<bot name>, for instance
1568--Win32Release.
2) The value should be on the form <build number>--<status>, for instance
553--OK, 554--building. The status is permitted to be failed, OK or
building.
Data is keyed by revision. This handler will delete all data from a revision
if data with that revision is present in the current update, since we
assume that more recent data is always better data. We also assume that
an update always has complete information on a revision (e.g. the status
for all the bots are reported in each update).
In particular the revision arrangement solves the problem when the latest
revision reports 'building' for a bot. Had we not deleted the old revision
we would first store a 'building' status for that bot and revision, and
later store a 'OK' or 'failed' status for that bot and revision. This is
undesirable since we don't want multiple statuses for one bot-revision
combination. Now we will effectively update the bot's status instead.
"""
def _parse_and_store_data(self):
build_status_root = _ensure_build_status_root_exists()
build_status_data = _filter_oauth_parameters(self.request.arguments())
db.run_in_transaction(self._parse_and_store_data_in_transaction,
build_status_root, build_status_data)
def _parse_and_store_data_in_transaction(self, build_status_root,
build_status_data):
encountered_revisions = set()
for revision_and_bot_name in build_status_data:
build_number_and_status = self.request.get(revision_and_bot_name)
try:
(build_number, status) = _parse_status(build_number_and_status)
(revision, bot_name) = _parse_name(revision_and_bot_name)
except ValueError as error:
logging.warn('Invalid parameter in request: %s.' % error)
self.response.set_status(400)
return
if revision not in encountered_revisions:
# There's new data on this revision in this update, so clear all status
# entries with that revision. Only do this once when we first encounter
# the revision.
_delete_all_with_revision(revision, build_status_root)
encountered_revisions.add(revision)
# Finally, write the item.
item = BuildStatusData(parent=build_status_root,
bot_name=bot_name,
revision=revision,
build_number=build_number,
status=status)
item.put()
request_posix_timestamp = float(self.request.get('oauth_timestamp'))
request_datetime = datetime.datetime.fromtimestamp(request_posix_timestamp)
build_status_root.last_updated_at = request_datetime
build_status_root.put()

View File

@ -5,13 +5,16 @@ api_version: 1
threadsafe: false
handlers:
# Serve stylesheets statically.
# Serve stylesheets, perf dashboard, and images statically.
- url: /stylesheets
static_dir: stylesheets
# Serve perf dashboard files statically.
- url: /perf
static_dir: static
- url: /images
static_dir: static
- url: /lkgr
static_files: static/lkgr_redirect.html
upload: static/lkgr_redirect.html
# This magic file is here to prove to the Google Account Domain Management
# that we own this domain. It needs to stay there so the domain management
@ -26,4 +29,4 @@ handlers:
# Redirect all other requests to our dynamic handlers.
- url: /.*
script: main.app
script: main.app

View File

@ -15,7 +15,6 @@ import math
from google.appengine.ext.webapp import template
import webapp2
import load_build_status
import load_coverage
@ -26,23 +25,6 @@ class ShowDashboard(webapp2.RequestHandler):
in the App Engine database using the AddCoverageData handler.
"""
def get(self):
build_status_loader = load_build_status.BuildStatusLoader()
# Split the build status data in two rows to fit them on the page.
# pylint: disable=W0612
build_status_data = build_status_loader.load_build_status_data()
split_point = int(math.ceil(len(build_status_data) / 2.0))
build_status_data_row_1 = build_status_data[:split_point]
build_status_data_row_2 = build_status_data[split_point:]
last_updated_at = build_status_loader.load_last_modified_at()
if last_updated_at is None:
self._show_error_page("No data has yet been uploaded to the dashboard.")
return
last_updated_at = last_updated_at.strftime("%Y-%m-%d %H:%M")
lkgr = build_status_loader.compute_lkgr()
coverage_loader = load_coverage.CoverageDataLoader()
small_medium_coverage_json_data = (
coverage_loader.load_coverage_json_data('small_medium_tests'))

View File

@ -1,29 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
"""Implements the LKGR page."""
import webapp2
import load_build_status
class ShowLkgr(webapp2.RequestHandler):
"""This handler shows the LKGR in the simplest possible way.
The page is intended to be used by automated tools.
"""
def get(self):
build_status_loader = load_build_status.BuildStatusLoader()
lkgr = build_status_loader.compute_lkgr()
if lkgr is None:
self.response.out.write('No data has been uploaded to the dashboard.')
else:
self.response.out.write(lkgr)

View File

@ -1,133 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
"""Loads build status data for the dashboard."""
from google.appengine.ext import db
def _status_not_ok(status):
return status not in ('OK', 'warnings')
def _all_ok(statuses):
return filter(_status_not_ok, statuses) == []
def _get_first_entry(iterable):
if not iterable:
return None
for item in iterable:
return item
class BuildStatusLoader:
""" Loads various build status data from the database."""
def __init__(self):
pass
@staticmethod
def load_build_status_data():
"""Returns the latest conclusive build status for each bot.
The statuses OK, failed and warnings are considered to be conclusive.
The algorithm looks at the 100 most recent status entries, which should
give data on roughly the last five revisions if the number of bots stay
around 20 (The number 100 should be increased if the number of bots
increases significantly). This should give us enough data to get a
conclusive build status for all active bots.
With this limit, the algorithm will adapt automatically if a bot is
decommissioned - it will eventually disappear. The limit should not be
too high either since we will perhaps remember offline bots too long,
which could be confusing. The algorithm also adapts automatically to new
bots - these show up immediately if they get a build status for a recent
revision.
Returns:
A list of BuildStatusData entities with one entity per bot.
"""
build_status_entries = db.GqlQuery('SELECT * '
'FROM BuildStatusData '
'ORDER BY revision DESC '
'LIMIT 100')
bots_to_latest_conclusive_entry = dict()
for entry in build_status_entries:
if entry.status == 'building':
# The 'building' status it not conclusive, so discard this entry and
# pick up the entry for this bot on the next revision instead. That
# entry is guaranteed to have a status != 'building' since a bot cannot
# be building two revisions simultaneously.
continue
if bots_to_latest_conclusive_entry.has_key(entry.bot_name):
# We've already determined this bot's status.
continue
bots_to_latest_conclusive_entry[entry.bot_name] = entry
return bots_to_latest_conclusive_entry.values()
@staticmethod
def load_last_modified_at():
build_status_root = db.GqlQuery('SELECT * '
'FROM BuildStatusRoot').get()
if not build_status_root:
# Operating on completely empty database
return None
return build_status_root.last_updated_at
@staticmethod
def compute_lkgr():
""" Finds the most recent revision for which all bots are green.
Returns:
The last known good revision (as an integer) or None if there
is no green revision in the database.
Implementation note: The data store fetches stuff as we go, so we won't
read in the whole status table unless the LKGR is right at the end or
we don't have a LKGR. Bots that are offline do not affect the LKGR
computation (e.g. they are not considered to be failed).
"""
build_status_entries = db.GqlQuery('SELECT * '
'FROM BuildStatusData '
'ORDER BY revision DESC ')
first_entry = _get_first_entry(build_status_entries)
if first_entry is None:
# No entries => no LKGR
return None
current_lkgr = first_entry.revision
statuses_for_current_lkgr = [first_entry.status]
for entry in build_status_entries:
if current_lkgr == entry.revision:
statuses_for_current_lkgr.append(entry.status)
else:
# Starting on new revision, check previous revision.
if _all_ok(statuses_for_current_lkgr):
# All bots are green; LKGR found.
return current_lkgr
else:
# Not all bots are green, so start over on the next revision.
current_lkgr = entry.revision
statuses_for_current_lkgr = [entry.status]
if _all_ok(statuses_for_current_lkgr):
# There was only one revision and it was OK.
return current_lkgr
# There are no all-green revision in the database.
return None

View File

@ -13,15 +13,10 @@
from google.appengine.ext.webapp import template
import webapp2
import add_build_status_data
import add_coverage_data
import dashboard
import lkgr_page
app = webapp2.WSGIApplication([('/', dashboard.ShowDashboard),
('/lkgr', lkgr_page.ShowLkgr),
('/add_coverage_data',
add_coverage_data.AddCoverageData),
('/add_build_status_data',
add_build_status_data.AddBuildStatusData)],
add_coverage_data.AddCoverageData)],
debug=True)

View File

@ -1,13 +1,5 @@
<html>
<head>
<title>WebRTC Performance Metrics</title>
<link rel="stylesheet" href="../stylesheets/perf.css" type="text/css">
<meta HTTP-EQUIV="REFRESH" content="0; url=/">
</head>
<body>
<h1>WebRTC Performance Metrics</h1>
<p><a href="video_perf.html">Video</a></p>
<p><a href="audio_perf.html">Audio</a></p>
<p><a href="audio_perf_chrome.html">Chrome Audio Path</a></p>
<p><a href="vie_auto_test_perf.html">vie_auto_test</a></p>
</body>
</html>

View File

@ -0,0 +1,6 @@
<html>
<head>
<meta HTTP-EQUIV="REFRESH"
content="0; url=http://webrtc-status.appspot.com/lkgr">
</head>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -30,8 +30,9 @@
background-color: #FFC343;
}
.last_known_good_revision {
font-size: 800%;
.perf_links {
margin-bottom: 50px;
font-size: 200%;
}
.status_cell {
@ -42,4 +43,4 @@
body {
margin-left: 35px;
margin-top: 25px;
}
}

View File

@ -13,7 +13,7 @@
Template file to be used to generate the WebRTC dashboard.
-->
<head>
<title>WebRTC Coverage Dashboard</title>
<title>WebRTC Quality Dashboard</title>
<meta http-equiv="refresh" content="60">
<link href="http://code.google.com/css/codesite.pack.04102009.css"
rel="stylesheet" type="text/css">
@ -62,49 +62,14 @@
</script>
</head>
<body>
<h1>WebRTC Quality Dashboard</h1>
<h2>Current Build Status</h2>
<div>(as of {{ last_updated_at }} UTC)</div>
<table>
<tr>
{% for entry in build_status_data_row_1 %}
<th class="status_cell">{{ entry.bot_name }}</th>
{% endfor %}
</tr>
<tr>
{% for entry in build_status_data_row_1 %}
<td title="Last built revision {{ entry.revision }}"
class="status_cell status_{{entry.status}}">
{{entry.status}}
</td>
{% endfor %}
</tr>
<tr>
{% for entry in build_status_data_row_2 %}
<th class="status_cell">{{ entry.bot_name }}</th>
{% endfor %}
</tr>
<tr>
{% for entry in build_status_data_row_2 %}
<td title="Last built revision {{ entry.revision }}"
class="status_cell status_{{entry.status}}">
{{entry.status}}
</td>
{% endfor %}
</tr>
</table>
<p></p>
<h2>Last Known Good Revision (LKGR)</h2>
<div class="last_known_good_revision">
{% if lkgr %}
<a href="http://code.google.com/p/webrtc/source/detail?r={{ lkgr }}">
{{ lkgr }}</a>
{% else %}
????
{% endif %}
</div>
<img src="images/webrtc_logo.png">
<h1>Performance Metrics</h1>
<p class="perf_links">
<a href="perf/video_perf.html">Chrome Video Quality</a> |
<a href="perf/audio_perf.html">Chrome Audio / Voice Engine Quality</a> |
<a href="perf/audio_perf_chrome.html">Chrome Audio Latency</a> |
<a href="perf/vie_auto_test_perf.html">Video Engine</a>
</p>
<h2>Code Coverage History (Small / Medium Tests)</h2>
<div id="table_div_small_medium_coverage"></div>
<h2>Code Coverage History (Large Tests)</h2>

View File

@ -1,93 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
"""Contains functions for parsing the build master's transposed grid page.
Compatible with build bot 0.8.4 P1.
"""
import re
import urllib
# This is here to work around a buggy build bot status message which makes no
# sense, but which means the build failed when the slave was lost.
BB_084_P1_BUGGY_STATUS = 'build<br/>successful<br/>exception<br/>slave<br/>lost'
class FailedToParseBuildStatus(Exception):
pass
def _map_status(status):
if status == 'exception' or status == BB_084_P1_BUGGY_STATUS:
return 'failed'
return status
def _parse_builds(revision, html):
"""Parses the bot list, which is a sequence of <td></td> lines.
See contract for parse_tgrid_page for more information on how this function
behaves.
Example input:
<td class="build success"><a href="builders/Android/builds/119">OK</a></td>
The first regular expression group captures Android, second 119, third OK.
"""
result = {}
for match in re.finditer('<td.*?>.*?<a href="builders/(.+?)/builds/(\d+)">'
'(OK|failed|building|warnings|exception|' +
BB_084_P1_BUGGY_STATUS + ')'
'.*?</a>.*?</td>',
html, re.DOTALL):
revision_and_bot_name = revision + "--" + urllib.unquote(match.group(1))
build_number_and_status = match.group(2) + "--" + _map_status(
match.group(3))
result[revision_and_bot_name] = build_number_and_status
return result
def parse_tgrid_page(html):
"""Parses the build master's tgrid page.
Example input:
<tr>
<td valign="bottom" class="sourcestamp">1568</td>
LIST OF BOTS
</tr>
The first regular expression group captures 1568, second group captures
everything in LIST OF BOTS. The list of bots is then passed into a
separate function for parsing.
Args:
html: The raw HTML from the tgrid page.
Returns: A dictionary with <svn revision>--<bot name> mapped to
<bot build number>--<status>, where status is either OK, failed,
building or warnings. The status may be 'exception' in the input, but
we simply map that to failed.
"""
result = {}
for match in re.finditer('<td.*?class="sourcestamp">(\d+).*?</td>(.*?)</tr>',
html, re.DOTALL):
revision = match.group(1)
builds_for_revision_html = match.group(2)
result.update(_parse_builds(revision, builds_for_revision_html))
if not result:
raise FailedToParseBuildStatus('Could not find any build statuses in %s.' %
html)
return result

View File

@ -1,601 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
"""Test the tgrid parser.
Compatible with build bot 0.8.4 P1.
"""
import unittest
import tgrid_parser
SAMPLE_FILE = """
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Buildbot</title>
<link rel="stylesheet" href="default.css" type="text/css" />
<link rel="alternate" type="application/rss+xml" title="RSS" href="rss">
</head>
<body class="interface">
<div class="header">
<a href=".">Home</a>
- <a href="waterfall">Waterfall</a>
<a href="grid">Grid</a>
<a href="tgrid">T-Grid</a>
<a href="console">Console</a>
<a href="builders">Builders</a>
<a href="one_line_per_build">Recent Builds</a>
<a href="buildslaves">Buildslaves</a>
<a href="changes">Changesources</a>
- <a href="json/help">JSON API</a>
- <a href="about">About</a>
</div>
<hr/>
<div class="content">
<h1>Transposed Grid View</h1>
<table class="Grid" border="0" cellspacing="0">
<tr>
<td class="title"><a href="http://www.chromium.org">WebRTC</a>
</td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/Android">Android</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/AndroidNDK">AndroidNDK</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/Chrome">Chrome</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/ChromeOS">ChromeOS</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/Linux32DBG">Linux32DBG</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/Linux32Release">Linux32Release</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/Linux64DBG">Linux64DBG</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/Linux64DBG-GCC4.6">Linux64DBG-GCC4.6</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/Linux64Release">Linux64Release</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/LinuxClang">LinuxClang</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/LinuxValgrind">LinuxValgrind</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/LinuxVideoTest">LinuxVideoTest</a></td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/MacOS32DBG">MacOS32DBG</a></td>
<td valign="middle" style="text-align: center" class="builder building">
<a href="builders/MacOS32Release">MacOS32Release</a><br/>(building)</td>
<td valign="middle" style="text-align: center" class="builder idle">
<a href="builders/Win32Debug">Win32Debug</a></td>
<td valign="middle" style="text-align: center" class="builder building">
<a href="builders/Win32Release">Win32Release</a><br/>(building)</td>
</tr>
<tr>
<td valign="bottom" class="sourcestamp">2006 </td>
<td class="build success">
<a href="builders/Android/builds/482">OK</a>
</td>
<td class="build success">
<a href="builders/AndroidNDK/builds/70">OK</a>
</td>
<td class="build success">
<a href="builders/Chrome/builds/243">warnings</a>
</td>
<td class="build success">
<a href="builders/ChromeOS/builds/933">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32DBG/builds/936">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32Release/builds/1050">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG/builds/1038">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG-GCC4.6/builds/371">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64Release/builds/936">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxClang/builds/610">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxValgrind/builds/317">OK</a>
</td>
<td class="build">&nbsp;</td>
<td class="build success">
<a href="builders/MacOS32DBG/builds/1052">OK</a>
</td>
<td class="build">&nbsp;</td>
<td class="build success">
<a href="builders/Win32Debug/builds/822">OK</a>
</td>
<td class="build">&nbsp;</td>
</tr>
<tr>
<td valign="bottom" class="sourcestamp">2007 </td>
<td class="build success">
<a href="builders/Android/builds/483">OK</a>
</td>
<td class="build success">
<a href="builders/AndroidNDK/builds/71">OK</a>
</td>
<td class="build success">
<a href="builders/Chrome/builds/244">OK</a>
</td>
<td class="build success">
<a href="builders/ChromeOS/builds/934">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32DBG/builds/937">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32Release/builds/1051">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG/builds/1039">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG-GCC4.6/builds/372">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64Release/builds/937">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxClang/builds/611">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxValgrind/builds/318">OK</a>
</td>
<td class="build failure">
<a href="builders/LinuxVideoTest/builds/731">failed<br/>voe_auto_test</a>
</td>
<td class="build success">
<a href="builders/MacOS32DBG/builds/1053">OK</a>
</td>
<td class="build success">
<a href="builders/MacOS32Release/builds/309">OK</a>
</td>
<td class="build success">
<a href="builders/Win32Debug/builds/823">OK</a>
</td>
<td class="build success">
<a href="builders/Win32Release/builds/809">OK</a>
</td>
</tr>
<tr>
<td valign="bottom" class="sourcestamp">2008 </td>
<td class="build success">
<a href="builders/Android/builds/484">OK</a>
</td>
<td class="build success">
<a href="builders/AndroidNDK/builds/72">OK</a>
</td>
<td class="build success">
<a href="builders/Chrome/builds/245">OK</a>
</td>
<td class="build success">
<a href="builders/ChromeOS/builds/935">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32DBG/builds/938">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32Release/builds/1052">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG/builds/1040">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG-GCC4.6/builds/373">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64Release/builds/938">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxClang/builds/612">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxValgrind/builds/319">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxVideoTest/builds/732">OK</a>
</td>
<td class="build success">
<a href="builders/MacOS32DBG/builds/1054">OK</a>
</td>
<td class="build success">
<a href="builders/MacOS32Release/builds/310">OK</a>
</td>
<td class="build success">
<a href="builders/Win32Debug/builds/824">OK</a>
</td>
<td class="build success">
<a href="builders/Win32Release/builds/810">OK</a>
</td>
</tr>
<tr>
<td valign="bottom" class="sourcestamp">2010 </td>
<td class="build success">
<a href="builders/Android/builds/485">OK</a>
</td>
<td class="build success">
<a href="builders/AndroidNDK/builds/73">OK</a>
</td>
<td class="build">&nbsp;</td>
<td class="build success">
<a href="builders/ChromeOS/builds/936">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32DBG/builds/939">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32Release/builds/1053">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG/builds/1041">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG-GCC4.6/builds/374">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64Release/builds/939">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxClang/builds/613">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxValgrind/builds/320">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxVideoTest/builds/733">OK</a>
</td>
<td class="build success">
<a href="builders/MacOS32DBG/builds/1055">OK</a>
</td>
<td class="build success">
<a href="builders/MacOS32Release/builds/311">OK</a>
</td>
<td class="build success">
<a href="builders/Win32Debug/builds/825">OK</a>
</td>
<td class="build success">
<a href="builders/Win32Release/builds/811">OK</a>
</td>
</tr>
<tr>
<td valign="bottom" class="sourcestamp">2011 </td>
<td class="build success">
<a href="builders/Android/builds/486">OK</a>
</td>
<td class="build success">
<a href="builders/AndroidNDK/builds/74">OK</a>
</td>
<td class="build">&nbsp;</td>
<td class="build success">
<a href="builders/ChromeOS/builds/937">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32DBG/builds/940">OK</a>
</td>
<td class="build success">
<a href="builders/Linux32Release/builds/1054">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG/builds/1042">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64DBG-GCC4.6/builds/375">OK</a>
</td>
<td class="build success">
<a href="builders/Linux64Release/builds/940">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxClang/builds/614">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxValgrind/builds/321">OK</a>
</td>
<td class="build success">
<a href="builders/LinuxVideoTest/builds/734">OK</a>
</td>
<td class="build success">
<a href="builders/MacOS32DBG/builds/1056">OK</a>
</td>
<td class="build running">
<a href="builders/MacOS32Release/builds/313">building</a>
</td>
<td class="build success">
<a href="builders/Win32Debug/builds/826">OK</a>
</td>
<td class="build running">
<a href="builders/Win32Release/builds/813">building</a>
</td>
</tr>
<tr>
<td valign="bottom" class="sourcestamp">latest </td>
<td class="build running">
<a href="builders/MacOS32Release/builds/313">building</a>
</td>
<td class="build success">
<a href="builders/Win32Debug/builds/826">OK</a>
</td>
</tr>
</table>
</div><div class="footer" style="clear:both">
<hr/>
<a href="http://buildbot.net/">BuildBot</a> (0.8.4p1)
working for the&nbsp;<a href="http://www.chromium.org">WebRTC
</a>&nbsp;project.<br/>
Page built: <b>Thu 12 Apr 2012 03:49:32</b> (CDT)
</div>
</body>
</html>
"""
MINIMAL_OK = """
<tr>
<td valign="bottom" class="sourcestamp">1570 </td>
<td class="build success">
<a href="builders/Linux%20Clang%20%5Bstable%5D/builds/121">OK</a></td>
</tr>
"""
MINIMAL_FAIL = """
<tr>
<td valign="bottom" class="sourcestamp">1573 </td>
<td class="build failure">
<a href="builders/Linux%20Large%20Tests/builds/731">failed<br/>voe_auto_test
</a>
</td>
</tr>
"""
MINIMAL_BUILDING = """
<tr>
<td valign="bottom" class="sourcestamp">1576 </td>
<td class="build running">
<a href="builders/Win32Debug/builds/434">building</a></td>
voe_auto_test</td>
</tr>
"""
MINIMAL_WARNED = """
<tr>
<td valign="bottom" class="sourcestamp">1576 </td>
<td class="build warnings">
<a href="builders/Chrome/builds/109">warnings</a><br />
make chrome</td>
</tr>
"""
MINIMAL_EXCEPTION = """
<tr>
<td valign="bottom" class="sourcestamp">1576 </td>
<td class="build exception">
<a href="builders/Chrome/builds/109">exception</a><br />
Sync</td>
</tr>
"""
MINIMAL_EXCEPTION_SLAVE_LOST = """
<tr>
<td valign="bottom" class="sourcestamp">1576 </td>
<td class="build retry">
<a href="builders/LinuxValgrind/builds/324">build<br/>successful<br/>exception<br/>slave<br/>lost</a>
</td>
</tr>
"""
MINIMAL_IN_TRUNK_SOURCESTAMP = """
<tr>
<td valign="bottom" class="sourcestamp">1576 in trunk </td>
<td class="build retry">
<a href="builders/LinuxValgrind/builds/324">build<br/>successful<br/>exception<br/>slave<br/>lost</a>
</td>
</tr>
"""
class TGridParserTest(unittest.TestCase):
def test_parser_throws_exception_on_empty_html(self):
self.assertRaises(tgrid_parser.FailedToParseBuildStatus,
tgrid_parser.parse_tgrid_page, '')
def test_parser_finds_successful_bot(self):
result = tgrid_parser.parse_tgrid_page(MINIMAL_OK)
self.assertEqual(1, len(result), 'There is only one bot in the sample.')
first_mapping = result.items()[0]
# Note: the parser should unescape % quotations, like %20 for space.
self.assertEqual('1570--Linux Clang [stable]', first_mapping[0])
self.assertEqual('121--OK', first_mapping[1])
def test_parser_finds_failed_bot(self):
result = tgrid_parser.parse_tgrid_page(MINIMAL_FAIL)
self.assertEqual(1, len(result), 'There is only one bot in the sample.')
first_mapping = result.items()[0]
self.assertEqual('1573--Linux Large Tests', first_mapping[0])
self.assertEqual('731--failed', first_mapping[1])
def test_parser_finds_building_bot(self):
result = tgrid_parser.parse_tgrid_page(MINIMAL_BUILDING)
self.assertEqual(1, len(result), 'There is only one bot in the sample.')
first_mapping = result.items()[0]
self.assertEqual('1576--Win32Debug', first_mapping[0])
self.assertEqual('434--building', first_mapping[1])
def test_parser_finds_warnings(self):
result = tgrid_parser.parse_tgrid_page(MINIMAL_WARNED)
self.assertEqual(1, len(result), 'There is only one bot in the sample.')
first_mapping = result.items()[0]
self.assertEqual('1576--Chrome', first_mapping[0])
self.assertEqual('109--warnings', first_mapping[1])
def test_parser_finds_exception_and_maps_to_failed(self):
result = tgrid_parser.parse_tgrid_page(MINIMAL_EXCEPTION)
self.assertEqual(1, len(result), 'There is only one bot in the sample.')
first_mapping = result.items()[0]
self.assertEqual('1576--Chrome', first_mapping[0])
self.assertEqual('109--failed', first_mapping[1])
def test_parser_finds_exception_slave_lost_and_maps_to_failed(self):
# This is to work around a bug in build bot 0.8.4p1 where it may say that
# the build was successful AND the slave was lost. In this case the build
# is not actually successful, so treat it as such.
result = tgrid_parser.parse_tgrid_page(MINIMAL_EXCEPTION_SLAVE_LOST)
self.assertEqual(1, len(result), 'There is only one bot in the sample.')
first_mapping = result.items()[0]
self.assertEqual('1576--LinuxValgrind', first_mapping[0])
self.assertEqual('324--failed', first_mapping[1])
def test_parser_finds_all_bots_and_revisions_except_forced_builds(self):
result = tgrid_parser.parse_tgrid_page(SAMPLE_FILE)
# 5*16 = 80 bots in sample. There's also five empty results because some
# bots did not run for some revisions, so 80 - 5 = 75 results. There are
# two additional statuses under an explicit 'latest' revision, which should
# be ignored since that means the build was forced.
self.assertEqual(75, len(result))
# Make some samples
self.assertTrue(result.has_key('2006--ChromeOS'))
self.assertEquals('933--OK', result['2006--ChromeOS'])
self.assertTrue(result.has_key('2006--Chrome'))
self.assertEquals('243--warnings', result['2006--Chrome'])
self.assertTrue(result.has_key('2006--LinuxClang'))
self.assertEquals('610--OK', result['2006--LinuxClang'])
# This one happened to not get reported in revision 2006, but it should be
# there in the next revision:
self.assertFalse(result.has_key('2006--Win32Release'))
self.assertTrue(result.has_key('2007--Win32Release'))
self.assertEquals('809--OK', result['2007--Win32Release'])
self.assertTrue(result.has_key('2007--ChromeOS'))
self.assertEquals('934--OK', result['2007--ChromeOS'])
self.assertTrue(result.has_key('2007--LinuxVideoTest'))
self.assertEquals('731--failed', result['2007--LinuxVideoTest'])
self.assertTrue(result.has_key('2011--Win32Release'))
self.assertEquals('813--building', result['2011--Win32Release'])
if __name__ == '__main__':
unittest.main()

View File

@ -1,94 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
"""This script checks the current build status on the master and submits
it to the dashboard. It is adapted to build bot version 0.7.12.
"""
import httplib
import constants
import dashboard_connection
import tgrid_parser
# Bots that must be green in order to increment the LKGR revision.
# TODO(kjellander): Remake this entirely as we have now switched to Chrome infra
# bots. This is just to get the LGKR moving forward for now.
BOTS = ['Linux32 Debug Coverage',
'Win Large Tests',
'Mac Large Tests',
'Linux Large Tests',
'Android Platform',
'Android NDK',
]
class FailedToGetStatusFromMaster(Exception):
pass
def _download_and_parse_build_status():
connection = httplib.HTTPConnection(constants.BUILD_MASTER_SERVER)
connection.request('GET', constants.BUILD_MASTER_TRANSPOSED_GRID_URL)
response = connection.getresponse()
if response.status != 200:
raise FailedToGetStatusFromMaster(('Failed to get build status from master:'
' got status %d, reason %s.' %
(response.status, response.reason)))
full_response = response.read()
connection.close()
return tgrid_parser.parse_tgrid_page(full_response)
def _is_chrome_only_build(revision_to_bot_name):
"""Figures out if a revision-to-bot-name mapping represents a Chrome build.
We assume here that Chrome revisions are always > 100000, whereas WebRTC
revisions will not reach that number in the foreseeable future.
"""
revision = int(revision_to_bot_name.split('--')[0])
bot_name = revision_to_bot_name.split('--')[1]
return 'Chrome' in bot_name and revision > 100000
def _filter_undesired_bots(bot_to_status_mapping, desired_bot_names):
"""Returns the desired bots for the builds status from the dictionary.
Args:
bot_to_status_mapping: Dictionary mapping bot name with revision to status.
desired_bot_names: List of bot names that will be the only bots returned in
the resulting dictionary.
Returns: A dictionary only containing the desired bots.
"""
result = {}
for revision_to_bot_name, status in bot_to_status_mapping.iteritems():
bot_name = revision_to_bot_name.split('--')[1]
if bot_name in desired_bot_names:
result[revision_to_bot_name] = status
return result
def _main():
dashboard = dashboard_connection.DashboardConnection(constants.CONSUMER_KEY)
dashboard.read_required_files(constants.CONSUMER_SECRET_FILE,
constants.ACCESS_TOKEN_FILE)
bot_to_status_mapping = _download_and_parse_build_status()
bot_to_status_mapping = _filter_undesired_bots(bot_to_status_mapping, BOTS)
dashboard.send_post_request(constants.ADD_BUILD_STATUS_DATA_URL,
bot_to_status_mapping)
if __name__ == '__main__':
_main()

View File

@ -1,39 +0,0 @@
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
#
# Use of this source code is governed by a BSD-style license
# that can be found in the LICENSE file in the root of the source
# tree. An additional intellectual property rights grant can be found
# in the file PATENTS. All contributing project authors may
# be found in the AUTHORS file in the root of the source tree.
"""Unit test for the build status tracker script."""
import copy
import unittest
import track_build_status
NORMAL_BOT_TO_STATUS_MAPPING = {
'1455--Win 64 Release': '455--OK',
'1455--CrOS': '900--failed',
'1455--Linux32 Debug': '344--OK',
'1456--Win Large Tests': '456--OK'}
class TrackBuildStatusTest(unittest.TestCase):
def test_get_desired_bots(self):
bot_to_status_mapping = copy.deepcopy(NORMAL_BOT_TO_STATUS_MAPPING)
desired_bot_names = ['Linux32 Debug']
# pylint: disable=W0212
result = track_build_status._filter_undesired_bots(bot_to_status_mapping,
desired_bot_names)
self.assertEquals(1, len(result))
self.assertTrue(desired_bot_names[0] in result.keys()[0])
if __name__ == '__main__':
unittest.main()