Completed
Push — master ( db97e6...168e4b )
by De
01:19
created

GetSuggestionsTests.throws()   B

Complexity

Conditions 5

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

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