Completed
Push — master ( 5e484f...74734e )
by De
01:15
created

FoobarClass.some_cls_method_missing_cls2()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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