Completed
Push — master ( 1de90a...104ec7 )
by De
01:05
created

AttributeErrorTests.test_removed_dict_methods()   A

Complexity

Conditions 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 2
1
# -*- coding: utf-8
2
"""Unit tests for get_suggestions_for_exception."""
3
from didyoumean_internal import get_suggestions_for_exception, \
4
    STAND_MODULES, AVOID_REC_MSG, \
5
    APPLY_REMOVED_MSG, BUFFER_REMOVED_MSG, CMP_REMOVED_MSG, \
6
    CMP_ARG_REMOVED_MSG, \
7
    MEMVIEW_ADDED_MSG, RELOAD_REMOVED_MSG, STDERR_REMOVED_MSG
8
import didyoumean_common_tests as common
9
import unittest2
10
import didyoumean_re as re
11
import warnings
12
import sys
13
import math
14
import os
15
import tempfile
16
from shutil import rmtree
17
18
19
this_is_a_global_list = []  # Value does not really matter but the type does
20
21
22
def func_gen(name='some_func', param='', body='pass', args=None):
23
    """Generate code corresponding to a function definition.
24
25
    Generate code for function definition (and eventually a call to it).
26
    Parameters are : name (with default), body (with default),
27
    parameters (with default) and arguments to call the functions with (if not
28
    provided or provided None, function call is not included in generated
29
    code).
30
    """
31
    func = "def {0}({1}):\n\t{2}\n".format(name, param, body)
32
    call = "" if args is None else "{0}({1})\n".format(name, args)
33
    return func + call
34
35
36
def my_generator():
37
    """Generate values for testing purposes.
38
39
    my_generator
40
    This is my generator, baby.
41
    """
42
    for i in range(5):
43
        yield i
44
45
46
def endlessly_recursive_func(n):
47
    """Call itself recursively with no end."""
48
    # http://stackoverflow.com/questions/871887/using-exec-with-recursive-functions
49
    return endlessly_recursive_func(n-1)
50
51
52
class FoobarClass():
53
    """Dummy class for testing purposes."""
54
55
    def __init__(self):
56
        """Constructor."""
57
        self.babar = 2
58
59
    @classmethod
60
    def this_is_cls_mthd(cls):
61
        """Just a class method."""
62
        return 5
63
64
    def nameerror_self(self):
65
        """Should be self.babar."""
66
        return babar
67
68
    def nameerror_self2(self):
69
        """Should be self.this_is_cls_mthd (or FoobarClass)."""
70
        return this_is_cls_mthd
71
72
    @classmethod
73
    def nameerror_cls(cls):
74
        """Should be cls.this_is_cls_mthd (or FoobarClass)."""
75
        return this_is_cls_mthd
76
77
    def some_method(self):
78
        """Method for testing purposes."""
79
        pass
80
81
    def some_method2(self, x):
82
        """Method for testing purposes."""
83
        pass
84
85
    def _some_semi_private_method(self):
86
        """Method for testing purposes."""
87
        pass
88
89
    def __some_private_method(self):
90
        """Method for testing purposes."""
91
        pass
92
93
    def some_method_missing_self_arg():
94
        """Method for testing purposes."""
95
        pass
96
97
    def some_method_missing_self_arg2(x):
98
        """Method for testing purposes."""
99
        pass
100
101
    @classmethod
102
    def some_cls_method_missing_cls():
103
        """Class method for testing purposes."""
104
        pass
105
106
    @classmethod
107
    def some_cls_method_missing_cls2(x):
108
        """Class method for testing purposes."""
109
        pass
110
111
112
# Logic to be able to have different tests on various version of Python
113
FIRST_VERSION = (0, 0)
114
LAST_VERSION = (10, 0)
115
ALL_VERSIONS = (FIRST_VERSION, LAST_VERSION)
116
INTERPRETERS = ['cython', 'pypy']
117
118
119
def from_version(version):
120
    """Create tuple describing a range of versions from a given version."""
121
    return (version, LAST_VERSION)
122
123
124
def up_to_version(version):
125
    """Create tuple describing a range of versions up to a given version."""
126
    return (FIRST_VERSION, version)
127
128
129
def version_in_range(version_range):
130
    """Test if current version is in a range version."""
131
    beg, end = version_range
132
    return beg <= sys.version_info < end
133
134
135
def interpreter_in(interpreters):
136
    """Test if current interpreter is in a list of interpreters."""
137
    is_pypy = hasattr(sys, "pypy_translation_info")
138
    interpreter = 'pypy' if is_pypy else 'cython'
139
    return interpreter in interpreters
140
141
142
def format_str(template, *args):
143
    """Format multiple string by using first arg as a template."""
144
    return [template.format(arg) for arg in args]
145
146
147
def listify(value, default):
148
    """Return list from value, using default value if value is None."""
149
    if value is None:
150
        value = default
151
    if not isinstance(value, list):
152
        value = [value]
153
    if default:
154
        assert all(v in default for v in value)
155
    return value
156
157
158
def no_exception(code):
159
    """Helper function to run code and check it works."""
160
    exec(code)
161
162
163
def get_exception(code):
164
    """Helper function to run code and get what it throws."""
165
    try:
166
        no_exception(code)
167
    except:
168
        return sys.exc_info()
169
    assert False, "No exception thrown running\n---\n{0}\n---".format(code)
170
171
172
# NameError for NameErrorTests
173
NAMEERROR = (NameError, re.NAMENOTDEFINED_RE)
174
NAMEERRORBEFOREREF = (NameError, re.VARREFBEFOREASSIGN_RE)
175
UNKNOWN_NAMEERROR = (NameError, None)
176
# UnboundLocalError for UnboundLocalErrorTests
177
UNBOUNDLOCAL = (UnboundLocalError, re.VARREFBEFOREASSIGN_RE)
178
UNKNOWN_UNBOUNDLOCAL = (UnboundLocalError, None)
179
# TypeError for TypeErrorTests
180
NBARGERROR = (TypeError, re.NB_ARG_RE)
181
MISSINGPOSERROR = (TypeError, re.MISSING_POS_ARG_RE)
182
UNHASHABLE = (TypeError, re.UNHASHABLE_RE)
183
UNSUBSCRIPTABLE = (TypeError, re.UNSUBSCRIPTABLE_RE)
184
NOATTRIBUTE_TYPEERROR = (TypeError, re.ATTRIBUTEERROR_RE)
185
UNEXPECTEDKWARG = (TypeError, re.UNEXPECTED_KEYWORDARG_RE)
186
UNEXPECTEDKWARG2 = (TypeError, re.UNEXPECTED_KEYWORDARG2_RE)
187
UNEXPECTEDKWARG3 = (TypeError, re.UNEXPECTED_KEYWORDARG3_RE)
188
UNSUPPORTEDOPERAND = (TypeError, re.UNSUPPORTED_OP_RE)
189
BADOPERANDUNARY = (TypeError, re.BAD_OPERAND_UNARY_RE)
190
OBJECTDOESNOTSUPPORT = (TypeError, re.OBJ_DOES_NOT_SUPPORT_RE)
191
CANNOTCONCAT = (TypeError, re.CANNOT_CONCAT_RE)
192
CANTCONVERT = (TypeError, re.CANT_CONVERT_RE)
193
MUSTBETYPENOTTYPE = (TypeError, re.MUST_BE_TYPE1_NOT_TYPE2_RE)
194
NOTCALLABLE = (TypeError, re.NOT_CALLABLE_RE)
195
DESCREXPECT = (TypeError, re.DESCRIPT_REQUIRES_TYPE_RE)
196
ARGNOTITERABLE = (TypeError, re.ARG_NOT_ITERABLE_RE)
197
MUSTCALLWITHINST = (TypeError, re.MUST_BE_CALLED_WITH_INST_RE)
198
OBJECTHASNOFUNC = (TypeError, re.OBJECT_HAS_NO_FUNC_RE)
199
EXCMUSTDERIVE = (TypeError, re.EXC_MUST_DERIVE_FROM_RE)
200
UNORDERABLE = (TypeError, re.UNORDERABLE_TYPES_RE)
201
OPNOTSUPPBETWEENINST = (TypeError, re.OP_NOT_SUPP_BETWEEN_INSTANCES_RE)
202
UNKNOWN_TYPEERROR = (TypeError, None)
203
# ImportError for ImportErrorTests
204
NOMODULE = (ImportError, re.NOMODULE_RE)
205
CANNOTIMPORT = (ImportError, re.CANNOTIMPORT_RE)
206
UNKNOWN_IMPORTERROR = (ImportError, None)
207
# KeyError for KeyErrorTests
208
KEYERROR = (KeyError, None)
209
# IndexError for IndexErrorTests
210
OUTOFRANGE = (IndexError, re.INDEXOUTOFRANGE_RE)
211
# ValueError for ValueErrorTests
212
TOOMANYVALUES = (ValueError, re.TOO_MANY_VALUES_UNPACK_RE)
213
NEEDMOREVALUES = (ValueError, re.NEED_MORE_VALUES_RE)
214
EXPECTEDLENGTH = (ValueError, re.EXPECTED_LENGTH_RE)
215
MATHDOMAIN = (ValueError, re.MATH_DOMAIN_ERROR_RE)
216
ZEROLENERROR = (ValueError, re.ZERO_LEN_FIELD_RE)
217
INVALIDLITERAL = (ValueError, re.INVALID_LITERAL_RE)
218
TIMEDATAFORMAT = (ValueError, re.TIME_DATA_DOES_NOT_MATCH_FORMAT_RE)
219
# AttributeError for AttributeErrorTests
220
ATTRIBUTEERROR = (AttributeError, re.ATTRIBUTEERROR_RE)
221
MODATTRIBUTEERROR = (AttributeError, re.MODULEHASNOATTRIBUTE_RE)
222
UNKNOWN_ATTRIBUTEERROR = (AttributeError, None)
223
# SyntaxError for SyntaxErrorTests
224
INVALIDSYNTAX = (SyntaxError, re.INVALID_SYNTAX_RE)
225
INVALIDTOKEN = (SyntaxError, re.INVALID_TOKEN_RE)
226
NOBINDING = (SyntaxError, re.NO_BINDING_NONLOCAL_RE)
227
NONLOCALMODULE = (SyntaxError, re.NONLOCAL_AT_MODULE_RE)
228
UNEXPECTED_OEF = (SyntaxError, re.UNEXPECTED_EOF_RE)
229
OUTSIDEFUNC = (SyntaxError, re.OUTSIDE_FUNCTION_RE)
230
MISSINGPARENT = (SyntaxError, re.MISSING_PARENT_RE)
231
INVALIDCOMP = (SyntaxError, re.INVALID_COMP_RE)
232
FUTUREFIRST = (SyntaxError, re.FUTURE_FIRST_RE)
233
FUTFEATNOTDEF = (SyntaxError, re.FUTURE_FEATURE_NOT_DEF_RE)
234
UNQUALIFIED_EXEC = (SyntaxError, re.UNQUALIFIED_EXEC_RE)
235
IMPORTSTAR = (SyntaxError, re.IMPORTSTAR_RE)
236
# MemoryError and OverflowError for MemoryErrorTests
237
MEMORYERROR = (MemoryError, '')
238
OVERFLOWERR = (OverflowError, re.RESULT_TOO_MANY_ITEMS_RE)
239
# IOError
240
NOFILE_IO = (common.NoFileIoError, re.NO_SUCH_FILE_RE)
241
NOFILE_OS = (common.NoFileOsError, re.NO_SUCH_FILE_RE)
242
NOTADIR_IO = (common.NotDirIoError, "^Not a directory$")
243
NOTADIR_OS = (common.NotDirOsError, "^Not a directory$")
244
ISADIR_IO = (common.IsDirIoError, "^Is a directory$")
245
ISADIR_OS = (common.IsDirOsError, "^Is a directory$")
246
DIRNOTEMPTY_OS = (OSError, "^Directory not empty$")
247
# RuntimeError
248
MAXRECURDEPTH = (RuntimeError, re.MAX_RECURSION_DEPTH_RE)
249
SIZECHANGEDDURINGITER = (RuntimeError, re.SIZE_CHANGED_DURING_ITER_RE)
250
251
252
class GetSuggestionsTests(unittest2.TestCase):
253
    """Generic class to test get_suggestions_for_exception.
254
255
    Many tests do not correspond to any handled exceptions but are
256
    kept because it is quite convenient to have a large panel of examples.
257
    Also, some correspond to example where suggestions could be added, those
258
    are flagged with a NICE_TO_HAVE comment.
259
    Finally, whenever it is easily possible, the code with the suggestions
260
    taken into account is usually tested too to ensure that the suggestion does
261
    work.
262
    """
