Completed
Push — master ( df6d20...4bf428 )
by De
01:04
created

AttributeErrorTests   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 228
Duplicated Lines 0 %

Importance

Changes 10
Bugs 1 Features 0
Metric Value
wmc 26
c 10
b 1
f 0
dl 0
loc 228
rs 10

21 Methods

Rating   Name   Duplication   Size   Complexity  
A test_from_module() 0 11 1
A test_method() 0 7 1
A test_no_sugg() 0 3 1
A test_from_module2() 0 11 1
A test_private_attr() 0 18 1
A test_from_class() 0 7 1
A test_builtin() 0 5 1
A test_from_class2() 0 7 1
A test_wrongmethod2() 0 7 1
A test_hidden() 0 5 1
A test_wrongmethod() 0 7 1
A test_builtin2() 0 11 1
A test_nonetype() 0 8 1
A test_get_on_nondict_cont() 0 10 2
A test_removed_has_key() 0 12 1
A test_unmatched_msg() 0 5 1
A test_removed_xreadlines() 0 16 1
A test_removed_method_attributes() 0 13 2
A test_set_dict_comprehension() 0 15 3
A test_join() 0 9 1
A test_removed_function_attributes() 0 17 2
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) + details)
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_get_on_nondict_cont(self):
802
        """Method get does not exist on all containers."""
803
        code = '{0}().get(0, None)'
804
        dictcode, tuplecode, listcode, setcode = \
805
            format_str(code, 'dict', 'tuple', 'list', 'set')
806
        self.runs(dictcode)
807
        self.throws(setcode, ATTRIBUTEERROR)
808
        for bad_code in tuplecode, listcode:
809
            self.throws(bad_code, ATTRIBUTEERROR,
810
                        "'obj[key]' with a len() check or "
811
                        "try: except: KeyError or IndexError")
812
813
    def test_removed_has_key(self):
814
        """Method has_key is removed from dict."""
815
        code = 'dict().has_key(1)'
816
        new_code = '1 in dict()'
817
        version = (3, 0)
818
        self.runs(code, up_to_version(version))
819
        self.throws(
820
            code,
821
            ATTRIBUTEERROR,
822
            "'key in dict' (has_key is removed)",
823
            from_version(version))
824
        self.runs(new_code)
825
826
    def test_removed_xreadlines(self):
827
        """Method xreadlines is removed."""
828
        # NICE_TO_HAVE
829
        code = "import os\nwith open(os.path.realpath(__file__)) as f:" \
830
            "\n\tf.{0}"
831
        old, sugg1, sugg2 = 'xreadlines', 'readline', 'readlines'
832
        old_code, new_code1, new_code2 = format_str(code, old, sugg1, sugg2)
833
        version = (3, 0)
834
        self.runs(old_code, up_to_version(version))
835
        self.throws(
836
            old_code,
837
            ATTRIBUTEERROR,
838
            ["'" + sugg1 + "'", "'" + sugg2 + "'", "'writelines'"],
839
            from_version(version))
840
        self.runs(new_code1)
841
        self.runs(new_code2)
842
843
    def test_removed_function_attributes(self):
844
        """Some functions attributes are removed."""
845
        # NICE_TO_HAVE
846
        version = (3, 0)
847
        code = func_gen() + 'some_func.{0}'
848
        attributes = [('func_name', '__name__', []),
849
                      ('func_doc', '__doc__', []),
850
                      ('func_defaults', '__defaults__', ["'__defaults__'"]),
851
                      ('func_dict', '__dict__', []),
852
                      ('func_closure', '__closure__', []),
853
                      ('func_globals', '__globals__', []),
854
                      ('func_code', '__code__', [])]
855
        for (old_att, new_att, sugg) in attributes:
856
            old_code, new_code = format_str(code, old_att, new_att)
857
            self.runs(old_code, up_to_version(version))
858
            self.throws(old_code, ATTRIBUTEERROR, sugg, from_version(version))
859
            self.runs(new_code)
860
861
    def test_removed_method_attributes(self):
862
        """Some methods attributes are removed."""
863
        # NICE_TO_HAVE
864
        version = (3, 0)
865
        code = 'FoobarClass().some_method.{0}'
866
        attributes = [('im_func', '__func__', []),
867
                      ('im_self', '__self__', []),
868
                      ('im_class', '__self__.__class__', ["'__class__'"])]
869
        for (old_att, new_att, sugg) in attributes:
870
            old_code, new_code = format_str(code, old_att, new_att)
871
            self.runs(old_code, up_to_version(version))
872
            self.throws(old_code, ATTRIBUTEERROR, sugg, from_version(version))
873
            self.runs(new_code)
874
875
    def test_join(self):
876
        """Test what happens when join is used incorrectly.
877
878
        This can be frustrating to call join on an iterable instead of a
879
        string, a suggestion could be nice.
880
        """
881
        # NICE_TO_HAVE
882
        code = "['a', 'b'].join('-')"
883
        self.throws(code, ATTRIBUTEERROR)
884
885
    def test_set_dict_comprehension(self):
886
        """{} creates a dict and not an empty set leading to errors."""
887
        # NICE_TO_HAVE
888
        version = (2, 7)
889
        for method in set(dir(set)) - set(dir(dict)):
890
            if not method.startswith('__'):  # boring suggestions
891
                code = "a = {0}\na." + method
892
                typo, dict1, dict2, sugg, set1 = format_str(
893
                    code, "{}", "dict()", "{0: 0}", "set()", "{0}")
