Add unit tests for calculator
This commit is contained in:
1 parent
05bfcd65df
commit
2e854cbeb6
2227 files changed
+408232
-1
No files matched your search
Whitespace-only changes.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
import ast
|
||||
import textwrap
|
||||
import unittest
|
||||
|
||||
from pyflakes import checker
|
||||
|
||||
__all__ = ['TestCase', 'skip', 'skipIf']
|
||||
|
||||
skip = unittest.skip
|
||||
skipIf = unittest.skipIf
|
||||
|
||||
|
||||
class TestCase(unittest.TestCase):
|
||||
|
||||
withDoctest = False
|
||||
|
||||
def flakes(self, input, *expectedOutputs, **kw):
|
||||
tree = ast.parse(textwrap.dedent(input))
|
||||
if kw.get('is_segment'):
|
||||
tree = tree.body[0]
|
||||
kw.pop('is_segment')
|
||||
w = checker.Checker(tree, withDoctest=self.withDoctest, **kw)
|
||||
outputs = [type(o) for o in w.messages]
|
||||
expectedOutputs = list(expectedOutputs)
|
||||
outputs.sort(key=lambda t: t.__name__)
|
||||
expectedOutputs.sort(key=lambda t: t.__name__)
|
||||
self.assertEqual(outputs, expectedOutputs, '''\
|
||||
for input:
|
||||
{}
|
||||
expected outputs:
|
||||
{!r}
|
||||
but got:
|
||||
{}'''.format(input, expectedOutputs, '\n'.join([str(o) for o in w.messages])))
|
||||
return w
|
||||
@@ -0,0 +1,781 @@
|
||||
"""
|
||||
Tests for L{pyflakes.scripts.pyflakes}.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
from pyflakes.checker import PYPY
|
||||
from pyflakes.messages import UnusedImport
|
||||
from pyflakes.reporter import Reporter
|
||||
from pyflakes.api import (
|
||||
main,
|
||||
check,
|
||||
checkPath,
|
||||
checkRecursive,
|
||||
iterSourceCode,
|
||||
)
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
def withStderrTo(stderr, f, *args, **kwargs):
|
||||
"""
|
||||
Call C{f} with C{sys.stderr} redirected to C{stderr}.
|
||||
"""
|
||||
(outer, sys.stderr) = (sys.stderr, stderr)
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
finally:
|
||||
sys.stderr = outer
|
||||
|
||||
|
||||
class Node:
|
||||
"""
|
||||
Mock an AST node.
|
||||
"""
|
||||
def __init__(self, lineno, col_offset=0):
|
||||
self.lineno = lineno
|
||||
self.col_offset = col_offset
|
||||
|
||||
|
||||
class SysStreamCapturing:
|
||||
"""Context manager capturing sys.stdin, sys.stdout and sys.stderr.
|
||||
|
||||
The file handles are replaced with a StringIO object.
|
||||
"""
|
||||
|
||||
def __init__(self, stdin):
|
||||
self._stdin = io.StringIO(stdin or '', newline=os.linesep)
|
||||
|
||||
def __enter__(self):
|
||||
self._orig_stdin = sys.stdin
|
||||
self._orig_stdout = sys.stdout
|
||||
self._orig_stderr = sys.stderr
|
||||
|
||||
sys.stdin = self._stdin
|
||||
sys.stdout = self._stdout_stringio = io.StringIO(newline=os.linesep)
|
||||
sys.stderr = self._stderr_stringio = io.StringIO(newline=os.linesep)
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.output = self._stdout_stringio.getvalue()
|
||||
self.error = self._stderr_stringio.getvalue()
|
||||
|
||||
sys.stdin = self._orig_stdin
|
||||
sys.stdout = self._orig_stdout
|
||||
sys.stderr = self._orig_stderr
|
||||
|
||||
|
||||
class LoggingReporter:
|
||||
"""
|
||||
Implementation of Reporter that just appends any error to a list.
|
||||
"""
|
||||
|
||||
def __init__(self, log):
|
||||
"""
|
||||
Construct a C{LoggingReporter}.
|
||||
|
||||
@param log: A list to append log messages to.
|
||||
"""
|
||||
self.log = log
|
||||
|
||||
def flake(self, message):
|
||||
self.log.append(('flake', str(message)))
|
||||
|
||||
def unexpectedError(self, filename, message):
|
||||
self.log.append(('unexpectedError', filename, message))
|
||||
|
||||
def syntaxError(self, filename, msg, lineno, offset, line):
|
||||
self.log.append(('syntaxError', filename, msg, lineno, offset, line))
|
||||
|
||||
|
||||
class TestIterSourceCode(TestCase):
|
||||
"""
|
||||
Tests for L{iterSourceCode}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
def makeEmptyFile(self, *parts):
|
||||
assert parts
|
||||
fpath = os.path.join(self.tempdir, *parts)
|
||||
open(fpath, 'a').close()
|
||||
return fpath
|
||||
|
||||
def test_emptyDirectory(self):
|
||||
"""
|
||||
There are no Python files in an empty directory.
|
||||
"""
|
||||
self.assertEqual(list(iterSourceCode([self.tempdir])), [])
|
||||
|
||||
def test_singleFile(self):
|
||||
"""
|
||||
If the directory contains one Python file, C{iterSourceCode} will find
|
||||
it.
|
||||
"""
|
||||
childpath = self.makeEmptyFile('foo.py')
|
||||
self.assertEqual(list(iterSourceCode([self.tempdir])), [childpath])
|
||||
|
||||
def test_onlyPythonSource(self):
|
||||
"""
|
||||
Files that are not Python source files are not included.
|
||||
"""
|
||||
self.makeEmptyFile('foo.pyc')
|
||||
self.assertEqual(list(iterSourceCode([self.tempdir])), [])
|
||||
|
||||
def test_recurses(self):
|
||||
"""
|
||||
If the Python files are hidden deep down in child directories, we will
|
||||
find them.
|
||||
"""
|
||||
os.mkdir(os.path.join(self.tempdir, 'foo'))
|
||||
apath = self.makeEmptyFile('foo', 'a.py')
|
||||
self.makeEmptyFile('foo', 'a.py~')
|
||||
os.mkdir(os.path.join(self.tempdir, 'bar'))
|
||||
bpath = self.makeEmptyFile('bar', 'b.py')
|
||||
cpath = self.makeEmptyFile('c.py')
|
||||
self.assertEqual(
|
||||
sorted(iterSourceCode([self.tempdir])),
|
||||
sorted([apath, bpath, cpath]))
|
||||
|
||||
def test_shebang(self):
|
||||
"""
|
||||
Find Python files that don't end with `.py`, but contain a Python
|
||||
shebang.
|
||||
"""
|
||||
python = os.path.join(self.tempdir, 'a')
|
||||
with open(python, 'w') as fd:
|
||||
fd.write('#!/usr/bin/env python\n')
|
||||
|
||||
self.makeEmptyFile('b')
|
||||
|
||||
with open(os.path.join(self.tempdir, 'c'), 'w') as fd:
|
||||
fd.write('hello\nworld\n')
|
||||
|
||||
python3 = os.path.join(self.tempdir, 'e')
|
||||
with open(python3, 'w') as fd:
|
||||
fd.write('#!/usr/bin/env python3\n')
|
||||
|
||||
pythonw = os.path.join(self.tempdir, 'f')
|
||||
with open(pythonw, 'w') as fd:
|
||||
fd.write('#!/usr/bin/env pythonw\n')
|
||||
|
||||
python3args = os.path.join(self.tempdir, 'g')
|
||||
with open(python3args, 'w') as fd:
|
||||
fd.write('#!/usr/bin/python3 -u\n')
|
||||
|
||||
python3d = os.path.join(self.tempdir, 'i')
|
||||
with open(python3d, 'w') as fd:
|
||||
fd.write('#!/usr/local/bin/python3d\n')
|
||||
|
||||
python38m = os.path.join(self.tempdir, 'j')
|
||||
with open(python38m, 'w') as fd:
|
||||
fd.write('#! /usr/bin/env python3.8m\n')
|
||||
|
||||
# Should NOT be treated as Python source
|
||||
notfirst = os.path.join(self.tempdir, 'l')
|
||||
with open(notfirst, 'w') as fd:
|
||||
fd.write('#!/bin/sh\n#!/usr/bin/python\n')
|
||||
|
||||
self.assertEqual(
|
||||
sorted(iterSourceCode([self.tempdir])),
|
||||
sorted([
|
||||
python, python3, pythonw, python3args, python3d,
|
||||
python38m,
|
||||
]))
|
||||
|
||||
def test_multipleDirectories(self):
|
||||
"""
|
||||
L{iterSourceCode} can be given multiple directories. It will recurse
|
||||
into each of them.
|
||||
"""
|
||||
foopath = os.path.join(self.tempdir, 'foo')
|
||||
barpath = os.path.join(self.tempdir, 'bar')
|
||||
os.mkdir(foopath)
|
||||
apath = self.makeEmptyFile('foo', 'a.py')
|
||||
os.mkdir(barpath)
|
||||
bpath = self.makeEmptyFile('bar', 'b.py')
|
||||
self.assertEqual(
|
||||
sorted(iterSourceCode([foopath, barpath])),
|
||||
sorted([apath, bpath]))
|
||||
|
||||
def test_explicitFiles(self):
|
||||
"""
|
||||
If one of the paths given to L{iterSourceCode} is not a directory but
|
||||
a file, it will include that in its output.
|
||||
"""
|
||||
epath = self.makeEmptyFile('e.py')
|
||||
self.assertEqual(list(iterSourceCode([epath])),
|
||||
[epath])
|
||||
|
||||
|
||||
class TestReporter(TestCase):
|
||||
"""
|
||||
Tests for L{Reporter}.
|
||||
"""
|
||||
|
||||
def test_syntaxError(self):
|
||||
"""
|
||||
C{syntaxError} reports that there was a syntax error in the source
|
||||
file. It reports to the error stream and includes the filename, line
|
||||
number, error message, actual line of source and a caret pointing to
|
||||
where the error is.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
reporter = Reporter(None, err)
|
||||
reporter.syntaxError('foo.py', 'a problem', 3, 8, 'bad line of source')
|
||||
self.assertEqual(
|
||||
("foo.py:3:8: a problem\n"
|
||||
"bad line of source\n"
|
||||
" ^\n"),
|
||||
err.getvalue())
|
||||
|
||||
def test_syntaxErrorNoOffset(self):
|
||||
"""
|
||||
C{syntaxError} doesn't include a caret pointing to the error if
|
||||
C{offset} is passed as C{None}.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
reporter = Reporter(None, err)
|
||||
reporter.syntaxError('foo.py', 'a problem', 3, None,
|
||||
'bad line of source')
|
||||
self.assertEqual(
|
||||
("foo.py:3: a problem\n"
|
||||
"bad line of source\n"),
|
||||
err.getvalue())
|
||||
|
||||
def test_syntaxErrorNoText(self):
|
||||
"""
|
||||
C{syntaxError} doesn't include text or nonsensical offsets if C{text} is C{None}.
|
||||
|
||||
This typically happens when reporting syntax errors from stdin.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
reporter = Reporter(None, err)
|
||||
reporter.syntaxError('<stdin>', 'a problem', 0, 0, None)
|
||||
self.assertEqual(("<stdin>:1:1: a problem\n"), err.getvalue())
|
||||
|
||||
def test_multiLineSyntaxError(self):
|
||||
"""
|
||||
If there's a multi-line syntax error, then we only report the last
|
||||
line. The offset is adjusted so that it is relative to the start of
|
||||
the last line.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
lines = [
|
||||
'bad line of source',
|
||||
'more bad lines of source',
|
||||
]
|
||||
reporter = Reporter(None, err)
|
||||
reporter.syntaxError('foo.py', 'a problem', 3, len(lines[0]) + 7,
|
||||
'\n'.join(lines))
|
||||
self.assertEqual(
|
||||
("foo.py:3:25: a problem\n" +
|
||||
lines[-1] + "\n" +
|
||||
" " * 24 + "^\n"),
|
||||
err.getvalue())
|
||||
|
||||
def test_unexpectedError(self):
|
||||
"""
|
||||
C{unexpectedError} reports an error processing a source file.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
reporter = Reporter(None, err)
|
||||
reporter.unexpectedError('source.py', 'error message')
|
||||
self.assertEqual('source.py: error message\n', err.getvalue())
|
||||
|
||||
def test_flake(self):
|
||||
"""
|
||||
C{flake} reports a code warning from Pyflakes. It is exactly the
|
||||
str() of a L{pyflakes.messages.Message}.
|
||||
"""
|
||||
out = io.StringIO()
|
||||
reporter = Reporter(out, None)
|
||||
message = UnusedImport('foo.py', Node(42), 'bar')
|
||||
reporter.flake(message)
|
||||
self.assertEqual(out.getvalue(), f"{message}\n")
|
||||
|
||||
|
||||
class CheckTests(TestCase):
|
||||
"""
|
||||
Tests for L{check} and L{checkPath} which check a file for flakes.
|
||||
"""
|
||||
|
||||
@contextlib.contextmanager
|
||||
def makeTempFile(self, content):
|
||||
"""
|
||||
Make a temporary file containing C{content} and return a path to it.
|
||||
"""
|
||||
fd, name = tempfile.mkstemp()
|
||||
try:
|
||||
with os.fdopen(fd, 'wb') as f:
|
||||
if not hasattr(content, 'decode'):
|
||||
content = content.encode('ascii')
|
||||
f.write(content)
|
||||
yield name
|
||||
finally:
|
||||
os.remove(name)
|
||||
|
||||
def assertHasErrors(self, path, errorList):
|
||||
"""
|
||||
Assert that C{path} causes errors.
|
||||
|
||||
@param path: A path to a file to check.
|
||||
@param errorList: A list of errors expected to be printed to stderr.
|
||||
"""
|
||||
err = io.StringIO()
|
||||
count = withStderrTo(err, checkPath, path)
|
||||
self.assertEqual(
|
||||
(count, err.getvalue()), (len(errorList), ''.join(errorList)))
|
||||
|
||||
def getErrors(self, path):
|
||||
"""
|
||||
Get any warnings or errors reported by pyflakes for the file at C{path}.
|
||||
|
||||
@param path: The path to a Python file on disk that pyflakes will check.
|
||||
@return: C{(count, log)}, where C{count} is the number of warnings or
|
||||
errors generated, and log is a list of those warnings, presented
|
||||
as structured data. See L{LoggingReporter} for more details.
|
||||
"""
|
||||
log = []
|
||||
reporter = LoggingReporter(log)
|
||||
count = checkPath(path, reporter)
|
||||
return count, log
|
||||
|
||||
def test_legacyScript(self):
|
||||
from pyflakes.scripts import pyflakes as script_pyflakes
|
||||
self.assertIs(script_pyflakes.checkPath, checkPath)
|
||||
|
||||
def test_missingTrailingNewline(self):
|
||||
"""
|
||||
Source which doesn't end with a newline shouldn't cause any
|
||||
exception to be raised nor an error indicator to be returned by
|
||||
L{check}.
|
||||
"""
|
||||
with self.makeTempFile("def foo():\n\tpass\n\t") as fName:
|
||||
self.assertHasErrors(fName, [])
|
||||
|
||||
def test_checkPathNonExisting(self):
|
||||
"""
|
||||
L{checkPath} handles non-existing files.
|
||||
"""
|
||||
count, errors = self.getErrors('extremo')
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(
|
||||
errors,
|
||||
[('unexpectedError', 'extremo', 'No such file or directory')])
|
||||
|
||||
def test_multilineSyntaxError(self):
|
||||
"""
|
||||
Source which includes a syntax error which results in the raised
|
||||
L{SyntaxError.text} containing multiple lines of source are reported
|
||||
with only the last line of that source.
|
||||
"""
|
||||
source = """\
|
||||
def foo():
|
||||
'''
|
||||
|
||||
def bar():
|
||||
pass
|
||||
|
||||
def baz():
|
||||
'''quux'''
|
||||
"""
|
||||
|
||||
# Sanity check - SyntaxError.text should be multiple lines, if it
|
||||
# isn't, something this test was unprepared for has happened.
|
||||
def evaluate(source):
|
||||
exec(source)
|
||||
try:
|
||||
evaluate(source)
|
||||
except SyntaxError as e:
|
||||
if not PYPY and sys.version_info < (3, 10):
|
||||
self.assertTrue(e.text.count('\n') > 1)
|
||||
else:
|
||||
self.fail()
|
||||
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
if PYPY:
|
||||
message = 'end of file (EOF) while scanning triple-quoted string literal'
|
||||
elif sys.version_info >= (3, 10):
|
||||
message = 'unterminated triple-quoted string literal (detected at line 8)' # noqa: E501
|
||||
else:
|
||||
message = 'invalid syntax'
|
||||
|
||||
if PYPY or sys.version_info >= (3, 10):
|
||||
column = 12
|
||||
else:
|
||||
column = 8
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
["""\
|
||||
%s:8:%d: %s
|
||||
'''quux'''
|
||||
%s^
|
||||
""" % (sourcePath, column, message, ' ' * (column - 1))])
|
||||
|
||||
def test_eofSyntaxError(self):
|
||||
"""
|
||||
The error reported for source files which end prematurely causing a
|
||||
syntax error reflects the cause for the syntax error.
|
||||
"""
|
||||
with self.makeTempFile("def foo(") as sourcePath:
|
||||
if PYPY:
|
||||
msg = 'parenthesis is never closed'
|
||||
elif sys.version_info >= (3, 10):
|
||||
msg = "'(' was never closed"
|
||||
else:
|
||||
msg = 'unexpected EOF while parsing'
|
||||
|
||||
if PYPY or sys.version_info >= (3, 10):
|
||||
column = 8
|
||||
else:
|
||||
column = 9
|
||||
|
||||
spaces = ' ' * (column - 1)
|
||||
expected = '{}:1:{}: {}\ndef foo(\n{}^\n'.format(
|
||||
sourcePath, column, msg, spaces
|
||||
)
|
||||
|
||||
self.assertHasErrors(sourcePath, [expected])
|
||||
|
||||
def test_eofSyntaxErrorWithTab(self):
|
||||
"""
|
||||
The error reported for source files which end prematurely causing a
|
||||
syntax error reflects the cause for the syntax error.
|
||||
"""
|
||||
with self.makeTempFile("if True:\n\tfoo =") as sourcePath:
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
[f"""\
|
||||
{sourcePath}:2:7: invalid syntax
|
||||
\tfoo =
|
||||
\t ^
|
||||
"""])
|
||||
|
||||
def test_nonDefaultFollowsDefaultSyntaxError(self):
|
||||
"""
|
||||
Source which has a non-default argument following a default argument
|
||||
should include the line number of the syntax error. However these
|
||||
exceptions do not include an offset.
|
||||
"""
|
||||
source = """\
|
||||
def foo(bar=baz, bax):
|
||||
pass
|
||||
"""
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
if sys.version_info >= (3, 12):
|
||||
msg = 'parameter without a default follows parameter with a default' # noqa: E501
|
||||
else:
|
||||
msg = 'non-default argument follows default argument'
|
||||
|
||||
if PYPY:
|
||||
column = 18
|
||||
elif sys.version_info >= (3, 10):
|
||||
column = 18
|
||||
else:
|
||||
column = 21
|
||||
last_line = ' ' * (column - 1) + '^\n'
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
[f"""\
|
||||
{sourcePath}:1:{column}: {msg}
|
||||
def foo(bar=baz, bax):
|
||||
{last_line}"""]
|
||||
)
|
||||
|
||||
def test_nonKeywordAfterKeywordSyntaxError(self):
|
||||
"""
|
||||
Source which has a non-keyword argument after a keyword argument should
|
||||
include the line number of the syntax error. However these exceptions
|
||||
do not include an offset.
|
||||
"""
|
||||
source = """\
|
||||
foo(bar=baz, bax)
|
||||
"""
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
last_line = ' ' * 16 + '^\n'
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
[f"""\
|
||||
{sourcePath}:1:17: positional argument follows keyword argument
|
||||
foo(bar=baz, bax)
|
||||
{last_line}"""])
|
||||
|
||||
def test_invalidEscape(self):
|
||||
"""
|
||||
The invalid escape syntax raises ValueError in Python 2
|
||||
"""
|
||||
# ValueError: invalid \x escape
|
||||
with self.makeTempFile(r"foo = '\xyz'") as sourcePath:
|
||||
position_end = 1
|
||||
if PYPY:
|
||||
column = 7
|
||||
elif sys.version_info < (3, 12):
|
||||
column = 13
|
||||
else:
|
||||
column = 7
|
||||
|
||||
last_line = '%s^\n' % (' ' * (column - 1))
|
||||
|
||||
decoding_error = """\
|
||||
%s:1:%d: (unicode error) 'unicodeescape' codec can't decode bytes \
|
||||
in position 0-%d: truncated \\xXX escape
|
||||
foo = '\\xyz'
|
||||
%s""" % (sourcePath, column, position_end, last_line)
|
||||
|
||||
self.assertHasErrors(
|
||||
sourcePath, [decoding_error])
|
||||
|
||||
@skipIf(sys.platform == 'win32', 'unsupported on Windows')
|
||||
def test_permissionDenied(self):
|
||||
"""
|
||||
If the source file is not readable, this is reported on standard
|
||||
error.
|
||||
"""
|
||||
if os.getuid() == 0:
|
||||
self.skipTest('root user can access all files regardless of '
|
||||
'permissions')
|
||||
with self.makeTempFile('') as sourcePath:
|
||||
os.chmod(sourcePath, 0)
|
||||
count, errors = self.getErrors(sourcePath)
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(
|
||||
errors,
|
||||
[('unexpectedError', sourcePath, "Permission denied")])
|
||||
|
||||
def test_pyflakesWarning(self):
|
||||
"""
|
||||
If the source file has a pyflakes warning, this is reported as a
|
||||
'flake'.
|
||||
"""
|
||||
with self.makeTempFile("import foo") as sourcePath:
|
||||
count, errors = self.getErrors(sourcePath)
|
||||
self.assertEqual(count, 1)
|
||||
self.assertEqual(
|
||||
errors, [('flake', str(UnusedImport(sourcePath, Node(1), 'foo')))])
|
||||
|
||||
def test_encodedFileUTF8(self):
|
||||
"""
|
||||
If source file declares the correct encoding, no error is reported.
|
||||
"""
|
||||
SNOWMAN = chr(0x2603)
|
||||
source = ("""\
|
||||
# coding: utf-8
|
||||
x = "%s"
|
||||
""" % SNOWMAN).encode('utf-8')
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
self.assertHasErrors(sourcePath, [])
|
||||
|
||||
def test_CRLFLineEndings(self):
|
||||
"""
|
||||
Source files with Windows CR LF line endings are parsed successfully.
|
||||
"""
|
||||
with self.makeTempFile("x = 42\r\n") as sourcePath:
|
||||
self.assertHasErrors(sourcePath, [])
|
||||
|
||||
def test_misencodedFileUTF8(self):
|
||||
"""
|
||||
If a source file contains bytes which cannot be decoded, this is
|
||||
reported on stderr.
|
||||
"""
|
||||
SNOWMAN = chr(0x2603)
|
||||
source = ("""\
|
||||
# coding: ascii
|
||||
x = "%s"
|
||||
""" % SNOWMAN).encode('utf-8')
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
self.assertHasErrors(
|
||||
sourcePath,
|
||||
[f"{sourcePath}:1:1: 'ascii' codec can't decode byte 0xe2 in position 21: ordinal not in range(128)\n"]) # noqa: E501
|
||||
|
||||
def test_misencodedFileUTF16(self):
|
||||
"""
|
||||
If a source file contains bytes which cannot be decoded, this is
|
||||
reported on stderr.
|
||||
"""
|
||||
SNOWMAN = chr(0x2603)
|
||||
source = ("""\
|
||||
# coding: ascii
|
||||
x = "%s"
|
||||
""" % SNOWMAN).encode('utf-16')
|
||||
with self.makeTempFile(source) as sourcePath:
|
||||
if sys.version_info < (3, 11, 4):
|
||||
expected = f"{sourcePath}: problem decoding source\n"
|
||||
else:
|
||||
expected = f"{sourcePath}:1: source code string cannot contain null bytes\n" # noqa: E501
|
||||
|
||||
self.assertHasErrors(sourcePath, [expected])
|
||||
|
||||
def test_checkRecursive(self):
|
||||
"""
|
||||
L{checkRecursive} descends into each directory, finding Python files
|
||||
and reporting problems.
|
||||
"""
|
||||
tempdir = tempfile.mkdtemp()
|
||||
try:
|
||||
os.mkdir(os.path.join(tempdir, 'foo'))
|
||||
file1 = os.path.join(tempdir, 'foo', 'bar.py')
|
||||
with open(file1, 'wb') as fd:
|
||||
fd.write(b"import baz\n")
|
||||
file2 = os.path.join(tempdir, 'baz.py')
|
||||
with open(file2, 'wb') as fd:
|
||||
fd.write(b"import contraband")
|
||||
log = []
|
||||
reporter = LoggingReporter(log)
|
||||
warnings = checkRecursive([tempdir], reporter)
|
||||
self.assertEqual(warnings, 2)
|
||||
self.assertEqual(
|
||||
sorted(log),
|
||||
sorted([('flake', str(UnusedImport(file1, Node(1), 'baz'))),
|
||||
('flake',
|
||||
str(UnusedImport(file2, Node(1), 'contraband')))]))
|
||||
finally:
|
||||
shutil.rmtree(tempdir)
|
||||
|
||||
def test_stdinReportsErrors(self):
|
||||
"""
|
||||
L{check} reports syntax errors from stdin
|
||||
"""
|
||||
source = "max(1 for i in range(10), key=lambda x: x+1)\n"
|
||||
err = io.StringIO()
|
||||
count = withStderrTo(err, check, source, "<stdin>")
|
||||
self.assertEqual(count, 1)
|
||||
errlines = err.getvalue().split("\n")[:-1]
|
||||
|
||||
expected_error = [
|
||||
"<stdin>:1:5: Generator expression must be parenthesized",
|
||||
"max(1 for i in range(10), key=lambda x: x+1)",
|
||||
" ^",
|
||||
]
|
||||
self.assertEqual(errlines, expected_error)
|
||||
|
||||
|
||||
class IntegrationTests(TestCase):
|
||||
"""
|
||||
Tests of the pyflakes script that actually spawn the script.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.tempdir = tempfile.mkdtemp()
|
||||
self.tempfilepath = os.path.join(self.tempdir, 'temp')
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tempdir)
|
||||
|
||||
def getPyflakesBinary(self):
|
||||
"""
|
||||
Return the path to the pyflakes binary.
|
||||
"""
|
||||
import pyflakes
|
||||
package_dir = os.path.dirname(pyflakes.__file__)
|
||||
return os.path.join(package_dir, '..', 'bin', 'pyflakes')
|
||||
|
||||
def runPyflakes(self, paths, stdin=None):
|
||||
"""
|
||||
Launch a subprocess running C{pyflakes}.
|
||||
|
||||
@param paths: Command-line arguments to pass to pyflakes.
|
||||
@param stdin: Text to use as stdin.
|
||||
@return: C{(returncode, stdout, stderr)} of the completed pyflakes
|
||||
process.
|
||||
"""
|
||||
env = dict(os.environ)
|
||||
env['PYTHONPATH'] = os.pathsep.join(sys.path)
|
||||
command = [sys.executable, self.getPyflakesBinary()]
|
||||
command.extend(paths)
|
||||
if stdin:
|
||||
p = subprocess.Popen(command, env=env, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate(stdin.encode('ascii'))
|
||||
else:
|
||||
p = subprocess.Popen(command, env=env,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
(stdout, stderr) = p.communicate()
|
||||
rv = p.wait()
|
||||
stdout = stdout.decode('utf-8')
|
||||
stderr = stderr.decode('utf-8')
|
||||
return (stdout, stderr, rv)
|
||||
|
||||
def test_goodFile(self):
|
||||
"""
|
||||
When a Python source file is all good, the return code is zero and no
|
||||
messages are printed to either stdout or stderr.
|
||||
"""
|
||||
open(self.tempfilepath, 'a').close()
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
self.assertEqual(d, ('', '', 0))
|
||||
|
||||
def test_fileWithFlakes(self):
|
||||
"""
|
||||
When a Python source file has warnings, the return code is non-zero
|
||||
and the warnings are printed to stdout.
|
||||
"""
|
||||
with open(self.tempfilepath, 'wb') as fd:
|
||||
fd.write(b"import contraband\n")
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
expected = UnusedImport(self.tempfilepath, Node(1), 'contraband')
|
||||
self.assertEqual(d, (f"{expected}{os.linesep}", '', 1))
|
||||
|
||||
def test_errors_io(self):
|
||||
"""
|
||||
When pyflakes finds errors with the files it's given, (if they don't
|
||||
exist, say), then the return code is non-zero and the errors are
|
||||
printed to stderr.
|
||||
"""
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
error_msg = '{}: No such file or directory{}'.format(self.tempfilepath,
|
||||
os.linesep)
|
||||
self.assertEqual(d, ('', error_msg, 1))
|
||||
|
||||
def test_errors_syntax(self):
|
||||
"""
|
||||
When pyflakes finds errors with the files it's given, (if they don't
|
||||
exist, say), then the return code is non-zero and the errors are
|
||||
printed to stderr.
|
||||
"""
|
||||
with open(self.tempfilepath, 'wb') as fd:
|
||||
fd.write(b"import")
|
||||
d = self.runPyflakes([self.tempfilepath])
|
||||
|
||||
if sys.version_info >= (3, 13):
|
||||
message = "Expected one or more names after 'import'"
|
||||
else:
|
||||
message = 'invalid syntax'
|
||||
|
||||
error_msg = '{0}:1:7: {1}{2}import{2} ^{2}'.format(
|
||||
self.tempfilepath, message, os.linesep)
|
||||
self.assertEqual(d, ('', error_msg, 1))
|
||||
|
||||
def test_readFromStdin(self):
|
||||
"""
|
||||
If no arguments are passed to C{pyflakes} then it reads from stdin.
|
||||
"""
|
||||
d = self.runPyflakes([], stdin='import contraband')
|
||||
expected = UnusedImport('<stdin>', Node(1), 'contraband')
|
||||
self.assertEqual(d, (f"{expected}{os.linesep}", '', 1))
|
||||
|
||||
|
||||
class TestMain(IntegrationTests):
|
||||
"""
|
||||
Tests of the pyflakes main function.
|
||||
"""
|
||||
def runPyflakes(self, paths, stdin=None):
|
||||
try:
|
||||
with SysStreamCapturing(stdin) as capture:
|
||||
main(args=paths)
|
||||
except SystemExit as e:
|
||||
self.assertIsInstance(e.code, bool)
|
||||
rv = int(e.code)
|
||||
return (capture.output, capture.error, rv)
|
||||
else:
|
||||
raise RuntimeError('SystemExit not raised')
|
||||
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Tests for detecting redefinition of builtins.
|
||||
"""
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.test.harness import TestCase
|
||||
|
||||
|
||||
class TestBuiltins(TestCase):
|
||||
|
||||
def test_builtin_unbound_local(self):
|
||||
self.flakes('''
|
||||
def foo():
|
||||
a = range(1, 10)
|
||||
range = a
|
||||
return range
|
||||
|
||||
foo()
|
||||
|
||||
print(range)
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_global_shadowing_builtin(self):
|
||||
self.flakes('''
|
||||
def f():
|
||||
global range
|
||||
range = None
|
||||
print(range)
|
||||
|
||||
f()
|
||||
''')
|
||||
@@ -0,0 +1,129 @@
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.checker import (FunctionScope, ClassScope, ModuleScope,
|
||||
Argument, FunctionDefinition, Assignment)
|
||||
from pyflakes.test.harness import TestCase
|
||||
|
||||
|
||||
class TestCodeSegments(TestCase):
|
||||
"""
|
||||
Tests for segments of a module
|
||||
"""
|
||||
|
||||
def test_function_segment(self):
|
||||
self.flakes('''
|
||||
def foo():
|
||||
def bar():
|
||||
pass
|
||||
''', is_segment=True)
|
||||
|
||||
self.flakes('''
|
||||
def foo():
|
||||
def bar():
|
||||
x = 0
|
||||
''', m.UnusedVariable, is_segment=True)
|
||||
|
||||
def test_class_segment(self):
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
class Bar:
|
||||
pass
|
||||
''', is_segment=True)
|
||||
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
def bar():
|
||||
x = 0
|
||||
''', m.UnusedVariable, is_segment=True)
|
||||
|
||||
def test_scope_class(self):
|
||||
checker = self.flakes('''
|
||||
class Foo:
|
||||
x = 0
|
||||
def bar(a, b=1, *d, **e):
|
||||
pass
|
||||
''', is_segment=True)
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
class_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ClassScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
# Ensure module scope is not present because we are analysing
|
||||
# the inner contents of Foo
|
||||
self.assertEqual(len(module_scopes), 0)
|
||||
self.assertEqual(len(class_scopes), 1)
|
||||
self.assertEqual(len(function_scopes), 1)
|
||||
|
||||
class_scope = class_scopes[0]
|
||||
function_scope = function_scopes[0]
|
||||
|
||||
self.assertIsInstance(class_scope, ClassScope)
|
||||
self.assertIsInstance(function_scope, FunctionScope)
|
||||
|
||||
self.assertIn('x', class_scope)
|
||||
self.assertIn('bar', class_scope)
|
||||
|
||||
self.assertIn('a', function_scope)
|
||||
self.assertIn('b', function_scope)
|
||||
self.assertIn('d', function_scope)
|
||||
self.assertIn('e', function_scope)
|
||||
|
||||
self.assertIsInstance(class_scope['bar'], FunctionDefinition)
|
||||
self.assertIsInstance(class_scope['x'], Assignment)
|
||||
|
||||
self.assertIsInstance(function_scope['a'], Argument)
|
||||
self.assertIsInstance(function_scope['b'], Argument)
|
||||
self.assertIsInstance(function_scope['d'], Argument)
|
||||
self.assertIsInstance(function_scope['e'], Argument)
|
||||
|
||||
def test_scope_function(self):
|
||||
checker = self.flakes('''
|
||||
def foo(a, b=1, *d, **e):
|
||||
def bar(f, g=1, *h, **i):
|
||||
pass
|
||||
''', is_segment=True)
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
# Ensure module scope is not present because we are analysing
|
||||
# the inner contents of foo
|
||||
self.assertEqual(len(module_scopes), 0)
|
||||
self.assertEqual(len(function_scopes), 2)
|
||||
|
||||
function_scope_foo = function_scopes[1]
|
||||
function_scope_bar = function_scopes[0]
|
||||
|
||||
self.assertIsInstance(function_scope_foo, FunctionScope)
|
||||
self.assertIsInstance(function_scope_bar, FunctionScope)
|
||||
|
||||
self.assertIn('a', function_scope_foo)
|
||||
self.assertIn('b', function_scope_foo)
|
||||
self.assertIn('d', function_scope_foo)
|
||||
self.assertIn('e', function_scope_foo)
|
||||
self.assertIn('bar', function_scope_foo)
|
||||
|
||||
self.assertIn('f', function_scope_bar)
|
||||
self.assertIn('g', function_scope_bar)
|
||||
self.assertIn('h', function_scope_bar)
|
||||
self.assertIn('i', function_scope_bar)
|
||||
|
||||
self.assertIsInstance(function_scope_foo['bar'], FunctionDefinition)
|
||||
self.assertIsInstance(function_scope_foo['a'], Argument)
|
||||
self.assertIsInstance(function_scope_foo['b'], Argument)
|
||||
self.assertIsInstance(function_scope_foo['d'], Argument)
|
||||
self.assertIsInstance(function_scope_foo['e'], Argument)
|
||||
|
||||
self.assertIsInstance(function_scope_bar['f'], Argument)
|
||||
self.assertIsInstance(function_scope_bar['g'], Argument)
|
||||
self.assertIsInstance(function_scope_bar['h'], Argument)
|
||||
self.assertIsInstance(function_scope_bar['i'], Argument)
|
||||
|
||||
def test_scope_async_function(self):
|
||||
self.flakes('async def foo(): pass', is_segment=True)
|
||||
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
Tests for dict duplicate keys Pyflakes behavior.
|
||||
"""
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.test.harness import TestCase
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
|
||||
def test_duplicate_keys(self):
|
||||
self.flakes(
|
||||
"{'yes': 1, 'yes': 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_bytes_vs_unicode_py3(self):
|
||||
self.flakes("{b'a': 1, u'a': 2}")
|
||||
|
||||
def test_duplicate_values_bytes_vs_unicode_py3(self):
|
||||
self.flakes(
|
||||
"{1: b'a', 1: u'a'}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_multiple_duplicate_keys(self):
|
||||
self.flakes(
|
||||
"{'yes': 1, 'yes': 2, 'no': 2, 'no': 3}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_in_function(self):
|
||||
self.flakes(
|
||||
'''
|
||||
def f(thing):
|
||||
pass
|
||||
f({'yes': 1, 'yes': 2})
|
||||
''',
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_in_lambda(self):
|
||||
self.flakes(
|
||||
"lambda x: {(0,1): 1, (0,1): 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_tuples(self):
|
||||
self.flakes(
|
||||
"{(0,1): 1, (0,1): 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_tuples_int_and_float(self):
|
||||
self.flakes(
|
||||
"{(0,1): 1, (0,1.0): 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_ints(self):
|
||||
self.flakes(
|
||||
"{1: 1, 1: 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_bools(self):
|
||||
self.flakes(
|
||||
"{True: 1, True: 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_bools_false(self):
|
||||
# Needed to ensure 2.x correctly coerces these from variables
|
||||
self.flakes(
|
||||
"{False: 1, False: 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_keys_none(self):
|
||||
self.flakes(
|
||||
"{None: 1, None: 2}",
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_variable_keys(self):
|
||||
self.flakes(
|
||||
'''
|
||||
a = 1
|
||||
{a: 1, a: 2}
|
||||
''',
|
||||
m.MultiValueRepeatedKeyVariable,
|
||||
m.MultiValueRepeatedKeyVariable,
|
||||
)
|
||||
|
||||
def test_duplicate_variable_values(self):
|
||||
self.flakes(
|
||||
'''
|
||||
a = 1
|
||||
b = 2
|
||||
{1: a, 1: b}
|
||||
''',
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_variable_values_same_value(self):
|
||||
# Current behaviour is not to look up variable values. This is to
|
||||
# confirm that.
|
||||
self.flakes(
|
||||
'''
|
||||
a = 1
|
||||
b = 1
|
||||
{1: a, 1: b}
|
||||
''',
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_duplicate_key_float_and_int(self):
|
||||
"""
|
||||
These do look like different values, but when it comes to their use as
|
||||
keys, they compare as equal and so are actually duplicates.
|
||||
The literal dict {1: 1, 1.0: 1} actually becomes {1.0: 1}.
|
||||
"""
|
||||
self.flakes(
|
||||
'''
|
||||
{1: 1, 1.0: 2}
|
||||
''',
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
m.MultiValueRepeatedKeyLiteral,
|
||||
)
|
||||
|
||||
def test_no_duplicate_key_error_same_value(self):
|
||||
self.flakes('''
|
||||
{'yes': 1, 'yes': 1}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors(self):
|
||||
self.flakes('''
|
||||
{'yes': 1, 'no': 2}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_keys_tuples_same_first_element(self):
|
||||
self.flakes("{(0,1): 1, (0,2): 1}")
|
||||
|
||||
def test_no_duplicate_key_errors_func_call(self):
|
||||
self.flakes('''
|
||||
def test(thing):
|
||||
pass
|
||||
test({True: 1, None: 2, False: 1})
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors_bool_or_none(self):
|
||||
self.flakes("{True: 1, None: 2, False: 1}")
|
||||
|
||||
def test_no_duplicate_key_errors_ints(self):
|
||||
self.flakes('''
|
||||
{1: 1, 2: 1}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors_vars(self):
|
||||
self.flakes('''
|
||||
test = 'yes'
|
||||
rest = 'yes'
|
||||
{test: 1, rest: 2}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors_tuples(self):
|
||||
self.flakes('''
|
||||
{(0,1): 1, (0,2): 1}
|
||||
''')
|
||||
|
||||
def test_no_duplicate_key_errors_instance_attributes(self):
|
||||
self.flakes('''
|
||||
class Test():
|
||||
pass
|
||||
f = Test()
|
||||
f.a = 1
|
||||
{f.a: 1, f.a: 1}
|
||||
''')
|
||||
@@ -0,0 +1,444 @@
|
||||
import textwrap
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.checker import (
|
||||
PYPY,
|
||||
DoctestScope,
|
||||
FunctionScope,
|
||||
ModuleScope,
|
||||
)
|
||||
from pyflakes.test.test_other import Test as TestOther
|
||||
from pyflakes.test.test_imports import Test as TestImports
|
||||
from pyflakes.test.test_undefined_names import Test as TestUndefinedNames
|
||||
from pyflakes.test.harness import TestCase, skip
|
||||
|
||||
|
||||
class _DoctestMixin:
|
||||
|
||||
withDoctest = True
|
||||
|
||||
def doctestify(self, input):
|
||||
lines = []
|
||||
for line in textwrap.dedent(input).splitlines():
|
||||
if line.strip() == '':
|
||||
pass
|
||||
elif (line.startswith(' ') or
|
||||
line.startswith('except:') or
|
||||
line.startswith('except ') or
|
||||
line.startswith('finally:') or
|
||||
line.startswith('else:') or
|
||||
line.startswith('elif ') or
|
||||
(lines and lines[-1].startswith(('>>> @', '... @')))):
|
||||
line = "... %s" % line
|
||||
else:
|
||||
line = ">>> %s" % line
|
||||
lines.append(line)
|
||||
doctestificator = textwrap.dedent('''\
|
||||
def doctest_something():
|
||||
"""
|
||||
%s
|
||||
"""
|
||||
''')
|
||||
return doctestificator % "\n ".join(lines)
|
||||
|
||||
def flakes(self, input, *args, **kw):
|
||||
return super().flakes(self.doctestify(input), *args, **kw)
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
|
||||
withDoctest = True
|
||||
|
||||
def test_scope_class(self):
|
||||
"""Check that a doctest is given a DoctestScope."""
|
||||
checker = self.flakes("""
|
||||
m = None
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> d = doctest_stuff()
|
||||
'''
|
||||
f = m
|
||||
return f
|
||||
""")
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
doctest_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is DoctestScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
self.assertEqual(len(module_scopes), 1)
|
||||
self.assertEqual(len(doctest_scopes), 1)
|
||||
|
||||
module_scope = module_scopes[0]
|
||||
doctest_scope = doctest_scopes[0]
|
||||
|
||||
self.assertIsInstance(doctest_scope, DoctestScope)
|
||||
self.assertIsInstance(doctest_scope, ModuleScope)
|
||||
self.assertNotIsInstance(doctest_scope, FunctionScope)
|
||||
self.assertNotIsInstance(module_scope, DoctestScope)
|
||||
|
||||
self.assertIn('m', module_scope)
|
||||
self.assertIn('doctest_stuff', module_scope)
|
||||
|
||||
self.assertIn('d', doctest_scope)
|
||||
|
||||
self.assertEqual(len(function_scopes), 1)
|
||||
self.assertIn('f', function_scopes[0])
|
||||
|
||||
def test_nested_doctest_ignored(self):
|
||||
"""Check that nested doctests are ignored."""
|
||||
checker = self.flakes("""
|
||||
m = None
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> def function_in_doctest():
|
||||
... \"\"\"
|
||||
... >>> ignored_undefined_name
|
||||
... \"\"\"
|
||||
... df = m
|
||||
... return df
|
||||
...
|
||||
>>> function_in_doctest()
|
||||
'''
|
||||
f = m
|
||||
return f
|
||||
""")
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
doctest_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is DoctestScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
self.assertEqual(len(module_scopes), 1)
|
||||
self.assertEqual(len(doctest_scopes), 1)
|
||||
|
||||
module_scope = module_scopes[0]
|
||||
doctest_scope = doctest_scopes[0]
|
||||
|
||||
self.assertIn('m', module_scope)
|
||||
self.assertIn('doctest_stuff', module_scope)
|
||||
self.assertIn('function_in_doctest', doctest_scope)
|
||||
|
||||
self.assertEqual(len(function_scopes), 2)
|
||||
|
||||
self.assertIn('f', function_scopes[0])
|
||||
self.assertIn('df', function_scopes[1])
|
||||
|
||||
def test_global_module_scope_pollution(self):
|
||||
"""Check that global in doctest does not pollute module scope."""
|
||||
checker = self.flakes("""
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> def function_in_doctest():
|
||||
... global m
|
||||
... m = 50
|
||||
... df = 10
|
||||
... m = df
|
||||
...
|
||||
>>> function_in_doctest()
|
||||
'''
|
||||
f = 10
|
||||
return f
|
||||
|
||||
""")
|
||||
|
||||
scopes = checker.deadScopes
|
||||
module_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is ModuleScope]
|
||||
doctest_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is DoctestScope]
|
||||
function_scopes = [
|
||||
scope for scope in scopes if scope.__class__ is FunctionScope]
|
||||
|
||||
self.assertEqual(len(module_scopes), 1)
|
||||
self.assertEqual(len(doctest_scopes), 1)
|
||||
|
||||
module_scope = module_scopes[0]
|
||||
doctest_scope = doctest_scopes[0]
|
||||
|
||||
self.assertIn('doctest_stuff', module_scope)
|
||||
self.assertIn('function_in_doctest', doctest_scope)
|
||||
|
||||
self.assertEqual(len(function_scopes), 2)
|
||||
|
||||
self.assertIn('f', function_scopes[0])
|
||||
self.assertIn('df', function_scopes[1])
|
||||
self.assertIn('m', function_scopes[1])
|
||||
|
||||
self.assertNotIn('m', module_scope)
|
||||
|
||||
def test_global_undefined(self):
|
||||
self.flakes("""
|
||||
global m
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> m
|
||||
'''
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_nested_class(self):
|
||||
"""Doctest within nested class are processed."""
|
||||
self.flakes("""
|
||||
class C:
|
||||
class D:
|
||||
'''
|
||||
>>> m
|
||||
'''
|
||||
def doctest_stuff(self):
|
||||
'''
|
||||
>>> m
|
||||
'''
|
||||
return 1
|
||||
""", m.UndefinedName, m.UndefinedName)
|
||||
|
||||
def test_ignore_nested_function(self):
|
||||
"""Doctest module does not process doctest in nested functions."""
|
||||
# 'syntax error' would cause a SyntaxError if the doctest was processed.
|
||||
# However doctest does not find doctest in nested functions
|
||||
# (https://bugs.python.org/issue1650090). If nested functions were
|
||||
# processed, this use of m should cause UndefinedName, and the
|
||||
# name inner_function should probably exist in the doctest scope.
|
||||
self.flakes("""
|
||||
def doctest_stuff():
|
||||
def inner_function():
|
||||
'''
|
||||
>>> syntax error
|
||||
>>> inner_function()
|
||||
1
|
||||
>>> m
|
||||
'''
|
||||
return 1
|
||||
m = inner_function()
|
||||
return m
|
||||
""")
|
||||
|
||||
def test_inaccessible_scope_class(self):
|
||||
"""Doctest may not access class scope."""
|
||||
self.flakes("""
|
||||
class C:
|
||||
def doctest_stuff(self):
|
||||
'''
|
||||
>>> m
|
||||
'''
|
||||
return 1
|
||||
m = 1
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_importBeforeDoctest(self):
|
||||
self.flakes("""
|
||||
import foo
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> foo
|
||||
'''
|
||||
""")
|
||||
|
||||
@skip("todo")
|
||||
def test_importBeforeAndInDoctest(self):
|
||||
self.flakes('''
|
||||
import foo
|
||||
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> import foo
|
||||
>>> foo
|
||||
"""
|
||||
|
||||
foo
|
||||
''', m.RedefinedWhileUnused)
|
||||
|
||||
def test_importInDoctestAndAfter(self):
|
||||
self.flakes('''
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> import foo
|
||||
>>> foo
|
||||
"""
|
||||
|
||||
import foo
|
||||
foo()
|
||||
''')
|
||||
|
||||
def test_offsetInDoctests(self):
|
||||
exc = self.flakes('''
|
||||
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> x # line 5
|
||||
"""
|
||||
|
||||
''', m.UndefinedName).messages[0]
|
||||
self.assertEqual(exc.lineno, 5)
|
||||
self.assertEqual(exc.col, 12)
|
||||
|
||||
def test_offsetInLambdasInDoctests(self):
|
||||
exc = self.flakes('''
|
||||
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> lambda: x # line 5
|
||||
"""
|
||||
|
||||
''', m.UndefinedName).messages[0]
|
||||
self.assertEqual(exc.lineno, 5)
|
||||
self.assertEqual(exc.col, 20)
|
||||
|
||||
def test_offsetAfterDoctests(self):
|
||||
exc = self.flakes('''
|
||||
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> x = 5
|
||||
"""
|
||||
|
||||
x
|
||||
|
||||
''', m.UndefinedName).messages[0]
|
||||
self.assertEqual(exc.lineno, 8)
|
||||
self.assertEqual(exc.col, 0)
|
||||
|
||||
def test_syntaxErrorInDoctest(self):
|
||||
exceptions = self.flakes(
|
||||
'''
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> from # line 4
|
||||
>>> fortytwo = 42
|
||||
>>> except Exception:
|
||||
"""
|
||||
''',
|
||||
m.DoctestSyntaxError,
|
||||
m.DoctestSyntaxError,
|
||||
m.DoctestSyntaxError).messages
|
||||
exc = exceptions[0]
|
||||
self.assertEqual(exc.lineno, 4)
|
||||
if not PYPY:
|
||||
self.assertEqual(exc.col, 18)
|
||||
else:
|
||||
self.assertEqual(exc.col, 26)
|
||||
|
||||
# PyPy error column offset is 0,
|
||||
# for the second and third line of the doctest
|
||||
# i.e. at the beginning of the line
|
||||
exc = exceptions[1]
|
||||
self.assertEqual(exc.lineno, 5)
|
||||
if PYPY:
|
||||
self.assertEqual(exc.col, 13)
|
||||
else:
|
||||
self.assertEqual(exc.col, 16)
|
||||
exc = exceptions[2]
|
||||
self.assertEqual(exc.lineno, 6)
|
||||
self.assertEqual(exc.col, 13)
|
||||
|
||||
def test_indentationErrorInDoctest(self):
|
||||
exc = self.flakes('''
|
||||
def doctest_stuff():
|
||||
"""
|
||||
>>> if True:
|
||||
... pass
|
||||
"""
|
||||
''', m.DoctestSyntaxError).messages[0]
|
||||
self.assertEqual(exc.lineno, 5)
|
||||
self.assertEqual(exc.col, 13)
|
||||
|
||||
def test_offsetWithMultiLineArgs(self):
|
||||
(exc1, exc2) = self.flakes(
|
||||
'''
|
||||
def doctest_stuff(arg1,
|
||||
arg2,
|
||||
arg3):
|
||||
"""
|
||||
>>> assert
|
||||
>>> this
|
||||
"""
|
||||
''',
|
||||
m.DoctestSyntaxError,
|
||||
m.UndefinedName).messages
|
||||
self.assertEqual(exc1.lineno, 6)
|
||||
self.assertEqual(exc1.col, 19)
|
||||
self.assertEqual(exc2.lineno, 7)
|
||||
self.assertEqual(exc2.col, 12)
|
||||
|
||||
def test_doctestCanReferToFunction(self):
|
||||
self.flakes("""
|
||||
def foo():
|
||||
'''
|
||||
>>> foo
|
||||
'''
|
||||
""")
|
||||
|
||||
def test_doctestCanReferToClass(self):
|
||||
self.flakes("""
|
||||
class Foo():
|
||||
'''
|
||||
>>> Foo
|
||||
'''
|
||||
def bar(self):
|
||||
'''
|
||||
>>> Foo
|
||||
'''
|
||||
""")
|
||||
|
||||
def test_noOffsetSyntaxErrorInDoctest(self):
|
||||
exceptions = self.flakes(
|
||||
'''
|
||||
def buildurl(base, *args, **kwargs):
|
||||
"""
|
||||
>>> buildurl('/blah.php', ('a', '&'), ('b', '=')
|
||||
'/blah.php?a=%26&b=%3D'
|
||||
>>> buildurl('/blah.php', a='&', 'b'='=')
|
||||
'/blah.php?b=%3D&a=%26'
|
||||
"""
|
||||
pass
|
||||
''',
|
||||
m.DoctestSyntaxError,
|
||||
m.DoctestSyntaxError).messages
|
||||
exc = exceptions[0]
|
||||
self.assertEqual(exc.lineno, 4)
|
||||
exc = exceptions[1]
|
||||
self.assertEqual(exc.lineno, 6)
|
||||
|
||||
def test_singleUnderscoreInDoctest(self):
|
||||
self.flakes('''
|
||||
def func():
|
||||
"""A docstring
|
||||
|
||||
>>> func()
|
||||
1
|
||||
>>> _
|
||||
1
|
||||
"""
|
||||
return 1
|
||||
''')
|
||||
|
||||
def test_globalUnderscoreInDoctest(self):
|
||||
self.flakes("""
|
||||
from gettext import ugettext as _
|
||||
|
||||
def doctest_stuff():
|
||||
'''
|
||||
>>> pass
|
||||
'''
|
||||
""", m.UnusedImport)
|
||||
|
||||
|
||||
class TestOther(_DoctestMixin, TestOther):
|
||||
"""Run TestOther with each test wrapped in a doctest."""
|
||||
|
||||
|
||||
class TestImports(_DoctestMixin, TestImports):
|
||||
"""Run TestImports with each test wrapped in a doctest."""
|
||||
|
||||
|
||||
class TestUndefinedNames(_DoctestMixin, TestUndefinedNames):
|
||||
"""Run TestUndefinedNames with each test wrapped in a doctest."""
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,222 @@
|
||||
from pyflakes.messages import IsLiteral
|
||||
from pyflakes.test.harness import TestCase
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
def test_is_str(self):
|
||||
self.flakes("""
|
||||
x = 'foo'
|
||||
if x is 'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_bytes(self):
|
||||
self.flakes("""
|
||||
x = b'foo'
|
||||
if x is b'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_unicode(self):
|
||||
self.flakes("""
|
||||
x = u'foo'
|
||||
if x is u'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_int(self):
|
||||
self.flakes("""
|
||||
x = 10
|
||||
if x is 10:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_true(self):
|
||||
self.flakes("""
|
||||
x = True
|
||||
if x is True:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_is_false(self):
|
||||
self.flakes("""
|
||||
x = False
|
||||
if x is False:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_is_not_str(self):
|
||||
self.flakes("""
|
||||
x = 'foo'
|
||||
if x is not 'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_not_bytes(self):
|
||||
self.flakes("""
|
||||
x = b'foo'
|
||||
if x is not b'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_not_unicode(self):
|
||||
self.flakes("""
|
||||
x = u'foo'
|
||||
if x is not u'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_not_int(self):
|
||||
self.flakes("""
|
||||
x = 10
|
||||
if x is not 10:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_not_true(self):
|
||||
self.flakes("""
|
||||
x = True
|
||||
if x is not True:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_is_not_false(self):
|
||||
self.flakes("""
|
||||
x = False
|
||||
if x is not False:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_left_is_str(self):
|
||||
self.flakes("""
|
||||
x = 'foo'
|
||||
if 'foo' is x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_bytes(self):
|
||||
self.flakes("""
|
||||
x = b'foo'
|
||||
if b'foo' is x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_unicode(self):
|
||||
self.flakes("""
|
||||
x = u'foo'
|
||||
if u'foo' is x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_int(self):
|
||||
self.flakes("""
|
||||
x = 10
|
||||
if 10 is x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_true(self):
|
||||
self.flakes("""
|
||||
x = True
|
||||
if True is x:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_left_is_false(self):
|
||||
self.flakes("""
|
||||
x = False
|
||||
if False is x:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_left_is_not_str(self):
|
||||
self.flakes("""
|
||||
x = 'foo'
|
||||
if 'foo' is not x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_not_bytes(self):
|
||||
self.flakes("""
|
||||
x = b'foo'
|
||||
if b'foo' is not x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_not_unicode(self):
|
||||
self.flakes("""
|
||||
x = u'foo'
|
||||
if u'foo' is not x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_not_int(self):
|
||||
self.flakes("""
|
||||
x = 10
|
||||
if 10 is not x:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_left_is_not_true(self):
|
||||
self.flakes("""
|
||||
x = True
|
||||
if True is not x:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_left_is_not_false(self):
|
||||
self.flakes("""
|
||||
x = False
|
||||
if False is not x:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_chained_operators_is_true(self):
|
||||
self.flakes("""
|
||||
x = 5
|
||||
if x is True < 4:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_chained_operators_is_str(self):
|
||||
self.flakes("""
|
||||
x = 5
|
||||
if x is 'foo' < 4:
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_chained_operators_is_true_end(self):
|
||||
self.flakes("""
|
||||
x = 5
|
||||
if 4 < x is True:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_chained_operators_is_str_end(self):
|
||||
self.flakes("""
|
||||
x = 5
|
||||
if 4 < x is 'foo':
|
||||
pass
|
||||
""", IsLiteral)
|
||||
|
||||
def test_is_tuple_constant(self):
|
||||
self.flakes('''\
|
||||
x = 5
|
||||
if x is ():
|
||||
pass
|
||||
''', IsLiteral)
|
||||
|
||||
def test_is_tuple_constant_containing_constants(self):
|
||||
self.flakes('''\
|
||||
x = 5
|
||||
if x is (1, '2', True, (1.5, ())):
|
||||
pass
|
||||
''', IsLiteral)
|
||||
|
||||
def test_is_tuple_containing_variables_ok(self):
|
||||
# a bit nonsensical, but does not trigger a SyntaxWarning
|
||||
self.flakes('''\
|
||||
x = 5
|
||||
if x is (x,):
|
||||
pass
|
||||
''')
|
||||
@@ -0,0 +1,94 @@
|
||||
from sys import version_info
|
||||
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
@skipIf(version_info < (3, 10), "Python >= 3.10 only")
|
||||
class TestMatch(TestCase):
|
||||
def test_match_bindings(self):
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = 1
|
||||
match x:
|
||||
case 1 as y:
|
||||
print(f'matched as {y}')
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = [1, 2, 3]
|
||||
match x:
|
||||
case [1, y, 3]:
|
||||
print(f'matched {y}')
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = {'foo': 1}
|
||||
match x:
|
||||
case {'foo': y}:
|
||||
print(f'matched {y}')
|
||||
''')
|
||||
|
||||
def test_match_pattern_matched_class(self):
|
||||
self.flakes('''
|
||||
from a import B
|
||||
|
||||
match 1:
|
||||
case B(x=1) as y:
|
||||
print(f'matched {y}')
|
||||
''')
|
||||
self.flakes('''
|
||||
from a import B
|
||||
|
||||
match 1:
|
||||
case B(a, x=z) as y:
|
||||
print(f'matched {y} {a} {z}')
|
||||
''')
|
||||
|
||||
def test_match_placeholder(self):
|
||||
self.flakes('''
|
||||
def f():
|
||||
match 1:
|
||||
case _:
|
||||
print('catchall!')
|
||||
''')
|
||||
|
||||
def test_match_singleton(self):
|
||||
self.flakes('''
|
||||
match 1:
|
||||
case True:
|
||||
print('true')
|
||||
''')
|
||||
|
||||
def test_match_or_pattern(self):
|
||||
self.flakes('''
|
||||
match 1:
|
||||
case 1 | 2:
|
||||
print('one or two')
|
||||
''')
|
||||
|
||||
def test_match_star(self):
|
||||
self.flakes('''
|
||||
x = [1, 2, 3]
|
||||
match x:
|
||||
case [1, *y]:
|
||||
print(f'captured: {y}')
|
||||
''')
|
||||
|
||||
def test_match_double_star(self):
|
||||
self.flakes('''
|
||||
x = {'foo': 'bar', 'baz': 'womp'}
|
||||
match x:
|
||||
case {'foo': k1, **rest}:
|
||||
print(f'{k1=} {rest=}')
|
||||
''')
|
||||
|
||||
def test_defined_in_different_branches(self):
|
||||
self.flakes('''
|
||||
def f(x):
|
||||
match x:
|
||||
case 1:
|
||||
def y(): pass
|
||||
case _:
|
||||
def y(): print(1)
|
||||
return y
|
||||
''')
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,825 @@
|
||||
"""
|
||||
Tests for behaviour related to type annotations.
|
||||
"""
|
||||
|
||||
from sys import version_info
|
||||
|
||||
from pyflakes import messages as m
|
||||
from pyflakes.test.harness import TestCase, skipIf
|
||||
|
||||
|
||||
class TestTypeAnnotations(TestCase):
|
||||
|
||||
def test_typingOverload(self):
|
||||
"""Allow intentional redefinitions via @typing.overload"""
|
||||
self.flakes("""
|
||||
import typing
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
def f(s: None) -> None:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def f(s: int) -> int:
|
||||
pass
|
||||
|
||||
def f(s):
|
||||
return s
|
||||
|
||||
@typing.overload
|
||||
def g(s: None) -> None:
|
||||
pass
|
||||
|
||||
@typing.overload
|
||||
def g(s: int) -> int:
|
||||
pass
|
||||
|
||||
def g(s):
|
||||
return s
|
||||
""")
|
||||
|
||||
def test_typingExtensionsOverload(self):
|
||||
"""Allow intentional redefinitions via @typing_extensions.overload"""
|
||||
self.flakes("""
|
||||
import typing_extensions
|
||||
from typing_extensions import overload
|
||||
|
||||
@overload
|
||||
def f(s: None) -> None:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def f(s: int) -> int:
|
||||
pass
|
||||
|
||||
def f(s):
|
||||
return s
|
||||
|
||||
@typing_extensions.overload
|
||||
def g(s: None) -> None:
|
||||
pass
|
||||
|
||||
@typing_extensions.overload
|
||||
def g(s: int) -> int:
|
||||
pass
|
||||
|
||||
def g(s):
|
||||
return s
|
||||
""")
|
||||
|
||||
def test_typingOverloadAsync(self):
|
||||
"""Allow intentional redefinitions via @typing.overload (async)"""
|
||||
self.flakes("""
|
||||
from typing import overload
|
||||
|
||||
@overload
|
||||
async def f(s: None) -> None:
|
||||
pass
|
||||
|
||||
@overload
|
||||
async def f(s: int) -> int:
|
||||
pass
|
||||
|
||||
async def f(s):
|
||||
return s
|
||||
""")
|
||||
|
||||
def test_overload_with_multiple_decorators(self):
|
||||
self.flakes("""
|
||||
from typing import overload
|
||||
dec = lambda f: f
|
||||
|
||||
@dec
|
||||
@overload
|
||||
def f(x: int) -> int:
|
||||
pass
|
||||
|
||||
@dec
|
||||
@overload
|
||||
def f(x: str) -> str:
|
||||
pass
|
||||
|
||||
@dec
|
||||
def f(x): return x
|
||||
""")
|
||||
|
||||
def test_overload_in_class(self):
|
||||
self.flakes("""
|
||||
from typing import overload
|
||||
|
||||
class C:
|
||||
@overload
|
||||
def f(self, x: int) -> int:
|
||||
pass
|
||||
|
||||
@overload
|
||||
def f(self, x: str) -> str:
|
||||
pass
|
||||
|
||||
def f(self, x): return x
|
||||
""")
|
||||
|
||||
def test_aliased_import(self):
|
||||
"""Detect when typing is imported as another name"""
|
||||
self.flakes("""
|
||||
import typing as t
|
||||
|
||||
@t.overload
|
||||
def f(s: None) -> None:
|
||||
pass
|
||||
|
||||
@t.overload
|
||||
def f(s: int) -> int:
|
||||
pass
|
||||
|
||||
def f(s):
|
||||
return s
|
||||
""")
|
||||
|
||||
def test_not_a_typing_overload(self):
|
||||
"""regression test for @typing.overload detection bug in 2.1.0"""
|
||||
self.flakes("""
|
||||
def foo(x):
|
||||
return x
|
||||
|
||||
@foo
|
||||
def bar():
|
||||
pass
|
||||
|
||||
def bar():
|
||||
pass
|
||||
""", m.RedefinedWhileUnused)
|
||||
|
||||
def test_variable_annotations(self):
|
||||
def undefined_names_before_py314(*, n: int):
|
||||
return (m.UndefinedName,) * n if version_info < (3, 14) else ()
|
||||
|
||||
self.flakes('''
|
||||
name: str
|
||||
age: int
|
||||
''')
|
||||
self.flakes('''
|
||||
name: str = 'Bob'
|
||||
age: int = 18
|
||||
''')
|
||||
self.flakes('''
|
||||
class C:
|
||||
name: str
|
||||
age: int
|
||||
''')
|
||||
self.flakes('''
|
||||
class C:
|
||||
name: str = 'Bob'
|
||||
age: int = 18
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
name: str
|
||||
age: int
|
||||
''', m.UnusedAnnotation, m.UnusedAnnotation)
|
||||
self.flakes('''
|
||||
def f():
|
||||
name: str = 'Bob'
|
||||
age: int = 18
|
||||
foo: not_a_real_type = None
|
||||
''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable, m.UndefinedName)
|
||||
self.flakes('''
|
||||
def f():
|
||||
name: str
|
||||
print(name)
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
from typing import Any
|
||||
def f():
|
||||
a: Any
|
||||
''', m.UnusedAnnotation)
|
||||
self.flakes('''
|
||||
foo: not_a_real_type
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
foo: not_a_real_type = None
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
class C:
|
||||
foo: not_a_real_type
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
class C:
|
||||
foo: not_a_real_type = None
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
def f():
|
||||
class C:
|
||||
foo: not_a_real_type
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
def f():
|
||||
class C:
|
||||
foo: not_a_real_type = None
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
bar: Bar
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
bar: 'Bar'
|
||||
''')
|
||||
self.flakes('''
|
||||
import foo
|
||||
bar: foo.Bar
|
||||
''')
|
||||
self.flakes('''
|
||||
import foo
|
||||
bar: 'foo.Bar'
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar: Bar): pass
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar: 'Bar'): pass
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar) -> Bar: return bar
|
||||
''')
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar) -> 'Bar': return bar
|
||||
''')
|
||||
self.flakes('''
|
||||
bar: 'Bar'
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
bar: 'foo.Bar'
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
bar: str
|
||||
''', m.UnusedImport)
|
||||
self.flakes('''
|
||||
from foo import Bar
|
||||
def f(bar: str): pass
|
||||
''', m.UnusedImport)
|
||||
self.flakes('''
|
||||
def f(a: A) -> A: pass
|
||||
class A: pass
|
||||
''', *undefined_names_before_py314(n=2))
|
||||
|
||||
self.flakes('''
|
||||
def f(a: 'A') -> 'A': return a
|
||||
class A: pass
|
||||
''')
|
||||
self.flakes('''
|
||||
a: A
|
||||
class A: pass
|
||||
''', *undefined_names_before_py314(n=1))
|
||||
self.flakes('''
|
||||
a: 'A'
|
||||
class A: pass
|
||||
''')
|
||||
self.flakes('''
|
||||
T: object
|
||||
def f(t: T): pass
|
||||
''', *undefined_names_before_py314(n=1))
|
||||
self.flakes('''
|
||||
T: object
|
||||
def g(t: 'T'): pass
|
||||
''')
|
||||
self.flakes('''
|
||||
a: 'A B'
|
||||
''', m.ForwardAnnotationSyntaxError)
|
||||
self.flakes('''
|
||||
a: 'A; B'
|
||||
''', m.ForwardAnnotationSyntaxError)
|
||||
self.flakes('''
|
||||
a: '1 + 2'
|
||||
''')
|
||||
self.flakes('''
|
||||
a: 'a: "A"'
|
||||
''', m.ForwardAnnotationSyntaxError)
|
||||
|
||||
def test_variable_annotation_references_self_name_undefined(self):
|
||||
self.flakes("""
|
||||
x: int = x
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_TypeAlias_annotations(self):
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
bar: TypeAlias = Bar
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
bar: TypeAlias = 'Bar'
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
class A:
|
||||
bar: TypeAlias = Bar
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
class A:
|
||||
bar: TypeAlias = 'Bar'
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
|
||||
bar: TypeAlias
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing_extensions import TypeAlias
|
||||
from foo import Bar
|
||||
|
||||
bar: TypeAlias
|
||||
""", m.UnusedImport)
|
||||
|
||||
def test_annotating_an_import(self):
|
||||
self.flakes('''
|
||||
from a import b, c
|
||||
b: c
|
||||
print(b)
|
||||
''')
|
||||
|
||||
def test_unused_annotation(self):
|
||||
# Unused annotations are fine in module and class scope
|
||||
self.flakes('''
|
||||
x: int
|
||||
class Cls:
|
||||
y: int
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
x: int
|
||||
''', m.UnusedAnnotation)
|
||||
# This should only print one UnusedVariable message
|
||||
self.flakes('''
|
||||
def f():
|
||||
x: int
|
||||
x = 3
|
||||
''', m.UnusedVariable)
|
||||
|
||||
def test_unused_annotation_in_outer_scope_reassigned_in_local_scope(self):
|
||||
self.flakes('''
|
||||
x: int
|
||||
x.__dict__
|
||||
def f(): x = 1
|
||||
''', m.UndefinedName, m.UnusedVariable)
|
||||
|
||||
def test_unassigned_annotation_is_undefined(self):
|
||||
self.flakes('''
|
||||
name: str
|
||||
print(name)
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_annotated_async_def(self):
|
||||
self.flakes('''
|
||||
class c: pass
|
||||
async def func(c: c) -> None: pass
|
||||
''')
|
||||
|
||||
def test_postponed_annotations(self):
|
||||
self.flakes('''
|
||||
from __future__ import annotations
|
||||
def f(a: A) -> A: pass
|
||||
class A:
|
||||
b: B
|
||||
class B: pass
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
from __future__ import annotations
|
||||
def f(a: A) -> A: pass
|
||||
class A:
|
||||
b: Undefined
|
||||
class B: pass
|
||||
''', m.UndefinedName)
|
||||
|
||||
self.flakes('''
|
||||
from __future__ import annotations
|
||||
T: object
|
||||
def f(t: T): pass
|
||||
def g(t: 'T'): pass
|
||||
''')
|
||||
|
||||
def test_annotations_do_not_define_names_with_future_annotations(self):
|
||||
self.flakes('''
|
||||
from __future__ import annotations
|
||||
def f():
|
||||
x: str
|
||||
print(x)
|
||||
''', m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3, 14), 'new in Python 3.14')
|
||||
def test_postponed_annotations_py314(self):
|
||||
self.flakes('''
|
||||
def f(x: C) -> None: pass
|
||||
class C: pass
|
||||
''')
|
||||
|
||||
def test_type_annotation_clobbers_all(self):
|
||||
self.flakes('''\
|
||||
from typing import TYPE_CHECKING, List
|
||||
|
||||
from y import z
|
||||
|
||||
if not TYPE_CHECKING:
|
||||
__all__ = ("z",)
|
||||
else:
|
||||
__all__: List[str]
|
||||
''')
|
||||
|
||||
def test_return_annotation_is_class_scope_variable(self):
|
||||
self.flakes("""
|
||||
from typing import TypeVar
|
||||
class Test:
|
||||
Y = TypeVar('Y')
|
||||
|
||||
def t(self, x: Y) -> Y:
|
||||
return x
|
||||
""")
|
||||
|
||||
def test_return_annotation_is_function_body_variable(self):
|
||||
self.flakes("""
|
||||
class Test:
|
||||
def t(self) -> Y:
|
||||
Y = 2
|
||||
return Y
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_positional_only_argument_annotations(self):
|
||||
self.flakes("""
|
||||
from x import C
|
||||
|
||||
def f(c: C, /): ...
|
||||
""")
|
||||
|
||||
def test_partially_quoted_type_annotation(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
def f() -> Optional['Queue[str]']:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_partially_quoted_type_assignment(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
MaybeQueue = Optional['Queue[str]']
|
||||
""")
|
||||
|
||||
def test_nested_partially_quoted_type_assignment(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
from typing import Callable
|
||||
|
||||
Func = Callable[['Queue[str]'], None]
|
||||
""")
|
||||
|
||||
def test_quoted_type_cast(self):
|
||||
self.flakes("""
|
||||
from typing import cast, Optional
|
||||
|
||||
maybe_int = cast('Optional[int]', 42)
|
||||
""")
|
||||
|
||||
def test_type_cast_literal_str_to_str(self):
|
||||
# Checks that our handling of quoted type annotations in the first
|
||||
# argument to `cast` doesn't cause issues when (only) the _second_
|
||||
# argument is a literal str which looks a bit like a type annotation.
|
||||
self.flakes("""
|
||||
from typing import cast
|
||||
|
||||
a_string = cast(str, 'Optional[int]')
|
||||
""")
|
||||
|
||||
def test_quoted_type_cast_renamed_import(self):
|
||||
self.flakes("""
|
||||
from typing import cast as tsac, Optional as Maybe
|
||||
|
||||
maybe_int = tsac('Maybe[int]', 42)
|
||||
""")
|
||||
|
||||
def test_quoted_TypeVar_constraints(self):
|
||||
self.flakes("""
|
||||
from typing import TypeVar, Optional
|
||||
|
||||
T = TypeVar('T', 'str', 'Optional[int]', bytes)
|
||||
""")
|
||||
|
||||
def test_quoted_TypeVar_bound(self):
|
||||
self.flakes("""
|
||||
from typing import TypeVar, Optional, List
|
||||
|
||||
T = TypeVar('T', bound='Optional[int]')
|
||||
S = TypeVar('S', int, bound='List[int]')
|
||||
""")
|
||||
|
||||
def test_literal_type_typing(self):
|
||||
self.flakes("""
|
||||
from typing import Literal
|
||||
|
||||
def f(x: Literal['some string']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_literal_type_typing_extensions(self):
|
||||
self.flakes("""
|
||||
from typing_extensions import Literal
|
||||
|
||||
def f(x: Literal['some string']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_annotated_type_typing_missing_forward_type(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated
|
||||
|
||||
def f(x: Annotated['integer']) -> None:
|
||||
return None
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_annotated_type_typing_missing_forward_type_multiple_args(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated
|
||||
|
||||
def f(x: Annotated['integer', 1]) -> None:
|
||||
return None
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_annotated_type_typing_with_string_args(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated
|
||||
|
||||
def f(x: Annotated[int, '> 0']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_annotated_type_typing_with_string_args_in_union(self):
|
||||
self.flakes("""
|
||||
from typing import Annotated, Union
|
||||
|
||||
def f(x: Union[Annotated['int', '>0'], 'integer']) -> None:
|
||||
return None
|
||||
""", m.UndefinedName)
|
||||
|
||||
def test_literal_type_some_other_module(self):
|
||||
"""err on the side of false-negatives for types named Literal"""
|
||||
self.flakes("""
|
||||
from my_module import compat
|
||||
from my_module.compat import Literal
|
||||
|
||||
def f(x: compat.Literal['some string']) -> None:
|
||||
return None
|
||||
def g(x: Literal['some string']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_literal_union_type_typing(self):
|
||||
self.flakes("""
|
||||
from typing import Literal
|
||||
|
||||
def f(x: Literal['some string', 'foo bar']) -> None:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_deferred_twice_annotation(self):
|
||||
self.flakes("""
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def f() -> "Optional['Queue[str]']":
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_partial_string_annotations_with_future_annotations(self):
|
||||
self.flakes("""
|
||||
from __future__ import annotations
|
||||
|
||||
from queue import Queue
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def f() -> Optional['Queue[str]']:
|
||||
return None
|
||||
""")
|
||||
|
||||
def test_forward_annotations_for_classes_in_scope(self):
|
||||
# see #749
|
||||
self.flakes("""
|
||||
from typing import Optional
|
||||
|
||||
def f():
|
||||
class C:
|
||||
a: "D"
|
||||
b: Optional["D"]
|
||||
c: "Optional[D]"
|
||||
|
||||
class D: pass
|
||||
""")
|
||||
|
||||
def test_idomiatic_typing_guards(self):
|
||||
# typing.TYPE_CHECKING: python3.5.3+
|
||||
self.flakes("""
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from t import T
|
||||
|
||||
def f() -> T:
|
||||
pass
|
||||
""")
|
||||
# False: the old, more-compatible approach
|
||||
self.flakes("""
|
||||
if False:
|
||||
from t import T
|
||||
|
||||
def f() -> T:
|
||||
pass
|
||||
""")
|
||||
# some choose to assign a constant and do it that way
|
||||
self.flakes("""
|
||||
MYPY = False
|
||||
|
||||
if MYPY:
|
||||
from t import T
|
||||
|
||||
def f() -> T:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_typing_guard_for_protocol(self):
|
||||
self.flakes("""
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Protocol
|
||||
else:
|
||||
Protocol = object
|
||||
|
||||
class C(Protocol):
|
||||
def f() -> int:
|
||||
pass
|
||||
""")
|
||||
|
||||
def test_typednames_correct_forward_ref(self):
|
||||
self.flakes("""
|
||||
from typing import TypedDict, List, NamedTuple
|
||||
|
||||
List[TypedDict("x", {})]
|
||||
List[TypedDict("x", x=int)]
|
||||
List[NamedTuple("a", a=int)]
|
||||
List[NamedTuple("a", [("a", int)])]
|
||||
""")
|
||||
self.flakes("""
|
||||
from typing import TypedDict, List, NamedTuple, TypeVar
|
||||
|
||||
List[TypedDict("x", {"x": "Y"})]
|
||||
List[TypedDict("x", x="Y")]
|
||||
List[NamedTuple("a", [("a", "Y")])]
|
||||
List[NamedTuple("a", a="Y")]
|
||||
List[TypedDict("x", {"x": List["a"]})]
|
||||
List[TypeVar("A", bound="C")]
|
||||
List[TypeVar("A", List["C"])]
|
||||
""", *[m.UndefinedName]*7)
|
||||
self.flakes("""
|
||||
from typing import NamedTuple, TypeVar, cast
|
||||
from t import A, B, C, D, E
|
||||
|
||||
NamedTuple("A", [("a", A["C"])])
|
||||
TypeVar("A", bound=A["B"])
|
||||
TypeVar("A", A["D"])
|
||||
cast(A["E"], [])
|
||||
""")
|
||||
|
||||
def test_namedtypes_classes(self):
|
||||
self.flakes("""
|
||||
from typing import TypedDict, NamedTuple
|
||||
class X(TypedDict):
|
||||
y: TypedDict("z", {"zz":int})
|
||||
|
||||
class Y(NamedTuple):
|
||||
y: NamedTuple("v", [("vv", int)])
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 11), 'new in Python 3.11')
|
||||
def test_variadic_generics(self):
|
||||
self.flakes("""
|
||||
from typing import Generic
|
||||
from typing import TypeVarTuple
|
||||
|
||||
Ts = TypeVarTuple('Ts')
|
||||
|
||||
class Shape(Generic[*Ts]): pass
|
||||
|
||||
def f(*args: *Ts) -> None: ...
|
||||
|
||||
def g(x: Shape[*Ts]) -> Shape[*Ts]: ...
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_statements(self):
|
||||
self.flakes("""
|
||||
type ListOrSet[T] = list[T] | set[T]
|
||||
|
||||
def f(x: ListOrSet[str]) -> None: ...
|
||||
|
||||
type RecursiveType = int | list[RecursiveType]
|
||||
|
||||
type ForwardRef = int | C
|
||||
|
||||
type ForwardRefInBounds[T: C] = T
|
||||
|
||||
class C: pass
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_functions(self):
|
||||
self.flakes("""
|
||||
def f[T](t: T) -> T: return t
|
||||
|
||||
async def g[T](t: T) -> T: return t
|
||||
|
||||
def with_forward_ref[T: C](t: T) -> T: return t
|
||||
|
||||
def can_access_inside[T](t: T) -> T:
|
||||
print(T)
|
||||
return t
|
||||
|
||||
class C: pass
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_do_not_escape_function_scopes(self):
|
||||
self.flakes("""
|
||||
from x import g
|
||||
|
||||
@g(T) # not accessible in decorators
|
||||
def f[T](t: T) -> T: return t
|
||||
|
||||
T # not accessible afterwards
|
||||
""", m.UndefinedName, m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_classes(self):
|
||||
self.flakes("""
|
||||
class C[T](list[T]): pass
|
||||
|
||||
class UsesForward[T: Forward](list[T]): pass
|
||||
|
||||
class Forward: pass
|
||||
|
||||
class WithinBody[T](list[T]):
|
||||
t = T
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_do_not_escape_class_scopes(self):
|
||||
self.flakes("""
|
||||
from x import g
|
||||
|
||||
@g(T) # not accessible in decorators
|
||||
class C[T](list[T]): pass
|
||||
|
||||
T # not accessible afterwards
|
||||
""", m.UndefinedName, m.UndefinedName)
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_TypeVarTuple(self):
|
||||
self.flakes("""
|
||||
def f[*T](*args: *T) -> None: ...
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 12), 'new in Python 3.12')
|
||||
def test_type_parameters_ParamSpec(self):
|
||||
self.flakes("""
|
||||
from typing import Callable
|
||||
|
||||
def f[R, **P](f: Callable[P, R]) -> Callable[P, R]:
|
||||
def g(*args: P.args, **kwargs: P.kwargs) -> R:
|
||||
return f(*args, **kwargs)
|
||||
return g
|
||||
""")
|
||||
|
||||
@skipIf(version_info < (3, 13), 'new in Python 3.13')
|
||||
def test_type_parameter_defaults(self):
|
||||
self.flakes("""
|
||||
def f[T = int](u: T) -> T:
|
||||
return u
|
||||
""")
|
||||
@@ -0,0 +1,819 @@
|
||||
import ast
|
||||
|
||||
from pyflakes import messages as m, checker
|
||||
from pyflakes.test.harness import TestCase, skip
|
||||
|
||||
|
||||
class Test(TestCase):
|
||||
def test_undefined(self):
|
||||
self.flakes('bar', m.UndefinedName)
|
||||
|
||||
def test_definedInListComp(self):
|
||||
self.flakes('[a for a in range(10) if a]')
|
||||
|
||||
def test_undefinedInListComp(self):
|
||||
self.flakes('''
|
||||
[a for a in range(10)]
|
||||
a
|
||||
''',
|
||||
m.UndefinedName)
|
||||
|
||||
def test_undefinedExceptionName(self):
|
||||
"""Exception names can't be used after the except: block.
|
||||
|
||||
The exc variable is unused inside the exception handler."""
|
||||
self.flakes('''
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass
|
||||
exc
|
||||
''', m.UndefinedName, m.UnusedVariable)
|
||||
|
||||
def test_namesDeclaredInExceptBlocks(self):
|
||||
"""Locals declared in except: blocks can be used after the block.
|
||||
|
||||
This shows the example in test_undefinedExceptionName is
|
||||
different."""
|
||||
self.flakes('''
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
e = exc
|
||||
e
|
||||
''')
|
||||
|
||||
@skip('error reporting disabled due to false positives below')
|
||||
def test_undefinedExceptionNameObscuringLocalVariable(self):
|
||||
"""Exception names obscure locals, can't be used after.
|
||||
|
||||
Last line will raise UnboundLocalError on Python 3 after exiting
|
||||
the except: block. Note next two examples for false positives to
|
||||
watch out for."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass
|
||||
exc
|
||||
''',
|
||||
m.UndefinedName)
|
||||
|
||||
def test_undefinedExceptionNameObscuringLocalVariable2(self):
|
||||
"""Exception names are unbound after the `except:` block.
|
||||
|
||||
Last line will raise UnboundLocalError.
|
||||
The exc variable is unused inside the exception handler.
|
||||
"""
|
||||
self.flakes('''
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass
|
||||
print(exc)
|
||||
exc = 'Original value'
|
||||
''', m.UndefinedName, m.UnusedVariable)
|
||||
|
||||
def test_undefinedExceptionNameObscuringLocalVariableFalsePositive1(self):
|
||||
"""Exception names obscure locals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise UnboundLocalError because it's only
|
||||
entered if no exception was raised."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
print('exception logged')
|
||||
raise
|
||||
exc
|
||||
''', m.UnusedVariable)
|
||||
|
||||
def test_delExceptionInExcept(self):
|
||||
"""The exception name can be deleted in the except: block."""
|
||||
self.flakes('''
|
||||
try:
|
||||
pass
|
||||
except Exception as exc:
|
||||
del exc
|
||||
''')
|
||||
|
||||
def test_undefinedExceptionNameObscuringLocalVariableFalsePositive2(self):
|
||||
"""Exception names obscure locals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise UnboundLocalError because `error` is
|
||||
only falsy if the `except:` block has not been entered."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
error = None
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
error = 'exception logged'
|
||||
if error:
|
||||
print(error)
|
||||
else:
|
||||
exc
|
||||
''', m.UnusedVariable)
|
||||
|
||||
@skip('error reporting disabled due to false positives below')
|
||||
def test_undefinedExceptionNameObscuringGlobalVariable(self):
|
||||
"""Exception names obscure globals, can't be used after.
|
||||
|
||||
Last line will raise UnboundLocalError because the existence of that
|
||||
exception name creates a local scope placeholder for it, obscuring any
|
||||
globals, etc."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
try:
|
||||
pass # nothing is raised
|
||||
except ValueError as exc:
|
||||
pass # block never entered, exc stays unbound
|
||||
exc
|
||||
''',
|
||||
m.UndefinedLocal)
|
||||
|
||||
@skip('error reporting disabled due to false positives below')
|
||||
def test_undefinedExceptionNameObscuringGlobalVariable2(self):
|
||||
"""Exception names obscure globals, can't be used after.
|
||||
|
||||
Last line will raise NameError on Python 3 because the name is
|
||||
locally unbound after the `except:` block, even if it's
|
||||
nonlocal. We should issue an error in this case because code
|
||||
only working correctly if an exception isn't raised, is invalid.
|
||||
Unless it's explicitly silenced, see false positives below."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
global exc
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
pass # block never entered, exc stays unbound
|
||||
exc
|
||||
''',
|
||||
m.UndefinedLocal)
|
||||
|
||||
def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive1(self):
|
||||
"""Exception names obscure globals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise NameError because it's only entered
|
||||
if no exception was raised."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
global exc
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
print('exception logged')
|
||||
raise
|
||||
exc
|
||||
''', m.UnusedVariable)
|
||||
|
||||
def test_undefinedExceptionNameObscuringGlobalVariableFalsePositive2(self):
|
||||
"""Exception names obscure globals, can't be used after. Unless.
|
||||
|
||||
Last line will never raise NameError because `error` is only
|
||||
falsy if the `except:` block has not been entered."""
|
||||
self.flakes('''
|
||||
exc = 'Original value'
|
||||
def func():
|
||||
global exc
|
||||
error = None
|
||||
try:
|
||||
raise ValueError('ve')
|
||||
except ValueError as exc:
|
||||
error = 'exception logged'
|
||||
if error:
|
||||
print(error)
|
||||
else:
|
||||
exc
|
||||
''', m.UnusedVariable)
|
||||
|
||||
def test_functionsNeedGlobalScope(self):
|
||||
self.flakes('''
|
||||
class a:
|
||||
def b():
|
||||
fu
|
||||
fu = 1
|
||||
''')
|
||||
|
||||
def test_builtins(self):
|
||||
self.flakes('range(10)')
|
||||
|
||||
def test_builtinWindowsError(self):
|
||||
"""
|
||||
C{WindowsError} is sometimes a builtin name, so no warning is emitted
|
||||
for using it.
|
||||
"""
|
||||
self.flakes('WindowsError')
|
||||
|
||||
def test_moduleAnnotations(self):
|
||||
"""
|
||||
Use of the C{__annotations__} in module scope should not emit
|
||||
an undefined name warning when version is greater than or equal to 3.6.
|
||||
"""
|
||||
self.flakes('__annotations__')
|
||||
|
||||
def test_magicGlobalsFile(self):
|
||||
"""
|
||||
Use of the C{__file__} magic global should not emit an undefined name
|
||||
warning.
|
||||
"""
|
||||
self.flakes('__file__')
|
||||
|
||||
def test_magicGlobalsBuiltins(self):
|
||||
"""
|
||||
Use of the C{__builtins__} magic global should not emit an undefined
|
||||
name warning.
|
||||
"""
|
||||
self.flakes('__builtins__')
|
||||
|
||||
def test_magicGlobalsName(self):
|
||||
"""
|
||||
Use of the C{__name__} magic global should not emit an undefined name
|
||||
warning.
|
||||
"""
|
||||
self.flakes('__name__')
|
||||
|
||||
def test_magicGlobalsPath(self):
|
||||
"""
|
||||
Use of the C{__path__} magic global should not emit an undefined name
|
||||
warning, if you refer to it from a file called __init__.py.
|
||||
"""
|
||||
self.flakes('__path__', m.UndefinedName)
|
||||
self.flakes('__path__', filename='package/__init__.py')
|
||||
|
||||
def test_magicModuleInClassScope(self):
|
||||
"""
|
||||
Use of the C{__module__} magic builtin should not emit an undefined
|
||||
name warning if used in class scope.
|
||||
"""
|
||||
self.flakes('__module__', m.UndefinedName)
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
__module__
|
||||
''')
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
def bar(self):
|
||||
__module__
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_magicQualnameInClassScope(self):
|
||||
"""
|
||||
Use of the C{__qualname__} magic builtin should not emit an undefined
|
||||
name warning if used in class scope.
|
||||
"""
|
||||
self.flakes('__qualname__', m.UndefinedName)
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
__qualname__
|
||||
''')
|
||||
self.flakes('''
|
||||
class Foo:
|
||||
def bar(self):
|
||||
__qualname__
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_globalImportStar(self):
|
||||
"""Can't find undefined names with import *."""
|
||||
self.flakes('from fu import *; bar',
|
||||
m.ImportStarUsed, m.ImportStarUsage)
|
||||
|
||||
def test_definedByGlobal(self):
|
||||
"""
|
||||
"global" can make an otherwise undefined name in another function
|
||||
defined.
|
||||
"""
|
||||
self.flakes('''
|
||||
def a(): global fu; fu = 1
|
||||
def b(): fu
|
||||
''')
|
||||
self.flakes('''
|
||||
def c(): bar
|
||||
def b(): global bar; bar = 1
|
||||
''')
|
||||
|
||||
def test_definedByGlobalMultipleNames(self):
|
||||
"""
|
||||
"global" can accept multiple names.
|
||||
"""
|
||||
self.flakes('''
|
||||
def a(): global fu, bar; fu = 1; bar = 2
|
||||
def b(): fu; bar
|
||||
''')
|
||||
|
||||
def test_globalInGlobalScope(self):
|
||||
"""
|
||||
A global statement in the global scope is ignored.
|
||||
"""
|
||||
self.flakes('''
|
||||
global x
|
||||
def foo():
|
||||
print(x)
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_global_reset_name_only(self):
|
||||
"""A global statement does not prevent other names being undefined."""
|
||||
# Only different undefined names are reported.
|
||||
# See following test that fails where the same name is used.
|
||||
self.flakes('''
|
||||
def f1():
|
||||
s
|
||||
|
||||
def f2():
|
||||
global m
|
||||
''', m.UndefinedName, m.UnusedIndirectAssignment)
|
||||
|
||||
@skip("todo")
|
||||
def test_unused_global(self):
|
||||
"""An unused global statement does not define the name."""
|
||||
self.flakes('''
|
||||
def f1():
|
||||
m
|
||||
|
||||
def f2():
|
||||
global m
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_del(self):
|
||||
"""Del deletes bindings."""
|
||||
self.flakes('a = 1; del a; a', m.UndefinedName)
|
||||
|
||||
def test_delGlobal(self):
|
||||
"""Del a global binding from a function."""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def f():
|
||||
global a
|
||||
del a
|
||||
a
|
||||
''')
|
||||
|
||||
def test_delUndefined(self):
|
||||
"""Del an undefined name."""
|
||||
self.flakes('del a', m.UndefinedName)
|
||||
|
||||
def test_delConditional(self):
|
||||
"""
|
||||
Ignores conditional bindings deletion.
|
||||
"""
|
||||
self.flakes('''
|
||||
context = None
|
||||
test = True
|
||||
if False:
|
||||
del(test)
|
||||
assert(test)
|
||||
''')
|
||||
|
||||
def test_delConditionalNested(self):
|
||||
"""
|
||||
Ignored conditional bindings deletion even if they are nested in other
|
||||
blocks.
|
||||
"""
|
||||
self.flakes('''
|
||||
context = None
|
||||
test = True
|
||||
if False:
|
||||
with context():
|
||||
del(test)
|
||||
assert(test)
|
||||
''')
|
||||
|
||||
def test_delWhile(self):
|
||||
"""
|
||||
Ignore bindings deletion if called inside the body of a while
|
||||
statement.
|
||||
"""
|
||||
self.flakes('''
|
||||
def test():
|
||||
foo = 'bar'
|
||||
while False:
|
||||
del foo
|
||||
assert(foo)
|
||||
''')
|
||||
|
||||
def test_delWhileTestUsage(self):
|
||||
"""
|
||||
Ignore bindings deletion if called inside the body of a while
|
||||
statement and name is used inside while's test part.
|
||||
"""
|
||||
self.flakes('''
|
||||
def _worker():
|
||||
o = True
|
||||
while o is not True:
|
||||
del o
|
||||
o = False
|
||||
''')
|
||||
|
||||
def test_delWhileNested(self):
|
||||
"""
|
||||
Ignore bindings deletions if node is part of while's test, even when
|
||||
del is in a nested block.
|
||||
"""
|
||||
self.flakes('''
|
||||
context = None
|
||||
def _worker():
|
||||
o = True
|
||||
while o is not True:
|
||||
while True:
|
||||
with context():
|
||||
del o
|
||||
o = False
|
||||
''')
|
||||
|
||||
def test_globalFromNestedScope(self):
|
||||
"""Global names are available from nested scopes."""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def b():
|
||||
def c():
|
||||
a
|
||||
''')
|
||||
|
||||
def test_laterRedefinedGlobalFromNestedScope(self):
|
||||
"""
|
||||
Test that referencing a local name that shadows a global, before it is
|
||||
defined, generates a warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def fun():
|
||||
a
|
||||
a = 2
|
||||
return a
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_laterRedefinedGlobalFromNestedScope2(self):
|
||||
"""
|
||||
Test that referencing a local name in a nested scope that shadows a
|
||||
global declared in an enclosing scope, before it is defined, generates
|
||||
a warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
a = 1
|
||||
def fun():
|
||||
global a
|
||||
def fun2():
|
||||
a
|
||||
a = 2
|
||||
return a
|
||||
''', m.UndefinedLocal, m.UnusedIndirectAssignment)
|
||||
|
||||
def test_intermediateClassScopeIgnored(self):
|
||||
"""
|
||||
If a name defined in an enclosing scope is shadowed by a local variable
|
||||
and the name is used locally before it is bound, an unbound local
|
||||
warning is emitted, even if there is a class scope between the enclosing
|
||||
scope and the local scope.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
x = 1
|
||||
class g:
|
||||
def h(self):
|
||||
a = x
|
||||
x = None
|
||||
print(x, a)
|
||||
print(x)
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_doubleNestingReportsClosestName(self):
|
||||
"""
|
||||
Test that referencing a local name in a nested scope that shadows a
|
||||
variable declared in two different outer scopes before it is defined
|
||||
in the innermost scope generates an UnboundLocal warning which
|
||||
refers to the nearest shadowed name.
|
||||
"""
|
||||
exc = self.flakes('''
|
||||
def a():
|
||||
x = 1
|
||||
def b():
|
||||
x = 2 # line 5
|
||||
def c():
|
||||
x
|
||||
x = 3
|
||||
return x
|
||||
return x
|
||||
return x
|
||||
''', m.UndefinedLocal).messages[0]
|
||||
|
||||
# _DoctestMixin.flakes adds two lines preceding the code above.
|
||||
expected_line_num = 7 if self.withDoctest else 5
|
||||
|
||||
self.assertEqual(exc.message_args, ('x', expected_line_num))
|
||||
|
||||
def test_laterRedefinedGlobalFromNestedScope3(self):
|
||||
"""
|
||||
Test that referencing a local name in a nested scope that shadows a
|
||||
global, before it is defined, generates a warning.
|
||||
"""
|
||||
self.flakes('''
|
||||
def fun():
|
||||
a = 1
|
||||
def fun2():
|
||||
a
|
||||
a = 1
|
||||
return a
|
||||
return a
|
||||
''', m.UndefinedLocal)
|
||||
|
||||
def test_undefinedAugmentedAssignment(self):
|
||||
self.flakes(
|
||||
'''
|
||||
def f(seq):
|
||||
a = 0
|
||||
seq[a] += 1
|
||||
seq[b] /= 2
|
||||
c[0] *= 2
|
||||
a -= 3
|
||||
d += 4
|
||||
e[any] = 5
|
||||
''',
|
||||
m.UndefinedName, # b
|
||||
m.UndefinedName, # c
|
||||
m.UndefinedName, m.UnusedVariable, # d
|
||||
m.UndefinedName, # e
|
||||
)
|
||||
|
||||
def test_nestedClass(self):
|
||||
"""Nested classes can access enclosing scope."""
|
||||
self.flakes('''
|
||||
def f(foo):
|
||||
class C:
|
||||
bar = foo
|
||||
def f(self):
|
||||
return foo
|
||||
return C()
|
||||
|
||||
f(123).f()
|
||||
''')
|
||||
|
||||
def test_badNestedClass(self):
|
||||
"""Free variables in nested classes must bind at class creation."""
|
||||
self.flakes('''
|
||||
def f():
|
||||
class C:
|
||||
bar = foo
|
||||
foo = 456
|
||||
return foo
|
||||
f()
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_definedAsStarArgs(self):
|
||||
"""Star and double-star arg names are defined."""
|
||||
self.flakes('''
|
||||
def f(a, *b, **c):
|
||||
print(a, b, c)
|
||||
''')
|
||||
|
||||
def test_definedAsStarUnpack(self):
|
||||
"""Star names in unpack are defined."""
|
||||
self.flakes('''
|
||||
a, *b = range(10)
|
||||
print(a, b)
|
||||
''')
|
||||
self.flakes('''
|
||||
*a, b = range(10)
|
||||
print(a, b)
|
||||
''')
|
||||
self.flakes('''
|
||||
a, *b, c = range(10)
|
||||
print(a, b, c)
|
||||
''')
|
||||
|
||||
def test_usedAsStarUnpack(self):
|
||||
"""
|
||||
Star names in unpack are used if RHS is not a tuple/list literal.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
a, *b = range(10)
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
(*a, b) = range(10)
|
||||
''')
|
||||
self.flakes('''
|
||||
def f():
|
||||
[a, *b, c] = range(10)
|
||||
''')
|
||||
|
||||
def test_unusedAsStarUnpack(self):
|
||||
"""
|
||||
Star names in unpack are unused if RHS is a tuple/list literal.
|
||||
"""
|
||||
self.flakes('''
|
||||
def f():
|
||||
a, *b = any, all, 4, 2, 'un'
|
||||
''', m.UnusedVariable, m.UnusedVariable)
|
||||
self.flakes('''
|
||||
def f():
|
||||
(*a, b) = [bool, int, float, complex]
|
||||
''', m.UnusedVariable, m.UnusedVariable)
|
||||
self.flakes('''
|
||||
def f():
|
||||
[a, *b, c] = 9, 8, 7, 6, 5, 4
|
||||
''', m.UnusedVariable, m.UnusedVariable, m.UnusedVariable)
|
||||
|
||||
def test_keywordOnlyArgs(self):
|
||||
"""Keyword-only arg names are defined."""
|
||||
self.flakes('''
|
||||
def f(*, a, b=None):
|
||||
print(a, b)
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
import default_b
|
||||
def f(*, a, b=default_b):
|
||||
print(a, b)
|
||||
''')
|
||||
|
||||
def test_keywordOnlyArgsUndefined(self):
|
||||
"""Typo in kwonly name."""
|
||||
self.flakes('''
|
||||
def f(*, a, b=default_c):
|
||||
print(a, b)
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_annotationUndefined(self):
|
||||
"""Undefined annotations."""
|
||||
self.flakes('''
|
||||
from abc import note1, note2, note3, note4, note5
|
||||
def func(a: note1, *args: note2,
|
||||
b: note3=12, **kw: note4) -> note5: pass
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
def func():
|
||||
d = e = 42
|
||||
def func(a: {1, d}) -> (lambda c: e): pass
|
||||
''')
|
||||
|
||||
def test_metaClassUndefined(self):
|
||||
self.flakes('''
|
||||
from abc import ABCMeta
|
||||
class A(metaclass=ABCMeta): pass
|
||||
''')
|
||||
|
||||
def test_definedInGenExp(self):
|
||||
"""
|
||||
Using the loop variable of a generator expression results in no
|
||||
warnings.
|
||||
"""
|
||||
self.flakes('(a for a in [1, 2, 3] if a)')
|
||||
|
||||
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if b)')
|
||||
|
||||
def test_undefinedInGenExpNested(self):
|
||||
"""
|
||||
The loop variables of generator expressions nested together are
|
||||
not defined in the other generator.
|
||||
"""
|
||||
self.flakes('(b for b in (a for a in [1, 2, 3] if b) if b)',
|
||||
m.UndefinedName)
|
||||
|
||||
self.flakes('(b for b in (a for a in [1, 2, 3] if a) if a)',
|
||||
m.UndefinedName)
|
||||
|
||||
def test_undefinedWithErrorHandler(self):
|
||||
"""
|
||||
Some compatibility code checks explicitly for NameError.
|
||||
It should not trigger warnings.
|
||||
"""
|
||||
self.flakes('''
|
||||
try:
|
||||
socket_map
|
||||
except NameError:
|
||||
socket_map = {}
|
||||
''')
|
||||
self.flakes('''
|
||||
try:
|
||||
_memoryview.contiguous
|
||||
except (NameError, AttributeError):
|
||||
raise RuntimeError("Python >= 3.3 is required")
|
||||
''')
|
||||
# If NameError is not explicitly handled, generate a warning
|
||||
self.flakes('''
|
||||
try:
|
||||
socket_map
|
||||
except:
|
||||
socket_map = {}
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
try:
|
||||
socket_map
|
||||
except Exception:
|
||||
socket_map = {}
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_definedInClass(self):
|
||||
"""
|
||||
Defined name for generator expressions and dict/set comprehension.
|
||||
"""
|
||||
self.flakes('''
|
||||
class A:
|
||||
T = range(10)
|
||||
|
||||
Z = (x for x in T)
|
||||
L = [x for x in T]
|
||||
B = dict((i, str(i)) for i in T)
|
||||
''')
|
||||
|
||||
self.flakes('''
|
||||
class A:
|
||||
T = range(10)
|
||||
|
||||
X = {x for x in T}
|
||||
Y = {x:x for x in T}
|
||||
''')
|
||||
|
||||
def test_definedInClassNested(self):
|
||||
"""Defined name for nested generator expressions in a class."""
|
||||
self.flakes('''
|
||||
class A:
|
||||
T = range(10)
|
||||
|
||||
Z = (x for x in (a for a in T))
|
||||
''')
|
||||
|
||||
def test_undefinedInLoop(self):
|
||||
"""
|
||||
The loop variable is defined after the expression is computed.
|
||||
"""
|
||||
self.flakes('''
|
||||
for i in range(i):
|
||||
print(i)
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
[42 for i in range(i)]
|
||||
''', m.UndefinedName)
|
||||
self.flakes('''
|
||||
(42 for i in range(i))
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_definedFromLambdaInDictionaryComprehension(self):
|
||||
"""
|
||||
Defined name referenced from a lambda function within a dict/set
|
||||
comprehension.
|
||||
"""
|
||||
self.flakes('''
|
||||
{lambda: id(x) for x in range(10)}
|
||||
''')
|
||||
|
||||
def test_definedFromLambdaInGenerator(self):
|
||||
"""
|
||||
Defined name referenced from a lambda function within a generator
|
||||
expression.
|
||||
"""
|
||||
self.flakes('''
|
||||
any(lambda: id(x) for x in range(10))
|
||||
''')
|
||||
|
||||
def test_undefinedFromLambdaInDictionaryComprehension(self):
|
||||
"""
|
||||
Undefined name referenced from a lambda function within a dict/set
|
||||
comprehension.
|
||||
"""
|
||||
self.flakes('''
|
||||
{lambda: id(y) for x in range(10)}
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_undefinedFromLambdaInComprehension(self):
|
||||
"""
|
||||
Undefined name referenced from a lambda function within a generator
|
||||
expression.
|
||||
"""
|
||||
self.flakes('''
|
||||
any(lambda: id(y) for x in range(10))
|
||||
''', m.UndefinedName)
|
||||
|
||||
def test_dunderClass(self):
|
||||
code = '''
|
||||
class Test(object):
|
||||
def __init__(self):
|
||||
print(__class__.__name__)
|
||||
self.x = 1
|
||||
|
||||
t = Test()
|
||||
'''
|
||||
self.flakes(code)
|
||||
|
||||
|
||||
class NameTests(TestCase):
|
||||
"""
|
||||
Tests for some extra cases of name handling.
|
||||
"""
|
||||
def test_impossibleContext(self):
|
||||
"""
|
||||
A Name node with an unrecognized context results in a RuntimeError being
|
||||
raised.
|
||||
"""
|
||||
tree = ast.parse("x = 10")
|
||||
# Make it into something unrecognizable.
|
||||
tree.body[0].targets[0].ctx = object()
|
||||
self.assertRaises(RuntimeError, checker.Checker, tree)
|
||||
Reference in new issue
Block a user