Completed
Push — master ( 67ac9b...84e688 )
by De
29:19 queued 07:00
created

AttributeErrorTests.test_wrongmethod5()   A

Complexity

Conditions 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
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_import_sugg(self):
731
        """Should import module first."""
732
        module = 'collections'
733
        sugg = 'import {0}'.format(module)
734
        typo, good_code = module, sugg + '\n' + module
735
        self.assertFalse(module in locals())
736
        self.assertFalse(module in globals())
737
        self.assertTrue(module in STAND_MODULES)
738
        suggestions = (
739
            # module.module is suggested on Python 3.3 :-/
740
            ["'{0}' from {1} (not imported)".format(module, module)]
741
            if version_in_range(((3, 3), (3, 4))) else []) + \
742
            ['to {0} first'.format(sugg)]
743
        self.throws(typo, NAMEERROR, suggestions)
744
        self.runs(good_code)
745
746
    def test_attribute_hidden(self):
747
        """Should be math.pi but module math is hidden."""
748
        math  # just a way to say that math module is needed in globals - noqa
749
        self.assertFalse('math' in locals())
750
        self.assertTrue('math' in globals())
751
        code = 'math = ""\npi'
752
        sugg = "'math.pi' (global hidden by local)"
753
        self.throws(code, NAMEERROR, sugg)
754
755
    def test_self(self):
756
        """"Should be self.babar."""
757
        code = 'FoobarClass().nameerror_self()'
758
        sugg = "'self.babar'"
759
        self.throws(code, NAMEERROR, sugg)
760
761
    def test_self2(self):
762
        """Should be self.this_is_cls_mthd."""
763
        code = 'FoobarClass().nameerror_self2()'
764
        suggs = ["'FoobarClass.this_is_cls_mthd'", "'self.this_is_cls_mthd'"]
765
        self.throws(code, NAMEERROR, suggs)
766
767
    def test_cls(self):
768
        """Should be cls.this_is_cls_mthd."""
769
        code = 'FoobarClass().nameerror_cls()'
770
        suggs = ["'FoobarClass.this_is_cls_mthd'", "'cls.this_is_cls_mthd'"]
771
        self.throws(code, NAMEERROR, suggs)
772
773
    def test_complex_numbers(self):
774
        """Should be 1j."""
775
        code = 'assert {0} ** 2 == -1'
776
        good = '1j'
777
        good_code, bad_code_i, bad_code_j = format_str(code, good, 'i', 'j')
778
        sugg = "'{0}' (imaginary unit)".format(good)
779
        self.throws(bad_code_i, NAMEERROR, sugg)
780
        self.throws(bad_code_j, NAMEERROR, sugg)
781
        self.runs(good_code)
782
783
    def test_shell_commands(self):
784
        """Trying shell commands."""
785
        cmd, good = 'ls', 'os.listdir(os.getcwd())'
786
        self.throws(cmd, NAMEERROR, quote(good))
787
        self.runs(good)
788
        cmd, good = 'pwd', 'os.getcwd()'
789
        self.throws(cmd, NAMEERROR, quote(good))
790
        self.runs(good)
791
        cmd, good = 'cd', 'os.chdir(path)'
792
        self.throws(cmd, NAMEERROR, quote(good))
793
        self.runs(good.replace('path', 'os.getcwd()'))
794
        cmd = 'rm'
795
        sugg = "'os.remove(filename)', 'shutil.rmtree(dir)' for recursive"
796
        self.throws(cmd, NAMEERROR, sugg)
797
798
    def test_unmatched_msg(self):
799
        """Test that arbitrary strings are supported."""
800
        code = 'raise NameError("unmatched NAMEERROR")'
801
        self.throws(code, UNKNOWN_NAMEERROR)
802
803
804
class UnboundLocalErrorTests(GetSuggestionsTests):
805
    """Class for tests related to UnboundLocalError."""
806
807
    def test_unbound_typo(self):
808
        """Should be foo."""
809
        code = 'def func():\n\tfoo = 1\n\t{0} +=1\nfunc()'
810
        typo, good = "foob", "foo"
811
        sugg = "'{0}' (local)".format(good)
812
        bad_code, good_code = format_str(code, typo, good)
813
        self.throws(bad_code, UNBOUNDLOCAL, sugg)
814
        self.runs(good_code)
815
816
    def test_unbound_global(self):
817
        """Should be global nb."""
818
        # NICE_TO_HAVE
819
        code = 'nb = 0\ndef func():\n\t{0}\n\tnb +=1\nfunc()'
820
        sugg = 'global nb'
821
        bad_code, good_code = format_str(code, "", sugg)
822
        sys.setrecursionlimit(1000)  # needed for weird PyPy versions
823
        self.throws(bad_code, UNBOUNDLOCAL)
824
        self.runs(good_code)  # this is to be run afterward :-/
825
        sys.setrecursionlimit(initial_recursion_limit)
826
827
    def test_unbound_nonlocal(self):
828
        """Shoud be nonlocal nb."""
829
        # NICE_TO_HAVE
830
        code = 'def foo():\n\tnb = 0\n\tdef bar():' \
831
               '\n\t\t{0}\n\t\tnb +=1\n\tbar()\nfoo()'
832
        sugg = 'nonlocal nb'
833
        bad_code, good_code = format_str(code, "", sugg)
834
        self.throws(bad_code, UNBOUNDLOCAL)
835
        before, after = before_and_after((3, 0))
836
        self.throws(good_code, INVALIDSYNTAX, [], before)
837
        self.runs(good_code, after)
838
839
    def test_unbound_nonlocal_and_global(self):
840
        """Shoud be nonlocal nb or global."""
841
        # NICE_TO_HAVE
842
        code = 'nb = 1\ndef foo():\n\tnb = 0\n\tdef bar():' \
843
               '\n\t\t{0}\n\t\tnb +=1\n\tbar()\nfoo()'
844
        sugg1, sugg2 = 'nonlocal nb', 'global nb'
845
        bad_code, good_code1, good_code2 = format_str(code, "", sugg1, sugg2)
846
        self.throws(bad_code, UNBOUNDLOCAL)
847
        self.runs(good_code2)
848
        before, after = before_and_after((3, 0))
849
        self.throws(good_code1, INVALIDSYNTAX, [], before)
850
        self.runs(good_code1, after)
851
852
    def test_unmatched_msg(self):
853
        """Test that arbitrary strings are supported."""
854
        code = 'raise UnboundLocalError("unmatched UNBOUNDLOCAL")'
855
        self.throws(code, UNKNOWN_UNBOUNDLOCAL)
856
857
858
class AttributeErrorTests(GetSuggestionsTests):
859
    """Class for tests related to AttributeError."""
860
861
    def test_nonetype(self):
862
        """In-place methods like sort returns None.
863
864
        Might also happen if the functions misses a return.
865
        """
866
        # NICE_TO_HAVE
867
        code = '[].sort().append(4)'
868
        self.throws(code, ATTRIBUTEERROR)
869
870
    def test_method(self):
871
        """Should be 'append'."""
872
        code = '[0].{0}(1)'
873
        typo, good = 'appendh', 'append'
874
        sugg = quote(good)
875
        bad_code, good_code = format_str(code, typo, good)
876
        self.throws(bad_code, ATTRIBUTEERROR, sugg)
877
        self.runs(good_code)
878
879
    def test_builtin(self):
880
        """Should be 'max(lst)'."""
881
        bad_code, good_code = '[0].max()', 'max([0])'
882
        self.throws(bad_code, ATTRIBUTEERROR, "'max(list)'")
883
        self.runs(good_code)
884
885
    def test_builtin2(self):
886
        """Should be 'next(gen)'."""
887
        code = 'my_generator().next()'
888
        new_code = 'next(my_generator())'
889
        sugg = "'next(generator)'"
890
        before, after = before_and_after((3, 0))
891
        self.runs(code, before)
892
        self.throws(code, ATTRIBUTEERROR, sugg, after)
893
        self.runs(new_code)
894
895
    def test_wrongmethod(self):
896
        """Should be 'lst.append(1)'."""
897
        code = '[0].{0}(1)'
898
        typo, typo2, good = 'add', 'push', 'append'
899
        sugg = quote(good)
900
        bad_code, bad_code2, good_code = format_str(code, typo, typo2, good)
901
        self.throws(bad_code, ATTRIBUTEERROR, sugg)
902
        self.throws(bad_code2, ATTRIBUTEERROR, sugg)
903
        self.runs(good_code)
904
905
    def test_wrongmethod2(self):
906
        """Should be 'lst.extend([4, 5, 6])'."""
907
        code = '[0].{0}([4, 5, 6])'
908
        typo, good = 'update', 'extend'
909
        sugg = quote(good)
910
        bad_code, good_code = format_str(code, typo, good)
911
        self.throws(bad_code, ATTRIBUTEERROR, sugg)
912
        self.runs(good_code)
913
914
    def test_wrongmethod3(self):
915
        """Should be 's.remove(42)' or 's.discard(42)'."""
916
        code = 's = set([42, 43])\n{0}'
917
        typo, good1, good2 = 'del s[42]', 's.remove(42)', 's.discard(42)'
918
        bad_code, good_code1, good_code2 = format_str(code, typo, good1, good2)
919
        suggs = ["'discard'", "'remove'", 'convert to list to edit the list']
920
        self.throws(bad_code, OBJECTDOESNOTSUPPORT, suggs)
921
        self.runs(good_code1)
922
        self.runs(good_code2)
923
924
    def test_wrongmethod4(self):
925
        """Should be 'del d[42]'."""
926
        code = 'd = dict()\nd[42] = False\n{0}'
927
        good, typo1, typo2 = 'del d[42]', 'd.remove(42)', 'd.discard(42)'
928
        good_code, bad_code1, bad_code2 = format_str(code, good, typo1, typo2)
929
        self.runs(good_code)
930
        sugg = "'__delitem__'"
931
        self.throws(bad_code1, ATTRIBUTEERROR, sugg)
932
        self.throws(bad_code2, ATTRIBUTEERROR, sugg)
933
934
    def test_wrongmethod5(self):
935
        """Should be 's.add()'."""
936
        code = 'set().{0}(1)'
937
        typo, typo2, good = 'append', 'push', 'add'
938
        sugg = quote(good)
939
        bad_code, bad_code2, good_code = format_str(code, typo, typo2, good)
940
        self.throws(bad_code, ATTRIBUTEERROR, sugg)
941
        self.throws(bad_code2, ATTRIBUTEERROR, sugg)
942
        self.runs(good_code)
943
944
    def test_hidden(self):
945
        """Accessing wrong string object."""
946
        # NICE_TO_HAVE
947
        code = 'import string\nstring = "a"\nascii = string.ascii_letters'
948
        self.throws(code, ATTRIBUTEERROR)
949
950
    def test_no_sugg(self):
951
        """No suggestion."""
952
        self.throws('[1, 2, 3].ldkjhfnvdlkjhvgfdhgf', ATTRIBUTEERROR)
