Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / test.py @ 58602137

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.networking.test import (
45
    NetworkingClient, NetworkingRestClient)
46
from kamaki.clients.cyclades.test import CycladesClient
47
from kamaki.clients.cyclades.test import CycladesRestClient
48
from kamaki.clients.image.test import ImageClient
49
from kamaki.clients.storage.test import StorageClient
50
from kamaki.clients.pithos.test import (
51
    PithosClient, PithosRestClient, PithosMethods)
52

    
53

    
54
class ClientError(TestCase):
55

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

    
97

    
98
class RequestManager(TestCase):
99

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

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

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

    
137

    
138
class FakeResp(object):
139

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

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

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

    
152

    
153
class ResponseManager(TestCase):
154

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

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

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

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

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

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

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

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

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

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

    
214

    
215
class SilentEvent(TestCase):
216

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

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

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

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

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

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

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

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

    
268

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

    
277

    
278
class Client(TestCase):
279

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
445

    
446
#  TestCase auxiliary methods
447

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

452
    :param test_name: (str)
453

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

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

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

    
471

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

    
477

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

    
489

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

    
507

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