Statistics
| Branch: | Tag: | Revision:

root / kamaki / cli / argument / test.py @ 4a25486d

History | View | Annotate | Download (20.4 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, MagicMock
35
from unittest import TestCase
36
from StringIO import StringIO
37
from datetime import datetime
38
#from itertools import product
39

    
40
from kamaki.cli import argument, errors
41
from kamaki.cli.config import Config
42

    
43

    
44
def assert_dicts_are_equal(test_case, d1, d2):
45
    for k, v in d1.items():
46
        test_case.assertTrue(k in d2)
47
        if isinstance(v, dict):
48
            test_case.assert_dicts_are_equal(v, d2[k])
49
        else:
50
            test_case.assertEqual(unicode(v), unicode(d2[k]))
51

    
52

    
53
cnf_path = 'kamaki.cli.config.Config'
54
arg_path = 'kamaki.cli.argument'
55

    
56

    
57
class Argument(TestCase):
58

    
59
    def test___init__(self):
60
        self.assertRaises(ValueError, argument.Argument, 'non-integer')
61
        self.assertRaises(AssertionError, argument.Argument, 1)
62
        self.assertRaises(AssertionError, argument.Argument, 0, 'noname')
63
        self.assertRaises(AssertionError, argument.Argument, 0, '--no name')
64
        self.assertRaises(AssertionError, argument.Argument, 0, ['-n', 'n m'])
65
        for arity, help, parsed_name, default in (
66
                (0, 'help 0', '--zero', None),
67
                (1, 'help 1', ['--one', '-o'], 'lala'),
68
                (-1, 'help -1', ['--help', '--or', '--more'], 0),
69
                (0, 'help 0 again', ['--again', ], True)):
70
            a = argument.Argument(arity, help, parsed_name, default)
71
            if arity:
72
                self.assertEqual(arity, a.arity)
73
            self.assertEqual(help, a.help)
74

    
75
            exp_name = parsed_name if (
76
                isinstance(parsed_name, list)) else [parsed_name, ]
77
            self.assertEqual(exp_name, a.parsed_name)
78

    
79
            exp_default = default if (default or arity) else False
80
            self.assertEqual(exp_default, a.default)
81

    
82
    def test_value(self):
83
        a = argument.Argument(1, parsed_name='--value')
84
        for value in (None, '', 0, 0.1, -12, [1, 'a', 2.8], (3, 'lala'), 'pi'):
85
            a.value = value
86
            self.assertEqual(value, a.value)
87

    
88
    def test_update_parser(self):
89
        for i, arity in enumerate((-1, 0, 1)):
90
            arp = argument.ArgumentParser()
91
            pname, aname = '--pname%s' % i, 'a_name_%s' % i
92
            a = argument.Argument(arity, 'args', pname, 42)
93
            a.update_parser(arp, aname)
94

    
95
            f = StringIO()
96
            arp.print_usage(file=f), f.seek(0)
97
            usage, exp = f.readline(), '[%s%s]\n' % (
98
                pname, (' %s' % aname.upper()) if arity else '')
99
            self.assertEqual(usage[-len(exp):], exp)
100
            del arp
101

    
102

    
103
class ConfigArgument(TestCase):
104

    
105
    def setUp(self):
106
        argument._config_arg = argument.ConfigArgument('Recovered Path')
107

    
108
    def test_value(self):
109
        c = argument._config_arg
110
        self.assertEqual(c.value, None)
111
        exp = '/some/random/path'
112
        c.value = exp
113
        self.assertTrue(isinstance(c.value, Config))
114
        self.assertEqual(c.file_path, exp)
115
        self.assertEqual(c.value.path, exp)
116

    
117
    def test_get(self):
118
        c = argument._config_arg
119
        c.value = None
120
        with patch('%s.get' % cnf_path, return_value='config') as get:
121
            self.assertEqual(c.value.get('global', 'config_cli'), 'config')
122
            self.assertEqual(get.mock_calls[-1], call('global', 'config_cli'))
123

    
124
    @patch('%s.keys' % cnf_path, return_value=(
125
        'image_cli', 'config_cli', 'history_cli', 'file'))
126
    def test_groups(self, keys):
127
        c = argument._config_arg
128
        c.value = None
129
        cset = set(c.groups)
130
        self.assertTrue(cset.issuperset(['image', 'config', 'history']))
131
        self.assertEqual(keys.mock_calls[-1], call('global'))
132
        self.assertFalse('file' in cset)
133
        self.assertEqual(keys.mock_calls[-1], call('global'))
134

    
135
    @patch('%s.items' % cnf_path, return_value=(
136
        ('image_cli', 'image'), ('file', 'pithos'),
137
        ('config_cli', 'config'), ('history_cli', 'history')))
138
    def test_cli_specs(self, items):
139
        c = argument._config_arg
140
        c.value = None
141
        cset = set(c.cli_specs)
142
        self.assertTrue(cset.issuperset([
143
            ('image', 'image'), ('config', 'config'), ('history', 'history')]))
144
        self.assertEqual(items.mock_calls[-1], call('global'))
145
        self.assertFalse(cset.issuperset([('file', 'pithos'), ]))
146
        self.assertEqual(items.mock_calls[-1], call('global'))
147

    
148
    def test_get_global(self):
149
        c = argument._config_arg
150
        c.value = None
151
        for k, v in (
152
                ('config_cli', 'config'),
153
                ('image_cli', 'image'),
154
                ('history_cli', 'history')):
155
            with patch('%s.get_global' % cnf_path, return_value=v) as gg:
156
                self.assertEqual(c.get_global(k), v)
157
                self.assertEqual(gg.mock_calls[-1], call(k))
158

    
159
    def test_get_cloud(self):
160
        c = argument._config_arg
161
        c.value = None
162
        with patch(
163
                '%s.get_cloud' % cnf_path,
164
                return_value='http://cloud') as get_cloud:
165
            self.assertTrue(len(c.get_cloud('mycloud', 'url')) > 0)
166
            self.assertEqual(get_cloud.mock_calls[-1],  call('mycloud', 'url'))
167
        with patch(
168
                '%s.get_cloud' % cnf_path,
169
                side_effect=KeyError('no token')) as get_cloud:
170
            self.assertRaises(KeyError, c.get_cloud, 'mycloud', 'token')
171
        invalidcloud = 'PLEASE_DO_NOT_EVER_NAME_YOUR_CLOUD_LIKE_THIS111'
172
        self.assertRaises(KeyError, c.get_cloud, invalidcloud, 'url')
173

    
174

    
175
class RuntimeConfigArgument(TestCase):
176

    
177
    def setUp(self):
178
        argument._config_arg = argument.ConfigArgument('Recovered Path')
179

    
180
    @patch('%s.Argument.__init__' % arg_path)
181
    def test___init__(self, arg):
182
        config, help, pname, default = 'config', 'help', 'pname', 'default'
183
        rca = argument.RuntimeConfigArgument(config, help, pname, default)
184
        self.assertTrue(isinstance(rca, argument.RuntimeConfigArgument))
185
        self.assertEqual(rca._config_arg, config)
186
        self.assertEqual(arg.mock_calls[-1], call(1, help, pname, default))
187

    
188
    @patch('%s.override' % cnf_path)
189
    def test_value(self, override):
190
        config, help, pname, default = argument._config_arg, 'help', '-n', 'df'
191
        config.value = None
192
        rca = argument.RuntimeConfigArgument(config, help, pname, default)
193
        self.assertEqual(rca.value, default)
194

    
195
        for options in ('grp', 'grp.opt', 'k v', '=nokey', 2.8, None, 42, ''):
196
            self.assertRaises(TypeError, rca.value, options)
197

    
198
        for options in ('key=val', 'grp.key=val', 'dotted.opt.key=val'):
199
            rca.value = options
200
            option, sep, val = options.partition('=')
201
            grp, sep, key = option.partition('.')
202
            grp, key = (grp, key) if key else ('global', grp)
203
            self.assertEqual(override.mock_calls[-1], call(grp, key, val))
204

    
205

    
206
class FlagArgument(TestCase):
207

    
208
    @patch('%s.Argument.__init__' % arg_path)
209
    def test___init__(self, arg):
210
        help, pname, default = 'help', 'pname', 'default'
211
        fa = argument.FlagArgument(help, pname, default)
212
        self.assertTrue(isinstance(fa, argument.FlagArgument))
213
        arg.assert_called_once(0, help, pname, default)
214

    
215

    
216
class ValueArgument(TestCase):
217

    
218
    @patch('%s.Argument.__init__' % arg_path)
219
    def test___init__(self, arg):
220
        help, pname, default = 'help', 'pname', 'default'
221
        fa = argument.ValueArgument(help, pname, default)
222
        self.assertTrue(isinstance(fa, argument.ValueArgument))
223
        arg.assert_called_once(1, help, pname, default)
224

    
225

    
226
class IntArgument(TestCase):
227

    
228
    def test_value(self):
229
        ia = argument.IntArgument(parsed_name='--ia')
230
        self.assertEqual(ia.value, None)
231
        for v in (1, 0, -1):
232
            ia.value = v
233
            self.assertEqual(ia.value, v)
234
        for v in ('1', '-1'):
235
            ia.value = v
236
            self.assertEqual(ia.value, int(v))
237
        for v, err in (
238
                ('invalid', errors.CLIError),
239
                (2.8, errors.CLIError),
240
                (923455555555555555555555555555555, errors.CLIError),
241
                (None, TypeError), (False, TypeError), ([1, 2, 3], TypeError)):
242
            try:
243
                ia.value = v
244
            except Exception as e:
245
                self.assertTrue(isinstance(e, err))
246

    
247

    
248
class DateArgument(TestCase):
249

    
250
    def test_timestamp(self):
251
        da = argument.DateArgument(parsed_name='--date')
252
        self.assertEqual(da.timestamp, None)
253
        date, format, exp = '24-10-1917', '%d-%m-%Y', -1646964000.0
254
        da._value = argument.dtm.strptime(date, format)
255
        self.assertEqual(da.timestamp, exp)
256

    
257
    def test_formated(self):
258
        da = argument.DateArgument(parsed_name='--date')
259
        self.assertEqual(da.formated, None)
260
        date, format, exp = (
261
            '24-10-1917', '%d-%m-%Y', 'Wed Oct 24 00:00:00 1917')
262
        da._value = argument.dtm.strptime(date, format)
263
        self.assertEqual(da.formated, exp)
264

    
265
    @patch('%s.DateArgument.timestamp' % arg_path)
266
    @patch('%s.DateArgument.format_date' % arg_path)
267
    def test_value(self, format_date, timestamp):
268
        da = argument.DateArgument(parsed_name='--date')
269
        self.assertTrue(isinstance(da.value, MagicMock))
270
        da.value = 'Something'
271
        format_date.assert_called_once(call('Something'))
272

    
273
    def test_format_date(self):
274
        da = argument.DateArgument(parsed_name='--date')
275
        for datestr, exp in (
276
                ('Wed Oct 24 01:02:03 1917', datetime(1917, 10, 24, 1, 2, 3)),
277
                ('24-10-1917', datetime(1917, 10, 24)),
278
                ('01:02:03 24-10-1917', datetime(1917, 10, 24, 1, 2, 3))):
279
            self.assertEqual(da.format_date(datestr), exp)
280
        for datestr, err in (
281
                ('32-40-20134', errors.CLIError),
282
                ('Wednesday, 24 Oct 2017', errors.CLIError),
283
                (None, TypeError), (0.8, TypeError)):
284
            self.assertRaises(err, da.format_date, datestr)
285

    
286

    
287
class VersionArgument(TestCase):
288

    
289
    def test_value(self):
290
        va = argument.VersionArgument(parsed_name='--version')
291
        self.assertTrue(va, argument.VersionArgument)
292
        va.value = 'some value'
293
        self.assertEqual(va.value, 'some value')
294

    
295

    
296
class RepeatableArgument(TestCase):
297

    
298
    @patch('%s.Argument.__init__' % arg_path)
299
    def test___init__(self, init):
300
        help, pname, default = 'help', 'pname', 'default'
301
        kva = argument.RepeatableArgument(help, pname, default)
302
        self.assertTrue(isinstance(kva, argument.RepeatableArgument))
303
        self.assertEqual(init.mock_calls[-1], call(-1, help, pname, default))
304

    
305

    
306
class KeyValueArgument(TestCase):
307

    
308
    @patch('%s.Argument.__init__' % arg_path)
309
    def test___init__(self, init):
310
        help, pname, default = 'help', 'pname', 'default'
311
        kva = argument.KeyValueArgument(help, pname, default)
312
        self.assertTrue(isinstance(kva, argument.KeyValueArgument))
313
        self.assertEqual(init.mock_calls[-1], call(-1, help, pname, default))
314

    
315
    def test_value(self):
316
        kva = argument.KeyValueArgument(parsed_name='--keyval')
317
        self.assertEqual(kva.value, [])
318
        for kvpairs in (
319
                'strval', 'key=val', 2.8, 42, None,
320
                ('key', 'val'), ('key val'), ['=val', 'key=val'],
321
                ['key1=val1', 'key2 val2'], ('key1 = val1', )):
322
            try:
323
                kva.value = kvpairs
324
            except Exception as e:
325
                self.assertTrue(isinstance(e, errors.CLIError))
326
        old = dict()
327
        kva = argument.KeyValueArgument(parsed_name='--keyval')
328
        for kvpairs, exp in (
329
                (('key=val', ), {'key': 'val'}),
330
                (['key1=val1', 'key2=val2'], {'key1': 'val1', 'key2': 'val2'}),
331
                (
332
                    ('k1=v1 v2', 'k3=', 'k 4=v4'),
333
                    {'k1': 'v1 v2', 'k3': '', 'k 4': 'v4'}),
334
                (('k=v1', 'k=v2', 'k=v3'), {'k': 'v3'})
335
            ):
336
            kva.value = kvpairs
337
            old.update(exp)
338
            assert_dicts_are_equal(self, kva.value, old)
339

    
340

    
341
class ProgressBarArgument(TestCase):
342

    
343
    class PseudoBar(object):
344
            message = ''
345
            suffix = ''
346

    
347
            def start():
348
                pass
349

    
350
    @patch('%s.FlagArgument.__init__' % arg_path)
351
    def test___init__(self, init):
352
        help, pname, default = 'help', '--progress', 'default'
353
        pba = argument.ProgressBarArgument(help, pname, default)
354
        self.assertTrue(isinstance(pba, argument.ProgressBarArgument))
355
        self.assertEqual(pba.suffix, '%(percent)d%%')
356
        init.assert_called_once(help, pname, default)
357

    
358
    def test_clone(self):
359
        pba = argument.ProgressBarArgument(parsed_name='--progress')
360
        pba.value = None
361
        pba_clone = pba.clone()
362
        self.assertTrue(isinstance(pba, argument.ProgressBarArgument))
363
        self.assertTrue(isinstance(pba_clone, argument.ProgressBarArgument))
364
        self.assertNotEqual(pba, pba_clone)
365
        self.assertEqual(pba.parsed_name, pba_clone.parsed_name)
366

    
367
    def test_get_generator(self):
368
        pba = argument.ProgressBarArgument(parsed_name='--progress')
369
        pba.value = None
370
        msg, msg_len = 'message', 40
371
        with patch('%s.KamakiProgressBar.start' % arg_path) as start:
372
            pba.get_generator(msg, msg_len)
373
            self.assertTrue(isinstance(pba.bar, argument.KamakiProgressBar))
374
            self.assertNotEqual(pba.bar.message, msg)
375
            self.assertEqual(
376
                pba.bar.message, '%s%s' % (msg, ' ' * (msg_len - len(msg))))
377
            self.assertEqual(pba.bar.suffix, '%(percent)d%% - %(eta)ds')
378
            start.assert_called_once()
379

    
380
    def test_finish(self):
381
        pba = argument.ProgressBarArgument(parsed_name='--progress')
382
        pba.value = None
383
        self.assertEqual(pba.finish(), None)
384
        pba.bar = argument.KamakiProgressBar()
385
        with patch('%s.KamakiProgressBar.finish' % arg_path) as finish:
386
            pba.finish()
387
            finish.assert_called_once()
388

    
389

    
390
class ArgumentParseManager(TestCase):
391

    
392
    @patch('%s.ArgumentParseManager.parse' % arg_path)
393
    @patch('%s.ArgumentParseManager.update_parser' % arg_path)
394
    def test___init__(self, parse, update_parser):
395
        for arguments in (None, {'k1': 'v1', 'k2': 'v2'}):
396
            apm = argument.ArgumentParseManager('exe', arguments)
397
            self.assertTrue(isinstance(apm, argument.ArgumentParseManager))
398

    
399
            self.assertTrue(isinstance(apm.parser, argument.ArgumentParser))
400
            self.assertFalse(apm.parser.add_help)
401
            self.assertEqual(
402
                apm.parser.formatter_class,
403
                argument.RawDescriptionHelpFormatter)
404

    
405
            self.assertEqual(
406
                apm.syntax, 'exe <cmd_group> [<cmd_subbroup> ...] <cmd>')
407
            assert_dicts_are_equal(
408
                self, apm.arguments,
409
                arguments or argument._arguments)
410
            self.assertFalse(apm._parser_modified)
411
            self.assertEqual(apm._parsed, None)
412
            self.assertEqual(apm._unparsed, None)
413
            self.assertEqual(parse.mock_calls[-1], call())
414
            if arguments:
415
                update_parser.assert_called_once()
416

    
417
    def test_syntax(self):
418
        apm = argument.ArgumentParseManager('exe')
419
        self.assertEqual(
420
            apm.syntax, 'exe <cmd_group> [<cmd_subbroup> ...] <cmd>')
421
        apm.syntax = 'some syntax'
422
        self.assertEqual(apm.syntax, 'some syntax')
423

    
424
    @patch('%s.ArgumentParseManager.update_parser' % arg_path)
425
    def test_arguments(self, update_parser):
426
        apm = argument.ArgumentParseManager('exe')
427
        assert_dicts_are_equal(self, apm.arguments, argument._arguments)
428
        update_parser.assert_called_once()
429
        exp = {'k1': 'v1', 'k2': 'v2'}
430
        apm.arguments = exp
431
        assert_dicts_are_equal(self, apm.arguments, exp)
432
        self.assertEqual(update_parser.mock_calls[-1], call())
433
        try:
434
            apm.arguments = None
435
        except Exception as e:
436
            self.assertTrue(isinstance(e, AssertionError))
437

    
438
    @patch('%s.ArgumentParseManager.parse' % arg_path)
439
    def test_parsed(self, parse):
440
        apm = argument.ArgumentParseManager('exe')
441
        self.assertEqual(apm.parsed, None)
442
        exp = 'you have been parsed'
443
        apm._parsed = exp
444
        self.assertEqual(apm.parsed, exp)
445
        apm._parser_modified = True
446
        apm._parsed = exp + ' v2'
447
        self.assertEqual(apm.parsed, exp + ' v2')
448
        self.assertEqual(parse.mock_calls, [call(), call()])
449

    
450
    @patch('%s.ArgumentParseManager.parse' % arg_path)
451
    def test_unparsed(self, parse):
452
        apm = argument.ArgumentParseManager('exe')
453
        self.assertEqual(apm.unparsed, None)
454
        exp = 'you have been unparsed'
455
        apm._unparsed = exp
456
        self.assertEqual(apm.unparsed, exp)
457
        apm._parser_modified = True
458
        apm._unparsed = exp + ' v2'
459
        self.assertEqual(apm.unparsed, exp + ' v2')
460
        self.assertEqual(parse.mock_calls, [call(), call()])
461

    
462
    @patch('%s.Argument.update_parser' % arg_path
463
        )
464
    def test_update_parser(self, update_parser):
465
        apm = argument.ArgumentParseManager('exe')
466
        body_count = len(update_parser.mock_calls)
467
        exp = len(argument._arguments)
468
        self.assertEqual(body_count, exp)
469
        apm.update_parser()
470
        exp = len(apm.arguments) + body_count
471
        body_count = len(update_parser.mock_calls)
472
        self.assertEqual(body_count, exp)
473
        expd = dict(
474
            k1=argument.Argument(0, parsed_name='-a'),
475
            k2=argument.Argument(0, parsed_name='-b'))
476
        apm.update_parser(expd)
477
        body_count = len(update_parser.mock_calls)
478
        self.assertEqual(body_count, exp + 2)
479

    
480
    def test_update_arguments(self):
481
        (inp, cor, exp) = (
482
            {'k1': 'v1', 'k2': 'v3'}, {'k2': 'v2'}, {'k1': 'v1', 'k2': 'v2'})
483
        apm = argument.ArgumentParseManager('exe')
484
        with patch(
485
                '%s.ArgumentParseManager.update_parser' % arg_path) as UP:
486
            apm.update_arguments(None)
487
            self.assertEqual(len(UP.mock_calls), 0)
488
            apm._arguments = inp
489
            apm.update_arguments(cor)
490
            assert_dicts_are_equal(self, apm.arguments, exp)
491
            UP.assert_called_once_with()
492

    
493
    def test_parse(self):
494
        apm = argument.ArgumentParseManager('exe')
495
        parsed, unparsed = apm.parser.parse_known_args()
496
        apm.parse()
497
        self.assertEqual(apm._parsed, parsed)
498
        self.assertEqual(apm.unparsed, unparsed)
499

    
500

    
501
if __name__ == '__main__':
502
    from sys import argv
503
    from kamaki.cli.test import runTestCase
504
    runTestCase(Argument, 'Argument', argv[1:])
505
    runTestCase(ConfigArgument, 'ConfigArgument', argv[1:])
506
    runTestCase(RuntimeConfigArgument, 'RuntimeConfigArgument', argv[1:])
507
    runTestCase(FlagArgument, 'FlagArgument', argv[1:])
508
    runTestCase(FlagArgument, 'ValueArgument', argv[1:])
509
    runTestCase(IntArgument, 'IntArgument', argv[1:])
510
    runTestCase(DateArgument, 'DateArgument', argv[1:])
511
    runTestCase(VersionArgument, 'VersionArgument', argv[1:])
512
    runTestCase(RepeatableArgument, 'RepeatableArgument', argv[1:])
513
    runTestCase(KeyValueArgument, 'KeyValueArgument', argv[1:])
514
    runTestCase(ProgressBarArgument, 'ProgressBarArgument', argv[1:])
515
    runTestCase(ArgumentParseManager, 'ArgumentParseManager', argv[1:])