Completed
Push — master ( 2a9ae1...786e1e )
by De
57s
created

GetFuncByNameTests   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 52
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 16
c 1
b 0
f 0
dl 0
loc 52
rs 10

8 Methods

Rating   Name   Duplication   Size   Complexity  
A test_get_lambda_by_name() 0 3 2
A test_get_class_func_by_name() 0 4 2
A test_inexisting_func() 0 3 1
A check_get_func_by_name() 0 15 3
A test_get_builtin_attr_by_name() 0 4 2
A get_func_by_name() 0 3 1
A test_get_custom_func_by_name() 0 4 2
A test_get_builtin_by_name() 0 6 3
1
# -*- coding: utf-8
2
"""Unit tests for code in didyoumean_internal.py."""
3
from didyoumean_internal import get_suggestion_string,\
4
    add_string_to_exception, get_func_by_name,\
5
    get_objects_in_frame, get_subclasses, get_types_for_str,\
6
    get_types_for_str_using_inheritance,\
7
    get_types_for_str_using_names
8
import didyoumean_common_tests as common
9
from didyoumean_common_tests import CommonTestOldStyleClass2,\
10
    CommonTestNewStyleClass2  # to have these 2 in defined names
11
import unittest2
12
import itertools
13
import sys
14
15
16
OLD_CLASS_SUPPORT = sys.version_info >= (3, 0)
17
IS_PYPY = hasattr(sys, "pypy_translation_info")
18
U_PREFIX_SUPPORT = not ((3, 0) <= sys.version_info < (3, 3))
19
U_PREFIX = "u" if U_PREFIX_SUPPORT else ""
20
global_var = 42  # Please don't change the value
21
22
23
class QuoteTests(unittest2.TestCase):
24
    """Class for tests related to quote."""
25
26
    def quote_empty_str(self):
27
        """Test quote on empty string."""
28
        self.assertEqual(quote(''), "''")
29
30
    def quote_str(self):
31
        """Test quote on non-empty string."""
32
        self.assertEqual(quote('abc'), "'abc'")
33
34
35
class GetObjectInFrameTests(unittest2.TestCase):
36
    """Class for tests related to frame/backtrace/etc inspection.
37
38
    Tested functions are : get_objects_in_frame.
39
    No tests about 'nonlocal' is written because it is only supported
40
    from Python 3.
41
    """
42
43
    def name_corresponds_to(self, name, expected):
44
        """Helper functions to test get_objects_in_frame.
45
46
        Check that the name corresponds to the expected objects (and their
47
        scope) in the frame of calling function.
48
        None can be used to match any object as it can be hard to describe
49
        an object when it is hidden by something in a closer scope.
50
        Also, extra care is to be taken when calling the function because
51
        giving value by names might affect the result (adding in local
52
        scope).
53
        """
54
        frame = sys._getframe(1)  # frame of calling function
55
        lst = get_objects_in_frame(frame).get(name, [])
56
        self.assertEqual(len(lst), len(expected))
57
        for scopedobj, exp in zip(lst, expected):
58
            obj, scope = scopedobj
59
            expobj, expscope = exp
60
            self.assertEqual(scope, expscope, name)
61
            if expobj is not None:
62
                self.assertEqual(obj, expobj, name)
63
64
    def test_builtin(self):
65
        """Test with builtin."""
66
        builtin = len
67
        name = builtin.__name__
68
        self.name_corresponds_to(name, [(builtin, 'builtin')])
69
70
    def test_builtin2(self):
71
        """Test with builtin."""
72
        name = 'True'
73
        self.name_corresponds_to(name, [(bool(1), 'builtin')])
74
75
    def test_global(self):
76
        """Test with global."""
77
        name = 'global_var'
78
        self.name_corresponds_to(name, [(42, 'global')])
79
80
    def test_local(self):
81
        """Test with local."""
82
        name = 'toto'
83
        self.name_corresponds_to(name, [])
84
        toto = 0
85
        self.name_corresponds_to(name, [(0, 'local')])
86
87
    def test_local_and_global(self):
88
        """Test with local hiding a global."""
89
        name = 'global_var'
90
        self.name_corresponds_to(name, [(42, 'global')])
91
        global_var = 1
