Completed
Push — master ( a2d4e7...b8e750 )
by De
59s
created

test_free_var_before_assignment()   A

Complexity

Conditions 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 4
rs 10
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
# AttributeError for AttributeErrorTests
179
ATTRIBUTEERROR = (AttributeError, re.ATTRIBUTEERROR_RE)
180
MODATTRIBUTEERROR = (AttributeError, re.MODULEHASNOATTRIBUTE_RE)
181
UNKNOWN_ATTRIBUTEERROR = (AttributeError, None)
182
# SyntaxError for SyntaxErrorTests
183
INVALIDSYNTAX = (SyntaxError, re.INVALID_SYNTAX_RE)
184
NOBINDING = (SyntaxError, re.NO_BINDING_NONLOCAL_RE)
185
OUTSIDEFUNC = (SyntaxError, re.OUTSIDE_FUNCTION_RE)
186
MISSINGPARENT = (SyntaxError, re.MISSING_PARENT_RE)
187
INVALIDCOMP = (SyntaxError, re.INVALID_COMP_RE)
188
FUTUREFIRST = (SyntaxError, re.FUTURE_FIRST_RE)
189
FUTFEATNOTDEF = (SyntaxError, re.FUTURE_FEATURE_NOT_DEF_RE)
190
UNQUALIFIED_EXEC = (SyntaxError, re.UNQUALIFIED_EXEC_RE)
191
IMPORTSTAR = (SyntaxError, re.IMPORTSTAR_RE)
192
# MemoryError and OverflowError for MemoryErrorTests
193
MEMORYERROR = (MemoryError, '')
194
OVERFLOWERR = (OverflowError, re.RESULT_TOO_MANY_ITEMS_RE)
195
# IOError
196
NOFILE_IO = (common.NoFileIoError, re.NO_SUCH_FILE_RE)
197
NOFILE_OS = (common.NoFileOsError, re.NO_SUCH_FILE_RE)
198
NOTADIR_IO = (common.NotDirIoError, "^Not a directory$")
199
NOTADIR_OS = (common.NotDirOsError, "^Not a directory$")
200
ISADIR_IO = (common.IsDirIoError, "^Is a directory$")
201
ISADIR_OS = (common.IsDirOsError, "^Is a directory$")
202
DIRNOTEMPTY_OS = (OSError, "^Directory not empty$")
203
204
205
class GetSuggestionsTests(unittest2.TestCase):
206
    """Generic class to test get_suggestions_for_exception.
207
208
    Many tests do not correspond to any handled exceptions but are
209
    kept because it is quite convenient to have a large panel of examples.
210
    Also, some correspond to example where suggestions could be added, those
211
    are flagged with a NICE_TO_HAVE comment.
212
    Finally, whenever it is easily possible, the code with the suggestions
213
    taken into account is usually tested too to ensure that the suggestion does
214
    work.
215
    """
216
217
    def runs(self, code, version_range=None, interpreters=None):
218
        """Helper function to run code.
219
220
        version_range and interpreters can be provided if the test depends on
221
        the used environment.
222
        """
223
        interpreters = listify(interpreters, INTERPRETERS)
224
        if version_range is None:
225
            version_range = ALL_VERSIONS
226
        if version_in_range(version_range) and interpreter_in(interpreters):
227
            no_exception(code)
228
229
    def throws(self, code, error_info,
230
               sugg=None, version_range=None, interpreters=None):
231
        """Run code and check it throws and relevant suggestions are provided.
232
233
        Helper function to run code, check that it throws, what it throws and
234
        that the exception leads to the expected suggestions.
235
        version_range and interpreters can be provided if the test depends on
236
        the used environment.
237
        """
238
        if version_range is None:
239
            version_range = ALL_VERSIONS
240
        interpreters = listify(interpreters, INTERPRETERS)
241
        sugg = listify(sugg, [])
242
        if version_in_range(version_range) and interpreter_in(interpreters):
243
            error_type, error_msg = error_info
244
            type_caught, value, traceback = get_exception(code)
245
            details = "Running following code :\n---\n{0}\n---".format(code)
246
            self.assertTrue(isinstance(value, type_caught))
247
            self.assertTrue(
248
                issubclass(type_caught, error_type),
249
                "{0} ({1}) not a subclass of {2}"
250
                .format(type_caught, value, error_type))
251
            msg = next((a for a in value.args if isinstance(a, str)), '')
252
            if error_msg is not None:
253
                self.assertRegexpMatches(msg, error_msg)
254
            suggestions = sorted(
255
                get_suggestions_for_exception(value, traceback))
256
            self.assertEqual(suggestions, sugg, details)
257
258
259
class NameErrorTests(GetSuggestionsTests):
260
    """Class for tests related to NameError."""
261
262
    def test_local(self):
263
        """Should be 'foo'."""
264
        code = "foo = 0\n{0}"
265
        typo, sugg = "foob", "foo"
266
        bad_code, good_code = format_str(code, typo, sugg)
267
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
268
        self.runs(good_code)
269
270
    def test_1_arg(self):
271
        """Should be 'foo'."""
272
        typo, sugg = "foob", "foo"
273
        code = func_gen(param=sugg, body='{0}', args='1')
274
        bad_code, good_code = format_str(code, typo, sugg)
275
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
276
        self.runs(good_code)
277
278
    def test_n_args(self):
279
        """Should be 'fool' or 'foot'."""
280
        typo, sugg1, sugg2 = "foob", "foot", "fool"
281
        code = func_gen(param='fool, foot', body='{0}', args='1, 2')
282
        bad, good1, good2 = format_str(code, typo, sugg1, sugg2)
283
        self.throws(bad, NAMEERROR, ["'fool' (local)", "'foot' (local)"])
284
        self.runs(good1)
285
        self.runs(good2)
286
287
    def test_builtin(self):
288
        """Should be 'max'."""
289
        typo, sugg = 'maxi', 'max'
290
        self.throws(typo, NAMEERROR, "'" + sugg + "' (builtin)")
291
        self.runs(sugg)
292
293
    def test_keyword(self):
294
        """Should be 'pass'."""
295
        typo, sugg = 'passs', 'pass'
296
        self.throws(typo, NAMEERROR, "'" + sugg + "' (keyword)")
297
        self.runs(sugg)
298
299
    def test_global(self):
300
        """Should be this_is_a_global_list."""
301
        typo, sugg = 'this_is_a_global_lis', 'this_is_a_global_list'
302
        # just a way to say that this_is_a_global_list is needed in globals
303
        this_is_a_global_list
304
        self.assertFalse(sugg in locals())
305
        self.assertTrue(sugg in globals())
306
        self.throws(typo, NAMEERROR, "'" + sugg + "' (global)")
307
        self.runs(sugg)
308
309
    def test_name(self):
310
        """Should be '__name__'."""
311
        typo, sugg = '__name_', '__name__'
312
        self.throws(typo, NAMEERROR, "'" + sugg + "' (global)")
313
        self.runs(sugg)
314
315
    def test_decorator(self):
316
        """Should be classmethod."""
317
        typo, sugg = "class_method", "classmethod"
318
        code = "@{0}\n" + func_gen()
319
        bad_code, good_code = format_str(code, typo, sugg)
320
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (builtin)")
321
        self.runs(good_code)
322
323
    def test_import(self):
324
        """Should be math."""
325
        code = 'import math\n{0}'
326
        typo, sugg = 'maths', 'math'
327
        bad_code, good_code = format_str(code, typo, sugg)
328
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
329
        self.runs(good_code)
330
331
    def test_import2(self):
332
        """Should be my_imported_math."""
333
        code = 'import math as my_imported_math\n{0}'
334
        typo, sugg = 'my_imported_maths', 'my_imported_math'
335
        bad_code, good_code = format_str(code, typo, sugg)
336
        self.throws(bad_code, NAMEERROR, "'" + sugg + "' (local)")
337
        self.runs(good_code)
338
339
    def test_imported(self):
