retry_unittest.py revision db550114d3137db4fca4b9b6ae8a95ef38292aea
1#!/usr/bin/env python
2
3# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Unit tests for client/common_lib/cros/retry.py."""
8
9import mox
10import time
11import unittest
12import signal
13
14import common
15from autotest_lib.client.common_lib.cros import retry
16from autotest_lib.client.common_lib import error
17
18
19class RetryTest(mox.MoxTestBase):
20    """Unit tests for retry decorators.
21
22    @var _FLAKY_FLAG: for use in tests that need to simulate random failures.
23    """
24
25    _FLAKY_FLAG = None
26
27    def setUp(self):
28        super(RetryTest, self).setUp()
29        self._FLAKY_FLAG = False
30
31
32    def testRetryDecoratorSucceeds(self):
33        """Tests that a wrapped function succeeds without retrying."""
34        @retry.retry(Exception)
35        def succeed():
36            return True
37
38        self.mox.StubOutWithMock(time, 'sleep')
39        self.mox.ReplayAll()
40        self.assertTrue(succeed())
41
42
43    def testRetryDecoratorFlakySucceeds(self):
44        """Tests that a wrapped function can retry and succeed."""
45        delay_sec = 10
46        @retry.retry(Exception, delay_sec=delay_sec)
47        def flaky_succeed():
48            if self._FLAKY_FLAG:
49                return True
50            self._FLAKY_FLAG = True
51            raise Exception()
52
53        self.mox.StubOutWithMock(time, 'sleep')
54        time.sleep(mox.Func(lambda x: abs(x - delay_sec) <= .5 * delay_sec))
55        self.mox.ReplayAll()
56        self.assertTrue(flaky_succeed())
57
58
59    def testRetryDecoratorFails(self):
60        """Tests that a wrapped function retries til the timeout, then fails."""
61        delay_sec = 10
62        @retry.retry(Exception, delay_sec=delay_sec)
63        def fail():
64            raise Exception()
65
66        self.mox.StubOutWithMock(time, 'sleep')
67        time.sleep(mox.Func(lambda x: abs(x - delay_sec) <= .5 * delay_sec))
68        self.mox.ReplayAll()
69        self.assertRaises(Exception, fail)
70
71
72    def testRetryDecoratorRaisesCrosDynamicSuiteException(self):
73        """Tests that dynamic_suite exceptions raise immediately, no retry."""
74        @retry.retry(Exception)
75        def fail():
76            raise error.ControlFileNotFound()
77
78        self.mox.StubOutWithMock(time, 'sleep')
79        self.mox.ReplayAll()
80        self.assertRaises(error.ControlFileNotFound, fail)
81
82
83    def testRetryDecoratorFailsWithTimeout(self):
84        """Tests that a wrapped function retries til the timeout, then fails."""
85        @retry.retry(Exception, timeout_min=0.02, delay_sec=0.1)
86        def fail():
87            time.sleep(2)
88            return True
89
90        self.mox.ReplayAll()
91        #self.assertEquals(None, fail())
92        self.assertRaises(error.TimeoutException, fail)
93
94
95    def testRetryDecoratorSucceedsBeforeTimeout(self):
96        """Tests that a wrapped function succeeds before the timeout."""
97        @retry.retry(Exception, timeout_min=0.02, delay_sec=0.1)
98        def succeed():
99            time.sleep(0.1)
100            return True
101
102        self.mox.ReplayAll()
103        self.assertTrue(succeed())
104
105
106    def testRetryDecoratorSucceedsWithExistingSignal(self):
107        """Tests that a wrapped function succeeds before the timeout and
108        previous signal being restored."""
109        class TestTimeoutException(Exception):
110            pass
111
112        def testFunc():
113            @retry.retry(Exception, timeout_min=0.05, delay_sec=0.1)
114            def succeed():
115                time.sleep(0.1)
116                return True
117
118            succeed()
119            # Wait for 1.5 second for previous signal to be raised
120            time.sleep(1.5)
121
122        def testHandler(signum, frame):
123            """
124            Register a handler for the timeout.
125            """
126            raise TestTimeoutException('Expected timed out.')
127
128        signal.signal(signal.SIGALRM, testHandler)
129        signal.alarm(1)
130        self.mox.ReplayAll()
131        self.assertRaises(TestTimeoutException, testFunc)
132
133
134    def testRetryDecoratorWithNoAlarmLeak(self):
135        """Tests that a wrapped function throws exception before the timeout
136        and no signal is leaked."""
137        def testFunc():
138            @retry.retry(Exception, timeout_min=0.06, delay_sec=0.1)
139            def fail():
140                time.sleep(0.1)
141                raise Exception()
142
143
144            def testHandler(signum, frame):
145                """
146                Register a handler for the timeout.
147                """
148                self.alarm_leaked = True
149
150
151            # Set handler for signal.SIGALRM to catch any leaked alarm.
152            self.alarm_leaked = False
153            signal.signal(signal.SIGALRM, testHandler)
154            try:
155                fail()
156            except Exception:
157                pass
158            # Wait for 2 seconds to check if any alarm is leaked
159            time.sleep(2)
160            return self.alarm_leaked
161
162        self.mox.ReplayAll()
163        self.assertFalse(testFunc())
164
165
166    def testRetryExponentialDecoratorSucceedsSimpleBackoff(self):
167        """Test that a wrapped function succeeds without retrying."""
168        @retry.retry_exponential(Exception, backoff_factor=1)
169        def succeed():
170            return True
171
172        self.mox.StubOutWithMock(time, 'sleep')
173        self.mox.ReplayAll()
174        self.assertTrue(succeed())
175
176
177    def testRetryExponentialDecoratorFlakySucceedsSimpleBackoff(self):
178        """Tests that a wrapped function can retry and succeed."""
179        max_retry = 6
180        delay_sec = 5
181        @retry.retry_exponential(Exception, delay_sec=delay_sec,
182                                 backoff_factor=1)
183        def flaky_succeed():
184            if self._FLAKY_FLAG:
185                return True
186            self._FLAKY_FLAG = True
187            raise Exception()
188
189        self.mox.StubOutWithMock(time, 'sleep')
190        time.sleep(mox.Func(lambda x: x < delay_sec*sum(range(max_retry))))
191        self.mox.ReplayAll()
192        self.assertTrue(flaky_succeed())
193
194
195    def testRetryExponentialDecoratorFailsSimpleBackoff(self):
196        """Tests that a wrapped function retries til the timeout then fails."""
197        max_retry = 6
198        delay_sec = 5
199        @retry.retry_exponential(Exception, delay_sec=delay_sec,
200                                 backoff_factor=1)
201        def fail():
202            raise Exception()
203
204        self.mox.StubOutWithMock(time, 'sleep')
205        time.sleep(mox.Func(lambda x: x < delay_sec*sum(range(max_retry))))
206        self.mox.ReplayAll()
207        self.assertRaises(Exception, fail)
208
209
210    def testRetryExponentialDecoratorRaisesBlacklist(self):
211        """Tests that a blacklisted exception is raised."""
212        max_retry = 6
213        delay_sec = 5
214        @retry.retry_exponential(Exception, delay_sec=delay_sec,
215                                 blacklist=[ValueError])
216        def fail():
217            raise ValueError()
218
219        self.mox.StubOutWithMock(time, 'sleep')
220        self.mox.ReplayAll()
221        self.assertRaises(ValueError, fail)
222
223
224    def testRetryExponentialDecoratorSucceedsBeforeTimeout(self):
225        """Tests that a wrapped function succeeds before the timeout."""
226        @retry.retry_exponential(Exception, timeout_min=0.02, delay_sec=0.1)
227        def succeed():
228            time.sleep(0.1)
229            return True
230
231        self.mox.ReplayAll()
232        self.assertTrue(succeed())
233
234
235    def testRetryExponentialDecoratorSucceedBadBackoff(self):
236        """Test that the retry function corrects a bad backoff factor."""
237        @retry.retry_exponential(Exception, backoff_factor=0.001)
238        def succeed():
239            return True
240
241        self.mox.StubOutWithMock(time, 'sleep')
242        self.mox.ReplayAll()
243        self.assertTrue(succeed())
244
245
246    def testRetryExponentialDecoratorSucceedsExponentialBackoff(self):
247        """Test that a wrapped function succeeds without retrying."""
248        @retry.retry_exponential(Exception)
249        def succeed():
250            return True
251
252        self.mox.StubOutWithMock(time, 'sleep')
253        self.mox.ReplayAll()
254        self.assertTrue(succeed())
255
256
257    def testRetryExponentialDecoratorFlakySucceedsExponentialBackoff(self):
258        """Test that wrapped function succeeds with exponential backoff."""
259        delay_sec = 2
260        backoff_factor = 2
261        attempt = 2
262        @retry.retry_exponential(Exception, delay_sec=delay_sec,
263                                 backoff_factor=backoff_factor)
264        def flaky_succeed():
265            if self._FLAKY_FLAG:
266                return True
267            self._FLAKY_FLAG = True
268            raise Exception()
269
270        self.mox.StubOutWithMock(time, 'sleep')
271        time.sleep(
272                mox.Func(lambda x: x < delay_sec*backoff_factor**(attempt-1)))
273        self.mox.ReplayAll()
274        self.assertTrue(flaky_succeed())
275
276
277    def testRetryExponentialDecoratorFailsSimpleBackoff(self):
278        """Tests that a wrapped function retries til the timeout then fails."""
279        max_retry = 6
280        delay_sec = 5
281        @retry.retry_exponential(Exception, delay_sec=delay_sec)
282        def fail():
283            raise Exception()
284
285        self.mox.StubOutWithMock(time, 'sleep')
286        time.sleep(mox.Func(lambda x: x < delay_sec*sum(range(max_retry))))
287        self.mox.ReplayAll()
288        self.assertRaises(Exception, fail)
289
290
291if __name__ == '__main__':
292    unittest.main()
293