894
                self.throws(typo, ATTRIBUTEERROR)
895
                self.throws(dict1, ATTRIBUTEERROR)
896
                self.throws(dict2, ATTRIBUTEERROR)
897
                self.runs(sugg)
898
                self.throws(set1, INVALIDSYNTAX, [], up_to_version(version))
899
                self.runs(set1, from_version(version))
900
901
    def test_unmatched_msg(self):
902
        """Test that arbitrary strings are supported."""
903
        self.throws(
904
            'raise AttributeError("unmatched ATTRIBUTEERROR")',
905
            UNKNOWN_ATTRIBUTEERROR)
906
907
    # TODO: Add sugg for situation where self/cls is the missing parameter
908
909
910
class TypeErrorTests(GetSuggestionsTests):
911
    """Class for tests related to TypeError."""
912
913
    def test_unhashable(self):
914
        """Test that other errors do not crash."""
915
        self.throws('dict()[list()] = 1', UNHASHABLE)
916
917
    def test_not_sub(self):
918
        """Should be function call, not [] operator."""
919
        typo, sugg = '[2]', '(2)'
920
        code = func_gen(param='a') + 'some_func{0}'
921
        bad_code, good_code = format_str(code, typo, sugg)
922
        self.throws(bad_code, UNSUBSCRIBTABLE, "'function(value)'")
923
        self.runs(good_code)
924
925
    def test_method_called_on_class(self):
926
        """Test where a method is called on a class and not an instance.
927
928
        Forgetting parenthesis makes the difference between using an
929
        instance and using a type.
930
        """
931
        # NICE_TO_HAVE
932
        wrong_type = (DESCREXPECT, MUSTCALLWITHINST, NBARGERROR)
933
        not_iterable = (ARGNOTITERABLE, ARGNOTITERABLE, ARGNOTITERABLE)
934
        version = (3, 0)
935
        for code, (err_cy, err_pyp, err_pyp3) in [
936
                ('set{0}.add(0)', wrong_type),
937
                ('list{0}.append(0)', wrong_type),
938
                ('0 in list{0}', not_iterable)]:
939
            bad_code, good_code = format_str(code, '', '()')
940
            self.runs(good_code)
941
            self.throws(bad_code, err_cy, [], ALL_VERSIONS, 'cython')
942
            self.throws(bad_code, err_pyp, [], up_to_version(version), 'pypy')
943
            self.throws(bad_code, err_pyp3, [], from_version(version), 'pypy')
944
945
    def test_set_operations(self):
946
        """+, +=, etc doesn't work on sets. A suggestion would be nice."""
947
        # NICE_TO_HAVE
948
        typo1 = 'set() + set()'
949
        typo2 = 's = set()\ns += set()'
950
        code1 = 'set() | set()'
951
        code2 = 'set().union(set())'
952
        code3 = 'set().update(set())'
953
        self.throws(typo1, UNSUPPORTEDOPERAND)
954
        self.throws(typo2, UNSUPPORTEDOPERAND)
955
        self.runs(code1)
956
        self.runs(code2)
957
        self.runs(code3)
958
959
    def test_dict_operations(self):
960
        """+, +=, etc doesn't work on dicts. A suggestion would be nice."""
961
        # NICE_TO_HAVE
962
        typo1 = 'dict() + dict()'
963
        typo2 = 'd = dict()\nd += dict()'
964
        typo3 = 'dict() & dict()'
965
        self.throws(typo1, UNSUPPORTEDOPERAND)
966
        self.throws(typo2, UNSUPPORTEDOPERAND)
967
        self.throws(typo3, UNSUPPORTEDOPERAND)
968
        code1 = 'dict().update(dict())'
969
        self.runs(code1)
970
971
    def test_len_on_iterable(self):
972
        """len() can't be called on iterable (weird but understandable)."""
973
        code = 'len(my_generator())'
974
        sugg = 'len(list(my_generator()))'
975
        self.throws(code, OBJECTHASNOFUNC)
976
        self.runs(sugg)
977
978
    def test_nb_args(self):
979
        """Should have 1 arg."""
980
        typo, sugg = '1, 2', '1'
981
        code = func_gen(param='a', args='{0}')
982
        bad_code, good_code = format_str(code, typo, sugg)
983
        self.throws(bad_code, NBARGERROR)
984
        self.runs(good_code)
985
986
    def test_nb_args1(self):
987
        """Should have 0 args."""
988
        typo, sugg = '1', ''
989
        code = func_gen(param='', args='{0}')
990
        bad_code, good_code = format_str(code, typo, sugg)
991
        self.throws(bad_code, NBARGERROR)
992
        self.runs(good_code)
993
994
    def test_nb_args2(self):
995
        """Should have 1 arg."""
996
        typo, sugg = '', '1'
997
        version = (3, 3)
998
        code = func_gen(param='a', args='{0}')
999
        bad_code, good_code = format_str(code, typo, sugg)
1000
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1001
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1002
        self.runs(good_code)
1003
1004
    def test_nb_args3(self):
1005
        """Should have 3 args."""
1006
        typo, sugg = '1', '1, 2, 3'
1007
        version = (3, 3)
1008
        code = func_gen(param='so, much, args', args='{0}')
1009
        bad_code, good_code = format_str(code, typo, sugg)
1010
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1011
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1012
        self.runs(good_code)
1013
1014
    def test_nb_args4(self):