340
        """Should be math.pi."""
341
        code = 'import math\n{0}'
342
        typo, sugg = 'pi', 'math.pi'
343
        bad_code, good_code = format_str(code, typo, sugg)
344
        self.throws(bad_code, NAMEERROR, "'" + sugg + "'")
345
        self.runs(good_code)
346
347
    def test_imported_twice(self):
348
        """Should be math.pi."""
349
        code = 'import math\nimport math\n{0}'
350
        typo, sugg = 'pi', 'math.pi'
351
        bad_code, good_code = format_str(code, typo, sugg)
352
        self.throws(bad_code, NAMEERROR, "'" + sugg + "'")
353
        self.runs(good_code)
354
355
    def test_not_imported(self):
356
        """Should be random.choice after importing random."""
357
        # This test assumes that `module` is not imported
358
        module, attr = 'random', 'choice'
359
        self.assertFalse(module in locals())
360
        self.assertFalse(module in globals())
361
        self.assertTrue(module in STAND_MODULES)
362
        bad_code = attr
363
        good_code = 'from {0} import {1}\n{2}'.format(module, attr, bad_code)
364
        self.runs(good_code)
365
        self.throws(
366
            bad_code, NAMEERROR,
367
            "'{0}' from {1} (not imported)".format(attr, module))
368
369
    def test_enclosing_scope(self):
370
        """Test that variables from enclosing scope are suggested."""
371
        # NICE_TO_HAVE
372
        typo, sugg = 'foob', 'foo'
373
        code = 'def f():\n\tfoo = 0\n\tdef g():\n\t\t{0}\n\tg()\nf()'
374
        bad_code, good_code = format_str(code, typo, sugg)
375
        self.throws(bad_code, NAMEERROR)
376
        self.runs(good_code)
377
378
    def test_no_sugg(self):
379
        """No suggestion."""
380
        self.throws('a = ldkjhfnvdlkjhvgfdhgf', NAMEERROR)
381
382
    def test_free_var_before_assignment(self):
383
        """No suggestion but different error message."""
384
        code = 'def f():\n\tdef g():\n\t\treturn free_var\n\tg()\n\tfree_var = 0\nf()'
385
        self.throws(code, NAMEERRORBEFOREREF)
386
387
    # For added/removed names, following functions with one name
388
    # per functions were added in the early stages of the project.
389
    # In the future, I'd like to have them replaced by something
390
    # a bit more concise using relevant data structure. In the
391
    # meantime, I am keeping both versions because safer is better.
392
    def test_removed_cmp(self):
393
        """Builtin cmp is removed."""
394
        # NICE_TO_HAVE
395
        code = 'cmp(1, 2)'
396
        version = (3, 0, 1)
397
        self.runs(code, up_to_version(version))
398
        self.throws(code, NAMEERROR, [], from_version(version))
399
400
    def test_removed_reduce(self):
401
        """Builtin reduce is removed - moved to functools."""
402
        code = 'reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])'
403
        version = (3, 0)
404
        self.runs(code, up_to_version(version))
405
        self.runs('from functools import reduce\n' + code,
406
                  from_version(version))
407
        self.throws(
408
            code,
409
            NAMEERROR,
410
            "'reduce' from functools (not imported)",
411
            from_version(version))
412
413
    def test_removed_apply(self):
414
        """Builtin apply is removed."""
415
        # NICE_TO_HAVE
416
        code = 'apply(sum, [[1, 2, 3]])'
417
        version = (3, 0)
418
        self.runs(code, up_to_version(version))
419
        self.throws(code, NAMEERROR, [], from_version(version))
420
421
    def test_removed_reload(self):
422
        """Builtin reload is removed.
423
424
        Moved to importlib.reload or imp.reload depending on version.
425
        """
426
        # NICE_TO_HAVE
427
        code = 'reload(math)'
428
        version = (3, 0)
429
        self.runs(code, up_to_version(version))
430
        self.throws(code, NAMEERROR, [], from_version(version))
431
432
    def test_removed_intern(self):
433
        """Builtin intern is removed - moved to sys."""
434
        code = 'intern("toto")'
435
        new_code = 'sys.intern("toto")'
436
        version = (3, 0)
437
        self.runs(code, up_to_version(version))
438
        self.throws(
439
            code, NAMEERROR,
440
            ["'iter' (builtin)", "'sys.intern'"],
441
            from_version(version))
442
        self.runs(new_code, from_version(version))
443
444
    def test_removed_execfile(self):
445
        """Builtin execfile is removed - use exec() and compile()."""
446
        # NICE_TO_HAVE
447
        code = 'execfile("some_filename")'
448
        version = (3, 0)
449
        # self.runs(code, up_to_version(version))
450
        self.throws(code, NAMEERROR, [], from_version(version))
451
452
    def test_removed_raw_input(self):
453
        """Builtin raw_input is removed - use input() instead."""
454
        code = 'i = raw_input("Prompt:")'
455
        version = (3, 0)
456
        # self.runs(code, up_to_version(version))
457
        self.throws(
458
            code, NAMEERROR, "'input' (builtin)", from_version(version))
459
460
    def test_removed_buffer(self):
461
        """Builtin buffer is removed - use memoryview instead."""
462
        # NICE_TO_HAVE
463
        code = 'buffer("abc")'
464
        version = (3, 0)
465
        self.runs(code, up_to_version(version))
466
        self.throws(code, NAMEERROR, [], from_version(version))
467
468
    def test_added_2_7(self):
469
        """Test for names added in 2.7."""
470
        version = (2, 7)
471
        for name in ['memoryview']:
472
            self.runs(name, from_version(version))
473
            self.throws(name, NAMEERROR, [], up_to_version(version))
474
475
    def test_removed_3_0(self):
476
        """Test for names removed in 3.0."""
477
        version = (3, 0)
478
        for name, suggs in {
479
                'StandardError': [],  # Exception
480
                'apply': [],
481
                'basestring': [],
482
                'buffer': [],
483
                'cmp': [],
484
                'coerce': [],
485
                'execfile': [],
486
                'file': ["'filter' (builtin)"],
487
                'intern': ["'iter' (builtin)", "'sys.intern'"],
488
                'long': [],
489
                'raw_input': ["'input' (builtin)"],
490
                'reduce': ["'reduce' from functools (not imported)"],
491
                'reload': [],
492
                'unichr': [],
493
                'unicode': ["'code' (local)"],
494
                'xrange': ["'range' (builtin)"],
495
                }.items():
496
            self.throws(name, NAMEERROR, suggs, from_version(version))
497
            self.runs(name, up_to_version(version))
498
499
    def test_added_3_0(self):
500
        """Test for names added in 3.0."""
501
        version = (3, 0)
502
        for name, suggs in {
503
                'ascii': [],
504
                'ResourceWarning': ["'FutureWarning' (builtin)"],
505
                '__build_class__': [],
506
                }.items():
507
            self.runs(name, from_version(version))
508
            self.throws(name, NAMEERROR, suggs, up_to_version(version))
509
510
    def test_added_3_3(self):
511
        """Test for names added in 3.3."""
512
        version = (3, 3)
513
        for name, suggs in {
514
                'BrokenPipeError': [],
515
                'ChildProcessError': [],
516
                'ConnectionAbortedError': [],
517
                'ConnectionError': ["'IndentationError' (builtin)"],
518
                'ConnectionRefusedError': [],
519
                'ConnectionResetError': [],
520
                'FileExistsError': [],
521
                'FileNotFoundError': [],
522
                'InterruptedError': [],
523
                'IsADirectoryError': [],
524
                'NotADirectoryError': [],
525
                'PermissionError': ["'ZeroDivisionError' (builtin)"],
526
                'ProcessLookupError': ["'LookupError' (builtin)"],
527
                'TimeoutError': [],
528
                '__loader__': [],
529
                }.items():
530
            self.runs(name, from_version(version))
531
            self.throws(name, NAMEERROR, suggs, up_to_version(version))
