Completed
Push — master ( 7a922e...2a5443 )
by De
01:10
created

SyntaxErrorTests   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 211
Duplicated Lines 0 %

Importance

Changes 23
Bugs 1 Features 3
Metric Value
wmc 28
c 23
b 1
f 3
dl 0
loc 211
rs 10

23 Methods

Rating   Name   Duplication   Size   Complexity  
A test_missing_colon() 0 7 1
A test_unqualified_exec() 0 13 2
A test_octal_literal() 0 9 1
A test_import_star() 0 13 3
A test_unpack() 0 7 1
A test_extended_unpacking() 0 6 1
A test_nonlocal() 0 7 1
A test_print() 0 10 1
A test_old_comparison() 0 20 1
A test_yield_return_out_of_func() 0 5 1
A test_import_future_not_def() 0 7 1
A test_wrong_bool_operator() 0 8 2
A test_nonlocal2() 0 7 1
A test_import_future_not_first() 0 4 1
A test_missing_colon2() 0 7 1
A test_simple_equal() 0 7 1
A test_keyword_as_identifier() 0 7 1
A test_unpack2() 0 7 1
A test_increment() 0 9 2
A test_exec() 0 10 1
A test_nonlocal3() 0 7 1
A test_ellipsis() 0 6 1
A test_no_error() 0 3 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) + 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_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, 3)
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
        code = 'class MyClass:\n\tdef func(self, a):\n\t\tpass\nMyClass().func({0}=1)'
1072
        bad_code, good_code = format_str(code, 'babar', 'a')
1073
        self.throws(bad_code, UNEXPECTEDKWARG)
1074
        self.runs(good_code)
1075
1076
    def test_keyword_arg_method2(self):
1077
        """Should be the same as previous test but on a method."""
1078
        # NICE_TO_HAVE
1079
        code = 'class MyClass:\n\tdef func(self, abcdef):\n\t\tpass\nMyClass().func({0}=1)'
1080
        bad_code, good_code = format_str(code, 'abcdf', 'abcdef')
1081
        self.throws(bad_code, UNEXPECTEDKWARG)
1082
        self.runs(good_code)
1083
1084
    def test_keyword_arg_class_method(self):
1085
        """Should be the same as previous test but on a class method."""
1086
        code = 'class MyClass:\n\t@classmethod\n\tdef func(cls, a):\n\t\tpass\nMyClass.func({0}=1)'
1087
        bad_code, good_code = format_str(code, 'babar', 'a')
1088
        self.throws(bad_code, UNEXPECTEDKWARG)
1089
        self.runs(good_code)
1090
1091
    def test_keyword_arg_class_method2(self):
1092
        """Should be the same as previous test but on a class method."""
1093
        # NICE_TO_HAVE
1094
        code = 'class MyClass:\n\t@classmethod\n\tdef func(cls, abcdef):\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_builtin(self):
1100
        """A few builtins (like int()) have a different error message."""
1101
        # NICE_TO_HAVE
1102
        # 'max', 'input', 'len', 'abs', 'all', etc have a specific error
1103
        # message and are not relevant here
1104
        for builtin in ['int', 'float', 'bool', 'complex']:
1105
            code = builtin + '(this_doesnt_exist=2)'
1106
            self.throws(code, UNEXPECTEDKWARG2, [], ALL_VERSIONS, 'cython')
1107
            self.throws(code, UNEXPECTEDKWARG, [], ALL_VERSIONS, 'pypy')
1108
1109
    def test_keyword_builtin_print(self):
1110
        """Builtin "print" has a different error message."""
1111
        # It would be NICE_TO_HAVE suggestions on keyword arguments
1112
        v3 = (3, 0)
1113
        code = "c = 'string'\nb = print(c, end_='toto')"
1114
        self.throws(code, INVALIDSYNTAX, [], up_to_version(v3))
1115
        self.throws(code, UNEXPECTEDKWARG2, [], from_version(v3), 'cython')
