okay fine

This commit is contained in:
pacnpal
2024-11-03 17:47:26 +00:00
parent 01c6004a79
commit 27eb239e97
10020 changed files with 1935769 additions and 2364 deletions

View File

@@ -0,0 +1 @@
__version__ = '3.2.0'

View File

@@ -0,0 +1,5 @@
from pyflakes.api import main
# python -m pyflakes
if __name__ == '__main__':
main(prog='pyflakes')

View File

@@ -0,0 +1,185 @@
"""
API for the command-line I{pyflakes} tool.
"""
import ast
import os
import platform
import re
import sys
from pyflakes import checker, __version__
from pyflakes import reporter as modReporter
__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main']
PYTHON_SHEBANG_REGEX = re.compile(br'^#!.*\bpython(3(\.\d+)?|w)?[dmu]?\s')
def check(codeString, filename, reporter=None):
"""
Check the Python source given by C{codeString} for flakes.
@param codeString: The Python source to check.
@type codeString: C{str}
@param filename: The name of the file the source came from, used to report
errors.
@type filename: C{str}
@param reporter: A L{Reporter} instance, where errors and warnings will be
reported.
@return: The number of warnings emitted.
@rtype: C{int}
"""
if reporter is None:
reporter = modReporter._makeDefaultReporter()
# First, compile into an AST and handle syntax errors.
try:
tree = ast.parse(codeString, filename=filename)
except SyntaxError as e:
reporter.syntaxError(filename, e.args[0], e.lineno, e.offset, e.text)
return 1
except Exception:
reporter.unexpectedError(filename, 'problem decoding source')
return 1
# Okay, it's syntactically valid. Now check it.
w = checker.Checker(tree, filename=filename)
w.messages.sort(key=lambda m: m.lineno)
for warning in w.messages:
reporter.flake(warning)
return len(w.messages)
def checkPath(filename, reporter=None):
"""
Check the given path, printing out any warnings detected.
@param reporter: A L{Reporter} instance, where errors and warnings will be
reported.
@return: the number of warnings printed
"""
if reporter is None:
reporter = modReporter._makeDefaultReporter()
try:
with open(filename, 'rb') as f:
codestr = f.read()
except OSError as e:
reporter.unexpectedError(filename, e.args[1])
return 1
return check(codestr, filename, reporter)
def isPythonFile(filename):
"""Return True if filename points to a Python file."""
if filename.endswith('.py'):
return True
# Avoid obvious Emacs backup files
if filename.endswith("~"):
return False
max_bytes = 128
try:
with open(filename, 'rb') as f:
text = f.read(max_bytes)
if not text:
return False
except OSError:
return False
return PYTHON_SHEBANG_REGEX.match(text)
def iterSourceCode(paths):
"""
Iterate over all Python source files in C{paths}.
@param paths: A list of paths. Directories will be recursed into and
any .py files found will be yielded. Any non-directories will be
yielded as-is.
"""
for path in paths:
if os.path.isdir(path):
for dirpath, dirnames, filenames in os.walk(path):
for filename in filenames:
full_path = os.path.join(dirpath, filename)
if isPythonFile(full_path):
yield full_path
else:
yield path
def checkRecursive(paths, reporter):
"""
Recursively check all source files in C{paths}.
@param paths: A list of paths to Python source files and directories
containing Python source files.
@param reporter: A L{Reporter} where all of the warnings and errors
will be reported to.
@return: The number of warnings found.
"""
warnings = 0
for sourcePath in iterSourceCode(paths):
warnings += checkPath(sourcePath, reporter)
return warnings
def _exitOnSignal(sigName, message):
"""Handles a signal with sys.exit.
Some of these signals (SIGPIPE, for example) don't exist or are invalid on
Windows. So, ignore errors that might arise.
"""
import signal
try:
sigNumber = getattr(signal, sigName)
except AttributeError:
# the signal constants defined in the signal module are defined by
# whether the C library supports them or not. So, SIGPIPE might not
# even be defined.
return
def handler(sig, f):
sys.exit(message)
try:
signal.signal(sigNumber, handler)
except ValueError:
# It's also possible the signal is defined, but then it's invalid. In
# this case, signal.signal raises ValueError.
pass
def _get_version():
"""
Retrieve and format package version along with python version & OS used
"""
return ('%s Python %s on %s' %
(__version__, platform.python_version(), platform.system()))
def main(prog=None, args=None):
"""Entry point for the script "pyflakes"."""
import argparse
# Handle "Keyboard Interrupt" and "Broken pipe" gracefully
_exitOnSignal('SIGINT', '... stopped')
_exitOnSignal('SIGPIPE', 1)
parser = argparse.ArgumentParser(prog=prog,
description='Check Python source files for errors')
parser.add_argument('-V', '--version', action='version', version=_get_version())
parser.add_argument('path', nargs='*',
help='Path(s) of Python file(s) to check. STDIN if not given.')
args = parser.parse_args(args=args).path
reporter = modReporter._makeDefaultReporter()
if args:
warnings = checkRecursive(args, reporter)
else:
warnings = check(sys.stdin.read(), '<stdin>', reporter)
raise SystemExit(warnings > 0)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,349 @@
"""
Provide the class Message and its subclasses.
"""
class Message:
message = ''
message_args = ()
def __init__(self, filename, loc):
self.filename = filename
self.lineno = loc.lineno
self.col = loc.col_offset
def __str__(self):
return '{}:{}:{}: {}'.format(self.filename, self.lineno, self.col+1,
self.message % self.message_args)
class UnusedImport(Message):
message = '%r imported but unused'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class RedefinedWhileUnused(Message):
message = 'redefinition of unused %r from line %r'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class ImportShadowedByLoopVar(Message):
message = 'import %r from line %r shadowed by loop variable'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
self.message_args = (name, orig_loc.lineno)
class ImportStarNotPermitted(Message):
message = "'from %s import *' only allowed at module level"
def __init__(self, filename, loc, modname):
Message.__init__(self, filename, loc)
self.message_args = (modname,)
class ImportStarUsed(Message):
message = "'from %s import *' used; unable to detect undefined names"
def __init__(self, filename, loc, modname):
Message.__init__(self, filename, loc)
self.message_args = (modname,)
class ImportStarUsage(Message):
message = "%r may be undefined, or defined from star imports: %s"
def __init__(self, filename, loc, name, from_list):
Message.__init__(self, filename, loc)
self.message_args = (name, from_list)
class UndefinedName(Message):
message = 'undefined name %r'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class DoctestSyntaxError(Message):
message = 'syntax error in doctest'
def __init__(self, filename, loc, position=None):
Message.__init__(self, filename, loc)
if position:
(self.lineno, self.col) = position
self.message_args = ()
class UndefinedExport(Message):
message = 'undefined name %r in __all__'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class UndefinedLocal(Message):
message = 'local variable %r {0} referenced before assignment'
default = 'defined in enclosing scope on line %r'
builtin = 'defined as a builtin'
def __init__(self, filename, loc, name, orig_loc):
Message.__init__(self, filename, loc)
if orig_loc is None:
self.message = self.message.format(self.builtin)
self.message_args = name
else:
self.message = self.message.format(self.default)
self.message_args = (name, orig_loc.lineno)
class DuplicateArgument(Message):
message = 'duplicate argument %r in function definition'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class MultiValueRepeatedKeyLiteral(Message):
message = 'dictionary key %r repeated with different values'
def __init__(self, filename, loc, key):
Message.__init__(self, filename, loc)
self.message_args = (key,)
class MultiValueRepeatedKeyVariable(Message):
message = 'dictionary key variable %s repeated with different values'
def __init__(self, filename, loc, key):
Message.__init__(self, filename, loc)
self.message_args = (key,)
class LateFutureImport(Message):
message = 'from __future__ imports must occur at the beginning of the file'
class FutureFeatureNotDefined(Message):
"""An undefined __future__ feature name was imported."""
message = 'future feature %s is not defined'
def __init__(self, filename, loc, name):
Message.__init__(self, filename, loc)
self.message_args = (name,)
class UnusedVariable(Message):
"""
Indicates that a variable has been explicitly assigned to but not actually
used.
"""
message = 'local variable %r is assigned to but never used'
def __init__(self, filename, loc, names):
Message.__init__(self, filename, loc)
self.message_args = (names,)
class UnusedAnnotation(Message):
"""
Indicates that a variable has been explicitly annotated to but not actually
used.
"""
message = 'local variable %r is annotated but never used'
def __init__(self, filename, loc, names):
Message.__init__(self, filename, loc)
self.message_args = (names,)
class ReturnOutsideFunction(Message):
"""
Indicates a return statement outside of a function/method.
"""
message = '\'return\' outside function'
class YieldOutsideFunction(Message):
"""
Indicates a yield or yield from statement outside of a function/method.
"""
message = '\'yield\' outside function'
# For whatever reason, Python gives different error messages for these two. We
# match the Python error message exactly.
class ContinueOutsideLoop(Message):
"""
Indicates a continue statement outside of a while or for loop.
"""
message = '\'continue\' not properly in loop'
class BreakOutsideLoop(Message):
"""
Indicates a break statement outside of a while or for loop.
"""
message = '\'break\' outside loop'
class DefaultExceptNotLast(Message):
"""
Indicates an except: block as not the last exception handler.
"""
message = 'default \'except:\' must be last'
class TwoStarredExpressions(Message):
"""
Two or more starred expressions in an assignment (a, *b, *c = d).
"""
message = 'two starred expressions in assignment'
class TooManyExpressionsInStarredAssignment(Message):
"""
Too many expressions in an assignment with star-unpacking
"""
message = 'too many expressions in star-unpacking assignment'
class IfTuple(Message):
"""
Conditional test is a non-empty tuple literal, which are always True.
"""
message = '\'if tuple literal\' is always true, perhaps remove accidental comma?'
class AssertTuple(Message):
"""
Assertion test is a non-empty tuple literal, which are always True.
"""
message = 'assertion is always true, perhaps remove parentheses?'
class ForwardAnnotationSyntaxError(Message):
message = 'syntax error in forward annotation %r'
def __init__(self, filename, loc, annotation):
Message.__init__(self, filename, loc)
self.message_args = (annotation,)
class RaiseNotImplemented(Message):
message = "'raise NotImplemented' should be 'raise NotImplementedError'"
class InvalidPrintSyntax(Message):
message = 'use of >> is invalid with print function'
class IsLiteral(Message):
message = 'use ==/!= to compare constant literals (str, bytes, int, float, tuple)'
class FStringMissingPlaceholders(Message):
message = 'f-string is missing placeholders'
class StringDotFormatExtraPositionalArguments(Message):
message = "'...'.format(...) has unused arguments at position(s): %s"
def __init__(self, filename, loc, extra_positions):
Message.__init__(self, filename, loc)
self.message_args = (extra_positions,)
class StringDotFormatExtraNamedArguments(Message):
message = "'...'.format(...) has unused named argument(s): %s"
def __init__(self, filename, loc, extra_keywords):
Message.__init__(self, filename, loc)
self.message_args = (extra_keywords,)
class StringDotFormatMissingArgument(Message):
message = "'...'.format(...) is missing argument(s) for placeholder(s): %s"
def __init__(self, filename, loc, missing_arguments):
Message.__init__(self, filename, loc)
self.message_args = (missing_arguments,)
class StringDotFormatMixingAutomatic(Message):
message = "'...'.format(...) mixes automatic and manual numbering"
class StringDotFormatInvalidFormat(Message):
message = "'...'.format(...) has invalid format string: %s"
def __init__(self, filename, loc, error):
Message.__init__(self, filename, loc)
self.message_args = (error,)
class PercentFormatInvalidFormat(Message):
message = "'...' %% ... has invalid format string: %s"
def __init__(self, filename, loc, error):
Message.__init__(self, filename, loc)
self.message_args = (error,)
class PercentFormatMixedPositionalAndNamed(Message):
message = "'...' %% ... has mixed positional and named placeholders"
class PercentFormatUnsupportedFormatCharacter(Message):
message = "'...' %% ... has unsupported format character %r"
def __init__(self, filename, loc, c):
Message.__init__(self, filename, loc)
self.message_args = (c,)
class PercentFormatPositionalCountMismatch(Message):
message = "'...' %% ... has %d placeholder(s) but %d substitution(s)"
def __init__(self, filename, loc, n_placeholders, n_substitutions):
Message.__init__(self, filename, loc)
self.message_args = (n_placeholders, n_substitutions)
class PercentFormatExtraNamedArguments(Message):
message = "'...' %% ... has unused named argument(s): %s"
def __init__(self, filename, loc, extra_keywords):
Message.__init__(self, filename, loc)
self.message_args = (extra_keywords,)
class PercentFormatMissingArgument(Message):
message = "'...' %% ... is missing argument(s) for placeholder(s): %s"
def __init__(self, filename, loc, missing_arguments):
Message.__init__(self, filename, loc)
self.message_args = (missing_arguments,)
class PercentFormatExpectedMapping(Message):
message = "'...' %% ... expected mapping but got sequence"
class PercentFormatExpectedSequence(Message):
message = "'...' %% ... expected sequence but got mapping"
class PercentFormatStarRequiresSequence(Message):
message = "'...' %% ... `*` specifier requires sequence"