532
533
    def test_added_3_4(self):
534
        """Test for names added in 3.4."""
535
        version = (3, 4)
536
        for name, suggs in {
537
                '__spec__': [],
538
                }.items():
539
            self.runs(name, from_version(version))
540
            self.throws(name, NAMEERROR, suggs, up_to_version(version))
541
542
    def test_added_3_5(self):
543
        """Test for names added in 3.5."""
544
        version = (3, 5)
545
        for name, suggs in {
546
                'StopAsyncIteration': ["'StopIteration' (builtin)"],
547
                }.items():
548
            self.runs(name, from_version(version))
549
            self.throws(name, NAMEERROR, suggs, up_to_version(version))
550
551
    def test_import_sugg(self):
552
        """Should import module first."""
553
        module = 'collections'
554
        sugg = 'import {0}'.format(module)
555
        typo, good_code = module, sugg + '\n' + module
556
        self.assertFalse(module in locals())
557
        self.assertFalse(module in globals())
558
        self.assertTrue(module in STAND_MODULES)
559
        suggestions = (
560
            # module.module is suggested on Python 3.3 :-/
561
            ["'{0}' from {1} (not imported)".format(module, module)]
562
            if version_in_range(((3, 3), (3, 4))) else []) + \
563
            ['to {0} first'.format(sugg)]
564
        self.throws(typo, NAMEERROR, suggestions)
565
        self.runs(good_code)
566
567
    def test_attribute_hidden(self):
568
        """Should be math.pi but module math is hidden."""
569
        math  # just a way to say that math module is needed in globals
570
        self.assertFalse('math' in locals())
571
        self.assertTrue('math' in globals())
572
        code = 'math = ""\npi'
573
        self.throws(code, NAMEERROR, "'math.pi' (global hidden by local)")
574
575
    def test_self(self):
576
        """"Should be self.babar."""
577
        self.throws(
578
            'FoobarClass().nameerror_self()',
579
            NAMEERROR, "'self.babar'")
580
581
    def test_self2(self):
582
        """Should be self.this_is_cls_mthd."""
583
        self.throws(
584
            'FoobarClass().nameerror_self2()', NAMEERROR,
585
            ["'FoobarClass.this_is_cls_mthd'", "'self.this_is_cls_mthd'"])
586
587
    def test_cls(self):
588
        """Should be cls.this_is_cls_mthd."""
589
        self.throws(
590
            'FoobarClass().nameerror_cls()', NAMEERROR,
591
            ["'FoobarClass.this_is_cls_mthd'", "'cls.this_is_cls_mthd'"])
592
593
    def test_complex_numbers(self):
594
        """Should be 1j."""
595
        code = 'assert {0} ** 2 == -1'
596
        sugg = '1j'
597
        good_code, bad_code_i, bad_code_j = format_str(code, sugg, 'i', 'j')
598
        suggestion = "'" + sugg + "' (imaginary unit)"
599
        self.throws(bad_code_i, NAMEERROR, suggestion)
600
        self.throws(bad_code_j, NAMEERROR, suggestion)
601
        self.runs(good_code)
602
603
    def test_shell_commands(self):
604
        """Trying shell commands."""
605
        cmd, sugg = 'ls', 'os.listdir(os.getcwd())'
606
        self.throws(cmd, NAMEERROR, "'" + sugg + "'")
607
        self.runs(sugg)
608
        cmd, sugg = 'pwd', 'os.getcwd()'
609
        self.throws(cmd, NAMEERROR, "'" + sugg + "'")
610
        self.runs(sugg)
611
        cmd, sugg = 'cd', 'os.chdir(path)'
612
        self.throws(cmd, NAMEERROR, "'" + sugg + "'")
613
        self.runs(sugg.replace('path', 'os.getcwd()'))
614
        cmd = 'rm'
615
        sugg = "'os.remove(filename)', 'shutil.rmtree(dir)' for recursive"
616
        self.throws(cmd, NAMEERROR, sugg)
617
618
    def test_unmatched_msg(self):
619
        """Test that arbitrary strings are supported."""
620
        self.throws(
621
            'raise NameError("unmatched NAMEERROR")',
622
            UNKNOWN_NAMEERROR)
623
624
625
class UnboundLocalErrorTests(GetSuggestionsTests):
626
    """Class for tests related to UnboundLocalError."""
627
628
    def test_unbound_typo(self):
629
        """Should be foo."""
630
        code = 'def func():\n\tfoo = 1\n\t{0} +=1\nfunc()'
631
        typo, sugg = "foob", "foo"
632
        bad_code, good_code = format_str(code, typo, sugg)
633
        self.throws(bad_code, UNBOUNDLOCAL, "'" + sugg + "' (local)")
634
        self.runs(good_code)
635
636
    def test_unbound_global(self):
637
        """Should be global nb."""
638
        # NICE_TO_HAVE
639
        code = 'nb = 0\ndef func():\n\t{0}nb +=1\nfunc()'
640
        sugg = 'global nb'
641
        bad_code, good_code = format_str(code, "", sugg + "\n\t")
642
        self.throws(bad_code, UNBOUNDLOCAL)
643
        self.runs(good_code)  # this is to be run afterward :-/
644
645
    def test_unmatched_msg(self):
646
        """Test that arbitrary strings are supported."""
647
        self.throws(
648
            'raise UnboundLocalError("unmatched UNBOUNDLOCAL")',
649
            UNKNOWN_UNBOUNDLOCAL)
650
651
652
class AttributeErrorTests(GetSuggestionsTests):
653
    """Class for tests related to AttributeError."""
654
655
    def test_nonetype(self):
656
        """In-place methods like sort returns None.
657
658
        Might also happen if the functions misses a return.
659
        """
660
        # NICE_TO_HAVE
661
        code = '[].sort().append(4)'
662
        self.throws(code, ATTRIBUTEERROR)
663
664
    def test_method(self):
665
        """Should be 'append'."""
666
        code = '[0].{0}(1)'
667
        typo, sugg = 'appendh', 'append'
668
        bad_code, good_code = format_str(code, typo, sugg)
669
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
670
        self.runs(good_code)
671
672
    def test_builtin(self):
673
        """Should be 'max(lst)'."""
674
        bad_code, good_code = '[0].max()', 'max([0])'
675
        self.throws(bad_code, ATTRIBUTEERROR, "'max(list)'")
676
        self.runs(good_code)
677
678
    def test_builtin2(self):
679
        """Should be 'next(gen)'."""
680
        code = 'my_generator().next()'
681
        new_code = 'next(my_generator())'
682
        version = (3, 0)
683
        self.runs(code, up_to_version(version))
684
        self.throws(
685
            code, ATTRIBUTEERROR,
686
            "'next(generator)'",
687
            from_version(version))
688
        self.runs(new_code)
689
690
    def test_wrongmethod(self):
691
        """Should be 'lst.append(1)'."""
692
        code = '[0].{0}(1)'
693
        typo, sugg = 'add', 'append'
694
        bad_code, good_code = format_str(code, typo, sugg)
695
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
696
        self.runs(good_code)
697
698
    def test_wrongmethod2(self):
699
        """Should be 'lst.extend([4, 5, 6])'."""
700
        code = '[0].{0}([4, 5, 6])'
701
        typo, sugg = 'update', 'extend'
702
        bad_code, good_code = format_str(code, typo, sugg)
703
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
704
        self.runs(good_code)
705
706
    def test_hidden(self):
707
        """Accessing wrong string object."""
708
        # NICE_TO_HAVE
709
        code = 'import string\nstring = "a"\nascii = string.ascii_letters'
710
        self.throws(code, ATTRIBUTEERROR)
711
712
    def test_no_sugg(self):
713
        """No suggestion."""
714
        self.throws('[1, 2, 3].ldkjhfnvdlkjhvgfdhgf', ATTRIBUTEERROR)
