Update Changelog for config unittests
[kamaki] / kamaki / cli / utils / test.py
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 TestCase
35 from tempfile import NamedTemporaryFile
36 from mock import patch, call
37 from itertools import product
38 from io import StringIO
39
40
41 class UtilsMethods(TestCase):
42
43     def assert_dicts_are_equal(self, d1, d2):
44         for k, v in d1.items():
45             self.assertTrue(k in d2)
46             if isinstance(v, dict):
47                 self.assert_dicts_are_equal(v, d2[k])
48             else:
49                 self.assertEqual(unicode(v), unicode(d2[k]))
50
51     def test_guess_mime_type(self):
52         from kamaki.cli.utils import guess_mime_type
53         from mimetypes import guess_type
54         for args in product(
55                 ('file.txt', 'file.png', 'file.zip', 'file.gz', None, 'X'),
56                 ('a type', None),
57                 ('an/encoding', None)):
58             filename, ctype, cencoding = args
59             if filename:
60                 exp_type, exp_enc = guess_type(filename)
61                 self.assertEqual(
62                     guess_mime_type(*args),
63                     (exp_type or ctype, exp_enc or cencoding))
64             else:
65                 self.assertRaises(AssertionError, guess_mime_type, *args)
66
67     @patch('kamaki.cli.utils.dumps', return_value='(dumps output)')
68     def test_print_json(self, JD):
69         from kamaki.cli.utils import print_json, INDENT_TAB
70         out = StringIO()
71         print_json('some data', out)
72         JD.assert_called_once_with('some data', indent=INDENT_TAB)
73         self.assertEqual(out.getvalue(), u'(dumps output)\n')
74
75     def test_print_dict(self):
76         from kamaki.cli.utils import print_dict, INDENT_TAB
77         out = StringIO()
78         self.assertRaises(AssertionError, print_dict, 'non-dict think')
79         self.assertRaises(AssertionError, print_dict, {}, indent=-10)
80         for args in product(
81                 (
82                     {'k1': 'v1'},
83                     {'k1': 'v1', 'k2': 'v2'},
84                     {'k1': 'v1', 'k2': 'v2', 'k3': 'v3'},
85                     {'k1': 'v1', 'k2': {'k1': 'v1', 'k2': 'v2'}, 'k3': 'v3'},
86                     {
87                         'k1': {'k1': 'v1', 'k2': 'v2'},
88                         'k2': [1, 2, 3],
89                         'k3': 'v3'},
90                     {
91                         'k1': {'k1': 'v1', 'k2': 'v2'},
92                         'k2': 42,
93                         'k3': {'k1': 1, 'k2': [1, 2, 3]}},
94                     {
95                         'k1': {
96                             'k1': 'v1',
97                             'k2': [1, 2, 3],
98                             'k3': {'k1': [(1, 2)]}},
99                         'k2': (3, 4, 5),
100                         'k3': {'k1': 1, 'k2': [1, 2, 3]}}),
101                 (tuple(), ('k1', ), ('k1', 'k2')),
102                 (0, 1, 2, 9), (False, True), (False, True)):
103             d, exclude, indent, with_enumeration, recursive_enumeration = args
104             with patch('kamaki.cli.utils.print_dict') as PD:
105                 with patch('kamaki.cli.utils.print_list') as PL:
106                     pd_calls, pl_calls = 0, 0
107                     print_dict(*args, out=out)
108                     exp_calls = u''
109                     for i, (k, v) in enumerate(d.items()):
110                         if k in exclude:
111                             continue
112                         str_k = u' ' * indent
113                         str_k += u'%s.' % (i + 1) if with_enumeration else u''
114                         str_k += u'%s:' % k
115                         if isinstance(v, dict):
116                             self.assertEqual(
117                                 PD.mock_calls[pd_calls],
118                                 call(
119                                     v,
120                                     exclude,
121                                     indent + INDENT_TAB,
122                                     recursive_enumeration,
123                                     recursive_enumeration,
124                                     out))
125                             pd_calls += 1
126                             exp_calls += str_k + '\n'
127                         elif isinstance(v, list) or isinstance(v, tuple):
128                             self.assertEqual(
129                                 PL.mock_calls[pl_calls],
130                                 call(
131                                     v,
132                                     exclude,
133                                     indent + INDENT_TAB,
134                                     recursive_enumeration,
135                                     recursive_enumeration,
136                                     out))
137                             pl_calls += 1
138                             exp_calls += str_k + '\n'
139                         else:
140                             exp_calls += u'%s %s\n' % (str_k, v)
141                     self.assertEqual(exp_calls, out.getvalue())
142                     out = StringIO()
143
144     def test_print_list(self):
145         from kamaki.cli.utils import print_list, INDENT_TAB
146         out = StringIO()
147         self.assertRaises(AssertionError, print_list, 'non-list non-tuple')
148         self.assertRaises(AssertionError, print_list, {}, indent=-10)
149         for args in product(
150                 (
151                     ['v1', ],
152                     ('v2', 'v3'),
153                     [1, '2', 'v3'],
154                     ({'k1': 'v1'}, 2, 'v3'),
155                     [(1, 2), 'v2', [(3, 4), {'k3': [5, 6], 'k4': 7}]]),
156                 (tuple(), ('v1', ), ('v1', 1), ('v1', 'k3')),
157                 (0, 1, 2, 9), (False, True), (False, True)):
158             l, exclude, indent, with_enumeration, recursive_enumeration = args
159             with patch('kamaki.cli.utils.print_dict') as PD:
160                 with patch('kamaki.cli.utils.print_list') as PL:
161                     pd_calls, pl_calls = 0, 0
162                     print_list(*args, out=out)
163                     exp_calls = ''
164                     for i, v in enumerate(l):
165                         str_v = u' ' * indent
166                         str_v += u'%s.' % (i + 1) if with_enumeration else u''
167                         if isinstance(v, dict):
168                             if with_enumeration:
169                                 exp_calls += str_v + '\n'
170                             elif i and i < len(l):
171                                 exp_calls += u'\n'
172                             self.assertEqual(
173                                 PD.mock_calls[pd_calls],
174                                 call(
175                                     v,
176                                     exclude,
177                                     indent + (
178                                         INDENT_TAB if with_enumeration else 0),
179                                     recursive_enumeration,
180                                     recursive_enumeration,
181                                     out))
182                             pd_calls += 1
183                         elif isinstance(v, list) or isinstance(v, tuple):
184                             if with_enumeration:
185                                 exp_calls += str_v + '\n'
186                             elif i and i < len(l):
187                                 exp_calls += u'\n'
188                             self.assertEqual(
189                                 PL.mock_calls[pl_calls],
190                                 call(
191                                     v,
192                                     exclude,
193                                     indent + INDENT_TAB,
194                                     recursive_enumeration,
195                                     recursive_enumeration,
196                                     out))
197                             pl_calls += 1
198                         elif ('%s' % v) in exclude:
199                             continue
200                         else:
201                             exp_calls += u'%s%s\n' % (str_v, v)
202                     self.assertEqual(out.getvalue(), exp_calls)
203                     out = StringIO()
204
205     @patch('kamaki.cli.utils.print_dict')
206     @patch('kamaki.cli.utils.print_list')
207     @patch('kamaki.cli.utils.bold', return_value='bold')
208     def test_print_items(self, bold, PL, PD):
209         from kamaki.cli.utils import print_items, INDENT_TAB
210         for args in product(
211                 (
212                     42, None, 'simple outputs',
213                     [1, 2, 3], {1: 1, 2: 2}, (3, 4),
214                     ({'k': 1, 'id': 2}, [5, 6, 7], (8, 9), '10')),
215                 (('id', 'name'), ('something', 2), ('lala', )),
216                 (False, True),
217                 (False, True)):
218             items, title, with_enumeration, with_redundancy = args
219             pl_counter, pd_counter = len(PL.mock_calls), len(PD.mock_calls)
220             bold_counter, out_counter = len(bold.mock_calls), 0
221             out = StringIO()
222             print_items(*args, out=out)
223             out.seek(0)
224             if not (isinstance(items, dict) or isinstance(
225                     items, list) or isinstance(items, tuple)):
226                 if items:
227                     self.assertEqual(out.getvalue(), '%s\n' % items)
228             else:
229                 for i, item in enumerate(items):
230                     if with_enumeration:
231                         exp_str = '%s. ' % (i + 1)
232                         self.assertEqual(out.read(len(exp_str)), exp_str)
233                     if isinstance(item, dict):
234                         title = sorted(set(title).intersection(item))
235                         pick = item.get if with_redundancy else item.pop
236                         header = ' '.join('%s' % pick(key) for key in title)
237                         if header:
238                             self.assertEqual(
239                                 bold.mock_calls[bold_counter], call(header))
240                             self.assertEqual(out.read(5), 'bold\n')
241                             bold_counter += 1
242                         self.assertEqual(
243                             PD.mock_calls[pd_counter],
244                             call(item, indent=INDENT_TAB, out=out))
245                         pd_counter += 1
246                     elif isinstance(item, list) or isinstance(item, tuple):
247                         self.assertEqual(
248                             PL.mock_calls[pl_counter],
249                             call(item, indent=INDENT_TAB, out=out))
250                         pl_counter += 1
251                     else:
252                         exp_str = u' %s\n' % item
253                         self.assertEqual(out.read(len(exp_str)), exp_str)
254
255     def test_format_size(self):
256         from kamaki.cli.utils import format_size
257         from kamaki.cli import CLIError
258         for v in ('wrong', {1: '1', 2: '2'}, ('tuples', 'not OK'), [1, 2]):
259             self.assertRaises(CLIError, format_size, v)
260         for step, B, K, M, G, T in (
261                 (1000, 'B', 'KB', 'MB', 'GB', 'TB'),
262                 (1024, 'B', 'KiB', 'MiB', 'GiB', 'TiB')):
263             Ki, Mi, Gi = step, step * step, step * step * step
264             for before, after in (
265                     (0, '0' + B), (512, '512' + B), (
266                         Ki - 1, '%s%s' % (step - 1, B)),
267                     (Ki, '1' + K), (42 * Ki, '42' + K), (
268                         Mi - 1, '%s.99%s' % (step - 1, K)),
269                     (Mi, '1' + M), (42 * Mi, '42' + M), (
270                         Ki * Mi - 1, '%s.99%s' % (step - 1, M)),
271                     (Gi, '1' + G), (42 * Gi, '42' + G), (
272                         Mi * Mi - 1, '%s.99%s' % (step - 1, G)),
273                     (Mi * Mi, '1' + T), (42 * Mi * Mi, '42' + T), (
274                         Mi * Gi - 1, '%s.99%s' % (step - 1, T)), (
275                         42 * Mi * Gi, '%s%s' % (42 * Ki, T))):
276                 self.assertEqual(format_size(before, step == 1000), after)
277
278     def test_to_bytes(self):
279         from kamaki.cli.utils import to_bytes
280         for v in ('wrong', 'KABUM', 'kbps', 'kibps'):
281             self.assertRaises(ValueError, to_bytes, v, 'B')
282             self.assertRaises(ValueError, to_bytes, 42, v)
283         for v in ([1, 2, 3], ('kb', 'mb'), {'kb': 1, 'byte': 2}):
284             self.assertRaises(TypeError, to_bytes, v, 'B')
285             self.assertRaises(AttributeError, to_bytes, 42, v)
286         kl, ki = 1000, 1024
287         for size, (unit, factor) in product(
288                 (0, 42, 3.14, 1023, 10000),
289                 (
290                     ('B', 1), ('b', 1),
291                     ('KB', kl), ('KiB', ki),
292                     ('mb', kl * kl), ('mIb', ki * ki),
293                     ('gB', kl * kl * kl), ('GIB', ki * ki * ki),
294                     ('TB', kl * kl * kl * kl), ('tiB', ki * ki * ki * ki))):
295             self.assertEqual(to_bytes(size, unit), int(size * factor))
296
297     def test_dict2file(self):
298         from kamaki.cli.utils import dict2file, INDENT_TAB
299         for d, depth in product((
300                     {'k': 42},
301                     {'k1': 'v1', 'k2': [1, 2, 3], 'k3': {'k': 'v'}},
302                     {'k1': {
303                         'k1.1': 'v1.1',
304                         'k1.2': [1, 2, 3],
305                         'k1.3': {'k': 'v'}}}),
306                 (-42, 0, 42)):
307             exp = ''
308             exp_d = []
309             exp_l = []
310             exp, exp_d, exp_l = '', [], []
311             with NamedTemporaryFile() as f:
312                 for k, v in d.items():
313                     sfx = '\n'
314                     if isinstance(v, dict):
315                         exp_d.append(call(v, f, depth + 1))
316                     elif isinstance(v, tuple) or isinstance(v, list):
317                         exp_l.append(call(v, f, depth + 1))
318                     else:
319                         sfx = '%s\n' % v
320                     exp += '%s%s: %s' % (
321                         ' ' * (depth * INDENT_TAB), k, sfx)
322                 with patch('kamaki.cli.utils.dict2file') as D2F:
323                     with patch('kamaki.cli.utils.list2file') as L2F:
324                         dict2file(d, f, depth)
325                         f.seek(0)
326                         self.assertEqual(f.read(), exp)
327                         self.assertEqual(L2F.mock_calls, exp_l)
328                         self.assertEqual(D2F.mock_calls, exp_d)
329
330     def test_list2file(self):
331         from kamaki.cli.utils import list2file, INDENT_TAB
332         for l, depth in product(
333                 (
334                     (1, 2, 3),
335                     [1, 2, 3],
336                     ('v', [1, 2, 3], (1, 2, 3), {'1': 1, 2: '2', 3: 3}),
337                     ['v', {'k1': 'v1', 'k2': [1, 2, 3], 'k3': {1: '1'}}]),
338                 (-42, 0, 42)):
339             with NamedTemporaryFile() as f:
340                 exp, exp_d, exp_l = '', [], []
341                 for v in l:
342                     if isinstance(v, dict):
343                         exp_d.append(call(v, f, depth + 1))
344                     elif isinstance(v, list) or isinstance(v, tuple):
345                         exp_l.append(call(v, f, depth + 1))
346                     else:
347                         exp += '%s%s\n' % (' ' * INDENT_TAB * depth, v)
348                 with patch('kamaki.cli.utils.dict2file') as D2F:
349                     with patch('kamaki.cli.utils.list2file') as L2F:
350                         list2file(l, f, depth)
351                         f.seek(0)
352                         self.assertEqual(f.read(), exp)
353                         self.assertEqual(L2F.mock_calls, exp_l)
354                         self.assertEqual(D2F.mock_calls, exp_d)
355
356     def test__parse_with_regex(self):
357         from re import compile as r_compile
358         from kamaki.cli.utils import _parse_with_regex
359         for args in product(
360                 (
361                     'this is a line',
362                     'this_is_also_a_line',
363                     'This "text" is quoted',
364                     'This "quoted" "text" is more "complicated"',
365                     'Is this \'quoted\' text "double \'quoted\' or not?"',
366                     '"What \'about\' the" oposite?',
367                     ' Try with a " single double quote',
368                     'Go "down \'deep " deeper \'bottom \' up" go\' up" !'),
369                 (
370                     '\'.*?\'|".*?"|^[\S]*$',
371                     r'"([A-Za-z0-9_\./\\-]*)"',
372                     r'\"(.+?)\"',
373                     '\\^a\\.\\*\\$')):
374             r_parser = r_compile(args[1])
375             self.assertEqual(
376                 _parse_with_regex(*args),
377                 (r_parser.split(args[0]), r_parser.findall(args[0])))
378
379     def test_split_input(self):
380         from kamaki.cli.utils import split_input
381         for line, expected in (
382                 ('set key="v1"', ['set', 'key=v1']),
383                 ('unparsable', ['unparsable']),
384                 ('"parsable"', ['parsable']),
385                 ('"parse" out', ['parse', 'out']),
386                 ('"one', ['"one']),
387                 ('two" or" more"', ['two or', 'more"']),
388                 ('Go "down \'deep " deeper \'bottom \' up" go\' up" !', [
389                     'Go', "down 'deep ", 'deeper', 'bottom ',
390                     'up go\' up', '!']),
391                 ('Is "this" a \'parsed\' string?', [
392                     'Is', 'this', 'a', 'parsed', 'string?'])):
393             self.assertEqual(split_input(line), expected)
394
395     def test_ask_user(self):
396         from kamaki.cli.utils import ask_user
397         msg = u'some question'
398         out = StringIO()
399         user_in = StringIO(u'n')
400         self.assertFalse(ask_user(msg, out=out, user_in=user_in))
401         self.assertEqual(out.getvalue(), u'%s [y/N]: ' % msg)
402
403         user_in.seek(0)
404         out.seek(0)
405         self.assertTrue(ask_user(msg, ('n', ), out=out, user_in=user_in))
406         self.assertEqual(out.getvalue(), u'%s [n/<not n>]: ' % msg)
407
408         user_in = StringIO(unicode('N'))
409         out.seek(0)
410         self.assertTrue(ask_user(msg, ('r', 'N'), out=out, user_in=user_in))
411         self.assertEqual(out.getvalue(), u'%s [r, N/<not r, N>]: ' % msg)
412
413     def test_remove_from_items(self):
414         from kamaki.cli.utils import remove_from_items
415         for v in ('wrong', [1, 2, 3], [{}, 2, {}]):
416             self.assertRaises(AssertionError, remove_from_items, v, 'none')
417         d = dict(k1=1, k2=dict(k2=2, k3=3), k3=3, k4=4)
418         for k in (d.keys() + ['kN']):
419             tmp1, tmp2 = dict(d), dict(d)
420             remove_from_items([tmp1, ], k)
421             tmp1.pop(k, None)
422             self.assert_dicts_are_equal(tmp1, tmp2)
423         for k in (d.keys() + ['kN']):
424             tmp1, tmp2 = dict(d), dict(d)
425             remove_from_items([tmp1, tmp2], k)
426             self.assert_dicts_are_equal(tmp1, tmp2)
427
428     def test_filter_dicts_by_dict(self):
429         from kamaki.cli.utils import filter_dicts_by_dict
430
431         dlist = [
432             dict(k1='v1', k2='v2', k3='v3'),
433             dict(k1='v1'),
434             dict(k2='v2', k3='v3'),
435             dict(k1='V1', k3='V3'),
436             dict()]
437         for l, f, em, cs, exp in (
438                 (dlist, dlist[2], True, False, dlist[0:1] + dlist[2:3]),
439                 (dlist, dlist[1], True, False, dlist[0:2] + dlist[3:4]),
440                 (dlist, dlist[1], True, True, dlist[0:2]),
441                 (dlist, {'k3': 'v'}, True, False, []),
442                 (dlist, {'k3': 'v'}, False, False, dlist[0:1] + dlist[2:4]),
443                 (dlist, {'k3': 'v'}, False, True, dlist[0:1] + dlist[2:3]),
444                 (dlist, {'k3': 'v'}, True, True, []),
445                 (dlist, dlist[4], True, False, dlist),
446                 ):
447             self.assertEqual(exp, filter_dicts_by_dict(l, f, em, cs))
448
449
450 if __name__ == '__main__':
451     from sys import argv
452     from kamaki.cli.test import runTestCase
453     runTestCase(UtilsMethods, 'UtilsMethods', argv[1:])