1116
        self.throws(code, UNEXPECTEDKWARG3, [], from_version(v3), 'pypy')
1117
1118
    def test_no_implicit_str_conv(self):
1119
        """Trying to concatenate a non-string value to a string."""
1120
        # NICE_TO_HAVE
1121
        code = '{0} + " things"'
1122
        typo, sugg = '12', 'str(12)'
1123
        bad_code, good_code = format_str(code, typo, sugg)
1124
        self.throws(bad_code, UNSUPPORTEDOPERAND)
1125
        self.runs(good_code)
1126
1127
    def test_no_implicit_str_conv2(self):
1128
        """Trying to concatenate a non-string value to a string."""
1129
        # NICE_TO_HAVE
1130
        code = '"things " + {0}'
1131
        typo, sugg = '12', 'str(12)'
1132
        bad_code, good_code = format_str(code, typo, sugg)
1133
        version = (3, 0)
1134
        version2 = (3, 6)
1135
        self.throws(
1136
            bad_code, CANNOTCONCAT, [], up_to_version(version), 'cython')
1137
        self.throws(
1138
            bad_code, CANTCONVERT, [], (version, version2), 'cython')
1139
        self.throws(
1140
            bad_code, MUSTBETYPENOTTYPE, [], from_version(version2), 'cython')
1141
        self.throws(
1142
            bad_code, UNSUPPORTEDOPERAND, [], ALL_VERSIONS, 'pypy')
1143
        self.runs(good_code)
1144
1145
    def test_assignment_to_range(self):
1146
        """Trying to assign to range works on list, not on range."""
1147
        # NICE_TO_HAVE
1148
        code = '{0}[2] = 1'
1149
        typo, sugg = 'range(4)', 'list(range(4))'
1150
        version = (3, 0)
1151
        bad_code, good_code = format_str(code, typo, sugg)
1152
        self.runs(bad_code, up_to_version(version))
1153
        self.throws(bad_code, OBJECTDOESNOTSUPPORT, [], from_version(version))
1154
        self.runs(good_code)
1155
1156
    def test_not_callable(self):
1157
        """Sometimes, one uses parenthesis instead of brackets."""
1158
        typo, getitem = '(0)', '[0]'
1159
        for ex, sugg in {
1160
            '[0]': "'list[value]'",
1161
            '{0: 0}': "'dict[value]'",
1162
            '"a"': "'str[value]'",
1163
        }.items():
1164
            self.throws(ex + typo, NOTCALLABLE, sugg)
1165
            self.runs(ex + getitem)
1166
        for ex in ['1', 'set()']:
1167
            self.throws(ex + typo, NOTCALLABLE)
1168
1169
    def test_unmatched_msg(self):
1170
        """Test that arbitrary strings are supported."""
1171
        self.throws(
1172
            'raise TypeError("unmatched TYPEERROR")',
1173
            UNKNOWN_TYPEERROR)
1174
1175
1176
class ImportErrorTests(GetSuggestionsTests):
1177
    """Class for tests related to ImportError."""
1178
1179
    def test_no_module_no_sugg(self):
1180
        """No suggestion."""
1181
        self.throws('import fqslkdfjslkqdjfqsd', NOMODULE)
1182
1183
    def test_no_module(self):
1184
        """Should be 'math'."""
1185
        code = 'import {0}'
1186
        typo, sugg = 'maths', 'math'
1187
        self.assertTrue(sugg in STAND_MODULES)
1188
        bad_code, good_code = format_str(code, typo, sugg)
1189
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1190
        self.runs(good_code)
1191
1192
    def test_no_module2(self):
1193
        """Should be 'math'."""
1194
        code = 'from {0} import pi'
1195
        typo, sugg = 'maths', 'math'
1196
        self.assertTrue(sugg in STAND_MODULES)
1197
        bad_code, good_code = format_str(code, typo, sugg)
1198
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1199
        self.runs(good_code)
