Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / test.py @ 058ee9a8

History | View | Annotate | Download (18.6 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.utils.test import Utils
42
from kamaki.clients.astakos.test import AstakosClient
43
from kamaki.clients.compute.test import ComputeClient, ComputeRestClient
44
from kamaki.clients.network.test import NetworkClient, NetworkRestClient
45
from kamaki.clients.cyclades.test import CycladesClient
46
from kamaki.clients.cyclades.test import CycladesRestClient
47
from kamaki.clients.image.test import ImageClient
48
from kamaki.clients.storage.test import StorageClient
49
from kamaki.clients.pithos.test import (
50
    PithosClient, PithosRestClient, PithosMethods)
51

    
52

    
53
class ClientError(TestCase):
54

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

    
96

    
97
class RequestManager(TestCase):
98

    
99
    def setUp(self):
100
        from kamaki.clients import RequestManager
101
        self.RM = RequestManager
102

    
103
    def test___init__(self):
104
        from kamaki.clients import HTTP_METHODS
105
        method_values = HTTP_METHODS + [v.lower() for v in HTTP_METHODS]
106
        for args in product(
107
                tuple(method_values),
108
                ('http://www.example.com', 'https://example.com', ''),
109
                ('/some/path', '/' ''),
110
                ('Some data', '', None),
111
                (dict(k1='v1', k2='v2'), dict()),
112
                (dict(k='v', k2=None, k3='v3'), dict(k=0), dict(k='v'), {})):
113
            req = self.RM(*args)
114
            method, url, path, data, headers, params = args
115
            self.assertEqual(req.method, method.upper())
116
            for i, (k, v) in enumerate(params.items()):
117
                path += '%s%s%s' % (
118
                    '&' if '?' in path or i else '?',
119
                    k,
120
                    ('=%s' % v) if v else '')
121
            self.assertEqual(req.path, path)
122
            self.assertEqual(req.data, data)
123
            self.assertEqual(req.headers, headers)
124
        self.assertRaises(AssertionError, self.RM, 'GOT', '', '', '', {}, {})
125

    
126
    @patch('httplib.HTTPConnection.getresponse')
127
    @patch('httplib.HTTPConnection.request')
128
    def test_perform(self, request, getresponse):
129
        from httplib import HTTPConnection
130
        self.RM('GET', 'http://example.com', '/').perform(
131
            HTTPConnection('http', 'example.com'))
132
        expected = dict(body=None, headers={}, url='/', method='GET')
133
        request.assert_called_once_with(**expected)
134
        getresponse.assert_called_once_with()
135

    
136

    
137
class FakeResp(object):
138

    
139
    READ = 'something to read'
140
    HEADERS = dict(k='v', k1='v1', k2='v2')
141
    reason = 'some reason'
142
    status = 42
143
    status_code = 200
144

    
145
    def read(self):
146
        return self.READ
147

    
148
    def getheaders(self):
149
        return self.HEADERS.items()
150

    
151

    
152
class ResponseManager(TestCase):
153

    
154
    def setUp(self):
155
        from kamaki.clients import ResponseManager, RequestManager
156
        from httplib import HTTPConnection
157
        self.RM = ResponseManager(RequestManager('GET', 'http://ok', '/'))
158
        self.HTTPC = HTTPConnection
159

    
160
    def tearDown(self):
161
        FakeResp.READ = 'something to read'
162

    
163
    @patch('kamaki.clients.RequestManager.perform', return_value=FakeResp())
164
    def test_content(self, perform):
165
        self.assertEqual(self.RM.content, FakeResp.READ)
166
        self.assertTrue(isinstance(perform.call_args[0][0], self.HTTPC))
167

    
168
    @patch('kamaki.clients.RequestManager.perform', return_value=FakeResp())
169
    def test_text(self, perform):
170
        self.assertEqual(self.RM.text, FakeResp.READ)
171
        self.assertTrue(isinstance(perform.call_args[0][0], self.HTTPC))
172

    
173
    @patch('kamaki.clients.RequestManager.perform', return_value=FakeResp())
174
    def test_status(self, perform):
175
        self.assertEqual(self.RM.status, FakeResp.reason)
176
        self.assertTrue(isinstance(perform.call_args[0][0], self.HTTPC))
177

    
178
    @patch('kamaki.clients.RequestManager.perform', return_value=FakeResp())
179
    def test_status_code(self, perform):
180
        self.assertEqual(self.RM.status_code, FakeResp.status)
181
        self.assertTrue(isinstance(perform.call_args[0][0], self.HTTPC))
182

    
183
    @patch('kamaki.clients.RequestManager.perform', return_value=FakeResp())
184
    def test_headers(self, perform):
185
        self.assertEqual(self.RM.headers, FakeResp.HEADERS)
186
        self.assertTrue(isinstance(perform.call_args[0][0], self.HTTPC))
187

    
188
    @patch('kamaki.clients.RequestManager.perform', return_value=FakeResp())
189
    def test_json(self, perform):
190
        try:
191
            self.RM.json
192
        except Exception as e:
193
            self.assertEqual(
194
                '%s' % e,
195
                'Response not formated in JSON - '
196
                'No JSON object could be decoded\n')
197

    
198
        from json import dumps
199
        FakeResp.READ = dumps(FakeResp.HEADERS)
200
        self.RM._request_performed = False
201
        self.assertEqual(self.RM.json, FakeResp.HEADERS)
202
        self.assertTrue(isinstance(perform.call_args[0][0], self.HTTPC))
203

    
204
    @patch('kamaki.clients.RequestManager.perform', return_value=FakeResp())
205
    def test_all(self, perform):
206
        self.assertEqual(self.RM.content, FakeResp.READ)
207
        self.assertEqual(self.RM.text, FakeResp.READ)
208
        self.assertEqual(self.RM.status, FakeResp.reason)
209
        self.assertEqual(self.RM.status_code, FakeResp.status)
210
        self.assertEqual(self.RM.headers, FakeResp.HEADERS)
211
        perform.assert_called_only_once
212

    
213

    
214
class SilentEvent(TestCase):
215

    
216
    def thread_content(self, methodid, raiseException=0):
217
        wait = 0.1
218
        self.can_finish = -1
219
        while self.can_finish < methodid and wait < 4:
220
            sleep(wait)
221
            wait = 2 * wait
222
        if raiseException and raiseException == methodid:
223
            raise Exception('Some exception')
224
        self._value = methodid
225
        self.assertTrue(wait < 4)
226

    
227
    def setUp(self):
228
        from kamaki.clients import SilentEvent
229
        self.SE = SilentEvent
230

    
231
    def test_run(self):
232
        threads = [self.SE(self.thread_content, i) for i in range(4)]
233
        for t in threads:
234
            t.start()
235

    
236
        for i in range(4):
237
            self.assertTrue(threads[i].is_alive())
238
            self.can_finish = i
239
            threads[i].join()
240
            self.assertFalse(threads[i].is_alive())
241

    
242
    def test_value(self):
243
        threads = [self.SE(self.thread_content, i) for i in range(4)]
244
        for t in threads:
245
            t.start()
246

    
247
        for mid, t in enumerate(threads):
248
            if t.is_alive():
249
                self.can_finish = mid
250
                continue
251
            self.assertTrue(mid, t.value)
252

    
253
    def test_exception(self):
254
        threads = [self.SE(self.thread_content, i, (i % 2)) for i in range(4)]
255
        for t in threads:
256
            t.start()
257

    
258
        for i, t in enumerate(threads):
259
            if t.is_alive():
260
                self.can_finish = i
261
                continue
262
            if i % 2:
263
                self.assertTrue(isinstance(t.exception, Exception))
264
            else:
265
                self.assertFalse(t.exception)
266

    
267

    
268
class FR(object):
269
    json = None
270
    text = None
271
    headers = dict()
272
    content = json
273
    status = None
274
    status_code = 200
275

    
276

    
277
class Client(TestCase):
278

    
279
    def assert_dicts_are_equal(self, d1, d2):
280
        for k, v in d1.items():
281
            self.assertTrue(k in d2)
282
            if isinstance(v, dict):
283
                self.assert_dicts_are_equal(v, d2[k])
284
            else:
285
                self.assertEqual(unicode(v), unicode(d2[k]))
286

    
287
    def setUp(self):
288
        from kamaki.clients import Client
289
        from kamaki.clients import ClientError as CE
290
        self.base_url = 'http://example.com'
291
        self.token = 's0m370k3n=='
292
        self.client = Client(self.base_url, self.token)
293
        self.CE = CE
294

    
295
    def tearDown(self):
296
        FR.text = None
297
        FR.status = None
298
        FR.status_code = 200
299
        self.client.token = self.token
300

    
301
    def test___init__(self):
302
        self.assertEqual(self.client.base_url, self.base_url)
303
        self.assertEqual(self.client.token, self.token)
304
        self.assert_dicts_are_equal(self.client.headers, {})
305
        DATE_FORMATS = ['%a %b %d %H:%M:%S %Y']
306
        self.assertEqual(self.client.DATE_FORMATS, DATE_FORMATS)
307

    
308
    def test__init_thread_limit(self):
309
        exp = 'Nothing set here'
310
        for faulty in (-1, 0.5, 'a string', {}):
311
            self.assertRaises(
312
                AssertionError,
313
                self.client._init_thread_limit,
314
                faulty)
315
            self.assertEqual(exp, getattr(self.client, '_thread_limit', exp))
316
            self.assertEqual(exp, getattr(self.client, '_elapsed_old', exp))
317
            self.assertEqual(exp, getattr(self.client, '_elapsed_new', exp))
318
        self.client._init_thread_limit(42)
319
        self.assertEqual(42, self.client._thread_limit)
320
        self.assertEqual(0.0, self.client._elapsed_old)
321
        self.assertEqual(0.0, self.client._elapsed_new)
322

    
323
    def test__watch_thread_limit(self):
324
        waits = (
325
            dict(args=((0.1, 1), (0.1, 2), (0.2, 1), (0.7, 1), (0.3, 2))),
326
            dict(args=((1.0 - (i / 10.0), (i + 1)) for i in range(7))),
327
            dict(max=1, args=tuple([(randint(1, 10) / 3.0, 1), ] * 10)),
328
            dict(
329
                limit=5,
330
                args=tuple([
331
                    (1.0 + (i / 10.0), (5 - i - 1)) for i in range(4)] + [
332
                    (2.0, 1), (1.9, 2), (2.0, 1), (2.0, 2)])),
333
            dict(args=tuple(
334
                [(1.0 - (i / 10.0), (i + 1)) for i in range(7)] + [
335
                (0.1, 7), (0.2, 6), (0.4, 5), (0.3, 6), (0.2, 7), (0.1, 7)])),)
336
        for wait_dict in waits:
337
            if 'max' in wait_dict:
338
                self.client.MAX_THREADS = wait_dict['max']
339
            else:
340
                self.client.MAX_THREADS = 7
341
            if 'limit' in wait_dict:
342
                self.client._init_thread_limit(wait_dict['limit'])
343
            else:
344
                self.client._init_thread_limit()
345
                self.client._watch_thread_limit(list())
346
                self.assertEqual(1, self.client._thread_limit)
347
            for wait, exp_limit in wait_dict['args']:
348
                self.client._elapsed_new = wait
349
                self.client._watch_thread_limit(list())
350
                self.assertEqual(exp_limit, self.client._thread_limit)
351

    
352
    def test__raise_for_status(self):
353
        r = FR()
354
        for txt, sts_code, sts in (('err msg', 10, None), ('', 42, 'Err St')):
355
            r.text, r.status_code, r.status = txt, sts_code, sts
356
            try:
357
                self.client._raise_for_status(r)
358
            except self.CE as ce:
359
                self.assertEqual('%s' % ce, '%s %s\n' % (sts or '', txt))
360
                self.assertEqual(ce.status, sts_code)
361

    
362
        for msg, sts_code in (('err msg', 32), ('', 42), ('an err', None)):
363
            err = self.CE(msg, sts_code) if sts_code else Exception(msg)
364
            try:
365
                self.client._raise_for_status(err)
366
            except self.CE as ce:
367
                self.assertEqual('%s' % ce, '%s %s\n' % (sts_code or '', msg))
368
                self.assertEqual(ce.status, sts_code or 0)
369

    
370
    @patch('kamaki.clients.Client.set_header')
371
    def test_set_header(self, SH):
372
        for name, value, condition in product(
373
                ('n4m3', '', None),
374
                ('v41u3', None, 42),
375
                (True, False, None, 1, '')):
376
            self.client.set_header(name, value, iff=condition)
377
            self.assertEqual(
378
                SH.mock_calls[-1], call(name, value, iff=condition))
379

    
380
    @patch('kamaki.clients.Client.set_param')
381
    def test_set_param(self, SP):
382
        for name, value, condition in product(
383
                ('n4m3', '', None),
384
                ('v41u3', None, 42),
385
                (True, False, None, 1, '')):
386
            self.client.set_param(name, value, iff=condition)
387
            self.assertEqual(
388
                SP.mock_calls[-1], call(name, value, iff=condition))
389

    
390
    @patch('kamaki.clients.RequestManager', return_value=FR)
391
    @patch('kamaki.clients.ResponseManager', return_value=FakeResp())
392
    @patch('kamaki.clients.ResponseManager.__init__')
393
    def test_request(self, Requ, RespInit, Resp):
394
        for args in product(
395
                ('get', '', dict(method='get')),
396
                ('/some/path', None, ['some', 'path']),
397
                (dict(), dict(h1='v1'), dict(h1='v2', h2='v2')),
398
                (dict(), dict(p1='v1'), dict(p1='v2', p2=None, p3='v3')),
399
                (dict(), dict(data='some data'), dict(
400
                    success=400,
401
                    json=dict(k2='v2', k1='v1')))):
402
            method, path, kwargs = args[0], args[1], args[-1]
403
            FakeResp.status_code = kwargs.get('success', 200)
404
            if not (method and (
405
                    isinstance(method, str) or isinstance(
406
                        method, unicode)) and (
407
                    isinstance(path, str) or isinstance(path, unicode))):
408
                self.assertRaises(
409
                    AssertionError, self.client.request, method, path,
410
                    **kwargs)
411
                continue
412
            self.client.request(method, path, **kwargs)
413
            self.assertEqual(
414
                RespInit.mock_calls[-1], call(FR, connection_retry_limit=0))
415

    
416
    @patch('kamaki.clients.Client.request', return_value='lala')
417
    def _test_foo(self, foo, request):
418
        method = getattr(self.client, foo)
419
        r = method('path', k='v')
420
        self.assertEqual(r, 'lala')
421
        request.assert_called_once_with(foo, 'path', k='v')
422

    
423
    def test_delete(self):
424
        self._test_foo('delete')
425

    
426
    def test_get(self):
427
        self._test_foo('get')
428

    
429
    def test_head(self):
430
        self._test_foo('head')
431

    
432
    def test_post(self):
433
        self._test_foo('post')
434

    
435
    def test_put(self):
436
        self._test_foo('put')
437

    
438
    def test_copy(self):
439
        self._test_foo('copy')
440

    
441
    def test_move(self):
442
        self._test_foo('move')
443

    
444

    
445
#  TestCase auxiliary methods
446

    
447
def runTestCase(cls, test_name, args=[], failure_collector=[]):
448
    """
449
    :param cls: (TestCase) a set of Tests
450

451
    :param test_name: (str)
452

453
    :param args: (list) these are prefixed with test_ and used as params when
454
        instantiating cls
455

456
    :param failure_collector: (list) collects info of test failures
457

458
    :returns: (int) total # of run tests
459
    """
460
    suite = TestSuite()
461
    if args:
462
        suite.addTest(cls('_'.join(['test'] + args)))
463
    else:
464
        suite.addTest(makeSuite(cls))
465
    print('* Test * %s *' % test_name)
466
    r = TextTestRunner(verbosity=2).run(suite)
467
    failure_collector += r.failures
468
    return r.testsRun
469

    
470

    
471
def _add_value(foo, value):
472
    def wrap(self):
473
        return foo(self, value)
474
    return wrap
475

    
476

    
477
def get_test_classes(module=__import__(__name__), name=''):
478
    module_stack = [module]
479
    while module_stack:
480
        module = module_stack[-1]
481
        module_stack = module_stack[:-1]
482
        for objname, obj in getmembers(module):
483
            if (objname == name or not name):
484
                if isclass(obj) and objname != 'TestCase' and (
485
                        issubclass(obj, TestCase)):
486
                    yield (obj, objname)
487

    
488

    
489
def main(argv):
490
    found = False
491
    failure_collector = list()
492
    num_of_tests = 0
493
    for cls, name in get_test_classes(name=argv[1] if len(argv) > 1 else ''):
494
        found = True
495
        num_of_tests += runTestCase(cls, name, argv[2:], failure_collector)
496
    if not found:
497
        print('Test "%s" not found' % ' '.join(argv[1:]))
498
    else:
499
        for i, failure in enumerate(failure_collector):
500
            print('Failure %s: ' % (i + 1))
501
            for field in failure:
502
                print('\t%s' % field)
503
        print('\nTotal tests run: %s' % num_of_tests)
504
        print('Total failures: %s' % len(failure_collector))
505

    
506

    
507
if __name__ == '__main__':
508
    from sys import argv
509
    main(argv)