Completed
Push — master ( 154999...b1f2dd )
by De
52s
created

UnboundLocalErrorTests.test_unbound_nonlocal()   A

Complexity

Conditions 1

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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