1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""
6Unit tests for decorators.py.
7"""
8
9# pylint: disable=W0613
10
11import time
12import traceback
13import unittest
14
15from devil.android import decorators
16from devil.android import device_errors
17from devil.utils import reraiser_thread
18
19_DEFAULT_TIMEOUT = 30
20_DEFAULT_RETRIES = 3
21
22
23class DecoratorsTest(unittest.TestCase):
24  _decorated_function_called_count = 0
25
26  def testFunctionDecoratorDoesTimeouts(self):
27    """Tests that the base decorator handles the timeout logic."""
28    DecoratorsTest._decorated_function_called_count = 0
29
30    @decorators.WithTimeoutAndRetries
31    def alwaysTimesOut(timeout=None, retries=None):
32      DecoratorsTest._decorated_function_called_count += 1
33      time.sleep(100)
34
35    start_time = time.time()
36    with self.assertRaises(device_errors.CommandTimeoutError):
37      alwaysTimesOut(timeout=1, retries=0)
38    elapsed_time = time.time() - start_time
39    self.assertTrue(elapsed_time >= 1)
40    self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
41
42  def testFunctionDecoratorDoesRetries(self):
43    """Tests that the base decorator handles the retries logic."""
44    DecoratorsTest._decorated_function_called_count = 0
45
46    @decorators.WithTimeoutAndRetries
47    def alwaysRaisesCommandFailedError(timeout=None, retries=None):
48      DecoratorsTest._decorated_function_called_count += 1
49      raise device_errors.CommandFailedError('testCommand failed')
50
51    with self.assertRaises(device_errors.CommandFailedError):
52      alwaysRaisesCommandFailedError(timeout=30, retries=10)
53    self.assertEquals(11, DecoratorsTest._decorated_function_called_count)
54
55  def testFunctionDecoratorRequiresParams(self):
56    """Tests that the base decorator requires timeout and retries params."""
57    @decorators.WithTimeoutAndRetries
58    def requiresExplicitTimeoutAndRetries(timeout=None, retries=None):
59      return (timeout, retries)
60
61    with self.assertRaises(KeyError):
62      requiresExplicitTimeoutAndRetries()
63    with self.assertRaises(KeyError):
64      requiresExplicitTimeoutAndRetries(timeout=10)
65    with self.assertRaises(KeyError):
66      requiresExplicitTimeoutAndRetries(retries=0)
67    expected_timeout = 10
68    expected_retries = 1
69    (actual_timeout, actual_retries) = (
70        requiresExplicitTimeoutAndRetries(timeout=expected_timeout,
71                                          retries=expected_retries))
72    self.assertEquals(expected_timeout, actual_timeout)
73    self.assertEquals(expected_retries, actual_retries)
74
75  def testFunctionDecoratorTranslatesReraiserExceptions(self):
76    """Tests that the explicit decorator translates reraiser exceptions."""
77    @decorators.WithTimeoutAndRetries
78    def alwaysRaisesProvidedException(exception, timeout=None, retries=None):
79      raise exception
80
81    exception_desc = 'Reraiser thread timeout error'
82    with self.assertRaises(device_errors.CommandTimeoutError) as e:
83      alwaysRaisesProvidedException(
84          reraiser_thread.TimeoutError(exception_desc),
85          timeout=10, retries=1)
86    self.assertEquals(exception_desc, str(e.exception))
87
88  def testConditionalRetriesDecoratorRetries(self):
89    def do_not_retry_no_adb_error(exc):
90      return not isinstance(exc, device_errors.NoAdbError)
91
92    actual_tries = [0]
93
94    @decorators.WithTimeoutAndConditionalRetries(do_not_retry_no_adb_error)
95    def alwaysRaisesCommandFailedError(timeout=None, retries=None):
96      actual_tries[0] += 1
97      raise device_errors.CommandFailedError('Command failed :(')
98
99    with self.assertRaises(device_errors.CommandFailedError):
100      alwaysRaisesCommandFailedError(timeout=10, retries=10)
101    self.assertEquals(11, actual_tries[0])
102
103  def testConditionalRetriesDecoratorDoesntRetry(self):
104    def do_not_retry_no_adb_error(exc):
105      return not isinstance(exc, device_errors.NoAdbError)
106
107    actual_tries = [0]
108
109    @decorators.WithTimeoutAndConditionalRetries(do_not_retry_no_adb_error)
110    def alwaysRaisesNoAdbError(timeout=None, retries=None):
111      actual_tries[0] += 1
112      raise device_errors.NoAdbError()
113
114    with self.assertRaises(device_errors.NoAdbError):
115      alwaysRaisesNoAdbError(timeout=10, retries=10)
116    self.assertEquals(1, actual_tries[0])
117
118  def testDefaultsFunctionDecoratorDoesTimeouts(self):
119    """Tests that the defaults decorator handles timeout logic."""
120    DecoratorsTest._decorated_function_called_count = 0
121
122    @decorators.WithTimeoutAndRetriesDefaults(1, 0)
123    def alwaysTimesOut(timeout=None, retries=None):
124      DecoratorsTest._decorated_function_called_count += 1
125      time.sleep(100)
126
127    start_time = time.time()
128    with self.assertRaises(device_errors.CommandTimeoutError):
129      alwaysTimesOut()
130    elapsed_time = time.time() - start_time
131    self.assertTrue(elapsed_time >= 1)
132    self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
133
134    DecoratorsTest._decorated_function_called_count = 0
135    with self.assertRaises(device_errors.CommandTimeoutError):
136      alwaysTimesOut(timeout=2)
137    elapsed_time = time.time() - start_time
138    self.assertTrue(elapsed_time >= 2)
139    self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
140
141  def testDefaultsFunctionDecoratorDoesRetries(self):
142    """Tests that the defaults decorator handles retries logic."""
143    DecoratorsTest._decorated_function_called_count = 0
144
145    @decorators.WithTimeoutAndRetriesDefaults(30, 10)
146    def alwaysRaisesCommandFailedError(timeout=None, retries=None):
147      DecoratorsTest._decorated_function_called_count += 1
148      raise device_errors.CommandFailedError('testCommand failed')
149
150    with self.assertRaises(device_errors.CommandFailedError):
151      alwaysRaisesCommandFailedError()
152    self.assertEquals(11, DecoratorsTest._decorated_function_called_count)
153
154    DecoratorsTest._decorated_function_called_count = 0
155    with self.assertRaises(device_errors.CommandFailedError):
156      alwaysRaisesCommandFailedError(retries=5)
157    self.assertEquals(6, DecoratorsTest._decorated_function_called_count)
158
159  def testDefaultsFunctionDecoratorPassesValues(self):
160    """Tests that the defaults decorator passes timeout and retries kwargs."""
161    @decorators.WithTimeoutAndRetriesDefaults(30, 10)
162    def alwaysReturnsTimeouts(timeout=None, retries=None):
163      return timeout
164
165    self.assertEquals(30, alwaysReturnsTimeouts())
166    self.assertEquals(120, alwaysReturnsTimeouts(timeout=120))
167
168    @decorators.WithTimeoutAndRetriesDefaults(30, 10)
169    def alwaysReturnsRetries(timeout=None, retries=None):
170      return retries
171
172    self.assertEquals(10, alwaysReturnsRetries())
173    self.assertEquals(1, alwaysReturnsRetries(retries=1))
174
175  def testDefaultsFunctionDecoratorTranslatesReraiserExceptions(self):
176    """Tests that the explicit decorator translates reraiser exceptions."""
177    @decorators.WithTimeoutAndRetriesDefaults(30, 10)
178    def alwaysRaisesProvidedException(exception, timeout=None, retries=None):
179      raise exception
180
181    exception_desc = 'Reraiser thread timeout error'
182    with self.assertRaises(device_errors.CommandTimeoutError) as e:
183      alwaysRaisesProvidedException(
184          reraiser_thread.TimeoutError(exception_desc))
185    self.assertEquals(exception_desc, str(e.exception))
186
187  def testExplicitFunctionDecoratorDoesTimeouts(self):
188    """Tests that the explicit decorator handles timeout logic."""
189    DecoratorsTest._decorated_function_called_count = 0
190
191    @decorators.WithExplicitTimeoutAndRetries(1, 0)
192    def alwaysTimesOut():
193      DecoratorsTest._decorated_function_called_count += 1
194      time.sleep(100)
195
196    start_time = time.time()
197    with self.assertRaises(device_errors.CommandTimeoutError):
198      alwaysTimesOut()
199    elapsed_time = time.time() - start_time
200    self.assertTrue(elapsed_time >= 1)
201    self.assertEquals(1, DecoratorsTest._decorated_function_called_count)
202
203  def testExplicitFunctionDecoratorDoesRetries(self):
204    """Tests that the explicit decorator handles retries logic."""
205    DecoratorsTest._decorated_function_called_count = 0
206
207    @decorators.WithExplicitTimeoutAndRetries(30, 10)
208    def alwaysRaisesCommandFailedError():
209      DecoratorsTest._decorated_function_called_count += 1
210      raise device_errors.CommandFailedError('testCommand failed')
211
212    with self.assertRaises(device_errors.CommandFailedError):
213      alwaysRaisesCommandFailedError()
214    self.assertEquals(11, DecoratorsTest._decorated_function_called_count)
215
216  def testExplicitDecoratorTranslatesReraiserExceptions(self):
217    """Tests that the explicit decorator translates reraiser exceptions."""
218    @decorators.WithExplicitTimeoutAndRetries(30, 10)
219    def alwaysRaisesProvidedException(exception):
220      raise exception
221
222    exception_desc = 'Reraiser thread timeout error'
223    with self.assertRaises(device_errors.CommandTimeoutError) as e:
224      alwaysRaisesProvidedException(
225          reraiser_thread.TimeoutError(exception_desc))
226    self.assertEquals(exception_desc, str(e.exception))
227
228  class _MethodDecoratorTestObject(object):
229    """An object suitable for testing the method decorator."""
230
231    def __init__(self, test_case, default_timeout=_DEFAULT_TIMEOUT,
232                 default_retries=_DEFAULT_RETRIES):
233      self._test_case = test_case
234      self.default_timeout = default_timeout
235      self.default_retries = default_retries
236      self.function_call_counters = {
237          'alwaysRaisesCommandFailedError': 0,
238          'alwaysTimesOut': 0,
239          'requiresExplicitTimeoutAndRetries': 0,
240      }
241
242    @decorators.WithTimeoutAndRetriesFromInstance(
243        'default_timeout', 'default_retries')
244    def alwaysTimesOut(self, timeout=None, retries=None):
245      self.function_call_counters['alwaysTimesOut'] += 1
246      time.sleep(100)
247      self._test_case.assertFalse(True, msg='Failed to time out?')
248
249    @decorators.WithTimeoutAndRetriesFromInstance(
250        'default_timeout', 'default_retries')
251    def alwaysRaisesCommandFailedError(self, timeout=None, retries=None):
252      self.function_call_counters['alwaysRaisesCommandFailedError'] += 1
253      raise device_errors.CommandFailedError('testCommand failed')
254
255    # pylint: disable=no-self-use
256
257    @decorators.WithTimeoutAndRetriesFromInstance(
258        'default_timeout', 'default_retries')
259    def alwaysReturnsTimeout(self, timeout=None, retries=None):
260      return timeout
261
262    @decorators.WithTimeoutAndRetriesFromInstance(
263        'default_timeout', 'default_retries', min_default_timeout=100)
264    def alwaysReturnsTimeoutWithMin(self, timeout=None, retries=None):
265      return timeout
266
267    @decorators.WithTimeoutAndRetriesFromInstance(
268        'default_timeout', 'default_retries')
269    def alwaysReturnsRetries(self, timeout=None, retries=None):
270      return retries
271
272    @decorators.WithTimeoutAndRetriesFromInstance(
273        'default_timeout', 'default_retries')
274    def alwaysRaisesProvidedException(self, exception, timeout=None,
275                                      retries=None):
276      raise exception
277
278    # pylint: enable=no-self-use
279
280  def testMethodDecoratorDoesTimeout(self):
281    """Tests that the method decorator handles timeout logic."""
282    test_obj = self._MethodDecoratorTestObject(self)
283    start_time = time.time()
284    with self.assertRaises(device_errors.CommandTimeoutError):
285      try:
286        test_obj.alwaysTimesOut(timeout=1, retries=0)
287      except:
288        traceback.print_exc()
289        raise
290    elapsed_time = time.time() - start_time
291    self.assertTrue(elapsed_time >= 1)
292    self.assertEquals(1, test_obj.function_call_counters['alwaysTimesOut'])
293
294  def testMethodDecoratorDoesRetries(self):
295    """Tests that the method decorator handles retries logic."""
296    test_obj = self._MethodDecoratorTestObject(self)
297    with self.assertRaises(device_errors.CommandFailedError):
298      try:
299        test_obj.alwaysRaisesCommandFailedError(retries=10)
300      except:
301        traceback.print_exc()
302        raise
303    self.assertEquals(
304        11, test_obj.function_call_counters['alwaysRaisesCommandFailedError'])
305
306  def testMethodDecoratorPassesValues(self):
307    """Tests that the method decorator passes timeout and retries kwargs."""
308    test_obj = self._MethodDecoratorTestObject(
309        self, default_timeout=42, default_retries=31)
310    self.assertEquals(42, test_obj.alwaysReturnsTimeout())
311    self.assertEquals(41, test_obj.alwaysReturnsTimeout(timeout=41))
312    self.assertEquals(31, test_obj.alwaysReturnsRetries())
313    self.assertEquals(32, test_obj.alwaysReturnsRetries(retries=32))
314
315  def testMethodDecoratorUsesMiniumumTimeout(self):
316    test_obj = self._MethodDecoratorTestObject(
317        self, default_timeout=42, default_retries=31)
318    self.assertEquals(100, test_obj.alwaysReturnsTimeoutWithMin())
319    self.assertEquals(41, test_obj.alwaysReturnsTimeoutWithMin(timeout=41))
320
321  def testMethodDecoratorTranslatesReraiserExceptions(self):
322    test_obj = self._MethodDecoratorTestObject(self)
323
324    exception_desc = 'Reraiser thread timeout error'
325    with self.assertRaises(device_errors.CommandTimeoutError) as e:
326      test_obj.alwaysRaisesProvidedException(
327          reraiser_thread.TimeoutError(exception_desc))
328    self.assertEquals(exception_desc, str(e.exception))
329
330if __name__ == '__main__':
331  unittest.main(verbosity=2)
332
333