blob: 0282102d1c88dc9aa9a676865e32cb662c6a7198 [file] [log] [blame]
#!/usr/bin/env python2
# Copyright (c) 2012 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.
"""Unit tests for client/common_lib/cros/retry.py."""
import itertools
import mox
import time
import unittest
import signal
import mock
import common
from autotest_lib.client.common_lib.cros import retry
from autotest_lib.client.common_lib import error
class RetryTest(mox.MoxTestBase):
"""Unit tests for retry decorators.
@var _FLAKY_FLAG: for use in tests that need to simulate random failures.
"""
_FLAKY_FLAG = None
def setUp(self):
super(RetryTest, self).setUp()
self._FLAKY_FLAG = False
patcher = mock.patch('time.sleep', autospec=True)
self._sleep_mock = patcher.start()
self.addCleanup(patcher.stop)
patcher = mock.patch('time.time', autospec=True)
self._time_mock = patcher.start()
self.addCleanup(patcher.stop)
def testRetryDecoratorSucceeds(self):
"""Tests that a wrapped function succeeds without retrying."""
@retry.retry(Exception)
def succeed():
return True
self.assertTrue(succeed())
self.assertFalse(self._sleep_mock.called)
def testRetryDecoratorFlakySucceeds(self):
"""Tests that a wrapped function can retry and succeed."""
delay_sec = 10
self._time_mock.side_effect = itertools.count(delay_sec)
@retry.retry(Exception, delay_sec=delay_sec)
def flaky_succeed():
if self._FLAKY_FLAG:
return True
self._FLAKY_FLAG = True
raise Exception()
self.assertTrue(flaky_succeed())
def testRetryDecoratorFails(self):
"""Tests that a wrapped function retries til the timeout, then fails."""
delay_sec = 10
self._time_mock.side_effect = itertools.count(delay_sec)
@retry.retry(Exception, delay_sec=delay_sec)
def fail():
raise Exception()
self.assertRaises(Exception, fail)
def testRetryDecoratorRaisesCrosDynamicSuiteException(self):
"""Tests that dynamic_suite exceptions raise immediately, no retry."""
@retry.retry(Exception)
def fail():
raise error.ControlFileNotFound()
self.assertRaises(error.ControlFileNotFound, fail)
class ActualRetryTest(unittest.TestCase):
"""Unit tests for retry decorators with real sleep."""
def testRetryDecoratorFailsWithTimeout(self):
"""Tests that a wrapped function retries til the timeout, then fails."""
@retry.retry(Exception, timeout_min=0.02, delay_sec=0.1)
def fail():
time.sleep(2)
return True
self.assertRaises(error.TimeoutException, fail)
def testRetryDecoratorSucceedsBeforeTimeout(self):
"""Tests that a wrapped function succeeds before the timeout."""
@retry.retry(Exception, timeout_min=0.02, delay_sec=0.1)
def succeed():
time.sleep(0.1)
return True
self.assertTrue(succeed())
def testRetryDecoratorSucceedsWithExistingSignal(self):
"""Tests that a wrapped function succeeds before the timeout and
previous signal being restored."""
class TestTimeoutException(Exception):
pass
def testFunc():
@retry.retry(Exception, timeout_min=0.05, delay_sec=0.1)
def succeed():
time.sleep(0.1)
return True
succeed()
# Wait for 1.5 second for previous signal to be raised
time.sleep(1.5)
def testHandler(signum, frame):
"""
Register a handler for the timeout.
"""
raise TestTimeoutException('Expected timed out.')
signal.signal(signal.SIGALRM, testHandler)
signal.alarm(1)
self.assertRaises(TestTimeoutException, testFunc)
def testRetryDecoratorWithNoAlarmLeak(self):
"""Tests that a wrapped function throws exception before the timeout
and no signal is leaked."""
def testFunc():
@retry.retry(Exception, timeout_min=0.06, delay_sec=0.1)
def fail():
time.sleep(0.1)
raise Exception()
def testHandler(signum, frame):
"""
Register a handler for the timeout.
"""
self.alarm_leaked = True
# Set handler for signal.SIGALRM to catch any leaked alarm.
self.alarm_leaked = False
signal.signal(signal.SIGALRM, testHandler)
try:
fail()
except Exception:
pass
# Wait for 2 seconds to check if any alarm is leaked
time.sleep(2)
return self.alarm_leaked
self.assertFalse(testFunc())
if __name__ == '__main__':
unittest.main()