Statistics
| Branch: | Tag: | Revision:

root / kamaki / clients / test.py @ 8be50626

History | View | Annotate | Download (18.5 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 (
49
    PithosClient, PithosRestClient, PithosMethods)
50

    
51

    
52
class ClientError(TestCase):
53

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

    
95

    
96
class RequestManager(TestCase):
97

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

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

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

    
135

    
136
class FakeResp(object):
137

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

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

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

    
150

    
151
class ResponseManager(TestCase):
152

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

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

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

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

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

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

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

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

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

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

    
212

    
213
class SilentEvent(TestCase):
214

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

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

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

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

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

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

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

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

    
266

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

    
275

    
276
class Client(TestCase):
277

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
443

    
444
#  TestCase auxiliary methods
445

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

450
    :param test_name: (str)
451

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

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

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

    
469

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

    
475

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

    
487

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

    
505

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