1015
        """Should have 3 args."""
1016
        typo, sugg = '', '1, 2, 3'
1017
        version = (3, 3)
1018
        code = func_gen(param='so, much, args', args='{0}')
1019
        bad_code, good_code = format_str(code, typo, sugg)
1020
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1021
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1022
        self.runs(good_code)
1023
1024
    def test_nb_args5(self):
1025
        """Should have 3 args."""
1026
        typo, sugg = '1, 2', '1, 2, 3'
1027
        version = (3, 3)
1028
        code = func_gen(param='so, much, args', args='{0}')
1029
        bad_code, good_code = format_str(code, typo, sugg)
1030
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1031
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1032
        self.runs(good_code)
1033
1034
    def test_nb_args6(self):
1035
        """Should provide more args."""
1036
        # Amusing message: 'func() takes exactly 2 arguments (2 given)'
1037
        version = (3, 3)
1038
        code = func_gen(param='a, b, c=3', args='{0}')
1039
        bad_code, good_code1, good_code2 = format_str(
1040
            code,
1041
            'b=2, c=3',
1042
            'a=1, b=2, c=3',
1043
            '1, b=2, c=3')
1044
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
1045
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
1046
        self.runs(good_code1)
1047
        self.runs(good_code2)
1048
1049
    def test_nb_arg_missing_self(self):
1050
        """Arg 'self' is missing."""
1051
        # NICE_TO_HAVE
1052
        obj = 'FoobarClass()'
1053
        self.throws(obj + '.some_method_missing_self_arg()', NBARGERROR)
1054
        self.throws(obj + '.some_method_missing_self_arg2(42)', NBARGERROR)
1055
        self.runs(obj + '.some_method()')
1056
        self.runs(obj + '.some_method2(42)')
1057
1058
    def test_nb_arg_missing_cls(self):
1059
        """Arg 'cls' is missing."""
1060
        # NICE_TO_HAVE
1061
        for obj in ('FoobarClass()', 'FoobarClass'):
1062
            self.throws(obj + '.some_cls_method_missing_cls()', NBARGERROR)
1063
            self.throws(obj + '.some_cls_method_missing_cls2(42)', NBARGERROR)
1064
            self.runs(obj + '.this_is_cls_mthd()')
1065
1066
    def test_keyword_args(self):
1067
        """Should be param 'babar' not 'a' but it's hard to guess."""
1068
        typo, sugg = 'a', 'babar'
1069
        code = func_gen(param=sugg, args='{0}=1')
1070
        bad_code, good_code = format_str(code, typo, sugg)
1071
        self.throws(bad_code, UNEXPECTEDKWARG)
1072
        self.runs(good_code)
1073
1074
    def test_keyword_args2(self):
1075
        """Should be param 'abcdef' not 'abcdf'."""
1076
        typo, sugg = 'abcdf', 'abcdef'
1077
        code = func_gen(param=sugg, args='{0}=1')
1078
        bad_code, good_code = format_str(code, typo, sugg)
1079
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1080
        self.runs(good_code)
1081
1082
    def test_keyword_arg_method(self):
1083
        """Should be the same as previous test but on a method."""
1084
        code = 'class MyClass:\n\tdef func(self, a):' \
1085
               '\n\t\tpass\nMyClass().func({0}=1)'
1086
        bad_code, good_code = format_str(code, 'babar', 'a')
1087
        self.throws(bad_code, UNEXPECTEDKWARG)
1088
        self.runs(good_code)
1089
1090
    def test_keyword_arg_method2(self):
1091
        """Should be the same as previous test but on a method."""
1092
        # NICE_TO_HAVE
1093
        code = 'class MyClass:\n\tdef func(self, abcdef):' \
1094
               '\n\t\tpass\nMyClass().func({0}=1)'
1095
        bad_code, good_code = format_str(code, 'abcdf', 'abcdef')
1096
        self.throws(bad_code, UNEXPECTEDKWARG)
1097
        self.runs(good_code)
1098
1099
    def test_keyword_arg_class_method(self):
1100
        """Should be the same as previous test but on a class method."""
1101
        code = 'class MyClass:\n\t@classmethod\n\tdef func(cls, a):' \
1102
               '\n\t\tpass\nMyClass.func({0}=1)'
1103
        bad_code, good_code = format_str(code, 'babar', 'a')
1104
        self.throws(bad_code, UNEXPECTEDKWARG)
1105
        self.runs(good_code)
1106
1107
    def test_keyword_arg_class_method2(self):
1108
        """Should be the same as previous test but on a class method."""
1109
        # NICE_TO_HAVE
1110
        code = 'class MyClass:\n\t@classmethod\n\tdef func(cls, abcdef):' \
1111
               '\n\t\tpass\nMyClass.func({0}=1)'
1112
        bad_code, good_code = format_str(code, 'abcdf', 'abcdef')
1113
        self.throws(bad_code, UNEXPECTEDKWARG)
1114
        self.runs(good_code)
1115
1116
    def test_keyword_builtin(self):
1117
        """A few builtins (like int()) have a different error message."""
1118
        # NICE_TO_HAVE
1119
        # 'max', 'input', 'len', 'abs', 'all', etc have a specific error
1120
        # message and are not relevant here
1121
        for builtin in ['int', 'float', 'bool', 'complex']:
1122
            code = builtin + '(this_doesnt_exist=2)'
1123
            self.throws(code, UNEXPECTEDKWARG2, [], ALL_VERSIONS, 'cython')
