You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
167 lines
4.9 KiB
167 lines
4.9 KiB
7 months ago
|
#!/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()
|