1200
1201
    def test_no_module3(self):
1202
        """Should be 'math'."""
1203
        code = 'import {0} as my_imported_math'
1204
        typo, sugg = 'maths', 'math'
1205
        self.assertTrue(sugg in STAND_MODULES)
1206
        bad_code, good_code = format_str(code, typo, sugg)
1207
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1208
        self.runs(good_code)
1209
1210
    def test_no_module4(self):
1211
        """Should be 'math'."""
1212
        code = 'from {0} import pi as three_something'
1213
        typo, sugg = 'maths', 'math'
1214
        self.assertTrue(sugg in STAND_MODULES)
1215
        bad_code, good_code = format_str(code, typo, sugg)
1216
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1217
        self.runs(good_code)
1218
1219
    def test_no_module5(self):
1220
        """Should be 'math'."""
1221
        code = '__import__("{0}")'
1222
        typo, sugg = 'maths', 'math'
1223
        self.assertTrue(sugg in STAND_MODULES)
1224
        bad_code, good_code = format_str(code, typo, sugg)
1225
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1226
        self.runs(good_code)
1227
1228
    def test_import_future_nomodule(self):
1229
        """Should be '__future__'."""
1230
        code = 'import {0}'
1231
        typo, sugg = '__future_', '__future__'
1232
        self.assertTrue(sugg in STAND_MODULES)
1233
        bad_code, good_code = format_str(code, typo, sugg)
1234
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1235
        self.runs(good_code)
1236
1237
    def test_no_name_no_sugg(self):
1238
        """No suggestion."""
1239
        self.throws('from math import fsfsdfdjlkf', CANNOTIMPORT)
1240
1241
    def test_wrong_import(self):
1242
        """Should be 'math'."""
1243
        code = 'from {0} import pi'
1244
        typo, sugg = 'itertools', 'math'
1245
        self.assertTrue(sugg in STAND_MODULES)
1246
        bad_code, good_code = format_str(code, typo, sugg)
1247
        self.throws(bad_code, CANNOTIMPORT, "'" + good_code + "'")
1248
        self.runs(good_code)
1249
1250
    def test_typo_in_method(self):
1251
        """Should be 'pi'."""
1252
        code = 'from math import {0}'
1253
        typo, sugg = 'pie', 'pi'
1254
        bad_code, good_code = format_str(code, typo, sugg)
1255
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1256
        self.runs(good_code)
1257
1258
    def test_typo_in_method2(self):
1259
        """Should be 'pi'."""
1260
        code = 'from math import e, {0}, log'
1261
        typo, sugg = 'pie', 'pi'
1262
        bad_code, good_code = format_str(code, typo, sugg)
1263
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1264
        self.runs(good_code)
1265
1266
    def test_typo_in_method3(self):
1267
        """Should be 'pi'."""
1268
        code = 'from math import {0} as three_something'
1269
        typo, sugg = 'pie', 'pi'
1270
        bad_code, good_code = format_str(code, typo, sugg)
1271
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1272
        self.runs(good_code)
1273
1274
    def test_unmatched_msg(self):
1275
        """Test that arbitrary strings are supported."""
1276
        self.throws(
1277
            'raise ImportError("unmatched IMPORTERROR")',
1278
            UNKNOWN_IMPORTERROR)
1279
1280
    def test_module_removed(self):
1281
        """Sometimes, modules are deleted/moved/renamed."""
1282
        # NICE_TO_HAVE
1283
        version1 = (2, 7)  # result for 2.6 seems to vary
1284
        version2 = (3, 0)
1285
        code = 'import {0}'
1286
        lower, upper = format_str(code, 'tkinter', 'Tkinter')
1287
        self.throws(lower, NOMODULE, [], (version1, version2))
1288
        self.throws(upper, NOMODULE, [], from_version(version2))
1289
1290
1291
class LookupErrorTests(GetSuggestionsTests):
1292
    """Class for tests related to LookupError."""
