fixed mccabe for global loops and nested functions

This commit is contained in:
Tarek Ziade 2011-02-15 11:55:10 +01:00
parent ab24ceea5a
commit e6068a3a38
9 changed files with 82 additions and 1696 deletions

View file

@ -67,18 +67,30 @@ class PathGraphingAstVisitor(compiler.visitor.ASTVisitor):
self.tail = None
def visitFunction(self, node):
if self.classname:
entity = '%s%s' % (self.classname, node.name)
else:
entity = node.name
name = '%d:1: %r' % (node.lineno, entity)
self.graph = PathGraph(name)
pathnode = PathNode(name)
self.tail = pathnode
self.default(node)
self.graphs["%s%s" % (self.classname, node.name)] = self.graph
self.reset()
if self.graph is not None:
# closure
pathnode = self.appendPathNode(name)
self.tail = pathnode
self.default(node)
bottom = PathNode("", look='point')
self.graph.connect(self.tail, bottom)
self.graph.connect(pathnode, bottom)
self.tail = bottom
else:
self.graph = PathGraph(name)
pathnode = PathNode(name)
self.tail = pathnode
self.default(node)
self.graphs["%s%s" % (self.classname, node.name)] = self.graph
self.reset()
def visitClass(self, node):
old_classname = self.classname
@ -96,7 +108,11 @@ class PathGraphingAstVisitor(compiler.visitor.ASTVisitor):
return pathnode
def visitSimpleStatement(self, node):
name = "Stmt %d" % node.lineno
if node.lineno is None:
lineno = 0
else:
lineno = node.lineno
name = "Stmt %d" % lineno
self.appendPathNode(name)
visitAssert = visitAssign = visitAssTuple = visitPrint = \
@ -106,13 +122,24 @@ class PathGraphingAstVisitor(compiler.visitor.ASTVisitor):
def visitLoop(self, node):
name = "Loop %d" % node.lineno
pathnode = self.appendPathNode(name)
self.tail = pathnode
self.default(node.body)
bottom = PathNode("", look='point')
self.graph.connect(self.tail, bottom)
self.graph.connect(pathnode, bottom)
self.tail = bottom
if self.graph is None:
# global loop
self.graph = PathGraph(name)
pathnode = PathNode(name)
self.tail = pathnode
self.default(node)
self.graphs["%s%s" % (self.classname, name)] = self.graph
self.reset()
else:
pathnode = self.appendPathNode(name)
self.tail = pathnode
self.default(node.body)
bottom = PathNode("", look='point')
self.graph.connect(self.tail, bottom)
self.graph.connect(pathnode, bottom)
self.tail = bottom
# TODO: else clause in node.else_
visitFor = visitWhile = visitLoop
@ -147,12 +174,7 @@ def get_code_complexity(code, min=7, filename='stdin'):
complex = []
ast = compiler.parse(code)
visitor = PathGraphingAstVisitor()
try:
visitor.preorder(ast, visitor)
except AttributeError:
print('McCabe: Could not parse code')
return -1
visitor.preorder(ast, visitor)
for graph in visitor.graphs.values():
if graph is None:
# ?
@ -172,10 +194,7 @@ def get_code_complexity(code, min=7, filename='stdin'):
def get_module_complexity(module_path, min=7):
"""Returns the complexity of a module"""
code = open(module_path, "rU").read() + '\n\n'
res = get_code_complexity(code, min, filename=module_path)
if res == -1:
print('McCabe: Problem with %s' % module_path)
return res
return get_code_complexity(code, min, filename=module_path)
def main(argv):

View file

@ -1,27 +0,0 @@
import textwrap
import _ast
from twisted.trial import unittest
from pyflakes import checker
class Test(unittest.TestCase):
def flakes(self, input, *expectedOutputs, **kw):
ast = compile(textwrap.dedent(input), "<test>", "exec",
_ast.PyCF_ONLY_AST)
w = checker.Checker(ast, **kw)
outputs = [type(o) for o in w.messages]
expectedOutputs = list(expectedOutputs)
outputs.sort()
expectedOutputs.sort()
self.assert_(outputs == expectedOutputs, '''\
for input:
%s
expected outputs:
%s
but got:
%s''' % (input, repr(expectedOutputs), '\n'.join(map(str, w.messages))))
return w

View file