263
264
    def runs(self, code, version_range=None, interpreters=None):
265
        """Helper function to run code.
266
267
        version_range and interpreters can be provided if the test depends on
268
        the used environment.
269
        """
270
        interpreters = listify(interpreters, INTERPRETERS)
271
        if version_range is None:
272
            version_range = ALL_VERSIONS
273
        if version_in_range(version_range) and interpreter_in(interpreters):
274
            no_exception(code)
275
276
    def throws(self, code, error_info,
277
               sugg=None, version_range=None, interpreters=None):
278
        """Run code and check it throws and relevant suggestions are provided.
279
280
        Helper function to run code, check that it throws, what it throws and
281
        that the exception leads to the expected suggestions.
282
        version_range and interpreters can be provided if the test depends on
283
        the used environment.
284
        """
285
        if version_range is None:
286
            version_range = ALL_VERSIONS
287
        interpreters = listify(interpreters, INTERPRETERS)
288
        sugg = sorted(listify(sugg, []))
289
        if version_in_range(version_range) and interpreter_in(interpreters):
290
            error_type, error_msg = error_info
291
            type_caught, value, traceback = get_exception(code)
292
            details = "Running following code :\n---\n{0}\n---".format(code)
293
            self.assertTrue(isinstance(value, type_caught))
294
            self.assertTrue(
295
                issubclass(type_caught, error_type),
296
                "{0} ({1}) not a subclass of {2}"
297
                .format(type_caught, value, error_type) + details)
298
            msg = next((a for a in value.args if isinstance(a, str)), '')
299
            if error_msg is not None:
300
                self.assertRegexpMatches(msg, error_msg)
301
            suggestions = sorted(
302
                get_suggestions_for_exception(value, traceback))
303
            self.assertEqual(suggestions, sugg, details)
304
305
306
class NameErrorTests(GetSuggestionsTests):
307
    """Class for tests related to NameError."""
308
309
    def test_local(self):
310
        """Should be 'foo'."""
311
        code = "foo = 0\n{0}"
312
        typo, sugg = "foob", "foo"
313
        bad_code, good_code = format_str(code, typo, sugg)
314
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
315
        self.runs(good_code)
316
317
    def test_1_arg(self):
318
        """Should be 'foo'."""
319
        typo, sugg = "foob", "foo"
320
        code = func_gen(param=sugg, body='{0}', args='1')
321
        bad_code, good_code = format_str(code, typo, sugg)
322
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
323
        self.runs(good_code)
324
325
    def test_n_args(self):
326
        """Should be 'fool' or 'foot'."""
327
        typo, sugg1, sugg2 = "foob", "foot", "fool"
328
        code = func_gen(param='fool, foot', body='{0}', args='1, 2')
329
        bad, good1, good2 = format_str(code, typo, sugg1, sugg2)
330
        self.throws(bad, NAMEERROR, ["'fool' (local)", "'foot' (local)"])
331
        self.runs(good1)
332
        self.runs(good2)
333
334
    def test_builtin(self):
335
        """Should be 'max'."""
336
        typo, sugg = 'maxi', 'max'
337
        self.throws(typo, NAMEERROR, "'" + sugg + "' (builtin)")
338
        self.runs(sugg)
339
340
    def test_keyword(self):
341
        """Should be 'pass'."""
342
        typo, sugg = 'passs', 'pass'
343
        self.throws(typo, NAMEERROR, "'" + sugg + "' (keyword)")
344
        self.runs(sugg)
345
346
    def test_global(self):
347
        """Should be this_is_a_global_list."""
348
        typo, sugg = 'this_is_a_global_lis', 'this_is_a_global_list'
349
        # just a way to say that this_is_a_global_list is needed in globals
350
        this_is_a_global_list
351
        self.assertFalse(sugg in locals())
352
        self.assertTrue(sugg in globals())
353
        self.throws(typo, NAMEERROR, "'" + sugg + "' (global)")
354
        self.runs(sugg)
355
356
    def test_name(self):
357
        """Should be '__name__'."""
358
        typo, sugg = '__name_', '__name__'
359
        self.throws(typo, NAMEERROR, "'" + sugg + "' (global)")
360
        self.runs(sugg)
361
362
    def test_decorator(self):
363
        """Should be classmethod."""
364
        typo, sugg = "class_method", "classmethod"
365
        code = "@{0}\n" + func_gen()
366
        bad_code, good_code = format_str(code, typo, sugg)
367
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (builtin)")
368
        self.runs(good_code)
369
370
    def test_import(self):
371
        """Should be math."""
372
        code = 'import math\n{0}'
373
        typo, sugg = 'maths', 'math'
374
        bad_code, good_code = format_str(code, typo, sugg)
375
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
376
        self.runs(good_code)
377
378
    def test_import2(self):
379
        """Should be my_imported_math."""
380
        code = 'import math as my_imported_math\n{0}'
381
        typo, sugg = 'my_imported_maths', 'my_imported_math'
382
        bad_code, good_code = format_str(code, typo, sugg)
383
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
384
        self.runs(good_code)
385
386
    def test_imported(self):
387
        """Should be math.pi."""
388
        code = 'import math\n{0}'
389
        typo, sugg = 'pi', 'math.pi'
390
        bad_code, good_code = format_str(code, typo, sugg)
391
        self.throws(bad_code, NAMEERROR, "'" + sugg + "'")
392
        self.runs(good_code)
393
394
    def test_imported_twice(self):
395
        """Should be math.pi."""
396
        code = 'import math\nimport math\n{0}'
397
        typo, sugg = 'pi', 'math.pi'
398
        bad_code, good_code = format_str(code, typo, sugg)
399
        self.throws(bad_code, NAMEERROR, "'" + sugg + "'")
400
        self.runs(good_code)
401
402
    def test_not_imported(self):
403
        """Should be random.choice after importing random."""
404
        # This test assumes that `module` is not imported
405
        module, attr = 'random', 'choice'
406
        self.assertFalse(module in locals())
407
        self.assertFalse(module in globals())
408
        self.assertTrue(module in STAND_MODULES)
409
        bad_code = attr
410
        good_code = 'from {0} import {1}\n{2}'.format(module, attr, bad_code)
411
        self.runs(good_code)
412
        self.throws(
413
            bad_code, NAMEERROR,
414
            "'{0}' from {1} (not imported)".format(attr, module))
415
416
    def test_enclosing_scope(self):
417
        """Test that variables from enclosing scope are suggested."""
418
        # NICE_TO_HAVE
419
        typo, sugg = 'foob', 'foo'
420
        code = 'def f():\n\tfoo = 0\n\tdef g():\n\t\t{0}\n\tg()\nf()'
421
        bad_code, good_code = format_str(code, typo, sugg)
422
        self.throws(bad_code, NAMEERROR)
423
        self.runs(good_code)
424
425
    def test_no_sugg(self):
426
        """No suggestion."""
427
        self.throws('a = ldkjhfnvdlkjhvgfdhgf', NAMEERROR)
428
429
    def test_free_var_before_assignment(self):
430
        """No suggestion but different error message."""
431
        code = 'def f():\n\tdef g():\n\t\treturn free_var' \
432
               '\n\tg()\n\tfree_var = 0\nf()'
433
        self.throws(code, NAMEERRORBEFOREREF)
434
435
    # For added/removed names, following functions with one name
436
    # per functions were added in the early stages of the project.
437
    # In the future, I'd like to have them replaced by something
438
    # a bit more concise using relevant data structure. In the
439
    # meantime, I am keeping both versions because safer is better.
440
    def test_removed_cmp(self):
441
        """Builtin cmp is removed."""
442
        code = 'cmp(1, 2)'
443
        sugg1 = '1 < 2'
444
        sugg2 = 'def cmp(a, b):\n\treturn (a > b) - (a < b)\ncmp(1, 2)'
445
        version = (3, 0, 1)
446
        self.runs(code, up_to_version(version))
447
        self.throws(code, NAMEERROR, CMP_REMOVED_MSG, from_version(version))
448
        self.runs(sugg1)
449
        self.runs(sugg2)
450
451
    def test_removed_reduce(self):
452
        """Builtin reduce is removed - moved to functools."""
453
        code = 'reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])'
454
        version = (3, 0)
455
        self.runs(code, up_to_version(version))
456
        self.runs('from functools import reduce\n' + code,
457
                  from_version(version))
458
        self.throws(
459
            code,
460
            NAMEERROR,
461
            "'reduce' from functools (not imported)",
462
            from_version(version))
463
464
    def test_removed_apply(self):
465
        """Builtin apply is removed."""
466
        code = 'apply(sum, [[1, 2, 3]])'
467
        sugg = 'sum([1, 2, 3])'
468
        version = (3, 0)
469
        self.runs(code, up_to_version(version))
470
        self.throws(code, NAMEERROR, APPLY_REMOVED_MSG, from_version(version))
471
        self.runs(sugg)
472
473
    def test_removed_reload(self):
474
        """Builtin reload is removed.
475
476
        Moved to importlib.reload or imp.reload depending on version.
477
        """
478
        code = 'reload(math)'
479
        sugg_template = 'import {0}\n{0}.reload(math)'
480
        sugg1, sugg2 = format_str(sugg_template, 'importlib', 'imp')
481
        version = (3, 0)
482
        self.runs(code, up_to_version(version))
483
        self.throws(code, NAMEERROR, RELOAD_REMOVED_MSG, from_version(version))
484
        self.runs(sugg1, from_version((3, 4)))
485
        self.runs(sugg2)
486
487
    def test_removed_intern(self):
488
        """Builtin intern is removed - moved to sys."""
489
        code = 'intern("toto")'
490
        new_code = 'sys.intern("toto")'
491
        version = (3, 0)
492
        self.runs(code, up_to_version(version))
493
        self.throws(
494
            code, NAMEERROR,
495
            ["'iter' (builtin)", "'sys.intern'"],
496
            from_version(version))
497
        self.runs(new_code, from_version(version))
498
499
    def test_removed_execfile(self):
500
        """Builtin execfile is removed - use exec() and compile()."""
501
        # NICE_TO_HAVE
502
        code = 'execfile("some_filename")'
503
        version = (3, 0)
504
        # self.runs(code, up_to_version(version))
505
        self.throws(code, NAMEERROR, [], from_version(version))
506
507
    def test_removed_raw_input(self):
508
        """Builtin raw_input is removed - use input() instead."""
509
        code = 'i = raw_input("Prompt:")'
510
        version = (3, 0)
511
        # self.runs(code, up_to_version(version))
512
        self.throws(
513
            code, NAMEERROR, "'input' (builtin)", from_version(version))
514
515
    def test_removed_buffer(self):
516
        """Builtin buffer is removed - use memoryview instead."""
517
        code = 'buffer(b"abc")'
518
        sugg = 'memoryview(b"abc")'
519
        version = (3, 0)
520
        self.runs(code, up_to_version(version))
521
        self.throws(code, NAMEERROR, BUFFER_REMOVED_MSG, from_version(version))
522
        self.runs(sugg, from_version((2, 7)))
523
524
    def test_added_2_7(self):
525
        """Test for names added in 2.7."""
526
        version = (2, 7)
527
        for name, suggs in {
528
                'memoryview': [MEMVIEW_ADDED_MSG],
529
                }.items():
530
            self.runs(name, from_version(version))
531
            self.throws(name, NAMEERROR, suggs, up_to_version(version))
532
533
    def test_removed_3_0(self):
534
        """Test for names removed in 3.0."""
535
        version = (3, 0)
