Completed
Push — master ( d71c47...3837b7 )
by De
01:14
created

before_mid_and_after()   A

Complexity

Conditions 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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