blob: 23f790038c3ea99fb488d976700524b5ec976f64 [file] [log] [blame]
#!/usr/bin/env vpython
# Copyright (c) 2012 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.
""" Source file for floating builder testcases."""
import calendar
import datetime
import itertools
import os
import time
import unittest
import mock
import test_env # pylint: disable=relative-import
from master import floating_builder as fb
def _to_timestamp(dt):
# Calculate the offset between local timezone and UTC.
current_time = time.mktime(dt.timetuple())
offset = (datetime.datetime.fromtimestamp(current_time) -
datetime.datetime.utcfromtimestamp(current_time))
return calendar.timegm((dt - offset).timetuple())
class _FakeSlaveStatus(object):
def __init__(self, name):
self.name = name
self.connect_times = []
self.last_message_received = None
def lastMessageReceived(self):
return self.last_message_received
class _FakeSlave(object):
def __init__(self, slavename):
self.slavename = slavename
self.slave_status = None
self.offline = False
def _set_last_seen(self, now, **kwargs):
td = datetime.timedelta(**kwargs)
self.slave_status = _FakeSlaveStatus(self.slavename)
self.slave_status.last_message_received = _to_timestamp(now + td)
def __str__(self):
return self.slavename
class _FakeBuilder(object):
def __init__(self, name, slaves):
self.name = name
self._all_slaves = slaves
self.botmaster = mock.MagicMock()
self.builder_status = mock.MagicMock()
self.builder_status.getSlaves.side_effect = lambda: [
s.slave_status for s in self._all_slaves
if s.slave_status]
self._online_slaves = ()
self._busy_slaves = ()
def __repr__(self):
return self.name
@property
def slaves(self):
return [_FakeSlaveBuilder(s, self)
for s in self._all_slaves
if s.slavename in self._online_slaves]
@property
def slavebuilders(self):
"""Returns the list of slavebuilders that would be handed to
NextSlaveFunc.
This is the set of slaves that are available for scheduling. We derive
this by returning all slaves that are both online and not busy.
"""
return self._get_slave_builders(lambda s:
s.slavename in self._online_slaves and
s.slavename not in self._busy_slaves)
def _get_slave_builders(self, fn):
return [_FakeSlaveBuilder(slave, self)
for slave in self._all_slaves
if fn(slave)]
def set_online_slaves(self, *slavenames):
self._online_slaves = set(slavenames)
def set_busy_slaves(self, *slavenames):
self._busy_slaves = set(slavenames)
class _FakeSlaveBuilder(object):
def __init__(self, slave, builder):
self.slave = slave
self.builder = builder
def __repr__(self):
return '{%s/%s}' % (self.builder.name, self.slave.slavename)
class FloatingBuilderTest(unittest.TestCase):
def setUp(self):
self._mocks = (
mock.patch('master.floating_builder._get_now'),
mock.patch('master.floating_builder.PokeBuilderTimer.reset'),
)
for patcher in self._mocks:
patcher.start()
# Mock current date/time.
self.now = datetime.datetime(2016, 1, 1, 8, 0, 0) # 1/1/2016 @8:00
fb._get_now.side_effect = lambda: self.now
# Mock PokeBuilderTimer to record when the poke builder was set, but not
# actually schedule any reactor magic.
self.poke_delta = None
def record_poke_delta(delta):
self.poke_delta = delta
fb.PokeBuilderTimer.reset.side_effect = record_poke_delta
self._slaves = dict((s, _FakeSlave(s)) for s in (
'primary-a', 'primary-b', 'floating-a', 'floating-b',
))
self.builder = _FakeBuilder(
'Test Builder',
[s[1] for s in sorted(self._slaves.iteritems())],
)
def tearDown(self):
for patcher in reversed(self._mocks):
patcher.stop()
def testJustStartedNoPrimariesOnlineWaits(self):
fs = fb.FloatingSet()
fs.AddPrimary('primary-a')
fs.AddFloating('floating-a', 'floating-b')
fnsf = fs.NextSlaveFunc(datetime.timedelta(seconds=10))
self.builder.set_online_slaves('floating-a', 'floating-b')
nsb = fnsf(self.builder, self.builder.slavebuilders)
self.assertIsNone(nsb)
self.assertEqual(self.poke_delta, datetime.timedelta(seconds=10))
self.now += datetime.timedelta(seconds=11)
nsb = fnsf(self.builder, self.builder.slavebuilders)
self.assertIsNotNone(nsb)
self.assertEqual(nsb.slave.slavename, 'floating-a')
def testPrimaryBuilderIsSelectedWhenAvailable(self):
fs = fb.FloatingSet()
fs.AddPrimary('primary-a')
fs.AddFloating('floating-a', 'floating-b')
fnsf = fs.NextSlaveFunc(datetime.timedelta(seconds=10))
self.builder.set_online_slaves('primary-a', 'floating-a', 'floating-b')
nsb = fnsf(self.builder, self.builder.slavebuilders)
self.assertIsNotNone(nsb)
self.assertEqual(nsb.slave.slavename, 'primary-a')
def testPrimaryBuilderIsSelectedWhenOneIsAvailableAndOneIsBusy(self):
fs = fb.FloatingSet()
fs.AddPrimary('primary-a', 'primary-b')
fs.AddFloating('floating-a', 'floating-b')
fnsf = fs.NextSlaveFunc(datetime.timedelta(seconds=10))
self.builder.set_online_slaves('primary-a', 'primary-b', 'floating-a',
'floating-b')
self.builder.set_busy_slaves('primary-a')
nsb = fnsf(self.builder, self.builder.slavebuilders)
self.assertIsNotNone(nsb)
self.assertEqual(nsb.slave.slavename, 'primary-b')
def testNoBuilderIsSelectedWhenPrimariesAreOfflineWithinGrace(self):
fs = fb.FloatingSet()
fs.AddPrimary('primary-a', 'primary-b')
fs.AddFloating('floating-a', 'floating-b')
fnsf = fs.NextSlaveFunc(datetime.timedelta(seconds=10))
self.now += datetime.timedelta(seconds=30)
self.builder.set_online_slaves('floating-a')
self._slaves['primary-b']._set_last_seen(self.now, seconds=-1)
nsb = fnsf(self.builder, self.builder.slavebuilders)
self.assertIsNone(nsb)
self.assertEqual(self.poke_delta, datetime.timedelta(seconds=9))
def testFloatingBuilderIsSelectedWhenPrimariesAreOfflineForAWhile(self):
fs = fb.FloatingSet()
fs.AddPrimary('primary-a', 'primary-b')
fs.AddFloating('floating-a', 'floating-b')
fnsf = fs.NextSlaveFunc(datetime.timedelta(seconds=10))
self.now += datetime.timedelta(seconds=30)
self.builder.set_online_slaves('floating-a')
nsb = fnsf(self.builder, self.builder.slavebuilders)
self.assertIsNotNone(nsb)
self.assertEqual(nsb.slave.slavename, 'floating-a')
if __name__ == '__main__':
unittest.main()