@ -1,662 +0,0 @@
from sys import version_info
from pyflakes import messages as m
from pyflakes.test import harness
class Test(harness.Test):
def test_unusedImport(self):
self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport)
self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport)
def test_aliasedImport(self):
self.flakes('import fu as FU, bar as FU',
m.RedefinedWhileUnused, m.UnusedImport)
self.flakes('from moo import fu as FU, bar as FU',
m.RedefinedWhileUnused, m.UnusedImport)
def test_usedImport(self):
self.flakes('import fu; print fu')
self.flakes('from baz import fu; print fu')
def test_redefinedWhileUnused(self):
self.flakes('import fu; fu = 3', m.RedefinedWhileUnused)
self.flakes('import fu; del fu', m.RedefinedWhileUnused)
self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused)
self.flakes('import fu; [fu, bar] = 3', m.RedefinedWhileUnused)
def test_redefinedByFunction(self):
self.flakes('''
import fu
def fu():
pass
''', m.RedefinedWhileUnused)
def test_redefinedInNestedFunction(self):
"""
Test that shadowing a global name with a nested function definition
generates a warning.
"""
self.flakes('''
import fu
def bar():
def baz():
def fu():
pass
''', m.RedefinedWhileUnused, m.UnusedImport)
def test_redefinedByClass(self):
self.flakes('''
import fu
class fu:
pass
''', m.RedefinedWhileUnused)
def test_redefinedBySubclass(self):
"""
If an imported name is redefined by a class statement which also uses
that name in the bases list, no warning is emitted.
"""
self.flakes('''
from fu import bar
class bar(bar):
pass
''')
def test_redefinedInClass(self):
"""
Test that shadowing a global with a class attribute does not produce a
warning.
"""
self.flakes('''
import fu
class bar:
fu = 1
print fu
''')
def test_usedInFunction(self):
self.flakes('''
import fu
def fun():
print fu
''')
def test_shadowedByParameter(self):
self.flakes('''
import fu
def fun(fu):
print fu
''', m.UnusedImport)
self.flakes('''
import fu
def fun(fu):
print fu
print fu
''')
def test_newAssignment(self):
self.flakes('fu = None')
def test_usedInGetattr(self):
self.flakes('import fu; fu.bar.baz')
self.flakes('import fu; "bar".fu.baz', m.UnusedImport)
def test_usedInSlice(self):
self.flakes('import fu; print fu.bar[1:]')
def test_usedInIfBody(self):
self.flakes('''
import fu
if True: print fu
''')
def test_usedInIfConditional(self):
self.flakes('''
import fu
if fu: pass
''')
def test_usedInElifConditional(self):
self.flakes('''
import fu
if False: pass
elif fu: pass
''')
def test_usedInElse(self):
self.flakes('''
import fu
if False: pass
else: print fu
''')
def test_usedInCall(self):
self.flakes('import fu; fu.bar()')
def test_usedInClass(self):
self.flakes('''
import fu
class bar:
bar = fu
''')
def test_usedInClassBase(self):
self.flakes('''
import fu
class bar(object, fu.baz):
pass
''')
def test_notUsedInNestedScope(self):
self.flakes('''
import fu
def bleh():
pass
print fu
''')
def test_usedInFor(self):
self.flakes('''
import fu
for bar in range(9):
print fu
''')
def test_usedInForElse(self):
self.flakes('''
import fu
for bar in range(10):
pass
else:
print fu
''')
def test_redefinedByFor(self):
self.flakes('''
import fu
for fu in range(2):
pass
''', m.RedefinedWhileUnused)
def test_shadowedByFor(self):
"""
Test that shadowing a global name with a for loop variable generates a
warning.
"""
self.flakes('''
import fu
fu.bar()
for fu in ():
pass
''', m.ImportShadowedByLoopVar)
def test_shadowedByForDeep(self):
"""
Test that shadowing a global name with a for loop variable nested in a
tuple unpack generates a warning.
"""
self.flakes('''
import fu
fu.bar()
for (x, y, z, (a, b, c, (fu,))) in ():
pass
''', m.ImportShadowedByLoopVar)
def test_usedInReturn(self):
self.flakes('''
import fu
def fun():
return fu
''')
def test_usedInOperators(self):
self.flakes('import fu; 3 + fu.bar')
self.flakes('import fu; 3 % fu.bar')
self.flakes('import fu; 3 - fu.bar')
self.flakes('import fu; 3 * fu.bar')
self.flakes('import fu; 3 ** fu.bar')
self.flakes('import fu; 3 / fu.bar')
self.flakes('import fu; 3 // fu.bar')
self.flakes('import fu; -fu.bar')
self.flakes('import fu; ~fu.bar')
self.flakes('import fu; 1 == fu.bar')
self.flakes('import fu; 1 | fu.bar')
self.flakes('import fu; 1 & fu.bar')
self.flakes('import fu; 1 ^ fu.bar')
self.flakes('import fu; 1 >> fu.bar')
self.flakes('import fu; 1 << fu.bar')
def test_usedInAssert(self):
self.flakes('import fu; assert fu.bar')
def test_usedInSubscript(self):
self.flakes('import fu; fu.bar[1]')
def test_usedInLogic(self):
self.flakes('import fu; fu and False')
self.flakes('import fu; fu or False')
self.flakes('import fu; not fu.bar')
def test_usedInList(self):
self.flakes('import fu; [fu]')
def test_usedInTuple(self):
self.flakes('import fu; (fu,)')
def test_usedInTry(self):
self.flakes('''
import fu
try: fu
except: pass
''')
def test_usedInExcept(self):
self.flakes('''
import fu
try: fu
except: pass
''')
def test_redefinedByExcept(self):
self.flakes('''
import fu
try: pass
except Exception, fu: pass
''', m.RedefinedWhileUnused)
def test_usedInRaise(self):
self.flakes('''
import fu
raise fu.bar
''')
def test_usedInYield(self):
self.flakes('''
import fu
def gen():
yield fu
''')
def test_usedInDict(self):
self.flakes('import fu; {fu:None}')
self.flakes('import fu; {1:fu}')
def test_usedInParameterDefault(self):
self.flakes('''
import fu
def f(bar=fu):
pass
''')
def test_usedInAttributeAssign(self):
self.flakes('import fu; fu.bar = 1')
def test_usedInKeywordArg(self):
self.flakes('import fu; fu.bar(stuff=fu)')
def test_usedInAssignment(self):
self.flakes('import fu; bar=fu')
self.flakes('import fu; n=0; n+=fu')
def test_usedInListComp(self):
self.flakes('import fu; [fu for _ in range(1)]')
self.flakes('import fu; [1 for _ in range(1) if fu]')
def test_redefinedByListComp(self):
self.flakes('import fu; [1 for fu in range(1)]',
m.RedefinedWhileUnused)
def test_usedInTryFinally(self):
self.flakes('''
import fu
try: pass
finally: fu
''')
self.flakes('''
import fu
try: fu
finally: pass
''')
def test_usedInWhile(self):
self.flakes('''
import fu
while 0:
fu
''')
self.flakes('''
import fu
while fu: pass
''')
def test_usedInGlobal(self):
self.flakes('''
import fu
def f(): global fu
''', m.UnusedImport)
def test_usedInBackquote(self):
self.flakes('import fu; `fu`')
def test_usedInExec(self):
self.flakes('import fu; exec "print 1" in fu.bar')
def test_usedInLambda(self):
self.flakes('import fu; lambda: fu')
def test_shadowedByLambda(self):
self.flakes('import fu; lambda fu: fu', m.UnusedImport)
def test_usedInSliceObj(self):
self.flakes('import fu; "meow"[::fu]')
def test_unusedInNestedScope(self):
self.flakes('''
def bar():
import fu
fu
''', m.UnusedImport, m.UndefinedName)
def test_methodsDontUseClassScope(self):
self.flakes('''
class bar:
import fu
def fun(self):
fu
''', m.UnusedImport, m.UndefinedName)
def test_nestedFunctionsNestScope(self):
self.flakes('''
def a():
def b():
fu
import fu
''')
def test_nestedClassAndFunctionScope(self):
self.flakes('''
def a():
import fu
class b:
def c(self):
print fu
''')
def test_importStar(self):
self.flakes('from fu import *', m.ImportStarUsed)
def test_packageImport(self):
"""
If a dotted name is imported and used, no warning is reported.
"""
self.flakes('''
import fu.bar
fu.bar
''')
def test_unusedPackageImport(self):
"""
If a dotted name is imported and not used, an unused import warning is
reported.
"""
self.flakes('import fu.bar', m.UnusedImport)
def test_duplicateSubmoduleImport(self):
"""
If a submodule of a package is imported twice, an unused import warning
and a redefined while unused warning are reported.
"""
self.flakes('''
import fu.bar, fu.bar
fu.bar
''', m.RedefinedWhileUnused)
self.flakes('''
import fu.bar
import fu.bar
fu.bar
''', m.RedefinedWhileUnused)
def test_differentSubmoduleImport(self):
"""
If two different submodules of a package are imported, no duplicate
import warning is reported for the package.
"""
self.flakes('''
import fu.bar, fu.baz
fu.bar, fu.baz
''')
self.flakes('''
import fu.bar
import fu.baz
fu.bar, fu.baz
''')
def test_assignRHSFirst(self):
self.flakes('import fu; fu = fu')
self.flakes('import fu; fu, bar = fu')
self.flakes('import fu; [fu, bar] = fu')
self.flakes('import fu; fu += fu')
def test_tryingMultipleImports(self):
self.flakes('''
try:
import fu
except ImportError:
import bar as fu
''')
test_tryingMultipleImports.todo = ''
def test_nonGlobalDoesNotRedefine(self):
self.flakes('''
import fu
def a():
fu = 3
return fu
fu
''')
def test_functionsRunLater(self):
self.flakes('''
def a():
fu
import fu
''')
def test_functionNamesAreBoundNow(self):
self.flakes('''
import fu
def fu():
fu
fu
''', m.RedefinedWhileUnused)
def test_ignoreNonImportRedefinitions(self):
self.flakes('a = 1; a = 2')
def test_importingForImportError(self):
self.flakes('''
try:
import fu
except ImportError:
pass
''')
test_importingForImportError.todo = ''
def test_importedInClass(self):
'''Imports in class scope can be used through self'''
self.flakes('''
class c:
import i
def __init__(self):
self.i
''')
test_importedInClass.todo = 'requires evaluating attribute access'
def test_futureImport(self):
'''__future__ is special'''
self.flakes('from __future__ import division')
self.flakes('''
"docstring is allowed before future import"
from __future__ import division
''')
def test_futureImportFirst(self):
"""
__future__ imports must come before anything else.
"""
self.flakes('''
x = 5
from __future__ import division
''', m.LateFutureImport)
self.flakes('''
from foo import bar
from __future__ import division
bar
''', m.LateFutureImport)
class TestSpecialAll(harness.Test):
"""
Tests for suppression of unused import warnings by C{__all__}.
"""
def test_ignoredInFunction(self):
"""
An C{__all__} definition does not suppress unused import warnings in a
function scope.
"""
self.flakes('''
def foo():
import bar
__all__ = ["bar"]
''', m.UnusedImport, m.UnusedVariable)
def test_ignoredInClass(self):
"""
An C{__all__} definition does not suppress unused import warnings in a
class scope.
"""
self.flakes('''
class foo:
import bar
__all__ = ["bar"]
''', m.UnusedImport)
def test_warningSuppressed(self):
"""
If a name is imported and unused but is named in C{__all__}, no warning
is reported.
"""
self.flakes('''
import foo
__all__ = ["foo"]
''')
def test_unrecognizable(self):
"""
If C{__all__} is defined in a way that can't be recognized statically,
it is ignored.
"""
self.flakes('''
import foo
__all__ = ["f" + "oo"]
''', m.UnusedImport)
self.flakes('''
import foo
__all__ = [] + ["foo"]
''', m.UnusedImport)
def test_unboundExported(self):
"""
If C{__all__} includes a name which is not bound, a warning is emitted.
"""
self.flakes('''
__all__ = ["foo"]
''', m.UndefinedExport)
# Skip this in __init__.py though, since the rules there are a little
# different.
for filename in ["foo/__init__.py", "__init__.py"]:
self.flakes('''
__all__ = ["foo"]
''', filename=filename)
def test_usedInGenExp(self):
"""
Using a global in a generator expression results in no warnings.
"""
self.flakes('import fu; (fu for _ in range(1))')
self.flakes('import fu; (1 for _ in range(1) if fu)')
def test_redefinedByGenExp(self):
"""
Re-using a global name as the loop variable for a generator
expression results in a redefinition warning.
"""
self.flakes('import fu; (1 for fu in range(1))',
m.RedefinedWhileUnused)
def test_usedAsDecorator(self):
"""
Using a global name in a decorator statement results in no warnings,
but using an undefined name in a decorator statement results in an
undefined name warning.
"""
self.flakes('''
from interior import decorate
@decorate
def f():
return "hello"
''')
self.flakes('''
from interior import decorate
@decorate('value')
def f():
return "hello"
''')
self.flakes('''
@decorate
def f():
return "hello"
''', m.UndefinedName)
class Python26Tests(harness.Test):
"""
Tests for checking of syntax which is valid in PYthon 2.6 and newer.
"""
if version_info < (2, 6):
skip = "Python 2.6 required for class decorator tests."
def test_usedAsClassDecorator(self):
"""
Using an imported name as a class decorator results in no warnings,
but using an undefined name as a class decorator results in an
undefined name warning.
"""
self.flakes('''
from interior import decorate
@decorate
class foo:
pass
''')
self.flakes('''
from interior import decorate
@decorate("foo")
class bar:
pass
''')
self.flakes('''
@decorate
class foo:
pass
''', m.UndefinedName)