1293
1294
1295
class KeyErrorTests(LookupErrorTests):
1296
    """Class for tests related to KeyError."""
1297
1298
    def test_no_sugg(self):
1299
        """No suggestion."""
1300
        self.throws('dict()["ffdsqmjklfqsd"]', KEYERROR)
1301
1302
1303
class IndexErrorTests(LookupErrorTests):
1304
    """Class for tests related to IndexError."""
1305
1306
    def test_no_sugg(self):
1307
        """No suggestion."""
1308
        self.throws('list()[2]', OUTOFRANGE)
1309
1310
1311
class SyntaxErrorTests(GetSuggestionsTests):
1312
    """Class for tests related to SyntaxError."""
1313
1314
    def test_no_error(self):
1315
        """No error."""
1316
        self.runs("1 + 2 == 2")
1317
1318
    def test_yield_return_out_of_func(self):
1319
        """yield/return needs to be in functions."""
1320
        sugg = "to indent it"
1321
        self.throws("yield 1", OUTSIDEFUNC, sugg)
1322
        self.throws("return 1", OUTSIDEFUNC, ["'sys.exit([arg])'", sugg])
1323
1324
    def test_print(self):
1325
        """print is a functions now and needs parenthesis."""
1326
        # NICE_TO_HAVE
1327
        code, new_code = 'print ""', 'print("")'
1328
        version = (3, 0)
1329
        version2 = (3, 4)
1330
        self.runs(code, up_to_version(version))
1331
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1332
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1333
        self.runs(new_code)
1334
1335
    def test_exec(self):
1336
        """exec is a functions now and needs parenthesis."""
1337
        # NICE_TO_HAVE
1338
        code, new_code = 'exec "1"', 'exec("1")'
1339
        version = (3, 0)
1340
        version2 = (3, 4)
1341
        self.runs(code, up_to_version(version))
1342
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1343
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1344
        self.runs(new_code)
1345
1346
    def test_old_comparison(self):
1347
        """<> comparison is removed, != always works."""
1348
        code = '1 {0} 2'
1349
        old, new = '<>', '!='
1350
        version = (3, 0)
1351
        old_code, new_code = format_str(code, old, new)
1352
        self.runs(old_code, up_to_version(version))
1353
        self.throws(
1354
            old_code,
1355
            INVALIDCOMP,
1356
            "'!='",
1357
            from_version(version),
1358
            'pypy')
1359
        self.throws(
1360
            old_code,
1361
            INVALIDSYNTAX,
1362
            "'!='",
1363
            from_version(version),
1364
            'cython')
1365
        self.runs(new_code)
1366
1367
    def test_missing_colon(self):
1368
        """Missing colon is a classic mistake."""
1369
        # NICE_TO_HAVE
1370
        code = "if True{0}\n\tpass"
1371
        bad_code, good_code = format_str(code, "", ":")
1372
        self.throws(bad_code, INVALIDSYNTAX)
1373
        self.runs(good_code)
1374
1375
    def test_missing_colon2(self):
1376
        """Missing colon is a classic mistake."""
1377
        # NICE_TO_HAVE
1378
        code = "class MyClass{0}\n\tpass"
1379
        bad_code, good_code = format_str(code, "", ":")
1380
        self.throws(bad_code, INVALIDSYNTAX)
1381
        self.runs(good_code)
1382
1383
    def test_simple_equal(self):
1384
        """'=' for comparison is a classic mistake."""
1385
        # NICE_TO_HAVE
1386
        code = "if 2 {0} 3:\n\tpass"
1387
        bad_code, good_code = format_str(code, "=", "==")
1388
        self.throws(bad_code, INVALIDSYNTAX)
1389
        self.runs(good_code)
1390
1391
    def test_keyword_as_identifier(self):
1392
        """Using a keyword as a variable name."""
1393
        # NICE_TO_HAVE
1394
        code = '{0} = 1'
