Completed
Push — master ( 168e4b...d71c47 )
by De
01:08
created

before_and_after()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
1
# -*- coding: utf-8
2
"""Unit tests for get_suggestions_for_exception."""
3
from didyoumean_internal import get_suggestions_for_exception, \
4
    STAND_MODULES, AVOID_REC_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 before_and_after(version):
184
    """Return a tuple with the version ranges before/after a given version."""
185
    return up_to_version(version), from_version(version)
186
187
188
def version_in_range(version_range):
189
    """Test if current version is in a range version."""
190
    beg, end = version_range
191
    return beg <= sys.version_info < end
192
193
194
def interpreter_in(interpreters):
195
    """Test if current interpreter is in a list of interpreters."""
196
    is_pypy = hasattr(sys, "pypy_translation_info")
197
    interpreter = 'pypy' if is_pypy else 'cython'
198
    return interpreter in interpreters
199
200
201
def format_str(template, *args):
202
    """Format multiple string by using first arg as a template."""
203
    return [template.format(arg) for arg in args]
204
205
206
class PythonEnvRange(object):
207
    """Class to describe a (range of) Python environment.
208
209
    A range of Python environments consist of:
210
     - a range of Python version (tuple)
211
     - a list of interpreters (strings).
212
    """
213
214
    def __init__(self, version_range=None, interpreters=None):
215
        """Init a PythonEnvRange.
216
217
        The parameters are:
218
         - a range of version (optional - ALL if not provided)
219
         - a list of interpreters (optional - ALL if not provided).
220
            Also, a single interpreter can be provided.
221
        """
222
        self.interpreters = listify(interpreters, INTERPRETERS, str)
223
        self.version_range = \
224
            ALL_VERSIONS if version_range is None else version_range
225
226
    def contains_current_env(self):
227
        """Check if current environment is in PythonEnvRange object."""
228
        return version_in_range(self.version_range) and \
229
            interpreter_in(self.interpreters)
230
231
232
def listify(value, default, expected_types):
233
    """Return list from value, using default value if value is None."""
234
    if value is None:
235
        value = list(default)
236
    if not isinstance(value, list):
237
        value = [value]
238
    if default:
239
        assert all(v in default for v in value)
240
    if expected_types is not None:
241
        assert all(isinstance(v, expected_types) for v in value)
242
    return value
243
244
245
def get_exception(code):
246
    """Helper function to run code and get what it throws (or None)."""
247
    try:
248
        exec(code)
249
    except:
250
        return sys.exc_info()
251
    return None
252
253
254
# NameError for NameErrorTests
255
NAMEERROR = (NameError, re.NAMENOTDEFINED_RE)
256
NAMEERRORBEFOREREF = (NameError, re.VARREFBEFOREASSIGN_RE)
257
UNKNOWN_NAMEERROR = (NameError, None)
258
# UnboundLocalError for UnboundLocalErrorTests
259
UNBOUNDLOCAL = (UnboundLocalError, re.VARREFBEFOREASSIGN_RE)
260
UNKNOWN_UNBOUNDLOCAL = (UnboundLocalError, None)
261
# TypeError for TypeErrorTests
262
NBARGERROR = (TypeError, re.NB_ARG_RE)
263
MISSINGPOSERROR = (TypeError, re.MISSING_POS_ARG_RE)
264
UNHASHABLE = (TypeError, re.UNHASHABLE_RE)
265
UNSUBSCRIPTABLE = (TypeError, re.UNSUBSCRIPTABLE_RE)
266
CANNOTBEINTERPRETED = (TypeError, re.CANNOT_BE_INTERPRETED_INT_RE)
267
INTEXPECTED = (TypeError, re.INTEGER_EXPECTED_GOT_RE)
268
INDICESMUSTBEINT = (TypeError, re.INDICES_MUST_BE_INT_RE)
269
CANNOTBEINTERPRETEDINDEX = (
270
    TypeError,
271
    r"^object cannot be interpreted as an index$")
272
NOATTRIBUTE_TYPEERROR = (TypeError, re.ATTRIBUTEERROR_RE)
273
UNEXPECTEDKWARG = (TypeError, re.UNEXPECTED_KEYWORDARG_RE)
274
UNEXPECTEDKWARG2 = (TypeError, re.UNEXPECTED_KEYWORDARG2_RE)
275
UNEXPECTEDKWARG3 = (TypeError, re.UNEXPECTED_KEYWORDARG3_RE)
276
UNSUPPORTEDOPERAND = (TypeError, re.UNSUPPORTED_OP_RE)
277
BADOPERANDUNARY = (TypeError, re.BAD_OPERAND_UNARY_RE)
278
OBJECTDOESNOTSUPPORT = (TypeError, re.OBJ_DOES_NOT_SUPPORT_RE)
279
CANNOTCONCAT = (TypeError, re.CANNOT_CONCAT_RE)
280
ONLYCONCAT = (TypeError, re.ONLY_CONCAT_RE)
281
CANTCONVERT = (TypeError, re.CANT_CONVERT_RE)
282
MUSTBETYPENOTTYPE = (TypeError, re.MUST_BE_TYPE1_NOT_TYPE2_RE)
283
NOTCALLABLE = (TypeError, re.NOT_CALLABLE_RE)
284
DESCREXPECT = (TypeError, re.DESCRIPT_REQUIRES_TYPE_RE)
285
ARGNOTITERABLE = (TypeError, re.ARG_NOT_ITERABLE_RE)
286
MUSTCALLWITHINST = (TypeError, re.MUST_BE_CALLED_WITH_INST_RE)
287
OBJECTHASNOFUNC = (TypeError, re.OBJECT_HAS_NO_FUNC_RE)
288
EXCMUSTDERIVE = (TypeError, re.EXC_MUST_DERIVE_FROM_RE)
289
UNORDERABLE = (TypeError, re.UNORDERABLE_TYPES_RE)
290
OPNOTSUPPBETWEENINST = (TypeError, re.OP_NOT_SUPP_BETWEEN_INSTANCES_RE)
291
UNKNOWN_TYPEERROR = (TypeError, None)
292
# ImportError for ImportErrorTests
293
NOMODULE = (ImportError, re.NOMODULE_RE)
294
CANNOTIMPORT = (ImportError, re.CANNOTIMPORT_RE)
295
UNKNOWN_IMPORTERROR = (ImportError, None)
296
# KeyError for KeyErrorTests
297
KEYERROR = (KeyError, None)
298
# IndexError for IndexErrorTests
299
OUTOFRANGE = (IndexError, re.INDEXOUTOFRANGE_RE)
300
# ValueError for ValueErrorTests
301
TOOMANYVALUES = (ValueError, re.TOO_MANY_VALUES_UNPACK_RE)
302
NEEDMOREVALUES = (ValueError, re.NEED_MORE_VALUES_RE)
303
EXPECTEDLENGTH = (ValueError, re.EXPECTED_LENGTH_RE)
304
MATHDOMAIN = (ValueError, re.MATH_DOMAIN_ERROR_RE)
305
ZEROLENERROR = (ValueError, re.ZERO_LEN_FIELD_RE)
306
INVALIDLITERAL = (ValueError, re.INVALID_LITERAL_RE)
307
TIMEDATAFORMAT = (ValueError, re.TIME_DATA_DOES_NOT_MATCH_FORMAT_RE)
308
# AttributeError for AttributeErrorTests
309
ATTRIBUTEERROR = (AttributeError, re.ATTRIBUTEERROR_RE)
310
MODATTRIBUTEERROR = (AttributeError, re.MODULEHASNOATTRIBUTE_RE)
311
INSTHASNOMETH = (AttributeError, re.INSTANCE_HAS_NO_METH_RE)
312
UNKNOWN_ATTRIBUTEERROR = (AttributeError, None)
313
# SyntaxError for SyntaxErrorTests
314
INVALIDSYNTAX = (SyntaxError, re.INVALID_SYNTAX_RE)
315
INVALIDTOKEN = (SyntaxError, re.INVALID_TOKEN_RE)
316
NOBINDING = (SyntaxError, re.NO_BINDING_NONLOCAL_RE)
317
NONLOCALMODULE = (SyntaxError, re.NONLOCAL_AT_MODULE_RE)
318
UNEXPECTED_OEF = (SyntaxError, re.UNEXPECTED_EOF_RE)
319
OUTSIDEFUNC = (SyntaxError, re.OUTSIDE_FUNCTION_RE)
320
MISSINGPARENT = (SyntaxError, re.MISSING_PARENT_RE)
321
INVALIDCOMP = (SyntaxError, re.INVALID_COMP_RE)
322
FUTUREFIRST = (SyntaxError, re.FUTURE_FIRST_RE)
323
FUTFEATNOTDEF = (SyntaxError, re.FUTURE_FEATURE_NOT_DEF_RE)
324
UNQUALIFIED_EXEC = (SyntaxError, re.UNQUALIFIED_EXEC_RE)
325
IMPORTSTAR = (SyntaxError, re.IMPORTSTAR_RE)
326
# MemoryError and OverflowError for MemoryErrorTests
327
MEMORYERROR = (MemoryError, '')
328
OVERFLOWERR = (OverflowError, re.RESULT_TOO_MANY_ITEMS_RE)
329
# IOError
330
NOFILE_IO = (common.NoFileIoError, re.NO_SUCH_FILE_RE)
331
NOFILE_OS = (common.NoFileOsError, re.NO_SUCH_FILE_RE)
332
NOTADIR_IO = (common.NotDirIoError, "^Not a directory$")
333
NOTADIR_OS = (common.NotDirOsError, "^Not a directory$")
334
ISADIR_IO = (common.IsDirIoError, "^Is a directory$")
335
ISADIR_OS = (common.IsDirOsError, "^Is a directory$")
336
DIRNOTEMPTY_OS = (OSError, "^Directory not empty$")
337
# RuntimeError
338
MAXRECURDEPTH = (RuntimeError, re.MAX_RECURSION_DEPTH_RE)
339
SIZECHANGEDDURINGITER = (RuntimeError, re.SIZE_CHANGED_DURING_ITER_RE)
340
341
342
class GetSuggestionsTests(unittest2.TestCase):
343
    """Generic class to test get_suggestions_for_exception.
344
345
    Many tests do not correspond to any handled exceptions but are
346
    kept because it is quite convenient to have a large panel of examples.
347
    Also, some correspond to example where suggestions could be added, those
348
    are flagged with a NICE_TO_HAVE comment.
349
    Finally, whenever it is easily possible, the code with the suggestions
350
    taken into account is usually tested too to ensure that the suggestion does
351
    work.
352
    """