953
954
    def test_from_module(self):
955
        """Should be math.pi."""
956
        code = 'import math\nmath.{0}'
957
        typo, good = 'pie', 'pi'
958
        sugg = quote(good)
959
        before, after = before_and_after((3, 5))
960
        bad_code, good_code = format_str(code, typo, good)
961
        self.throws(bad_code, ATTRIBUTEERROR, sugg, before)
962
        self.throws(bad_code, MODATTRIBUTEERROR, sugg, after)
963
        self.runs(good_code)
964
965
    def test_from_module2(self):
966
        """Should be math.pi."""
967
        code = 'import math\nm = math\nm.{0}'
968
        typo, good = 'pie', 'pi'
969
        sugg = quote(good)
970
        before, after = before_and_after((3, 5))
971
        bad_code, good_code = format_str(code, typo, good)
972
        self.throws(bad_code, ATTRIBUTEERROR, sugg, before)
973
        self.throws(bad_code, MODATTRIBUTEERROR, sugg, after)
974
        self.runs(good_code)
975
976
    def test_from_class(self):
977
        """Should be 'this_is_cls_mthd'."""
978
        code = 'FoobarClass().{0}()'
979
        typo, good = 'this_is_cls_mth', 'this_is_cls_mthd'
980
        bad_code, good_code = format_str(code, typo, good)
981
        sugg = quote(good)
982
        self.throws(bad_code, ATTRIBUTEERROR, sugg)
983
        self.runs(good_code)
984
985
    def test_from_class2(self):
986
        """Should be 'this_is_cls_mthd'."""
987
        code = 'FoobarClass.{0}()'
988
        typo, good = 'this_is_cls_mth', 'this_is_cls_mthd'
989
        bad_code, good_code = format_str(code, typo, good)
990
        sugg = quote(good)
991
        self.throws(bad_code, ATTRIBUTEERROR, sugg)
992
        self.runs(good_code)
993
994
    def test_private_attr(self):
995
        """Test that 'private' members are suggested with a warning message.
996
997
        Sometimes 'private' members are suggested but it's not ideal, a
998
        warning must be added to the suggestion.
999
        """
1000
        code = 'FoobarClass().{0}'
1001
        method = '__some_private_method'
1002
        method2 = '_some_semi_private_method'
1003
        typo, priv, good = method, '_FoobarClass' + method, method2
1004
        suggs = ["'{0}' (but it is supposed to be private)".format(priv),
1005
                 "'{0}'".format(good)]
1006
        bad_code, priv_code, good_code = format_str(code, typo, priv, good)
1007
        self.throws(bad_code, ATTRIBUTEERROR, suggs)
1008
        self.runs(priv_code)
1009
        self.runs(good_code)
1010
1011
    def test_get_on_nondict_cont(self):
1012
        """Method get does not exist on all containers."""
1013
        code = '{0}().get(0, None)'
1014
        dictcode, tuplecode, listcode, setcode = \
1015
            format_str(code, 'dict', 'tuple', 'list', 'set')
1016
        self.runs(dictcode)
1017
        self.throws(setcode, ATTRIBUTEERROR)
1018
        for bad_code in tuplecode, listcode:
1019
            self.throws(bad_code, ATTRIBUTEERROR,
1020
                        "'obj[key]' with a len() check or "
1021
                        "try: except: KeyError or IndexError")
1022
1023
    def test_removed_has_key(self):
1024
        """Method has_key is removed from dict."""
1025
        code = 'dict().has_key(1)'
1026
        new_code = '1 in dict()'
1027
        sugg = "'key in dict' (has_key is removed)"
1028
        before, after = before_and_after((3, 0))
1029
        self.runs(code, before)
1030
        self.throws(code, ATTRIBUTEERROR, sugg, after)
1031
        self.runs(new_code)
1032
1033
    def test_removed_dict_methods(self):
1034
        """Different methos (iterXXX) have been removed from dict."""
1035
        before, after = before_and_after((3, 0))
1036
        code = 'dict().{0}()'
1037
        for method, sugg in {
1038
            'iterkeys': [],
1039
            'itervalues': ["'values'"],
1040
            'iteritems': ["'items'"],
1041
        }.items():
1042
            meth_code, = format_str(code, method)
1043
            self.runs(meth_code, before)
1044
            self.throws(meth_code, ATTRIBUTEERROR, sugg, after)
1045
1046
    def test_remove_exc_attr(self):
1047
        """Attribute sys.exc_xxx have been removed."""
1048
        before, mid, after = before_mid_and_after((3, 0), (3, 5))
1049
        for att_name, sugg in {
1050
            'exc_type': [EXC_ATTR_REMOVED_MSG],
1051
            'exc_value': [EXC_ATTR_REMOVED_MSG],
1052
            'exc_traceback': ["'last_traceback'", EXC_ATTR_REMOVED_MSG],
1053
        }.items():
1054
            code = 'import sys\nsys.' + att_name
1055
            if att_name == 'exc_type':
1056
                self.runs(code, before)  # others may be undef
1057
            self.runs(code, mid, 'pypy')
1058
            self.throws(code, ATTRIBUTEERROR, sugg, mid, 'cpython')
1059
            self.throws(code, MODATTRIBUTEERROR, sugg, after)
1060
        self.runs('import sys\nsys.exc_info()')
1061
1062
    def test_removed_xreadlines(self):
1063
        """Method xreadlines is removed."""
1064
        # NICE_TO_HAVE
1065
        code = "import os\nwith open(os.path.realpath(__file__)) as f:" \
1066
            "\n\tf.{0}"
1067
        old, good1, good2 = 'xreadlines', 'readline', 'readlines'
1068
        suggs = [quote(good1), quote(good2), "'writelines'"]
1069
        old_code, new_code1, new_code2 = format_str(code, old, good1, good2)
1070
        before, after = before_and_after((3, 0))
1071
        self.runs(old_code, before)
1072
        self.throws(old_code, ATTRIBUTEERROR, suggs, after)
1073
        self.runs(new_code1)
1074
        self.runs(new_code2)
1075
1076
    def test_removed_function_attributes(self):
1077
        """Some functions attributes are removed."""
1078
        # NICE_TO_HAVE
1079
        before, after = before_and_after((3, 0))
1080
        code = func_gen() + 'some_func.{0}'
1081
        attributes = [('func_name', '__name__', []),
1082
                      ('func_doc', '__doc__', []),
1083
                      ('func_defaults', '__defaults__', ["'__defaults__'"]),
1084
                      ('func_dict', '__dict__', []),
1085
                      ('func_closure', '__closure__', []),
1086
                      ('func_globals', '__globals__', []),
1087
                      ('func_code', '__code__', [])]
1088
        for (old_att, new_att, sugg) in attributes:
1089
            old_code, new_code = format_str(code, old_att, new_att)
1090
            self.runs(old_code, before)
1091
            self.throws(old_code, ATTRIBUTEERROR, sugg, after)
1092
            self.runs(new_code)
1093
1094
    def test_removed_method_attributes(self):
1095
        """Some methods attributes are removed."""
1096
        # NICE_TO_HAVE
1097
        before, after = before_and_after((3, 0))
1098
        code = 'FoobarClass().some_method.{0}'
1099
        attributes = [('im_func', '__func__', []),
1100
                      ('im_self', '__self__', []),
1101
                      ('im_class', '__self__.__class__', ["'__class__'"])]
1102
        for (old_att, new_att, sugg) in attributes:
1103
            old_code, new_code = format_str(code, old_att, new_att)
1104
            self.runs(old_code, before)
1105
            self.throws(old_code, ATTRIBUTEERROR, sugg, after)
1106
            self.runs(new_code)
1107
1108
    def test_moved_between_str_string(self):
1109
        """Some methods have been moved from string to str."""
1110
        # NICE_TO_HAVE
1111
        version1 = (3, 0)
1112
        version2 = (3, 5)
1113
        code = 'import string\n{0}.maketrans'
1114
        code_str, code_string = format_str(code, 'str', 'string')
1115
        code_str2 = 'str.maketrans'  # No 'string' import
1116
        code_str3 = 'import string as my_string\nstr.maketrans'  # Named import
1117
        self.throws(code_str, ATTRIBUTEERROR, [], up_to_version(version1))
1118
        self.throws(code_str2, ATTRIBUTEERROR, [], up_to_version(version1))
1119
        self.throws(code_str3, ATTRIBUTEERROR, [], up_to_version(version1))
1120
        self.runs(code_string, up_to_version(version1))
1121
        self.throws(code_string, ATTRIBUTEERROR, [], (version1, version2))
1122
        self.throws(code_string, MODATTRIBUTEERROR, [], from_version(version2))
1123
        self.runs(code_str, from_version(version1))
1124
        self.runs(code_str2, from_version(version1))
1125
        self.runs(code_str3, from_version(version1))
1126
1127
    def test_moved_between_imp_importlib(self):
1128
        """Some methods have been moved from imp to importlib."""
1129
        # NICE_TO_HAVE
1130
        # reload removed from Python 3
1131
        # importlib module new in Python 2.7
1132
        # importlib.reload new in Python 3.4
1133
        # imp.reload new in Python 3.2
1134
        version27 = (2, 7)
1135
        version3 = (3, 0)
1136
        version26 = up_to_version(version27)
1137
        code = '{0}reload(math)'
1138
        null, code_imp, code_importlib = format_str(
1139
            code, '', 'import imp\nimp.', 'import importlib\nimportlib.')
1140
        self.runs(null, up_to_version(version3))
1141
        self.throws(null, NAMEERROR,
1142
                    RELOAD_REMOVED_MSG, from_version(version3))
1143
        self.runs(code_imp)
1144
        self.throws(code_importlib, NOMODULE, [], version26)
1145
        self.throws(code_importlib, ATTRIBUTEERROR,
1146
                    "'reload(module)'", (version27, version3))
1147
        self.throws(code_importlib, ATTRIBUTEERROR,
1148
                    [], (version3, (3, 4)))
1149
        self.runs(code_importlib, from_version((3, 4)))
1150
1151
    def test_join(self):
1152
        """Test what happens when join is used incorrectly.
1153
1154
        This can be frustrating to call join on an iterable instead of a
1155
        string.
1156
        """
1157
        code = "['a', 'b'].join('-')"
1158
        self.throws(code, ATTRIBUTEERROR, "'my_string.join(list)'")
1159
1160
    def test_set_dict_comprehension(self):
1161
        """{} creates a dict and not an empty set leading to errors."""
1162
        # NICE_TO_HAVE
1163
        before, after = before_and_after((2, 7))
1164
        suggs = {
1165
            'discard': "'__delitem__'",
1166
            'remove': "'__delitem__'",
1167
        }
1168
        for method in set(dir(set)) - set(dir(dict)):
1169
            if not method.startswith('__'):  # boring suggestions
1170
                code = "a = {0}\na." + method
1171
                typo, dict1, dict2, good, set1 = format_str(
1172
                    code, "{}", "dict()", "{0: 0}", "set()", "{0}")
