Completed
Push — master ( ba5da2...8ab8de )
by De
01:20
created

endlessly_recursive_func()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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