92
        self.name_corresponds_to(name, [(1, 'local'), (42, 'global')])
93
94
    def test_global_keword(self):
95
        """Test with global keyword."""
96
        # Funny detail : the global keyword works even if at the end of
97
        # the function (after the code it affects) but this raises a
98
        # SyntaxWarning.
99
        global global_var
100
        name = 'global_var'
101
        global_var = 42  # value is unchanged
102
        self.name_corresponds_to(name, [(42, 'global')])
103
104
    def test_del_local(self):
105
        """Test with deleted local."""
106
        name = 'toto'
107
        self.name_corresponds_to(name, [])
108
        toto = 0
109
        self.name_corresponds_to(name, [(0, 'local')])
110
        del toto
111
        self.name_corresponds_to(name, [])
112
113
    def test_del_local_hiding_global(self):
114
        """Test with deleted local hiding a global."""
115
        name = 'global_var'
116
        glob_desc = [(42, 'global')]
117
        local_desc = [(1, 'local')]
118
        self.name_corresponds_to(name, glob_desc)
119
        global_var = 1
120
        self.name_corresponds_to(name, local_desc + glob_desc)
121
        del global_var
122
        self.name_corresponds_to(name, glob_desc)
123
124
    def test_enclosing(self):
125
        """Test with nested functions."""
126
        foo = 1
127
        bar = 2
128
129
        def nested_func(foo, baz):
130
            qux = 5
131
            self.name_corresponds_to('qux', [(5, 'local')])
132
            self.name_corresponds_to('baz', [(4, 'local')])
133
            self.name_corresponds_to('foo', [(3, 'local')])
134
            self.name_corresponds_to('bar', [])
135
            self.name_corresponds_to(
136
                'global_var', [(42, 'global')])
137
        nested_func(3, 4)
138
        self.name_corresponds_to('nested_func', [(nested_func, 'local')])
139
        self.name_corresponds_to('foo', [(1, 'local')])
140
        self.name_corresponds_to('baz', [])
141
142
    def test_enclosing2(self):
143
        """Test with nested functions."""
144
        bar = 2
145
146
        def nested_func():
147
            self.name_corresponds_to('bar', [])
148
            bar = 3
149
            self.name_corresponds_to('bar', [(3, 'local')])
150
151
        nested_func()
152
        self.name_corresponds_to('nested_func', [(nested_func, 'local')])
153
154
    def test_enclosing3(self):
155
        """Test with nested functions."""
156
        bar = 2
157
158
        def nested_func():
159
            self.name_corresponds_to('bar', [(2, 'local')])
160
            tmp = bar
161
            self.name_corresponds_to('bar', [(2, 'local')])
162
163
        nested_func()
164
        self.name_corresponds_to('nested_func', [(nested_func, 'local')])
165
166
    def test_enclosing4(self):
167
        """Test with nested functions."""
168
        global_var = 1
169
170
        def nested_func():
171
            self.name_corresponds_to('global_var', [(42, 'global')])
172
173
        nested_func()
174
        self.name_corresponds_to('global_var', [(1, 'local'), (42, 'global')])
175
176
    def test_enclosing5(self):
177
        """Test with nested functions."""
178
        bar = 2
179
        foo = 3
180
181
        def nested_func():
182
            bar = 4
183
            baz = 5
184
            self.name_corresponds_to('foo', [])
185
            self.name_corresponds_to('bar', [(4, 'local')])
186
187
            def nested_func2():
188
                self.name_corresponds_to('foo', [])
189
                self.name_corresponds_to('bar', [])
190
191
            nested_func2()
192
193
        nested_func()
194
        self.name_corresponds_to('nested_func', [(nested_func, 'local')])
195
196
197
class OldStyleBaseClass:
198
    """Dummy class for testing purposes."""
199
200
    pass
201
202
203
class OldStyleDerivedClass(OldStyleBaseClass):
204
    """Dummy class for testing purposes."""
205
206
    pass
207
208
209
class NewStyleBaseClass(object):
210
    """Dummy class for testing purposes."""
211
212
    pass
213
214
215
class NewStyleDerivedClass(NewStyleBaseClass):
216
    """Dummy class for testing purposes."""
217
218
    pass
219
220
221
def a_function():
222
    """Dummy function for testing purposes."""