1124
            self.throws(code, UNEXPECTEDKWARG, [], ALL_VERSIONS, 'pypy')
1125
1126
    def test_keyword_builtin_print(self):
1127
        """Builtin "print" has a different error message."""
1128
        # It would be NICE_TO_HAVE suggestions on keyword arguments
1129
        v3 = (3, 0)
1130
        code = "c = 'string'\nb = print(c, end_='toto')"
1131
        self.throws(code, INVALIDSYNTAX, [], up_to_version(v3))
1132
        self.throws(code, UNEXPECTEDKWARG2, [], from_version(v3), 'cython')
1133
        self.throws(code, UNEXPECTEDKWARG3, [], from_version(v3), 'pypy')
1134
1135
    def test_no_implicit_str_conv(self):
1136
        """Trying to concatenate a non-string value to a string."""
1137
        # NICE_TO_HAVE
1138
        code = '{0} + " things"'
1139
        typo, sugg = '12', 'str(12)'
1140
        bad_code, good_code = format_str(code, typo, sugg)
1141
        self.throws(bad_code, UNSUPPORTEDOPERAND)
1142
        self.runs(good_code)
1143
1144
    def test_no_implicit_str_conv2(self):
1145
        """Trying to concatenate a non-string value to a string."""
1146
        # NICE_TO_HAVE
1147
        code = '"things " + {0}'
1148
        typo, sugg = '12', 'str(12)'
1149
        bad_code, good_code = format_str(code, typo, sugg)
1150
        version = (3, 0)
1151
        version2 = (3, 6)
1152
        self.throws(
1153
            bad_code, CANNOTCONCAT, [], up_to_version(version), 'cython')
1154
        self.throws(
1155
            bad_code, CANTCONVERT, [], (version, version2), 'cython')
1156
        self.throws(
1157
            bad_code, MUSTBETYPENOTTYPE, [], from_version(version2), 'cython')
1158
        self.throws(
1159
            bad_code, UNSUPPORTEDOPERAND, [], ALL_VERSIONS, 'pypy')
1160
        self.runs(good_code)
1161
1162
    def test_assignment_to_range(self):
1163
        """Trying to assign to range works on list, not on range."""
1164
        # NICE_TO_HAVE
1165
        code = '{0}[2] = 1'
1166
        typo, sugg = 'range(4)', 'list(range(4))'
1167
        version = (3, 0)
1168
        bad_code, good_code = format_str(code, typo, sugg)
1169
        self.runs(bad_code, up_to_version(version))
1170
        self.throws(bad_code, OBJECTDOESNOTSUPPORT, [], from_version(version))
1171
        self.runs(good_code)
1172
1173
    def test_not_callable(self):
1174
        """Sometimes, one uses parenthesis instead of brackets."""
1175
        typo, getitem = '(0)', '[0]'
1176
        for ex, sugg in {
1177
            '[0]': "'list[value]'",
1178
            '{0: 0}': "'dict[value]'",
1179
            '"a"': "'str[value]'",
1180
        }.items():
1181
            self.throws(ex + typo, NOTCALLABLE, sugg)
1182
            self.runs(ex + getitem)
1183
        for ex in ['1', 'set()']:
1184
            self.throws(ex + typo, NOTCALLABLE)
1185
1186
    def test_unmatched_msg(self):
1187
        """Test that arbitrary strings are supported."""
1188
        self.throws(
1189
            'raise TypeError("unmatched TYPEERROR")',
1190
            UNKNOWN_TYPEERROR)
1191
1192
1193
class ImportErrorTests(GetSuggestionsTests):
1194
    """Class for tests related to ImportError."""
1195
1196
    def test_no_module_no_sugg(self):
1197
        """No suggestion."""
1198
        self.throws('import fqslkdfjslkqdjfqsd', NOMODULE)
1199
1200
    def test_no_module(self):
1201
        """Should be 'math'."""
1202
        code = 'import {0}'
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_module2(self):
1210
        """Should be 'math'."""
1211
        code = 'from {0} import pi'
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_no_module3(self):
1219
        """Should be 'math'."""
1220
        code = 'import {0} as my_imported_math'
1221
        typo, sugg = 'maths', 'math'
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_module4(self):
1228
        """Should be 'math'."""
1229
        code = 'from {0} import pi as three_something'
1230
        typo, sugg = 'maths', 'math'
1231
        self.assertTrue(sugg in STAND_MODULES)
1232
        bad_code, good_code = format_str(code, typo, sugg)
1233
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1234
        self.runs(good_code)
1235
1236
    def test_no_module5(self):
1237
        """Should be 'math'."""
1238
        code = '__import__("{0}")'
1239
        typo, sugg = 'maths', 'math'
1240
        self.assertTrue(sugg in STAND_MODULES)
1241
        bad_code, good_code = format_str(code, typo, sugg)
1242
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1243
        self.runs(good_code)
1244
1245
    def test_import_future_nomodule(self):
1246
        """Should be '__future__'."""
1247
        code = 'import {0}'
1248
        typo, sugg = '__future_', '__future__'
1249
        self.assertTrue(sugg in STAND_MODULES)
1250
        bad_code, good_code = format_str(code, typo, sugg)
1251
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1252
        self.runs(good_code)
1253
1254
    def test_no_name_no_sugg(self):
1255
        """No suggestion."""
1256
        self.throws('from math import fsfsdfdjlkf', CANNOTIMPORT)
