Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / test.py @ 9af3a427

History | View | Annotate | Download (12.8 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from mock import patch, call
35
from unittest import makeSuite, TestSuite, TextTestRunner, TestCase
36
from time import sleep
37
from inspect import getmembers, isclass
38
from itertools import product
39
from random import randint
40

    
41
from kamaki.clients.connection.test import (
42
    KamakiConnection,
43
    KamakiHTTPConnection,
44
    KamakiResponse,
45
    KamakiHTTPResponse)
46
from kamaki.clients.utils.test import Utils
47
from kamaki.clients.astakos.test import Astakos
48
from kamaki.clients.compute.test import Compute, ComputeRest
49
from kamaki.clients.cyclades.test import Cyclades, CycladesRest
50
from kamaki.clients.image.test import Image
51
from kamaki.clients.storage.test import Storage
52
from kamaki.clients.pithos.test import Pithos, PithosRest
53

    
54

    
55
class ClientError(TestCase):
56

    
57
    def test___init__(self):
58
        from kamaki.clients import ClientError
59
        for msg, status, details, exp_msg, exp_status, exp_details in (
60
                ('some msg', 42, 0.28, 0, 0, 0),
61
                ('some msg', 'fail', [], 0, 0, 0),
62
                ('some msg', 42, 'details on error', 0, 0, 0),
63
                (
64
                    '404 {"ExampleError":'
65
                    ' {"message": "a msg", "code": 42, "details": "dets"}}',
66
                    404,
67
                    0,
68
                    '404 ExampleError (a msg)\n',
69
                    42,
70
                    ['dets']),
71
                (
72
                    '404 {"ExampleError":'
73
                    ' {"message": "a msg", "code": 42}}',
74
                    404,
75
                    'details on error',
76
                    '404 ExampleError (a msg)\n',
77
                    42,
78
                    0),
79
                (
80
                    '404 {"ExampleError":'
81
                    ' {"details": "Explain your error"}}',
82
                    404,
83
                    'details on error',
84
                    '404 ExampleError',
85
                    0,
86
                    ['details on error', 'Explain your error']),
87
                ('some msg\n', -10, ['details', 'on', 'error'], 0, 0, 0)):
88
            ce = ClientError(msg, status, details)
89
            exp_msg = exp_msg or (msg if msg.endswith('\n') else msg + '\n')
90
            exp_status = exp_status or status
91
            exp_details = exp_details or details
92
            self.assertEqual('%s' % ce, exp_msg)
93
            self.assertEqual(
94
                exp_status if isinstance(exp_status, int) else 0,
95
                ce.status)
96
            self.assertEqual(exp_details, ce.details)
97

    
98

    
99
class SilentEvent(TestCase):
100

    
101
    def thread_content(self, methodid, raiseException=0):
102
        wait = 0.1
103
        self.can_finish = -1
104
        while self.can_finish < methodid and wait < 4:
105
            sleep(wait)
106
            wait = 2 * wait
107
        if raiseException and raiseException == methodid:
108
            raise Exception('Some exception')
109
        self._value = methodid
110
        self.assertTrue(wait < 4)
111

    
112
    def setUp(self):
113
        from kamaki.clients import SilentEvent
114
        self.SE = SilentEvent
115

    
116
    def test_run(self):
117
        threads = [self.SE(self.thread_content, i) for i in range(4)]
118
        for t in threads:
119
            t.start()
120

    
121
        for i in range(4):
122
            self.assertTrue(threads[i].is_alive())
123
            self.can_finish = i
124
            threads[i].join()
125
            self.assertFalse(threads[i].is_alive())
126

    
127
    def test_value(self):
128
        threads = [self.SE(self.thread_content, i) for i in range(4)]
129
        for t in threads:
130
            t.start()
131

    
132
        for mid, t in enumerate(threads):
133
            if t.is_alive():
134
                self.can_finish = mid
135
                continue
136
            self.assertTrue(mid, t.value)
137

    
138
    def test_exception(self):
139
        threads = [self.SE(self.thread_content, i, (i % 2)) for i in range(4)]
140
        for t in threads:
141
            t.start()
142

    
143
        for i, t in enumerate(threads):
144
            if t.is_alive():
145
                self.can_finish = i
146
                continue
147
            if i % 2:
148
                self.assertTrue(isinstance(t.exception, Exception))
149
            else:
150
                self.assertFalse(t.exception)
151

    
152

    
153
class FakeConnection(object):
154
    """A fake Connection class"""
155

    
156
    def __init__(self):
157
        pass
158

    
159
    def set_header(self, name, value):
160
        pass
161

    
162
    def set_param(self, name, value):
163
        pass
164

    
165

    
166
class FR(object):
167
    json = None
168
    text = None
169
    headers = {}
170
    content = json
171
    status = None
172
    status_code = 200
173

    
174
    def release(self):
175
        pass
176

    
177

    
178
class Client(TestCase):
179

    
180
    def assert_dicts_are_equal(self, d1, d2):
181
        for k, v in d1.items():
182
            self.assertTrue(k in d2)
183
            if isinstance(v, dict):
184
                self.assert_dicts_are_equal(v, d2[k])
185
            else:
186
                self.assertEqual(unicode(v), unicode(d2[k]))
187

    
188
    def setUp(self):
189
        from kamaki.clients import Client
190
        from kamaki.clients import ClientError as CE
191
        self.base_url = 'http://example.com'
192
        self.token = 's0m370k3n=='
193
        self.client = Client(self.base_url, self.token, FakeConnection())
194
        self.CE = CE
195

    
196
    def tearDown(self):
197
        FR.text = None
198
        FR.status = None
199
        FR.status_code = 200
200

    
201
    def test___init__(self):
202
        self.assertEqual(self.client.base_url, self.base_url)
203
        self.assertEqual(self.client.token, self.token)
204
        self.assert_dicts_are_equal(self.client.headers, {})
205
        DATE_FORMATS = [
206
            '%a %b %d %H:%M:%S %Y',
207
            '%A, %d-%b-%y %H:%M:%S GMT',
208
            '%a, %d %b %Y %H:%M:%S GMT']
209
        self.assertEqual(self.client.DATE_FORMATS, DATE_FORMATS)
210
        self.assertTrue(isinstance(self.client.http_client, FakeConnection))
211

    
212
    def test__init_thread_limit(self):
213
        exp = 'Nothing set here'
214
        for faulty in (-1, 0.5, 'a string', {}):
215
            self.assertRaises(
216
                AssertionError,
217
                self.client._init_thread_limit,
218
                faulty)
219
            self.assertEqual(exp, getattr(self.client, '_thread_limit', exp))
220
            self.assertEqual(exp, getattr(self.client, '_elapsed_old', exp))
221
            self.assertEqual(exp, getattr(self.client, '_elapsed_new', exp))
222
        self.client._init_thread_limit(42)
223
        self.assertEqual(42, self.client._thread_limit)
224
        self.assertEqual(0.0, self.client._elapsed_old)
225
        self.assertEqual(0.0, self.client._elapsed_new)
226

    
227
    def test__watch_thread_limit(self):
228
        waits = (
229
            dict(args=((0.1, 1), (0.1, 2), (0.2, 1), (0.7, 1), (0.3, 2))),
230
            dict(args=((1.0 - (i / 10.0), (i + 1)) for i in range(7))),
231
            dict(max=1, args=tuple([(randint(1, 10) / 3.0, 1), ] * 10)),
232
            dict(
233
                limit=5,
234
                args=tuple([
235
                    (1.0 + (i / 10.0), (5 - i - 1)) for i in range(4)] + [
236
                    (2.0, 1), (1.9, 2), (2.0, 1), (2.0, 2)])),
237
            dict(args=tuple(
238
                [(1.0 - (i / 10.0), (i + 1)) for i in range(7)] + [
239
                (0.1, 7), (0.2, 6), (0.4, 5), (0.3, 6), (0.2, 7), (0.1, 7)])),)
240
        for wait_dict in waits:
241
            if 'max' in wait_dict:
242
                self.client.MAX_THREADS = wait_dict['max']
243
            else:
244
                self.client.MAX_THREADS = 7
245
            if 'limit' in wait_dict:
246
                self.client._init_thread_limit(wait_dict['limit'])
247
            else:
248
                self.client._init_thread_limit()
249
                self.client._watch_thread_limit(list())
250
                self.assertEqual(1, self.client._thread_limit)
251
            for wait, exp_limit in wait_dict['args']:
252
                self.client._elapsed_new = wait
253
                self.client._watch_thread_limit(list())
254
                self.assertEqual(exp_limit, self.client._thread_limit)
255

    
256
    def test__raise_for_status(self):
257
        r = FR()
258
        for txt, sts_code, sts in (('err msg', 10, None), ('', 42, 'Err St')):
259
            r.text, r.status_code, r.status = txt, sts_code, sts
260
            try:
261
                self.client._raise_for_status(r)
262
            except self.CE as ce:
263
                self.assertEqual('%s' % ce, '%s %s\n' % (sts or '', txt))
264
                self.assertEqual(ce.status, sts_code)
265

    
266
        for msg, sts_code in (('err msg', 32), ('', 42), ('an err', None)):
267
            err = self.CE(msg, sts_code) if sts_code else Exception(msg)
268
            try:
269
                self.client._raise_for_status(err)
270
            except self.CE as ce:
271
                self.assertEqual('%s' % ce, '%s %s\n' % (sts_code or '', msg))
272
                self.assertEqual(ce.status, sts_code or 0)
273

    
274
    @patch('%s.FakeConnection.set_header' % __name__)
275
    def test_set_header(self, SH):
276
        num_of_calls = 0
277
        for name, value, condition in product(
278
                ('n4m3', '', None),
279
                ('v41u3', None, 42),
280
                (True, False, None, 1, '')):
281
            self.client.set_header(name, value, iff=condition)
282
            if value is not None and condition:
283
                self.assertEqual(SH.mock_calls[-1], call(name, value))
284
                num_of_calls += 1
285
            else:
286
                self.assertEqual(num_of_calls, len(SH.mock_calls))
287

    
288
    @patch('%s.FakeConnection.set_param' % __name__)
289
    def test_set_param(self, SP):
290
        num_of_calls = 0
291
        for name, value, condition in product(
292
                ('n4m3', '', None),
293
                ('v41u3', None, 42),
294
                (True, False, None, 1, '')):
295
            self.client.set_param(name, value, iff=condition)
296
            if condition:
297
                self.assertEqual(SP.mock_calls[-1], call(name, value))
298
                num_of_calls += 1
299
            else:
300
                self.assertEqual(num_of_calls, len(SP.mock_calls))
301

    
302

    
303
#  TestCase auxiliary methods
304

    
305
def runTestCase(cls, test_name, args=[], failure_collector=[]):
306
    """
307
    :param cls: (TestCase) a set of Tests
308

309
    :param test_name: (str)
310

311
    :param args: (list) these are prefixed with test_ and used as params when
312
        instantiating cls
313

314
    :param failure_collector: (list) collects info of test failures
315

316
    :returns: (int) total # of run tests
317
    """
318
    suite = TestSuite()
319
    if args:
320
        suite.addTest(cls('_'.join(['test'] + args)))
321
    else:
322
        suite.addTest(makeSuite(cls))
323
    print('* Test * %s *' % test_name)
324
    r = TextTestRunner(verbosity=2).run(suite)
325
    failure_collector += r.failures
326
    return r.testsRun
327

    
328

    
329
def _add_value(foo, value):
330
    def wrap(self):
331
        return foo(self, value)
332
    return wrap
333

    
334

    
335
def get_test_classes(module=__import__(__name__), name=''):
336
    module_stack = [module]
337
    while module_stack:
338
        module = module_stack[-1]
339
        module_stack = module_stack[:-1]
340
        for objname, obj in getmembers(module):
341
            if (objname == name or not name):
342
                if isclass(obj) and objname != 'TestCase' and (
343
                        issubclass(obj, TestCase)):
344
                    yield (obj, objname)
345

    
346

    
347
def main(argv):
348
    found = False
349
    failure_collector = list()
350
    num_of_tests = 0
351
    for cls, name in get_test_classes(name=argv[1] if len(argv) > 1 else ''):
352
        found = True
353
        num_of_tests += runTestCase(cls, name, argv[2:], failure_collector)
354
    if not found:
355
        print('Test "%s" not found' % ' '.join(argv[1:]))
356
    else:
357
        for i, failure in enumerate(failure_collector):
358
            print('Failure %s: ' % (i + 1))
359
            for field in failure:
360
                print('\t%s' % field)
361
        print('\nTotal tests run: %s' % num_of_tests)
362
        print('Total failures: %s' % len(failure_collector))
363

    
364

    
365
if __name__ == '__main__':
366
    from sys import argv
367
    main(argv)