223
    pass
224
225
226
def a_generator():
227
    """Dummy generator for testing purposes."""
228
    yield 1
229
230
231
NEW_STYLE_CLASSES = [bool, int, float, str, tuple, list, set, dict, object,
232
                     NewStyleBaseClass, NewStyleDerivedClass,
233
                     common.CommonTestNewStyleClass,
234
                     common.CommonTestNewStyleClass2,
235
                     type(a_function), type(a_generator),
236
                     type(len), type(None), type(type(None)),
237
                     type(object), type(sys), type(range),
238
                     type(NewStyleBaseClass), type(NewStyleDerivedClass),
239
                     type(OldStyleBaseClass), type(OldStyleDerivedClass)]
240
OLD_STYLE_CLASSES = [OldStyleBaseClass, OldStyleDerivedClass,
241
                     CommonTestOldStyleClass2]
242
CLASSES = [(c, True) for c in NEW_STYLE_CLASSES] + \
243
    [(c, False) for c in OLD_STYLE_CLASSES]
244
245
246
class GetTypesForStrTests(unittest2.TestCase):
247
    """Test get_types_for_str."""
248
249
    def test_get_subclasses(self):
250
        """Test the get_subclasses function.
251
252
        All types are found when looking for subclasses of object, except
253
        for the old style classes on Python 2.x.
254
        """
255
        all_classes = get_subclasses(object)
256
        for typ, new in CLASSES:
257
            self.assertTrue(typ in get_subclasses(typ))
258
            if new or OLD_CLASS_SUPPORT:
259
                self.assertTrue(typ in all_classes)
260
            else:
261
                self.assertFalse(typ in all_classes)
262
        self.assertFalse(0 in all_classes)
263
264
    def test_get_types_for_str_using_inheritance(self):
265
        """Test the get_types_for_str_using_inheritance function.
266
267
        All types are found when looking for subclasses of object, except
268
        for the old style classes on Python 2.x.
269
270
        Also, it seems like the returns is (almost) always precise as the
271
        returned set contains only the expected type and nothing else.
272
        """
273
        for typ, new in CLASSES:
274
            types = get_types_for_str_using_inheritance(typ.__name__)
275
            if new or OLD_CLASS_SUPPORT:
276
                self.assertEqual(types, set([typ]), typ)
277
            else:
278
                self.assertEqual(types, set(), typ)
279
280
        self.assertFalse(get_types_for_str_using_inheritance('faketype'))
281
282
    def get_types_using_names(self, type_str):
283
        """Wrapper around the get_types_using_names function."""
284
        return get_types_for_str_using_names(type_str, sys._getframe(1))
285
286
    def test_get_types_for_str_using_names(self):
287
        """Test the get_types_using_names function.
288
289
        Old style classes are retrieved even on Python 2.x.
290
        However, a few builtin types are not in the names so can't be found.
291
        """
292
        for typ in OLD_STYLE_CLASSES:
293
            types = self.get_types_using_names(typ.__name__)
294
            self.assertEqual(types, set([typ]), typ)
295
        for n in ['generator', 'module', 'function', 'faketype']:
296
            self.assertEqual(self.get_types_using_names(n), set(), n)
297
        n = 'NoneType'
298
        if IS_PYPY:
299
            self.assertEqual(len(self.get_types_using_names(n)), 1, n)
300
        else:
301
            self.assertEqual(self.get_types_using_names(n), set(), n)
302
303
    def get_types_for_str(self, type_str):
304
        """Wrapper around the get_types_for_str function."""
305
        return get_types_for_str(type_str, sys._getframe(1))
306
307
    def test_get_types_for_str(self):
308
        """Test the get_types_for_str function.
309
310
        Check that for all tested types, the proper type is retrieved.
311
        """
312
        for typ, _ in CLASSES:
313
            types = self.get_types_for_str(typ.__name__)
314
            self.assertEqual(types, set([typ]), typ)
315
316
        self.assertEqual(self.get_types_for_str('faketype'), set())
317
318
    def test_get_types_for_str2(self):
319
        """Test the get_types_for_str function.
320
321
        Check that for all tested strings, a single type is retrived.
322
        This is useful to ensure that we are using the right names.
323
        """
