Completed
Push — master ( 875d60...87fae7 )
by De
03:01
created

QuoteTests.quote_empty_str()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
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
    suffix_repr = "',"
545
546
547
class AddStringToIOErrorTest(
548
        unittest2.TestCase, AddStringToExcFromCodeTest):
549
    """Class for tests of add_string_to_exception on NoFileIoError."""
550
551
    code = 'with open("/does_not_exist") as f:\n\tpass'
552
    error_type = common.NoFileIoError
553
554
555
class AddStringToUnicodeDecodeTest(
556
        unittest2.TestCase, AddStringToExcFromCodeTest):
557
    """Class for tests of add_string_to_exception on UnicodeDecodeError."""
558
559
    code = "'foo'.encode('utf-16').decode('utf-8')"
560
    error_type = UnicodeDecodeError
561
562
563
class AddStringToUnicodeEncodeTest(
564
        unittest2.TestCase, AddStringToExcFromCodeTest):
565
    """Class for tests of add_string_to_exception on UnicodeEncodeError."""
566
567
    code = U_PREFIX + '"\u0411".encode("iso-8859-15")'
568
    error_type = UnicodeEncodeError
569
570
571
class AddStringToExcFromInstanceTest(AddStringToExcTest):
572
    """Generic class for tests about add_string_to_exception.
573
574
    The tested function is called on an exception created by calling the
575
    constructor (`self.exc_type`) with the right arguments (`self.args`).
576
    Because of the way it creates exception, the tests are somewhat artificial
577
    (compared to AddStringToExcFromCodeTest for instance). However, the major
578
    advantage is that they can be easily generated (to have all subclasses of
579
    Exception tested).
580
    """
581
582
    check_str_sum = False
583
    exc_type = NotImplemented
584
    args = NotImplemented
585
586
    def get_exception(self):
587
        """Get the exception by calling the constructor with correct args."""
588
        return self.exc_type(*self.args)
589
590
591
class AddStringToZeroDivisionError(
592
        unittest2.TestCase, AddStringToExcFromInstanceTest):
593
    """Class for tests of add_string_to_exception on ZeroDivisionError."""
594
595
    exc_type = ZeroDivisionError
596
    args = ('', '', '', '', '')
597
598
599
def get_instance(klass):
600
    """Get instance for class by bruteforcing the parameters.
601
602
    Construction is attempted with a decreasing number of arguments so that
603
    the instanciated object has as many non-null attributes set as possible.
604
    This is important not for the creation but when the object gets used
605
    later on. Also, the order of the values has its importance for similar
606
    reasons.
607
    """
608
    my_unicode = str if sys.version_info >= (3, 0) else unicode
609
    values_tried = [my_unicode(), bytes(), 0]
610
    for nb_arg in reversed(range(6)):
611
        for p in itertools.product(values_tried, repeat=nb_arg):
612
            try:
613
                return klass(*p), p
614
            except (TypeError, AttributeError) as e:
615
                pass
616
            except Exception as e:
617
                print(type(e), e)
618
    return None
619
620
621
def generate_add_string_to_exc_tests():
622
    """Generate tests for add_string_to_exception.
623
624
    This function dynamically creates tests cases for the function
625
    add_string_to_exception for as many Exception subclasses as possible.
626
    This is not used at the moment because the generated classes need to
627
    be added in the global namespace and there is no good way to do this.
628
    However, it may be a good idea to call this when new versions of
629
    Python are released to ensure we handle all exceptions properly (and
630
    find the tests to be added manually if need be).
631
    """
632
    for klass in get_subclasses(Exception):
633
        r = get_instance(klass)
634
        if r is not None:
635
            _, p = r
636
            class_name = ("NameForAddStringToExcFromInstanceTest" +
637
                          klass.__name__ + str(id(klass)))
638
            assert class_name not in globals(), class_name
639
            globals()[class_name] = type(
640
                    class_name,
641
                    (AddStringToExcFromInstanceTest, unittest2.TestCase),
642
                    {'exc_type': klass, 'args': p})
643
644
645
if __name__ == '__main__':
646
    print(sys.version_info)
647
    unittest2.main()
648