Completed
Push — master ( 7deac6...f59314 )
by De
01:05
created

ranges_between()   A

Complexity

Conditions 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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