Completed
Push — master ( e1209d...156f3f )
by De
01:06
created

listify()   B

Complexity

Conditions 6

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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