Made the necessary adaptations for the dashboard launch and fixed some bugs (already live).
Will now recognize warnings as a status. Returns proper HTTP status codes for the most common errors now. Will be more strict when checking build status data (no newlines in bot names). Prepared cron scripts. Prepared dashboard for production use. BUG= TEST= Review URL: https://webrtc-codereview.appspot.com/404003 git-svn-id: http://webrtc.googlecode.com/svn/trunk@1772 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
52b59d095e
commit
914ef27c63
@ -14,8 +14,7 @@ __author__ = 'phoglund@webrtc.org (Patrik Höglund)'
|
||||
|
||||
# This identifies our application using the information we got when we
|
||||
# registered the application on Google appengine.
|
||||
# TODO(phoglund): update to the right value when we have registered the app.
|
||||
DASHBOARD_SERVER = 'localhost:8080'
|
||||
DASHBOARD_SERVER = 'webrtc-dashboard.appspot.com'
|
||||
DASHBOARD_SERVER_HTTP = 'http://' + DASHBOARD_SERVER
|
||||
CONSUMER_KEY = DASHBOARD_SERVER
|
||||
CONSUMER_SECRET_FILE = 'consumer.secret'
|
||||
@ -30,9 +29,9 @@ ACCESS_TOKEN_URL = DASHBOARD_SERVER_HTTP + '/_ah/OAuthGetAccessToken'
|
||||
BUILD_MASTER_SERVER = 'webrtc-cb-linux-master.cbf.corp.google.com:8010'
|
||||
BUILD_MASTER_TRANSPOSED_GRID_URL = '/tgrid'
|
||||
|
||||
# The build-bot user which runs build bot jobs.
|
||||
BUILD_BOT_USER = 'phoglund'
|
||||
# Build bot constants.
|
||||
BUILD_BOT_COVERAGE_WWW_DIRECTORY = '/var/www/'
|
||||
|
||||
# Dashboard data input URLs.
|
||||
ADD_COVERAGE_DATA_URL = '/add_coverage_data'
|
||||
ADD_BUILD_STATUS_DATA_URL = '/add_build_status_data'
|
||||
ADD_COVERAGE_DATA_URL = DASHBOARD_SERVER_HTTP + '/add_coverage_data'
|
||||
ADD_BUILD_STATUS_DATA_URL = DASHBOARD_SERVER_HTTP + '/add_build_status_data'
|
||||
|
@ -13,12 +13,13 @@
|
||||
__author__ = 'phoglund@webrtc.org (Patrik Höglund)'
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from google.appengine.ext import db
|
||||
|
||||
import oauth_post_request_handler
|
||||
|
||||
VALID_STATUSES = ['OK', 'failed', 'building']
|
||||
VALID_STATUSES = ['OK', 'failed', 'building', 'warnings']
|
||||
|
||||
|
||||
class OrphanedBuildStatusesExistException(Exception):
|
||||
@ -85,6 +86,8 @@ def _parse_name(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)
|
||||
|
||||
@ -142,7 +145,9 @@ class AddBuildStatusData(oauth_post_request_handler.OAuthPostRequestHandler):
|
||||
(build_number, status) = _parse_status(build_number_and_status)
|
||||
(revision, bot_name) = _parse_name(revision_and_bot_name)
|
||||
except ValueError as error:
|
||||
self._show_error_page('Invalid parameter in request: %s.' % error)
|
||||
logger.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
|
||||
@ -163,3 +168,4 @@ class AddBuildStatusData(oauth_post_request_handler.OAuthPostRequestHandler):
|
||||
request_datetime = datetime.datetime.fromtimestamp(request_posix_timestamp)
|
||||
build_status_root.last_updated_at = request_datetime
|
||||
build_status_root.put()
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
||||
__author__ = 'phoglund@webrtc.org (Patrik Höglund)'
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from google.appengine.ext import db
|
||||
|
||||
@ -53,9 +54,9 @@ class AddCoverageData(oauth_post_request_handler.OAuthPostRequestHandler):
|
||||
function_coverage_string = self.request.get('function_coverage')
|
||||
function_coverage = _parse_percentage(function_coverage_string)
|
||||
|
||||
except ValueError as exception:
|
||||
self._show_error_page('Invalid parameter in request. Details: %s' %
|
||||
exception)
|
||||
except ValueError as error:
|
||||
logger.warn('Invalid parameter in request: %s.' % error)
|
||||
self.response.set_status(400)
|
||||
return
|
||||
|
||||
item = CoverageData(date=parsed_date,
|
||||
|
@ -1,14 +1,24 @@
|
||||
application: dashboard
|
||||
application: webrtc-dashboard
|
||||
version: 1
|
||||
runtime: python27
|
||||
api_version: 1
|
||||
threadsafe: false
|
||||
|
||||
handlers:
|
||||
# Serve stylesheets statically.
|
||||
- url: /stylesheets
|
||||
static_dir: stylesheets
|
||||
# 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
|
||||
# doesn't get suspicious.
|
||||
- url: /google403c95edcde16425.html
|
||||
static_files: static/google403c95edcde16425.html
|
||||
upload: static/google403c95edcde16425.html
|
||||
|
||||
# Note: tests should be disabled in production.
|
||||
# - url: /test.*
|
||||
# script: gaeunit.py
|
||||
|
||||
# Redirect all other requests to our dynamic handlers.
|
||||
- url: /.*
|
||||
script: dashboard.app
|
@ -32,13 +32,16 @@ class ShowDashboard(webapp2.RequestHandler):
|
||||
build_status_loader = load_build_status.BuildStatusLoader()
|
||||
build_status_data = build_status_loader.load_build_status_data()
|
||||
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()
|
||||
coverage_json_data = coverage_loader.load_coverage_json_data()
|
||||
|
||||
# Fill in the template with the data and respond:
|
||||
page_template_filename = 'templates/dashboard_template.html'
|
||||
self.response.write(template.render(page_template_filename, vars()))
|
||||
|
||||
|
@ -15,8 +15,12 @@ __author__ = 'phoglund@webrtc.org (Patrik Höglund)'
|
||||
from google.appengine.ext import db
|
||||
|
||||
|
||||
def _status_not_ok(status):
|
||||
return status not in ('OK', 'warnings')
|
||||
|
||||
|
||||
def _all_ok(statuses):
|
||||
return filter(lambda status: status != "OK", statuses) == []
|
||||
return filter(_status_not_ok, statuses) == []
|
||||
|
||||
|
||||
def _get_first_entry(iterable):
|
||||
@ -32,7 +36,7 @@ class BuildStatusLoader:
|
||||
def load_build_status_data(self):
|
||||
"""Returns the latest conclusive build status for each bot.
|
||||
|
||||
The statuses OK or failed are considered to be conclusive.
|
||||
The statuses OK, failed and warnings are considered to be conclusive.
|
||||
|
||||
The two most recent revisions are considered. The set of bots returned
|
||||
will therefore be the bots that were reported the two most recent
|
||||
@ -49,7 +53,7 @@ class BuildStatusLoader:
|
||||
|
||||
bots_to_latest_conclusive_entry = dict()
|
||||
for entry in build_status_entries:
|
||||
if entry.status == "building":
|
||||
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
|
||||
|
@ -13,6 +13,7 @@
|
||||
__author__ = 'phoglund@webrtc.org (Patrik Höglund)'
|
||||
|
||||
from google.appengine.api import oauth
|
||||
import logging
|
||||
import webapp2
|
||||
|
||||
|
||||
@ -38,7 +39,8 @@ class OAuthPostRequestHandler(webapp2.RequestHandler):
|
||||
try:
|
||||
self._authenticate_user()
|
||||
except UserNotAuthenticatedException as exception:
|
||||
self._show_error_page('Failed to authenticate user: %s' % exception)
|
||||
logging.warn('Failed to authenticate: %s.' % exception)
|
||||
self.response.set_status(403)
|
||||
return
|
||||
|
||||
# Do the actual work.
|
||||
@ -46,7 +48,6 @@ class OAuthPostRequestHandler(webapp2.RequestHandler):
|
||||
|
||||
def _parse_and_store_data(self):
|
||||
"""Reads data from POST request and responds accordingly."""
|
||||
|
||||
raise NotImplementedError('You must override this method!')
|
||||
|
||||
def _authenticate_user(self):
|
||||
@ -54,6 +55,8 @@ class OAuthPostRequestHandler(webapp2.RequestHandler):
|
||||
if oauth.is_current_user_admin():
|
||||
# The user on whose behalf we are acting is indeed an administrator
|
||||
# of this application, so we're good to go.
|
||||
logging.info('Authenticated on behalf of user %s.' %
|
||||
oauth.get_current_user())
|
||||
return
|
||||
else:
|
||||
raise UserNotAuthenticatedException('We are acting on behalf of '
|
||||
@ -62,7 +65,4 @@ class OAuthPostRequestHandler(webapp2.RequestHandler):
|
||||
oauth.get_current_user())
|
||||
except oauth.OAuthRequestError as exception:
|
||||
raise UserNotAuthenticatedException('Invalid OAuth request: %s' %
|
||||
exception)
|
||||
|
||||
def _show_error_page(self, error_message):
|
||||
self.response.write('<html><body>%s</body></html>' % error_message)
|
||||
exception.__class__.__name__)
|
||||
|
@ -0,0 +1 @@
|
||||
google-site-verification: google403c95edcde16425.html
|
@ -25,6 +25,11 @@
|
||||
background-color: #fffc6c;
|
||||
}
|
||||
|
||||
.status_warnings {
|
||||
color: #000000;
|
||||
background-color: #FFC343;
|
||||
}
|
||||
|
||||
.last_known_good_revision {
|
||||
font-size: 800%;
|
||||
}
|
||||
|
@ -51,7 +51,7 @@
|
||||
|
||||
<h1>WebRTC Quality Dashboard</h1>
|
||||
<h2>Current Build Status</h2>
|
||||
<div>(as of {{ last_updated_at }})</div>
|
||||
<div>(as of {{ last_updated_at }} UTC)</div>
|
||||
<table>
|
||||
<tr>
|
||||
{% for entry in build_status_data %}
|
||||
|
@ -14,6 +14,7 @@ __author__ = 'phoglund@webrtc.org (Patrik Höglund)'
|
||||
|
||||
import httplib
|
||||
import shelve
|
||||
import urlparse
|
||||
import oauth.oauth as oauth
|
||||
|
||||
import constants
|
||||
@ -35,12 +36,9 @@ class DashboardConnection:
|
||||
authenticated using OAuth. This class requires a consumer secret and a
|
||||
access token.
|
||||
|
||||
The consumer secret is created manually on the machine running the script
|
||||
(only authorized users should be able to log into the machine and see the
|
||||
secret though). The access token is generated by the
|
||||
request_oauth_permission.py script. Both these values are stored as files
|
||||
on disk, in the scripts' working directory, according to the formats
|
||||
prescribed in the read_required_files method.
|
||||
The access token and consumer secrets are stored as files on disk in the
|
||||
working directory of the scripts. Both files are created by the
|
||||
request_oauth_permission script.
|
||||
"""
|
||||
|
||||
def __init__(self, consumer_key):
|
||||
@ -50,15 +48,15 @@ class DashboardConnection:
|
||||
"""Reads required data for making OAuth requests.
|
||||
|
||||
Args:
|
||||
consumer_secret_file: A plain text file with a single line containing
|
||||
the consumer secret string.
|
||||
consumer_secret_file: A shelve file with an entry consumer_secret
|
||||
containing the consumer secret in string form.
|
||||
access_token_file: A shelve file with an entry access_token
|
||||
containing the access token in string form.
|
||||
"""
|
||||
self.access_token_ = self._read_access_token(access_token_file)
|
||||
self.access_token_string_ = self._read_access_token(access_token_file)
|
||||
self.consumer_secret_ = self._read_consumer_secret(consumer_secret_file)
|
||||
|
||||
def send_post_request(self, sub_url, parameters):
|
||||
def send_post_request(self, url, parameters):
|
||||
"""Sends an OAuth request for a protected resource in the dashboard.
|
||||
|
||||
Use this when you want to report new data to the dashboard. You must have
|
||||
@ -72,8 +70,8 @@ class DashboardConnection:
|
||||
information in the response.
|
||||
|
||||
Args:
|
||||
sub_url: A relative url within the dashboard domain, for example
|
||||
/add_coverage_data.
|
||||
url: An absolute url within the dashboard domain, for example
|
||||
http://webrtc-dashboard.appspot.com/add_coverage_data.
|
||||
parameters: A dict which maps from POST parameter names to values.
|
||||
|
||||
Raises:
|
||||
@ -81,30 +79,31 @@ class DashboardConnection:
|
||||
with HTTP 200 to our request or if the response is non-empty.
|
||||
"""
|
||||
consumer = oauth.OAuthConsumer(self.consumer_key_, self.consumer_secret_)
|
||||
create_oauth_request = oauth.OAuthRequest.from_consumer_and_token
|
||||
oauth_request = create_oauth_request(consumer,
|
||||
token=self.access_token_,
|
||||
http_method='POST',
|
||||
http_url=sub_url,
|
||||
parameters=parameters)
|
||||
access_token = oauth.OAuthToken.from_string(self.access_token_string_)
|
||||
|
||||
oauth_request = oauth.OAuthRequest.from_consumer_and_token(
|
||||
consumer,
|
||||
token=access_token,
|
||||
http_method='POST',
|
||||
http_url=url,
|
||||
parameters=parameters)
|
||||
|
||||
signature_method_hmac_sha1 = oauth.OAuthSignatureMethod_HMAC_SHA1()
|
||||
oauth_request.sign_request(signature_method_hmac_sha1, consumer,
|
||||
self.access_token_)
|
||||
access_token)
|
||||
|
||||
connection = httplib.HTTPConnection(constants.DASHBOARD_SERVER)
|
||||
|
||||
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
connection.request('POST', sub_url, body=oauth_request.to_postdata(),
|
||||
connection.request('POST', url, body=oauth_request.to_postdata(),
|
||||
headers=headers)
|
||||
|
||||
response = connection.getresponse()
|
||||
connection.close()
|
||||
|
||||
if response.status != 200:
|
||||
message = ('Failed to report to %s%s: got response %d (%s)' %
|
||||
(constants.DASHBOARD_SERVER, sub_url, response.status,
|
||||
response.reason))
|
||||
message = ('Failed to report to %s: got response %d (%s)' %
|
||||
(url, response.status, response.reason))
|
||||
raise FailedToReportToDashboard(message)
|
||||
|
||||
# The response content should be empty on success, so check that:
|
||||
@ -115,29 +114,21 @@ class DashboardConnection:
|
||||
raise FailedToReportToDashboard(message)
|
||||
|
||||
def _read_access_token(self, filename):
|
||||
return self._read_shelve(filename, 'access_token')
|
||||
|
||||
def _read_consumer_secret(self, filename):
|
||||
return self._read_shelve(filename, 'consumer_secret')
|
||||
|
||||
def _read_shelve(self, filename, key):
|
||||
input_file = shelve.open(filename)
|
||||
|
||||
if not input_file.has_key('access_token'):
|
||||
if not input_file.has_key(key):
|
||||
raise FailedToReadRequiredInputFile('Missing correct %s file in current '
|
||||
'directory. You may have to run '
|
||||
'request_oauth_permission.py.' %
|
||||
filename)
|
||||
|
||||
token = input_file['access_token']
|
||||
result = input_file[key]
|
||||
input_file.close()
|
||||
|
||||
return oauth.OAuthToken.from_string(token)
|
||||
|
||||
def _read_consumer_secret(self, filename):
|
||||
try:
|
||||
input_file = open(filename, 'r')
|
||||
except IOError as error:
|
||||
raise FailedToReadRequiredInputFile(error)
|
||||
|
||||
whole_file = input_file.read()
|
||||
if whole_file.count('\n') > 1 or not whole_file.strip():
|
||||
raise FailedToReadRequiredInputFile('Expected a single line with the '
|
||||
'consumer secret in file %s.' %
|
||||
filename)
|
||||
return whole_file
|
||||
|
||||
return result
|
||||
|
@ -13,18 +13,21 @@
|
||||
The script is intended to be run manually whenever we wish to change which
|
||||
dashboard administrator we act on behalf of when running the
|
||||
track_coverage.py script. For example, this will be useful if the current
|
||||
dashboard administrator leaves the project.
|
||||
dashboard administrator leaves the project. This script can also be used to
|
||||
launch a new dashboard if that is desired.
|
||||
|
||||
This script should be run on the build bot which runs the track_coverage.py
|
||||
script. This script will present a link during its execution, which the new
|
||||
administrator should follow and then click approve on the web page that
|
||||
appears. The new administrator should have admin rights on the coverage
|
||||
dashboard, otherwise track_coverage.py will not work.
|
||||
dashboard, otherwise the track_* scripts will not work.
|
||||
|
||||
If successful, this script will write the access token to a file access.token
|
||||
in the current directory, which later can be read by track_coverage.py.
|
||||
in the current directory, which later can be read by the track_* scripts.
|
||||
The token is stored in string form (as reported by the web server) using the
|
||||
shelve module.
|
||||
shelve module. The consumer secret passed in as an argument to this script
|
||||
will also similarly be stored in a file consumer.secret. The shelve keys
|
||||
will be 'access_token' and 'consumer_secret', respectively.
|
||||
"""
|
||||
|
||||
__author__ = 'phoglund@webrtc.org (Patrik Höglund)'
|
||||
@ -106,11 +109,19 @@ def _write_access_token_to_file(access_token, filename):
|
||||
print 'Wrote the access token to the file %s.' % filename
|
||||
|
||||
|
||||
def _write_consumer_secret_to_file(consumer_secret, filename):
|
||||
output = shelve.open(filename)
|
||||
output['consumer_secret'] = consumer_secret
|
||||
output.close()
|
||||
|
||||
print 'Wrote the consumer secret to the file %s.' % filename
|
||||
|
||||
|
||||
def _main():
|
||||
if len(sys.argv) != 2:
|
||||
print ('Usage: %s <consumer secret>.\n\nThe consumer secret is an OAuth '
|
||||
'concept and is obtained from the appengine running the dashboard.' %
|
||||
sys.argv[0])
|
||||
'concept and is obtained from the Google Accounts domain dashboard.'
|
||||
% sys.argv[0])
|
||||
return
|
||||
|
||||
consumer_secret = sys.argv[1]
|
||||
@ -124,6 +135,8 @@ def _main():
|
||||
access_token_string = _request_access_token(consumer, unauthorized_token)
|
||||
|
||||
_write_access_token_to_file(access_token_string, constants.ACCESS_TOKEN_FILE)
|
||||
_write_consumer_secret_to_file(consumer_secret,
|
||||
constants.CONSUMER_SECRET_FILE)
|
||||
|
||||
if __name__ == '__main__':
|
||||
_main()
|
||||
|
@ -29,7 +29,8 @@ def _parse_builds(revision, html):
|
||||
result = {}
|
||||
|
||||
for match in re.finditer('<td.*?>.*?<a href="builders/(.+?)/builds/(\d+)">'
|
||||
'(OK|failed|building)</a>.*?</td>', html, re.DOTALL):
|
||||
'(OK|failed|building|warnings)</a>.*?</td>',
|
||||
html, re.DOTALL):
|
||||
revision_and_bot_name = revision + "--" + match.group(1)
|
||||
build_number_and_status = match.group(2) + "--" + match.group(3)
|
||||
|
||||
@ -54,8 +55,8 @@ def parse_tgrid_page(html):
|
||||
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 or
|
||||
building.
|
||||
<bot build number>--<status>, where status is either OK, failed,
|
||||
building or warnings.
|
||||
"""
|
||||
result = {}
|
||||
|
||||
|
@ -34,6 +34,9 @@ SAMPLE_FILE = """
|
||||
<table class="Grid" border="0" cellspacing="0">
|
||||
<tr>
|
||||
<td valign="bottom" class="sourcestamp">1570</td>
|
||||
<td class="build warnings"><a href="builders/Chrome/builds/109">warnings</a>
|
||||
<br />
|
||||
make chrome</td>
|
||||
<td class="build success">
|
||||
<a href="builders/Android/builds/121">OK</a></td>
|
||||
<td class="build success">
|
||||
@ -61,6 +64,9 @@ SAMPLE_FILE = """
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="bottom" class="sourcestamp">1571</td>
|
||||
<td class="build warnings"><a href="builders/Chrome/builds/109">warnings</a>
|
||||
<br />
|
||||
make chrome</td>
|
||||
<td class="build success">
|
||||
<a href="builders/Android/builds/122">OK</a></td>
|
||||
<td class="build success">
|
||||
@ -118,6 +124,15 @@ 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>
|
||||
"""
|
||||
|
||||
class TGridParserTest(unittest.TestCase):
|
||||
def test_parser_throws_exception_on_empty_html(self):
|
||||
self.assertRaises(tgrid_parser.FailedToParseBuildStatus,
|
||||
@ -150,16 +165,28 @@ class TGridParserTest(unittest.TestCase):
|
||||
self.assertEqual('1576--Win32Debug', first_mapping[0])
|
||||
self.assertEqual('434--building', first_mapping[1])
|
||||
|
||||
def test_parser_finds_warned_bot(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_all_bots_and_revisions(self):
|
||||
result = tgrid_parser.parse_tgrid_page(SAMPLE_FILE)
|
||||
|
||||
# 2 * 12 = 24 bots in sample
|
||||
self.assertEqual(24, len(result))
|
||||
# 2 * 13 = 26 bots in sample
|
||||
self.assertEqual(26, len(result))
|
||||
|
||||
# Make some samples
|
||||
self.assertTrue(result.has_key('1570--ChromeOS'))
|
||||
self.assertEquals('578--OK', result['1570--ChromeOS'])
|
||||
|
||||
self.assertTrue(result.has_key('1570--Chrome'))
|
||||
self.assertEquals('109--warnings', result['1570--Chrome'])
|
||||
|
||||
self.assertTrue(result.has_key('1570--LinuxCLANG'))
|
||||
self.assertEquals('259--OK', result['1570--LinuxCLANG'])
|
||||
|
||||
|
@ -55,7 +55,7 @@ def _find_latest_32bit_debug_build(www_directory_contents, coverage_www_dir):
|
||||
www_directory_contents.sort(reverse=True)
|
||||
|
||||
for entry in www_directory_contents:
|
||||
match = re.match('Linux32bitDBG_\d+', entry)
|
||||
match = re.match('Linux32DBG_\d+', entry)
|
||||
if match is not None:
|
||||
return entry
|
||||
|
||||
@ -97,9 +97,7 @@ def _main():
|
||||
dashboard.read_required_files(constants.CONSUMER_SECRET_FILE,
|
||||
constants.ACCESS_TOKEN_FILE)
|
||||
|
||||
coverage_www_dir = os.path.join('/home', constants.BUILD_BOT_USER, 'www')
|
||||
|
||||
www_dir_contents = os.listdir(coverage_www_dir)
|
||||
www_dir_contents = os.listdir(BUILD_BOT_COVERAGE_WWW_DIRECTORY)
|
||||
latest_build_directory = _find_latest_32bit_debug_build(www_dir_contents,
|
||||
coverage_www_dir)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user