blob: 5765158208365c280ce79c028e64a4e5a2a2b5ba [file] [log] [blame]
# Copyright 2015 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""URL endpoint for a cron job to run bisects integration tests."""
import datetime
import time
from google.appengine.api import mail
from dashboard import auto_bisect
from dashboard import datastore_hooks
from dashboard import request_handler
from dashboard import start_try_job
from dashboard import stored_object
from dashboard import utils
from dashboard.models import try_job
_BISECT_FYI_CONFIGS_KEY = 'bisect_fyi_config_map'
_TEST_FAILURE_TEMPLATE = """
Test Name: %(test_name)s
Error:%(error)s
Bisect Config: %(info)s
"""
class BisectFYIHandler(request_handler.RequestHandler):
"""URL endpoint for a cron job to run bisects integration tests."""
def get(self):
"""A get request is the same a post request for this endpoint."""
self.post()
def post(self):
"""Runs auto bisects."""
datastore_hooks.SetPrivilegedRequest()
_RunBisectIntegrationTests()
def _RunBisectIntegrationTests():
"""Runs bisect jobs with pre determined configs."""
errors_list = {}
bisect_fyi_configs = stored_object.Get(_BISECT_FYI_CONFIGS_KEY)
for test_name, config in bisect_fyi_configs.iteritems():
if config.get('bisect_config'):
results = _StartBisectFYIJob(test_name, config.get('bisect_config'))
if 'error' in results:
errors_list[test_name] = {
'error': results['error'],
'info': config.get('bisect_config')}
else:
errors_list[test_name] = {'error': 'Missing bisect config.'}
if errors_list:
_SendEmailAlert(errors_list)
def _StartBisectFYIJob(test_name, bisect_config):
"""Re-starts a bisect-job after modifying it's config based on run count.
Args:
test_name: Name of the test case.
bisect_job: TryJob entity with initialized bot name and config.
Returns:
If successful, a dict containing "issue_id" and "issue_url" for the
bisect job. Otherwise, a dict containing "error", with some description
of the reason why a job wasn't started.
"""
try:
bisect_job = _MakeBisectFYITryJob(test_name, bisect_config)
except auto_bisect.NotBisectableError as e:
return {'error': e.message}
try:
bisect_result = start_try_job.PerformBisect(bisect_job)
except request_handler.InvalidInputError as e:
bisect_result = {'error': e.message}
if 'error' in bisect_result:
if bisect_job.key:
bisect_job.key.delete()
return bisect_result
def _MakeBisectFYITryJob(test_name, bisect_config):
"""Creates a TryJob entity with the bisect config.
Args:
test_name: Name of the test case.
bisect_config: A dictionary of parameters for a bisect job.
Returns:
A TryJob entity, which has not yet been put in the datastore.
Raises:
NotBisectableError: A valid bisect config could not be created.
"""
bisect_bot = bisect_config.get('recipe_tester_name')
if not bisect_bot:
raise auto_bisect.NotBisectableError('Could not select a bisect bot.')
config_python_string = utils.BisectConfigPythonString(bisect_config)
bisect_job = try_job.TryJob(
bot=bisect_bot,
config=config_python_string,
bug_id=bisect_config.get('bug_id', -1),
master_name=bisect_config.get('master_name', 'ChromiumPerf'),
job_type='bisect-fyi',
job_name=test_name)
return bisect_job
def VerifyBisectFYIResults(job):
"""Verifies the bisect results against expected results in test config.
Args:
job: TryJob entity.
Returns:
A message with the missing properties, otherwise returns an empty string.
"""
expected_results = _GetBisectConfig(job).get('expected_results')
try:
utils.Validate(expected_results, job.results_data)
except ValueError as e:
return 'Bisect result is not as expected: %s.' % e
return ''
def IsBugUpdated(job, issue_tracker):
"""Verifies whether bug is updated with the bisect results."""
comment_info = issue_tracker.GetLastBugCommentsAndTimestamp(job.bug_id)
if not comment_info:
return False
last_comment_timestamp = datetime.datetime.strptime(
comment_info['timestamp'], '%Y-%m-%dT%H:%M:%S')
bug_update_timestamp = time.mktime(last_comment_timestamp.timetuple())
try_job_timestamp = time.mktime(job.last_ran_timestamp.timetuple())
if bug_update_timestamp <= try_job_timestamp:
return False
return True
def _TextBody(errors_list):
"""Returns the text body for an email about test failures."""
test_alerts = []
for test_name, data in errors_list.iteritems():
test_alerts.append(
_TEST_FAILURE_TEMPLATE % {
'test_name': test_name,
'error': data.get('error'),
'info': data.get('info', '')
}
)
return '.\n'.join(test_alerts)
def _SendEmailAlert(errors_list):
"""Sends email alert about bisect integration tests failures."""
mail.send_mail(
sender='gasper-alerts@google.com',
to='chrome-performance-monitoring-alerts@google.com',
subject='[Bisect FYI Alert]Failed to run bisect integration tests.',
body=_TextBody(errors_list))
def _GetBisectConfig(job):
bisect_fyi_configs = stored_object.Get(_BISECT_FYI_CONFIGS_KEY)
for test_name, config in bisect_fyi_configs.iteritems():
if job.job_name == test_name:
return config
return {}