Completed
Push — master ( 48c0d1...01ae8b )
by De
55s
created

AttributeErrorTests.test_hidden()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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