536
        for name, suggs in {
537
                'StandardError': [STDERR_REMOVED_MSG],
538
                'apply': [APPLY_REMOVED_MSG],
539
                'basestring': [],
540
                'buffer': [BUFFER_REMOVED_MSG],
541
                'cmp': [CMP_REMOVED_MSG],
542
                'coerce': [],
543
                'execfile': [],
544
                'file': ["'filter' (builtin)"],
545
                'intern': ["'iter' (builtin)", "'sys.intern'"],
546
                'long': [],
547
                'raw_input': ["'input' (builtin)"],
548
                'reduce': ["'reduce' from functools (not imported)"],
549
                'reload': [RELOAD_REMOVED_MSG],
550
                'unichr': [],
551
                'unicode': ["'code' (local)"],
552
                'xrange': ["'range' (builtin)"],
553
                }.items():
554
            self.throws(name, NAMEERROR, suggs, from_version(version))
555
            self.runs(name, up_to_version(version))
556
557
    def test_added_3_0(self):
558
        """Test for names added in 3.0."""
559
        version = (3, 0)
560
        for name, suggs in {
561
                'ascii': [],
562
                'ResourceWarning': ["'FutureWarning' (builtin)"],
563
                '__build_class__': [],
564
                }.items():
565
            self.runs(name, from_version(version))
566
            self.throws(name, NAMEERROR, suggs, up_to_version(version))
567
568
    def test_added_3_3(self):
569
        """Test for names added in 3.3."""
570
        version = (3, 3)
571
        for name, suggs in {
572
                'BrokenPipeError': [],
573
                'ChildProcessError': [],
574
                'ConnectionAbortedError': [],
575
                'ConnectionError': ["'IndentationError' (builtin)"],
576
                'ConnectionRefusedError': [],
577
                'ConnectionResetError': [],
578
                'FileExistsError': [],
579
                'FileNotFoundError': [],
580
                'InterruptedError': [],
581
                'IsADirectoryError': [],
582
                'NotADirectoryError': [],
583
                'PermissionError': ["'ZeroDivisionError' (builtin)"],
584
                'ProcessLookupError': ["'LookupError' (builtin)"],
585
                'TimeoutError': [],
586
                '__loader__': [],
587
                }.items():
588
            self.runs(name, from_version(version))
589
            self.throws(name, NAMEERROR, suggs, up_to_version(version))
590
591
    def test_added_3_4(self):
592
        """Test for names added in 3.4."""
593
        version = (3, 4)
594
        for name, suggs in {
595
                '__spec__': [],
596
                }.items():
597
            self.runs(name, from_version(version))
598
            self.throws(name, NAMEERROR, suggs, up_to_version(version))
599
600
    def test_added_3_5(self):
601
        """Test for names added in 3.5."""
602
        version = (3, 5)
603
        for name, suggs in {
604
                'StopAsyncIteration': ["'StopIteration' (builtin)"],
605
                }.items():
606
            self.runs(name, from_version(version))
607
            self.throws(name, NAMEERROR, suggs, up_to_version(version))
608
609
    def test_import_sugg(self):
610
        """Should import module first."""
611
        module = 'collections'
612
        sugg = 'import {0}'.format(module)
613
        typo, good_code = module, sugg + '\n' + module
614
        self.assertFalse(module in locals())
615
        self.assertFalse(module in globals())
616
        self.assertTrue(module in STAND_MODULES)
617
        suggestions = (
618
            # module.module is suggested on Python 3.3 :-/
619
            ["'{0}' from {1} (not imported)".format(module, module)]
620
            if version_in_range(((3, 3), (3, 4))) else []) + \
621
            ['to {0} first'.format(sugg)]
622
        self.throws(typo, NAMEERROR, suggestions)
623
        self.runs(good_code)
624
625
    def test_attribute_hidden(self):
626
        """Should be math.pi but module math is hidden."""
627
        math  # just a way to say that math module is needed in globals
628
        self.assertFalse('math' in locals())
629
        self.assertTrue('math' in globals())
630
        code = 'math = ""\npi'
631
        self.throws(code, NAMEERROR, "'math.pi' (global hidden by local)")
632
633
    def test_self(self):
634
        """"Should be self.babar."""
635
        self.throws(
636
            'FoobarClass().nameerror_self()',
637
            NAMEERROR, "'self.babar'")
638
639
    def test_self2(self):
640
        """Should be self.this_is_cls_mthd."""
641
        self.throws(
642
            'FoobarClass().nameerror_self2()', NAMEERROR,
643
            ["'FoobarClass.this_is_cls_mthd'", "'self.this_is_cls_mthd'"])
644
645
    def test_cls(self):
646
        """Should be cls.this_is_cls_mthd."""
647
        self.throws(
648
            'FoobarClass().nameerror_cls()', NAMEERROR,
649
            ["'FoobarClass.this_is_cls_mthd'", "'cls.this_is_cls_mthd'"])
650
651
    def test_complex_numbers(self):
652
        """Should be 1j."""
653
        code = 'assert {0} ** 2 == -1'
654
        sugg = '1j'
655
        good_code, bad_code_i, bad_code_j = format_str(code, sugg, 'i', 'j')
656
        suggestion = "'" + sugg + "' (imaginary unit)"
657
        self.throws(bad_code_i, NAMEERROR, suggestion)
658
        self.throws(bad_code_j, NAMEERROR, suggestion)
659
        self.runs(good_code)
660
661
    def test_shell_commands(self):
662
        """Trying shell commands."""
663
        cmd, sugg = 'ls', 'os.listdir(os.getcwd())'
664
        self.throws(cmd, NAMEERROR, "'" + sugg + "'")
665
        self.runs(sugg)
666
        cmd, sugg = 'pwd', 'os.getcwd()'
667
        self.throws(cmd, NAMEERROR, "'" + sugg + "'")
668
        self.runs(sugg)
669
        cmd, sugg = 'cd', 'os.chdir(path)'
670
        self.throws(cmd, NAMEERROR, "'" + sugg + "'")
671
        self.runs(sugg.replace('path', 'os.getcwd()'))
672
        cmd = 'rm'
673
        sugg = "'os.remove(filename)', 'shutil.rmtree(dir)' for recursive"
674
        self.throws(cmd, NAMEERROR, sugg)
675
676
    def test_unmatched_msg(self):
677
        """Test that arbitrary strings are supported."""
678
        self.throws(
679
            'raise NameError("unmatched NAMEERROR")',
680
            UNKNOWN_NAMEERROR)
681
682
683
class UnboundLocalErrorTests(GetSuggestionsTests):
684
    """Class for tests related to UnboundLocalError."""
685
686
    def test_unbound_typo(self):
687
        """Should be foo."""
688
        code = 'def func():\n\tfoo = 1\n\t{0} +=1\nfunc()'
689
        typo, sugg = "foob", "foo"
690
        bad_code, good_code = format_str(code, typo, sugg)
691
        self.throws(bad_code, UNBOUNDLOCAL, "'" + sugg + "' (local)")
692
        self.runs(good_code)
693
694
    def test_unbound_global(self):
695
        """Should be global nb."""
696
        # NICE_TO_HAVE
697
        code = 'nb = 0\ndef func():\n\t{0}\n\tnb +=1\nfunc()'
698
        sugg = 'global nb'
699
        bad_code, good_code = format_str(code, "", sugg)
700
        self.throws(bad_code, UNBOUNDLOCAL)
701
        self.runs(good_code)  # this is to be run afterward :-/
702
703
    def test_unbound_nonlocal(self):
704
        """Shoud be nonlocal nb."""
705
        # NICE_TO_HAVE
706
        code = 'def foo():\n\tnb = 0\n\tdef bar():' \
707
               '\n\t\t{0}\n\t\tnb +=1\n\tbar()\nfoo()'
708
        sugg = 'nonlocal nb'
709
        bad_code, good_code = format_str(code, "", sugg)
710
        self.throws(bad_code, UNBOUNDLOCAL)
711
        version = (3, 0)
712
        self.runs(good_code, from_version(version))
713
        self.throws(good_code, INVALIDSYNTAX, [], up_to_version(version))
714
715
    def test_unbound_nonlocal_and_global(self):
716
        """Shoud be nonlocal nb or global."""
717
        # NICE_TO_HAVE
718
        code = 'nb = 1\ndef foo():\n\tnb = 0\n\tdef bar():' \
719
               '\n\t\t{0}\n\t\tnb +=1\n\tbar()\nfoo()'
720
        sugg1, sugg2 = 'nonlocal nb', 'global nb'
721
        bad_code, good_code1, good_code2 = format_str(code, "", sugg1, sugg2)
722
        self.throws(bad_code, UNBOUNDLOCAL)
723
        self.runs(good_code2)
724
        version = (3, 0)
725
        self.runs(good_code1, from_version(version))
726
        self.throws(good_code1, INVALIDSYNTAX, [], up_to_version(version))
727
728
    def test_unmatched_msg(self):
729
        """Test that arbitrary strings are supported."""
730
        self.throws(
731
            'raise UnboundLocalError("unmatched UNBOUNDLOCAL")',
732
            UNKNOWN_UNBOUNDLOCAL)
733
734
735
class AttributeErrorTests(GetSuggestionsTests):
736
    """Class for tests related to AttributeError."""
737
738
    def test_nonetype(self):
739
        """In-place methods like sort returns None.
740
741
        Might also happen if the functions misses a return.
742
        """
743
        # NICE_TO_HAVE
744
        code = '[].sort().append(4)'
745
        self.throws(code, ATTRIBUTEERROR)
746
747
    def test_method(self):
748
        """Should be 'append'."""
749
        code = '[0].{0}(1)'
750
        typo, sugg = 'appendh', 'append'
751
        bad_code, good_code = format_str(code, typo, sugg)
752
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
753
        self.runs(good_code)
754
755
    def test_builtin(self):
756
        """Should be 'max(lst)'."""
757
        bad_code, good_code = '[0].max()', 'max([0])'
758
        self.throws(bad_code, ATTRIBUTEERROR, "'max(list)'")
759
        self.runs(good_code)
760
761
    def test_builtin2(self):
762
        """Should be 'next(gen)'."""
763
        code = 'my_generator().next()'
764
        new_code = 'next(my_generator())'
765
        version = (3, 0)
766
        self.runs(code, up_to_version(version))
767
        self.throws(
768
            code, ATTRIBUTEERROR,
769
            "'next(generator)'",
770
            from_version(version))
771
        self.runs(new_code)
772
773
    def test_wrongmethod(self):
774
        """Should be 'lst.append(1)'."""
775
        code = '[0].{0}(1)'
776
        typo, sugg = 'add', 'append'
777
        bad_code, good_code = format_str(code, typo, sugg)
778
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
779
        self.runs(good_code)
780
781
    def test_wrongmethod2(self):
782
        """Should be 'lst.extend([4, 5, 6])'."""
783
        code = '[0].{0}([4, 5, 6])'
784
        typo, sugg = 'update', 'extend'
785
        bad_code, good_code = format_str(code, typo, sugg)
786
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
787
        self.runs(good_code)
788
789
    def test_hidden(self):
790
        """Accessing wrong string object."""
791
        # NICE_TO_HAVE
792
        code = 'import string\nstring = "a"\nascii = string.ascii_letters'
793
        self.throws(code, ATTRIBUTEERROR)
794
795
    def test_no_sugg(self):
796
        """No suggestion."""
797
        self.throws('[1, 2, 3].ldkjhfnvdlkjhvgfdhgf', ATTRIBUTEERROR)
798
799
    def test_from_module(self):
800
        """Should be math.pi."""
801
        code = 'import math\nmath.{0}'
802
        typo, sugg = 'pie', 'pi'
803
        version = (3, 5)
804
        bad_code, good_code = format_str(code, typo, sugg)
805
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'",
806
                    up_to_version(version))
807
        self.throws(bad_code, MODATTRIBUTEERROR, "'" + sugg + "'",
808
                    from_version(version))
809
        self.runs(good_code)
810
811
    def test_from_module2(self):
812
        """Should be math.pi."""
813
        code = 'import math\nm = math\nm.{0}'
814
        typo, sugg = 'pie', 'pi'
815
        version = (3, 5)
