普通文本  |  168行  |  4.91 KB

#!/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()