1173
                sugg = suggs.get(method, None)
1174
                self.throws(typo, ATTRIBUTEERROR, sugg)
1175
                self.throws(dict1, ATTRIBUTEERROR, sugg)
1176
                self.throws(dict2, ATTRIBUTEERROR, sugg)
1177
                self.runs(good)
1178
                self.throws(set1, INVALIDSYNTAX, [], before)
1179
                self.runs(set1, after)
1180
1181
    def test_unmatched_msg(self):
1182
        """Test that arbitrary strings are supported."""
1183
        code = 'raise AttributeError("unmatched ATTRIBUTEERROR")'
1184
        self.throws(code, UNKNOWN_ATTRIBUTEERROR)
1185
1186
    # TODO: Add sugg for situation where self/cls is the missing parameter
1187
1188
1189
class TypeErrorTests(GetSuggestionsTests):
1190
    """Class for tests related to TypeError."""
1191
1192
    def test_unhashable(self):
1193
        """Test for UNHASHABLE exception."""
1194
        # NICE_TO_HAVE : suggest hashable equivalent
1195
        self.throws('s = set([list()])', UNHASHABLE)
1196
        self.throws('s = set([dict()])', UNHASHABLE)
1197
        self.throws('s = set([set()])', UNHASHABLE)
1198
        self.runs('s = set([tuple()])')
1199
        self.runs('s = set([frozenset()])')
1200
1201
    def test_not_sub(self):
1202
        """Should be function call, not [] operator."""
1203
        # https://twitter.com/raymondh/status/772957699478663169
1204
        typo, good = '[2]', '(2)'
1205
        code = func_gen(param='a') + 'some_func{0}'
1206
        bad_code, good_code = format_str(code, typo, good)
1207
        suggestion = "'function(value)'"
1208
        suggs = ["'__get__'", "'__getattribute__'", suggestion]
1209
        # Only Python 2.7 with cpython has a different error message
1210
        # (leading to more suggestions based on fuzzy matches)
1211
        before, mid, after = before_mid_and_after((2, 7), (3, 0))
1212
        self.throws(bad_code, UNSUBSCRIPTABLE, suggestion, interpreters='pypy')
1213
        self.throws(bad_code, UNSUBSCRIPTABLE, suggestion, before, 'cpython')
1214
        self.throws(bad_code, NOATTRIBUTE_TYPEERROR, suggs, mid, 'cpython')
1215
        self.throws(bad_code, UNSUBSCRIPTABLE, suggestion, after, 'cpython')
1216
        self.runs(good_code)
1217
1218
    def test_method_called_on_class(self):
1219
        """Test where a method is called on a class and not an instance.
1220
1221
        Forgetting parenthesis makes the difference between using an
1222
        instance and using a type.
1223
        """
1224
        # NICE_TO_HAVE
1225
        wrong_type = (DESCREXPECT, MUSTCALLWITHINST, NBARGERROR)
1226
        not_iterable = (ARGNOTITERABLE, ARGNOTITERABLE, ARGNOTITERABLE)
1227
        before, after = before_and_after((3, 0))
1228
        for code, (err_cy, err_pyp, err_pyp3) in [
1229
                ('set{0}.add(0)', wrong_type),
1230
                ('list{0}.append(0)', wrong_type),
1231
                ('0 in list{0}', not_iterable)]:
1232
            bad_code, good_code = format_str(code, '', '()')
1233
            self.runs(good_code)
1234
            self.throws(bad_code, err_cy, interpreters='cpython')
1235
            self.throws(bad_code, err_pyp, [], before, 'pypy')
1236
            self.throws(bad_code, err_pyp3, [], after, 'pypy')
1237
1238
    def test_set_operations(self):
1239
        """+, +=, etc doesn't work on sets. A suggestion would be nice."""
1240
        # NICE_TO_HAVE
1241
        typo1 = 'set() + set()'
1242
        typo2 = 's = set()\ns += set()'
1243
        code1 = 'set() | set()'
1244
        code2 = 'set().union(set())'
1245
        code3 = 'set().update(set())'
1246
        self.throws(typo1, UNSUPPORTEDOPERAND)
1247
        self.throws(typo2, UNSUPPORTEDOPERAND)
1248
        self.runs(code1)
1249
        self.runs(code2)
1250
        self.runs(code3)
1251
1252
    def test_dict_operations(self):
1253
        """+, +=, etc doesn't work on dicts. A suggestion would be nice."""
1254
        # NICE_TO_HAVE
1255
        typo1 = 'dict() + dict()'
1256
        typo2 = 'd = dict()\nd += dict()'
1257
        typo3 = 'dict() & dict()'
1258
        self.throws(typo1, UNSUPPORTEDOPERAND)
1259
        self.throws(typo2, UNSUPPORTEDOPERAND)
1260
        self.throws(typo3, UNSUPPORTEDOPERAND)
1261
        code1 = 'dict().update(dict())'
1262
        self.runs(code1)
1263
1264
    def test_unsupported_operand_caret(self):
1265
        """Use '**' for power, not '^'."""
1266
        code = '3.5 {0} 2'
1267
        bad_code, good_code = format_str(code, '^', '**')
1268
        self.runs(good_code)
1269
        self.throws(bad_code, UNSUPPORTEDOPERAND, "'val1 ** val2'")
1270
1271
    def test_unary_operand_custom(self):
1272
        """Test unary operand errors on custom types."""
1273
        before, after = before_and_after((3, 0))
1274
        ops = {
1275
            '+{0}': ('__pos__', "'__doc__'"),
1276
            '-{0}': ('__neg__', None),
1277
            '~{0}': ('__invert__', None),
1278
            'abs({0})': ('__abs__', None),
1279
        }
1280
        obj = 'CustomClass()'
1281
        sugg = 'implement "{0}" on CustomClass'
1282
        for op, suggestions in ops.items():
1283
            code = op.format(obj)
1284
            magic, sugg_attr = suggestions
1285
            sugg_unary = sugg.format(magic)
1286
            self.throws(code, ATTRIBUTEERROR, sugg_attr, before)
1287
            self.throws(code, BADOPERANDUNARY, sugg_unary, after)
1288
1289
    def test_unary_operand_builtin(self):
1290
        """Test unary operand errors on builtin types."""
1291
        ops = [
1292
            '+{0}',
1293
            '-{0}',
1294
            '~{0}',
1295
            'abs({0})',
1296
        ]
1297
        obj = 'set()'
1298
        for op in ops:
1299
            code = op.format(obj)
1300
            self.throws(code, BADOPERANDUNARY)
1301
1302
    def test_len_on_iterable(self):
1303
        """len() can't be called on iterable (weird but understandable)."""
1304
        code = 'len(my_generator())'
1305
        sugg = 'len(list(my_generator()))'
1306
        self.throws(code, OBJECTHASNOFUNC, "'len(list(generator))'")
1307
        self.runs(sugg)
1308
1309
    def test_len_on_custom(self):
1310
        """len() can't be called on custom."""
1311
        before, after = before_and_after((3, 0))
1312
        code = 'o = {0}()\nlen(o)'
1313
        bad, good = format_str(code, 'CustomClass', 'LenClass')
1314
        sugg = 'implement "__len__" on CustomClass'
1315
        self.throws(bad, ATTRIBUTEERROR, ["'__module__'"], before)
1316
        self.throws(bad, OBJECTHASNOFUNC, sugg, after)
1317
        self.runs(good)
1318
1319
    def test_nb_args(self):
1320
        """Should have 1 arg."""
1321
        typo, good = '1, 2', '1'
1322
        code = func_gen(param='a', args='{0}')
1323
        bad_code, good_code = format_str(code, typo, good)
1324
        self.throws(bad_code, NBARGERROR)
1325
        self.runs(good_code)
1326
1327
    def test_nb_args1(self):
1328
        """Should have 0 args."""
1329
        typo, good = '1', ''
1330
        code = func_gen(param='', args='{0}')
1331
        bad_code, good_code = format_str(code, typo, good)
1332
        self.throws(bad_code, NBARGERROR)
1333
        self.runs(good_code)
1334
1335
    def test_nb_args2(self):
1336
        """Should have 1 arg."""
1337
        typo, good = '', '1'
1338
        before, after = before_and_after((3, 3))
1339
        code = func_gen(param='a', args='{0}')
1340
        bad_code, good_code = format_str(code, typo, good)
1341
        self.throws(bad_code, NBARGERROR, [], before)
1342
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1343
        self.runs(good_code)
1344
1345
    def test_nb_args3(self):
1346
        """Should have 3 args."""
1347
        typo, good = '1', '1, 2, 3'
1348
        before, after = before_and_after((3, 3))
1349
        code = func_gen(param='so, much, args', args='{0}')
1350
        bad_code, good_code = format_str(code, typo, good)
1351
        self.throws(bad_code, NBARGERROR, [], before)
1352
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1353
        self.runs(good_code)
1354
1355
    def test_nb_args4(self):
1356
        """Should have 3 args."""
1357
        typo, good = '', '1, 2, 3'
1358
        before, after = before_and_after((3, 3))
1359
        code = func_gen(param='so, much, args', args='{0}')
1360
        bad_code, good_code = format_str(code, typo, good)
1361
        self.throws(bad_code, NBARGERROR, [], before)
1362
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1363
        self.runs(good_code)
1364
1365
    def test_nb_args5(self):
1366
        """Should have 3 args."""
1367
        typo, good = '1, 2', '1, 2, 3'
1368
        before, after = before_and_after((3, 3))
1369
        code = func_gen(param='so, much, args', args='{0}')
1370
        bad_code, good_code = format_str(code, typo, good)
1371
        self.throws(bad_code, NBARGERROR, [], before)
1372
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1373
        self.runs(good_code)
1374
1375
    def test_nb_args6(self):
1376
        """Should provide more args."""
1377
        # Amusing message: 'func() takes exactly 2 arguments (2 given)'
1378
        before, after = before_and_after((3, 3))
1379
        code = func_gen(param='a, b, c=3', args='{0}')
1380
        bad_code, good_code1, good_code2 = format_str(
1381
            code,
1382
            'b=2, c=3',
1383
            'a=1, b=2, c=3',
1384
            '1, b=2, c=3')
1385
        self.throws(bad_code, NBARGERROR, [], before)
1386
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1387
        self.runs(good_code1)
1388
        self.runs(good_code2)
1389
1390
    def test_nb_arg7(self):
1391
        """More tests."""
1392
        code = 'dict().get(1, 2, 3)'
1393
        self.throws(code, NBARGERROR)
1394
1395
    def test_nb_arg8(self):
1396
        """More tests."""
1397
        code = 'dict().get()'
1398
        self.throws(code, NBARGERROR)
1399
1400
    def test_nb_arg_missing_self(self):
1401
        """Arg 'self' is missing."""
1402
        # NICE_TO_HAVE
1403
        obj = 'FoobarClass()'
1404
        self.throws(obj + '.some_method_missing_self_arg()', NBARGERROR)