1395
        bad_code, good_code = format_str(code, "from", "from_")
1396
        self.throws(bad_code, INVALIDSYNTAX)
1397
        self.runs(good_code)
1398
1399
    def test_increment(self):
1400
        """Trying to use '++' or '--'."""
1401
        # NICE_TO_HAVE
1402
        code = 'a = 0\na{0}'
1403
        for op in ('-', '+'):
1404
            typo, sugg = 2 * op, op + '=1'
1405
            bad_code, good_code = format_str(code, typo, sugg)
1406
            self.throws(bad_code, INVALIDSYNTAX)
1407
            self.runs(good_code)
1408
1409
    def test_wrong_bool_operator(self):
1410
        """Trying to use '&&' or '||'."""
1411
        # NICE_TO_HAVE
1412
        code = 'True {0} False'
1413
        for typo, sugg in (('&&', 'and'), ('||', 'or')):
1414
            bad_code, good_code = format_str(code, typo, sugg)
1415
            self.throws(bad_code, INVALIDSYNTAX)
1416
            self.runs(good_code)
1417
1418
    def test_import_future_not_first(self):
1419
        """Test what happens when import from __future__ is not first."""
1420
        code = 'a = 8/7\nfrom __future__ import division'
1421
        self.throws(code, FUTUREFIRST)
1422
1423
    def test_import_future_not_def(self):
1424
        """Should be 'division'."""
1425
        code = 'from __future__ import {0}'
1426
        typo, sugg = 'divisio', 'division'
1427
        bad_code, good_code = format_str(code, typo, sugg)
1428
        self.throws(bad_code, FUTFEATNOTDEF, "'" + sugg + "'")
1429
        self.runs(good_code)
1430
1431
    def test_unqualified_exec(self):
1432
        """Exec in nested functions."""
1433
        # NICE_TO_HAVE
1434
        version = (3, 0)
1435
        codes = [
1436
            "def func1():\n\tbar='1'\n\tdef func2():"
1437
            "\n\t\texec(bar)\n\tfunc2()\nfunc1()",
1438
            "def func1():\n\texec('1')\n\tdef func2():"
1439
            "\n\t\tTrue",
1440
        ]
1441
        for code in codes:
1442
            self.throws(code, UNQUALIFIED_EXEC, [], up_to_version(version))
1443
            self.runs(code, from_version(version))
1444
1445
    def test_import_star(self):
1446
        """'import *' in nested functions."""
1447
        # NICE_TO_HAVE
1448
        codes = [
1449
            "def func1():\n\tbar='1'\n\tdef func2():"
1450
            "\n\t\tfrom math import *\n\t\tTrue\n\tfunc2()\nfunc1()",
1451
            "def func1():\n\tfrom math import *"
1452
            "\n\tdef func2():\n\t\tTrue",
1453
        ]
1454
        with warnings.catch_warnings():
1455
            warnings.simplefilter("ignore", category=SyntaxWarning)
1456
            for code in codes:
1457
                self.throws(code, IMPORTSTAR, [])
1458
1459
    def test_unpack(self):
1460
        """Extended tuple unpacking does not work prior to Python 3."""
1461
        # NICE_TO_HAVE
1462
        version = (3, 0)
1463
        code = 'a, *b = (1, 2, 3)'
1464
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1465
        self.runs(code, from_version(version))
1466
1467
    def test_unpack2(self):
1468
        """Unpacking in function arguments was supported up to Python 3."""
1469
        # NICE_TO_HAVE
1470
        version = (3, 0)
1471
        code = 'def addpoints((x1, y1), (x2, y2)):\n\tpass'
1472
        self.runs(code, up_to_version(version))
1473
        self.throws(code, INVALIDSYNTAX, [], from_version(version))
1474
1475
    def test_nonlocal(self):
1476
        """nonlocal keyword is added in Python 3."""
1477
        # NICE_TO_HAVE
1478
        version = (3, 0)