1257
1258
    def test_wrong_import(self):
1259
        """Should be 'math'."""
1260
        code = 'from {0} import pi'
1261
        typo, sugg = 'itertools', 'math'
1262
        self.assertTrue(sugg in STAND_MODULES)
1263
        bad_code, good_code = format_str(code, typo, sugg)
1264
        self.throws(bad_code, CANNOTIMPORT, "'" + good_code + "'")
1265
        self.runs(good_code)
1266
1267
    def test_typo_in_method(self):
1268
        """Should be 'pi'."""
1269
        code = 'from math import {0}'
1270
        typo, sugg = 'pie', 'pi'
1271
        bad_code, good_code = format_str(code, typo, sugg)
1272
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1273
        self.runs(good_code)
1274
1275
    def test_typo_in_method2(self):
1276
        """Should be 'pi'."""
1277
        code = 'from math import e, {0}, log'
1278
        typo, sugg = 'pie', 'pi'
1279
        bad_code, good_code = format_str(code, typo, sugg)
1280
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1281
        self.runs(good_code)
1282
1283
    def test_typo_in_method3(self):
1284
        """Should be 'pi'."""
1285
        code = 'from math import {0} as three_something'
1286
        typo, sugg = 'pie', 'pi'
1287
        bad_code, good_code = format_str(code, typo, sugg)
1288
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1289
        self.runs(good_code)
1290
1291
    def test_unmatched_msg(self):
1292
        """Test that arbitrary strings are supported."""
1293
        self.throws(
1294
            'raise ImportError("unmatched IMPORTERROR")',
1295
            UNKNOWN_IMPORTERROR)
1296
1297
    def test_module_removed(self):
1298
        """Sometimes, modules are deleted/moved/renamed."""
1299
        # NICE_TO_HAVE
1300
        version1 = (2, 7)  # result for 2.6 seems to vary
1301
        version2 = (3, 0)
1302
        code = 'import {0}'
1303
        lower, upper = format_str(code, 'tkinter', 'Tkinter')
1304
        self.throws(lower, NOMODULE, [], (version1, version2))
1305
        self.throws(upper, NOMODULE, [], from_version(version2))
1306
1307
1308
class LookupErrorTests(GetSuggestionsTests):
1309
    """Class for tests related to LookupError."""
1310
1311
1312
class KeyErrorTests(LookupErrorTests):
1313
    """Class for tests related to KeyError."""
1314
1315
    def test_no_sugg(self):
1316
        """No suggestion."""
1317
        self.throws('dict()["ffdsqmjklfqsd"]', KEYERROR)
1318
1319
1320
class IndexErrorTests(LookupErrorTests):
1321
    """Class for tests related to IndexError."""
1322
1323
    def test_no_sugg(self):
1324
        """No suggestion."""
1325
        self.throws('list()[2]', OUTOFRANGE)
1326
1327
1328
class SyntaxErrorTests(GetSuggestionsTests):
1329
    """Class for tests related to SyntaxError."""
1330
1331
    def test_no_error(self):
1332
        """No error."""
1333
        self.runs("1 + 2 == 2")
1334
1335
    def test_yield_return_out_of_func(self):
1336
        """yield/return needs to be in functions."""
1337
        sugg = "to indent it"
1338
        self.throws("yield 1", OUTSIDEFUNC, sugg)
1339
        self.throws("return 1", OUTSIDEFUNC, ["'sys.exit([arg])'", sugg])
1340
1341
    def test_print(self):
1342
        """print is a functions now and needs parenthesis."""
1343
        # NICE_TO_HAVE
1344
        code, new_code = 'print ""', 'print("")'
1345
        version = (3, 0)
1346
        version2 = (3, 4)
1347
        self.runs(code, up_to_version(version))
1348
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1349
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1350
        self.runs(new_code)
1351
1352
    def test_exec(self):
1353
        """exec is a functions now and needs parenthesis."""
1354
        # NICE_TO_HAVE
1355
        code, new_code = 'exec "1"', 'exec("1")'
1356
        version = (3, 0)
1357
        version2 = (3, 4)
1358
        self.runs(code, up_to_version(version))
1359
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1360
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1361
        self.runs(new_code)
1362
1363
    def test_old_comparison(self):
1364
        """<> comparison is removed, != always works."""
1365
        code = '1 {0} 2'
1366
        old, new = '<>', '!='
1367
        version = (3, 0)
1368
        old_code, new_code = format_str(code, old, new)
1369
        self.runs(old_code, up_to_version(version))
1370
        self.throws(
1371
            old_code,
1372
            INVALIDCOMP,
1373
            "'!='",
1374
            from_version(version),
1375
            'pypy')
1376
        self.throws(
1377
            old_code,
1378
            INVALIDSYNTAX,
1379
            "'!='",
1380
            from_version(version),
1381
            'cython')
1382
        self.runs(new_code)
1383
1384
    def test_missing_colon(self):
1385
        """Missing colon is a classic mistake."""
1386
        # NICE_TO_HAVE
1387
        code = "if True{0}\n\tpass"
1388
        bad_code, good_code = format_str(code, "", ":")
1389
        self.throws(bad_code, INVALIDSYNTAX)
1390
        self.runs(good_code)
1391
1392
    def test_missing_colon2(self):
1393
        """Missing colon is a classic mistake."""
1394
        # NICE_TO_HAVE
1395
        code = "class MyClass{0}\n\tpass"
