#!/usr/bin/env python # 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 mox import time import unittest import signal 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 def testRetryDecoratorSucceeds(self): """Tests that a wrapped function succeeds without retrying.""" @retry.retry(Exception) def succeed(): return True self.mox.StubOutWithMock(time, 'sleep') self.mox.ReplayAll() self.assertTrue(succeed()) def testRetryDecoratorFlakySucceeds(self): """Tests that a wrapped function can retry and succeed.""" delay_sec = 10 @retry.retry(Exception, delay_sec=delay_sec) def flaky_succeed(): if self._FLAKY_FLAG: return True self._FLAKY_FLAG = True raise Exception() self.mox.StubOutWithMock(time, 'sleep') time.sleep(mox.Func(lambda x: abs(x - delay_sec) <= .5 * delay_sec)) self.mox.ReplayAll() self.assertTrue(flaky_succeed()) def testRetryDecoratorFails(self): """Tests that a wrapped function retries til the timeout, then fails.""" delay_sec = 10 @retry.retry(Exception, delay_sec=delay_sec) def fail(): raise Exception() self.mox.StubOutWithMock(time, 'sleep') time.sleep(mox.Func(lambda x: abs(x - delay_sec) <= .5 * delay_sec)) self.mox.ReplayAll() 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.mox.StubOutWithMock(time, 'sleep') self.mox.ReplayAll() self.assertRaises(error.ControlFileNotFound, fail) 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.mox.ReplayAll() #self.assertEquals(None, fail()) 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.mox.ReplayAll() 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.mox.ReplayAll() 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.mox.ReplayAll() self.assertFalse(testFunc()) if __name__ == '__main__': unittest.main()