TypeErrorTests   F
last analyzed

Complexity

Total Complexity 76

Size/Duplication

Total Lines 672
Duplicated Lines 2.38 %

Importance

Changes 62
Bugs 1 Features 1
Metric Value
wmc 76
c 62
b 1
f 1
dl 16
loc 672
rs 1.9164

58 Methods

Rating   Name   Duplication   Size   Complexity  
A test_customclass_cannot_be_interpreter_as_int() 0 11 2
A test_deletion_from_string() 0 7 1
B test_cannot_concatenate_iter_to_list() 0 25 1
A test_keyword_arg_class_method2() 0 10 1
A test_exc_must_derive_from() 0 4 1
A test_nb_arg_missing_cls() 0 7 2
A test_indices_cant_be_custom() 0 13 2
A test_nb_args1() 0 7 1
A test_len_on_custom() 0 9 1
A test_iter_cannot_be_interpreted_as_int() 0 9 1
A test_dict_operations() 0 11 1
A test_str_cannot_be_interpreted_as_int() 0 9 2
A test_not_callable() 0 12 3
A test_indices_cant_be_str() 0 7 2
A test_set_operations() 0 13 1
A test_nb_arg7() 0 4 1
A test_keyword_args2() 0 8 1
A test_not_sub() 16 16 1
A test_unary_operand_builtin() 0 12 2
A test_assignment_to_custom() 0 11 1
A test_unsupported_operand_caret() 0 6 1
A test_method_called_on_class() 0 19 2
A test_nb_args() 0 7 1
A test_float_cannot_be_interpreted_as_int() 0 17 2
A test_nb_args2() 0 9 1
A test_nb_args5() 0 9 1
A test_nb_arg8() 0 4 1
A test_keyword_arg_class_method() 0 7 1
A test_keyword_arg_method() 0 7 1
A test_no_implicit_str_conv() 0 8 1
A test_unary_operand_custom() 0 17 2
A test_len_on_iterable() 0 6 1
A test_unhashable() 0 8 1
A test_keyword_arg_other_objects_with_name() 0 11 1
A test_nb_args3() 0 9 1
A test_keyword_args() 0 7 1
A test_assignment_to_range() 0 10 1
A test_not_callable_custom() 0 10 1
A test_keyword_arg_method2() 0 9 1
A test_deletion_from_custom() 0 9 1
A test_unordered_custom2() 0 8 2
A test_indices_cant_be_float() 0 14 2
A test_unordered_builtin() 0 8 2
A test_nb_args6() 0 14 1
A test_unordered_custom() 0 8 2
A test_assignment_to_string() 0 7 1
A test_nb_args4() 0 9 1
A test_no_implicit_str_conv2() 0 13 1
A test_nb_arg_missing_self() 0 8 1
B test_object_indexing() 0 25 1
A test_unmatched_msg() 0 4 1
A test_keyword_builtin() 0 16 4
A test_keyword_builtin_print() 0 10 1
A test_keyword_sort_cmpkey() 0 15 1
A test_keyword_arg_multiples_instances() 0 9 1
A test_keyword_arg_lambda() 0 8 1
A test_keyword_arg_lambda_method() 0 9 1
B test_c_func_takes_no_keyword_arguments() 0 26 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like TypeErrorTests often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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