Completed
Push — master ( 78c0ba...67ac9b )
by De
27s
created

AttributeErrorTests.test_wrongmethod4()   A

Complexity

Conditions 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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