View File

@@ -0,0 +1,92 @@
"""
Provide the Reporter class.
"""
import re
import sys
class Reporter:
"""
Formats the results of pyflakes checks to users.
"""
def __init__(self, warningStream, errorStream):
"""
Construct a L{Reporter}.
@param warningStream: A file-like object where warnings will be
written to. The stream's C{write} method must accept unicode.
C{sys.stdout} is a good value.
@param errorStream: A file-like object where error output will be
written to. The stream's C{write} method must accept unicode.
C{sys.stderr} is a good value.
"""
self._stdout = warningStream
self._stderr = errorStream
def unexpectedError(self, filename, msg):
"""
An unexpected error occurred trying to process C{filename}.
@param filename: The path to a file that we could not process.
@ptype filename: C{unicode}
@param msg: A message explaining the problem.
@ptype msg: C{unicode}
"""
self._stderr.write(f"{filename}: {msg}\n")
def syntaxError(self, filename, msg, lineno, offset, text):
"""
There was a syntax error in C{filename}.
@param filename: The path to the file with the syntax error.
@ptype filename: C{unicode}
@param msg: An explanation of the syntax error.
@ptype msg: C{unicode}
@param lineno: The line number where the syntax error occurred.
@ptype lineno: C{int}
@param offset: The column on which the syntax error occurred, or None.
@ptype offset: C{int}
@param text: The source code containing the syntax error.
@ptype text: C{unicode}
"""
if text is None:
line = None
else:
line = text.splitlines()[-1]
# lineno might be None if the error was during tokenization
# lineno might be 0 if the error came from stdin
lineno = max(lineno or 0, 1)
if offset is not None:
# some versions of python emit an offset of -1 for certain encoding errors
offset = max(offset, 1)
self._stderr.write('%s:%d:%d: %s\n' %
(filename, lineno, offset, msg))
else:
self._stderr.write('%s:%d: %s\n' % (filename, lineno, msg))
if line is not None:
self._stderr.write(line)
self._stderr.write('\n')
if offset is not None:
self._stderr.write(re.sub(r'\S', ' ', line[:offset - 1]) +
"^\n")
def flake(self, message):
"""
pyflakes found something wrong with the code.
@param: A L{pyflakes.messages.Message}.
"""
self._stdout.write(str(message))
self._stdout.write('\n')
def _makeDefaultReporter():
"""
Make a reporter that can be used when no reporter is specified.
"""
return Reporter(sys.stdout, sys.stderr)