1405
        self.throws(obj + '.some_method_missing_self_arg2(42)', NBARGERROR)
1406
        self.runs(obj + '.some_method()')
1407
        self.runs(obj + '.some_method2(42)')
1408
1409
    def test_nb_arg_missing_cls(self):
1410
        """Arg 'cls' is missing."""
1411
        # NICE_TO_HAVE
1412
        for obj in ('FoobarClass()', 'FoobarClass'):
1413
            self.throws(obj + '.some_cls_method_missing_cls()', NBARGERROR)
1414
            self.throws(obj + '.some_cls_method_missing_cls2(42)', NBARGERROR)
1415
            self.runs(obj + '.this_is_cls_mthd()')
1416
1417
    def test_keyword_args(self):
1418
        """Should be param 'babar' not 'a' but it's hard to guess."""
1419
        typo, good = 'a', 'babar'
1420
        code = func_gen(param=good, args='{0}=1')
1421
        bad_code, good_code = format_str(code, typo, good)
1422
        self.throws(bad_code, UNEXPECTEDKWARG)
1423
        self.runs(good_code)
1424
1425
    def test_keyword_args2(self):
1426
        """Should be param 'abcdef' not 'abcdf'."""
1427
        typo, good = 'abcdf', 'abcdef'
1428
        code = func_gen(param=good, args='{0}=1')
1429
        bad_code, good_code = format_str(code, typo, good)
1430
        sugg = quote(good)
1431
        self.throws(bad_code, UNEXPECTEDKWARG, sugg)
1432
        self.runs(good_code)
1433
1434
    def test_keyword_arg_method(self):
1435
        """Should be the same as previous test but on a method."""
1436
        code = 'class MyClass:\n\tdef func(self, a):' \
1437
               '\n\t\tpass\nMyClass().func({0}=1)'
1438
        bad_code, good_code = format_str(code, 'babar', 'a')
1439
        self.throws(bad_code, UNEXPECTEDKWARG)
1440
        self.runs(good_code)
1441
1442
    def test_keyword_arg_method2(self):
1443
        """Should be the same as previous test but on a method."""
1444
        typo, good = 'abcdf', 'abcdef'
1445
        code = 'class MyClass:\n\tdef func(self, ' + good + '):' \
1446
               '\n\t\tpass\nMyClass().func({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_class_method(self):
1453
        """Should be the same as previous test but on a class method."""
1454
        code = 'class MyClass:\n\t@classmethod\n\tdef func(cls, 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_class_method2(self):
1461
        """Should be the same as previous test but on a class method."""
1462
        typo, good = 'abcdf', 'abcdef'
1463
        code = 'class MyClass:\n\t@classmethod ' \
1464
               '\n\tdef func(cls, ' + good + '):\n ' \
1465
               '\t\tpass\nMyClass.func({0}=1)'
1466
        bad_code, good_code = format_str(code, typo, good)
1467
        sugg = quote(good)
1468
        self.throws(bad_code, UNEXPECTEDKWARG, sugg)
1469
        self.runs(good_code)
1470
1471
    def test_keyword_arg_multiples_instances(self):
1472
        """If multiple functions are found, suggestions should be unique."""
1473
        typo, good = 'abcdf', 'abcdef'
1474
        code = 'class MyClass:\n\tdef func(self, ' + good + '):' \
1475
               '\n\t\tpass\na = MyClass()\nb = MyClass()\na.func({0}=1)'
1476
        bad_code, good_code = format_str(code, typo, good)
1477
        sugg = quote(good)
1478
        self.throws(bad_code, UNEXPECTEDKWARG, sugg)
1479
        self.runs(good_code)
1480
1481
    def test_keyword_arg_lambda(self):
1482
        """Test with lambda functions instead of usual function."""
1483
        typo, good = 'abcdf', 'abcdef'
1484
        sugg = quote(good)
1485
        code = 'f = lambda arg1, ' + good + ': None\nf(42, {0}=None)'
1486
        bad_code, good_code = format_str(code, typo, good)
1487
        self.throws(bad_code, UNEXPECTEDKWARG, sugg)
1488
        self.runs(good_code)
1489
1490
    def test_keyword_arg_lambda_method(self):
1491
        """Test with lambda methods instead of usual methods."""
1492
        typo, good = 'abcdf', 'abcdef'
1493
        sugg = quote(good)
1494
        code = 'class MyClass:\n\tfunc = lambda self, ' + good + ': None' \
1495
               '\nMyClass().func({0}=1)'
1496
        bad_code, good_code = format_str(code, typo, good)
1497
        self.throws(bad_code, UNEXPECTEDKWARG, sugg)
1498
        self.runs(good_code)
1499
1500
    def test_keyword_arg_other_objects_with_name(self):
1501
        """Mix of previous tests but with more objects defined.
1502
1503
        Non-function object with same same as the function tested are defined
1504 View Code Duplication
        to ensure that things do work fine.
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1505
        """
1506
        code = 'func = "not_a_func"\nclass MyClass:\n\tdef func(self, a):' \
1507
               '\n\t\tpass\nMyClass().func({0}=1)'
1508
        bad_code, good_code = format_str(code, 'babar', 'a')
1509
        self.throws(bad_code, UNEXPECTEDKWARG)
1510
        self.runs(good_code)
1511
1512
    def test_keyword_builtin(self):
1513
        """A few builtins (like int()) have a different error message."""
1514
        # NICE_TO_HAVE
1515
        # 'max', 'input', 'len', 'abs', 'all', etc have a specific error
1516
        # message and are not relevant here
1517
        before, after = before_and_after((3, 7))
1518
        for builtin, kwarg in [
1519
                ('float', False), ('bool', False),
1520
                ('int', True), ('complex', True)]:
1521
            code = builtin + '(this_doesnt_exist=2)'
1522
            old_exc = UNEXPECTEDKWARG2
1523
            new_exc = UNEXPECTEDKWARG4 if kwarg else NOKWARGS
1524
            sugg = [] if kwarg else NO_KEYWORD_ARG_MSG
1525
            self.throws(code, old_exc, [], before, interpreters='cpython')
1526
            self.throws(code, new_exc, sugg, after, interpreters='cpython')
1527
            self.throws(code, UNEXPECTEDKWARG, interpreters='pypy')
1528
1529
    def test_keyword_builtin_print(self):
1530
        """Builtin "print" has a different error message."""
1531
        # It would be NICE_TO_HAVE suggestions on keyword arguments
1532
        before, mid, after = before_mid_and_after((3, 0), (3, 7))
1533
        code = "c = 'string'\nb = print(c, end_='toto')"
1534
        self.throws(code, INVALIDSYNTAX, [], before)
1535
        self.throws(code, UNEXPECTEDKWARG2, [], mid, 'cpython')
1536
        self.throws(code, UNEXPECTEDKWARG4, [], after, 'cpython')
1537
        self.throws(code, UNEXPECTEDKWARG3, [], mid, 'pypy')
1538
        self.throws(code, UNEXPECTEDKWARG3, [], after, 'pypy')
1539
1540
    def test_keyword_sort_cmpkey(self):
1541
        """Sort and sorted functions have a cmp/key param dep. on the vers."""
1542
        before, mid, after = before_mid_and_after((3, 0), (3, 7))
1543
        code = "import functools as f\nl = [1, 8, 3]\n" \
1544
               "def comp(a, b): return (a > b) - (a < b)\nl.sort({0})"
1545
        sugg = CMP_ARG_REMOVED_MSG
1546
        cmp_arg, key_arg, cmp_to_key = format_str(
1547
                code, 'cmp=comp', 'key=id', 'key=f.cmp_to_key(comp)')
1548
        self.runs(cmp_arg, before)
1549
        self.throws(cmp_arg, UNEXPECTEDKWARG2, sugg, mid, 'cpython')
1550
        self.throws(cmp_arg, UNEXPECTEDKWARG4, sugg, after, 'cpython')
1551
        self.throws(cmp_arg, UNEXPECTEDKWARG, sugg, mid, 'pypy')
1552
        self.throws(cmp_arg, UNEXPECTEDKWARG, sugg, after, 'pypy')
1553
        self.runs(key_arg)
1554
        self.runs(cmp_to_key, from_version((2, 7)))
1555
1556
    def test_c_func_takes_no_keyword_arguments(self):
1557
        """TODO."""
1558
        # http://stackoverflow.com/questions/24463202/typeerror-get-takes-no-keyword-arguments
1559
        # https://www.python.org/dev/peps/pep-0457/
1560
        # https://www.python.org/dev/peps/pep-0436/#functions-with-positional-only-parameters
1561
        sugg = NO_KEYWORD_ARG_MSG
1562
        code = 'dict().get(0, {0}None)'
1563
        good_code, bad_code = format_str(code, '', 'default=')
1564
        self.runs(good_code)
1565
        self.throws(bad_code, NOKWARGS, sugg, interpreters='cpython')
1566
        self.runs(bad_code, interpreters='pypy')
1567
        # It would be better to have the suggestion only when the function
1568
        # doesn't accept keyword arguments but does accept positional
1569
        # arguments but we cannot use introspection on builtin function.
1570
        code2 = 'globals({0})'
1571
        good_code, bad_code1, bad_code2 = format_str(code2, '', '2', 'foo=2')
1572
        self.runs(good_code)
1573
        self.throws(bad_code1, NBARGERROR)
1574 View Code Duplication
        self.throws(bad_code2, NBARGERROR, interpreters='pypy')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1575
        self.throws(bad_code2, NOKWARGS, sugg, interpreters='cpython')
1576
        # The explanation is only relevant for C functions
1577
        code3 = 'def func_no_arg(n):\n\tpass\nfunc_no_arg({0}2)'
1578
        good_code, good_code2, bad_code = format_str(code3, '', 'n=', 'foo=')
1579
        self.runs(good_code)
1580
        self.runs(good_code2)
1581
        self.throws(bad_code, UNEXPECTEDKWARG)
1582
1583
    def test_iter_cannot_be_interpreted_as_int(self):
1584
        """Trying to call `range(len(iterable))` (bad) and forget the len."""
1585
        before, after = before_and_after((3, 0))
1586
        bad_code = 'range([0, 1, 2])'
1587
        good_code = 'range(len([0, 1, 2]))'
1588
        sugg = "'len(list)'"
1589
        self.runs(good_code)
1590
        self.throws(bad_code, INTEXPECTED, sugg, before)
1591
        self.throws(bad_code, CANNOTBEINTERPRETED, sugg, after)
1592
1593
    RANGE_CODE_TEMPLATES = [
1594
        'range({0})',
1595
        'range({0}, 14)',
1596
        'range(0, 24, {0})'
1597
    ]
1598
    INDEX_CODE_TEMPLATES = ['[1, 2, 3][{0}]', '(1, 2, 3)[{0}]']
1599
1600
    def test_str_cannot_be_interpreted_as_int(self):
1601
        """Forget to convert str to int."""
1602
        before, after = before_and_after((3, 0))