715
716
    def test_from_module(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
717
        """Should be math.pi."""
718
        code = 'import math\nmath.{0}'
719
        typo, sugg = 'pie', 'pi'
720
        version = (3, 5)
721
        bad_code, good_code = format_str(code, typo, sugg)
722
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'",
723
                    up_to_version(version))
724
        self.throws(bad_code, MODATTRIBUTEERROR, "'" + sugg + "'",
725
                    from_version(version))
726
        self.runs(good_code)
727
728
    def test_from_module2(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
729
        """Should be math.pi."""
730
        code = 'import math\nm = math\nm.{0}'
731
        typo, sugg = 'pie', 'pi'
732
        version = (3, 5)
733
        bad_code, good_code = format_str(code, typo, sugg)
734
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'",
735
                    up_to_version(version))
736
        self.throws(bad_code, MODATTRIBUTEERROR, "'" + sugg + "'",
737
                    from_version(version))
738
        self.runs(good_code)
739
740
    def test_from_class(self):
741
        """Should be 'this_is_cls_mthd'."""
742
        code = 'FoobarClass().{0}()'
743
        typo, sugg = 'this_is_cls_mth', 'this_is_cls_mthd'
744
        bad_code, good_code = format_str(code, typo, sugg)
745
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
746
        self.runs(good_code)
747
748
    def test_from_class2(self):
749
        """Should be 'this_is_cls_mthd'."""
750
        code = 'FoobarClass.{0}()'
751
        typo, sugg = 'this_is_cls_mth', 'this_is_cls_mthd'
752
        bad_code, good_code = format_str(code, typo, sugg)
753
        self.throws(bad_code, ATTRIBUTEERROR, "'" + sugg + "'")
754
        self.runs(good_code)
755
756
    def test_private_attr(self):
757
        """Test that 'private' members are suggested with a warning message.
758
759
        Sometimes 'private' members are suggested but it's not ideal, a
760
        warning must be added to the suggestion.
761
        """
762
        code = 'FoobarClass().{0}'
763
        method = '__some_private_method'
764
        method2 = '_some_semi_private_method'
765
        typo, sugg, sugg2 = method, '_FoobarClass' + method, method2
766
        bad_code, bad_sugg, good_sugg = format_str(code, typo, sugg, sugg2)
767
        self.throws(
768
            bad_code,
769
            ATTRIBUTEERROR,
770
            ["'{0}' (but it is supposed to be private)".format(sugg),
771
             "'{0}'".format(sugg2)])
772
        self.runs(bad_sugg)
773
        self.runs(good_sugg)
774
775
    def test_removed_has_key(self):
776
        """Method has_key is removed from dict."""
777
        code = 'dict().has_key(1)'
778
        new_code = '1 in dict()'
779
        version = (3, 0)
780
        self.runs(code, up_to_version(version))
781
        self.throws(
782
            code,
783
            ATTRIBUTEERROR,
784
            "'key in dict'", from_version(version))
785
        self.runs(new_code)
786
787
    def test_removed_xreadlines(self):
788
        """Method xreadlines is removed."""
789
        # NICE_TO_HAVE
790
        code = "import os\nwith open(os.path.realpath(__file__)) as f:" \
791
            "\n\tf.{0}"
792
        old, sugg1, sugg2 = 'xreadlines', 'readline', 'readlines'
793
        old_code, new_code1, new_code2 = format_str(code, old, sugg1, sugg2)
794
        version = (3, 0)
795
        self.runs(old_code, up_to_version(version))
796
        self.throws(
797
            old_code,
798
            ATTRIBUTEERROR,
799
            ["'" + sugg1 + "'", "'" + sugg2 + "'", "'writelines'"],
800
            from_version(version))
801
        self.runs(new_code1)
802
        self.runs(new_code2)
803
804
    def test_removed_function_attributes(self):
805
        """Some functions attributes are removed."""
806
        # NICE_TO_HAVE
807
        version = (3, 0)
808
        code = func_gen() + 'some_func.{0}'
809
        attributes = [('func_name', '__name__', []),
810
                      ('func_doc', '__doc__', []),
811
                      ('func_defaults', '__defaults__', ["'__defaults__'"]),
812
                      ('func_dict', '__dict__', []),
813
                      ('func_closure', '__closure__', []),
814
                      ('func_globals', '__globals__', []),
815
                      ('func_code', '__code__', [])]
816
        for (old_att, new_att, sugg) in attributes:
817
            old_code, new_code = format_str(code, old_att, new_att)
818
            self.runs(old_code, up_to_version(version))
819
            self.throws(old_code, ATTRIBUTEERROR, sugg, from_version(version))
820
            self.runs(new_code)
821
822
    def test_removed_method_attributes(self):
823
        """Some methods attributes are removed."""
824
        # NICE_TO_HAVE
825
        version = (3, 0)
826
        code = 'FoobarClass().some_method.{0}'
827
        attributes = [('im_func', '__func__', []),
828
                      ('im_self', '__self__', []),
829
                      ('im_class', '__self__.__class__', ["'__class__'"])]
830
        for (old_att, new_att, sugg) in attributes:
831
            old_code, new_code = format_str(code, old_att, new_att)
832
            self.runs(old_code, up_to_version(version))
833
            self.throws(old_code, ATTRIBUTEERROR, sugg, from_version(version))
834
            self.runs(new_code)
835
836
    def test_join(self):
837
        """Test what happens when join is used incorrectly.
838
839
        This can be frustrating to call join on an iterable instead of a
840
        string, a suggestion could be nice.
841
        """
842
        # NICE_TO_HAVE
843
        code = "['a', 'b'].join('-')"
844
        self.throws(code, ATTRIBUTEERROR)
845
846
    def test_set_dict_comprehension(self):
847
        """{} creates a dict and not an empty set leading to errors."""
848
        # NICE_TO_HAVE
849
        version = (2, 7)
850
        for method in set(dir(set)) - set(dir(dict)):
851
            if not method.startswith('__'):  # boring suggestions
852
                code = "a = {0}\na." + method
853
                typo, dict1, dict2, sugg, set1 = format_str(
854
                    code, "{}", "dict()", "{0: 0}", "set()", "{0}")
855
                self.throws(typo, ATTRIBUTEERROR)
856
                self.throws(dict1, ATTRIBUTEERROR)
857
                self.throws(dict2, ATTRIBUTEERROR)
858
                self.runs(sugg)
859
                self.throws(set1, INVALIDSYNTAX, [], up_to_version(version))
860
                self.runs(set1, from_version(version))
861
862
    def test_unmatched_msg(self):
863
        """Test that arbitrary strings are supported."""
864
        self.throws(
865
            'raise AttributeError("unmatched ATTRIBUTEERROR")',
866
            UNKNOWN_ATTRIBUTEERROR)
867
868
    # TODO: Add sugg for situation where self/cls is the missing parameter
869
870
871
class TypeErrorTests(GetSuggestionsTests):
872
    """Class for tests related to TypeError."""
873
874
    def test_unhashable(self):
875
        """Test that other errors do not crash."""
876
        self.throws('dict()[list()] = 1', UNHASHABLE)
877
878
    def test_not_sub(self):
879
        """Should be function call, not [] operator."""
880
        typo, sugg = '[2]', '(2)'
881
        code = func_gen(param='a') + 'some_func{0}'
882
        bad_code, good_code = format_str(code, typo, sugg)
883
        self.throws(bad_code, UNSUBSCRIBTABLE, "'function(value)'")
884
        self.runs(good_code)
885
886
    def test_method_called_on_class(self):
887
        """Test where a method is called on a class and not an instance.
888
889
        Forgetting parenthesis makes the difference between using an
890
        instance and using a type.
891
        """
892
        # NICE_TO_HAVE
893
        wrong_type = (DESCREXPECT, MUSTCALLWITHINST, NBARGERROR)
894
        not_iterable = (ARGNOTITERABLE, ARGNOTITERABLE, ARGNOTITERABLE)
895
        version = (3, 0)