324
        for n in ['module', 'NoneType', 'function',
325
                  'NewStyleBaseClass', 'NewStyleDerivedClass',
326
                  'OldStyleBaseClass', 'OldStyleDerivedClass']:
327
            types = self.get_types_for_str(n)
328
            self.assertEqual(len(types), 1, n)
329
        for n in ['generator']:  # FIXME: with pypy, we find an additional type
330
            types = self.get_types_for_str(n)
331
            self.assertEqual(len(types), 2 if IS_PYPY else 1, n)
332
333
    def test_old_class_not_in_namespace(self):
334
        """Test the get_types_for_str function.
335
336
        Check that at the moment, CommonTestOldStyleClass is not found
337
        because it is not in the namespace. This behavior is to be improved.
338
        """
339
        typ = common.CommonTestOldStyleClass
340
        expect_with_inherit = set([typ]) if OLD_CLASS_SUPPORT else set()
341
        name = typ.__name__
342
        types1 = get_types_for_str_using_inheritance(name)
343
        types2 = self.get_types_using_names(name)
344
        types3 = self.get_types_for_str(name)
345
        self.assertEqual(types1, expect_with_inherit)
346
        self.assertEqual(types2, set())
347
        self.assertEqual(types3, expect_with_inherit)
348
349
350
class GetFuncByNameTests(unittest2.TestCase):
351
    """Test get_func_by_name."""
352
353
    def get_func_by_name(self, func_name):
354
        """Wrapper around the get_func_by_name function."""
355
        return get_func_by_name(func_name, sys._getframe(1))
356
357
    def check_get_func_by_name(self, function, exact_match=True):
358
        """Wrapper around the get_func_by_name to check its result."""
359
        self.assertTrue(hasattr(function, '__call__'), function)
360
        self.assertTrue(hasattr(function, '__name__'), function)
361
        res = self.get_func_by_name(function.__name__)
362
        self.assertTrue(function in res)
363
        self.assertTrue(len(res) >= 1, res)
364
        if exact_match:
365
            # Equality above does not hold
366
            # Using set is complicated because everything can't be hashed
367
            # But using id, something seems to be possible
368
            self.assertEqual(len(set(res)), 1, res)
369
            res_ids = [id(e) for e in res]
370
            set_ids = set(res_ids)
371
            self.assertEqual(len(set_ids), 1, set_ids)
372
373
    def test_get_builtin_by_name(self):
374
        """Test get_func_by_name on builtin functions."""
375
        for f in [bool, int, float, str, tuple, list, set, dict, all]:
376
            self.check_get_func_by_name(f)
377
        for f in [object]:
378
            self.check_get_func_by_name(f, False)
379
380
    def test_get_builtin_attr_by_name(self):
381
        """Test get_func_by_name on builtin attributes."""
382
        for f in [dict.get]:
383
            self.check_get_func_by_name(f, False)
384
385
    def test_get_lambda_by_name(self):
386
        """Test get_func_by_name on lambda functions."""
387
        self.check_get_func_by_name(lambda x: x)
388
389
    def test_get_custom_func_by_name(self):
390
        """Test get_func_by_name on custom functions."""
391
        for f in [a_function, a_generator]:
392
            self.check_get_func_by_name(f)
393
394
    def test_get_class_func_by_name(self):
395
        """Test get_func_by_name on custom functions."""
396
        for f, new in CLASSES:
397
            self.check_get_func_by_name(f, False)
398
399
    def test_inexisting_func(self):
400
        """Test get_func_by_name on an inexisting function name."""
401
        self.assertEqual(self.get_func_by_name('dkalskjdas'), [])
402
403
404
class GetSuggStringTests(unittest2.TestCase):
405
    """Tests about get_suggestion_string."""
406
407
    def test_no_sugg(self):
408
        """Empty list of suggestions."""
409
        self.assertEqual(get_suggestion_string(()), "")
410
411
    def test_one_sugg(self):
412
        """Single suggestion."""
413
        self.assertEqual(get_suggestion_string(('0',)), ". Did you mean 0?")
414
415
    def test_same_sugg(self):
416
        """Identical suggestion."""
417
        self.assertEqual(
418
            get_suggestion_string(('0', '0')), ". Did you mean 0, 0?")
419
420
    def test_multiple_suggs(self):