1603
        suggs = ["'int(str)'", "'len(str)'"]
1604
        for code in self.RANGE_CODE_TEMPLATES:
1605
            bad_code, good_code = format_str(code, '"12"', 'int("12")')
1606
            self.runs(good_code)
1607
            self.throws(bad_code, INTEXPECTED, suggs, before)
1608
            self.throws(bad_code, CANNOTBEINTERPRETED, suggs, after)
1609
1610
    def test_float_cannot_be_interpreted_as_int(self):
1611
        """Use float instead of int."""
1612
        before, mid, after = before_mid_and_after((2, 7), (3, 0))
1613
        sugg = ["'int(float)'"]
1614
        suggs = ["'int(float)'", "'math.ceil(float)'", "'math.floor(float)'"]
1615
        for code in self.RANGE_CODE_TEMPLATES:
1616
            full_code = 'import math\n' + code
1617
            good1, good2, bad = format_str(
1618
                full_code, 'int(12.0)', 'math.floor(12.0)', '12.0')
1619
            self.runs(good1)
1620
            self.runs(good2, before)
1621
            # floor returns a float before Python 3 -_-
1622
            self.throws(good2, INTEXPECTED, sugg, mid)
1623
            self.runs(good2, after)
1624
            self.runs(bad, before)
1625
            self.throws(bad, INTEXPECTED, sugg, mid)
1626
            self.throws(bad, CANNOTBEINTERPRETED, suggs, after)
1627
1628
    def test_customclass_cannot_be_interpreter_as_int(self):
1629
        """Forget to implement the __index__ method."""
1630
        # http://stackoverflow.com/questions/17342899/object-cannot-be-interpreted-as-an-integer
1631
        # https://twitter.com/raymondh/status/773224135409360896
1632
        before, after = before_and_after((3, 0))
1633
        sugg = 'implement "__index__" on CustomClass'
1634
        for code in self.RANGE_CODE_TEMPLATES:
1635
            bad, good = format_str(code, 'CustomClass()', 'IndexClass()')
1636
            self.throws(bad, ATTRIBUTEERROR, [], before)
1637
            self.throws(bad, CANNOTBEINTERPRETED, sugg, after)
1638
            self.runs(good, after)  # Fails on old python ?
1639
1640
    def test_indices_cant_be_str(self):
1641
        """Use str as index."""
1642
        suggs = ["'int(str)'", "'len(str)'"]
1643
        for code in self.INDEX_CODE_TEMPLATES:
1644
            bad, good = format_str(code, '"2"', 'int("2")')
1645
            self.runs(good)
1646
            self.throws(bad, INDICESMUSTBEINT, suggs)
1647
1648
    def test_indices_cant_be_float(self):
1649
        """Use float as index."""
1650
        before, after = before_and_after((3, 0))
1651
        sugg = ["'int(float)'"]
1652
        suggs = ["'int(float)'", "'math.ceil(float)'", "'math.floor(float)'"]
1653
        for code in self.INDEX_CODE_TEMPLATES:
1654
            good1, good2, bad = format_str(
1655
                    code, 'int(2.0)', 'math.floor(2.0)', '2.0')
1656
            self.runs(good1)
1657
            # floor returns a float before Python 3 -_-
1658
            self.throws(good2, INDICESMUSTBEINT, sugg, before)
1659
            self.runs(good2, after)
1660
            self.throws(bad, INDICESMUSTBEINT, sugg, before)
1661
            self.throws(bad, INDICESMUSTBEINT, suggs, after)
1662
1663
    def test_indices_cant_be_custom(self):
1664
        """Use custom as index."""
1665
        before, after = before_and_after((3, 0))
1666
        sugg = 'implement "__index__" on CustomClass'
1667
        # On Pypy, detected type is 'instance' so attribute detection is much
1668
        # less precise, leading to additional suggestions
1669
        suggs = ["'len(instance)'", 'implement "__index__" on instance']
1670
        for code in self.INDEX_CODE_TEMPLATES:
1671
            bad, good = format_str(code, 'CustomClass()', 'IndexClass()')
1672
            self.throws(bad, INDICESMUSTBEINT, suggs, before, 'pypy')
1673
            self.throws(bad, CANNOTBEINTERPRETEDINDEX, [], before, 'cpython')
1674
            self.throws(bad, INDICESMUSTBEINT, sugg, after)
1675
            self.runs(good)
1676
1677
    def test_no_implicit_str_conv(self):
1678
        """Trying to concatenate a non-string value to a string."""
1679
        # NICE_TO_HAVE
1680
        code = '{0} + " things"'
1681
        typo, good = '12', 'str(12)'
1682
        bad_code, good_code = format_str(code, typo, good)
1683
        self.throws(bad_code, UNSUPPORTEDOPERAND)
1684
        self.runs(good_code)
1685
1686
    def test_cannot_concatenate_iter_to_list(self):
1687
        """Trying to concatenate a non-list iterable to a list."""
1688
        # NICE_TO_HAVE
1689
        before, after = before_and_after((3, 0))
1690
        code = 'list() + {0}'
1691
        good, bad, sugg, bad2, bad3, bad4 = \
1692
            format_str(code, 'list()', 'set()', 'list(set())',
1693
                       'range(10)', 'dict().keys()', 'dict().iterkeys()')
1694
        self.runs(good)
1695
        self.runs(sugg)
1696
        self.throws(bad, ONLYCONCAT, interpreters='cpython')
1697
        self.throws(bad, UNSUPPORTEDOPERAND, interpreters='pypy')
1698
        # Other examples are more interesting but depend on the version used:
1699
        #  - range returns a list or a range object
1700
        self.runs(bad2, before)
1701
        self.throws(bad2, ONLYCONCAT, [], after, 'cpython')
1702
        self.throws(bad2, UNSUPPORTEDOPERAND, [], after, 'pypy')
1703
        #  - keys return a list or a view object
1704
        self.runs(bad3, before)
1705
        self.throws(bad3, ONLYCONCAT, [], after, 'cpython')
1706
        self.throws(bad3, UNSUPPORTEDOPERAND, [], after, 'pypy')
1707
        #  - iterkeys returns an iterator or doesn't exist
1708
        self.throws(bad4, ONLYCONCAT, [], before, 'cpython')
1709
        self.throws(bad4, UNSUPPORTEDOPERAND, [], before, 'pypy')
1710
        self.throws(bad4, ATTRIBUTEERROR, [], after)
1711
1712
    def test_no_implicit_str_conv2(self):
1713
        """Trying to concatenate a non-string value to a string."""
1714
        # NICE_TO_HAVE
1715
        code = '"things " + {0}'
1716
        typo, good = '12', 'str(12)'
1717
        bad_code, good_code = format_str(code, typo, good)
1718
        before, range1, range2, after = ranges_between((3, 0), (3, 6), (3, 7))
1719
        self.throws(bad_code, CANNOTCONCAT, [], before, 'cpython')
1720
        self.throws(bad_code, CANTCONVERT, [], range1, 'cpython')
1721
        self.throws(bad_code, MUSTBETYPENOTTYPE, [], range2, 'cpython')
1722
        self.throws(bad_code, ONLYCONCAT, [], after, 'cpython')
1723
        self.throws(bad_code, UNSUPPORTEDOPERAND, interpreters='pypy')
1724
        self.runs(good_code)
1725
1726
    def test_assignment_to_range(self):
1727
        """Trying to assign to range works on list, not on range."""
1728
        code = '{0}[2] = 1'
1729
        typo, good = 'range(4)', 'list(range(4))'
1730
        sugg = 'convert to list to edit the list'
1731
        before, after = before_and_after((3, 0))
1732
        bad_code, good_code = format_str(code, typo, good)
1733
        self.runs(good_code)
1734
        self.runs(bad_code, before)
1735
        self.throws(bad_code, OBJECTDOESNOTSUPPORT, sugg, after)
1736
1737
    def test_assignment_to_string(self):
1738
        """Trying to assign to string does not work."""
1739
        code = "s = 'abc'\ns[1] = 'd'"
1740
        good_code = "s = 'abc'\nl = list(s)\nl[1] = 'd'\ns = ''.join(l)"
1741
        sugg = 'convert to list to edit the list and use "join()" on the list'
1742
        self.runs(good_code)
1743
        self.throws(code, OBJECTDOESNOTSUPPORT, sugg)
1744
1745
    def test_assignment_to_custom(self):
1746
        """Trying to assign to custom obj."""
1747
        before, after = before_and_after((3, 0))
1748
        code = "o = {0}()\no[1] = 'd'"
1749
        bad, good = format_str(code, 'CustomClass', 'SetItemClass')
1750
        sugg = 'implement "__setitem__" on CustomClass'
1751
        sys.setrecursionlimit(1000)  # needed for weird PyPy versions
1752
        self.throws(bad, ATTRIBUTEERROR, [], before)
1753
        self.throws(bad, OBJECTDOESNOTSUPPORT, sugg, after)
1754
        self.runs(good)
1755
        sys.setrecursionlimit(initial_recursion_limit)
1756
1757
    def test_deletion_from_string(self):
1758
        """Delete from string does not work."""
1759
        code = "s = 'abc'\ndel s[1]"
1760
        good_code = "s = 'abc'\nl = list(s)\ndel l[1]\ns = ''.join(l)"
1761
        sugg = 'convert to list to edit the list and use "join()" on the list'
1762
        self.runs(good_code)
1763
        self.throws(code, OBJECTDOESNOTSUPPORT, sugg)
1764
1765
    def test_deletion_from_custom(self):
1766
        """Delete from custom obj does not work."""
1767
        before, after = before_and_after((3, 0))
1768
        code = "o = {0}()\ndel o[1]"
1769
        bad, good = format_str(code, 'CustomClass', 'DelItemClass')
1770
        sugg = 'implement "__delitem__" on CustomClass'
1771
        self.throws(bad, ATTRIBUTEERROR, [], before)
1772
        self.throws(bad, OBJECTDOESNOTSUPPORT, sugg, after)
1773
        self.runs(good)
1774
1775
    def test_object_indexing(self):
1776
        """Index from object does not work if __getitem__ is not defined."""
1777
        before, after = before_and_after((3, 0))
1778
        code = "{0}[0]"
1779
        good_code, set_code, custom_bad, custom_good = \
1780
            format_str(code, '"a_string"', "set()",
1781
                       "CustomClass()", "GetItemClass()")
1782
        self.runs(good_code)
1783
        sugg_for_iterable = 'convert to list first or use the iterator ' \
1784
            'protocol to get the different elements'
1785
        sugg_imp = 'implement "__getitem__" on CustomClass'
1786
        self.throws(set_code,
1787
                    OBJECTDOESNOTSUPPORT,
1788
                    sugg_for_iterable, interpreters='cpython')
1789
        self.throws(set_code,
1790
                    UNSUBSCRIPTABLE,
1791
                    sugg_for_iterable, interpreters='pypy')
1792
        self.throws(custom_bad, ATTRIBUTEERROR, [], before, 'pypy')