816
        bad_code, good_code = format_str(code, typo, sugg)
817
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'",
818
                    up_to_version(version))
819
        self.throws(bad_code, MODATTRIBUTEERROR, "'" + sugg + "'",
820
                    from_version(version))
821
        self.runs(good_code)
822
823
    def test_from_class(self):
824
        """Should be 'this_is_cls_mthd'."""
825
        code = 'FoobarClass().{0}()'
826
        typo, sugg = 'this_is_cls_mth', 'this_is_cls_mthd'
827
        bad_code, good_code = format_str(code, typo, sugg)
828
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
829
        self.runs(good_code)
830
831
    def test_from_class2(self):
832
        """Should be 'this_is_cls_mthd'."""
833
        code = 'FoobarClass.{0}()'
834
        typo, sugg = 'this_is_cls_mth', 'this_is_cls_mthd'
835
        bad_code, good_code = format_str(code, typo, sugg)
836
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
837
        self.runs(good_code)
838
839
    def test_private_attr(self):
840
        """Test that 'private' members are suggested with a warning message.
841
842
        Sometimes 'private' members are suggested but it's not ideal, a
843
        warning must be added to the suggestion.
844
        """
845
        code = 'FoobarClass().{0}'
846
        method = '__some_private_method'
847
        method2 = '_some_semi_private_method'
848
        typo, sugg, sugg2 = method, '_FoobarClass' + method, method2
849
        bad_code, bad_sugg, good_sugg = format_str(code, typo, sugg, sugg2)
850
        self.throws(
851
            bad_code,
852
            ATTRIBUTEERROR,
853
            ["'{0}' (but it is supposed to be private)".format(sugg),
854
             "'{0}'".format(sugg2)])
855
        self.runs(bad_sugg)
856
        self.runs(good_sugg)
857
858
    def test_get_on_nondict_cont(self):
859
        """Method get does not exist on all containers."""
860
        code = '{0}().get(0, None)'
861
        dictcode, tuplecode, listcode, setcode = \
862
            format_str(code, 'dict', 'tuple', 'list', 'set')
863
        self.runs(dictcode)
864
        self.throws(setcode, ATTRIBUTEERROR)
865
        for bad_code in tuplecode, listcode:
866
            self.throws(bad_code, ATTRIBUTEERROR,
867
                        "'obj[key]' with a len() check or "
868
                        "try: except: KeyError or IndexError")
869
870
    def test_removed_has_key(self):
871
        """Method has_key is removed from dict."""
872
        code = 'dict().has_key(1)'
873
        new_code = '1 in dict()'
874
        version = (3, 0)
875
        self.runs(code, up_to_version(version))
876
        self.throws(
877
            code,
878
            ATTRIBUTEERROR,
879
            "'key in dict' (has_key is removed)",
880
            from_version(version))
881
        self.runs(new_code)
882
883
    def test_removed_dict_methods(self):
884
        """Different methos (iterXXX) have been removed from dict."""
885
        version = (3, 0)
886
        code = 'dict().{0}()'
887
        for method, sugg in {
888
            'iterkeys': [],
889
            'itervalues': ["'values'"],
890
            'iteritems': ["'items'"],
891
        }.items():
892
            meth_code, = format_str(code, method)
893
            self.runs(meth_code, up_to_version(version))
894
            self.throws(meth_code, ATTRIBUTEERROR, sugg, from_version(version))
895
896
    def test_removed_xreadlines(self):
897
        """Method xreadlines is removed."""
898
        # NICE_TO_HAVE
899
        code = "import os\nwith open(os.path.realpath(__file__)) as f:" \
900
            "\n\tf.{0}"
901
        old, sugg1, sugg2 = 'xreadlines', 'readline', 'readlines'
902
        old_code, new_code1, new_code2 = format_str(code, old, sugg1, sugg2)
903
        version = (3, 0)
904
        self.runs(old_code, up_to_version(version))
905
        self.throws(
906
            old_code,
907
            ATTRIBUTEERROR,
908
            ["'" + sugg1 + "'", "'" + sugg2 + "'", "'writelines'"],
909
            from_version(version))
910
        self.runs(new_code1)
911
        self.runs(new_code2)
912
913
    def test_removed_function_attributes(self):
914
        """Some functions attributes are removed."""
915
        # NICE_TO_HAVE
916
        version = (3, 0)
917
        code = func_gen() + 'some_func.{0}'
918
        attributes = [('func_name', '__name__', []),
919
                      ('func_doc', '__doc__', []),
920 View Code Duplication
                      ('func_defaults', '__defaults__', ["'__defaults__'"]),
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
921
                      ('func_dict', '__dict__', []),
922
                      ('func_closure', '__closure__', []),
923
                      ('func_globals', '__globals__', []),
924
                      ('func_code', '__code__', [])]
925
        for (old_att, new_att, sugg) in attributes:
926
            old_code, new_code = format_str(code, old_att, new_att)
927
            self.runs(old_code, up_to_version(version))
928
            self.throws(old_code, ATTRIBUTEERROR, sugg, from_version(version))
929
            self.runs(new_code)
930
931
    def test_removed_method_attributes(self):
932
        """Some methods attributes are removed."""
933
        # NICE_TO_HAVE
934
        version = (3, 0)
935
        code = 'FoobarClass().some_method.{0}'
936
        attributes = [('im_func', '__func__', []),
937
                      ('im_self', '__self__', []),
938
                      ('im_class', '__self__.__class__', ["'__class__'"])]
939
        for (old_att, new_att, sugg) in attributes:
940
            old_code, new_code = format_str(code, old_att, new_att)
941
            self.runs(old_code, up_to_version(version))
942
            self.throws(old_code, ATTRIBUTEERROR, sugg, from_version(version))
943
            self.runs(new_code)
944
945
    def test_moved_between_str_string(self):
946
        """Some methods have been moved from string to str."""
947
        # NICE_TO_HAVE
948
        version1 = (3, 0)
949
        version2 = (3, 5)
950
        code = 'import string\n{0}.maketrans'
951
        code_str, code_string = format_str(code, 'str', 'string')
952
        code_str2 = 'str.maketrans'  # No 'string' import
953
        code_str3 = 'import string as my_string\nstr.maketrans'  # Named import
954
        self.throws(code_str, ATTRIBUTEERROR, [], up_to_version(version1))
955
        self.throws(code_str2, ATTRIBUTEERROR, [], up_to_version(version1))
956
        self.throws(code_str3, ATTRIBUTEERROR, [], up_to_version(version1))
957
        self.runs(code_string, up_to_version(version1))
958
        self.throws(code_string, ATTRIBUTEERROR, [], (version1, version2))
959
        self.throws(code_string, MODATTRIBUTEERROR, [], from_version(version2))
960
        self.runs(code_str, from_version(version1))
961
        self.runs(code_str2, from_version(version1))
962
        self.runs(code_str3, from_version(version1))
963
964
    def test_moved_between_imp_importlib(self):
965
        """Some methods have been moved from imp to importlib."""
966
        # NICE_TO_HAVE
967
        # reload removed from Python 3
968
        # importlib module new in Python 2.7
969
        # importlib.reload new in Python 3.4
970
        # imp.reload new in Python 3.2
971
        version27 = (2, 7)
972
        version3 = (3, 0)
973
        version26 = up_to_version(version27)
974
        code = '{0}reload(math)'
975
        null, code_imp, code_importlib = format_str(
976
            code, '', 'import imp\nimp.', 'import importlib\nimportlib.')
977
        self.runs(null, up_to_version(version3))
978
        self.throws(null, NAMEERROR,
979
                    RELOAD_REMOVED_MSG, from_version(version3))
980
        self.runs(code_imp)
981 View Code Duplication
        self.throws(code_importlib, NOMODULE, [], version26)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
982
        self.throws(code_importlib, ATTRIBUTEERROR,
983
                    "'reload(module)'", (version27, version3))
984
        self.throws(code_importlib, ATTRIBUTEERROR,
985
                    [], (version3, (3, 4)))
986
        self.runs(code_importlib, from_version((3, 4)))
987
988
    def test_join(self):
989
        """Test what happens when join is used incorrectly.
990
991
        This can be frustrating to call join on an iterable instead of a
992
        string.
993
        """
994
        code = "['a', 'b'].join('-')"
995
        self.throws(code, ATTRIBUTEERROR, "'my_string.join(list)'")
996
997
    def test_set_dict_comprehension(self):
998
        """{} creates a dict and not an empty set leading to errors."""
999
        # NICE_TO_HAVE
1000
        version = (2, 7)
1001
        for method in set(dir(set)) - set(dir(dict)):
1002
            if not method.startswith('__'):  # boring suggestions
1003
                code = "a = {0}\na." + method
1004
                typo, dict1, dict2, sugg, set1 = format_str(
1005
                    code, "{}", "dict()", "{0: 0}", "set()", "{0}")
1006
                self.throws(typo, ATTRIBUTEERROR)
1007
                self.throws(dict1, ATTRIBUTEERROR)
1008
                self.throws(dict2, ATTRIBUTEERROR)
1009
                self.runs(sugg)
1010
                self.throws(set1, INVALIDSYNTAX, [], up_to_version(version))
1011
                self.runs(set1, from_version(version))
1012
1013
    def test_unmatched_msg(self):
1014
        """Test that arbitrary strings are supported."""
1015
        self.throws(
1016
            'raise AttributeError("unmatched ATTRIBUTEERROR")',
1017
            UNKNOWN_ATTRIBUTEERROR)
1018
1019
    # TODO: Add sugg for situation where self/cls is the missing parameter
1020
1021
1022
class TypeErrorTests(GetSuggestionsTests):
1023
    """Class for tests related to TypeError."""
1024
1025
    def test_unhashable(self):
1026
        """Test for UNHASHABLE exception."""
1027
        # NICE_TO_HAVE : suggest hashable equivalent
1028
        self.throws('s = set([list()])', UNHASHABLE)
1029
        self.throws('s = set([dict()])', UNHASHABLE)
1030
        self.throws('s = set([set()])', UNHASHABLE)
1031
        self.runs('s = set([tuple()])')
1032
        self.runs('s = set([frozenset()])')
1033
1034
    def test_not_sub(self):
1035
        """Should be function call, not [] operator."""
1036
        typo, sugg = '[2]', '(2)'
1037
        code = func_gen(param='a') + 'some_func{0}'
1038
        bad_code, good_code = format_str(code, typo, sugg)
1039
        suggestion = "'function(value)'"
1040
        # Only Python 2.7 with cpython has a different error message
1041
        # (leading to more suggestions based on fuzzy matches)
1042
        version1 = (2, 7)
1043
        version2 = (3, 0)
1044
        self.throws(bad_code, UNSUBSCRIPTABLE, suggestion,
1045
                    ALL_VERSIONS, 'pypy')
1046
        self.throws(bad_code, UNSUBSCRIPTABLE, suggestion,
1047
                    up_to_version(version1), 'cython')
1048
        self.throws(bad_code, UNSUBSCRIPTABLE, suggestion,
1049
                    from_version(version2), 'cython')
1050
        self.throws(bad_code, NOATTRIBUTE_TYPEERROR,
1051
                    ["'__get__'", "'__getattribute__'", suggestion],
1052
                    (version1, version2), 'cython')
1053
        self.runs(good_code)
1054
1055
    def test_method_called_on_class(self):
1056
        """Test where a method is called on a class and not an instance.
1057
1058
        Forgetting parenthesis makes the difference between using an
1059
        instance and using a type.
1060
        """
1061
        # NICE_TO_HAVE
1062
        wrong_type = (DESCREXPECT, MUSTCALLWITHINST, NBARGERROR)
1063
        not_iterable = (ARGNOTITERABLE, ARGNOTITERABLE, ARGNOTITERABLE)
1064
        version = (3, 0)
1065
        for code, (err_cy, err_pyp, err_pyp3) in [
1066
                ('set{0}.add(0)', wrong_type),
1067
                ('list{0}.append(0)', wrong_type),
1068
                ('0 in list{0}', not_iterable)]:
1069
            bad_code, good_code = format_str(code, '', '()')
1070
            self.runs(good_code)
