Completed
Push — master ( 652866...ac3ef3 )
by De
01:15
created

NameErrorTests   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 365
Duplicated Lines 0 %

Importance

Changes 31
Bugs 1 Features 5
Metric Value
wmc 45
c 31
b 1
f 5
dl 0
loc 365
rs 8.3673

38 Methods

Rating   Name   Duplication   Size   Complexity  
A test_removed_execfile() 0 7 1
A test_local() 0 7 1
A test_unmatched_msg() 0 5 1
A test_global() 0 9 1
A test_removed_3_0() 0 23 2
A test_imported_twice() 0 7 1
A test_shell_commands() 0 14 1
A test_added_3_0() 0 10 2
A test_added_3_4() 0 8 2
A test_attribute_hidden() 0 7 1
A test_added_2_7() 0 6 2
A test_removed_buffer() 0 7 1
A test_name() 0 5 1
A test_complex_numbers() 0 9 1
A test_keyword() 0 5 1
A test_added_3_3() 0 22 2
A test_builtin() 0 5 1
A test_removed_cmp() 0 7 1
A test_free_var_before_assignment() 0 5 1
A test_self2() 0 5 1
A test_imported() 0 7 1
A test_1_arg() 0 7 1
A test_self() 0 5 1
A test_decorator() 0 7 1
A test_removed_raw_input() 0 7 1
A test_import_sugg() 0 15 2
A test_n_args() 0 8 1
A test_added_3_5() 0 8 2
A test_import2() 0 7 1
A test_no_sugg() 0 3 1
A test_removed_intern() 0 11 1
A test_import() 0 7 1
A test_not_imported() 0 13 1
A test_enclosing_scope() 0 8 1
A test_removed_apply() 0 7 1
A test_removed_reload() 0 10 1
A test_cls() 0 5 1
A test_removed_reduce() 0 12 1

How to fix   Complexity   

Complex Class

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