Completed
Push — master ( 652866...ac3ef3 )
by De
01:15
created

get_instance()   B

Complexity

Conditions 6

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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