Completed
Push — master ( fa297f...267ed8 )
by De
56s
created

AttributeErrorTests.test_remove_exc_attr()   A

Complexity

Conditions 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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