Completed
Push — master ( e57624...3acadb )
by De
01:01
created

GetItemClass   A

Complexity

Total Complexity 1

Size/Duplication

Total Lines 6
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 1
c 1
b 0
f 0
dl 0
loc 6
rs 10

1 Method

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