View file

@ -1,542 +0,0 @@
# (c) 2005-2010 Divmod, Inc.
# See LICENSE file for details
"""
Tests for various Pyflakes behavior.
"""
from sys import version_info
from pyflakes import messages as m
from pyflakes.test import harness
class Test(harness.Test):
def test_duplicateArgs(self):
self.flakes('def fu(bar, bar): pass', m.DuplicateArgument)
def test_localReferencedBeforeAssignment(self):
self.flakes('''
a = 1
def f():
a; a=1
f()
''', m.UndefinedName)
test_localReferencedBeforeAssignment.todo = 'this requires finding all ' \
'assignments in the function body first'
def test_redefinedFunction(self):
"""
Test that shadowing a function definition with another one raises a
warning.
"""
self.flakes('''
def a(): pass
def a(): pass
''', m.RedefinedFunction)
def test_redefinedClassFunction(self):
"""
Test that shadowing a function definition in a class suite with another
one raises a warning.
"""
self.flakes('''
class A:
def a(): pass
def a(): pass
''', m.RedefinedFunction)
def test_functionDecorator(self):
"""
Test that shadowing a function definition with a decorated version of
that function does not raise a warning.
"""
self.flakes('''
from somewhere import somedecorator
def a(): pass
a = somedecorator(a)
''')
def test_classFunctionDecorator(self):
"""
Test that shadowing a function definition in a class suite with a
decorated version of that function does not raise a warning.
"""
self.flakes('''
class A:
def a(): pass
a = classmethod(a)
''')
def test_unaryPlus(self):
'''Don't die on unary +'''
self.flakes('+1')
def test_undefinedBaseClass(self):
"""
If a name in the base list of a class definition is undefined, a
warning is emitted.
"""
self.flakes('''
class foo(foo):
pass
''', m.UndefinedName)
def test_classNameUndefinedInClassBody(self):
"""
If a class name is used in the body of that class's definition and
the name is not already defined, a warning is emitted.
"""
self.flakes('''
class foo:
foo
''', m.UndefinedName)
def test_classNameDefinedPreviously(self):
"""
If a class name is used in the body of that class's definition and
the name was previously defined in some other way, no warning is
emitted.
"""
self.flakes('''
foo = None
class foo:
foo
''')
def test_comparison(self):
"""
If a defined name is used on either side of any of the six comparison
operators, no warning is emitted.
"""
self.flakes('''
x = 10
y = 20
x < y
x <= y
x == y
x != y
x >= y
x > y
''')
def test_identity(self):
"""
If a deefined name is used on either side of an identity test, no
warning is emitted.
"""
self.flakes('''
x = 10
y = 20
x is y
x is not y
''')
def test_containment(self):
"""
If a defined name is used on either side of a containment test, no
warning is emitted.
"""
self.flakes('''
x = 10
y = 20
x in y
x not in y
''')
def test_loopControl(self):
"""
break and continue statements are supported.
"""
self.flakes('''
for x in [1, 2]:
break
''')
self.flakes('''
for x in [1, 2]:
continue
''')
def test_ellipsis(self):
"""
Ellipsis in a slice is supported.
"""
self.flakes('''
[1, 2][...]
''')
def test_extendedSlice(self):
"""
Extended slices are supported.
"""
self.flakes('''
x = 3
[1, 2][x,:]
''')
class TestUnusedAssignment(harness.Test):
"""
Tests for warning about unused assignments.
"""
def test_unusedVariable(self):
"""
Warn when a variable in a function is assigned a value that's never
used.
"""
self.flakes('''
def a():
b = 1
''', m.UnusedVariable)
def test_assignToGlobal(self):
"""
Assigning to a global and then not using that global is perfectly
acceptable. Do not mistake it for an unused local variable.
"""
self.flakes('''
b = 0
def a():
global b
b = 1
''')
def test_assignToMember(self):
"""
Assigning to a member of another object and then not using that member
variable is perfectly acceptable. Do not mistake it for an unused
local variable.
"""
# XXX: Adding this test didn't generate a failure. Maybe not
# necessary?
self.flakes('''
class b:
pass
def a():
b.foo = 1
''')
def test_assignInForLoop(self):
"""
Don't warn when a variable in a for loop is assigned to but not used.
"""
self.flakes('''
def f():
for i in range(10):
pass
''')
def test_assignInListComprehension(self):
"""
Don't warn when a variable in a list comprehension is assigned to but
not used.
"""
self.flakes('''
def f():
[None for i in range(10)]
''')
def test_generatorExpression(self):
"""
Don't warn when a variable in a generator expression is assigned to
but not used.
"""
self.flakes('''
def f():
(None for i in range(10))
''')
def test_assignmentInsideLoop(self):
"""
Don't warn when a variable assignment occurs lexically after its use.
"""
self.flakes('''
def f():
x = None
for i in range(10):
if i > 2:
return x
x = i * 2
''')
def test_tupleUnpacking(self):
"""
Don't warn when a variable included in tuple unpacking is unused. It's
very common for variables in a tuple unpacking assignment to be unused
in good Python code, so warning will only create false positives.
"""
self.flakes('''
def f():
(x, y) = 1, 2
''')
def test_listUnpacking(self):
"""
Don't warn when a variable included in list unpacking is unused.
"""
self.flakes('''
def f():
[x, y] = [1, 2]
''')
def test_closedOver(self):
"""
Don't warn when the assignment is used in an inner function.
"""
self.flakes('''
def barMaker():
foo = 5
def bar():
return foo
return bar
''')
def test_doubleClosedOver(self):
"""
Don't warn when the assignment is used in an inner function, even if
that inner function itself is in an inner function.
"""
self.flakes('''
def barMaker():
foo = 5
def bar():
def baz():
return foo
return bar
''')
class Python25Test(harness.Test):
"""
Tests for checking of syntax only available in Python 2.5 and newer.
"""
if version_info < (2, 5):
skip = "Python 2.5 required for if-else and with tests"
def test_ifexp(self):
"""
Test C{foo if bar else baz} statements.
"""
self.flakes("a = 'moo' if True else 'oink'")
self.flakes("a = foo if True else 'oink'", m.UndefinedName)
self.flakes("a = 'moo' if True else bar", m.UndefinedName)
def test_withStatementNoNames(self):
"""
No warnings are emitted for using inside or after a nameless C{with}
statement a name defined beforehand.
"""
self.flakes('''
from __future__ import with_statement
bar = None
with open("foo"):
bar
bar
''')
def test_withStatementSingleName(self):
"""
No warnings are emitted for using a name defined by a C{with} statement
within the suite or afterwards.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as bar:
bar
bar
''')
def test_withStatementAttributeName(self):
"""
No warnings are emitted for using an attribute as the target of a
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo.bar:
pass
''')
def test_withStatementSubscript(self):
"""
No warnings are emitted for using a subscript as the target of a
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo[0]:
pass
''')
def test_withStatementSubscriptUndefined(self):
"""
An undefined name warning is emitted if the subscript used as the
target of a C{with} statement is not defined.
"""
self.flakes('''
from __future__ import with_statement
import foo
with open('foo') as foo[bar]:
pass
''', m.UndefinedName)
def test_withStatementTupleNames(self):
"""
No warnings are emitted for using any of the tuple of names defined by
a C{with} statement within the suite or afterwards.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as (bar, baz):
bar, baz
bar, baz
''')
def test_withStatementListNames(self):
"""
No warnings are emitted for using any of the list of names defined by a
C{with} statement within the suite or afterwards.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as [bar, baz]:
bar, baz
bar, baz
''')
def test_withStatementComplicatedTarget(self):
"""
If the target of a C{with} statement uses any or all of the valid forms
for that part of the grammar (See
U{http://docs.python.org/reference/
compound_stmts.html#the-with-statement}),
the names involved are checked both for definedness and any bindings
created are respected in the suite of the statement and afterwards.
"""
self.flakes('''
from __future__ import with_statement
c = d = e = g = h = i = None
with open('foo') as [(a, b), c[d], e.f, g[h:i]]:
a, b, c, d, e, g, h, i
a, b, c, d, e, g, h, i
''')
def test_withStatementSingleNameUndefined(self):
"""
An undefined name warning is emitted if the name first defined by a
C{with} statement is used before the C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
bar
with open('foo') as bar:
pass
''', m.UndefinedName)
def test_withStatementTupleNamesUndefined(self):
"""
An undefined name warning is emitted if a name first defined by a the
tuple-unpacking form of the C{with} statement is used before the
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
baz
with open('foo') as (bar, baz):
pass
''', m.UndefinedName)
def test_withStatementSingleNameRedefined(self):
"""
A redefined name warning is emitted if a name bound by an import is
rebound by the name defined by a C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import bar
with open('foo') as bar:
pass
''', m.RedefinedWhileUnused)
def test_withStatementTupleNamesRedefined(self):
"""
A redefined name warning is emitted if a name bound by an import is
rebound by one of the names defined by the tuple-unpacking form of a
C{with} statement.
"""
self.flakes('''
from __future__ import with_statement
import bar
with open('foo') as (bar, baz):
pass
''', m.RedefinedWhileUnused)
def test_withStatementUndefinedInside(self):
"""
An undefined name warning is emitted if a name is used inside the
body of a C{with} statement without first being bound.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as bar:
baz
''', m.UndefinedName)
def test_withStatementNameDefinedInBody(self):
"""
A name defined in the body of a C{with} statement can be used after
the body ends without warning.
"""
self.flakes('''
from __future__ import with_statement
with open('foo') as bar:
baz = 10
baz
''')
def test_withStatementUndefinedInExpression(self):
"""
An undefined name warning is emitted if a name in the I{test}
expression of a C{with} statement is undefined.
"""
self.flakes('''
from __future__ import with_statement
with bar as baz:
pass
''', m.UndefinedName)
self.flakes('''
from __future__ import with_statement
with bar as bar:
pass
''', m.UndefinedName)
class Python27Test(harness.Test):
"""
Tests for checking of syntax only available in Python 2.7 and newer.
"""
if version_info < (2, 7):
skip = "Python 2.7 required for dict/set comprehension tests"
def test_dictComprehension(self):
"""
Dict comprehensions are properly handled.
"""
self.flakes('''
a = {1: x for x in range(10)}
''')
def test_setComprehensionAndLiteral(self):
"""
Set comprehensions are properly handled.
"""
self.flakes('''
a = {1, 2, 3}
b = {x for x in range(10)}
''')