421
        """Multiple suggestions."""
422
        self.assertEqual(
423
            get_suggestion_string(('0', '1')), ". Did you mean 0, 1?")
424
425
426
class AddStringToExcTest(common.TestWithStringFunction):
427
    """Generic class for tests about add_string_to_exception."""
428
429
    prefix_repr = ""
430
    suffix_repr = ""
431
    check_str_sum = True
432
433
    def get_exception(self):
434
        """Abstract method to get an instance of exception."""
435
        raise NotImplementedError("'get_exception' needs to be implemented")
436
437
    def get_exc_before_and_after(self, string, func):
438
        """Retrieve string representations of exceptions.
439
440
        Retrieve string representations of exceptions raised by code
441
        before and after calling add_string_to_exception.
442
        """
443
        value = self.get_exception()
444
        before = func(value)
445
        add_string_to_exception(value, string)
446
        after = func(value)
447
        return (before, after)
448
449
    def check_string_added(self, func, string, prefix="", suffix=""):
450
        """Check that add_string_to_exception adds the strings."""
451
        s1, s2 = self.get_exc_before_and_after(string, func)
452
        self.assertStringAdded(
453
            prefix + string + suffix, s1, s2, self.check_str_sum)
454
455
    def test_add_empty_string_to_str(self):
456
        """Empty string added to error's str value."""
457
        self.check_string_added(str, "")
458
459
    def test_add_empty_string_to_repr(self):
460
        """Empty string added to error's repr value."""
461
        self.check_string_added(repr, "")
462
463
    def test_add_string_to_str(self):
464
        """Non-empty string added to error's str value."""
465
        self.check_string_added(str, "ABCDEstr")
466
467
    def test_add_string_to_repr(self):
468
        """Non-empty string added to error's repr value."""
469
        self.check_string_added(
470
            repr, "ABCDErepr", self.prefix_repr, self.suffix_repr)
471
472
473
class AddStringToExcFromCodeTest(AddStringToExcTest):
474
    """Generic class for tests about add_string_to_exception.
475
476
    The tested function is called on an exception created by running
477
    some failing code (`self.code`) and catching what it throws.
478
    """
479
480
    code = NotImplemented
481
482
    def get_exception(self):
483
        """Get the exception by running the code and catching errors."""
484
        type_, value, _ = common.get_exception(self.code)
485
        self.assertTrue(
486
            issubclass(type_, self.error_type),
487
            "{0} ({1}) not a subclass of {2}"
488
            .format(type_, value, self.error_type))
489
        return value
490
491
492
class AddStringToNameErrorTest(unittest2.TestCase, AddStringToExcFromCodeTest):
493
    """Class for tests of add_string_to_exception on NameError."""
494
495
    code = 'babar = 0\nbaba'
496
    error_type = NameError
497
498
499
class AddStringToTypeErrorTest(unittest2.TestCase, AddStringToExcFromCodeTest):
500
    """Class for tests of add_string_to_exception on TypeError."""
501
502
    code = '[0](0)'
503
    error_type = TypeError
504
505
506
class AddStringToImportErrorTest(
507
        unittest2.TestCase, AddStringToExcFromCodeTest):
508
    """Class for tests of add_string_to_exception on ImportError."""
509
510
    code = 'import maths'
511
    error_type = ImportError
512
513
514
class AddStringToKeyErrorTest(
515
        unittest2.TestCase, AddStringToExcFromCodeTest):
516
    """Class for tests of add_string_to_exception on KeyError."""
517
518
    code = 'dict()["ffdsqmjklfqsd"]'
519
    error_type = KeyError
520
521
522
class AddStringToAttributeErrorTest(
523
        unittest2.TestCase, AddStringToExcFromCodeTest):
524
    """Class for tests of add_string_to_exception on AttributeError."""
525
526
    code = '[].does_not_exist'
527
    error_type = AttributeError
528
529
530
class AddStringToSyntaxErrorTest(
531
        unittest2.TestCase, AddStringToExcFromCodeTest):
532
    """Class for tests of add_string_to_exception on SyntaxError."""
533
534
    code = 'return'
535
    error_type = SyntaxError
536
537
538
class AddStringToMemoryErrorTest(
539
        unittest2.TestCase, AddStringToExcFromCodeTest):