896
        for code, (err_cy, err_pyp, err_pyp3) in [
897
                ('set{0}.add(0)', wrong_type),
898
                ('list{0}.append(0)', wrong_type),
899
                ('0 in list{0}', not_iterable)]:
900
            bad_code, good_code = format_str(code, '', '()')
901
            self.runs(good_code)
902
            self.throws(bad_code, err_cy, [], ALL_VERSIONS, 'cython')
903
            self.throws(bad_code, err_pyp, [], up_to_version(version), 'pypy')
904
            self.throws(bad_code, err_pyp3, [], from_version(version), 'pypy')
905
906
    def test_set_operations(self):
907
        """+, +=, etc doesn't work on sets. A suggestion would be nice."""
908
        # NICE_TO_HAVE
909
        typo1 = 'set() + set()'
910
        typo2 = 's = set()\ns += set()'
911
        code1 = 'set() | set()'
912
        code2 = 'set().union(set())'
913
        code3 = 'set().update(set())'
914
        self.throws(typo1, UNSUPPORTEDOPERAND)
915
        self.throws(typo2, UNSUPPORTEDOPERAND)
916
        self.runs(code1)
917
        self.runs(code2)
918
        self.runs(code3)
919
920
    def test_dict_operations(self):
921
        """+, +=, etc doesn't work on dicts. A suggestion would be nice."""
922
        # NICE_TO_HAVE
923
        typo1 = 'dict() + dict()'
924
        typo2 = 'd = dict()\nd += dict()'
925
        typo3 = 'dict() & dict()'
926
        self.throws(typo1, UNSUPPORTEDOPERAND)
927
        self.throws(typo2, UNSUPPORTEDOPERAND)
928
        self.throws(typo3, UNSUPPORTEDOPERAND)
929
        code1 = 'dict().update(dict())'
930
        self.runs(code1)
931
932
    def test_len_on_iterable(self):
933
        """len() can't be called on iterable (weird but understandable)."""
934
        code = 'len(my_generator())'
935
        sugg = 'len(list(my_generator()))'
936
        self.throws(code, OBJECTHASNOFUNC)
937
        self.runs(sugg)
938
939
    def test_nb_args(self):
940
        """Should have 1 arg."""
941
        typo, sugg = '1, 2', '1'
942
        code = func_gen(param='a', args='{0}')
943
        bad_code, good_code = format_str(code, typo, sugg)
944
        self.throws(bad_code, NBARGERROR)
945
        self.runs(good_code)
946
947
    def test_nb_args1(self):
948
        """Should have 0 args."""
949
        typo, sugg = '1', ''
950
        code = func_gen(param='', args='{0}')
951
        bad_code, good_code = format_str(code, typo, sugg)
952
        self.throws(bad_code, NBARGERROR)
953
        self.runs(good_code)