1071
            self.throws(bad_code, err_cy, [], ALL_VERSIONS, 'cython')
1072
            self.throws(bad_code, err_pyp, [], up_to_version(version), 'pypy')
1073
            self.throws(bad_code, err_pyp3, [], from_version(version), 'pypy')
1074
1075
    def test_set_operations(self):
1076
        """+, +=, etc doesn't work on sets. A suggestion would be nice."""
1077
        # NICE_TO_HAVE
1078
        typo1 = 'set() + set()'
1079
        typo2 = 's = set()\ns += set()'
1080
        code1 = 'set() | set()'
1081
        code2 = 'set().union(set())'
1082
        code3 = 'set().update(set())'
1083
        self.throws(typo1, UNSUPPORTEDOPERAND)
1084
        self.throws(typo2, UNSUPPORTEDOPERAND)
1085
        self.runs(code1)
1086
        self.runs(code2)
1087
        self.runs(code3)
1088
1089
    def test_dict_operations(self):
1090
        """+, +=, etc doesn't work on dicts. A suggestion would be nice."""
1091
        # NICE_TO_HAVE
1092
        typo1 = 'dict() + dict()'
1093
        typo2 = 'd = dict()\nd += dict()'
1094
        typo3 = 'dict() & dict()'
1095
        self.throws(typo1, UNSUPPORTEDOPERAND)
1096
        self.throws(typo2, UNSUPPORTEDOPERAND)
1097
        self.throws(typo3, UNSUPPORTEDOPERAND)
1098
        code1 = 'dict().update(dict())'
1099
        self.runs(code1)
1100
1101
    def test_unsupported_operand_caret(self):
1102
        """Use '**' for power, not '^'."""
1103
        # NICE_TO_HAVE
1104
        code = '3.5 {0} 2'
1105
        bad_code, good_code = format_str(code, '^', '**')
1106
        self.runs(good_code)
1107
        self.throws(bad_code, UNSUPPORTEDOPERAND)
1108
1109
    def test_unary_operand_custom(self):
1110
        """Test unary operand errors on custom types."""
1111
        version = (3, 0)
1112
        ops = {
1113
            '+{0}': ('__pos__', "'__doc__'"),
1114
            '-{0}': ('__neg__', None),
1115
            '~{0}': ('__invert__', "'__init__'"),
1116
            'abs({0})': ('__abs__', None),
1117
        }
1118
        obj = 'FoobarClass()'
1119
        sugg = 'implement "{0}" on FoobarClass'
1120
        for op, suggestions in ops.items():
1121
            code = op.format(obj)
1122
            magic, sugg_attr = suggestions
1123
            sugg_unary = sugg.format(magic)
1124
            self.throws(code, ATTRIBUTEERROR, sugg_attr,
1125
                        up_to_version(version))
1126
            self.throws(code, BADOPERANDUNARY, sugg_unary,
1127
                        from_version(version))
1128
1129
    def test_unary_operand_builtin(self):
1130
        """Test unary operand errors on builtin types."""
1131
        ops = [
1132
            '+{0}',
1133
            '-{0}',
1134
            '~{0}',
1135
            'abs({0})',
1136
        ]
1137
        obj = 'set()'
1138
        for op in ops:
1139
            code = op.format(obj)
1140
            self.throws(code, BADOPERANDUNARY)
1141
1142
    def test_len_on_iterable(self):
1143
        """len() can't be called on iterable (weird but understandable)."""
1144
        code = 'len(my_generator())'
1145
        sugg = 'len(list(my_generator()))'
1146
        self.throws(code, OBJECTHASNOFUNC, "'len(list(generator))'")
1147
        self.runs(sugg)
1148
1149
    def test_nb_args(self):
1150
        """Should have 1 arg."""
1151
        typo, sugg = '1, 2', '1'
1152
        code = func_gen(param='a', args='{0}')
1153
        bad_code, good_code = format_str(code, typo, sugg)
1154
        self.throws(bad_code, NBARGERROR)
1155
        self.runs(good_code)
1156
1157
    def test_nb_args1(self):
1158
        """Should have 0 args."""
1159
        typo, sugg = '1', ''
1160
        code = func_gen(param='', args='{0}')
1161
        bad_code, good_code = format_str(code, typo, sugg)
1162
        self.throws(bad_code, NBARGERROR)
1163
        self.runs(good_code)
1164
1165
    def test_nb_args2(self):
1166
        """Should have 1 arg."""
1167
        typo, sugg = '', '1'
1168
        version = (3, 3)
1169
        code = func_gen(param='a', args='{0}')
1170
        bad_code, good_code = format_str(code, typo, sugg)
1171
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1172
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1173
        self.runs(good_code)
1174
1175
    def test_nb_args3(self):
1176
        """Should have 3 args."""
1177
        typo, sugg = '1', '1, 2, 3'
1178
        version = (3, 3)
1179
        code = func_gen(param='so, much, args', args='{0}')
1180
        bad_code, good_code = format_str(code, typo, sugg)
1181
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1182
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1183
        self.runs(good_code)
1184
1185
    def test_nb_args4(self):
1186
        """Should have 3 args."""
1187
        typo, sugg = '', '1, 2, 3'
1188
        version = (3, 3)
1189
        code = func_gen(param='so, much, args', args='{0}')
1190
        bad_code, good_code = format_str(code, typo, sugg)
1191
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1192
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1193
        self.runs(good_code)
1194
1195
    def test_nb_args5(self):
1196
        """Should have 3 args."""
1197
        typo, sugg = '1, 2', '1, 2, 3'
1198
        version = (3, 3)
1199
        code = func_gen(param='so, much, args', args='{0}')
1200
        bad_code, good_code = format_str(code, typo, sugg)
1201
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1202
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1203
        self.runs(good_code)
1204
1205
    def test_nb_args6(self):
1206
        """Should provide more args."""
1207
        # Amusing message: 'func() takes exactly 2 arguments (2 given)'
1208
        version = (3, 3)
1209
        code = func_gen(param='a, b, c=3', args='{0}')
1210
        bad_code, good_code1, good_code2 = format_str(
1211
            code,
1212
            'b=2, c=3',
1213
            'a=1, b=2, c=3',
1214
            '1, b=2, c=3')
1215
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1216
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1217
        self.runs(good_code1)
1218
        self.runs(good_code2)
1219
1220
    def test_nb_arg_missing_self(self):
1221
        """Arg 'self' is missing."""
1222
        # NICE_TO_HAVE
1223
        obj = 'FoobarClass()'
1224
        self.throws(obj + '.some_method_missing_self_arg()', NBARGERROR)
1225
        self.throws(obj + '.some_method_missing_self_arg2(42)', NBARGERROR)
1226
        self.runs(obj + '.some_method()')
1227
        self.runs(obj + '.some_method2(42)')
1228
1229
    def test_nb_arg_missing_cls(self):
1230
        """Arg 'cls' is missing."""
1231
        # NICE_TO_HAVE
1232
        for obj in ('FoobarClass()', 'FoobarClass'):
1233
            self.throws(obj + '.some_cls_method_missing_cls()', NBARGERROR)
1234
            self.throws(obj + '.some_cls_method_missing_cls2(42)', NBARGERROR)
1235
            self.runs(obj + '.this_is_cls_mthd()')
1236
1237
    def test_keyword_args(self):
1238
        """Should be param 'babar' not 'a' but it's hard to guess."""
1239
        typo, sugg = 'a', 'babar'
1240
        code = func_gen(param=sugg, args='{0}=1')
1241
        bad_code, good_code = format_str(code, typo, sugg)
1242
        self.throws(bad_code, UNEXPECTEDKWARG)
1243
        self.runs(good_code)
1244
1245
    def test_keyword_args2(self):
1246
        """Should be param 'abcdef' not 'abcdf'."""
1247
        typo, sugg = 'abcdf', 'abcdef'
1248
        code = func_gen(param=sugg, args='{0}=1')
1249
        bad_code, good_code = format_str(code, typo, sugg)
1250
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1251
        self.runs(good_code)
1252
1253
    def test_keyword_arg_method(self):
1254
        """Should be the same as previous test but on a method."""
1255
        code = 'class MyClass:\n\tdef func(self, a):' \
1256
               '\n\t\tpass\nMyClass().func({0}=1)'
1257
        bad_code, good_code = format_str(code, 'babar', 'a')
1258
        self.throws(bad_code, UNEXPECTEDKWARG)
1259
        self.runs(good_code)
1260
1261
    def test_keyword_arg_method2(self):
1262
        """Should be the same as previous test but on a method."""
1263
        typo, sugg = 'abcdf', 'abcdef'
1264
        code = 'class MyClass:\n\tdef func(self, ' + sugg + '):' \
1265
               '\n\t\tpass\nMyClass().func({0}=1)'
1266
        bad_code, good_code = format_str(code, typo, sugg)
1267
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1268
        self.runs(good_code)
1269
1270
    def test_keyword_arg_class_method(self):
1271
        """Should be the same as previous test but on a class method."""
1272
        code = 'class MyClass:\n\t@classmethod\n\tdef func(cls, a):' \
1273
               '\n\t\tpass\nMyClass.func({0}=1)'
1274
        bad_code, good_code = format_str(code, 'babar', 'a')
1275
        self.throws(bad_code, UNEXPECTEDKWARG)
1276
        self.runs(good_code)
1277
1278
    def test_keyword_arg_class_method2(self):
1279
        """Should be the same as previous test but on a class method."""
1280
        typo, sugg = 'abcdf', 'abcdef'
1281
        code = 'class MyClass:\n\t@classmethod ' \
1282
               '\n\tdef func(cls, ' + sugg + '):\n ' \
1283
               '\t\tpass\nMyClass.func({0}=1)'
1284
        bad_code, good_code = format_str(code, typo, sugg)
1285
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1286
        self.runs(good_code)
1287
1288
    def test_keyword_arg_multiples_instances(self):
1289
        """If multiple functions are found, suggestions should be unique."""
1290
        typo, sugg = 'abcdf', 'abcdef'
1291
        code = 'class MyClass:\n\tdef func(self, ' + sugg + '):' \
1292
               '\n\t\tpass\na = MyClass()\nb = MyClass()\na.func({0}=1)'
1293
        bad_code, good_code = format_str(code, typo, sugg)
1294
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1295
        self.runs(good_code)
1296
1297
    def test_keyword_arg_lambda(self):
1298
        """Test with lambda functions instead of usual function."""
1299
        typo, sugg = 'abcdf', 'abcdef'
1300
        code = 'f = lambda arg1, ' + sugg + ': None\nf(42, {0}=None)'
1301
        bad_code, good_code = format_str(code, typo, sugg)
1302
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1303
        self.runs(good_code)
1304
1305
    def test_keyword_arg_lambda_method(self):
1306
        """Test with lambda methods instead of usual methods."""
1307
        typo, sugg = 'abcdf', 'abcdef'
1308
        code = 'class MyClass:\n\tfunc = lambda self, ' + sugg + ': None' \
1309
               '\nMyClass().func({0}=1)'
1310
        bad_code, good_code = format_str(code, typo, sugg)
1311
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1312
        self.runs(good_code)
1313
1314
    def test_keyword_arg_other_objects_with_name(self):
1315
        """Mix of previous tests but with more objects defined.
1316
1317
        Non-function object with same same as the function tested are defined
1318
        to ensure that things do work fine.
1319
        """
1320
        code = 'func = "not_a_func"\nclass MyClass:\n\tdef func(self, a):' \
1321
               '\n\t\tpass\nMyClass().func({0}=1)'
1322
        bad_code, good_code = format_str(code, 'babar', 'a')
1323
        self.throws(bad_code, UNEXPECTEDKWARG)
1324
        self.runs(good_code)
1325
1326
    def test_keyword_builtin(self):
1327
        """A few builtins (like int()) have a different error message."""
1328
        # NICE_TO_HAVE
1329
        # 'max', 'input', 'len', 'abs', 'all', etc have a specific error
1330
        # message and are not relevant here
1331
        for builtin in ['int', 'float', 'bool', 'complex']:
1332
            code = builtin + '(this_doesnt_exist=2)'