1479
        code = 'def func():\n\tfoo = 1\n\tdef nested():\n\t\tnonlocal foo'
1480
        self.runs(code, from_version(version))
1481
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1482
1483
    def test_nonlocal2(self):
1484
        """nonlocal must be used only when binding exists."""
1485
        # NICE_TO_HAVE
1486
        version = (3, 0)
1487
        code = 'def func():\n\tdef nested():\n\t\tnonlocal foo'
1488
        self.throws(code, NOBINDING, [], from_version(version))
1489
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1490
1491
    def test_nonlocal3(self):
1492
        """nonlocal must be used only when binding to non-global exists."""
1493
        # NICE_TO_HAVE
1494
        version = (3, 0)
1495
        code = 'foo = 1\ndef func():\n\tdef nested():\n\t\tnonlocal foo'
1496
        self.throws(code, NOBINDING, [], from_version(version))
1497
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1498
1499
    def test_octal_literal(self):
1500
        """Syntax for octal liberals has changed."""
1501
        # NICE_TO_HAVE
1502
        version = (3, 0)
1503
        bad, good = '0720', '0o720'
1504
        self.runs(good)
1505
        self.runs(bad, up_to_version(version))
1506
        self.throws(bad, INVALIDTOKEN, [], from_version(version), 'cython')
1507
        self.throws(bad, INVALIDSYNTAX, [], from_version(version), 'pypy')
1508
1509
    def test_extended_unpacking(self):
1510
        """Extended iterable unpacking is added with Python 3."""
1511
        version = (3, 0)
1512
        code = '(a, *rest, b) = range(5)'
1513
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1514
        self.runs(code, from_version(version))
1515
1516
    def test_ellipsis(self):
1517
        """Triple dot (...) aka Ellipsis can be used anywhere in Python 3."""
1518
        version = (3, 0)
1519
        code = '...'
1520
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1521
        self.runs(code, from_version(version))
1522
1523
1524
class MemoryErrorTests(GetSuggestionsTests):
1525
    """Class for tests related to MemoryError."""
1526
1527
    def test_out_of_memory(self):
1528
        """Test what happens in case of MemoryError."""
1529
        code = '[0] * 999999999999999'
1530
        self.throws(code, MEMORYERROR)
1531
1532
    def test_out_of_memory_range(self):
1533
        """Test what happens in case of MemoryError."""
1534
        code = '{0}(999999999999999)'
1535
        typo, sugg = 'range', 'xrange'
1536
        bad_code, good_code = format_str(code, typo, sugg)
1537
        self.runs(bad_code, ALL_VERSIONS, 'pypy')
1538
        version = (2, 7)
1539
        version2 = (3, 0)
1540
        self.throws(
1541
            bad_code,
1542
            OVERFLOWERR, "'" + sugg + "'",
1543
            up_to_version(version),
1544
            'cython')
1545
        self.throws(
1546
            bad_code,
1547
            MEMORYERROR, "'" + sugg + "'",
1548
            (version, version2),
1549
            'cython')
1550
        self.runs(good_code, up_to_version(version2), 'cython')
1551
        self.runs(bad_code, from_version(version2), 'cython')
1552
1553
1554
class ValueErrorTests(GetSuggestionsTests):
1555
    """Class for tests related to ValueError."""
1556
1557
    def test_too_many_values(self):
1558
        """Unpack 4 values in 3 variables."""
1559
        code = 'a, b, c = [1, 2, 3, 4]'
1560
        version = (3, 0)
1561
        self.throws(code, EXPECTEDLENGTH, [], up_to_version(version), 'pypy')
1562
        self.throws(code, TOOMANYVALUES, [], from_version(version), 'pypy')
1563
        self.throws(code, TOOMANYVALUES, [], ALL_VERSIONS, 'cython')
1564
1565
    def test_not_enough_values(self):
1566
        """Unpack 2 values in 3 variables."""