540
    """Class for tests of add_string_to_exception on MemoryError."""
541
542
    code = '[0] * 999999999999999'
543
    error_type = MemoryError
544
    prefix_repr = "'"
545
    suffix_repr = "',"
546
547
548
class AddStringToIOErrorTest(
549
        unittest2.TestCase, AddStringToExcFromCodeTest):
550
    """Class for tests of add_string_to_exception on NoFileIoError."""
551
552
    code = 'with open("/does_not_exist") as f:\n\tpass'
553
    error_type = common.NoFileIoError
554
555
556
class AddStringToUnicodeDecodeTest(
557
        unittest2.TestCase, AddStringToExcFromCodeTest):
558
    """Class for tests of add_string_to_exception on UnicodeDecodeError."""
559
560
    code = "'foo'.encode('utf-16').decode('utf-8')"
561
    error_type = UnicodeDecodeError
562
563
564
class AddStringToUnicodeEncodeTest(
565
        unittest2.TestCase, AddStringToExcFromCodeTest):
566
    """Class for tests of add_string_to_exception on UnicodeEncodeError."""
567
568
    code = U_PREFIX + '"\u0411".encode("iso-8859-15")'
569
    error_type = UnicodeEncodeError
570
571
572
class AddStringToExcFromInstanceTest(AddStringToExcTest):
573
    """Generic class for tests about add_string_to_exception.
574
575
    The tested function is called on an exception created by calling the
576
    constructor (`self.exc_type`) with the right arguments (`self.args`).
577
    Because of the way it creates exception, the tests are somewhat artificial
578
    (compared to AddStringToExcFromCodeTest for instance). However, the major
579
    advantage is that they can be easily generated (to have all subclasses of
580
    Exception tested).
581
    """
582
583
    check_str_sum = False
584
    exc_type = NotImplemented
585
    args = NotImplemented
586
587
    def get_exception(self):
588
        """Get the exception by calling the constructor with correct args."""
589
        return self.exc_type(*self.args)
590
591
592
class AddStringToZeroDivisionError(
593
        unittest2.TestCase, AddStringToExcFromInstanceTest):
594
    """Class for tests of add_string_to_exception on ZeroDivisionError."""
595
596
    exc_type = ZeroDivisionError
597
    args = ('', '', '', '', '')
598
599
600
def get_instance(klass):
601
    """Get instance for class by bruteforcing the parameters.
602
603
    Construction is attempted with a decreasing number of arguments so that
604
    the instanciated object has as many non-null attributes set as possible.
605
    This is important not for the creation but when the object gets used
606
    later on. Also, the order of the values has its importance for similar
607
    reasons.
608
    """
609
    my_unicode = str if sys.version_info >= (3, 0) else unicode
610
    values_tried = [my_unicode(), bytes(), 0]
611
    for nb_arg in reversed(range(6)):
612
        for p in itertools.product(values_tried, repeat=nb_arg):
613
            try:
614
                return klass(*p), p
615
            except (TypeError, AttributeError) as e:
616
                pass
617
            except Exception as e:
618
                print(type(e), e)
619
    return None
620
621
622
def generate_add_string_to_exc_tests():
623
    """Generate tests for add_string_to_exception.
624
625
    This function dynamically creates tests cases for the function
626
    add_string_to_exception for as many Exception subclasses as possible.
627
    This is not used at the moment because the generated classes need to
628
    be added in the global namespace and there is no good way to do this.
629
    However, it may be a good idea to call this when new versions of
630
    Python are released to ensure we handle all exceptions properly (and
631
    find the tests to be added manually if need be).
632
    """
633
    for klass in get_subclasses(Exception):
634
        r = get_instance(klass)
635
        if r is not None:
636
            _, p = r
637
            class_name = ("NameForAddStringToExcFromInstanceTest" +
638
                          klass.__name__ + str(id(klass)))
639
            assert class_name not in globals(), class_name
640
            globals()[class_name] = type(
641
                    class_name,
642
                    (AddStringToExcFromInstanceTest, unittest2.TestCase),
643
                    {'exc_type': klass, 'args': p})
644
645
646
if __name__ == '__main__':
647
    print(sys.version_info)
648
    unittest2.main()
649