Completed
Push — master ( 621544...e4ecaf )
by De
23s
created

NameErrorTests.test_builtin()   A

Complexity

Conditions 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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