1567
        code = 'a, b, c = [1, 2]'
1568
        version = (3, 0)
1569
        self.throws(code, EXPECTEDLENGTH, [], up_to_version(version), 'pypy')
1570
        self.throws(code, NEEDMOREVALUES, [], from_version(version), 'pypy')
1571
        self.throws(code, NEEDMOREVALUES, [], ALL_VERSIONS, 'cython')
1572
1573
    def test_conversion_fails(self):
1574
        """Conversion fails."""
1575
        self.throws('int("toto")', INVALIDLITERAL)
1576
1577
    def test_math_domain(self):
1578
        """Math function used out of its domain."""
1579
        code = 'import math\nlg = math.log(-1)'
1580
        self.throws(code, MATHDOMAIN)
1581
1582
    def test_zero_len_field_in_format(self):
1583
        """Format {} is not valid before Python 2.7."""
1584
        code = '"{0}".format(0)'
1585
        old, new = '{0}', '{}'
1586
        old_code, new_code = format_str(code, old, new)
1587
        version = (2, 7)
1588
        self.runs(old_code)
1589
        self.throws(new_code, ZEROLENERROR, '{0}', up_to_version(version))
1590
        self.runs(new_code, from_version(version))
1591
1592
    def test_timedata_does_not_match(self):
1593
        """Strptime arguments are in wrong order."""
1594
        code = 'import datetime\ndatetime.datetime.strptime({0}, {1})'
1595
        timedata, timeformat = '"30 Nov 00"', '"%d %b %y"'
1596
        good_code = code.format(*(timedata, timeformat))
1597
        bad_code = code.format(*(timeformat, timedata))
1598
        self.runs(good_code)
1599
        self.throws(bad_code, TIMEDATAFORMAT,
1600
                    ['to swap value and format parameters'])
1601
1602
1603
class IOErrorTests(GetSuggestionsTests):
1604
    """Class for tests related to IOError."""
1605
1606
    def test_no_such_file(self):
1607
        """File does not exist."""
1608
        code = 'with open("doesnotexist") as f:\n\tpass'
1609
        self.throws(code, NOFILE_IO)
1610
1611
    def test_no_such_file2(self):
1612
        """File does not exist."""
1613
        code = 'os.listdir("doesnotexist")'
1614
        self.throws(code, NOFILE_OS)
1615
1616
    def test_no_such_file_user(self):
1617
        """Suggestion when one needs to expanduser."""
1618
        code = 'os.listdir("{0}")'
1619
        typo, sugg = "~", os.path.expanduser("~")
1620
        bad_code, good_code = format_str(code, typo, sugg)
1621
        self.throws(
1622
            bad_code, NOFILE_OS,
1623
            "'" + sugg + "' (calling os.path.expanduser)")
1624
        self.runs(good_code)
1625
1626
    def test_no_such_file_vars(self):
1627
        """Suggestion when one needs to expandvars."""
1628
        code = 'os.listdir("{0}")'
1629
        key = 'HOME'
1630
        typo, sugg = "$" + key, os.path.expanduser("~")
1631
        original_home = os.environ.get('HOME')
1632
        os.environ[key] = sugg
1633
        bad_code, good_code = format_str(code, typo, sugg)
1634
        self.throws(
1635
            bad_code, NOFILE_OS,
1636
            "'" + sugg + "' (calling os.path.expandvars)")
1637
        self.runs(good_code)
1638
        if original_home is None:
1639
            del os.environ[key]
1640
        else:
1641
            os.environ[key] = original_home
1642
1643
    def create_tmp_dir_with_files(self, filelist):
1644
        """Create a temporary directory with files in it."""
1645
        tmpdir = tempfile.mkdtemp()
1646
        absfiles = [os.path.join(tmpdir, f) for f in filelist]
1647
        for name in absfiles:
1648
            open(name, 'a').close()
1649
        return (tmpdir, absfiles)
1650
1651
    def test_is_dir_empty(self):
