Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / test.py @ b773795c

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.cyclades.test import CycladesClient
45
from kamaki.clients.cyclades.test import CycladesRestClient
46
from kamaki.clients.image.test import ImageClient
47
from kamaki.clients.storage.test import StorageClient
48
from kamaki.clients.pithos.test import PithosClient, PithosRestClient
49

    
50

    
51
class ClientError(TestCase):
52

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

    
94

    
95
class RequestManager(TestCase):
96

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

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

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

    
134

    
135
class FakeResp(object):
136

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

    
143
    def read(self):
144
        return self.READ
145

    
146
    def getheaders(self):
147
        return self.HEADERS.items()
148

    
149

    
150
class ResponseManager(TestCase):
151

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

    
158
    def tearDown(self):
159
        FakeResp.READ = 'something to read'
160

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

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

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

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

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

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

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

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

    
211

    
212
class SilentEvent(TestCase):
213

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

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

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

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

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

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

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

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

    
265

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

    
274

    
275
class Client(TestCase):
276

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

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

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

    
299
    def test___init__(self):
300
        self.assertEqual(self.client.base_url, self.base_url)
301
        self.assertEqual(self.client.token, self.token)
302
        self.assert_dicts_are_equal(self.client.headers, {})
303
        DATE_FORMATS = [
304
            '%a %b %d %H:%M:%S %Y',
305
            '%A, %d-%b-%y %H:%M:%S GMT',
306
            '%a, %d %b %Y %H:%M:%S GMT']
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)