1333
            self.throws(code, UNEXPECTEDKWARG2, [], ALL_VERSIONS, 'cython')
1334
            self.throws(code, UNEXPECTEDKWARG, [], ALL_VERSIONS, 'pypy')
1335
1336
    def test_keyword_builtin_print(self):
1337
        """Builtin "print" has a different error message."""
1338
        # It would be NICE_TO_HAVE suggestions on keyword arguments
1339
        v3 = (3, 0)
1340
        code = "c = 'string'\nb = print(c, end_='toto')"
1341
        self.throws(code, INVALIDSYNTAX, [], up_to_version(v3))
1342
        self.throws(code, UNEXPECTEDKWARG2, [], from_version(v3), 'cython')
1343
        self.throws(code, UNEXPECTEDKWARG3, [], from_version(v3), 'pypy')
1344
1345
    def test_keyword_sort_cmpkey(self):
1346
        """Sort and sorted functions have a cmp/key param dep. on the vers."""
1347
        # NICE_TO_HAVE
1348
        v3 = (3, 0)
1349
        code = "import functools as f\nl = [1, 8, 3]\n" \
1350
               "def comp(a, b): return (a > b) - (a < b)\nl.sort({0})"
1351
        cmp_arg, key_arg, cmp_to_key = format_str(
1352
                code, 'cmp=comp', 'key=id', 'key=f.cmp_to_key(comp)')
1353
        self.runs(cmp_arg, up_to_version(v3))
1354
        self.throws(cmp_arg, UNEXPECTEDKWARG2,
1355
                    CMP_ARG_REMOVED_MSG, from_version(v3), 'cython')
1356 View Code Duplication
        self.throws(cmp_arg, UNEXPECTEDKWARG,
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1357
                    CMP_ARG_REMOVED_MSG, from_version(v3), 'pypy')
1358
        self.runs(key_arg)
1359
        self.runs(cmp_to_key, from_version((2, 7)))
1360
1361
    def test_no_implicit_str_conv(self):
1362
        """Trying to concatenate a non-string value to a string."""
1363
        # NICE_TO_HAVE
1364
        code = '{0} + " things"'
1365
        typo, sugg = '12', 'str(12)'
1366
        bad_code, good_code = format_str(code, typo, sugg)
1367
        self.throws(bad_code, UNSUPPORTEDOPERAND)
1368
        self.runs(good_code)
1369
1370
    def test_no_implicit_str_conv2(self):
1371
        """Trying to concatenate a non-string value to a string."""
1372
        # NICE_TO_HAVE
1373
        code = '"things " + {0}'
1374
        typo, sugg = '12', 'str(12)'
1375
        bad_code, good_code = format_str(code, typo, sugg)
1376
        version = (3, 0)
1377
        version2 = (3, 6)
1378
        self.throws(
1379
            bad_code, CANNOTCONCAT, [], up_to_version(version), 'cython')
1380
        self.throws(
1381
            bad_code, CANTCONVERT, [], (version, version2), 'cython')
1382
        self.throws(
1383
            bad_code, MUSTBETYPENOTTYPE, [], from_version(version2), 'cython')
1384
        self.throws(
1385
            bad_code, UNSUPPORTEDOPERAND, [], ALL_VERSIONS, 'pypy')
1386
        self.runs(good_code)
1387
1388
    def test_assignment_to_range(self):
1389
        """Trying to assign to range works on list, not on range."""
1390
        code = '{0}[2] = 1'
1391
        typo, sugg = 'range(4)', 'list(range(4))'
1392
        version = (3, 0)
1393
        bad_code, good_code = format_str(code, typo, sugg)
1394
        self.runs(good_code)
1395
        self.runs(bad_code, up_to_version(version))
1396
        self.throws(
1397
            bad_code,
1398
            OBJECTDOESNOTSUPPORT,
1399
            'convert to list to edit the list',
1400
            from_version(version))
1401
1402
    def test_assignment_to_string(self):
1403
        """Trying to assign to string does not work."""
1404 View Code Duplication
        code = "s = 'abc'\ns[1] = 'd'"
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1405
        good_code = "s = 'abc'\nl = list(s)\nl[1] = 'd'\ns = ''.join(l)"
1406
        self.runs(good_code)
1407
        self.throws(
1408
            code,
1409
            OBJECTDOESNOTSUPPORT,
1410
            'convert to list to edit the list and use "join()" on the list')
1411
1412
    def test_deletion_from_string(self):
1413
        """Delete from string does not work."""
1414
        code = "s = 'abc'\ndel s[1]"
1415
        good_code = "s = 'abc'\nl = list(s)\ndel l[1]\ns = ''.join(l)"
1416
        self.runs(good_code)
1417
        self.throws(
1418
            code,
1419
            OBJECTDOESNOTSUPPORT,
1420
            'convert to list to edit the list and use "join()" on the list')
1421
1422
    def test_object_indexing(self):
1423
        """Index from object does not work if __getitem__ is not defined."""
1424
        version = (3, 0)
1425
        code = "{0}[0]"
1426
        good_code, set_code, custom_code = \
1427
            format_str(code, '"a_string"', "set()", "FoobarClass()")
1428
        self.runs(good_code)
1429
        sugg_for_iterable = 'convert to list first or use the iterator ' \
1430
            'protocol to get the different elements'
1431
        self.throws(set_code,
1432
                    OBJECTDOESNOTSUPPORT,
1433
                    sugg_for_iterable, ALL_VERSIONS, 'cython')
1434
        self.throws(set_code,
1435
                    UNSUBSCRIPTABLE,
1436
                    sugg_for_iterable, ALL_VERSIONS, 'pypy')
1437
        self.throws(custom_code,
1438
                    ATTRIBUTEERROR, [], up_to_version(version), 'pypy')
1439
        self.throws(custom_code,
1440
                    UNSUBSCRIPTABLE,
1441
                    'implement "__getitem__" on FoobarClass',
1442
                    from_version(version), 'pypy')
1443
        self.throws(custom_code,
1444
                    ATTRIBUTEERROR, [], up_to_version(version), 'cython')
1445
        self.throws(custom_code,
1446
                    OBJECTDOESNOTSUPPORT,
1447
                    'implement "__getitem__" on FoobarClass',
1448
                    from_version(version), 'cython')
1449
1450
    def test_not_callable(self):
1451
        """Sometimes, one uses parenthesis instead of brackets."""
1452
        typo, getitem = '(0)', '[0]'
1453
        for ex, sugg in {
1454
            '[0]': "'list[value]'",
1455
            '{0: 0}': "'dict[value]'",
1456
            '"a"': "'str[value]'",
1457
        }.items():
1458
            self.throws(ex + typo, NOTCALLABLE, sugg)
1459
            self.runs(ex + getitem)
1460
        for ex in ['1', 'set()']:
1461
            self.throws(ex + typo, NOTCALLABLE)
1462
1463
    def test_exc_must_derive_from(self):
1464
        """Test when a non-exc object is raised."""
1465
        code = 'raise "ExceptionString"'
1466
        self.throws(code, EXCMUSTDERIVE)
1467
1468
    def test_unordered_builtin(self):
1469
        """Test for UNORDERABLE exception on builtin types."""
1470
        version = (3, 0)
1471
        version2 = (3, 6)
1472
        for op in ['>', '>=', '<', '<=']:
1473
            code = "'10' {0} 2".format(op)
1474
            self.runs(code, up_to_version(version))
1475
            self.throws(code, UNORDERABLE, [], (version, version2))
1476
            self.throws(code, OPNOTSUPPBETWEENINST, [], from_version(version2))
1477
1478
    def test_unordered_custom(self):
1479
        """Test for UNORDERABLE exception on custom types."""
1480
        version = (3, 0)
1481
        version2 = (3, 6)
1482
        for op in ['>', '>=', '<', '<=']:
1483
            code = "FoobarClass() {0} FoobarClass()".format(op)
1484
            self.runs(code, up_to_version(version))
1485
            self.throws(code, UNORDERABLE, [], (version, version2))
1486
            self.throws(code, OPNOTSUPPBETWEENINST, [], from_version(version2))
1487
1488
    def test_unordered_custom2(self):
1489
        """Test for UNORDERABLE exception on custom types."""
1490
        version = (3, 0)
1491
        version2 = (3, 6)
1492
        for op in ['>', '>=', '<', '<=']:
1493
            code = "FoobarClass() {0} 2".format(op)
1494
            self.runs(code, up_to_version(version))
1495
            self.throws(code, UNORDERABLE, [], (version, version2))
1496
            self.throws(code, OPNOTSUPPBETWEENINST, [], from_version(version2))
1497
1498
    def test_unmatched_msg(self):
1499
        """Test that arbitrary strings are supported."""
1500
        self.throws(
1501
            'raise TypeError("unmatched TYPEERROR")',
1502
            UNKNOWN_TYPEERROR)
1503
1504
1505
class ImportErrorTests(GetSuggestionsTests):
1506
    """Class for tests related to ImportError."""
1507
1508
    def test_no_module_no_sugg(self):
1509
        """No suggestion."""
1510
        self.throws('import fqslkdfjslkqdjfqsd', NOMODULE)
1511
1512
    def test_no_module(self):
1513
        """Should be 'math'."""
1514
        code = 'import {0}'
1515
        typo, sugg = 'maths', 'math'
1516
        self.assertTrue(sugg in STAND_MODULES)
1517
        bad_code, good_code = format_str(code, typo, sugg)
1518
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1519
        self.runs(good_code)
1520
1521
    def test_no_module2(self):
1522
        """Should be 'math'."""
1523
        code = 'from {0} import pi'
1524
        typo, sugg = 'maths', 'math'
1525
        self.assertTrue(sugg in STAND_MODULES)
1526
        bad_code, good_code = format_str(code, typo, sugg)
1527
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1528
        self.runs(good_code)
1529
1530
    def test_no_module3(self):
1531
        """Should be 'math'."""
1532
        code = 'import {0} as my_imported_math'
1533
        typo, sugg = 'maths', 'math'
1534
        self.assertTrue(sugg in STAND_MODULES)
1535
        bad_code, good_code = format_str(code, typo, sugg)
1536
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1537
        self.runs(good_code)
1538
1539
    def test_no_module4(self):
1540
        """Should be 'math'."""
1541
        code = 'from {0} import pi as three_something'
1542
        typo, sugg = 'maths', 'math'
1543
        self.assertTrue(sugg in STAND_MODULES)
1544
        bad_code, good_code = format_str(code, typo, sugg)
1545
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1546
        self.runs(good_code)
1547
1548
    def test_no_module5(self):
1549
        """Should be 'math'."""
1550
        code = '__import__("{0}")'
1551
        typo, sugg = 'maths', 'math'
1552
        self.assertTrue(sugg in STAND_MODULES)
1553
        bad_code, good_code = format_str(code, typo, sugg)
1554
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1555
        self.runs(good_code)
1556
1557
    def test_import_future_nomodule(self):
1558
        """Should be '__future__'."""
1559
        code = 'import {0}'
1560
        typo, sugg = '__future_', '__future__'
1561
        self.assertTrue(sugg in STAND_MODULES)
1562
        bad_code, good_code = format_str(code, typo, sugg)
1563
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1564
        self.runs(good_code)
1565
1566
    def test_no_name_no_sugg(self):
1567
        """No suggestion."""
1568
        self.throws('from math import fsfsdfdjlkf', CANNOTIMPORT)
1569
1570
    def test_wrong_import(self):
1571
        """Should be 'math'."""
1572
        code = 'from {0} import pi'
1573
        typo, sugg = 'itertools', 'math'
1574
        self.assertTrue(sugg in STAND_MODULES)
1575
        bad_code, good_code = format_str(code, typo, sugg)
1576
        self.throws(bad_code, CANNOTIMPORT, "'" + good_code + "'")
1577
        self.runs(good_code)
1578
1579
    def test_typo_in_method(self):
1580
        """Should be 'pi'."""
1581
        code = 'from math import {0}'
1582
        typo, sugg = 'pie', 'pi'
1583
        bad_code, good_code = format_str(code, typo, sugg)
1584
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1585
        self.runs(good_code)
