AddStringToUnicodeDecodeTest   A
last analyzed

Complexity

Total Complexity 0

Size/Duplication

Total Lines 6
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 0
c 0
b 0
f 0
dl 0
loc 6
rs 10
1
# -*- coding: utf-8
2
"""Unit tests for code in didyoumean_internal.py."""
3
from didyoumean_internal import quote, 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 test_quote_empty_str(self):
27
        """Test quote on empty string."""
28
        self.assertEqual(quote(''), "''")
29
30
    def test_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  # noqa
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  # noqa
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  # noqa
127
        bar = 2  # noqa
128
129
        def nested_func(foo, baz):
130
            qux = 5  # noqa
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  # noqa
145
146
        def nested_func():
147
            self.name_corresponds_to('bar', [])
148
            bar = 3  # noqa
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  # noqa
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  # noqa
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  # noqa
179
        foo = 3  # noqa
180
181
        def nested_func():
182
            bar = 4  # noqa
183
            baz = 5  # noqa
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, '__name__'), function)
360
        res = self.get_func_by_name(function.__name__)
361
        self.assertTrue(function in res)
362
        self.assertTrue(len(res) >= 1, res)
363
        if exact_match:
364
            # Equality above does not hold
365
            # Using set is complicated because everything can't be hashed
366
            # But using id, something seems to be possible
367
            self.assertEqual(len(set(res)), 1, res)
368
            res_ids = [id(e) for e in res]
369
            set_ids = set(res_ids)
370
            self.assertEqual(len(set_ids), 1, set_ids)
371
372
    def test_get_builtin_by_name(self):
373
        """Test get_func_by_name on builtin functions."""
374
        for f in [bool, int, float, str, tuple, list, set, dict, all]:
375
            self.check_get_func_by_name(f)
376
        for f in [object]:
377
            self.check_get_func_by_name(f, False)
378
379
    def test_get_builtin_attr_by_name(self):
380
        """Test get_func_by_name on builtin attributes."""
381
        for f in [dict.get]:
382
            self.check_get_func_by_name(f, False)
383
384
    def test_get_lambda_by_name(self):
385
        """Test get_func_by_name on lambda functions."""
386
        self.check_get_func_by_name(lambda x: x)
387
388
    def test_get_custom_func_by_name(self):
389
        """Test get_func_by_name on custom functions."""
390
        for f in [a_function, a_generator]:
391
            self.check_get_func_by_name(f)
392
393
    def test_get_class_func_by_name(self):
394
        """Test get_func_by_name on custom functions."""
395
        for f, new in CLASSES:
396
            self.check_get_func_by_name(f, False)
397
398
    def test_inexisting_func(self):
399
        """Test get_func_by_name on an inexisting function name."""
400
        self.assertEqual(self.get_func_by_name('dkalskjdas'), [])
401
402
403
class GetSuggStringTests(unittest2.TestCase):
404
    """Tests about get_suggestion_string."""
405
406
    def test_no_sugg(self):
407
        """Empty list of suggestions."""
408
        self.assertEqual(get_suggestion_string(()), "")
409
410
    def test_one_sugg(self):
411
        """Single suggestion."""
412
        self.assertEqual(get_suggestion_string(('0',)), ". Did you mean 0?")
413
414
    def test_same_sugg(self):
415
        """Identical suggestion."""
416
        self.assertEqual(
417
            get_suggestion_string(('0', '0')), ". Did you mean 0, 0?")
418
419
    def test_multiple_suggs(self):
420
        """Multiple suggestions."""
421
        self.assertEqual(
422
            get_suggestion_string(('0', '1')), ". Did you mean 0, 1?")
423
424
425
class AddStringToExcTest(common.TestWithStringFunction):
426
    """Generic class for tests about add_string_to_exception."""
427
428
    prefix_repr = ""
429
    suffix_repr = ""
430
    check_str_sum = True
431
432
    def get_exception(self):
433
        """Abstract method to get an instance of exception."""
434
        raise NotImplementedError("'get_exception' needs to be implemented")