View File

@@ -0,0 +1,7 @@
"""
Implementation of the command-line I{pyflakes} tool.
"""
# For backward compatibility
__all__ = ['check', 'checkPath', 'checkRecursive', 'iterSourceCode', 'main']
from pyflakes.api import check, checkPath, checkRecursive, iterSourceCode, main

View File

@@ -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

View File

@@ -0,0 +1,803 @@
"""
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 and sys.version_info >= (3, 9):
column = 18
elif PYPY:
column = 8
elif sys.version_info >= (3, 10):
column = 18
elif sys.version_info >= (3, 9):
column = 21
else:
column = 9
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:
if sys.version_info >= (3, 9):
column = 17
elif not PYPY:
column = 14
else:
column = 13
last_line = ' ' * (column - 1) + '^\n'
columnstr = '%d:' % column
message = 'positional argument follows keyword argument'
self.assertHasErrors(
sourcePath,
["""\
{}:1:{} {}
foo(bar=baz, bax)
{}""".format(sourcePath, columnstr, message, 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 and sys.version_info >= (3, 9):
column = 7
elif PYPY:
column = 6
elif (3, 9) <= 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]
if sys.version_info >= (3, 9):
expected_error = [
"<stdin>:1:5: Generator expression must be parenthesized",
"max(1 for i in range(10), key=lambda x: x+1)",
" ^",
]
elif PYPY:
expected_error = [
"<stdin>:1:4: Generator expression must be parenthesized if not sole argument", # noqa: E501
"max(1 for i in range(10), key=lambda x: x+1)",
" ^",
]
else:
expected_error = [
"<stdin>:1:5: Generator expression must be parenthesized",
]
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***REMOVED***iron)
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])
error_msg = '{0}:1:7: invalid syntax{1}import{1} ^{1}'.format(
self.tempfilepath, 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')

View File

@@ -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()
''')

View File

@@ -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)

View File

@@ -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}
''')

View File

@@ -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

View File

@@ -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
''')

View File

@@ -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

View File

@@ -0,0 +1,799 @@
"""
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):
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
''', m.UndefinedName, m.UndefinedName)
self.flakes('''
def f(a: 'A') -> 'A': return a
class A: pass
''')
self.flakes('''
a: A
class A: pass
''', m.UndefinedName)
self.flakes('''
a: 'A'
class A: pass
''')
self.flakes('''
T: object
def f(t: T): pass
''', m.UndefinedName)
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_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
""")

View File

@@ -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_[AWS-SECRET-REMOVED]able(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_[AWS-SECRET-REMOVED]able2(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_[AWS-SECRET-REMOVED]ableFalsePositive1(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_[AWS-SECRET-REMOVED]ableFalsePositive2(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_[AWS-SECRET-REMOVED]iable(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_[AWS-SECRET-REMOVED]iable2(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_[AWS-SECRET-REMOVED]iableFalsePositive1(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_[AWS-SECRET-REMOVED]iableFalsePositive2(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)
@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)
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_[AWS-SECRET-REMOVED]on(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_[AWS-SECRET-REMOVED]sion(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)