Completed
Push — master ( 632e45...a78d28 )
by De
56s
created

SyntaxErrorTests.test_octal_literal()   A

Complexity

Conditions 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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