954
955
    def test_nb_args2(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
956
        """Should have 1 arg."""
957
        typo, sugg = '', '1'
958
        version = (3, 3)
959
        code = func_gen(param='a', args='{0}')
960
        bad_code, good_code = format_str(code, typo, sugg)
961
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
962
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
963
        self.runs(good_code)
964
965
    def test_nb_args3(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
966
        """Should have 3 args."""
967
        typo, sugg = '1', '1, 2, 3'
968
        version = (3, 3)
969
        code = func_gen(param='so, much, args', args='{0}')
970
        bad_code, good_code = format_str(code, typo, sugg)
971
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
972
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
973
        self.runs(good_code)
974
975
    def test_nb_args4(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
976
        """Should have 3 args."""
977
        typo, sugg = '', '1, 2, 3'
978
        version = (3, 3)
979
        code = func_gen(param='so, much, args', args='{0}')
980
        bad_code, good_code = format_str(code, typo, sugg)
981
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
982
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
983
        self.runs(good_code)
984
985
    def test_nb_args5(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
986
        """Should have 3 args."""
987
        typo, sugg = '1, 2', '1, 2, 3'
988
        version = (3, 3)
989
        code = func_gen(param='so, much, args', args='{0}')
990
        bad_code, good_code = format_str(code, typo, sugg)
991
        self.throws(bad_code, NBARGERROR, [], up_to_version(version))
992
        self.throws(bad_code, MISSINGPOSERROR, [], from_version(version))
993
        self.runs(good_code)
994
995
    def test_keyword_args(self):
996
        """Should be param 'babar' not 'a' but it's hard to guess."""
997
        typo, sugg = 'a', 'babar'
998
        code = func_gen(param=sugg, args='{0}=1')
999
        bad_code, good_code = format_str(code, typo, sugg)
1000
        self.throws(bad_code, UNEXPECTEDKWARG)
1001
        self.runs(good_code)
1002
1003
    def test_keyword_args2(self):
1004
        """Should be param 'abcdef' not 'abcdf'."""
1005
        typo, sugg = 'abcdf', 'abcdef'
1006
        code = func_gen(param=sugg, args='{0}=1')
1007
        bad_code, good_code = format_str(code, typo, sugg)
1008
        self.throws(bad_code, UNEXPECTEDKWARG, "'" + sugg + "'")
1009
        self.runs(good_code)
1010
1011
    def test_keyword_builtin(self):
1012
        """A few builtins (like int()) have a different error message."""
1013
        # NICE_TO_HAVE
1014
        # 'max', 'input', 'len', 'abs', 'all', etc have a specific error
1015
        # message and are not relevant here
1016
        for builtin in ['int', 'float', 'bool', 'complex']:
1017
            code = builtin + '(this_doesnt_exist=2)'
1018
            self.throws(code, UNEXPECTEDKWARG2, [], ALL_VERSIONS, 'cython')
1019
            self.throws(code, UNEXPECTEDKWARG, [], ALL_VERSIONS, 'pypy')
1020
1021
    def test_keyword_builtin_print(self):
1022
        """Builtin "print" has a different error message."""
1023
        # It would be NICE_TO_HAVE suggestions on keyword arguments
1024
        v3 = (3, 0)
1025
        code = "c = 'string'\nb = print(c, end_='toto')"
1026
        self.throws(code, INVALIDSYNTAX, [], up_to_version(v3))
1027
        self.throws(code, UNEXPECTEDKWARG2, [], from_version(v3), 'cython')
1028
        self.throws(code, UNEXPECTEDKWARG3, [], from_version(v3), 'pypy')
1029
1030
    def test_no_implicit_str_conv(self):
1031
        """Trying to concatenate a non-string value to a string."""
1032
        # NICE_TO_HAVE
1033
        code = '{0} + " things"'
1034
        typo, sugg = '12', 'str(12)'
1035
        bad_code, good_code = format_str(code, typo, sugg)
1036
        self.throws(bad_code, UNSUPPORTEDOPERAND)
1037
        self.runs(good_code)
1038
1039
    def test_no_implicit_str_conv2(self):
1040
        """Trying to concatenate a non-string value to a string."""
1041
        # NICE_TO_HAVE
1042
        code = '"things " + {0}'
1043
        typo, sugg = '12', 'str(12)'
1044
        bad_code, good_code = format_str(code, typo, sugg)
1045
        version = (3, 0)
1046
        self.throws(
1047
            bad_code, CANNOTCONCAT, [], up_to_version(version), 'cython')
1048
        self.throws(
1049
            bad_code, CANTCONVERT, [], from_version(version), 'cython')
1050
        self.throws(
1051
            bad_code, UNSUPPORTEDOPERAND, [], ALL_VERSIONS, 'pypy')
1052
        self.runs(good_code)
1053
1054
    def test_assignment_to_range(self):
1055
        """Trying to assign to range works on list, not on range."""
1056
        # NICE_TO_HAVE
1057
        code = '{0}[2] = 1'
1058
        typo, sugg = 'range(4)', 'list(range(4))'
1059
        version = (3, 0)
1060
        bad_code, good_code = format_str(code, typo, sugg)
1061
        self.runs(bad_code, up_to_version(version))
1062
        self.throws(bad_code, OBJECTDOESNOTSUPPORT, [], from_version(version))
1063
        self.runs(good_code)
1064
1065
    def test_not_callable(self):
1066
        """Sometimes, one uses parenthesis instead of brackets."""
1067
        typo, getitem = '(0)', '[0]'
1068
        for ex, sugg in {
1069
            '[0]': "'list[value]'",
1070
            '{0: 0}': "'dict[value]'",
1071
            '"a"': "'str[value]'",
1072
        }.items():
1073
            self.throws(ex + typo, NOTCALLABLE, sugg)
1074
            self.runs(ex + getitem)
1075
        for ex in ['1', 'set()']:
1076
            self.throws(ex + typo, NOTCALLABLE)
1077
1078
    def test_unmatched_msg(self):
1079
        """Test that arbitrary strings are supported."""
1080
        self.throws(
1081
            'raise TypeError("unmatched TYPEERROR")',
1082
            UNKNOWN_TYPEERROR)
1083
1084
1085
class ImportErrorTests(GetSuggestionsTests):
1086
    """Class for tests related to ImportError."""
1087
1088
    def test_no_module_no_sugg(self):
1089
        """No suggestion."""
1090
        self.throws('import fqslkdfjslkqdjfqsd', NOMODULE)
1091
1092
    def test_no_module(self):
1093
        """Should be 'math'."""
1094
        code = 'import {0}'
1095
        typo, sugg = 'maths', 'math'
1096
        self.assertTrue(sugg in STAND_MODULES)
1097
        bad_code, good_code = format_str(code, typo, sugg)
1098
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1099
        self.runs(good_code)
1100
1101
    def test_no_module2(self):
1102
        """Should be 'math'."""
1103
        code = 'from {0} import pi'
1104
        typo, sugg = 'maths', 'math'
1105
        self.assertTrue(sugg in STAND_MODULES)
1106
        bad_code, good_code = format_str(code, typo, sugg)
1107
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1108
        self.runs(good_code)
1109
1110
    def test_no_module3(self):
1111
        """Should be 'math'."""
1112
        code = 'import {0} as my_imported_math'
1113
        typo, sugg = 'maths', 'math'
1114
        self.assertTrue(sugg in STAND_MODULES)
1115
        bad_code, good_code = format_str(code, typo, sugg)
1116
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1117
        self.runs(good_code)
1118
1119
    def test_no_module4(self):
1120
        """Should be 'math'."""
1121
        code = 'from {0} import pi as three_something'
1122
        typo, sugg = 'maths', 'math'
1123
        self.assertTrue(sugg in STAND_MODULES)
1124
        bad_code, good_code = format_str(code, typo, sugg)
1125
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1126
        self.runs(good_code)
1127
1128
    def test_no_module5(self):
1129
        """Should be 'math'."""
1130
        code = '__import__("{0}")'
1131
        typo, sugg = 'maths', 'math'
1132
        self.assertTrue(sugg in STAND_MODULES)
1133
        bad_code, good_code = format_str(code, typo, sugg)
1134
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1135
        self.runs(good_code)
1136
1137
    def test_import_future_nomodule(self):
1138
        """Should be '__future__'."""
1139
        code = 'import {0}'
1140
        typo, sugg = '__future_', '__future__'
1141
        self.assertTrue(sugg in STAND_MODULES)
1142
        bad_code, good_code = format_str(code, typo, sugg)
1143
        self.throws(bad_code, NOMODULE, "'" + sugg + "'")
1144
        self.runs(good_code)
1145
1146
    def test_no_name_no_sugg(self):
1147
        """No suggestion."""
1148
        self.throws('from math import fsfsdfdjlkf', CANNOTIMPORT)
1149
1150
    def test_wrong_import(self):
1151
        """Should be 'math'."""
1152
        code = 'from {0} import pi'
1153
        typo, sugg = 'itertools', 'math'
1154
        self.assertTrue(sugg in STAND_MODULES)
1155
        bad_code, good_code = format_str(code, typo, sugg)
1156
        self.throws(bad_code, CANNOTIMPORT, "'" + good_code + "'")
1157
        self.runs(good_code)
1158
1159
    def test_typo_in_method(self):
1160
        """Should be 'pi'."""
1161
        code = 'from math import {0}'
1162
        typo, sugg = 'pie', 'pi'
1163
        bad_code, good_code = format_str(code, typo, sugg)
1164
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1165
        self.runs(good_code)
1166
1167
    def test_typo_in_method2(self):
1168
        """Should be 'pi'."""
1169
        code = 'from math import e, {0}, log'
1170
        typo, sugg = 'pie', 'pi'
1171
        bad_code, good_code = format_str(code, typo, sugg)
1172
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1173
        self.runs(good_code)
1174
1175
    def test_typo_in_method3(self):
1176
        """Should be 'pi'."""
1177
        code = 'from math import {0} as three_something'
1178
        typo, sugg = 'pie', 'pi'
1179
        bad_code, good_code = format_str(code, typo, sugg)
1180
        self.throws(bad_code, CANNOTIMPORT, "'" + sugg + "'")
1181
        self.runs(good_code)
1182
1183
    def test_unmatched_msg(self):
1184
        """Test that arbitrary strings are supported."""
1185
        self.throws(
1186
            'raise ImportError("unmatched IMPORTERROR")',
1187
            UNKNOWN_IMPORTERROR)
1188
1189
    def test_module_removed(self):
1190
        """Sometimes, modules are deleted/moved/renamed."""
1191
        # NICE_TO_HAVE
1192
        version1 = (2, 7)  # result for 2.6 seems to vary
1193
        version2 = (3, 0)
1194
        code = 'import {0}'
1195
        lower, upper = format_str(code, 'tkinter', 'Tkinter')
1196
        self.throws(lower, NOMODULE, [], (version1, version2))
1197
        self.throws(upper, NOMODULE, [], from_version(version2))
1198
1199
1200
class LookupErrorTests(GetSuggestionsTests):
1201
    """Class for tests related to LookupError."""
1202
1203
1204
class KeyErrorTests(LookupErrorTests):
1205
    """Class for tests related to KeyError."""
1206
1207
    def test_no_sugg(self):
1208
        """No suggestion."""
1209
        self.throws('dict()["ffdsqmjklfqsd"]', KEYERROR)
1210
1211
1212
class IndexErrorTests(LookupErrorTests):
1213
    """Class for tests related to IndexError."""
1214
1215
    def test_no_sugg(self):
1216
        """No suggestion."""
1217
        self.throws('list()[2]', OUTOFRANGE)
1218
1219
1220
class SyntaxErrorTests(GetSuggestionsTests):
1221
    """Class for tests related to SyntaxError."""
1222
1223
    def test_no_error(self):
1224
        """No error."""
1225
        self.runs("1 + 2 == 2")
1226
1227
    def test_yield_return_out_of_func(self):
1228
        """yield/return needs to be in functions."""
1229
        sugg = "to indent it"
1230
        self.throws("yield 1", OUTSIDEFUNC, sugg)
1231
        self.throws("return 1", OUTSIDEFUNC, ["'sys.exit([arg])'", sugg])
1232
1233
    def test_print(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1234
        """print is a functions now and needs parenthesis."""
1235
        # NICE_TO_HAVE
1236
        code, new_code = 'print ""', 'print("")'
1237
        version = (3, 0)
1238
        version2 = (3, 4)
1239
        self.runs(code, up_to_version(version))
1240
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1241
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1242
        self.runs(new_code)
1243
1244
    def test_exec(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1245
        """exec is a functions now and needs parenthesis."""
1246
        # NICE_TO_HAVE
1247
        code, new_code = 'exec "1"', 'exec("1")'
1248
        version = (3, 0)
1249
        version2 = (3, 4)
1250
        self.runs(code, up_to_version(version))
1251
        self.throws(code, INVALIDSYNTAX, [], (version, version2))
1252
        self.throws(code, INVALIDSYNTAX, [], from_version(version2))
1253
        self.runs(new_code)
1254
1255
    def test_old_comparison(self):
1256
        """<> comparison is removed, != always works."""
1257
        code = '1 {0} 2'
1258
        old, new = '<>', '!='
1259
        version = (3, 0)
1260
        old_code, new_code = format_str(code, old, new)
1261
        self.runs(old_code, up_to_version(version))
1262
        self.throws(
1263
            old_code,
1264
            INVALIDCOMP,
1265
            "'!='",
1266
            from_version(version),
1267
            'pypy')
1268
        self.throws(
1269
            old_code,
1270
            INVALIDSYNTAX,
1271
            "'!='",
1272
            from_version(version),
1273
            'cython')
1274
        self.runs(new_code)
1275
1276
    def test_missing_colon(self):
1277
        """Missing colon is a classic mistake."""
1278
        # NICE_TO_HAVE
1279
        code = "if True{0}\n\tpass"
1280
        bad_code, good_code = format_str(code, "", ":")
1281
        self.throws(bad_code, INVALIDSYNTAX)
1282
        self.runs(good_code)
1283
1284
    def test_simple_equal(self):
1285
        """'=' for comparison is a classic mistake."""
1286
        # NICE_TO_HAVE
1287
        code = "if 2 {0} 3:\n\tpass"
1288
        bad_code, good_code = format_str(code, "=", "==")
1289
        self.throws(bad_code, INVALIDSYNTAX)
1290
        self.runs(good_code)
1291
1292
    def test_keyword_as_identifier(self):
1293
        """Using a keyword as a variable name."""
1294
        # NICE_TO_HAVE
1295
        code = '{0} = 1'
1296
        bad_code, good_code = format_str(code, "from", "from_")
1297
        self.throws(bad_code, INVALIDSYNTAX)
1298
        self.runs(good_code)
1299
1300
    def test_increment(self):
1301
        """Trying to use '++' or '--'."""
1302
        # NICE_TO_HAVE
1303
        code = 'a = 0\na{0}'
1304
        for op in ('-', '+'):
1305
            typo, sugg = 2 * op, op + '=1'
1306
            bad_code, good_code = format_str(code, typo, sugg)
1307
            self.throws(bad_code, INVALIDSYNTAX)
1308
            self.runs(good_code)
1309
1310
    def test_wrong_bool_operator(self):
1311
        """Trying to use '&&' or '||'."""
1312
        # NICE_TO_HAVE
1313
        code = 'True {0} False'
1314
        for typo, sugg in (('&&', 'and'), ('||', 'or')):
1315
            bad_code, good_code = format_str(code, typo, sugg)
1316
            self.throws(bad_code, INVALIDSYNTAX)
1317
            self.runs(good_code)
1318
1319
    def test_import_future_not_first(self):
1320
        """Test what happens when import from __future__ is not first."""
1321
        code = 'a = 8/7\nfrom __future__ import division'
1322
        self.throws(code, FUTUREFIRST)
1323
1324
    def test_import_future_not_def(self):
1325
        """Should be 'division'."""
1326
        code = 'from __future__ import {0}'
1327
        typo, sugg = 'divisio', 'division'
1328
        bad_code, good_code = format_str(code, typo, sugg)
1329
        self.throws(bad_code, FUTFEATNOTDEF, "'" + sugg + "'")
1330
        self.runs(good_code)
1331
1332
    def test_unqualified_exec(self):
1333
        """Exec in nested functions."""
1334
        # NICE_TO_HAVE
1335
        version = (3, 0)
1336
        codes = [
1337
            "def func1():\n\tbar='1'\n\tdef func2():"
1338
            "\n\t\texec(bar)\n\tfunc2()\nfunc1()",
1339
            "def func1():\n\texec('1')\n\tdef func2():"
1340
            "\n\t\tTrue",
1341
        ]
1342
        for code in codes:
1343
            self.throws(code, UNQUALIFIED_EXEC, [], up_to_version(version))
1344
            self.runs(code, from_version(version))
1345
1346
    def test_import_star(self):
1347
        """'import *' in nested functions."""
1348
        # NICE_TO_HAVE
1349
        codes = [
1350
            "def func1():\n\tbar='1'\n\tdef func2():"
1351
            "\n\t\tfrom math import *\n\t\tTrue\n\tfunc2()\nfunc1()",
1352
            "def func1():\n\tfrom math import *"
1353
            "\n\tdef func2():\n\t\tTrue",
1354
        ]
1355
        with warnings.catch_warnings():
1356
            warnings.simplefilter("ignore", category=SyntaxWarning)
1357
            for code in codes:
1358
                self.throws(code, IMPORTSTAR, [])
1359
1360
    def test_unpack(self):
1361
        """Extended tuple unpacking does not work prior to Python 3."""
1362
        # NICE_TO_HAVE
1363
        version = (3, 0)
1364
        code = 'a, *b = (1, 2, 3)'
1365
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1366
        self.runs(code, from_version(version))
1367
1368
    def test_unpack2(self):
1369
        """Unpacking in function arguments was supported up to Python 3."""
1370
        # NICE_TO_HAVE
1371
        version = (3, 0)
1372
        code = 'def addpoints((x1, y1), (x2, y2)):\n\tpass'
1373
        self.runs(code, up_to_version(version))
1374
        self.throws(code, INVALIDSYNTAX, [], from_version(version))
1375
1376
    def test_nonlocal(self):
1377
        """nonlocal keyword is added in Python 3."""
1378
        # NICE_TO_HAVE
1379
        version = (3, 0)
1380
        code = 'def func():\n\tfoo = 1\n\tdef nested():\n\t\tnonlocal foo'
1381
        self.runs(code, from_version(version))
1382
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1383
1384
    def test_nonlocal2(self):
1385
        """nonlocal must be used only when binding exists."""
1386
        # NICE_TO_HAVE
1387
        version = (3, 0)
1388
        code = 'def func():\n\tdef nested():\n\t\tnonlocal foo'
1389
        self.throws(code, NOBINDING, [], from_version(version))
1390
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1391
1392
    def test_nonlocal3(self):
1393
        """nonlocal must be used only when binding to non-global exists."""
1394
        # NICE_TO_HAVE
1395
        version = (3, 0)
1396
        code = 'foo = 1\ndef func():\n\tdef nested():\n\t\tnonlocal foo'
1397
        self.throws(code, NOBINDING, [], from_version(version))
1398
        self.throws(code, INVALIDSYNTAX, [], up_to_version(version))
1399
1400
1401
class MemoryErrorTests(GetSuggestionsTests):
1402
    """Class for tests related to MemoryError."""
1403
1404
    def test_out_of_memory(self):
1405
        """Test what happens in case of MemoryError."""
1406
        code = '[0] * 999999999999999'
1407
        self.throws(code, MEMORYERROR)
1408
1409
    def test_out_of_memory_range(self):
1410
        """Test what happens in case of MemoryError."""
1411
        code = '{0}(999999999999999)'
1412
        typo, sugg = 'range', 'xrange'
1413
        bad_code, good_code = format_str(code, typo, sugg)
1414
        self.runs(bad_code, ALL_VERSIONS, 'pypy')
1415
        version = (2, 7)
1416
        version2 = (3, 0)
1417
        self.throws(
1418
            bad_code,
1419
            OVERFLOWERR, "'" + sugg + "'",
1420
            up_to_version(version),
1421
            'cython')
1422
        self.throws(
1423
            bad_code,
1424
            MEMORYERROR, "'" + sugg + "'",
1425
            (version, version2),
1426
            'cython')
1427
        self.runs(good_code, up_to_version(version2), 'cython')
1428
        self.runs(bad_code, from_version(version2), 'cython')
1429
1430
1431
class ValueErrorTests(GetSuggestionsTests):
1432
    """Class for tests related to ValueError."""
1433
1434
    def test_too_many_values(self):
1435
        """Unpack 4 values in 3 variables."""
1436
        code = 'a, b, c = [1, 2, 3, 4]'
1437
        version = (3, 0)
1438
        self.throws(code, EXPECTEDLENGTH, [], up_to_version(version), 'pypy')
1439
        self.throws(code, TOOMANYVALUES, [], from_version(version), 'pypy')
1440
        self.throws(code, TOOMANYVALUES, [], ALL_VERSIONS, 'cython')
1441
1442
    def test_not_enough_values(self):
1443
        """Unpack 2 values in 3 variables."""
1444
        code = 'a, b, c = [1, 2]'
1445
        version = (3, 0)
1446
        self.throws(code, EXPECTEDLENGTH, [], up_to_version(version), 'pypy')
1447
        self.throws(code, NEEDMOREVALUES, [], from_version(version), 'pypy')
1448
        self.throws(code, NEEDMOREVALUES, [], ALL_VERSIONS, 'cython')
1449
1450
    def test_conversion_fails(self):
1451
        """Conversion fails."""
1452
        self.throws('int("toto")', INVALIDLITERAL)
1453
1454
    def test_math_domain(self):
1455
        """Math function used out of its domain."""
1456
        code = 'import math\nlg = math.log(-1)'
1457
        self.throws(code, MATHDOMAIN)
1458
1459
    def test_zero_len_field_in_format(self):
1460
        """Format {} is not valid before Python 2.7."""
1461
        code = '"{0}".format(0)'
1462
        old, new = '{0}', '{}'
1463
        old_code, new_code = format_str(code, old, new)
1464
        version = (2, 7)
1465
        self.runs(old_code)
1466
        self.throws(new_code, ZEROLENERROR, '{0}', up_to_version(version))
1467
        self.runs(new_code, from_version(version))
1468
1469
1470
class IOErrorTests(GetSuggestionsTests):
1471
    """Class for tests related to IOError."""
1472
1473
    def test_no_such_file(self):
1474
        """File does not exist."""
1475
        code = 'with open("doesnotexist") as f:\n\tpass'
1476
        self.throws(code, NOFILE_IO)
1477
1478
    def test_no_such_file2(self):
1479
        """File does not exist."""
1480
        code = 'os.listdir("doesnotexist")'
1481
        self.throws(code, NOFILE_OS)
1482
1483
    def test_no_such_file_user(self):
1484
        """Suggestion when one needs to expanduser."""
1485
        code = 'os.listdir("{0}")'
1486
        typo, sugg = "~", os.path.expanduser("~")
1487
        bad_code, good_code = format_str(code, typo, sugg)
1488
        self.throws(
1489
            bad_code, NOFILE_OS,
1490
            "'" + sugg + "' (calling os.path.expanduser)")
1491
        self.runs(good_code)
1492
1493
    def test_no_such_file_vars(self):
1494
        """Suggestion when one needs to expandvars."""
1495
        code = 'os.listdir("{0}")'
1496
        key = 'HOME'
1497
        typo, sugg = "$" + key, os.path.expanduser("~")
1498
        original_home = os.environ.get('HOME')
1499
        os.environ[key] = sugg
1500
        bad_code, good_code = format_str(code, typo, sugg)
1501
        self.throws(
1502
            bad_code, NOFILE_OS,
1503
            "'" + sugg + "' (calling os.path.expandvars)")
1504
        self.runs(good_code)
1505
        if original_home is None:
1506
            del os.environ[key]
1507
        else:
1508
            os.environ[key] = original_home
1509
1510
    def create_tmp_dir_with_files(self, filelist):
1511
        """Create a temporary directory with files in it."""
1512
        tmpdir = tempfile.mkdtemp()
1513
        absfiles = [os.path.join(tmpdir, f) for f in filelist]
1514
        for name in absfiles:
1515
            open(name, 'a').close()
1516
        return (tmpdir, absfiles)
1517
1518
    def test_is_dir_empty(self):
1519
        """Suggestion when file is an empty directory."""
1520
        # Create empty temp dir
1521
        tmpdir, _ = self.create_tmp_dir_with_files([])
1522
        code = 'with open("{0}") as f:\n\tpass'
1523
        bad_code, _ = format_str(code, tmpdir, "TODO")
1524
        self.throws(
1525
            bad_code, ISADIR_IO, "to add content to {0} first".format(tmpdir))
1526
        rmtree(tmpdir)
1527
1528
    def test_is_dir_small(self):
1529
        """Suggestion when file is directory with a few files."""
1530
        # Create temp dir with a few files
1531
        nb_files = 3
1532
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
1533
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
1534
        code = 'with open("{0}") as f:\n\tpass'
1535
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
1536
        self.throws(
1537
            bad_code, ISADIR_IO,
1538
            "any of the 3 files in directory ('0.txt', '1.txt', '2.txt')")
1539
        self.runs(good_code)
1540
        rmtree(tmpdir)
1541
1542
    def test_is_dir_big(self):
1543
        """Suggestion when file is directory with many files."""
1544
        # Create temp dir with many files
1545
        tmpdir = tempfile.mkdtemp()
1546
        nb_files = 30
1547
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
1548
        tmpdir, absfiles = self.create_tmp_dir_with_files(files)
1549
        code = 'with open("{0}") as f:\n\tpass'
1550
        bad_code, good_code = format_str(code, tmpdir, absfiles[0])
1551
        self.throws(
1552
            bad_code, ISADIR_IO,
1553
            "any of the 30 files in directory "
1554
            "('0.txt', '1.txt', '10.txt', '11.txt', etc)")
1555
        self.runs(good_code)
1556
        rmtree(tmpdir)
1557
1558
    def test_is_not_dir(self):
1559
        """Suggestion when file is not a directory."""
1560
        code = 'with open("{0}") as f:\n\tpass'
1561
        code = 'os.listdir("{0}")'
1562
        typo, sugg = __file__, os.path.dirname(__file__)
1563
        bad_code, good_code = format_str(code, typo, sugg)
1564
        self.throws(
1565
            bad_code, NOTADIR_OS,
1566
            "'" + sugg + "' (calling os.path.dirname)")
1567
        self.runs(good_code)
1568
1569
    def test_dir_is_not_empty(self):
1570
        """Suggestion when directory is not empty."""
1571
        # NICE_TO_HAVE
1572
        nb_files = 3
1573
        files = sorted([str(i) + ".txt" for i in range(nb_files)])
1574
        tmpdir, _ = self.create_tmp_dir_with_files(files)
1575
        self.throws('os.rmdir("{0}")'.format(tmpdir), DIRNOTEMPTY_OS)
1576
        rmtree(tmpdir)  # this should be the suggestion
1577
1578
1579
class AnyErrorTests(GetSuggestionsTests):
1580
    """Class for tests not related to an error type in particular."""
1581
1582
    def test_wrong_except(self):
1583
        """Test where except is badly used and thus does not catch.
1584
1585
        Common mistake : "except Exc1, Exc2" doesn't catch Exc2.
1586
        Adding parenthesis solves the issue.
1587
        """
1588
        # NICE_TO_HAVE
1589
        version = (3, 0)
1590
        raised_exc, other_exc = KeyError, TypeError
1591
        raised, other = raised_exc.__name__, other_exc.__name__
1592
        code = "try:\n\traise {0}()\nexcept {{0}}:\n\tpass".format(raised)
1593
        typo = "{0}, {1}".format(other, raised)
1594
        sugg = "({0})".format(typo)
1595
        bad1, bad2, good1, good2 = format_str(code, typo, other, sugg, raised)
1596
        self.throws(bad1, (raised_exc, None), [], up_to_version(version))
1597
        self.throws(bad1, INVALIDSYNTAX, [], from_version(version))
1598
        self.throws(bad2, (raised_exc, None))
1599
        self.runs(good1)
1600
        self.runs(good2)
1601
1602
1603
if __name__ == '__main__':
1604
    print(sys.version_info)
1605
    unittest2.main()
1606