1396
        bad_code, good_code = format_str(code, "", ":")
1397
        self.throws(bad_code, INVALIDSYNTAX)
1398
        self.runs(good_code)
1399
1400
    def test_simple_equal(self):
1401
        """'=' for comparison is a classic mistake."""
1402
        # NICE_TO_HAVE
1403
        code = "if 2 {0} 3:\n\tpass"
1404
        bad_code, good_code = format_str(code, "=", "==")
1405
        self.throws(bad_code, INVALIDSYNTAX)
1406
        self.runs(good_code)
1407
1408
    def test_keyword_as_identifier(self):
1409
        """Using a keyword as a variable name."""
1410
        # NICE_TO_HAVE
1411
        code = '{0} = 1'
1412
        bad_code, good_code = format_str(code, "from", "from_")
1413
        self.throws(bad_code, INVALIDSYNTAX)
1414
        self.runs(good_code)
1415
1416
    def test_increment(self):
1417
        """Trying to use '++' or '--'."""
1418
        # NICE_TO_HAVE
1419
        code = 'a = 0\na{0}'
1420
        for op in ('-', '+'):
1421
            typo, sugg = 2 * op, op + '=1'
1422
            bad_code, good_code = format_str(code, typo, sugg)
1423
            self.throws(bad_code, INVALIDSYNTAX)
1424
            self.runs(good_code)
1425
1426
    def test_wrong_bool_operator(self):
1427
        """Trying to use '&&' or '||'."""
1428
        # NICE_TO_HAVE
1429
        code = 'True {0} False'
1430
        for typo, sugg in (('&&', 'and'), ('||', 'or')):
1431
            bad_code, good_code = format_str(code, typo, sugg)
1432
            self.throws(bad_code, INVALIDSYNTAX)
1433
            self.runs(good_code)
1434
1435
    def test_import_future_not_first(self):
1436
        """Test what happens when import from __future__ is not first."""
1437
        code = 'a = 8/7\nfrom __future__ import division'
1438
        self.throws(code, FUTUREFIRST)
1439
1440
    def test_import_future_not_def(self):
1441
        """Should be 'division'."""
1442
        code = 'from __future__ import {0}'
1443
        typo, sugg = 'divisio', 'division'
1444
        bad_code, good_code = format_str(code, typo, sugg)
1445
        self.throws(bad_code, FUTFEATNOTDEF, "'" + sugg + "'")
1446
        self.runs(good_code)
1447
1448
    def test_unqualified_exec(self):
1449
        """Exec in nested functions."""
1450
        # NICE_TO_HAVE
1451
        version = (3, 0)
1452
        codes = [
1453
            "def func1():\n\tbar='1'\n\tdef func2():"
1454
            "\n\t\texec(bar)\n\tfunc2()\nfunc1()",
1455
            "def func1():\n\texec('1')\n\tdef func2():"
1456
            "\n\t\tTrue",
1457
        ]
1458
        for code in codes:
1459
            self.throws(code, UNQUALIFIED_EXEC, [], up_to_version(version))
1460
            self.runs(code, from_version(version))
1461
1462
    def test_import_star(self):
1463
        """'import *' in nested functions."""
1464
        # NICE_TO_HAVE
1465
        codes = [
1466
            "def func1():\n\tbar='1'\n\tdef func2():"
1467
            "\n\t\tfrom math import *\n\t\tTrue\n\tfunc2()\nfunc1()",
1468
            "def func1():\n\tfrom math import *"
1469
            "\n\tdef func2():\n\t\tTrue",
1470
        ]
1471
        with warnings.catch_warnings():
1472
            warnings.simplefilter("ignore", category=SyntaxWarning)
1473
            for code in codes:
1474
                self.throws(code, IMPORTSTAR, [])
1475
1476
    def test_unpack(self):
1477
        """Extended tuple unpacking does not work prior to Python 3."""
1478
        # NICE_TO_HAVE
1479
        version = (3, 0)
1480
        code = 'a, *b = (1, 2, 3)'
1481
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1482
        self.runs(code, from_version(version))
1483
1484
    def test_unpack2(self):
1485
        """Unpacking in function arguments was supported up to Python 3."""
1486
        # NICE_TO_HAVE
1487
        version = (3, 0)
1488
        code = 'def addpoints((x1, y1), (x2, y2)):\n\tpass'
1489
        self.runs(code, up_to_version(version))
1490
        self.throws(code, INVALIDSYNTAX, [], from_version(version))
1491
1492
    def test_nonlocal(self):
1493
        """nonlocal keyword is added in Python 3."""
1494
        # NICE_TO_HAVE
1495
        version = (3, 0)
1496
        code = 'def func():\n\tfoo = 1\n\tdef nested():\n\t\tnonlocal foo'
1497
        self.runs(code, from_version(version))
1498
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1499
1500
    def test_nonlocal2(self):
1501
        """nonlocal must be used only when binding exists."""
1502
        # NICE_TO_HAVE
1503
        version = (3, 0)
1504
        code = 'def func():\n\tdef nested():\n\t\tnonlocal foo'
1505
        self.throws(code, NOBINDING, [], from_version(version))
1506
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1507
1508
    def test_nonlocal3(self):
1509
        """nonlocal must be used only when binding to non-global exists."""
1510
        # NICE_TO_HAVE
1511
        version = (3, 0)
1512
        code = 'foo = 1\ndef func():\n\tdef nested():\n\t\tnonlocal foo'