435
436
    def get_exc_before_and_after(self, string, func):
437
        """Retrieve string representations of exceptions.
438
439
        Retrieve string representations of exceptions raised by code
440
        before and after calling add_string_to_exception.
441
        """
442
        value = self.get_exception()
443
        before = func(value)
444
        add_string_to_exception(value, string)
445
        after = func(value)
446
        return (before, after)
447
448
    def check_string_added(self, func, string, prefix="", suffix=""):
449
        """Check that add_string_to_exception adds the strings."""
450
        s1, s2 = self.get_exc_before_and_after(string, func)
451
        self.assertStringAdded(
452
            prefix + string + suffix, s1, s2, self.check_str_sum)
453
454
    def test_add_empty_string_to_str(self):
455
        """Empty string added to error's str value."""
456
        self.check_string_added(str, "")
457
458
    def test_add_empty_string_to_repr(self):
459
        """Empty string added to error's repr value."""
460
        self.check_string_added(repr, "")
461
462
    def test_add_string_to_str(self):
463
        """Non-empty string added to error's str value."""
464
        self.check_string_added(str, "ABCDEstr")
465
466
    def test_add_string_to_repr(self):
467
        """Non-empty string added to error's repr value."""
468
        self.check_string_added(
469
            repr, "ABCDErepr", self.prefix_repr, self.suffix_repr)
470
471
472
class AddStringToExcFromCodeTest(AddStringToExcTest):
473
    """Generic class for tests about add_string_to_exception.
474
475
    The tested function is called on an exception created by running
476
    some failing code (`self.code`) and catching what it throws.
477
    """
478
479
    code = NotImplemented
480
481
    def get_exception(self):
482
        """Get the exception by running the code and catching errors."""
483
        type_, value, _ = common.get_exception(self.code)
484
        self.assertTrue(
485
            issubclass(type_, self.error_type),
486
            "{0} ({1}) not a subclass of {2}"
487
            .format(type_, value, self.error_type))
488
        return value
489
490
491
class AddStringToNameErrorTest(unittest2.TestCase, AddStringToExcFromCodeTest):
492
    """Class for tests of add_string_to_exception on NameError."""
493
494
    code = 'babar = 0\nbaba'
495
    error_type = NameError
496
497
498
class AddStringToTypeErrorTest(unittest2.TestCase, AddStringToExcFromCodeTest):
499
    """Class for tests of add_string_to_exception on TypeError."""
500
501
    code = '[0](0)'
502
    error_type = TypeError
503
504
505
class AddStringToImportErrorTest(
506
        unittest2.TestCase, AddStringToExcFromCodeTest):
507
    """Class for tests of add_string_to_exception on ImportError."""
508
509
    code = 'import maths'
510
    error_type = ImportError
511
512
513
class AddStringToKeyErrorTest(
514
        unittest2.TestCase, AddStringToExcFromCodeTest):
515
    """Class for tests of add_string_to_exception on KeyError."""
516
517
    code = 'dict()["ffdsqmjklfqsd"]'
518
    error_type = KeyError
519
520
521
class AddStringToAttributeErrorTest(
522
        unittest2.TestCase, AddStringToExcFromCodeTest):
523
    """Class for tests of add_string_to_exception on AttributeError."""
524
525
    code = '[].does_not_exist'
526
    error_type = AttributeError
527
528
529
class AddStringToSyntaxErrorTest(
530
        unittest2.TestCase, AddStringToExcFromCodeTest):
531
    """Class for tests of add_string_to_exception on SyntaxError."""
532
533
    code = 'return'
534
    error_type = SyntaxError
535
536
537
class AddStringToMemoryErrorTest(
538
        unittest2.TestCase, AddStringToExcFromCodeTest):
539
    """Class for tests of add_string_to_exception on MemoryError."""
540
541
    code = '[0] * 999999999999999'
542
    error_type = MemoryError
543
    prefix_repr = "'"
544
    # Trailing comma removed from Python 3.7
545
    # See https://bugs.python.org/issue30399
