blob: 5cabeb70e0fa1d149197b6433f22782620e11d93 [file] [log] [blame]
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Fake CIDB for unit testing."""
from __future__ import print_function
import datetime
import itertools
from chromite.cbuildbot import constants
from chromite.lib import clactions
class FakeCIDBConnection(object):
"""Fake connection to a Continuous Integration database.
This class is a partial re-implementation of CIDBConnection, using
in-memory lists rather than a backing database.
"""
NUM_RESULTS_NO_LIMIT = -1
def __init__(self, fake_keyvals=None):
self.buildTable = []
self.clActionTable = []
self.buildStageTable = {}
self.failureTable = {}
self.fake_time = None
self.fake_keyvals = fake_keyvals or {}
def SetTime(self, fake_time):
"""Sets a fake time to be retrieved by GetTime.
Args:
fake_time: datetime.datetime object.
"""
self.fake_time = fake_time
def GetTime(self):
"""Gets the current database time."""
return self.fake_time or datetime.datetime.now()
def InsertBuild(self, builder_name, waterfall, build_number,
build_config, bot_hostname, master_build_id=None,
timeout_seconds=None, status=constants.BUILDER_STATUS_PASSED,
important=None):
"""Insert a build row.
Note this API slightly differs from cidb as we pass status to avoid having
to have a later FinishBuild call in testing.
"""
deadline = None
if timeout_seconds is not None:
timediff = datetime.timedelta(seconds=timeout_seconds)
deadline = datetime.datetime.now() + timediff
build_id = len(self.buildTable)
row = {'id': build_id,
'builder_name': builder_name,
'buildbot_generation': constants.BUILDBOT_GENERATION,
'waterfall': waterfall,
'build_number': build_number,
'build_config' : build_config,
'bot_hostname': bot_hostname,
'start_time': datetime.datetime.now(),
'master_build_id' : master_build_id,
'deadline': deadline,
'status': status,
'finish_time': datetime.datetime.now(),
'important': important}
self.buildTable.append(row)
return build_id
def UpdateMetadata(self, build_id, metadata):
"""See cidb.UpdateMetadata."""
d = metadata.GetDict()
versions = d.get('version') or {}
self.buildTable[build_id - 1].update(
{'chrome_version': versions.get('chrome'),
'milestone_version': versions.get('milestone'),
'platform_version': versions.get('platform'),
'full_version': versions.get('full'),
'sdk_version': d.get('sdk-versions'),
'toolchain_url': d.get('toolchain-url'),
'build_type': d.get('build_type'),
'important': d.get('important')})
return 1
def InsertCLActions(self, build_id, cl_actions, timestamp=None):
"""Insert a list of |cl_actions|."""
if not cl_actions:
return 0
rows = []
for cl_action in cl_actions:
change_number = int(cl_action.change_number)
patch_number = int(cl_action.patch_number)
change_source = cl_action.change_source
action = cl_action.action
reason = cl_action.reason
rows.append({
'build_id' : build_id,
'change_source' : change_source,
'change_number': change_number,
'patch_number' : patch_number,
'action' : action,
'timestamp': timestamp or datetime.datetime.now(),
'reason' : reason})
self.clActionTable.extend(rows)
return len(rows)
def InsertBuildStage(self, build_id, name, board=None,
status=constants.BUILDER_STATUS_PLANNED):
build_stage_id = len(self.buildStageTable)
row = {'build_id': build_id,
'name': name,
'board': board,
'status': status}
self.buildStageTable[build_stage_id] = row
return build_stage_id
def InsertBoardPerBuild(self, build_id, board):
# TODO(akeshet): Fill this placeholder.
pass
def InsertFailure(self, build_stage_id, exception_type, exception_message,
exception_category=constants.EXCEPTION_CATEGORY_UNKNOWN,
outer_failure_id=None,
extra_info=None):
failure_id = len(self.failureTable)
values = {'build_stage_id': build_stage_id,
'exception_type': exception_type,
'exception_message': exception_message,
'exception_category': exception_category,
'outer_failure_id': outer_failure_id,
'extra_info': extra_info}
self.failureTable[failure_id] = values
return failure_id
def StartBuildStage(self, build_stage_id):
if build_stage_id > len(self.buildStageTable):
return
self.buildStageTable[build_stage_id]['status'] = (
constants.BUILDER_STATUS_INFLIGHT)
def ExtendDeadline(self, build_id, timeout):
# No sanity checking in fake object.
now = datetime.datetime.now()
timediff = datetime.timedelta(seconds=timeout)
self.buildStageTable[build_id]['deadline'] = now + timediff
def FinishBuildStage(self, build_stage_id, status):
if build_stage_id > len(self.buildStageTable):
return
self.buildStageTable[build_stage_id]['status'] = status
def GetActionsForChanges(self, changes):
"""Gets all the actions for the given changes."""
clauses = set()
for change in changes:
change_source = 'internal' if change.internal else 'external'
clauses.add((int(change.gerrit_number), change_source))
values = []
for row in self.GetActionHistory():
if (row.change_number, row.change_source) in clauses:
values.append(row)
return values
def GetActionHistory(self, *args, **kwargs):
"""Get all the actions for all changes."""
# pylint: disable=W0613
values = []
for item, action_id in zip(self.clActionTable, itertools.count()):
row = (
action_id,
item['build_id'],
item['action'],
item['reason'],
self.buildTable[item['build_id']]['build_config'],
item['change_number'],
item['patch_number'],
item['change_source'],
item['timestamp'])
values.append(row)
return clactions.CLActionHistory(clactions.CLAction(*row) for row in values)
def GetBuildStatus(self, build_id):
"""Gets the status of the build."""
return self.buildTable[build_id - 1]
def GetBuildStatuses(self, build_ids):
"""Gets the status of the builds."""
return [self.buildTable[x -1] for x in build_ids]
def GetSlaveStatuses(self, master_build_id):
"""Gets the slaves of given build."""
return [b for b in self.buildTable
if b['master_build_id'] == master_build_id]
def GetBuildHistory(self, build_config, num_results,
ignore_build_id=None, start_date=None, end_date=None,
starting_build_number=None):
"""Returns the build history for the given |build_config|."""
def ReduceToBuildConfig(new_list, current_build):
"""Filters a build list to only those of a given config."""
if current_build['build_config'] == build_config:
new_list.append(current_build)
return new_list
build_configs = reduce(ReduceToBuildConfig, self.buildTable, [])
# Reverse sort as that's what's expected.
build_configs = sorted(build_configs[-num_results:], reverse=True)
# Filter results.
if ignore_build_id is not None:
build_configs = [b for b in build_configs if b['id'] != ignore_build_id]
if start_date is not None:
build_configs = [b for b in build_configs
if b['start_time'].date() >= start_date]
if end_date is not None:
build_configs = [b for b in build_configs
if 'finish_time' in b and
b['finish_time'] and
b['finish_time'].date() <= end_date]
if starting_build_number is not None:
build_configs = [b for b in build_configs
if b['build_number'] >= starting_build_number]
return build_configs
def GetTimeToDeadline(self, build_id):
"""Gets the time remaining until deadline."""
now = datetime.datetime.now()
deadline = self.buildTable[build_id]['deadline']
return max(0, (deadline - now).total_seconds())
def GetKeyVals(self):
"""Gets contents of keyvalTable."""
return self.fake_keyvals