1586
1587
    def test_typo_in_method2(self):
1588
        """Should be 'pi'."""
1589
        code = 'from math import e, {0}, log'
1590
        typo, sugg = 'pie', 'pi'
1591
        bad_code, good_code = format_str(code, typo, sugg)
1592
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1593
        self.runs(good_code)
1594
1595
    def test_typo_in_method3(self):
1596
        """Should be 'pi'."""
1597
        code = 'from math import {0} as three_something'
1598
        typo, sugg = 'pie', 'pi'
1599
        bad_code, good_code = format_str(code, typo, sugg)
1600
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1601
        self.runs(good_code)
1602
1603
    def test_unmatched_msg(self):
1604
        """Test that arbitrary strings are supported."""
1605
        self.throws(
1606
            'raise ImportError("unmatched IMPORTERROR")',
1607
            UNKNOWN_IMPORTERROR)
1608
1609
    def test_module_removed(self):
1610
        """Sometimes, modules are deleted/moved/renamed."""
1611
        # NICE_TO_HAVE
1612
        version1 = (2, 7)  # result for 2.6 seems to vary
1613
        version2 = (3, 0)
1614
        code = 'import {0}'
1615
        lower, upper = format_str(code, 'tkinter', 'Tkinter')
1616
        self.throws(lower, NOMODULE, [], (version1, version2))
1617
        self.throws(upper, NOMODULE, [], from_version(version2))
1618
1619
1620
class LookupErrorTests(GetSuggestionsTests):
1621
    """Class for tests related to LookupError."""
1622
1623
1624
class KeyErrorTests(LookupErrorTests):
1625
    """Class for tests related to KeyError."""
1626
1627
    def test_no_sugg(self):
1628
        """No suggestion."""
1629
        self.throws('dict()["ffdsqmjklfqsd"]', KEYERROR)
1630
1631
1632
class IndexErrorTests(LookupErrorTests):
1633
    """Class for tests related to IndexError."""
1634
1635
    def test_no_sugg(self):
1636
        """No suggestion."""
1637
        self.throws('list()[2]', OUTOFRANGE)
1638
1639
1640
class SyntaxErrorTests(GetSuggestionsTests):
1641
    """Class for tests related to SyntaxError."""
1642
1643
    def test_no_error(self):
1644
        """No error."""
1645
        self.runs("1 + 2 == 2")
1646
1647
    def test_yield_return_out_of_func(self):
1648
        """yield/return needs to be in functions."""
1649
        sugg = "to indent it"
1650
        self.throws("yield 1", OUTSIDEFUNC, sugg)
1651
        self.throws("return 1", OUTSIDEFUNC, ["'sys.exit([arg])'", sugg])
1652
1653
    def test_print(self):
1654
        """print is a functions now and needs parenthesis."""
1655
        # NICE_TO_HAVE
1656
        code, new_code = 'print ""', 'print("")'
1657
        version = (3, 0)
1658
        version2 = (3, 4)
1659
        self.runs(code, up_to_version(version))
1660
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1661
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1662
        self.runs(new_code)
1663
1664
    def test_exec(self):
1665
        """exec is a functions now and needs parenthesis."""
1666
        # NICE_TO_HAVE
1667
        code, new_code = 'exec "1"', 'exec("1")'
1668
        version = (3, 0)
1669
        version2 = (3, 4)
1670
        self.runs(code, up_to_version(version))
1671
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1672
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1673
        self.runs(new_code)