546
    suffix_repr = "'" if sys.version_info >= (3, 7) else "',"
547
548
549
class AddStringToIOErrorTest(
550
        unittest2.TestCase, AddStringToExcFromCodeTest):
551
    """Class for tests of add_string_to_exception on NoFileIoError."""
552
553
    code = 'with open("/does_not_exist") as f:\n\tpass'
554
    error_type = common.NoFileIoError
555
556
557
class AddStringToUnicodeDecodeTest(
558
        unittest2.TestCase, AddStringToExcFromCodeTest):
559
    """Class for tests of add_string_to_exception on UnicodeDecodeError."""
560
561
    code = "'foo'.encode('utf-16').decode('utf-8')"
562
    error_type = UnicodeDecodeError
563
564
565
class AddStringToUnicodeEncodeTest(
566
        unittest2.TestCase, AddStringToExcFromCodeTest):
567
    """Class for tests of add_string_to_exception on UnicodeEncodeError."""
568
569
    code = U_PREFIX + '"\u0411".encode("iso-8859-15")'
570
    error_type = UnicodeEncodeError
571
572
573
class AddStringToExcFromInstanceTest(AddStringToExcTest):
574
    """Generic class for tests about add_string_to_exception.
575
576
    The tested function is called on an exception created by calling the
577
    constructor (`self.exc_type`) with the right arguments (`self.args`).
578
    Because of the way it creates exception, the tests are somewhat artificial
579
    (compared to AddStringToExcFromCodeTest for instance). However, the major
580
    advantage is that they can be easily generated (to have all subclasses of
581
    Exception tested).
582
    """
583
584
    check_str_sum = False
585
    exc_type = NotImplemented
586
    args = NotImplemented
587
588
    def get_exception(self):
589
        """Get the exception by calling the constructor with correct args."""
590
        return self.exc_type(*self.args)
591
592
593
class AddStringToZeroDivisionError(
594
        unittest2.TestCase, AddStringToExcFromInstanceTest):
595
    """Class for tests of add_string_to_exception on ZeroDivisionError."""
596
597
    exc_type = ZeroDivisionError
598
    args = ('', '', '', '', '')
599
600
601
def get_instance(klass):
602
    """Get instance for class by bruteforcing the parameters.
603
604
    Construction is attempted with a decreasing number of arguments so that
605
    the instanciated object has as many non-null attributes set as possible.
606
    This is important not for the creation but when the object gets used
607
    later on. Also, the order of the values has its importance for similar
608
    reasons.
609
    """
610
    my_unicode = str if sys.version_info >= (3, 0) else unicode
611
    values_tried = [my_unicode(), bytes(), 0]
612
    for nb_arg in reversed(range(6)):
613
        for p in itertools.product(values_tried, repeat=nb_arg):
614
            try:
615
                return klass(*p), p
616
            except (TypeError, AttributeError) as e:
617
                pass
618
            except Exception as e:
619
                print(type(e), e)
620
    return None
621
622
623
def generate_add_string_to_exc_tests():
624
    """Generate tests for add_string_to_exception.
625
626
    This function dynamically creates tests cases for the function
627
    add_string_to_exception for as many Exception subclasses as possible.
628
    This is not used at the moment because the generated classes need to
629
    be added in the global namespace and there is no good way to do this.
630
    However, it may be a good idea to call this when new versions of
631
    Python are released to ensure we handle all exceptions properly (and
632
    find the tests to be added manually if need be).
633
    """
634
    for klass in get_subclasses(Exception):
635
        r = get_instance(klass)
636
        if r is not None:
637
            _, p = r
638
            class_name = ("NameForAddStringToExcFromInstanceTest" +
639
                          klass.__name__ + str(id(klass)))
640
            assert class_name not in globals(), class_name
641
            globals()[class_name] = type(
642
                    class_name,
643
                    (AddStringToExcFromInstanceTest, unittest2.TestCase),
644
                    {'exc_type': klass, 'args': p})
645
646
647
if __name__ == '__main__':
648
    print(sys.version_info)
649
    unittest2.main()
650