1513
        self.throws(code, NOBINDING, [], from_version(version))
1514
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1515
1516
    def test_octal_literal(self):
1517
        """Syntax for octal liberals has changed."""
1518
        # NICE_TO_HAVE
1519
        version = (3, 0)
1520
        bad, good = '0720', '0o720'
1521
        self.runs(good)
1522
        self.runs(bad, up_to_version(version))
1523
        self.throws(bad, INVALIDTOKEN, [], from_version(version), 'cython')
1524
        self.throws(bad, INVALIDSYNTAX, [], from_version(version), 'pypy')
1525
1526
    def test_extended_unpacking(self):
1527
        """Extended iterable unpacking is added with Python 3."""
1528
        version = (3, 0)
1529
        code = '(a, *rest, b) = range(5)'
1530
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1531
        self.runs(code, from_version(version))
1532
1533
    def test_ellipsis(self):
1534
        """Triple dot (...) aka Ellipsis can be used anywhere in Python 3."""
1535
        version = (3, 0)
1536
        code = '...'
1537
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1538
        self.runs(code, from_version(version))
1539
1540
1541
class MemoryErrorTests(GetSuggestionsTests):
1542
    """Class for tests related to MemoryError."""
1543
1544
    def test_out_of_memory(self):
1545
        """Test what happens in case of MemoryError."""
1546
        code = '[0] * 999999999999999'
1547
        self.throws(code, MEMORYERROR)
1548
1549
    def test_out_of_memory_range(self):
1550
        """Test what happens in case of MemoryError."""
1551
        code = '{0}(999999999999999)'
1552
        typo, sugg = 'range', 'xrange'
1553
        bad_code, good_code = format_str(code, typo, sugg)
1554
        self.runs(bad_code, ALL_VERSIONS, 'pypy')
1555
        version = (2, 7)
1556
        version2 = (3, 0)
1557
        self.throws(
1558
            bad_code,
1559
            OVERFLOWERR, "'" + sugg + "'",
1560
            up_to_version(version),
1561
            'cython')
1562
        self.throws(
1563
            bad_code,
1564
            MEMORYERROR, "'" + sugg + "'",
1565
            (version, version2),
1566
            'cython')
1567
        self.runs(good_code, up_to_version(version2), 'cython')
1568
        self.runs(bad_code, from_version(version2), 'cython')
1569
1570
1571
class ValueErrorTests(GetSuggestionsTests):
1572
    """Class for tests related to ValueError."""
1573
1574
    def test_too_many_values(self):
1575
        """Unpack 4 values in 3 variables."""
1576
        code = 'a, b, c = [1, 2, 3, 4]'
1577
        version = (3, 0)
1578
        self.throws(code, EXPECTEDLENGTH, [], up_to_version(version), 'pypy')
1579
        self.throws(code, TOOMANYVALUES, [], from_version(version), 'pypy')
1580
        self.throws(code, TOOMANYVALUES, [], ALL_VERSIONS, 'cython')
1581
1582
    def test_not_enough_values(self):
1583
        """Unpack 2 values in 3 variables."""
1584
        code = 'a, b, c = [1, 2]'
1585
        version = (3, 0)
1586
        self.throws(code, EXPECTEDLENGTH, [], up_to_version(version), 'pypy')
1587
        self.throws(code, NEEDMOREVALUES, [], from_version(version), 'pypy')
1588
        self.throws(code, NEEDMOREVALUES, [], ALL_VERSIONS, 'cython')
1589
1590
    def test_conversion_fails(self):
1591
        """Conversion fails."""
1592
        self.throws('int("toto")', INVALIDLITERAL)
1593
1594
    def test_math_domain(self):
1595
        """Math function used out of its domain."""
1596
        code = 'import math\nlg = math.log(-1)'
1597
        self.throws(code, MATHDOMAIN)
1598
1599
    def test_zero_len_field_in_format(self):
1600
        """Format {} is not valid before Python 2.7."""
1601
        code = '"{0}".format(0)'
1602
        old, new = '{0}', '{}'
1603
        old_code, new_code = format_str(code, old, new)
1604
        version = (2, 7)
1605
        self.runs(old_code)
1606
        self.throws(new_code, ZEROLENERROR, '{0}', up_to_version(version))
1607
        self.runs(new_code, from_version(version))
1608
1609
    def test_timedata_does_not_match(self):
1610
        """Strptime arguments are in wrong order."""
1611
        code = 'import datetime\ndatetime.datetime.strptime({0}, {1})'
1612
        timedata, timeformat = '"30 Nov 00"', '"%d %b %y"'
1613
        good_code = code.format(*(timedata, timeformat))
1614
        bad_code = code.format(*(timeformat, timedata))
1615
        self.runs(good_code)
1616
        self.throws(bad_code, TIMEDATAFORMAT,
1617
                    ['to swap value and format parameters'])
1618
1619
1620
class IOErrorTests(GetSuggestionsTests):
1621
    """Class for tests related to IOError."""
1622
1623
    def test_no_such_file(self):
1624
        """File does not exist."""
1625
        code = 'with open("doesnotexist") as f:\n\tpass'
1626
        self.throws(code, NOFILE_IO)
1627
1628
    def test_no_such_file2(self):
1629
        """File does not exist."""
1630
        code = 'os.listdir("doesnotexist")'
1631
        self.throws(code, NOFILE_OS)
1632
1633
    def test_no_such_file_user(self):