View file

@ -1,180 +0,0 @@
"""
Tests for L{pyflakes.scripts.pyflakes}.
"""
import sys
from StringIO import StringIO
from twisted.python.filepath import FilePath
from twisted.trial.unittest import TestCase
from pyflakes.scripts.pyflakes import checkPath
def withStderrTo(stderr, f):
"""
Call C{f} with C{sys.stderr} redirected to C{stderr}.
"""
(outer, sys.stderr) = (sys.stderr, stderr)
try:
return f()
finally:
sys.stderr = outer
class CheckTests(TestCase):
"""
Tests for L{check} and L{checkPath} which check a file for flakes.
"""
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}.
"""
fName = self.mktemp()
FilePath(fName).setContent("def foo():\n\tpass\n\t")
self.assertFalse(checkPath(fName))
def test_checkPathNonExisting(self):
"""
L{checkPath} handles non-existing files.
"""
err = StringIO()
count = withStderrTo(err, lambda: checkPath('extremo'))
self.assertEquals(err.getvalue(),
'extremo: No such file or directory\n')
self.assertEquals(count, 1)
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
exc = self.assertRaises(SyntaxError, evaluate, source)
self.assertTrue(exc.text.count('\n') > 1)
sourcePath = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEqual(count, 1)
self.assertEqual(
err.getvalue(),
"""\
%s:8: invalid syntax
'''quux'''
^
""" % (sourcePath.path,))
def test_eofSyntaxError(self):
"""
The error reported for source files which end prematurely causing a
syntax error reflects the cause for the syntax error.
"""
source = "def foo("
sourcePath = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEqual(count, 1)
self.assertEqual(
err.getvalue(),
"""\
%s:1: unexpected EOF while parsing
def foo(
^
""" % (sourcePath.path,))
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
"""
sourcePath = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEqual(count, 1)
self.assertEqual(
err.getvalue(),
"""\
%s:1: non-default argument follows default argument
def foo(bar=baz, bax):
""" % (sourcePath.path,))
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)
"""
sourcePath = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEqual(count, 1)
self.assertEqual(
err.getvalue(),
"""\
%s:1: non-keyword arg after keyword arg
foo(bar=baz, bax)
""" % (sourcePath.path,))
def test_permissionDenied(self):
"""
If the a source file is not readable, this is reported on standard
error.
"""
sourcePath = FilePath(self.mktemp())
sourcePath.setContent('')
sourcePath.chmod(0)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEquals(count, 1)
self.assertEquals(
err.getvalue(), "%s: Permission denied\n" % (sourcePath.path,))
def test_misencodedFile(self):
"""
If a source file contains bytes which cannot be decoded, this is
reported on stderr.
"""
source = u"""\
# coding: ascii
x = "\N{SNOWMAN}"
""".encode('utf-8')
sourcePath = FilePath(self.mktemp())
sourcePath.setContent(source)
err = StringIO()
count = withStderrTo(err, lambda: checkPath(sourcePath.path))
self.assertEquals(count, 1)
self.assertEquals(
err.getvalue(),
"%s: problem decoding source\n" % (sourcePath.path,))

View file

@ -1,261 +0,0 @@
from _ast import PyCF_ONLY_AST
from twisted.trial.unittest import TestCase
from pyflakes import messages as m, checker
from pyflakes.test import harness
class Test(harness.Test):
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_functionsNeedGlobalScope(self):
self.flakes('''
class a:
def b():
fu
fu = 1
''')
def test_builtins(self):
self.flakes('range(10)')
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_globalImportStar(self):
'''Can't find undefined names with import *'''
self.flakes('from fu import *; bar', m.ImportStarUsed)
def test_localImportStar(self):
'''
A local import * still allows undefined names to be found
in upper scopes
'''
self.flakes('''
def a():
from fu import *
bar
''', m.ImportStarUsed, m.UndefinedName)
def test_unpackedParameter(self):
'''Unpacked function parameters create bindings'''
self.flakes('''
def a((bar, baz)):
bar; baz
''')
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
''')
test_definedByGlobal.todo = ''
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_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_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]
self.assertEqual(exc.message_args, ('x', 5))
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_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_definedInGenExp(self):
"""
Using the loop variable of a generator expression results in no
warnings.
"""
self.flakes('(a for a in xrange(10) if a)')
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 = compile("x = 10", "<test>", "exec", PyCF_ONLY_AST)
# Make it into something unrecognizable.
tree.body[0].targets[0].ctx = object()
self.assertRaises(RuntimeError, checker.Checker, tree)

1
flake8/tests/__init__.py Normal file
View file

@ -0,0 +1 @@
#

View file

@ -0,0 +1,38 @@
import unittest
import sys
from StringIO import StringIO
from flake8.mccabe import get_code_complexity
_GLOBAL = """\
for i in range(10):
pass
def a():
def b():
def c():
pass
c()
b()
"""
class McCabeTest(unittest.TestCase):
def setUp(self):
self.old = sys.stdout
self.out = sys.stdout = StringIO()
def tearDown(self):
sys.sdtout = self.old
def test_sample(self):
self.assertEqual(get_code_complexity(_GLOBAL, 1), 2)
self.out.seek(0)
res = self.out.read().strip().split('\n')
wanted = ["stdin:5:1: 'a' is too complex (3)",
'stdin:Loop 2 is too complex (1)']
self.assertEqual(res, wanted)