1652
        """Suggestion when file is an empty directory."""
1653
        # Create empty temp dir
1654
        tmpdir, _ = self.create_tmp_dir_with_files([])
1655
        code = 'with open("{0}") as f:\n\tpass'
1656
        bad_code, _ = format_str(code, tmpdir, "TODO")
1657
        self.throws(
1658
            bad_code, ISADIR_IO, "to add content to {0} first".format(tmpdir))
1659
        rmtree(tmpdir)
1660
1661
    def test_is_dir_small(self):
1662
        """Suggestion when file is directory with a few files."""
1663
        # Create temp dir with a few files
1664
        nb_files = 3
1665
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
1666
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
1667
        code = 'with open("{0}") as f:\n\tpass'
1668
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
1669
        self.throws(
1670
            bad_code, ISADIR_IO,
1671
            "any of the 3 files in directory ('0.txt', '1.txt', '2.txt')")
1672
        self.runs(good_code)
1673
        rmtree(tmpdir)
1674
1675
    def test_is_dir_big(self):
1676
        """Suggestion when file is directory with many files."""
1677
        # Create temp dir with many files
1678
        tmpdir = tempfile.mkdtemp()
1679
        nb_files = 30
1680
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
1681
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
1682
        code = 'with open("{0}") as f:\n\tpass'
1683
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
1684
        self.throws(
1685
            bad_code, ISADIR_IO,
1686
            "any of the 30 files in directory "
1687
            "('0.txt', '1.txt', '10.txt', '11.txt', etc)")
1688
        self.runs(good_code)
1689
        rmtree(tmpdir)
1690
1691
    def test_is_not_dir(self):
1692
        """Suggestion when file is not a directory."""
1693
        code = 'with open("{0}") as f:\n\tpass'
1694
        code = 'os.listdir("{0}")'
1695
        typo, sugg = __file__, os.path.dirname(__file__)
1696
        bad_code, good_code = format_str(code, typo, sugg)
1697
        self.throws(
1698
            bad_code, NOTADIR_OS,
1699
            "'" + sugg + "' (calling os.path.dirname)")
1700
        self.runs(good_code)
1701
1702
    def test_dir_is_not_empty(self):
1703
        """Suggestion when directory is not empty."""
1704
        # NICE_TO_HAVE
1705
        nb_files = 3
1706
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
1707
        tmpdir, _ = self.create_tmp_dir_with_files(files)
1708
        self.throws('os.rmdir("{0}")'.format(tmpdir), DIRNOTEMPTY_OS)
1709
        rmtree(tmpdir)  # this should be the suggestion
1710
1711
1712
class AnyErrorTests(GetSuggestionsTests):
1713
    """Class for tests not related to an error type in particular."""
1714
1715
    def test_wrong_except(self):
1716
        """Test where except is badly used and thus does not catch.
1717
1718
        Common mistake : "except Exc1, Exc2" doesn't catch Exc2.
1719
        Adding parenthesis solves the issue.
1720
        """
1721
        # NICE_TO_HAVE
1722
        version = (3, 0)
1723
        raised_exc, other_exc = KeyError, TypeError
1724
        raised, other = raised_exc.__name__, other_exc.__name__
1725
        code = "try:\n\traise {0}()\nexcept {{0}}:\n\tpass".format(raised)
1726
        typo = "{0}, {1}".format(other, raised)
1727
        sugg = "({0})".format(typo)
1728
        bad1, bad2, good1, good2 = format_str(code, typo, other, sugg, raised)
1729
        self.throws(bad1, (raised_exc, None), [], up_to_version(version))
1730
        self.throws(bad1, INVALIDSYNTAX, [], from_version(version))
1731
        self.throws(bad2, (raised_exc, None))
1732
        self.runs(good1)
1733
        self.runs(good2)
1734
1735
1736
if __name__ == '__main__':
1737
    print(sys.version_info)
1738
    unittest2.main()
1739