Completed
Push — master ( eb5e12...1ec1f9 )
by De
01:03
created

AttributeErrorTests.test_private_attr()   D

Complexity

Conditions 1

Size

Total Lines 18

Duplication

Lines 1
Ratio 5.56 %

Importance

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