1634
        """Suggestion when one needs to expanduser."""
1635
        code = 'os.listdir("{0}")'
1636
        typo, sugg = "~", os.path.expanduser("~")
1637
        bad_code, good_code = format_str(code, typo, sugg)
1638
        self.throws(
1639
            bad_code, NOFILE_OS,
1640
            "'" + sugg + "' (calling os.path.expanduser)")
1641
        self.runs(good_code)
1642
1643
    def test_no_such_file_vars(self):
1644
        """Suggestion when one needs to expandvars."""
1645
        code = 'os.listdir("{0}")'
1646
        key = 'HOME'
1647
        typo, sugg = "$" + key, os.path.expanduser("~")
1648
        original_home = os.environ.get('HOME')
1649
        os.environ[key] = sugg
1650
        bad_code, good_code = format_str(code, typo, sugg)
1651
        self.throws(
1652
            bad_code, NOFILE_OS,
1653
            "'" + sugg + "' (calling os.path.expandvars)")
1654
        self.runs(good_code)
1655
        if original_home is None:
1656
            del os.environ[key]
1657
        else:
1658
            os.environ[key] = original_home
1659
1660
    def create_tmp_dir_with_files(self, filelist):
1661
        """Create a temporary directory with files in it."""
1662
        tmpdir = tempfile.mkdtemp()
1663
        absfiles = [os.path.join(tmpdir, f) for f in filelist]
1664
        for name in absfiles:
1665
            open(name, 'a').close()
1666
        return (tmpdir, absfiles)
1667
1668
    def test_is_dir_empty(self):
1669
        """Suggestion when file is an empty directory."""
1670
        # Create empty temp dir
1671
        tmpdir, _ = self.create_tmp_dir_with_files([])
1672
        code = 'with open("{0}") as f:\n\tpass'
1673
        bad_code, _ = format_str(code, tmpdir, "TODO")
1674
        self.throws(
1675
            bad_code, ISADIR_IO, "to add content to {0} first".format(tmpdir))
1676
        rmtree(tmpdir)
1677
1678
    def test_is_dir_small(self):
1679
        """Suggestion when file is directory with a few files."""
1680
        # Create temp dir with a few files
1681
        nb_files = 3
1682
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
1683
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
1684
        code = 'with open("{0}") as f:\n\tpass'
1685
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
1686
        self.throws(
1687
            bad_code, ISADIR_IO,
1688
            "any of the 3 files in directory ('0.txt', '1.txt', '2.txt')")
1689
        self.runs(good_code)
1690
        rmtree(tmpdir)
1691
1692
    def test_is_dir_big(self):
1693
        """Suggestion when file is directory with many files."""
1694
        # Create temp dir with many files
1695
        tmpdir = tempfile.mkdtemp()
1696
        nb_files = 30
1697
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
1698
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
1699
        code = 'with open("{0}") as f:\n\tpass'
1700
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
1701
        self.throws(
1702
            bad_code, ISADIR_IO,
1703
            "any of the 30 files in directory "
1704
            "('0.txt', '1.txt', '10.txt', '11.txt', etc)")
1705
        self.runs(good_code)
1706
        rmtree(tmpdir)
1707
1708
    def test_is_not_dir(self):
1709
        """Suggestion when file is not a directory."""
1710
        code = 'with open("{0}") as f:\n\tpass'
1711
        code = 'os.listdir("{0}")'
1712
        typo, sugg = __file__, os.path.dirname(__file__)
1713
        bad_code, good_code = format_str(code, typo, sugg)
1714
        self.throws(
1715
            bad_code, NOTADIR_OS,
1716
            "'" + sugg + "' (calling os.path.dirname)")
1717
        self.runs(good_code)
1718
1719
    def test_dir_is_not_empty(self):
1720
        """Suggestion when directory is not empty."""
1721
        # NICE_TO_HAVE
1722
        nb_files = 3
1723
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
1724
        tmpdir, _ = self.create_tmp_dir_with_files(files)
1725
        self.throws('os.rmdir("{0}")'.format(tmpdir), DIRNOTEMPTY_OS)
1726
        rmtree(tmpdir)  # this should be the suggestion
1727
1728
1729
class AnyErrorTests(GetSuggestionsTests):
1730
    """Class for tests not related to an error type in particular."""
1731
1732
    def test_wrong_except(self):
1733
        """Test where except is badly used and thus does not catch.
1734
1735
        Common mistake : "except Exc1, Exc2" doesn't catch Exc2.
1736
        Adding parenthesis solves the issue.
1737
        """
1738
        # NICE_TO_HAVE
1739
        version = (3, 0)
1740
        raised_exc, other_exc = KeyError, TypeError
1741
        raised, other = raised_exc.__name__, other_exc.__name__
1742
        code = "try:\n\traise {0}()\nexcept {{0}}:\n\tpass".format(raised)
1743
        typo = "{0}, {1}".format(other, raised)
1744
        sugg = "({0})".format(typo)
1745
        bad1, bad2, good1, good2 = format_str(code, typo, other, sugg, raised)
1746
        self.throws(bad1, (raised_exc, None), [], up_to_version(version))
1747
        self.throws(bad1, INVALIDSYNTAX, [], from_version(version))
1748
        self.throws(bad2, (raised_exc, None))
1749
        self.runs(good1)
1750
        self.runs(good2)
1751
1752
1753
if __name__ == '__main__':
1754
    print(sys.version_info)
1755
    unittest2.main()
1756