Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / test.py @ ddc97a10

History | View | Annotate | Download (17.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 unittest import makeSuite, TestSuite, TextTestRunner, TestCase
35
from inspect import getmembers, isclass
36
from tempfile import NamedTemporaryFile
37
from mock import patch, call
38
from itertools import product
39

    
40
from kamaki.cli.command_tree.test import Command, CommandTree
41
from kamaki.cli.config.test import Config
42
from kamaki.cli.argument.test import (
43
    Argument, ConfigArgument, RuntimeConfigArgument, FlagArgument,
44
    ValueArgument, IntArgument, DateArgument, VersionArgument,
45
    RepeatableArgument, KeyValueArgument, ProgressBarArgument,
46
    ArgumentParseManager)
47
from kamaki.cli.utils.test import UtilsMethods
48

    
49

    
50
class History(TestCase):
51

    
52
    def setUp(self):
53
        from kamaki.cli.history import History as HClass
54
        self.HCLASS = HClass
55
        self.file = NamedTemporaryFile()
56

    
57
    def tearDown(self):
58
        self.file.close()
59

    
60
    def test__match(self):
61
        self.assertRaises(AttributeError, self.HCLASS._match, 'ok', 42)
62
        self.assertRaises(TypeError, self.HCLASS._match, 2.71, 'ok')
63
        for args, expected in (
64
                (('XXX', None), True),
65
                ((None, None), True),
66
                (('this line has some terms', 'some terms'), True),
67
                (('this line has some terms', 'some bad terms'), False),
68
                (('small line', 'not so small line terms'), False),
69
                ((['line', 'with', 'some', 'terms'], 'some terms'), True),
70
                ((['line', 'with', 'some terms'], 'some terms'), False)):
71
            self.assertEqual(self.HCLASS._match(*args), expected)
72

    
73
    def test_add(self):
74
        history = self.HCLASS(self.file.name)
75
        some_strings = ('a brick', 'two bricks', 'another brick', 'A wall!')
76
        for i, line in enumerate(some_strings):
77
            history.add(line)
78
            self.file.seek(0)
79
            self.assertEqual(
80
                self.file.read(), '\n'.join(some_strings[:(i + 1)]) + '\n')
81

    
82
    def test_empty(self):
83
        content = 'a brick\ntwo bricks\nanother brick\nA wall!\n'
84
        self.file.write(content)
85
        self.file.flush()
86
        self.file.seek(0)
87
        self.assertEqual(self.file.read(), content)
88
        history = self.HCLASS(self.file.name)
89
        history.empty()
90
        self.file.seek(0)
91
        self.assertEqual(self.file.read(), '')
92

    
93
    def test_retrieve(self):
94
        sample_history = (
95
            'kamaki history show\n',
96
            'kamaki file list\n',
97
            'kamaki file create /pithos/f1\n',
98
            'kamaki file info /pithos/f1\n',
99
            'last command is always excluded')
100
        self.file.write(''.join(sample_history))
101
        self.file.flush()
102

    
103
        history = self.HCLASS(self.file.name)
104
        self.assertRaises(ValueError, history.retrieve, 'must be number')
105
        self.assertRaises(TypeError, history.retrieve, [1, 2, 3])
106

    
107
        for i in (0, len(sample_history) + 1, - len(sample_history) - 1):
108
            self.assertEqual(history.retrieve(i), None)
109
        for i in range(1, len(sample_history)):
110
            self.assertEqual(history.retrieve(i), sample_history[i - 1])
111
            self.assertEqual(history.retrieve(- i), sample_history[- i])
112

    
113

    
114
class LoggerMethods(TestCase):
115

    
116
    class PseudoLogger(object):
117
        level = 'some level'
118
        _setLevel_calls = []
119
        _addHandler_calls = []
120

    
121
        def setLevel(self, *args):
122
            self._setLevel_calls.append(args)
123

    
124
        def addHandler(self, *args):
125
            self._addHandler_calls.append(args)
126

    
127
    class PseudoHandler(object):
128
        _setFormatter_calls = []
129

    
130
        def setFormatter(self, *args):
131
            self._setFormatter_calls.append(args)
132

    
133
    def setUp(self):
134
        from kamaki.cli.logger import LOG_FILE, _blacklist
135
        self.LF, self.BL = list(LOG_FILE), dict(_blacklist)
136

    
137
    def tearDown(self):
138
        self.PseudoLogger._setLevel_calls = []
139
        self.PseudoLogger._addHandler_calls = []
140
        self.PseudoLogger._setFormatter_calls = []
141
        from kamaki.cli.logger import LOG_FILE, _blacklist
142
        for e in LOG_FILE:
143
            LOG_FILE.pop()
144
        for e in self.LF:
145
            LOG_FILE.append(e)
146
        _blacklist.clear()
147
        _blacklist.update(self.BL)
148

    
149
    @patch('kamaki.cli.logger.logging.getLogger', return_value=PseudoLogger())
150
    def test_deactivate(self, GL):
151
        from kamaki.cli.logger import deactivate, _blacklist
152
        self.assertEqual(_blacklist, {})
153
        deactivate('some logger')
154
        GL.assert_called_once_with('some logger')
155
        self.assertEqual(
156
            _blacklist.get('some logger', None), self.PseudoLogger.level)
157
        from logging import CRITICAL
158
        self.assertEqual(self.PseudoLogger._setLevel_calls[-1], (CRITICAL, ))
159

    
160
    @patch('kamaki.cli.logger.logging.getLogger', return_value=PseudoLogger())
161
    def test_activate(self, GL):
162
        from kamaki.cli.logger import activate
163
        activate('another logger')
164
        GL.assert_called_once_with('another logger')
165
        self.assertEqual(
166
            self.PseudoLogger._setLevel_calls[-1], (self.PseudoLogger.level, ))
167

    
168
    def test_get_log_filename(self):
169
        from kamaki.cli.logger import get_log_filename, LOG_FILE
170
        f = NamedTemporaryFile()
171
        for e in LOG_FILE:
172
            LOG_FILE.pop()
173
        LOG_FILE.append(f.name)
174
        self.assertEqual(get_log_filename(), f.name)
175
        LOG_FILE.pop()
176
        LOG_FILE.append(2 * f.name)
177
        print('\n  Should print error msg here: ')
178
        self.assertEqual(get_log_filename(), None)
179

    
180
    def test_set_log_filename(self):
181
        from kamaki.cli.logger import set_log_filename, LOG_FILE
182
        for n in ('some name', 'some other name'):
183
            set_log_filename(n)
184
            self.assertEqual(LOG_FILE[0], n)
185

    
186
    @patch('kamaki.cli.logger.get_logger', return_value=PseudoLogger())
187
    @patch('kamaki.cli.logger.logging.Formatter', return_value='f0rm4t')
188
    @patch(
189
        'kamaki.cli.logger.logging.StreamHandler',
190
        return_value=PseudoHandler())
191
    @patch(
192
        'kamaki.cli.logger.logging.FileHandler',
193
        return_value=PseudoHandler())
194
    def test__add_logger(self, FH, SH, F, GL):
195
        from kamaki.cli.logger import _add_logger
196
        from logging import DEBUG
197
        stdf, cnt = '%(name)s\n %(message)s', 0
198
        for name, level, filename, fmt in product(
199
                ('my logger', ),
200
                ('my level', None),
201
                ('my filename', None),
202
                ('my fmt', None)):
203
            log = _add_logger(name, level, filename, fmt)
204
            self.assertTrue(isinstance(log, self.PseudoLogger))
205
            self.assertEqual(GL.mock_calls[-1], call(name))
206
            if filename:
207
                self.assertEqual(FH.mock_calls[-1], call(filename))
208
            else:
209
                self.assertEqual(SH.mock_calls[-1], call())
210
            self.assertEqual(F.mock_calls[-1], call(fmt or stdf))
211
            self.assertEqual(
212
                self.PseudoHandler._setFormatter_calls[-1], ('f0rm4t', ))
213
            cnt += 1
214
            self.assertEqual(len(self.PseudoLogger._addHandler_calls), cnt)
215
            h = self.PseudoLogger._addHandler_calls[-1]
216
            self.assertTrue(isinstance(h[0], self.PseudoHandler))
217
            l = self.PseudoLogger._setLevel_calls[-1]
218
            self.assertEqual(l, (level or DEBUG, ))
219

    
220
    @patch('kamaki.cli.logger.get_log_filename', return_value='my log fname')
221
    @patch('kamaki.cli.logger.get_logger', return_value='my get logger ret')
222
    def test_add_file_logger(self, GL, GLF):
223
        from kamaki.cli.logger import add_file_logger
224
        with patch('kamaki.cli.logger._add_logger', return_value='AL') as AL:
225
            GLFcount = GLF.call_count
226
            for name, level, filename in product(
227
                    ('my name'), ('my level', None), ('my filename', None)):
228
                self.assertEqual(add_file_logger(name, level, filename), 'AL')
229
                self.assertEqual(AL.mock_calls[-1], call(
230
                    name, level, filename or 'my log fname',
231
                    fmt='%(name)s(%(levelname)s) %(asctime)s\n\t%(message)s'))
232
                if filename:
233
                    self.assertEqual(GLFcount, GLF.call_count)
234
                else:
235
                    GLFcount = GLF.call_count
236
                    self.assertEqual(GLF.mock_calls[-1], call())
237
        with patch('kamaki.cli.logger._add_logger', side_effect=Exception):
238
            self.assertEqual(add_file_logger('X'), 'my get logger ret')
239
            GL.assert_called_once_with('X')
240

    
241
    @patch('kamaki.cli.logger.get_logger', return_value='my get logger ret')
242
    def test_add_stream_logger(self, GL):
243
        from kamaki.cli.logger import add_stream_logger
244
        with patch('kamaki.cli.logger._add_logger', return_value='AL') as AL:
245
            for name, level, fmt in product(
246
                    ('my name'), ('my level', None), ('my fmt', None)):
247
                self.assertEqual(add_stream_logger(name, level, fmt), 'AL')
248
                self.assertEqual(AL.mock_calls[-1], call(name, level, fmt=fmt))
249
        with patch('kamaki.cli.logger._add_logger', side_effect=Exception):
250
            self.assertEqual(add_stream_logger('X'), 'my get logger ret')
251
            GL.assert_called_once_with('X')
252

    
253
    @patch('kamaki.cli.logger.logging.getLogger', return_value=PseudoLogger())
254
    def test_get_logger(self, GL):
255
        from kamaki.cli.logger import get_logger
256
        get_logger('my logger name')
257
        GL.assert_called_once_with('my logger name')
258

    
259

    
260
_RET = None
261

    
262

    
263
class PseudoException(object):
264

    
265
    def __init__(self, *args):
266
        global _RET
267
        _RET = args
268

    
269

    
270
class CLIError(TestCase):
271

    
272
    @patch('__builtin__.super', return_value=PseudoException())
273
    def test___init__(self, S):
274
        from kamaki.cli.errors import CLIError
275
        global _RET
276
        for message, details, importance in (
277
                ('some msg', [], 0),
278
                ('some msg\n', 'details', 0),
279
                ('some msg', ['details1', 'details2'], 10)):
280
            clie = CLIError(message, details, importance)
281
            self.assertEqual(S.mock_calls[-1], call(CLIError, clie))
282
            self.assertEqual(_RET[0], (message + '\n') if (
283
                message and not message.endswith('\n')) else message)
284
            self.assertEqual(clie.details, (list(details) if (
285
                isinstance(details, list)) else ['%s' % details]) if (
286
                    details) else [])
287
            self.assertEqual(clie.importance, int(importance))
288
        clie = CLIError(message, details, 'non int')
289
        self.assertEqual(clie.importance, 0)
290

    
291
    def test_raiseCLIError(self):
292
        from kamaki.cli.errors import raiseCLIError, CLIError
293
        for err, message, importance, details in (
294
                (Exception('msg'), '', 0, []),
295
                (Exception('msg'), 'orther msg', 0, []),
296
                (Exception('msg'), 'orther msg', 0, ['d1', 'd2']),
297
                (Exception('msg'), '', 10, []),
298
                (Exception('msg'), '', None, []),
299
                (CLIError('some msg'), '', None, ['d1', 'd2'])
300
            ):
301
            try:
302
                raiseCLIError(err, message, importance, details)
303
            except CLIError as clie:
304
                exp_msg = '%s' % (message or err)
305
                exp_msg += '' if exp_msg.endswith('\n') else '\n'
306
                self.assertEqual('%s' % clie, exp_msg)
307
                self.assertEqual(clie.importance, importance or 0)
308
                exp_d = list(details) if isinstance(details, list) else [
309
                    '%s' % (details or '')]
310
                base_msg = '%s' % err
311
                if message and base_msg != message:
312
                    exp_d.append(base_msg)
313
                self.assertEqual(clie.details, exp_d)
314

    
315

    
316
class CLIUnimplemented(TestCase):
317

    
318
    def test___init__(self):
319
        from kamaki.cli.errors import CLIUnimplemented
320
        cliu = CLIUnimplemented()
321
        self.assertEqual(
322
            '%s' % cliu,
323
            'I \'M SORRY, DAVE.\nI \'M AFRAID I CAN\'T DO THAT.\n')
324
        self.assertEqual(cliu.details, [
325
            '      _        |',
326
            '   _-- --_     |',
327
            '  --     --    |',
328
            ' --   .   --   |',
329
            ' -_       _-   |',
330
            '   -_   _-     |',
331
            '      -        |'])
332
        self.assertEqual(cliu.importance, 3)
333

    
334

    
335
class CLIBaseUrlError(TestCase):
336

    
337
    def test___init__(self):
338
        from kamaki.cli.errors import CLIBaseUrlError
339
        for service in ('', 'some service'):
340
            clibue = CLIBaseUrlError(service=service)
341
            self.assertEqual('%s' % clibue, 'No URL for %s\n' % service)
342
            self.assertEqual(clibue.details, [
343
                'Two ways to resolve this:',
344
                '(Use the correct cloud name, instead of "default")',
345
                'A. (recommended) Let kamaki discover endpoint URLs for all',
346
                'services by setting a single Authentication URL and token:',
347
                '  /config set cloud.default.url <AUTH_URL>',
348
                '  /config set cloud.default.token <t0k3n>',
349
                'B. (advanced users) Explicitly set an %s endpoint URL' % (
350
                    service.upper()),
351
                'Note: URL option has a higher priority, so delete it to',
352
                'make that work',
353
                '  /config delete cloud.default.url',
354
                '  /config set cloud.%s.url <%s_URL>' % (
355
                    service, service.upper())])
356
            self.assertEqual(clibue.importance, 2)
357

    
358

    
359
class CLISyntaxError(TestCase):
360

    
361
    def test___init__(self):
362
        from kamaki.cli.errors import CLISyntaxError
363
        clise = CLISyntaxError()
364
        self.assertEqual('%s' % clise, 'Syntax Error\n')
365
        self.assertEqual(clise.details, [])
366
        self.assertEqual(clise.importance, 1)
367

    
368

    
369
class CLIInvalidArgument(TestCase):
370

    
371
    def test___init__(self):
372
        from kamaki.cli.errors import CLIInvalidArgument
373
        cliia = CLIInvalidArgument()
374
        self.assertEqual('%s' % cliia, 'Invalid Argument\n')
375
        self.assertEqual(cliia.details, [])
376
        self.assertEqual(cliia.importance, 1)
377

    
378

    
379
class CLIUnknownCommand(TestCase):
380

    
381
    def test___init__(self):
382
        from kamaki.cli.errors import CLIUnknownCommand
383
        cliec = CLIUnknownCommand()
384
        self.assertEqual('%s' % cliec, 'Unknown Command\n')
385
        self.assertEqual(cliec.details, [])
386
        self.assertEqual(cliec.importance, 1)
387

    
388

    
389
class CLICmdSpecError(TestCase):
390

    
391
    def test___init__(self):
392
        from kamaki.cli.errors import CLICmdSpecError
393
        clicse = CLICmdSpecError()
394
        self.assertEqual('%s' % clicse, 'Command Specification Error\n')
395
        self.assertEqual(clicse.details, [])
396
        self.assertEqual(clicse.importance, 0)
397

    
398

    
399
#  TestCase auxiliary methods
400

    
401
def runTestCase(cls, test_name, args=[], failure_collector=[]):
402
    """
403
    :param cls: (TestCase) a set of Tests
404

405
    :param test_name: (str)
406

407
    :param args: (list) these are prefixed with test_ and used as params when
408
        instantiating cls
409

410
    :param failure_collector: (list) collects info of test failures
411

412
    :returns: (int) total # of run tests
413
    """
414
    suite = TestSuite()
415
    if args:
416
        suite.addTest(cls('_'.join(['test'] + args)))
417
    else:
418
        suite.addTest(makeSuite(cls))
419
    print('* Test * %s *' % test_name)
420
    r = TextTestRunner(verbosity=2).run(suite)
421
    failure_collector += r.failures
422
    return r.testsRun
423

    
424

    
425
def get_test_classes(module=__import__(__name__), name=''):
426
    module_stack = [module]
427
    while module_stack:
428
        module = module_stack[-1]
429
        module_stack = module_stack[:-1]
430
        for objname, obj in getmembers(module):
431
            if (objname == name or not name):
432
                if isclass(obj) and objname != 'TestCase' and (
433
                        issubclass(obj, TestCase)):
434
                    yield (obj, objname)
435

    
436

    
437
def main(argv):
438
    found = False
439
    failure_collector = list()
440
    num_of_tests = 0
441
    for cls, name in get_test_classes(name=argv[1] if len(argv) > 1 else ''):
442
        found = True
443
        num_of_tests += runTestCase(cls, name, argv[2:], failure_collector)
444
    if not found:
445
        print('Test "%s" not found' % ' '.join(argv[1:]))
446
    else:
447
        for i, failure in enumerate(failure_collector):
448
            print('Failure %s: ' % (i + 1))
449
            for field in failure:
450
                print('\t%s' % field)
451
        print('\nTotal tests run: %s' % num_of_tests)
452
        print('Total failures: %s' % len(failure_collector))
453

    
454

    
455
if __name__ == '__main__':
456
    from sys import argv
457
    main(argv)