1674
1675 View Code Duplication
    def test_old_comparison(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1676
        """<> comparison is removed, != always works."""
1677
        code = '1 {0} 2'
1678
        old, new = '<>', '!='
1679
        version = (3, 0)
1680
        old_code, new_code = format_str(code, old, new)
1681
        self.runs(old_code, up_to_version(version))
1682
        self.throws(
1683
            old_code,
1684
            INVALIDCOMP,
1685
            "'!='",
1686
            from_version(version),
1687
            'pypy')
1688
        self.throws(
1689
            old_code,
1690
            INVALIDSYNTAX,
1691
            "'!='",
1692
            from_version(version),
1693
            'cython')
1694
        self.runs(new_code)
1695
1696
    def test_missing_colon(self):
1697
        """Missing colon is a classic mistake."""
1698
        # NICE_TO_HAVE
1699
        code = "if True{0}\n\tpass"
1700
        bad_code, good_code = format_str(code, "", ":")
1701
        self.throws(bad_code, INVALIDSYNTAX)
1702
        self.runs(good_code)
1703
1704
    def test_missing_colon2(self):
1705
        """Missing colon is a classic mistake."""
1706
        # NICE_TO_HAVE
1707
        code = "class MyClass{0}\n\tpass"
1708
        bad_code, good_code = format_str(code, "", ":")
1709
        self.throws(bad_code, INVALIDSYNTAX)
1710
        self.runs(good_code)
1711
1712
    def test_simple_equal(self):
1713
        """'=' for comparison is a classic mistake."""
1714
        # NICE_TO_HAVE
1715
        code = "if 2 {0} 3:\n\tpass"
1716
        bad_code, good_code = format_str(code, "=", "==")
1717
        self.throws(bad_code, INVALIDSYNTAX)
1718
        self.runs(good_code)
1719
1720
    def test_keyword_as_identifier(self):
1721
        """Using a keyword as a variable name."""
1722
        # NICE_TO_HAVE
1723
        code = '{0} = 1'
1724
        bad_code, good_code = format_str(code, "from", "from_")
1725
        self.throws(bad_code, INVALIDSYNTAX)
1726
        self.runs(good_code)
1727
1728
    def test_increment(self):
1729
        """Trying to use '++' or '--'."""
1730
        # NICE_TO_HAVE
1731
        code = 'a = 0\na{0}'
1732
        # Adding pointless suffix to avoid wrong assumptions
1733
        for end in ('', '  ', ';', ' ;'):
1734
            code2 = code + end
1735
            for op in ('-', '+'):
1736
                typo, sugg = 2 * op, op + '=1'
1737
                bad_code, good_code = format_str(code + end, typo, sugg)
1738
                self.throws(bad_code, INVALIDSYNTAX)
1739
                self.runs(good_code)
1740
1741
    def test_wrong_bool_operator(self):
1742
        """Trying to use '&&' or '||'."""
1743
        code = 'True {0} False'
1744
        for typo, sugg in (('&&', 'and'), ('||', 'or')):
1745
            bad_code, good_code = format_str(code, typo, sugg)
1746
            self.throws(bad_code, INVALIDSYNTAX, "'" + sugg + "'")
1747
            self.runs(good_code)
1748
1749
    def test_import_future_not_first(self):
1750
        """Test what happens when import from __future__ is not first."""
1751
        code = 'a = 8/7\nfrom __future__ import division'
1752
        self.throws(code, FUTUREFIRST)
1753
1754
    def test_import_future_not_def(self):
1755
        """Should be 'division'."""
1756
        code = 'from __future__ import {0}'
1757
        typo, sugg = 'divisio', 'division'
1758
        bad_code, good_code = format_str(code, typo, sugg)
1759
        self.throws(bad_code, FUTFEATNOTDEF, "'" + sugg + "'")
1760
        self.runs(good_code)
1761
1762
    def test_unqualified_exec(self):
1763
        """Exec in nested functions."""
1764
        # NICE_TO_HAVE
1765
        version = (3, 0)
1766
        codes = [
1767
            "def func1():\n\tbar='1'\n\tdef func2():"
1768
            "\n\t\texec(bar)\n\tfunc2()\nfunc1()",
1769
            "def func1():\n\texec('1')\n\tdef func2():"
1770
            "\n\t\tTrue",
1771
        ]
1772
        for code in codes:
1773
            self.throws(code, UNQUALIFIED_EXEC, [], up_to_version(version))
1774
            self.runs(code, from_version(version))
1775
1776
    def test_import_star(self):
1777
        """'import *' in nested functions."""
1778
        # NICE_TO_HAVE
1779
        codes = [
1780
            "def func1():\n\tbar='1'\n\tdef func2():"
1781
            "\n\t\tfrom math import *\n\t\tTrue\n\tfunc2()\nfunc1()",
1782
            "def func1():\n\tfrom math import *"
1783
            "\n\tdef func2():\n\t\tTrue",
1784
        ]
1785
        with warnings.catch_warnings():
1786
            warnings.simplefilter("ignore", category=SyntaxWarning)
1787
            for code in codes:
1788
                self.throws(code, IMPORTSTAR, [])
1789
1790
    def test_unpack(self):
1791
        """Extended tuple unpacking does not work prior to Python 3."""
1792
        # NICE_TO_HAVE
1793
        version = (3, 0)
1794
        code = 'a, *b = (1, 2, 3)'
1795
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1796
        self.runs(code, from_version(version))
1797
1798
    def test_unpack2(self):
1799
        """Unpacking in function arguments was supported up to Python 3."""
1800
        # NICE_TO_HAVE
1801
        version = (3, 0)
1802
        code = 'def addpoints((x1, y1), (x2, y2)):\n\tpass'
1803
        self.runs(code, up_to_version(version))
1804
        self.throws(code, INVALIDSYNTAX, [], from_version(version))
1805
1806
    def test_nonlocal(self):
1807
        """nonlocal keyword is added in Python 3."""
1808 View Code Duplication
        # NICE_TO_HAVE
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1809
        version = (3, 0)
1810
        code = 'def func():\n\tfoo = 1\n\tdef nested():\n\t\tnonlocal foo'
1811
        self.runs(code, from_version(version))
1812
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1813
1814
    def test_nonlocal2(self):
1815
        """nonlocal must be used only when binding exists."""
1816
        # NICE_TO_HAVE
1817
        version = (3, 0)
1818
        code = 'def func():\n\tdef nested():\n\t\tnonlocal foo'
1819
        self.throws(code, NOBINDING, [], from_version(version))
1820
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1821
1822
    def test_nonlocal3(self):
1823
        """nonlocal must be used only when binding to non-global exists."""
1824
        # just a way to say that this_is_a_global_list is needed in globals
1825
        name = 'this_is_a_global_list'
1826
        this_is_a_global_list
1827
        self.assertFalse(name in locals())
1828
        self.assertTrue(name in globals())
1829
        version = (3, 0)
1830
        code = 'def func():\n\tdef nested():\n\t\t{0} ' + name
1831
        typo, sugg = 'nonlocal', 'global'
1832
        bad_code, good_code = format_str(code, typo, sugg)
1833
        self.runs(good_code)
1834
        self.throws(bad_code,
1835
                    NOBINDING, "'{0} {1}'".format(sugg, name),
1836
                    from_version(version))
1837
        self.throws(bad_code, INVALIDSYNTAX, [], up_to_version(version))
1838
1839
    def test_nonlocal4(self):
1840
        """suggest close matches to variable name."""
1841
        # NICE_TO_HAVE (needs access to variable in enclosing scope)
1842
        version = (3, 0)
1843
        code = 'def func():\n\tfoo = 1\n\tdef nested():\n\t\tnonlocal {0}'
1844
        typo, sugg = 'foob', 'foo'
1845
        bad_code, good_code = format_str(code, typo, sugg)
1846
        self.runs(good_code, from_version(version))
1847
        self.throws(good_code, INVALIDSYNTAX, [], up_to_version(version))
1848
        self.throws(bad_code, NOBINDING, [], from_version(version))
1849
        self.throws(bad_code, INVALIDSYNTAX, [], up_to_version(version))
1850
1851
    def test_nonlocal_at_module_level(self):
1852
        """nonlocal must be used in function."""
1853
        version1 = (2, 7)
1854
        version2 = (3, 0)
1855
        code = 'nonlocal foo'
1856
        self.throws(code, UNEXPECTED_OEF, [], up_to_version(version1))
1857
        self.throws(code, INVALIDSYNTAX, [], (version1, version2))
1858
        self.throws(code, NONLOCALMODULE, [], from_version(version2))
1859
1860
    def test_octal_literal(self):
1861
        """Syntax for octal liberals has changed."""
1862
        # NICE_TO_HAVE
1863
        version = (3, 0)
1864
        bad, good = '0720', '0o720'
1865
        self.runs(good)
1866
        self.runs(bad, up_to_version(version))
1867
        self.throws(bad, INVALIDTOKEN, [], from_version(version), 'cython')
1868
        self.throws(bad, INVALIDSYNTAX, [], from_version(version), 'pypy')
1869
1870
    def test_extended_unpacking(self):
1871
        """Extended iterable unpacking is added with Python 3."""
1872
        version = (3, 0)
1873
        code = '(a, *rest, b) = range(5)'
1874
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1875
        self.runs(code, from_version(version))
1876
1877
    def test_ellipsis(self):
1878
        """Triple dot (...) aka Ellipsis can be used anywhere in Python 3."""
1879
        version = (3, 0)
1880
        code = '...'
1881
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1882
        self.runs(code, from_version(version))
1883
1884
    def test_fstring(self):
1885
        """Fstring (see PEP 498) appeared in Python 3.6."""
1886
        # NICE_TO_HAVE
1887
        version = (3, 6)
1888
        code = 'f"toto"'
1889
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1890
        self.runs(code, from_version(version))
1891
1892
1893
class MemoryErrorTests(GetSuggestionsTests):
1894
    """Class for tests related to MemoryError."""
1895
1896
    def test_out_of_memory(self):
1897
        """Test what happens in case of MemoryError."""
1898
        code = '[0] * 999999999999999'
1899
        self.throws(code, MEMORYERROR)
1900
1901
    def test_out_of_memory_range(self):
1902
        """Test what happens in case of MemoryError."""
1903
        code = '{0}(999999999999999)'
1904
        typo, sugg = 'range', 'xrange'
1905
        bad_code, good_code = format_str(code, typo, sugg)
1906
        self.runs(bad_code, ALL_VERSIONS, 'pypy')
1907
        version = (2, 7)
1908
        version2 = (3, 0)
1909
        self.throws(
1910
            bad_code,
1911
            OVERFLOWERR, "'" + sugg + "'",
1912
            up_to_version(version),
1913
            'cython')
1914
        self.throws(
1915
            bad_code,
1916
            MEMORYERROR, "'" + sugg + "'",
1917
            (version, version2),
1918
            'cython')
1919
        self.runs(good_code, up_to_version(version2), 'cython')
1920
        self.runs(bad_code, from_version(version2), 'cython')
1921
1922
1923
class ValueErrorTests(GetSuggestionsTests):
1924
    """Class for tests related to ValueError."""
1925
1926
    def test_too_many_values(self):
1927
        """Unpack 4 values in 3 variables."""
1928
        code = 'a, b, c = [1, 2, 3, 4]'
1929
        version = (3, 0)
1930
        self.throws(code, EXPECTEDLENGTH, [], up_to_version(version), 'pypy')
1931
        self.throws(code, TOOMANYVALUES, [], from_version(version), 'pypy')
1932
        self.throws(code, TOOMANYVALUES, [], ALL_VERSIONS, 'cython')
1933
1934
    def test_not_enough_values(self):
1935
        """Unpack 2 values in 3 variables."""
1936
        code = 'a, b, c = [1, 2]'
1937
        version = (3, 0)
1938
        self.throws(code, EXPECTEDLENGTH, [], up_to_version(version), 'pypy')
1939
        self.throws(code, NEEDMOREVALUES, [], from_version(version), 'pypy')
1940
        self.throws(code, NEEDMOREVALUES, [], ALL_VERSIONS, 'cython')
1941
1942
    def test_conversion_fails(self):
1943
        """Conversion fails."""
1944
        self.throws('int("toto")', INVALIDLITERAL)
1945
1946
    def test_math_domain(self):
1947
        """Math function used out of its domain."""
1948
        code = 'import math\nlg = math.log(-1)'
1949
        self.throws(code, MATHDOMAIN)
1950
1951
    def test_zero_len_field_in_format(self):
1952
        """Format {} is not valid before Python 2.7."""
1953
        code = '"{0}".format(0)'
1954
        old, new = '{0}', '{}'
1955
        old_code, new_code = format_str(code, old, new)
1956
        version = (2, 7)
1957
        self.runs(old_code)
1958
        self.throws(new_code, ZEROLENERROR, '{0}', up_to_version(version))
1959
        self.runs(new_code, from_version(version))
1960
1961
    def test_timedata_does_not_match(self):
1962
        """Strptime arguments are in wrong order."""
1963
        code = 'import datetime\ndatetime.datetime.strptime({0}, {1})'
1964
        timedata, timeformat = '"30 Nov 00"', '"%d %b %y"'
1965
        good_code = code.format(*(timedata, timeformat))
1966
        bad_code = code.format(*(timeformat, timedata))
1967
        self.runs(good_code)
1968
        self.throws(bad_code, TIMEDATAFORMAT,
1969
                    ['to swap value and format parameters'])
1970
1971
1972
class RuntimeErrorTests(GetSuggestionsTests):
1973
    """Class for tests related to RuntimeError."""
1974
1975
    def test_max_depth(self):
1976
        """Reach maximum recursion depth."""
1977
        sys.setrecursionlimit(200)
1978
        code = 'endlessly_recursive_func(0)'
1979
        self.throws(code, MAXRECURDEPTH,
1980
                    ["increase the limit with `sys.setrecursionlimit(limit)`"
1981
                        " (current value is 200)",
1982
                     AVOID_REC_MSG])
1983
1984
    def test_dict_size_changed_during_iter(self):
1985
        """Test size change during iteration (dict)."""
1986
        # NICE_TO_HAVE
1987
        code = 'd = dict(enumerate("notimportant"))' \
1988
            '\nfor e in d:\n\td.pop(e)'
1989
        self.throws(code, SIZECHANGEDDURINGITER)
1990
1991
    def test_set_changed_size_during_iter(self):
1992
        """Test size change during iteration (set)."""
1993
        # NICE_TO_HAVE
1994
        code = 's = set("notimportant")' \
1995
            '\nfor e in s:\n\ts.pop()'
1996
        self.throws(code, SIZECHANGEDDURINGITER)
1997
1998
    def test_dequeue_changed_during_iter(self):
1999
        """Test size change during iteration (dequeue)."""
2000
        # NICE_TO_HAVE
2001
        # "deque mutated during iteration"
2002
        pass
2003
2004
2005
class IOErrorTests(GetSuggestionsTests):
2006
    """Class for tests related to IOError."""
2007
2008
    def test_no_such_file(self):
2009
        """File does not exist."""
2010
        code = 'with open("doesnotexist") as f:\n\tpass'
2011
        self.throws(code, NOFILE_IO)
2012
2013
    def test_no_such_file2(self):
2014
        """File does not exist."""
2015
        code = 'os.listdir("doesnotexist")'
2016
        self.throws(code, NOFILE_OS)
2017
2018
    def test_no_such_file_user(self):
2019
        """Suggestion when one needs to expanduser."""
2020
        code = 'os.listdir("{0}")'
2021
        typo, sugg = "~", os.path.expanduser("~")
2022
        bad_code, good_code = format_str(code, typo, sugg)
2023
        self.throws(
2024
            bad_code, NOFILE_OS,
2025
            "'" + sugg + "' (calling os.path.expanduser)")
2026
        self.runs(good_code)
2027
2028
    def test_no_such_file_vars(self):
2029
        """Suggestion when one needs to expandvars."""
2030
        code = 'os.listdir("{0}")'
2031
        key = 'HOME'
2032
        typo, sugg = "$" + key, os.path.expanduser("~")
2033
        original_home = os.environ.get('HOME')
2034
        os.environ[key] = sugg
2035
        bad_code, good_code = format_str(code, typo, sugg)
2036
        self.throws(
2037
            bad_code, NOFILE_OS,
2038
            "'" + sugg + "' (calling os.path.expandvars)")
2039
        self.runs(good_code)
2040
        if original_home is None:
2041
            del os.environ[key]
2042
        else:
2043
            os.environ[key] = original_home
2044
2045
    def create_tmp_dir_with_files(self, filelist):
2046
        """Create a temporary directory with files in it."""
2047
        tmpdir = tempfile.mkdtemp()
2048
        absfiles = [os.path.join(tmpdir, f) for f in filelist]
2049
        for name in absfiles:
2050
            open(name, 'a').close()
2051
        return (tmpdir, absfiles)
2052
2053
    def test_is_dir_empty(self):
2054
        """Suggestion when file is an empty directory."""
2055
        # Create empty temp dir
2056
        tmpdir, _ = self.create_tmp_dir_with_files([])
2057
        code = 'with open("{0}") as f:\n\tpass'
2058
        bad_code, _ = format_str(code, tmpdir, "TODO")
2059
        self.throws(
2060
            bad_code, ISADIR_IO, "to add content to {0} first".format(tmpdir))
2061
        rmtree(tmpdir)
2062
2063
    def test_is_dir_small(self):
2064
        """Suggestion when file is directory with a few files."""
2065
        # Create temp dir with a few files
2066
        nb_files = 3
2067
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
2068
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
2069
        code = 'with open("{0}") as f:\n\tpass'
2070
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
2071
        self.throws(
2072
            bad_code, ISADIR_IO,
2073
            "any of the 3 files in directory ('0.txt', '1.txt', '2.txt')")
2074
        self.runs(good_code)
2075
        rmtree(tmpdir)
2076
2077
    def test_is_dir_big(self):
2078
        """Suggestion when file is directory with many files."""
2079
        # Create temp dir with many files
2080
        tmpdir = tempfile.mkdtemp()
2081
        nb_files = 30
2082
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
2083
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
2084
        code = 'with open("{0}") as f:\n\tpass'
2085
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
2086
        self.throws(
2087
            bad_code, ISADIR_IO,
2088
            "any of the 30 files in directory "
2089
            "('0.txt', '1.txt', '10.txt', '11.txt', etc)")
2090
        self.runs(good_code)
2091
        rmtree(tmpdir)
2092
2093
    def test_is_not_dir(self):
2094
        """Suggestion when file is not a directory."""
2095
        code = 'with open("{0}") as f:\n\tpass'
2096
        code = 'os.listdir("{0}")'
2097
        typo, sugg = __file__, os.path.dirname(__file__)
2098
        bad_code, good_code = format_str(code, typo, sugg)
2099
        self.throws(
2100
            bad_code, NOTADIR_OS,
2101
            "'" + sugg + "' (calling os.path.dirname)")
2102
        self.runs(good_code)
2103
2104
    def test_dir_is_not_empty(self):
2105
        """Suggestion when directory is not empty."""
2106
        # NICE_TO_HAVE
2107
        nb_files = 3
2108
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
2109
        tmpdir, _ = self.create_tmp_dir_with_files(files)
2110
        self.throws('os.rmdir("{0}")'.format(tmpdir), DIRNOTEMPTY_OS)
2111
        rmtree(tmpdir)  # this should be the suggestion
2112
2113
2114
class AnyErrorTests(GetSuggestionsTests):
2115
    """Class for tests not related to an error type in particular."""
2116
2117
    def test_wrong_except(self):
2118
        """Test where except is badly used and thus does not catch.
2119
2120
        Common mistake : "except Exc1, Exc2" doesn't catch Exc2.
2121
        Adding parenthesis solves the issue.
2122
        """
2123
        # NICE_TO_HAVE
2124
        version = (3, 0)
2125
        raised_exc, other_exc = KeyError, TypeError
2126
        raised, other = raised_exc.__name__, other_exc.__name__
2127
        code = "try:\n\traise {0}()\nexcept {{0}}:\n\tpass".format(raised)
2128
        typo = "{0}, {1}".format(other, raised)
2129
        sugg = "({0})".format(typo)
2130
        bad1, bad2, good1, good2 = format_str(code, typo, other, sugg, raised)
2131
        self.throws(bad1, (raised_exc, None), [], up_to_version(version))
2132
        self.throws(bad1, INVALIDSYNTAX, [], from_version(version))
2133
        self.throws(bad2, (raised_exc, None))
2134
        self.runs(good1)
2135
        self.runs(good2)
2136
2137
2138
if __name__ == '__main__':
2139
    print(sys.version_info)
2140
    unittest2.main()
2141