353
354
    def runs(self, code, version_range=None, interpreters=None):
355
        """Helper function to run code.
356
357
        version_range and interpreters can be provided if the test depends on
358
        the used environment.
359
        """
360
        details = "Running following code :\n---\n{0}\n---".format(code)
361
        if PythonEnvRange(version_range, interpreters).contains_current_env():
362
            exc = get_exception(code)
363
            self.assertTrue(exc is None, "Exc thrown : " + str(exc) + details)
364
365
    def throws(self, code, error_info,
366
               sugg=None, version_range=None, interpreters=None):
367
        """Run code and check it throws and relevant suggestions are provided.
368
369
        Helper function to run code, check that it throws, what it throws and
370
        that the exception leads to the expected suggestions.
371
        version_range and interpreters can be provided if the test depends on
372
        the used environment.
373
        """
374
        sugg = sorted(listify(sugg, [], str))
375
        error_type, error_msg = error_info
376
        details = "Running following code :\n---\n{0}\n---".format(code)
377
        if PythonEnvRange(version_range, interpreters).contains_current_env():
378
            exc = get_exception(code)
379
            self.assertFalse(exc is None, "No exc thrown." + details)
380
            type_caught, value, traceback = exc
381
            self.assertTrue(isinstance(value, type_caught))
382
            self.assertTrue(
383
                issubclass(type_caught, error_type),
384
                "{0} ({1}) not a subclass of {2}"
385
                .format(type_caught, value, error_type) + details)
386
            msg = next((a for a in value.args if isinstance(a, str)), '')
387
            if error_msg is not None:
388
                self.assertRegexpMatches(msg, error_msg, details)
389
            suggestions = sorted(
390
                get_suggestions_for_exception(value, traceback))
391
            self.assertEqual(suggestions, sugg, details)
392
393
394
class NameErrorTests(GetSuggestionsTests):
395
    """Class for tests related to NameError."""
396
397
    def test_local(self):
398
        """Should be 'foo'."""
399
        code = "foo = 0\n{0}"
400
        typo, sugg = "foob", "foo"
401
        bad_code, good_code = format_str(code, typo, sugg)
402
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
403
        self.runs(good_code)
404
405
    def test_1_arg(self):
406
        """Should be 'foo'."""
407
        typo, sugg = "foob", "foo"
408
        code = func_gen(param=sugg, body='{0}', args='1')
409
        bad_code, good_code = format_str(code, typo, sugg)
410
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
411
        self.runs(good_code)
412
413
    def test_n_args(self):
414
        """Should be 'fool' or 'foot'."""
415
        typo, sugg1, sugg2 = "foob", "foot", "fool"
416
        code = func_gen(param='fool, foot', body='{0}', args='1, 2')
417
        bad, good1, good2 = format_str(code, typo, sugg1, sugg2)
418
        self.throws(bad, NAMEERROR, ["'fool' (local)", "'foot' (local)"])
419
        self.runs(good1)
420
        self.runs(good2)
421
422
    def test_builtin(self):
423
        """Should be 'max'."""
424
        typo, sugg = 'maxi', 'max'
425
        self.throws(typo, NAMEERROR, "'" + sugg + "' (builtin)")
426
        self.runs(sugg)
427
428
    def test_keyword(self):
429
        """Should be 'pass'."""
430
        typo, sugg = 'passs', 'pass'
431
        self.throws(typo, NAMEERROR, "'" + sugg + "' (keyword)")
432
        self.runs(sugg)
433
434
    def test_global(self):
435
        """Should be this_is_a_global_list."""
436
        typo, sugg = 'this_is_a_global_lis', 'this_is_a_global_list'
437
        # just a way to say that this_is_a_global_list is needed in globals
438
        this_is_a_global_list
439
        self.assertFalse(sugg in locals())
440
        self.assertTrue(sugg in globals())
441
        self.throws(typo, NAMEERROR, "'" + sugg + "' (global)")
442
        self.runs(sugg)
443
444
    def test_name(self):
445
        """Should be '__name__'."""
446
        typo, sugg = '__name_', '__name__'
447
        self.throws(typo, NAMEERROR, "'" + sugg + "' (global)")
448
        self.runs(sugg)
449
450
    def test_decorator(self):
451
        """Should be classmethod."""
452
        typo, sugg = "class_method", "classmethod"
453
        code = "@{0}\n" + func_gen()
454
        bad_code, good_code = format_str(code, typo, sugg)
455
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (builtin)")
456
        self.runs(good_code)
457
458
    def test_import(self):
459
        """Should be math."""
460
        code = 'import math\n{0}'
461
        typo, sugg = 'maths', 'math'
462
        bad_code, good_code = format_str(code, typo, sugg)
463
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
464
        self.runs(good_code)
465
466
    def test_import2(self):
467
        """Should be my_imported_math."""
468
        code = 'import math as my_imported_math\n{0}'
469
        typo, sugg = 'my_imported_maths', 'my_imported_math'
470
        bad_code, good_code = format_str(code, typo, sugg)
471
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
472
        self.runs(good_code)
473
474
    def test_imported(self):
475
        """Should be math.pi."""
476
        code = 'import math\n{0}'
477
        typo, sugg = 'pi', 'math.pi'
478
        bad_code, good_code = format_str(code, typo, sugg)
479
        self.throws(bad_code, NAMEERROR, "'" + sugg + "'")
480
        self.runs(good_code)
481
482
    def test_imported_twice(self):
483
        """Should be math.pi."""
484
        code = 'import math\nimport math\n{0}'
485
        typo, sugg = 'pi', 'math.pi'
486
        bad_code, good_code = format_str(code, typo, sugg)
487
        self.throws(bad_code, NAMEERROR, "'" + sugg + "'")
488
        self.runs(good_code)
489
490
    def test_not_imported(self):
491
        """Should be random.choice after importing random."""
492
        # This test assumes that `module` is not imported
493
        module, attr = 'random', 'choice'
494
        self.assertFalse(module in locals())
495
        self.assertFalse(module in globals())
496
        self.assertTrue(module in STAND_MODULES)
497
        bad_code = attr
498
        good_code = 'from {0} import {1}\n{2}'.format(module, attr, bad_code)
499
        self.runs(good_code)
500
        self.throws(
501
            bad_code, NAMEERROR,
502
            "'{0}' from {1} (not imported)".format(attr, module))
503
504
    def test_enclosing_scope(self):
505
        """Test that variables from enclosing scope are suggested."""
506
        # NICE_TO_HAVE
507
        typo, sugg = 'foob', 'foo'
508
        code = 'def f():\n\tfoo = 0\n\tdef g():\n\t\t{0}\n\tg()\nf()'
509
        bad_code, good_code = format_str(code, typo, sugg)
510
        self.throws(bad_code, NAMEERROR)
511
        self.runs(good_code)
512
513
    def test_no_sugg(self):
514
        """No suggestion."""
515
        self.throws('a = ldkjhfnvdlkjhvgfdhgf', NAMEERROR)
516
517
    def test_free_var_before_assignment(self):
518
        """No suggestion but different error message."""
519
        code = 'def f():\n\tdef g():\n\t\treturn free_var' \
520
               '\n\tg()\n\tfree_var = 0\nf()'
521
        self.throws(code, NAMEERRORBEFOREREF)
522
523
    # For added/removed names, following functions with one name
524
    # per functions were added in the early stages of the project.
525
    # In the future, I'd like to have them replaced by something
526
    # a bit more concise using relevant data structure. In the
527
    # meantime, I am keeping both versions because safer is better.
528
    def test_removed_cmp(self):
529
        """Builtin cmp is removed."""
530
        code = 'cmp(1, 2)'
531
        sugg1 = '1 < 2'
532
        sugg2 = 'def cmp(a, b):\n\treturn (a > b) - (a < b)\ncmp(1, 2)'
533
        before, after = before_and_after((3, 0, 1))
534
        self.runs(code, before)
535
        self.throws(code, NAMEERROR, CMP_REMOVED_MSG, after)
536
        self.runs(sugg1)
537
        self.runs(sugg2)
538
539
    def test_removed_reduce(self):
540
        """Builtin reduce is removed - moved to functools."""
541
        code = 'reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])'
542
        sugg = "'reduce' from functools (not imported)"
543
        before, after = before_and_after((3, 0))
544
        self.runs(code, before)
545
        self.throws(code, NAMEERROR, sugg, after)
546
        self.runs('from functools import reduce\n' + code, after)
547
548
    def test_removed_apply(self):
549
        """Builtin apply is removed."""
550
        code = 'apply(sum, [[1, 2, 3]])'
551
        sugg = 'sum([1, 2, 3])'
552
        before, after = before_and_after((3, 0))
553
        self.runs(code, before)
554
        self.throws(code, NAMEERROR, APPLY_REMOVED_MSG, after)
555
        self.runs(sugg)
556
557
    def test_removed_reload(self):
558
        """Builtin reload is removed.
559
560
        Moved to importlib.reload or imp.reload depending on version.
561
        """
562
        code = 'reload(math)'
563
        sugg_template = 'import {0}\n{0}.reload(math)'
564
        sugg1, sugg2 = format_str(sugg_template, 'importlib', 'imp')
565
        version = (3, 0)
566
        self.runs(code, up_to_version(version))
567
        self.throws(code, NAMEERROR, RELOAD_REMOVED_MSG, from_version(version))
568
        self.runs(sugg1, from_version((3, 4)))
569
        self.runs(sugg2)
570
571
    def test_removed_intern(self):
572
        """Builtin intern is removed - moved to sys."""
573
        code = 'intern("toto")'
574
        new_code = 'sys.intern("toto")'
575
        suggs = ["'iter' (builtin)", "'sys.intern'"]
576
        before, after = before_and_after((3, 0))