1793
        self.throws(custom_bad, UNSUBSCRIPTABLE, sugg_imp, after, 'pypy')
1794
        self.throws(custom_bad, ATTRIBUTEERROR, [], before, 'cpython')
1795
        self.throws(custom_bad,
1796
                    OBJECTDOESNOTSUPPORT,
1797
                    sugg_imp,
1798
                    after, 'cpython')
1799
        self.runs(custom_good)
1800
1801
    def test_not_callable(self):
1802
        """Sometimes, one uses parenthesis instead of brackets."""
1803
        typo, getitem = '(0)', '[0]'
1804
        for ex, sugg in {
1805
            '[0]': "'list[value]'",
1806
            '{0: 0}': "'dict[value]'",
1807
            '"a"': "'str[value]'",
1808
        }.items():
1809
            self.throws(ex + typo, NOTCALLABLE, sugg)
1810
            self.runs(ex + getitem)
1811
        for ex in ['1', 'set()']:
1812
            self.throws(ex + typo, NOTCALLABLE)
1813
1814
    def test_not_callable_custom(self):
1815
        """One must define __call__ to call custom objects."""
1816
        before, after = before_and_after((3, 0))
1817
        code = 'o = {0}()\no()'
1818
        bad, good = format_str(code, 'CustomClass', 'CallClass')
1819
        sugg = 'implement "__call__" on CustomClass'
1820
        self.throws(bad, INSTHASNOMETH, [], before, 'cpython')
1821
        self.throws(bad, ATTRIBUTEERROR, [], before, 'pypy')
1822
        self.throws(bad, NOTCALLABLE, sugg, after)
1823
        self.runs(good)
1824
1825
    def test_exc_must_derive_from(self):
1826
        """Test when a non-exc object is raised."""
1827
        code = 'raise "ExceptionString"'
1828
        self.throws(code, EXCMUSTDERIVE)
1829
1830
    def test_unordered_builtin(self):
1831
        """Test for UNORDERABLE exception on builtin types."""
1832
        before, mid, after = before_mid_and_after((3, 0), (3, 6))
1833
        for op in ['>', '>=', '<', '<=']:
1834
            code = "'10' {0} 2".format(op)
1835
            self.runs(code, before)
1836
            self.throws(code, UNORDERABLE, [], mid)
1837
            self.throws(code, OPNOTSUPPBETWEENINST, [], after)
1838
1839
    def test_unordered_custom(self):
1840
        """Test for UNORDERABLE exception on custom types."""
1841
        before, mid, after = before_mid_and_after((3, 0), (3, 6))
1842
        for op in ['>', '>=', '<', '<=']:
1843
            code = "CustomClass() {0} CustomClass()".format(op)
1844
            self.runs(code, before)
1845
            self.throws(code, UNORDERABLE, [], mid)
1846
            self.throws(code, OPNOTSUPPBETWEENINST, [], after)
1847
1848
    def test_unordered_custom2(self):
1849
        """Test for UNORDERABLE exception on custom types."""
1850
        before, mid, after = before_mid_and_after((3, 0), (3, 6))
1851
        for op in ['>', '>=', '<', '<=']:
1852
            code = "CustomClass() {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_unmatched_msg(self):
1858
        """Test that arbitrary strings are supported."""
1859
        code = 'raise TypeError("unmatched TYPEERROR")'
1860
        self.throws(code, UNKNOWN_TYPEERROR)
1861
1862
1863
class ImportErrorTests(GetSuggestionsTests):
1864
    """Class for tests related to ImportError."""
1865
1866
    def test_no_module_no_sugg(self):
1867
        """No suggestion."""
1868
        self.throws('import fqslkdfjslkqdjfqsd', NOMODULE)
1869
1870
    def test_no_module(self):
1871
        """Should be 'math'."""
1872
        code = 'import {0}'
1873
        typo, good = 'maths', 'math'
1874
        self.assertTrue(good in STAND_MODULES)
1875
        bad_code, good_code = format_str(code, typo, good)
1876
        sugg = quote(good)
1877
        self.throws(bad_code, NOMODULE, sugg)
1878
        self.runs(good_code)
1879
1880
    def test_no_module2(self):
1881
        """Should be 'math'."""
1882
        code = 'from {0} import pi'
1883
        typo, good = 'maths', 'math'
1884
        self.assertTrue(good in STAND_MODULES)
1885
        bad_code, good_code = format_str(code, typo, good)
1886
        sugg = quote(good)
1887
        self.throws(bad_code, NOMODULE, sugg)
1888
        self.runs(good_code)
1889
1890
    def test_no_module3(self):
1891
        """Should be 'math'."""
1892
        code = 'import {0} as my_imported_math'
1893
        typo, good = 'maths', 'math'
1894
        self.assertTrue(good in STAND_MODULES)
1895
        bad_code, good_code = format_str(code, typo, good)
1896
        sugg = quote(good)
1897
        self.throws(bad_code, NOMODULE, sugg)
1898
        self.runs(good_code)
1899
1900
    def test_no_module4(self):
1901
        """Should be 'math'."""
1902
        code = 'from {0} import pi as three_something'
1903
        typo, good = 'maths', 'math'
1904
        self.assertTrue(good in STAND_MODULES)
1905
        bad_code, good_code = format_str(code, typo, good)
1906
        sugg = quote(good)
1907
        self.throws(bad_code, NOMODULE, sugg)
1908
        self.runs(good_code)
1909
1910
    def test_no_module5(self):
1911
        """Should be 'math'."""
1912
        code = '__import__("{0}")'
1913
        typo, good = 'maths', 'math'
1914
        self.assertTrue(good in STAND_MODULES)
1915
        bad_code, good_code = format_str(code, typo, good)
1916
        sugg = quote(good)
1917
        self.throws(bad_code, NOMODULE, sugg)
1918
        self.runs(good_code)
1919
1920
    def test_import_future_nomodule(self):
1921
        """Should be '__future__'."""
1922
        code = 'import {0}'
1923
        typo, good = '__future_', '__future__'
1924
        self.assertTrue(good in STAND_MODULES)
1925
        bad_code, good_code = format_str(code, typo, good)
1926
        sugg = quote(good)
1927
        self.throws(bad_code, NOMODULE, sugg)
1928
        self.runs(good_code)
1929
1930
    def test_no_name_no_sugg(self):
1931
        """No suggestion."""
1932
        self.throws('from math import fsfsdfdjlkf', CANNOTIMPORT)
1933
1934
    def test_wrong_import(self):
1935
        """Should be 'math'."""
1936
        code = 'from {0} import pi'
1937
        typo, good = 'itertools', 'math'
1938
        self.assertTrue(good in STAND_MODULES)
1939
        bad_code, good_code = format_str(code, typo, good)
1940
        sugg = "'{0}'".format(good_code)
1941
        self.throws(bad_code, CANNOTIMPORT, sugg)
1942
        self.runs(good_code)
1943
1944
    def test_typo_in_method(self):
1945
        """Should be 'pi'."""
1946
        code = 'from math import {0}'
1947
        typo, good = 'pie', 'pi'
1948
        sugg = quote(good)
1949
        bad_code, good_code = format_str(code, typo, good)
1950
        self.throws(bad_code, CANNOTIMPORT, sugg)
1951
        self.runs(good_code)
1952
1953
    def test_typo_in_method2(self):
1954
        """Should be 'pi'."""
1955
        code = 'from math import e, {0}, log'
1956
        typo, good = 'pie', 'pi'
1957
        sugg = quote(good)
1958
        bad_code, good_code = format_str(code, typo, good)
1959
        self.throws(bad_code, CANNOTIMPORT, sugg)
1960
        self.runs(good_code)
1961
1962
    def test_typo_in_method3(self):
1963
        """Should be 'pi'."""
1964
        code = 'from math import {0} as three_something'
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_unmatched_msg(self):
1972
        """Test that arbitrary strings are supported."""
1973
        code = 'raise ImportError("unmatched IMPORTERROR")'
1974
        self.throws(code, UNKNOWN_IMPORTERROR)
1975
1976
    def test_module_removed(self):
1977
        """Sometimes, modules are deleted/moved/renamed."""
1978
        # NICE_TO_HAVE
1979
        # result for 2.6 seems to vary
1980
        _, mid, after = before_mid_and_after((2, 7), (3, 0))
1981
        code = 'import {0}'
1982
        lower, upper = format_str(code, 'tkinter', 'Tkinter')
1983
        self.throws(lower, NOMODULE, [], mid)
1984
        self.throws(upper, NOMODULE, [], after)
1985
1986
1987
class LookupErrorTests(GetSuggestionsTests):
1988
    """Class for tests related to LookupError."""
1989
1990
1991
class KeyErrorTests(LookupErrorTests):
1992
    """Class for tests related to KeyError."""
1993
1994
    def test_no_sugg(self):
1995
        """No suggestion."""
1996
        self.throws('dict()["ffdsqmjklfqsd"]', KEYERROR)
1997
1998
    def test_set_remove(self):
1999
        """Set.remove throws when key is not found."""
2000
        # NICE_TO_HAVE
2001
        code = 's = set()\ns.{0}(42)'
2002
        bad_code, good_code = format_str(code, "remove", "discard")
2003
        self.runs(good_code)
2004
        self.throws(bad_code, KEYERROR)
2005
2006
    def test_dict_pop(self):
2007
        """Test dict."""
2008
        code = 'd = dict()\nd.pop(42)'
2009
        self.throws(code, KEYERROR)
2010
2011
2012
class IndexErrorTests(LookupErrorTests):
2013
    """Class for tests related to IndexError."""
2014
2015
    def test_no_sugg(self):
2016
        """No suggestion."""
2017
        self.throws('list()[2]', OUTOFRANGE)
2018
2019
2020
class SyntaxErrorTests(GetSuggestionsTests):
2021
    """Class for tests related to SyntaxError."""
2022
2023
    def test_no_error(self):
2024
        """No error."""
2025
        self.runs("1 + 2 == 2")
2026
2027
    def test_yield_return_out_of_func(self):
2028
        """yield/return needs to be in functions."""
2029
        sugg = "to indent it"
2030
        self.throws("yield 1", OUTSIDEFUNC, sugg)
2031
        self.throws("return 1", OUTSIDEFUNC, ["'sys.exit([arg])'", sugg])
2032
2033
    def test_print(self):
2034
        """print is a function now and needs parenthesis."""
2035
        # NICE_TO_HAVE
2036
        code, new_code = 'print ""', 'print("")'
2037
        before, after = before_and_after((3, 0))
2038
        self.runs(code, before)
2039
        self.throws(code, INVALIDSYNTAX, [], after)
2040
        self.runs(new_code)
2041
2042
    def test_exec(self):
2043
        """exec is a function now and needs parenthesis."""
2044
        # NICE_TO_HAVE
2045
        code, new_code = 'exec "1"', 'exec("1")'
2046
        before, after = before_and_after((3, 0))
2047
        self.runs(code, before)
2048
        self.throws(code, INVALIDSYNTAX, [], after)
2049
        self.runs(new_code)
2050
2051
    def test_old_comparison(self):
2052
        """<> comparison is removed, != always works."""
2053
        code = '1 {0} 2'
2054
        old, new = '<>', '!='
2055
        sugg = "'{0}'".format(new)
2056
        before, after = before_and_after((3, 0))
2057
        old_code, new_code = format_str(code, old, new)
2058
        self.runs(old_code, before)
2059
        self.throws(old_code, INVALIDCOMP, sugg, after, 'pypy')
2060
        self.throws(old_code, INVALIDSYNTAX, sugg, after, 'cpython')
2061
        self.runs(new_code)
2062
2063
    def test_backticks(self):
2064
        """String with backticks is removed in Python3, use 'repr' instead."""
2065
        # NICE_TO_HAVE
2066
        before, after = before_and_after((3, 0))
2067
        expr = "2+3"
2068
        backtick_str, repr_str = "`%s`" % expr, "repr(%s)" % expr
2069
        self.runs(backtick_str, before)
2070
        self.throws(backtick_str, INVALIDSYNTAX, [], after)
2071
        self.runs(repr_str)
2072
2073
    def test_missing_colon(self):
2074
        """Missing colon is a classic mistake."""
2075
        # NICE_TO_HAVE
2076
        code = "if True{0}\n\tpass"
2077
        bad_code, good_code = format_str(code, "", ":")
2078
        self.throws(bad_code, INVALIDSYNTAX)
2079
        self.runs(good_code)
2080
2081
    def test_missing_colon2(self):
2082
        """Missing colon is a classic mistake."""
2083
        # NICE_TO_HAVE
2084
        code = "class MyClass{0}\n\tpass"
2085
        bad_code, good_code = format_str(code, "", ":")
2086
        self.throws(bad_code, INVALIDSYNTAX)
2087
        self.runs(good_code)
2088
2089
    def test_simple_equal(self):
2090
        """'=' for comparison is a classic mistake."""
2091
        # NICE_TO_HAVE
2092
        code = "if 2 {0} 3:\n\tpass"
2093
        bad_code, good_code = format_str(code, "=", "==")
2094
        self.throws(bad_code, INVALIDSYNTAX)
2095
        self.runs(good_code)
2096
2097
    def test_keyword_as_identifier(self):
2098
        """Using a keyword as a variable name."""
2099
        # NICE_TO_HAVE
2100
        code = '{0} = 1'
2101
        bad_code, good_code = format_str(code, "from", "from_")
2102
        self.throws(bad_code, INVALIDSYNTAX)
2103
        self.runs(good_code)
2104
2105
    def test_increment(self):
2106
        """Trying to use '++' or '--'."""
2107
        # NICE_TO_HAVE
2108
        code = 'a = 0\na{0}'
2109
        # Adding pointless suffix to avoid wrong assumptions
2110
        for end in ('', '  ', ';', ' ;'):
2111
            code2 = code + end
2112
            for op in ('-', '+'):
2113
                typo, good = 2 * op, op + '=1'
2114
                bad_code, good_code = format_str(code2, typo, good)
2115
                self.throws(bad_code, INVALIDSYNTAX)
2116
                self.runs(good_code)
2117
2118
    def test_wrong_bool_operator(self):
2119
        """Trying to use '&&' or '||'."""
2120
        code = 'True {0} False'
2121
        for typo, good in (('&&', 'and'), ('||', 'or')):
2122
            bad_code, good_code = format_str(code, typo, good)
2123
            sugg = quote(good)
2124
            self.throws(bad_code, INVALIDSYNTAX, sugg)
2125
            self.runs(good_code)
2126
2127
    def test_import_future_not_first(self):
2128
        """Test what happens when import from __future__ is not first."""
2129
        code = 'a = 8/7\nfrom __future__ import division'
2130
        self.throws(code, FUTUREFIRST)
2131
2132
    def test_import_future_not_def(self):
2133
        """Should be 'division'."""
2134
        code = 'from __future__ import {0}'
2135
        typo, good = 'divisio', 'division'
2136
        bad_code, good_code = format_str(code, typo, good)
2137
        sugg = quote(good)
2138
        self.throws(bad_code, FUTFEATNOTDEF, sugg)
2139
        self.runs(good_code)
2140
2141
    def test_unqualified_exec(self):
2142
        """Exec in nested functions."""
2143
        # NICE_TO_HAVE
2144
        before, after = before_and_after((3, 0))
2145
        codes = [
2146
            "def func1():\n\tbar='1'\n\tdef func2():"
2147
            "\n\t\texec(bar)\n\tfunc2()\nfunc1()",
2148
            "def func1():\n\texec('1')\n\tdef func2():"
2149
            "\n\t\tTrue",
2150
        ]
2151
        sys.setrecursionlimit(1000)  # needed for weird PyPy versions
2152
        for code in codes:
2153
            self.throws(code, UNQUALIFIED_EXEC, [], before)
2154
            self.runs(code, after)
2155
        sys.setrecursionlimit(initial_recursion_limit)
2156
2157
    def test_import_star(self):
2158
        """'import *' in nested functions."""
2159
        # NICE_TO_HAVE
2160
        codes = [
2161
            "def func1():\n\tbar='1'\n\tdef func2():"
2162
            "\n\t\tfrom math import *\n\t\tTrue\n\tfunc2()\nfunc1()",
2163
            "def func1():\n\tfrom math import *"
2164
            "\n\tdef func2():\n\t\tTrue",
2165
        ]
2166
        sys.setrecursionlimit(1000)  # needed for weird PyPy versions
2167
        with warnings.catch_warnings():
2168
            warnings.simplefilter("ignore", category=SyntaxWarning)
2169
            for code in codes:
2170
                self.throws(code, IMPORTSTAR)
2171
        sys.setrecursionlimit(initial_recursion_limit)
2172
2173
    def test_unpack(self):
2174
        """Extended tuple unpacking does not work prior to Python 3."""
2175
        # NICE_TO_HAVE
2176
        before, after = before_and_after((3, 0))
2177
        code = 'a, *b = (1, 2, 3)'
2178
        self.throws(code, INVALIDSYNTAX, [], before)
2179
        self.runs(code, after)
2180
2181
    def test_unpack2(self):
2182
        """Unpacking in function arguments was supported up to Python 3."""
2183
        # NICE_TO_HAVE
2184
        before, after = before_and_after((3, 0))
2185
        code = 'def addpoints((x1, y1), (x2, y2)):\n\tpass'
2186
        self.runs(code, before)
2187
        self.throws(code, INVALIDSYNTAX, [], after)
2188
2189
    def test_nonlocal(self):
2190
        """nonlocal keyword is added in Python 3."""
2191
        # NICE_TO_HAVE
2192
        before, after = before_and_after((3, 0))
2193
        code = 'def func():\n\tfoo = 1\n\tdef nested():\n\t\tnonlocal foo'
2194
        self.throws(code, INVALIDSYNTAX, [], before)
2195
        self.runs(code, after)
2196
2197
    def test_nonlocal2(self):
2198
        """nonlocal must be used only when binding exists."""
2199
        # NICE_TO_HAVE
2200
        before, after = before_and_after((3, 0))
2201
        code = 'def func():\n\tdef nested():\n\t\tnonlocal foo'
2202
        self.throws(code, INVALIDSYNTAX, [], before)
2203
        self.throws(code, NOBINDING, [], after)
2204
2205
    def test_nonlocal3(self):
2206
        """nonlocal must be used only when binding to non-global exists."""
2207
        # just a way to say that this_is_a_global_list is needed in globals
2208
        name = 'this_is_a_global_list'
2209
        this_is_a_global_list
2210
        self.assertFalse(name in locals())
2211
        self.assertTrue(name in globals())
2212
        before, after = before_and_after((3, 0))
2213
        code = 'def func():\n\tdef nested():\n\t\t{0} ' + name
2214
        typo, good = 'nonlocal', 'global'
2215
        sugg = "'{0} {1}'".format(good, name)
2216
        bad_code, good_code = format_str(code, typo, good)
2217
        self.runs(good_code)
2218
        self.throws(bad_code, INVALIDSYNTAX, [], before)
2219
        self.throws(bad_code, NOBINDING, sugg, after)
2220
2221
    def test_nonlocal4(self):
2222
        """suggest close matches to variable name."""
2223
        # NICE_TO_HAVE (needs access to variable in enclosing scope)
2224
        before, after = before_and_after((3, 0))
2225
        code = 'def func():\n\tfoo = 1\n\tdef nested():\n\t\tnonlocal {0}'
2226
        typo, good = 'foob', 'foo'
2227
        bad_code, good_code = format_str(code, typo, good)
2228
        self.throws(good_code, INVALIDSYNTAX, [], before)
2229
        self.runs(good_code, after)
2230
        self.throws(bad_code, INVALIDSYNTAX, [], before)
2231
        self.throws(bad_code, NOBINDING, [], after)
2232
2233
    def test_nonlocal_at_module_level(self):
2234
        """nonlocal must be used in function."""
2235
        before, mid, after = before_mid_and_after((2, 7), (3, 0))
2236
        code = 'nonlocal foo'
2237
        self.throws(code, UNEXPECTED_OEF, [], before)
2238
        self.throws(code, INVALIDSYNTAX, [], mid)
2239
        self.throws(code, NONLOCALMODULE, [], after)
2240 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2241
    def test_octal_literal(self):
2242
        """Syntax for octal liberals has changed."""
2243
        # NICE_TO_HAVE
2244
        before, after = before_and_after((3, 0))
2245
        bad, good = '0720', '0o720'
2246
        self.runs(good)
2247
        self.runs(bad, before)
2248
        self.throws(bad, INVALIDTOKEN, [], after, 'cpython')
2249
        self.throws(bad, INVALIDSYNTAX, [], after, 'pypy')
2250
2251
    def test_extended_unpacking(self):
2252
        """Extended iterable unpacking is added with Python 3."""
2253
        before, after = before_and_after((3, 0))
2254
        code = '(a, *rest, b) = range(5)'
2255
        self.throws(code, INVALIDSYNTAX, [], before)
2256
        self.runs(code, after)
2257
2258
    def test_ellipsis(self):
2259
        """Triple dot (...) aka Ellipsis can be used anywhere in Python 3."""
2260
        before, after = before_and_after((3, 0))
2261
        code = '...'
2262
        self.throws(code, INVALIDSYNTAX, [], before)
2263
        self.runs(code, after)
2264
2265
    def test_fstring(self):
2266
        """Fstring (see PEP 498) appeared in Python 3.6."""
2267
        # NICE_TO_HAVE
2268
        before, after = before_and_after((3, 6))
2269
        code = 'f"toto"'
2270
        self.throws(code, INVALIDSYNTAX, [], before)
2271
        self.runs(code, after)
2272
2273
2274
class MemoryErrorTests(GetSuggestionsTests):
2275
    """Class for tests related to MemoryError."""