577
        self.runs(code, before)
578
        self.throws(code, NAMEERROR, suggs, after)
579
        self.runs(new_code, after)
580
581
    def test_removed_execfile(self):
582
        """Builtin execfile is removed - use exec() and compile()."""
583
        # NICE_TO_HAVE
584
        code = 'execfile("some_filename")'
585
        _, after = before_and_after((3, 0))
586
        # self.runs(code, before)
587
        self.throws(code, NAMEERROR, [], after)
588
589
    def test_removed_raw_input(self):
590
        """Builtin raw_input is removed - use input() instead."""
591
        code = 'i = raw_input("Prompt:")'
592
        _, after = before_and_after((3, 0))
593
        # self.runs(code, before)
594
        self.throws(code, NAMEERROR, "'input' (builtin)", after)
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
        before, after = before_and_after((2, 7))
608
        for name, suggs in {
609
                'memoryview': [MEMVIEW_ADDED_MSG],
610
                }.items():
611
            self.throws(name, NAMEERROR, suggs, before)
612
            self.runs(name, after)
613
614
    def test_removed_3_0(self):
615
        """Test for names removed in 3.0."""
616
        before, after = before_and_after((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.runs(name, before)
636
            self.throws(name, NAMEERROR, suggs, after)
637
638
    def test_added_3_0(self):
639
        """Test for names added in 3.0."""
640
        before, after = before_and_after((3, 0))
641
        for name, suggs in {
642
                'ascii': [],
643
                'ResourceWarning': ["'FutureWarning' (builtin)"],
644
                '__build_class__': [],
645
                }.items():
646
            self.throws(name, NAMEERROR, suggs, before)
647
            self.runs(name, after)
648
649
    def test_added_3_3(self):
650
        """Test for names added in 3.3."""
651
        before, after = before_and_after((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.throws(name, NAMEERROR, suggs, before)
670
            self.runs(name, after)
671
672
    def test_added_3_4(self):
673
        """Test for names added in 3.4."""
674
        before, after = before_and_after((3, 4))
675
        for name, suggs in {
676
                '__spec__': [],
677
                }.items():
678
            self.throws(name, NAMEERROR, suggs, before)
679
            self.runs(name, after)
680
681
    def test_added_3_5(self):
682
        """Test for names added in 3.5."""
683
        before, after = before_and_after((3, 5))
684
        for name, suggs in {
685
                'StopAsyncIteration': ["'StopIteration' (builtin)"],
686
                }.items():
687
            self.throws(name, NAMEERROR, suggs, before)
688
            self.runs(name, after)
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
        before, after = before_and_after((3, 0))
796
        self.throws(good_code, INVALIDSYNTAX, [], before)
797
        self.runs(good_code, after)
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
        before, after = before_and_after((3, 0))
809
        self.throws(good_code1, INVALIDSYNTAX, [], before)
810
        self.runs(good_code1, after)
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
        before, after = before_and_after((3, 0))
851
        self.runs(code, before)
852
        self.throws(code, ATTRIBUTEERROR, sugg, after)
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
        before, after = before_and_after((3, 5))
887
        bad_code, good_code = format_str(code, typo, good)
888
        self.throws(bad_code, ATTRIBUTEERROR, sugg, before)
889
        self.throws(bad_code, MODATTRIBUTEERROR, sugg, after)
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
        before, after = before_and_after((3, 5))
898
        bad_code, good_code = format_str(code, typo, good)
899
        self.throws(bad_code, ATTRIBUTEERROR, sugg, before)
900
        self.throws(bad_code, MODATTRIBUTEERROR, sugg, after)
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
        before, after = before_and_after((3, 0))
956
        self.runs(code, before)
957
        self.throws(code, ATTRIBUTEERROR, sugg, after)
958
        self.runs(new_code)
959
960
    def test_removed_dict_methods(self):
961
        """Different methos (iterXXX) have been removed from dict."""
962
        before, after = before_and_after((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, before)
971
            self.throws(meth_code, ATTRIBUTEERROR, sugg, after)
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
        before, after = before_and_after((3, 0))
997
        self.runs(old_code, before)
998
        self.throws(old_code, ATTRIBUTEERROR, suggs, after)
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
        before, after = before_and_after((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, before)
1017
            self.throws(old_code, ATTRIBUTEERROR, sugg, after)
1018
            self.runs(new_code)
1019
1020
    def test_removed_method_attributes(self):
1021
        """Some methods attributes are removed."""
1022
        # NICE_TO_HAVE
1023
        before, after = before_and_after((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, before)
1031
            self.throws(old_code, ATTRIBUTEERROR, sugg, after)
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
        before, after = before_and_after((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, [], before)
1100
                self.runs(set1, after)
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
        before, after = before_and_after((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, [], before, 'pypy')
1162
            self.throws(bad_code, err_pyp3, [], after, '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
        before, after = before_and_after((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, before)
1213
            self.throws(code, BADOPERANDUNARY, sugg_unary, after)
1214
1215
    def test_unary_operand_builtin(self):
1216
        """Test unary operand errors on builtin types."""
1217
        ops = [
1218
            '+{0}',
1219
            '-{0}',
1220
            '~{0}',
1221
            'abs({0})',
1222
        ]
1223
        obj = 'set()'
1224
        for op in ops:
1225
            code = op.format(obj)
1226
            self.throws(code, BADOPERANDUNARY)
1227
1228
    def test_len_on_iterable(self):
1229
        """len() can't be called on iterable (weird but understandable)."""
1230
        code = 'len(my_generator())'
1231
        sugg = 'len(list(my_generator()))'
1232
        self.throws(code, OBJECTHASNOFUNC, "'len(list(generator))'")
1233
        self.runs(sugg)
1234
1235
    def test_len_on_custom(self):
1236
        """len() can't be called on custom."""
1237
        before, after = before_and_after((3, 0))
1238
        code = 'o = {0}()\nlen(o)'
1239
        bad, good = format_str(code, 'CustomClass', 'LenClass')
1240
        sugg = 'implement "__len__" on CustomClass'
1241
        self.throws(bad, ATTRIBUTEERROR, ["'__module__'"], before)
1242
        self.throws(bad, OBJECTHASNOFUNC, sugg, after)
1243
        self.runs(good)
1244
1245
    def test_nb_args(self):
1246
        """Should have 1 arg."""
1247
        typo, sugg = '1, 2', '1'
1248
        code = func_gen(param='a', args='{0}')
1249
        bad_code, good_code = format_str(code, typo, sugg)
1250
        self.throws(bad_code, NBARGERROR)
1251
        self.runs(good_code)
1252
1253
    def test_nb_args1(self):
1254
        """Should have 0 args."""
1255
        typo, sugg = '1', ''
1256
        code = func_gen(param='', args='{0}')
1257
        bad_code, good_code = format_str(code, typo, sugg)
1258
        self.throws(bad_code, NBARGERROR)
1259
        self.runs(good_code)
1260
1261
    def test_nb_args2(self):
1262
        """Should have 1 arg."""
1263
        typo, sugg = '', '1'
1264
        before, after = before_and_after((3, 3))
1265
        code = func_gen(param='a', args='{0}')
1266
        bad_code, good_code = format_str(code, typo, sugg)
1267
        self.throws(bad_code, NBARGERROR, [], before)
1268
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1269
        self.runs(good_code)
1270
1271
    def test_nb_args3(self):
1272
        """Should have 3 args."""
1273
        typo, sugg = '1', '1, 2, 3'
1274
        before, after = before_and_after((3, 3))
1275
        code = func_gen(param='so, much, args', args='{0}')
1276
        bad_code, good_code = format_str(code, typo, sugg)
1277
        self.throws(bad_code, NBARGERROR, [], before)
1278
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1279
        self.runs(good_code)
1280
1281
    def test_nb_args4(self):
1282
        """Should have 3 args."""
1283
        typo, sugg = '', '1, 2, 3'
1284
        before, after = before_and_after((3, 3))
1285
        code = func_gen(param='so, much, args', args='{0}')
1286
        bad_code, good_code = format_str(code, typo, sugg)
1287
        self.throws(bad_code, NBARGERROR, [], before)
1288
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1289
        self.runs(good_code)
1290
1291
    def test_nb_args5(self):
1292
        """Should have 3 args."""
1293
        typo, sugg = '1, 2', '1, 2, 3'
1294
        before, after = before_and_after((3, 3))
1295
        code = func_gen(param='so, much, args', args='{0}')
1296
        bad_code, good_code = format_str(code, typo, sugg)
1297
        self.throws(bad_code, NBARGERROR, [], before)
1298
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1299
        self.runs(good_code)
1300
1301
    def test_nb_args6(self):
1302
        """Should provide more args."""
1303
        # Amusing message: 'func() takes exactly 2 arguments (2 given)'
1304
        before, after = before_and_after((3, 3))
1305
        code = func_gen(param='a, b, c=3', args='{0}')
1306
        bad_code, good_code1, good_code2 = format_str(
1307
            code,
1308
            'b=2, c=3',
1309
            'a=1, b=2, c=3',
1310
            '1, b=2, c=3')
1311
        self.throws(bad_code, NBARGERROR, [], before)
1312
        self.throws(bad_code, MISSINGPOSERROR, [], after)
1313
        self.runs(good_code1)
1314
        self.runs(good_code2)
1315
1316
    def test_nb_arg_missing_self(self):
1317
        """Arg 'self' is missing."""
1318
        # NICE_TO_HAVE
1319
        obj = 'FoobarClass()'
1320
        self.throws(obj + '.some_method_missing_self_arg()', NBARGERROR)
1321
        self.throws(obj + '.some_method_missing_self_arg2(42)', NBARGERROR)
1322
        self.runs(obj + '.some_method()')
1323
        self.runs(obj + '.some_method2(42)')
1324
1325
    def test_nb_arg_missing_cls(self):
1326
        """Arg 'cls' is missing."""
1327
        # NICE_TO_HAVE
1328
        for obj in ('FoobarClass()', 'FoobarClass'):
1329
            self.throws(obj + '.some_cls_method_missing_cls()', NBARGERROR)
1330
            self.throws(obj + '.some_cls_method_missing_cls2(42)', NBARGERROR)
1331
            self.runs(obj + '.this_is_cls_mthd()')
1332
1333
    def test_keyword_args(self):
1334
        """Should be param 'babar' not 'a' but it's hard to guess."""
1335
        typo, sugg = 'a', 'babar'
1336
        code = func_gen(param=sugg, args='{0}=1')
1337
        bad_code, good_code = format_str(code, typo, sugg)
1338
        self.throws(bad_code, UNEXPECTEDKWARG)
1339
        self.runs(good_code)
1340
1341
    def test_keyword_args2(self):
1342
        """Should be param 'abcdef' not 'abcdf'."""
1343
        typo, sugg = 'abcdf', 'abcdef'
1344
        code = func_gen(param=sugg, args='{0}=1')
1345
        bad_code, good_code = format_str(code, typo, sugg)
1346
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1347
        self.runs(good_code)
1348
1349
    def test_keyword_arg_method(self):
1350
        """Should be the same as previous test but on a method."""
1351
        code = 'class MyClass:\n\tdef func(self, a):' \
1352
               '\n\t\tpass\nMyClass().func({0}=1)'
1353
        bad_code, good_code = format_str(code, 'babar', 'a')
1354
        self.throws(bad_code, UNEXPECTEDKWARG)
1355
        self.runs(good_code)
1356
1357
    def test_keyword_arg_method2(self):
1358
        """Should be the same as previous test but on a method."""
1359
        typo, sugg = 'abcdf', 'abcdef'
1360
        code = 'class MyClass:\n\tdef func(self, ' + sugg + '):' \
1361
               '\n\t\tpass\nMyClass().func({0}=1)'
1362
        bad_code, good_code = format_str(code, typo, sugg)
1363
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1364
        self.runs(good_code)
1365
1366
    def test_keyword_arg_class_method(self):
1367
        """Should be the same as previous test but on a class method."""
1368
        code = 'class MyClass:\n\t@classmethod\n\tdef func(cls, a):' \
1369
               '\n\t\tpass\nMyClass.func({0}=1)'
1370
        bad_code, good_code = format_str(code, 'babar', 'a')
1371
        self.throws(bad_code, UNEXPECTEDKWARG)
1372
        self.runs(good_code)
1373
1374
    def test_keyword_arg_class_method2(self):
1375
        """Should be the same as previous test but on a class method."""
1376
        typo, sugg = 'abcdf', 'abcdef'
1377
        code = 'class MyClass:\n\t@classmethod ' \
1378
               '\n\tdef func(cls, ' + sugg + '):\n ' \
1379
               '\t\tpass\nMyClass.func({0}=1)'
1380
        bad_code, good_code = format_str(code, typo, sugg)
1381
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1382
        self.runs(good_code)
1383
1384
    def test_keyword_arg_multiples_instances(self):
1385
        """If multiple functions are found, suggestions should be unique."""
1386
        typo, sugg = 'abcdf', 'abcdef'
1387
        code = 'class MyClass:\n\tdef func(self, ' + sugg + '):' \
1388
               '\n\t\tpass\na = MyClass()\nb = MyClass()\na.func({0}=1)'
1389
        bad_code, good_code = format_str(code, typo, sugg)
1390
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1391
        self.runs(good_code)
1392
1393
    def test_keyword_arg_lambda(self):
1394
        """Test with lambda functions instead of usual function."""
1395
        typo, sugg = 'abcdf', 'abcdef'
1396
        code = 'f = lambda arg1, ' + sugg + ': None\nf(42, {0}=None)'
1397
        bad_code, good_code = format_str(code, typo, sugg)
1398
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1399
        self.runs(good_code)
1400
1401
    def test_keyword_arg_lambda_method(self):
1402
        """Test with lambda methods instead of usual methods."""
1403
        typo, sugg = 'abcdf', 'abcdef'
1404
        code = 'class MyClass:\n\tfunc = lambda self, ' + sugg + ': None' \
1405
               '\nMyClass().func({0}=1)'
1406
        bad_code, good_code = format_str(code, typo, sugg)
1407
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1408
        self.runs(good_code)
1409
1410
    def test_keyword_arg_other_objects_with_name(self):
1411
        """Mix of previous tests but with more objects defined.
1412
1413
        Non-function object with same same as the function tested are defined
1414
        to ensure that things do work fine.
1415
        """
1416
        code = 'func = "not_a_func"\nclass MyClass:\n\tdef func(self, a):' \
1417
               '\n\t\tpass\nMyClass().func({0}=1)'
1418
        bad_code, good_code = format_str(code, 'babar', 'a')
1419
        self.throws(bad_code, UNEXPECTEDKWARG)
1420
        self.runs(good_code)
1421
1422
    def test_keyword_builtin(self):
1423
        """A few builtins (like int()) have a different error message."""
1424
        # NICE_TO_HAVE
1425
        # 'max', 'input', 'len', 'abs', 'all', etc have a specific error
1426
        # message and are not relevant here
1427
        for builtin in ['int', 'float', 'bool', 'complex']:
1428
            code = builtin + '(this_doesnt_exist=2)'
1429
            self.throws(code, UNEXPECTEDKWARG2, interpreters='cython')
1430
            self.throws(code, UNEXPECTEDKWARG, interpreters='pypy')
1431
1432
    def test_keyword_builtin_print(self):
1433
        """Builtin "print" has a different error message."""
1434
        # It would be NICE_TO_HAVE suggestions on keyword arguments
1435
        before, after = before_and_after((3, 0))
1436
        code = "c = 'string'\nb = print(c, end_='toto')"
1437
        self.throws(code, INVALIDSYNTAX, [], before)
1438
        self.throws(code, UNEXPECTEDKWARG2, [], after, 'cython')
1439
        self.throws(code, UNEXPECTEDKWARG3, [], after, 'pypy')
1440
1441
    def test_keyword_sort_cmpkey(self):
1442
        """Sort and sorted functions have a cmp/key param dep. on the vers."""
1443
        before, after = before_and_after((3, 0))
1444
        code = "import functools as f\nl = [1, 8, 3]\n" \
1445
               "def comp(a, b): return (a > b) - (a < b)\nl.sort({0})"
1446
        cmp_arg, key_arg, cmp_to_key = format_str(
1447
                code, 'cmp=comp', 'key=id', 'key=f.cmp_to_key(comp)')
1448
        self.runs(cmp_arg, before)
1449
        self.throws(cmp_arg, UNEXPECTEDKWARG2,
1450
                    CMP_ARG_REMOVED_MSG, after, 'cython')
1451
        self.throws(cmp_arg, UNEXPECTEDKWARG,
1452
                    CMP_ARG_REMOVED_MSG, after, 'pypy')
1453
        self.runs(key_arg)
1454
        self.runs(cmp_to_key, from_version((2, 7)))
1455
1456
    def test_iter_cannot_be_interpreted_as_int(self):
1457
        """Trying to call `range(len(iterable))` (bad) and forget the len."""
1458
        before, after = before_and_after((3, 0))
1459
        bad_code = 'range([0, 1, 2])'
1460
        good_code = 'range(len([0, 1, 2]))'
1461
        sugg = "'len(list)'"
1462
        self.runs(good_code)
1463
        self.throws(bad_code, INTEXPECTED, sugg, before)
1464
        self.throws(bad_code, CANNOTBEINTERPRETED, sugg, after)
1465
1466
    RANGE_CODE_TEMPLATES = [
1467
        'range({0})',
1468
        'range({0}, 14)',
1469
        'range(0, 24, {0})'
1470
    ]
1471
    INDEX_CODE_TEMPLATES = ['[1, 2, 3][{0}]', '(1, 2, 3)[{0}]']
1472
1473
    def test_str_cannot_be_interpreted_as_int(self):
1474
        """Forget to convert str to int."""
1475
        before, after = before_and_after((3, 0))
1476 View Code Duplication
        suggs = ["'int(str)'", "'len(str)'"]
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1477
        for code in self.RANGE_CODE_TEMPLATES:
1478
            bad_code, good_code = format_str(code, '"12"', 'int("12")')
1479
            self.runs(good_code)
1480
            self.throws(bad_code, INTEXPECTED, suggs, before)
1481
            self.throws(bad_code, CANNOTBEINTERPRETED, suggs, after)
1482
1483
    def test_float_cannot_be_interpreted_as_int(self):
1484
        """Use float instead of int."""
1485
        v27 = (2, 7)
1486
        v3 = (3, 0)
1487
        sugg = ["'int(float)'"]
1488
        suggs = ["'int(float)'", "'math.ceil(float)'", "'math.floor(float)'"]
1489
        for code in self.RANGE_CODE_TEMPLATES:
1490
            full_code = 'import math\n' + code
1491
            good1, good2, bad = format_str(
1492
                full_code, 'int(12.0)', 'math.floor(12.0)', '12.0')
1493
            self.runs(good1)
1494
            self.runs(good2, up_to_version(v27))
1495
            # floor returns a float before Python 3 -_-
1496
            self.throws(good2, INTEXPECTED, sugg, (v27, v3))
1497
            self.runs(good2, from_version(v3))
1498
            self.runs(bad, up_to_version(v27))
1499
            self.throws(bad, INTEXPECTED, sugg, (v27, v3))
1500
            self.throws(bad, CANNOTBEINTERPRETED, suggs, from_version(v3))
1501
1502
    def test_customclass_cannot_be_interpreter_as_int(self):
1503
        """Forget to implement the __index__ method."""
1504
        # http://stackoverflow.com/questions/17342899/object-cannot-be-interpreted-as-an-integer
1505
        # https://twitter.com/raymondh/status/773224135409360896
1506
        before, after = before_and_after((3, 0))
1507
        sugg = 'implement "__index__" on CustomClass'
1508
        for code in self.RANGE_CODE_TEMPLATES:
1509
            bad, good = format_str(code, 'CustomClass()', 'IndexClass()')
1510
            self.throws(bad, ATTRIBUTEERROR, [], before)
1511
            self.throws(bad, CANNOTBEINTERPRETED, sugg, after)
1512
            self.runs(good, after)  # Fails on old python ?
1513
1514
    def test_indices_cant_be_str(self):
1515
        """Use str as index."""
1516
        suggs = ["'int(str)'", "'len(str)'"]
1517
        for code in self.INDEX_CODE_TEMPLATES:
1518
            bad, good = format_str(code, '"2"', 'int("2")')
1519
            self.runs(good)
1520
            self.throws(bad, INDICESMUSTBEINT, suggs)
1521
1522
    def test_indices_cant_be_float(self):
1523
        """Use float as index."""
1524
        before, after = before_and_after((3, 0))
1525
        sugg = ["'int(float)'"]
1526
        suggs = ["'int(float)'", "'math.ceil(float)'", "'math.floor(float)'"]
1527
        for code in self.INDEX_CODE_TEMPLATES:
1528
            good1, good2, bad = format_str(
1529
                    code, 'int(2.0)', 'math.floor(2.0)', '2.0')
1530
            self.runs(good1)
1531
            # floor returns a float before Python 3 -_-
1532
            self.throws(good2, INDICESMUSTBEINT, sugg, before)
1533
            self.runs(good2, after)
1534
            self.throws(bad, INDICESMUSTBEINT, sugg, before)
1535
            self.throws(bad, INDICESMUSTBEINT, suggs, after)
1536
1537
    def test_indices_cant_be_custom(self):
1538
        """Use custom as index."""
1539
        before, after = before_and_after((3, 0))
1540
        sugg = 'implement "__index__" on CustomClass'
1541
        # On Pypy, detected type is 'instance' so attribute detection is much
1542
        # less precise, leading to additional suggestions
1543
        suggs = ["'len(instance)'", 'implement "__index__" on instance']
1544
        for code in self.INDEX_CODE_TEMPLATES:
1545
            bad, good = format_str(code, 'CustomClass()', 'IndexClass()')
1546
            self.throws(bad, INDICESMUSTBEINT, suggs, before, 'pypy')
1547
            self.throws(bad, CANNOTBEINTERPRETEDINDEX, [], before, 'cython')
1548
            self.throws(bad, INDICESMUSTBEINT, sugg, after)
1549
            self.runs(good)
1550
1551
    def test_no_implicit_str_conv(self):
1552
        """Trying to concatenate a non-string value to a string."""
1553
        # NICE_TO_HAVE
1554
        code = '{0} + " things"'
1555
        typo, sugg = '12', 'str(12)'
1556
        bad_code, good_code = format_str(code, typo, sugg)
1557
        self.throws(bad_code, UNSUPPORTEDOPERAND)
1558
        self.runs(good_code)
1559
1560
    def test_cannot_concatenate_iter_to_list(self):
1561
        """Trying to concatenate a non-list iterable to a list."""
1562
        # NICE_TO_HAVE
1563
        before, after = before_and_after((3, 0))
1564
        code = 'list() + {0}'
1565
        good, bad, sugg, bad2, bad3, bad4 = \
1566
            format_str(code, 'list()', 'set()', 'list(set())',
1567
                       'range(10)', 'dict().keys()', 'dict().iterkeys()')
1568
        self.runs(good)
1569
        self.runs(sugg)
1570
        self.throws(bad, ONLYCONCAT, interpreters='cython')
1571
        self.throws(bad, UNSUPPORTEDOPERAND, interpreters='pypy')
1572
        # Other examples are more interesting but depend on the version used:
1573
        #  - range returns a list or a range object
1574
        self.runs(bad2, before)
1575
        self.throws(bad2, ONLYCONCAT, [], after, 'cython')
1576
        self.throws(bad2, UNSUPPORTEDOPERAND, [], after, 'pypy')
1577
        #  - keys return a list or a view object
1578
        self.runs(bad3, before)
1579
        self.throws(bad3, ONLYCONCAT, [], after, 'cython')
1580
        self.throws(bad3, UNSUPPORTEDOPERAND, [], after, 'pypy')
1581
        #  - iterkeys returns an iterator or doesn't exist
1582
        self.throws(bad4, ONLYCONCAT, [], before, 'cython')
1583
        self.throws(bad4, UNSUPPORTEDOPERAND, [], before, 'pypy')
1584
        self.throws(bad4, ATTRIBUTEERROR, [], after)
1585
1586
    def test_no_implicit_str_conv2(self):
1587
        """Trying to concatenate a non-string value to a string."""
1588
        # NICE_TO_HAVE
1589
        code = '"things " + {0}'
1590
        typo, sugg = '12', 'str(12)'
1591
        bad_code, good_code = format_str(code, typo, sugg)
1592
        version = (3, 0)
1593 View Code Duplication
        version2 = (3, 6)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1594
        self.throws(
1595
            bad_code, CANNOTCONCAT, [], up_to_version(version), 'cython')
1596
        self.throws(
1597
            bad_code, CANTCONVERT, [], (version, version2), 'cython')
1598
        self.throws(
1599
            bad_code, MUSTBETYPENOTTYPE, [], from_version(version2), 'cython')
1600
        self.throws(bad_code, UNSUPPORTEDOPERAND, interpreters='pypy')
1601
        self.runs(good_code)
1602
1603
    def test_assignment_to_range(self):
1604
        """Trying to assign to range works on list, not on range."""
1605
        code = '{0}[2] = 1'
1606
        typo, good = 'range(4)', 'list(range(4))'
1607
        sugg = 'convert to list to edit the list'
1608
        before, after = before_and_after((3, 0))
1609
        bad_code, good_code = format_str(code, typo, good)
1610
        self.runs(good_code)
1611
        self.runs(bad_code, before)
1612
        self.throws(bad_code, OBJECTDOESNOTSUPPORT, sugg, after)
1613
1614
    def test_assignment_to_string(self):
1615
        """Trying to assign to string does not work."""
1616
        code = "s = 'abc'\ns[1] = 'd'"
1617
        good_code = "s = 'abc'\nl = list(s)\nl[1] = 'd'\ns = ''.join(l)"
1618
        self.runs(good_code)
1619
        self.throws(
1620
            code,
1621
            OBJECTDOESNOTSUPPORT,
1622
            'convert to list to edit the list and use "join()" on the list')
1623
1624
    def test_assignment_to_custom(self):
1625
        """Trying to assign to custom obj."""
1626
        before, after = before_and_after((3, 0))
1627
        code = "o = {0}()\no[1] = 'd'"
1628
        bad, good = format_str(code, 'CustomClass', 'SetItemClass')
1629
        sugg = 'implement "__setitem__" on CustomClass'
1630
        self.throws(bad, ATTRIBUTEERROR, [], before)
1631
        self.throws(bad, OBJECTDOESNOTSUPPORT, sugg, after)
1632
        self.runs(good)
1633
1634
    def test_deletion_from_string(self):
1635
        """Delete from string does not work."""
1636
        code = "s = 'abc'\ndel s[1]"
1637
        good_code = "s = 'abc'\nl = list(s)\ndel l[1]\ns = ''.join(l)"
1638
        sugg = 'convert to list to edit the list and use "join()" on the list'
1639
        self.runs(good_code)
1640
        self.throws(code, OBJECTDOESNOTSUPPORT, sugg)
1641
1642
    def test_deletion_from_custom(self):
1643
        """Delete from custom obj does not work."""
1644
        before, after = before_and_after((3, 0))
1645
        code = "o = {0}()\ndel o[1]"
1646
        bad, good = format_str(code, 'CustomClass', 'DelItemClass')
1647
        sugg = 'implement "__delitem__" on CustomClass'
1648
        self.throws(bad, ATTRIBUTEERROR, [], before)
1649
        self.throws(bad, OBJECTDOESNOTSUPPORT, sugg, after)
1650
        self.runs(good)
1651
1652
    def test_object_indexing(self):
1653
        """Index from object does not work if __getitem__ is not defined."""
1654
        before, after = before_and_after((3, 0))
1655
        code = "{0}[0]"
1656
        good_code, set_code, custom_bad, custom_good = \
1657
            format_str(code, '"a_string"', "set()",
1658
                       "CustomClass()", "GetItemClass()")
1659
        self.runs(good_code)
1660
        sugg_for_iterable = 'convert to list first or use the iterator ' \
1661
            'protocol to get the different elements'
1662
        sugg_imp = 'implement "__getitem__" on CustomClass'
1663
        self.throws(set_code,
1664
                    OBJECTDOESNOTSUPPORT,
1665
                    sugg_for_iterable, interpreters='cython')
1666
        self.throws(set_code,
1667
                    UNSUBSCRIPTABLE,
1668
                    sugg_for_iterable, interpreters='pypy')
1669
        self.throws(custom_bad, ATTRIBUTEERROR, [], before, 'pypy')
1670
        self.throws(custom_bad, UNSUBSCRIPTABLE, sugg_imp, after, 'pypy')
1671
        self.throws(custom_bad, ATTRIBUTEERROR, [], before, 'cython')
1672
        self.throws(custom_bad,
1673
                    OBJECTDOESNOTSUPPORT,
1674
                    sugg_imp,
1675
                    after, 'cython')
1676
        self.runs(custom_good)
1677
1678
    def test_not_callable(self):
1679
        """Sometimes, one uses parenthesis instead of brackets."""
1680
        typo, getitem = '(0)', '[0]'
1681
        for ex, sugg in {
1682
            '[0]': "'list[value]'",
1683
            '{0: 0}': "'dict[value]'",
1684
            '"a"': "'str[value]'",
1685
        }.items():
1686
            self.throws(ex + typo, NOTCALLABLE, sugg)
1687
            self.runs(ex + getitem)
1688
        for ex in ['1', 'set()']:
1689
            self.throws(ex + typo, NOTCALLABLE)
1690
1691
    def test_not_callable_custom(self):
1692
        """One must define __call__ to call custom objects."""
1693
        before, after = before_and_after((3, 0))
1694
        code = 'o = {0}()\no()'
1695
        bad, good = format_str(code, 'CustomClass', 'CallClass')
1696
        sugg = 'implement "__call__" on CustomClass'
1697
        self.throws(bad, INSTHASNOMETH, [], before, 'cython')
1698
        self.throws(bad, ATTRIBUTEERROR, [], before, 'pypy')
1699
        self.throws(bad, NOTCALLABLE, sugg, after)
1700
        self.runs(good)
1701
1702
    def test_exc_must_derive_from(self):
1703
        """Test when a non-exc object is raised."""
1704
        code = 'raise "ExceptionString"'
1705
        self.throws(code, EXCMUSTDERIVE)
1706
1707
    def test_unordered_builtin(self):
1708
        """Test for UNORDERABLE exception on builtin types."""
1709
        version = (3, 0)
1710
        version2 = (3, 6)
1711
        for op in ['>', '>=', '<', '<=']:
1712
            code = "'10' {0} 2".format(op)
1713
            self.runs(code, up_to_version(version))
1714
            self.throws(code, UNORDERABLE, [], (version, version2))
1715
            self.throws(code, OPNOTSUPPBETWEENINST, [], from_version(version2))
1716
1717
    def test_unordered_custom(self):
1718
        """Test for UNORDERABLE exception on custom types."""
1719
        version = (3, 0)
1720
        version2 = (3, 6)
1721
        for op in ['>', '>=', '<', '<=']:
1722
            code = "CustomClass() {0} CustomClass()".format(op)
1723
            self.runs(code, up_to_version(version))
1724
            self.throws(code, UNORDERABLE, [], (version, version2))
1725
            self.throws(code, OPNOTSUPPBETWEENINST, [], from_version(version2))
1726
1727
    def test_unordered_custom2(self):
1728
        """Test for UNORDERABLE exception on custom types."""
1729
        version = (3, 0)
1730
        version2 = (3, 6)
1731
        for op in ['>', '>=', '<', '<=']:
1732
            code = "CustomClass() {0} 2".format(op)
1733
            self.runs(code, up_to_version(version))
1734
            self.throws(code, UNORDERABLE, [], (version, version2))
1735
            self.throws(code, OPNOTSUPPBETWEENINST, [], from_version(version2))
1736
1737
    def test_unmatched_msg(self):
1738
        """Test that arbitrary strings are supported."""
1739
        self.throws(
1740
            'raise TypeError("unmatched TYPEERROR")',
1741
            UNKNOWN_TYPEERROR)
1742
1743
1744
class ImportErrorTests(GetSuggestionsTests):
1745
    """Class for tests related to ImportError."""
1746
1747
    def test_no_module_no_sugg(self):
1748
        """No suggestion."""
1749
        self.throws('import fqslkdfjslkqdjfqsd', NOMODULE)
1750
1751
    def test_no_module(self):
1752
        """Should be 'math'."""
1753
        code = 'import {0}'
1754
        typo, sugg = 'maths', 'math'
1755
        self.assertTrue(sugg in STAND_MODULES)
1756
        bad_code, good_code = format_str(code, typo, sugg)
1757
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1758
        self.runs(good_code)
1759
1760
    def test_no_module2(self):
1761
        """Should be 'math'."""
1762
        code = 'from {0} import pi'
1763
        typo, sugg = 'maths', 'math'
1764
        self.assertTrue(sugg in STAND_MODULES)
1765
        bad_code, good_code = format_str(code, typo, sugg)
1766
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1767
        self.runs(good_code)
1768
1769
    def test_no_module3(self):
1770
        """Should be 'math'."""
1771
        code = 'import {0} as my_imported_math'
1772
        typo, sugg = 'maths', 'math'
1773
        self.assertTrue(sugg in STAND_MODULES)
1774
        bad_code, good_code = format_str(code, typo, sugg)
1775
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1776
        self.runs(good_code)
1777
1778
    def test_no_module4(self):
1779
        """Should be 'math'."""
1780
        code = 'from {0} import pi as three_something'
1781
        typo, sugg = 'maths', 'math'
1782
        self.assertTrue(sugg in STAND_MODULES)
1783
        bad_code, good_code = format_str(code, typo, sugg)
1784
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1785
        self.runs(good_code)
1786
1787
    def test_no_module5(self):
1788
        """Should be 'math'."""
1789
        code = '__import__("{0}")'
1790
        typo, sugg = 'maths', 'math'
1791
        self.assertTrue(sugg in STAND_MODULES)
1792
        bad_code, good_code = format_str(code, typo, sugg)
1793
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1794
        self.runs(good_code)
1795
1796
    def test_import_future_nomodule(self):
1797
        """Should be '__future__'."""
1798
        code = 'import {0}'
1799
        typo, sugg = '__future_', '__future__'
1800
        self.assertTrue(sugg in STAND_MODULES)
1801
        bad_code, good_code = format_str(code, typo, sugg)
1802
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1803
        self.runs(good_code)
1804
1805
    def test_no_name_no_sugg(self):
1806
        """No suggestion."""
1807
        self.throws('from math import fsfsdfdjlkf', CANNOTIMPORT)
1808
1809
    def test_wrong_import(self):
1810
        """Should be 'math'."""
1811
        code = 'from {0} import pi'
1812
        typo, sugg = 'itertools', 'math'
1813
        self.assertTrue(sugg in STAND_MODULES)
1814
        bad_code, good_code = format_str(code, typo, sugg)
1815
        self.throws(bad_code, CANNOTIMPORT, "'" + good_code + "'")
1816
        self.runs(good_code)
1817
1818
    def test_typo_in_method(self):
1819
        """Should be 'pi'."""
1820
        code = 'from math import {0}'
1821
        typo, sugg = 'pie', 'pi'
1822
        bad_code, good_code = format_str(code, typo, sugg)
1823
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1824
        self.runs(good_code)
1825
1826
    def test_typo_in_method2(self):
1827
        """Should be 'pi'."""
1828
        code = 'from math import e, {0}, log'
1829
        typo, sugg = 'pie', 'pi'
1830
        bad_code, good_code = format_str(code, typo, sugg)
1831
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1832
        self.runs(good_code)
1833
1834
    def test_typo_in_method3(self):
1835
        """Should be 'pi'."""
1836
        code = 'from math import {0} as three_something'
1837
        typo, sugg = 'pie', 'pi'
1838
        bad_code, good_code = format_str(code, typo, sugg)
1839
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1840
        self.runs(good_code)
1841
1842
    def test_unmatched_msg(self):
1843
        """Test that arbitrary strings are supported."""
1844
        self.throws(
1845
            'raise ImportError("unmatched IMPORTERROR")',
1846
            UNKNOWN_IMPORTERROR)
1847
1848
    def test_module_removed(self):
1849
        """Sometimes, modules are deleted/moved/renamed."""
1850
        # NICE_TO_HAVE
1851
        version1 = (2, 7)  # result for 2.6 seems to vary
1852
        version2 = (3, 0)
1853
        code = 'import {0}'
1854
        lower, upper = format_str(code, 'tkinter', 'Tkinter')
1855
        self.throws(lower, NOMODULE, [], (version1, version2))
1856
        self.throws(upper, NOMODULE, [], from_version(version2))
1857
1858
1859
class LookupErrorTests(GetSuggestionsTests):
1860
    """Class for tests related to LookupError."""
1861
1862
1863
class KeyErrorTests(LookupErrorTests):
1864
    """Class for tests related to KeyError."""
1865
1866
    def test_no_sugg(self):
1867
        """No suggestion."""
1868
        self.throws('dict()["ffdsqmjklfqsd"]', KEYERROR)
1869
1870
1871
class IndexErrorTests(LookupErrorTests):
1872
    """Class for tests related to IndexError."""
1873
1874
    def test_no_sugg(self):
1875
        """No suggestion."""
1876
        self.throws('list()[2]', OUTOFRANGE)
1877
1878
1879
class SyntaxErrorTests(GetSuggestionsTests):
1880
    """Class for tests related to SyntaxError."""
1881
1882
    def test_no_error(self):
1883
        """No error."""
1884
        self.runs("1 + 2 == 2")
1885
1886
    def test_yield_return_out_of_func(self):
1887
        """yield/return needs to be in functions."""
1888
        sugg = "to indent it"
1889
        self.throws("yield 1", OUTSIDEFUNC, sugg)
1890
        self.throws("return 1", OUTSIDEFUNC, ["'sys.exit([arg])'", sugg])
1891
1892
    def test_print(self):
1893
        """print is a functions now and needs parenthesis."""
1894
        # NICE_TO_HAVE
1895
        code, new_code = 'print ""', 'print("")'
1896
        version = (3, 0)
1897
        version2 = (3, 4)
1898
        self.runs(code, up_to_version(version))
1899
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1900
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1901
        self.runs(new_code)
1902
1903
    def test_exec(self):
1904
        """exec is a functions now and needs parenthesis."""
1905
        # NICE_TO_HAVE
1906
        code, new_code = 'exec "1"', 'exec("1")'
1907
        version = (3, 0)
1908
        version2 = (3, 4)
1909
        self.runs(code, up_to_version(version))
1910
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1911
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1912
        self.runs(new_code)
1913
1914
    def test_old_comparison(self):
1915
        """<> comparison is removed, != always works."""
1916
        code = '1 {0} 2'
1917
        old, new = '<>', '!='
1918
        sugg = "'!='"
1919
        before, after = before_and_after((3, 0))
1920
        old_code, new_code = format_str(code, old, new)
1921
        self.runs(old_code, before)
1922
        self.throws(old_code, INVALIDCOMP, sugg, after, 'pypy')
1923
        self.throws(old_code, INVALIDSYNTAX, sugg, after, 'cython')
1924
        self.runs(new_code)
1925
1926
    def test_backticks(self):
1927
        """String with backticks is removed in Python3, use 'repr' instead."""
1928
        # NICE_TO_HAVE
1929
        before, after = before_and_after((3, 0))
1930
        expr = "2+3"
1931
        backtick_str, repr_str = "`%s`" % expr, "repr(%s)" % expr
1932
        self.runs(backtick_str, before)
1933
        self.throws(backtick_str, INVALIDSYNTAX, [], after)
1934
        self.runs(repr_str)
1935
1936
    def test_missing_colon(self):
1937
        """Missing colon is a classic mistake."""
1938
        # NICE_TO_HAVE
1939
        code = "if True{0}\n\tpass"
1940
        bad_code, good_code = format_str(code, "", ":")
1941
        self.throws(bad_code, INVALIDSYNTAX)
1942
        self.runs(good_code)
1943
1944
    def test_missing_colon2(self):
1945
        """Missing colon is a classic mistake."""
1946
        # NICE_TO_HAVE
1947
        code = "class MyClass{0}\n\tpass"
1948
        bad_code, good_code = format_str(code, "", ":")
1949
        self.throws(bad_code, INVALIDSYNTAX)
1950
        self.runs(good_code)
1951
1952
    def test_simple_equal(self):
1953
        """'=' for comparison is a classic mistake."""
1954
        # NICE_TO_HAVE
1955
        code = "if 2 {0} 3:\n\tpass"
1956
        bad_code, good_code = format_str(code, "=", "==")
1957
        self.throws(bad_code, INVALIDSYNTAX)
1958
        self.runs(good_code)
1959
1960
    def test_keyword_as_identifier(self):
1961
        """Using a keyword as a variable name."""
1962
        # NICE_TO_HAVE
1963
        code = '{0} = 1'
1964
        bad_code, good_code = format_str(code, "from", "from_")
1965
        self.throws(bad_code, INVALIDSYNTAX)
1966
        self.runs(good_code)
1967
1968
    def test_increment(self):
1969
        """Trying to use '++' or '--'."""
1970
        # NICE_TO_HAVE
1971
        code = 'a = 0\na{0}'
1972
        # Adding pointless suffix to avoid wrong assumptions
1973
        for end in ('', '  ', ';', ' ;'):
1974
            code2 = code + end
1975
            for op in ('-', '+'):
1976
                typo, sugg = 2 * op, op + '=1'
1977
                bad_code, good_code = format_str(code + end, typo, sugg)
1978
                self.throws(bad_code, INVALIDSYNTAX)
1979
                self.runs(good_code)
1980
1981
    def test_wrong_bool_operator(self):
1982
        """Trying to use '&&' or '||'."""
1983
        code = 'True {0} False'
1984
        for typo, sugg in (('&&', 'and'), ('||', 'or')):
1985
            bad_code, good_code = format_str(code, typo, sugg)
1986
            self.throws(bad_code, INVALIDSYNTAX, "'" + sugg + "'")
1987
            self.runs(good_code)
1988
1989
    def test_import_future_not_first(self):
1990
        """Test what happens when import from __future__ is not first."""
1991
        code = 'a = 8/7\nfrom __future__ import division'
1992
        self.throws(code, FUTUREFIRST)
1993
1994
    def test_import_future_not_def(self):
1995
        """Should be 'division'."""
1996
        code = 'from __future__ import {0}'
1997
        typo, sugg = 'divisio', 'division'
1998
        bad_code, good_code = format_str(code, typo, sugg)
1999
        self.throws(bad_code, FUTFEATNOTDEF, "'" + sugg + "'")
2000
        self.runs(good_code)
2001
2002
    def test_unqualified_exec(self):
2003
        """Exec in nested functions."""
2004
        # NICE_TO_HAVE
2005
        before, after = before_and_after((3, 0))
2006
        codes = [
2007
            "def func1():\n\tbar='1'\n\tdef func2():"
2008
            "\n\t\texec(bar)\n\tfunc2()\nfunc1()",
2009
            "def func1():\n\texec('1')\n\tdef func2():"
2010
            "\n\t\tTrue",
2011
        ]
2012
        for code in codes:
2013
            self.throws(code, UNQUALIFIED_EXEC, [], before)
2014
            self.runs(code, after)
2015
2016
    def test_import_star(self):
2017
        """'import *' in nested functions."""
2018
        # NICE_TO_HAVE
2019
        codes = [
2020
            "def func1():\n\tbar='1'\n\tdef func2():"
2021
            "\n\t\tfrom math import *\n\t\tTrue\n\tfunc2()\nfunc1()",
2022
            "def func1():\n\tfrom math import *"
2023
            "\n\tdef func2():\n\t\tTrue",
2024
        ]
2025
        with warnings.catch_warnings():
2026
            warnings.simplefilter("ignore", category=SyntaxWarning)
2027
            for code in codes:
2028
                self.throws(code, IMPORTSTAR)
2029
2030
    def test_unpack(self):
2031
        """Extended tuple unpacking does not work prior to Python 3."""
2032
        # NICE_TO_HAVE
2033
        before, after = before_and_after((3, 0))
2034
        code = 'a, *b = (1, 2, 3)'
2035
        self.throws(code, INVALIDSYNTAX, [], before)
2036
        self.runs(code, after)
2037
2038
    def test_unpack2(self):
2039
        """Unpacking in function arguments was supported up to Python 3."""
2040
        # NICE_TO_HAVE
2041
        before, after = before_and_after((3, 0))
2042
        code = 'def addpoints((x1, y1), (x2, y2)):\n\tpass'
2043
        self.runs(code, before)
2044
        self.throws(code, INVALIDSYNTAX, [], after)
2045
2046
    def test_nonlocal(self):
2047
        """nonlocal keyword is added in Python 3."""
2048
        # NICE_TO_HAVE
2049
        before, after = before_and_after((3, 0))
2050
        code = 'def func():\n\tfoo = 1\n\tdef nested():\n\t\tnonlocal foo'
2051
        self.throws(code, INVALIDSYNTAX, [], before)
2052
        self.runs(code, after)
2053
2054
    def test_nonlocal2(self):
2055
        """nonlocal must be used only when binding exists."""
2056
        # NICE_TO_HAVE
2057
        before, after = before_and_after((3, 0))
2058
        code = 'def func():\n\tdef nested():\n\t\tnonlocal foo'
2059
        self.throws(code, INVALIDSYNTAX, [], before)
2060
        self.throws(code, NOBINDING, [], after)
2061
2062
    def test_nonlocal3(self):
2063
        """nonlocal must be used only when binding to non-global exists."""
2064
        # just a way to say that this_is_a_global_list is needed in globals
2065
        name = 'this_is_a_global_list'
2066
        this_is_a_global_list
2067
        self.assertFalse(name in locals())
2068
        self.assertTrue(name in globals())
2069
        before, after = before_and_after((3, 0))
2070
        code = 'def func():\n\tdef nested():\n\t\t{0} ' + name
2071
        typo, sugg = 'nonlocal', 'global'
2072
        bad_code, good_code = format_str(code, typo, sugg)
2073
        self.runs(good_code)
2074
        self.throws(bad_code, INVALIDSYNTAX, [], before)
2075
        self.throws(bad_code, NOBINDING, "'{0} {1}'".format(sugg, name), after)
2076
2077
    def test_nonlocal4(self):
2078
        """suggest close matches to variable name."""
2079
        # NICE_TO_HAVE (needs access to variable in enclosing scope)
2080
        before, after = before_and_after((3, 0))
2081
        code = 'def func():\n\tfoo = 1\n\tdef nested():\n\t\tnonlocal {0}'
2082
        typo, sugg = 'foob', 'foo'
2083
        bad_code, good_code = format_str(code, typo, sugg)
2084
        self.throws(good_code, INVALIDSYNTAX, [], before)
2085
        self.runs(good_code, after)
2086
        self.throws(bad_code, INVALIDSYNTAX, [], before)
2087
        self.throws(bad_code, NOBINDING, [], after)
2088
2089
    def test_nonlocal_at_module_level(self):
2090
        """nonlocal must be used in function."""
2091
        version1 = (2, 7)
2092
        version2 = (3, 0)
2093
        code = 'nonlocal foo'
2094
        self.throws(code, UNEXPECTED_OEF, [], up_to_version(version1))
2095
        self.throws(code, INVALIDSYNTAX, [], (version1, version2))
2096
        self.throws(code, NONLOCALMODULE, [], from_version(version2))
2097
2098
    def test_octal_literal(self):
2099
        """Syntax for octal liberals has changed."""
2100
        # NICE_TO_HAVE
2101
        before, after = before_and_after((3, 0))
2102
        bad, good = '0720', '0o720'
2103
        self.runs(good)
2104
        self.runs(bad, before)
2105
        self.throws(bad, INVALIDTOKEN, [], after, 'cython')
2106
        self.throws(bad, INVALIDSYNTAX, [], after, 'pypy')
2107
2108
    def test_extended_unpacking(self):
2109
        """Extended iterable unpacking is added with Python 3."""
2110
        before, after = before_and_after((3, 0))
2111
        code = '(a, *rest, b) = range(5)'
2112
        self.throws(code, INVALIDSYNTAX, [], before)
2113
        self.runs(code, after)
2114
2115
    def test_ellipsis(self):
2116
        """Triple dot (...) aka Ellipsis can be used anywhere in Python 3."""
2117
        before, after = before_and_after((3, 0))
2118
        code = '...'
2119
        self.throws(code, INVALIDSYNTAX, [], before)
2120
        self.runs(code, after)
2121
2122
    def test_fstring(self):
2123
        """Fstring (see PEP 498) appeared in Python 3.6."""
2124
        # NICE_TO_HAVE
2125
        before, after = before_and_after((3, 6))
2126
        code = 'f"toto"'
2127
        self.throws(code, INVALIDSYNTAX, [], before)
2128
        self.runs(code, after)
2129
2130
2131
class MemoryErrorTests(GetSuggestionsTests):
2132
    """Class for tests related to MemoryError."""
2133
2134
    def test_out_of_memory(self):
2135
        """Test what happens in case of MemoryError."""
2136
        code = '[0] * 999999999999999'
2137
        self.throws(code, MEMORYERROR)
2138
2139
    def test_out_of_memory_range(self):
2140
        """Test what happens in case of MemoryError."""
2141
        code = '{0}(999999999999999)'
2142
        typo, sugg = 'range', 'xrange'
2143
        bad_code, good_code = format_str(code, typo, sugg)
2144
        self.runs(bad_code, interpreters='pypy')
2145
        version = (2, 7)
2146
        version2 = (3, 0)
2147
        self.throws(
2148
            bad_code,
2149
            OVERFLOWERR, "'" + sugg + "'",
2150
            up_to_version(version),
2151
            'cython')
2152
        self.throws(
2153
            bad_code,
2154
            MEMORYERROR, "'" + sugg + "'",
2155
            (version, version2),
2156
            'cython')
2157
        self.runs(good_code, up_to_version(version2), 'cython')
2158
        self.runs(bad_code, from_version(version2), 'cython')
2159
2160
2161
class ValueErrorTests(GetSuggestionsTests):
2162
    """Class for tests related to ValueError."""
2163
2164
    def test_too_many_values(self):
2165
        """Unpack 4 values in 3 variables."""
2166
        code = 'a, b, c = [1, 2, 3, 4]'
2167
        before, after = before_and_after((3, 0))
2168
        self.throws(code, EXPECTEDLENGTH, [], before, 'pypy')
2169
        self.throws(code, TOOMANYVALUES, [], after, 'pypy')
2170
        self.throws(code, TOOMANYVALUES, interpreters='cython')
2171
2172
    def test_not_enough_values(self):
2173
        """Unpack 2 values in 3 variables."""
2174
        code = 'a, b, c = [1, 2]'
2175
        before, after = before_and_after((3, 0))
2176
        self.throws(code, EXPECTEDLENGTH, [], before, 'pypy')
2177
        self.throws(code, NEEDMOREVALUES, [], after, 'pypy')
2178
        self.throws(code, NEEDMOREVALUES, interpreters='cython')
2179
2180
    def test_conversion_fails(self):
2181
        """Conversion fails."""
2182
        self.throws('int("toto")', INVALIDLITERAL)
2183
2184
    def test_math_domain(self):
2185
        """Math function used out of its domain."""
2186
        code = 'import math\nlg = math.log(-1)'
2187
        self.throws(code, MATHDOMAIN)
2188
2189
    def test_zero_len_field_in_format(self):
2190
        """Format {} is not valid before Python 2.7."""
2191
        code = '"{0}".format(0)'
2192
        old, new = '{0}', '{}'
2193
        old_code, new_code = format_str(code, old, new)
2194
        before, after = before_and_after((2, 7))
2195
        self.runs(old_code)
2196
        self.throws(new_code, ZEROLENERROR, '{0}', before)
2197
        self.runs(new_code, after)
2198
2199
    def test_timedata_does_not_match(self):
2200
        """Strptime arguments are in wrong order."""
2201
        # https://twitter.com/brandon_rhodes/status/781234730091941888
2202
        code = 'import datetime\ndatetime.datetime.strptime({0}, {1})'
2203
        timedata, timeformat = '"30 Nov 00"', '"%d %b %y"'
2204
        good_code = code.format(*(timedata, timeformat))
2205
        bad_code = code.format(*(timeformat, timedata))
2206
        sugg = 'to swap value and format parameters'
2207
        self.runs(good_code)
2208
        self.throws(bad_code, TIMEDATAFORMAT, sugg)
2209
2210
2211
class RuntimeErrorTests(GetSuggestionsTests):
2212
    """Class for tests related to RuntimeError."""
2213
2214
    def test_max_depth(self):
2215
        """Reach maximum recursion depth."""
2216
        original_limit = sys.getrecursionlimit()
2217
        sys.setrecursionlimit(200)
2218
        code = 'endlessly_recursive_func(0)'
2219
        self.throws(code, MAXRECURDEPTH,
2220
                    ["increase the limit with `sys.setrecursionlimit(limit)`"
2221
                        " (current value is 200)",
2222
                     AVOID_REC_MSG])
2223
        sys.setrecursionlimit(original_limit)
2224
2225
    def test_dict_size_changed_during_iter(self):
2226
        """Test size change during iteration (dict)."""
2227
        # NICE_TO_HAVE
2228
        code = 'd = dict(enumerate("notimportant"))' \
2229
            '\nfor e in d:\n\td.pop(e)'
2230
        self.throws(code, SIZECHANGEDDURINGITER)
2231
2232
    def test_set_changed_size_during_iter(self):
2233
        """Test size change during iteration (set)."""
2234
        # NICE_TO_HAVE
2235
        code = 's = set("notimportant")' \
2236
            '\nfor e in s:\n\ts.pop()'
2237
        self.throws(code, SIZECHANGEDDURINGITER)
2238
2239
    def test_dequeue_changed_during_iter(self):
2240
        """Test size change during iteration (dequeue)."""
2241
        # NICE_TO_HAVE
2242
        # "deque mutated during iteration"
2243
        pass
2244
2245
2246
class IOErrorTests(GetSuggestionsTests):
2247
    """Class for tests related to IOError."""
2248
2249
    def test_no_such_file(self):
2250
        """File does not exist."""
2251
        code = 'with open("doesnotexist") as f:\n\tpass'
2252
        self.throws(code, NOFILE_IO)
2253
2254
    def test_no_such_file2(self):
2255
        """File does not exist."""
2256
        code = 'os.listdir("doesnotexist")'
2257
        self.throws(code, NOFILE_OS)
2258
2259
    def test_no_such_file_user(self):
2260
        """Suggestion when one needs to expanduser."""
2261
        code = 'os.listdir("{0}")'
2262
        typo, sugg = "~", os.path.expanduser("~")
2263
        bad_code, good_code = format_str(code, typo, sugg)
2264
        self.throws(
2265
            bad_code, NOFILE_OS,
2266
            "'" + sugg + "' (calling os.path.expanduser)")
2267
        self.runs(good_code)
2268
2269
    def test_no_such_file_vars(self):
2270
        """Suggestion when one needs to expandvars."""
2271
        code = 'os.listdir("{0}")'
2272
        key = 'HOME'
2273
        typo, sugg = "$" + key, os.path.expanduser("~")
2274
        original_home = os.environ.get('HOME')
2275
        os.environ[key] = sugg
2276
        bad_code, good_code = format_str(code, typo, sugg)
2277
        self.throws(
2278
            bad_code, NOFILE_OS,
2279
            "'" + sugg + "' (calling os.path.expandvars)")
2280
        self.runs(good_code)
2281
        if original_home is None:
2282
            del os.environ[key]
2283
        else:
2284
            os.environ[key] = original_home
2285
2286
    def create_tmp_dir_with_files(self, filelist):
2287
        """Create a temporary directory with files in it."""
2288
        tmpdir = tempfile.mkdtemp()
2289
        absfiles = [os.path.join(tmpdir, f) for f in filelist]
2290
        for name in absfiles:
2291
            open(name, 'a').close()
2292
        return (tmpdir, absfiles)
2293
2294
    def test_is_dir_empty(self):
2295
        """Suggestion when file is an empty directory."""
2296
        # Create empty temp dir
2297
        tmpdir, _ = self.create_tmp_dir_with_files([])
2298
        code = 'with open("{0}") as f:\n\tpass'
2299
        bad_code, _ = format_str(code, tmpdir, "TODO")
2300
        self.throws(
2301
            bad_code, ISADIR_IO, "to add content to {0} first".format(tmpdir))
2302
        rmtree(tmpdir)
2303
2304
    def test_is_dir_small(self):
2305
        """Suggestion when file is directory with a few files."""
2306
        # Create temp dir with a few files
2307
        nb_files = 3
2308
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
2309
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
2310
        code = 'with open("{0}") as f:\n\tpass'
2311
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
2312
        self.throws(
2313
            bad_code, ISADIR_IO,
2314
            "any of the 3 files in directory ('0.txt', '1.txt', '2.txt')")
2315
        self.runs(good_code)
2316
        rmtree(tmpdir)
2317
2318
    def test_is_dir_big(self):
2319
        """Suggestion when file is directory with many files."""
2320
        # Create temp dir with many files
2321
        tmpdir = tempfile.mkdtemp()
2322
        nb_files = 30
2323
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
2324
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
2325
        code = 'with open("{0}") as f:\n\tpass'
2326
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
2327
        self.throws(
2328
            bad_code, ISADIR_IO,
2329
            "any of the 30 files in directory "
2330
            "('0.txt', '1.txt', '10.txt', '11.txt', etc)")
2331
        self.runs(good_code)
2332
        rmtree(tmpdir)
2333
2334
    def test_is_not_dir(self):
2335
        """Suggestion when file is not a directory."""
2336
        code = 'with open("{0}") as f:\n\tpass'
2337
        code = 'os.listdir("{0}")'
2338
        typo, sugg = __file__, os.path.dirname(__file__)
2339
        bad_code, good_code = format_str(code, typo, sugg)
2340
        self.throws(
2341
            bad_code, NOTADIR_OS,
2342
            "'" + sugg + "' (calling os.path.dirname)")
2343
        self.runs(good_code)
2344
2345
    def test_dir_is_not_empty(self):
2346
        """Suggestion when directory is not empty."""
2347
        # NICE_TO_HAVE
2348
        nb_files = 3
2349
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
2350
        tmpdir, _ = self.create_tmp_dir_with_files(files)
2351
        self.throws('os.rmdir("{0}")'.format(tmpdir), DIRNOTEMPTY_OS)
2352
        rmtree(tmpdir)  # this should be the suggestion
2353
2354
2355
class AnyErrorTests(GetSuggestionsTests):
2356
    """Class for tests not related to an error type in particular."""
2357
2358
    def test_wrong_except(self):
2359
        """Test where except is badly used and thus does not catch.
2360
2361
        Common mistake : "except Exc1, Exc2" doesn't catch Exc2.
2362
        Adding parenthesis solves the issue.
2363
        """
2364
        # NICE_TO_HAVE
2365
        before, after = before_and_after((3, 0))
2366
        raised_exc, other_exc = KeyError, TypeError
2367
        raised, other = raised_exc.__name__, other_exc.__name__
2368
        code = "try:\n\traise {0}()\nexcept {{0}}:\n\tpass".format(raised)
2369
        typo = "{0}, {1}".format(other, raised)
2370
        sugg = "({0})".format(typo)
2371
        bad1, bad2, good1, good2 = format_str(code, typo, other, sugg, raised)
2372
        self.throws(bad1, (raised_exc, None), [], before)
2373
        self.throws(bad1, INVALIDSYNTAX, [], after)
2374
        self.throws(bad2, (raised_exc, None))
2375
        self.runs(good1)
2376
        self.runs(good2)
2377
2378
2379
if __name__ == '__main__':
2380
    print(sys.version_info)
2381
    unittest2.main()
2382