2276
2277
    def test_out_of_memory(self):
2278
        """Test what happens in case of MemoryError."""
2279
        code = '[0] * 999999999999999'
2280
        self.throws(code, MEMORYERROR)
2281
2282
    def test_out_of_memory_range(self):
2283
        """Test what happens in case of MemoryError."""
2284
        code = '{0}(999999999999999)'
2285
        typo, good = 'range', 'xrange'
2286
        sugg = quote(good)
2287
        bad_code, good_code = format_str(code, typo, good)
2288
        before, mid, after = before_mid_and_after((2, 7), (3, 0))
2289
        self.runs(bad_code, interpreters='pypy')
2290
        self.throws(bad_code, OVERFLOWERR, sugg, before, 'cpython')
2291
        self.throws(bad_code, MEMORYERROR, sugg, mid, 'cpython')
2292
        self.runs(bad_code, after, 'cpython')
2293
        self.runs(good_code, before, 'cpython')
2294
        self.runs(good_code, mid, 'cpython')
2295
2296
2297
class ValueErrorTests(GetSuggestionsTests):
2298
    """Class for tests related to ValueError."""
2299
2300
    def test_too_many_values(self):
2301
        """Unpack 4 values in 3 variables."""
2302
        code = 'a, b, c = [1, 2, 3, 4]'
2303
        before, after = before_and_after((3, 0))
2304
        self.throws(code, EXPECTEDLENGTH, [], before, 'pypy')
2305
        self.throws(code, TOOMANYVALUES, [], after, 'pypy')
2306
        self.throws(code, TOOMANYVALUES, interpreters='cpython')
2307
2308
    def test_not_enough_values(self):
2309
        """Unpack 2 values in 3 variables."""
2310
        code = 'a, b, c = [1, 2]'
2311
        before, after = before_and_after((3, 0))
2312
        self.throws(code, EXPECTEDLENGTH, [], before, 'pypy')
2313
        self.throws(code, NEEDMOREVALUES, [], after, 'pypy')
2314
        self.throws(code, NEEDMOREVALUES, interpreters='cpython')
2315
2316
    def test_conversion_fails(self):
2317
        """Conversion fails."""
2318
        self.throws('int("toto")', INVALIDLITERAL)
2319
2320
    def test_math_domain(self):
2321
        """Math function used out of its domain."""
2322
        code = 'import math\nlg = math.log(-1)'
2323
        self.throws(code, MATHDOMAIN)
2324
2325
    def test_zero_len_field_in_format(self):
2326
        """Format {} is not valid before Python 2.7."""
2327
        code = '"{0}".format(0)'
2328
        old, new = '{0}', '{}'
2329
        old_code, new_code = format_str(code, old, new)
2330
        before, after = before_and_after((2, 7))
2331
        self.runs(old_code)
2332
        self.throws(new_code, ZEROLENERROR, '{0}', before)
2333
        self.runs(new_code, after)
2334
2335
    def test_timedata_does_not_match(self):
2336
        """Strptime arguments are in wrong order."""
2337
        # https://twitter.com/brandon_rhodes/status/781234730091941888
2338
        code = 'import datetime\ndatetime.datetime.strptime({0}, {1})'
2339
        timedata, timeformat = '"30 Nov 00"', '"%d %b %y"'
2340
        good_code = code.format(*(timedata, timeformat))
2341
        bad_code = code.format(*(timeformat, timedata))
2342
        sugg = 'to swap value and format parameters'
2343
        self.runs(good_code)
2344
        self.throws(bad_code, TIMEDATAFORMAT, sugg)
2345
2346
2347
class RuntimeErrorTests(GetSuggestionsTests):
2348
    """Class for tests related to RuntimeError."""
2349
2350
    def test_max_depth(self):
2351
        """Reach maximum recursion depth."""
2352
        sys.setrecursionlimit(200)
2353
        code = 'endlessly_recursive_func(0)'
2354
        suggs = ["increase the limit with `sys.setrecursionlimit(limit)`"
2355
                 " (current value is 200)", AVOID_REC_MSG]
2356
        self.throws(code, MAXRECURDEPTH, suggs)
2357
        sys.setrecursionlimit(initial_recursion_limit)
2358
2359
    def test_dict_size_changed_during_iter(self):
2360
        """Test size change during iteration (dict)."""
2361
        # NICE_TO_HAVE
2362
        code = 'd = dict(enumerate("notimportant"))' \
2363
            '\nfor e in d:\n\td.pop(e)'
2364
        self.throws(code, SIZECHANGEDDURINGITER)
2365
2366
    def test_set_changed_size_during_iter(self):
2367
        """Test size change during iteration (set)."""
2368
        # NICE_TO_HAVE
2369
        code = 's = set("notimportant")' \
2370
            '\nfor e in s:\n\ts.pop()'
2371
        self.throws(code, SIZECHANGEDDURINGITER)
2372
2373
    def test_dequeue_changed_during_iter(self):
2374
        """Test size change during iteration (dequeue)."""
2375
        # NICE_TO_HAVE
2376
        # "deque mutated during iteration"
2377
        pass
2378
2379
2380
class IOErrorTests(GetSuggestionsTests):
2381
    """Class for tests related to IOError."""
2382
2383
    def test_no_such_file(self):
2384
        """File does not exist."""
2385
        code = 'with open("doesnotexist") as f:\n\tpass'
2386
        self.throws(code, NOFILE_IO)
2387
2388
    def test_no_such_file2(self):
2389
        """File does not exist."""
2390
        code = 'os.listdir("doesnotexist")'
2391
        self.throws(code, NOFILE_OS)
2392
2393
    def test_no_such_file_user(self):
2394
        """Suggestion when one needs to expanduser."""
2395
        code = 'os.listdir("{0}")'
2396
        typo, good = "~", os.path.expanduser("~")
2397
        sugg = "'{0}' (calling os.path.expanduser)".format(good)
2398
        bad_code, good_code = format_str(code, typo, good)
2399
        self.throws(bad_code, NOFILE_OS, sugg)
2400
        self.runs(good_code)
2401
2402
    def test_no_such_file_vars(self):
2403
        """Suggestion when one needs to expandvars."""
2404
        code = 'os.listdir("{0}")'
2405
        key = 'HOME'
2406
        typo, good = "$" + key, os.path.expanduser("~")
2407
        original_home = os.environ.get('HOME')
2408
        os.environ[key] = good
2409
        bad_code, good_code = format_str(code, typo, good)
2410
        sugg = "'{0}' (calling os.path.expandvars)".format(good)
2411
        self.throws(bad_code, NOFILE_OS, sugg)
2412
        self.runs(good_code)
2413
        if original_home is None:
2414
            del os.environ[key]
2415
        else:
2416
            os.environ[key] = original_home
2417
2418
    def create_tmp_dir_with_files(self, filelist):
2419
        """Create a temporary directory with files in it."""
2420
        tmpdir = tempfile.mkdtemp()
2421
        absfiles = [os.path.join(tmpdir, f) for f in filelist]
2422
        for name in absfiles:
2423
            open(name, 'a').close()
2424
        return (tmpdir, absfiles)
2425
2426
    def test_is_dir_empty(self):
2427
        """Suggestion when file is an empty directory."""
2428
        # Create empty temp dir
2429
        tmpdir, _ = self.create_tmp_dir_with_files([])
2430
        code = 'with open("{0}") as f:\n\tpass'
2431
        bad_code, _ = format_str(code, tmpdir, "TODO")
2432
        sugg = "to add content to {0} first".format(tmpdir)
2433
        self.throws(bad_code, ISADIR_IO, sugg)
2434
        rmtree(tmpdir)
2435
2436
    def test_is_dir_small(self):
2437
        """Suggestion when file is directory with a few files."""
2438
        # Create temp dir with a few files
2439
        nb_files = 3
2440
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
2441
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
2442
        code = 'with open("{0}") as f:\n\tpass'
2443
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
2444
        suggs = "any of the 3 files in directory ('0.txt', '1.txt', '2.txt')"
2445
        self.throws(bad_code, ISADIR_IO, suggs)
2446
        self.runs(good_code)
2447
        rmtree(tmpdir)
2448
2449
    def test_is_dir_big(self):
2450
        """Suggestion when file is directory with many files."""
2451
        # Create temp dir with many files
2452
        tmpdir = tempfile.mkdtemp()
2453
        nb_files = 30
2454
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
2455
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
2456
        code = 'with open("{0}") as f:\n\tpass'
2457
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
2458
        suggs = "any of the 30 files in directory " \
2459
            "('0.txt', '1.txt', '10.txt', '11.txt', etc)"
2460
        self.throws(bad_code, ISADIR_IO, suggs)
2461
        self.runs(good_code)
2462
        rmtree(tmpdir)
2463
2464
    def test_is_not_dir(self):
2465
        """Suggestion when file is not a directory."""
2466
        code = 'with open("{0}") as f:\n\tpass'
2467
        code = 'os.listdir("{0}")'
2468
        typo, good = __file__, os.path.dirname(__file__)
2469
        sugg = "'{0}' (calling os.path.dirname)".format(good)
2470
        bad_code, good_code = format_str(code, typo, good)
2471
        self.throws(bad_code, NOTADIR_OS, sugg)
2472
        self.runs(good_code)
2473
2474
    def test_dir_is_not_empty(self):
2475
        """Suggestion when directory is not empty."""
2476
        # NICE_TO_HAVE
2477
        nb_files = 3
2478
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
2479
        tmpdir, _ = self.create_tmp_dir_with_files(files)
2480
        self.throws('os.rmdir("{0}")'.format(tmpdir), DIRNOTEMPTY_OS)
2481
        rmtree(tmpdir)  # this should be the suggestion
2482
2483
2484
class AnyErrorTests(GetSuggestionsTests):
2485
    """Class for tests not related to an error type in particular."""
2486
2487
    def test_wrong_except(self):
2488
        """Test where except is badly used and thus does not catch.
2489
2490
        Common mistake : "except Exc1, Exc2" doesn't catch Exc2.
2491
        Adding parenthesis solves the issue.
2492
        """
2493
        # NICE_TO_HAVE
2494
        before, after = before_and_after((3, 0))
2495
        raised_exc, other_exc = KeyError, TypeError
2496
        raised, other = raised_exc.__name__, other_exc.__name__
2497
        code = "try:\n\traise {0}()\nexcept {{0}}:\n\tpass".format(raised)
2498
        typo = "{0}, {1}".format(other, raised)
2499
        sugg = "({0})".format(typo)
2500
        bad1, bad2, good1, good2 = format_str(code, typo, other, sugg, raised)
2501
        self.throws(bad1, (raised_exc, None), [], before)
2502
        self.throws(bad1, INVALIDSYNTAX, [], after)
2503
        self.throws(bad2, (raised_exc, None))
2504
        self.runs(good1)
2505
        self.runs(good2)
2506
2507
2508
if __name__ == '__main__':
2509
    print(sys.version_info)
2510
    unittest2.main()
2511