← Back to team overview

divmod-dev team mailing list archive

[Merge] lp:~florent.x/pyflakes/989203-python3 into lp:pyflakes

 

Florent has proposed merging lp:~florent.x/pyflakes/989203-python3 into lp:pyflakes.

Requested reviews:
  Divmod-dev (divmod-dev)
Related bugs:
  Bug #989203 in Pyflakes: "pyflakes native support for Python 3"
  https://bugs.launchpad.net/pyflakes/+bug/989203

For more details, see:
https://code.launchpad.net/~florent.x/pyflakes/989203-python3/+merge/143992

This branch brings the minimal changes to achieve Python 3 support.

Tests are green: https://travis-ci.org/florentx/pyflakes/builds/4250803
-- 
https://code.launchpad.net/~florent.x/pyflakes/989203-python3/+merge/143992
Your team Divmod-dev is requested to review the proposed merge of lp:~florent.x/pyflakes/989203-python3 into lp:pyflakes.
=== modified file '.travis.yml'
--- .travis.yml	2013-01-19 00:26:52 +0000
+++ .travis.yml	2013-01-19 17:22:21 +0000
@@ -3,10 +3,14 @@
   - 2.5
   - 2.6
   - 2.7
+  - 3.2
+  - 3.3
   - pypy
+before_install:
+  - if [ "${TRAVIS_PYTHON_VERSION::1}" == "3" ]; then export TEST_PKG=unittest2py3k; else export TEST_PKG=unittest2; fi
 install:
   - python setup.py install
-  - pip install unittest2
+  - pip install $TEST_PKG
 script:
   - unit2 discover
 matrix:

=== modified file 'pyflakes/checker.py'
--- pyflakes/checker.py	2012-01-10 19:09:22 +0000
+++ pyflakes/checker.py	2013-01-19 17:22:21 +0000
@@ -2,9 +2,14 @@
 # (c) 2005-2010 Divmod, Inc.
 # See LICENSE file for details
 
-import __builtin__
 import os.path
 import _ast
+try:
+    import builtins
+    PY2 = False
+except ImportError:
+    import __builtin__ as builtins
+    PY2 = True
 
 from pyflakes import messages
 
@@ -173,11 +178,18 @@
     pass
 
 
-# Globally defined names which are not attributes of the __builtin__ module, or
+# Globally defined names which are not attributes of the builtins module, or
 # are only present on some platforms.
 _MAGIC_GLOBALS = ['__file__', '__builtins__', 'WindowsError']
 
 
+def getNodeName(node):
+    # Returns node.id, or node.name, or None
+    if hasattr(node, 'id'):     # One of the many nodes with an id
+        return node.id
+    if hasattr(node, 'name'):   # a ExceptHandler node
+        return node.name
+
 
 class Checker(object):
     """
@@ -275,7 +287,7 @@
                 all = []
 
             # Look for imported names that aren't used.
-            for importation in scope.itervalues():
+            for importation in scope.values():
                 if isinstance(importation, Importation):
                     if not importation.used and importation.name not in all:
                         self.report(
@@ -293,6 +305,83 @@
     def report(self, messageClass, *args, **kwargs):
         self.messages.append(messageClass(self.filename, *args, **kwargs))
 
+    def handleNodeLoad(self, node):
+        name = getNodeName(node)
+        if not name:
+            return
+        # try local scope
+        importStarred = self.scope.importStarred
+        try:
+            self.scope[name].used = (self.scope, node.lineno)
+        except KeyError:
+            pass
+        else:
+            return
+
+        # try enclosing function scopes
+        for scope in self.scopeStack[-2:0:-1]:
+            importStarred = importStarred or scope.importStarred
+            if not isinstance(scope, FunctionScope):
+                continue
+            try:
+                scope[name].used = (self.scope, node.lineno)
+            except KeyError:
+                pass
+            else:
+                return
+
+        # try global scope
+        importStarred = importStarred or self.scopeStack[0].importStarred
+        try:
+            self.scopeStack[0][name].used = (self.scope, node.lineno)
+        except KeyError:
+            if ((not hasattr(builtins, name)) and name not in _MAGIC_GLOBALS and not importStarred):
+                if (os.path.basename(self.filename) == '__init__.py' and name == '__path__'):
+                    # the special name __path__ is valid only in packages
+                    pass
+                else:
+                    self.report(messages.UndefinedName, node.lineno, name)
+
+    def handleNodeStore(self, node):
+        name = getNodeName(node)
+        if not name:
+            return
+        # if the name hasn't already been defined in the current scope
+        if isinstance(self.scope, FunctionScope) and name not in self.scope:
+            # for each function or module scope above us
+            for scope in self.scopeStack[:-1]:
+                if not isinstance(scope, (FunctionScope, ModuleScope)):
+                    continue
+                # if the name was defined in that scope, and the name has
+                # been accessed already in the current scope, and hasn't
+                # been declared global
+                if (name in scope and scope[name].used and scope[name].used[0] is self.scope
+                        and name not in self.scope.globals):
+                    # then it's probably a mistake
+                    self.report(messages.UndefinedLocal, scope[name].used[1], name,
+                        scope[name].source.lineno)
+                    break
+
+        parent = getattr(node, 'parent', None)
+        if isinstance(parent, (_ast.For, _ast.comprehension, _ast.Tuple, _ast.List)):
+            binding = Binding(name, node)
+        elif parent is not None and name == '__all__' and isinstance(self.scope, ModuleScope):
+            binding = ExportBinding(name, parent.value)
+        else:
+            binding = Assignment(name, node)
+        if name in self.scope:
+            binding.used = self.scope[name].used
+        self.addBinding(node.lineno, binding)
+
+    def handleNodeDelete(self, node):
+        name = getNodeName(node)
+        if not name:
+            return
+        if isinstance(self.scope, FunctionScope) and name in self.scope.globals:
+            del self.scope.globals[name]
+        else:
+            self.addBinding(node.lineno, UnBinding(name, node))
+
     def handleChildren(self, tree):
         for node in iter_child_nodes(tree):
             self.handleNode(node, tree)
@@ -309,7 +398,7 @@
     def handleNode(self, node, parent):
         node.parent = parent
         if self.traceTree:
-            print '  ' * self.nodeDepth + node.__class__.__name__
+            print('  ' * self.nodeDepth + node.__class__.__name__)
         self.nodeDepth += 1
         if self.futuresAllowed and not \
                (isinstance(node, _ast.ImportFrom) or self.isDocstring(node)):
@@ -321,14 +410,14 @@
         finally:
             self.nodeDepth -= 1
         if self.traceTree:
-            print '  ' * self.nodeDepth + 'end ' + node.__class__.__name__
+            print('  ' * self.nodeDepth + 'end ' + node.__class__.__name__)
 
     def ignore(self, node):
         pass
 
     # "stmt" type nodes
-    RETURN = DELETE = PRINT = WHILE = IF = WITH = RAISE = TRYEXCEPT = \
-        TRYFINALLY = ASSERT = EXEC = EXPR = handleChildren
+    RETURN = DELETE = PRINT = WHILE = IF = WITH = WITHITEM = RAISE = \
+        TRYEXCEPT = TRYFINALLY = TRY = ASSERT = EXEC = EXPR = handleChildren
 
     CONTINUE = BREAK = PASS = ignore
 
@@ -350,7 +439,7 @@
     EQ = NOTEQ = LT = LTE = GT = GTE = IS = ISNOT = IN = NOTIN = ignore
 
     # additional node types
-    COMPREHENSION = EXCEPTHANDLER = KEYWORD = handleChildren
+    COMPREHENSION = KEYWORD = handleChildren
 
     def addBinding(self, lineno, value, reportRedef=True):
         '''Called when a binding is altered.
@@ -436,81 +525,11 @@
         """
         # Locate the name in locals / function / globals scopes.
         if isinstance(node.ctx, (_ast.Load, _ast.AugLoad)):
-            # try local scope
-            importStarred = self.scope.importStarred
-            try:
-                self.scope[node.id].used = (self.scope, node.lineno)
-            except KeyError:
-                pass
-            else:
-                return
-
-            # try enclosing function scopes
-
-            for scope in self.scopeStack[-2:0:-1]:
-                importStarred = importStarred or scope.importStarred
-                if not isinstance(scope, FunctionScope):
-                    continue
-                try:
-                    scope[node.id].used = (self.scope, node.lineno)
-                except KeyError:
-                    pass
-                else:
-                    return
-
-            # try global scope
-
-            importStarred = importStarred or self.scopeStack[0].importStarred
-            try:
-                self.scopeStack[0][node.id].used = (self.scope, node.lineno)
-            except KeyError:
-                if ((not hasattr(__builtin__, node.id))
-                        and node.id not in _MAGIC_GLOBALS
-                        and not importStarred):
-                    if (os.path.basename(self.filename) == '__init__.py' and
-                        node.id == '__path__'):
-                        # the special name __path__ is valid only in packages
-                        pass
-                    else:
-                        self.report(messages.UndefinedName, node.lineno, node.id)
+            self.handleNodeLoad(node)
         elif isinstance(node.ctx, (_ast.Store, _ast.AugStore)):
-            # if the name hasn't already been defined in the current scope
-            if isinstance(self.scope, FunctionScope) and node.id not in self.scope:
-                # for each function or module scope above us
-                for scope in self.scopeStack[:-1]:
-                    if not isinstance(scope, (FunctionScope, ModuleScope)):
-                        continue
-                    # if the name was defined in that scope, and the name has
-                    # been accessed already in the current scope, and hasn't
-                    # been declared global
-                    if (node.id in scope
-                            and scope[node.id].used
-                            and scope[node.id].used[0] is self.scope
-                            and node.id not in self.scope.globals):
-                        # then it's probably a mistake
-                        self.report(messages.UndefinedLocal,
-                                    scope[node.id].used[1],
-                                    node.id,
-                                    scope[node.id].source.lineno)
-                        break
-
-            if isinstance(node.parent,
-                          (_ast.For, _ast.comprehension, _ast.Tuple, _ast.List)):
-                binding = Binding(node.id, node)
-            elif (node.id == '__all__' and
-                  isinstance(self.scope, ModuleScope)):
-                binding = ExportBinding(node.id, node.parent.value)
-            else:
-                binding = Assignment(node.id, node)
-            if node.id in self.scope:
-                binding.used = self.scope[node.id].used
-            self.addBinding(node.lineno, binding)
+            self.handleNodeStore(node)
         elif isinstance(node.ctx, _ast.Del):
-            if isinstance(self.scope, FunctionScope) and \
-                   node.id in self.scope.globals:
-                del self.scope.globals[node.id]
-            else:
-                self.addBinding(node.lineno, UnBinding(node.id, node))
+            self.handleNodeDelete(node)
         else:
             # must be a Param context -- this only happens for names in function
             # arguments, but these aren't dispatched through here
@@ -536,18 +555,28 @@
         def runFunction():
             args = []
 
-            def addArgs(arglist):
-                for arg in arglist:
-                    if isinstance(arg, _ast.Tuple):
-                        addArgs(arg.elts)
-                    else:
-                        if arg.id in args:
+            if PY2:
+                def addArgs(arglist):
+                    for arg in arglist:
+                        if isinstance(arg, _ast.Tuple):
+                            addArgs(arg.elts)
+                        else:
+                            if arg.id in args:
+                                self.report(messages.DuplicateArgument,
+                                            node.lineno, arg.id)
+                            args.append(arg.id)
+            else:
+                def addArgs(arglist):
+                    for arg in arglist:
+                        if arg.arg in args:
                             self.report(messages.DuplicateArgument,
-                                        node.lineno, arg.id)
-                        args.append(arg.id)
+                                        node.lineno, arg.arg)
+                        args.append(arg.arg)
 
             self.pushFunctionScope()
             addArgs(node.args.args)
+            if not PY2:
+                addArgs(node.args.kwonlyargs)
             # vararg/kwarg identifiers are not Name nodes
             if node.args.vararg:
                 args.append(node.args.vararg)
@@ -566,7 +595,7 @@
                 """
                 Check to see if any assignments have not been used.
                 """
-                for name, binding in self.scope.iteritems():
+                for name, binding in self.scope.items():
                     if (not binding.used and not name in self.scope.globals
                         and isinstance(binding, Assignment)):
                         self.report(messages.UnusedVariable,
@@ -600,13 +629,9 @@
             self.handleNode(target, node)
 
     def AUGASSIGN(self, node):
-        # AugAssign is awkward: must set the context explicitly and visit twice,
-        # once with AugLoad context, once with AugStore context
-        node.target.ctx = _ast.AugLoad()
-        self.handleNode(node.target, node)
+        self.handleNodeLoad(node.target)
         self.handleNode(node.value, node)
-        node.target.ctx = _ast.AugStore()
-        self.handleNode(node.target, node)
+        self.handleNodeStore(node.target)
 
     def IMPORT(self, node):
         for alias in node.names:
@@ -632,3 +657,10 @@
             if node.module == '__future__':
                 importation.used = (self.scope, node.lineno)
             self.addBinding(node.lineno, importation)
+
+    def EXCEPTHANDLER(self, node):
+        # in addition to handling children, we must handle the name of the exception, which is not
+        # a Name node, but a simple string.
+        if node.name:
+            self.handleNodeStore(node)
+        self.handleChildren(node)

=== modified file 'pyflakes/reporter.py'
--- pyflakes/reporter.py	2012-10-23 13:07:35 +0000
+++ pyflakes/reporter.py	2013-01-19 17:22:21 +0000
@@ -2,6 +2,10 @@
 # See LICENSE file for details
 
 import sys
+try:
+    u = unicode
+except NameError:
+    u = str
 
 
 class Reporter(object):
@@ -33,7 +37,7 @@
         @param msg: A message explaining the problem.
         @ptype msg: C{unicode}
         """
-        self._stderr.write(u"%s: %s\n" % (filename, msg))
+        self._stderr.write(u("%s: %s\n") % (filename, msg))
 
 
     def syntaxError(self, filename, msg, lineno, offset, text):
@@ -54,11 +58,11 @@
         line = text.splitlines()[-1]
         if offset is not None:
             offset = offset - (len(text) - len(line))
-        self._stderr.write(u'%s:%d: %s\n' % (filename, lineno, msg))
-        self._stderr.write(line)
-        self._stderr.write(u'\n')
+        self._stderr.write(u('%s:%d: %s\n') % (filename, lineno, msg))
+        self._stderr.write(u(line))
+        self._stderr.write(u('\n'))
         if offset is not None:
-            self._stderr.write(u" " * (offset + 1) + u"^\n")
+            self._stderr.write(u(" " * (offset + 1) + "^\n"))
 
 
     def flake(self, message):
@@ -67,8 +71,8 @@
 
         @param: A L{pyflakes.messages.Message}.
         """
-        self._stdout.write(unicode(message))
-        self._stdout.write(u'\n')
+        self._stdout.write(u(message))
+        self._stdout.write(u('\n'))
 
 
 

=== modified file 'pyflakes/scripts/pyflakes.py'
--- pyflakes/scripts/pyflakes.py	2012-10-23 11:48:54 +0000
+++ pyflakes/scripts/pyflakes.py	2013-01-19 17:22:21 +0000
@@ -34,7 +34,8 @@
     # First, compile into an AST and handle syntax errors.
     try:
         tree = compile(codeString, filename, "exec", _ast.PyCF_ONLY_AST)
-    except SyntaxError, value:
+    except SyntaxError:
+        value = sys.exc_info()[1]
         msg = value.args[0]
 
         (lineno, offset, text) = value.lineno, value.offset, value.text
@@ -44,14 +45,14 @@
             # Avoid using msg, since for the only known case, it contains a
             # bogus message that claims the encoding the file declared was
             # unknown.
-            reporter.unexpectedError(filename, u'problem decoding source')
+            reporter.unexpectedError(filename, 'problem decoding source')
         else:
             reporter.syntaxError(filename, msg, lineno, offset, text)
         return 1
     else:
         # Okay, it's syntactically valid.  Now check it.
         w = checker.Checker(tree, filename)
-        w.messages.sort(lambda a, b: cmp(a.lineno, b.lineno))
+        w.messages.sort(key=lambda m: m.lineno)
         for warning in w.messages:
             reporter.flake(warning)
         return len(w.messages)
@@ -69,8 +70,9 @@
     if reporter is None:
         reporter = modReporter._makeDefaultReporter()
     try:
-        return check(file(filename, 'U').read() + '\n', filename, reporter)
-    except IOError, msg:
+        return check(open(filename, 'U').read() + '\n', filename, reporter)
+    except IOError:
+        msg = sys.exc_info()[1]
         reporter.unexpectedError(filename, msg.args[1])
         return 1
 

=== modified file 'pyflakes/test/harness.py'
--- pyflakes/test/harness.py	2013-01-08 16:34:11 +0000
+++ pyflakes/test/harness.py	2013-01-19 17:22:21 +0000
@@ -15,8 +15,8 @@
         w = checker.Checker(ast, **kw)
         outputs = [type(o) for o in w.messages]
         expectedOutputs = list(expectedOutputs)
-        outputs.sort()
-        expectedOutputs.sort()
+        outputs.sort(key=lambda t: t.__name__)
+        expectedOutputs.sort(key=lambda t: t.__name__)
         self.assertEqual(outputs, expectedOutputs, '''\
 for input:
 %s

=== modified file 'pyflakes/test/test_imports.py'
--- pyflakes/test/test_imports.py	2013-01-08 16:34:11 +0000
+++ pyflakes/test/test_imports.py	2013-01-19 17:22:21 +0000
@@ -16,8 +16,8 @@
         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')
+        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)
@@ -74,28 +74,28 @@
         import fu
         class bar:
             fu = 1
-        print fu
+        print(fu)
         ''')
 
     def test_usedInFunction(self):
         self.flakes('''
         import fu
         def fun():
-            print fu
+            print(fu)
         ''')
 
     def test_shadowedByParameter(self):
         self.flakes('''
         import fu
         def fun(fu):
-            print fu
+            print(fu)
         ''', m.UnusedImport)
 
         self.flakes('''
         import fu
         def fun(fu):
-            print fu
-        print fu
+            print(fu)
+        print(fu)
         ''')
 
     def test_newAssignment(self):
@@ -106,12 +106,12 @@
         self.flakes('import fu; "bar".fu.baz', m.UnusedImport)
 
     def test_usedInSlice(self):
-        self.flakes('import fu; print fu.bar[1:]')
+        self.flakes('import fu; print(fu.bar[1:])')
 
     def test_usedInIfBody(self):
         self.flakes('''
         import fu
-        if True: print fu
+        if True: print(fu)
         ''')
 
     def test_usedInIfConditional(self):
@@ -131,7 +131,7 @@
         self.flakes('''
         import fu
         if False: pass
-        else: print fu
+        else: print(fu)
         ''')
 
     def test_usedInCall(self):
@@ -156,14 +156,14 @@
         import fu
         def bleh():
             pass
-        print fu
+        print(fu)
         ''')
 
     def test_usedInFor(self):
         self.flakes('''
         import fu
         for bar in range(9):
-            print fu
+            print(fu)
         ''')
 
     def test_usedInForElse(self):
@@ -172,7 +172,7 @@
         for bar in range(10):
             pass
         else:
-            print fu
+            print(fu)
         ''')
 
     def test_redefinedByFor(self):
@@ -262,11 +262,12 @@
         ''')
 
     def test_redefinedByExcept(self):
+        as_exc = ', ' if version_info < (2, 6) else ' as '
         self.flakes('''
         import fu
         try: pass
-        except Exception, fu: pass
-        ''', m.RedefinedWhileUnused)
+        except Exception%sfu: pass
+        ''' % as_exc, m.RedefinedWhileUnused)
 
     def test_usedInRaise(self):
         self.flakes('''
@@ -341,11 +342,16 @@
         def f(): global fu
         ''', m.UnusedImport)
 
+    @skipIf(version_info >= (3,), 'deprecated syntax')
     def test_usedInBackquote(self):
         self.flakes('import fu; `fu`')
 
     def test_usedInExec(self):
-        self.flakes('import fu; exec "print 1" in fu.bar')
+        if version_info < (3,):
+            exec_stmt = 'exec "print 1" in fu.bar'
+        else:
+            exec_stmt = 'exec("print(1)", fu.bar)'
+        self.flakes('import fu; %s' % exec_stmt)
 
     def test_usedInLambda(self):
         self.flakes('import fu; lambda: fu')
@@ -385,7 +391,7 @@
             import fu
             class b:
                 def c(self):
-                    print fu
+                    print(fu)
         ''')
 
     def test_importStar(self):

=== modified file 'pyflakes/test/test_script.py'
--- pyflakes/test/test_script.py	2013-01-08 16:34:11 +0000
+++ pyflakes/test/test_script.py	2013-01-19 17:22:21 +0000
@@ -7,9 +7,12 @@
 import shutil
 import subprocess
 import tempfile
-from StringIO import StringIO
+try:
+    from io import StringIO
+except ImportError:
+    from StringIO import StringIO
 
-from unittest2 import TestCase
+from unittest2 import skipIf, TestCase
 
 from pyflakes.messages import UnusedImport
 from pyflakes.reporter import Reporter
@@ -207,8 +210,8 @@
         """
         err = StringIO()
         reporter = Reporter(None, err)
-        reporter.unexpectedError(u'source.py', u'error message')
-        self.assertEquals(u'source.py: error message\n', err.getvalue())
+        reporter.unexpectedError('source.py', 'error message')
+        self.assertEquals('source.py: error message\n', err.getvalue())
 
 
     def test_flake(self):
@@ -234,6 +237,8 @@
         Make a temporary file containing C{content} and return a path to it.
         """
         _, fpath = tempfile.mkstemp()
+        if not hasattr(content, 'decode'):
+            content = content.encode('ascii')
         fd = open(fpath, 'wb')
         fd.write(content)
         fd.close()
@@ -309,10 +314,11 @@
         # 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
+            exec(source)
         try:
             evaluate(source)
-        except SyntaxError, e:
+        except SyntaxError:
+            e = sys.exc_info()[1]
             self.assertTrue(e.text.count('\n') > 1)
         else:
             self.fail()
@@ -353,12 +359,13 @@
     pass
 """
         sourcePath = self.makeTempFile(source)
+        last_line = '        ^\n' if sys.version_info >= (3, 2) else ''
         self.assertHasErrors(
             sourcePath,
             ["""\
 %s:1: non-default argument follows default argument
 def foo(bar=baz, bax):
-""" % (sourcePath,)])
+%s""" % (sourcePath, last_line)])
 
 
     def test_nonKeywordAfterKeywordSyntaxError(self):
@@ -371,12 +378,13 @@
 foo(bar=baz, bax)
 """
         sourcePath = self.makeTempFile(source)
+        last_line = '             ^\n' if sys.version_info >= (3, 2) else ''
         self.assertHasErrors(
             sourcePath,
             ["""\
 %s:1: non-keyword arg after keyword arg
 foo(bar=baz, bax)
-""" % (sourcePath,)])
+%s""" % (sourcePath, last_line)])
 
 
     def test_permissionDenied(self):
@@ -405,15 +413,17 @@
             errors, [('flake', str(UnusedImport(sourcePath, 1, 'foo')))])
 
 
+    @skipIf(sys.version_info >= (3,), "need adaptation for Python 3")
     def test_misencodedFile(self):
         """
         If a source file contains bytes which cannot be decoded, this is
         reported on stderr.
         """
-        source = u"""\
+        SNOWMAN = unichr(0x2603)
+        source = ("""\
 # coding: ascii
-x = "\N{SNOWMAN}"
-""".encode('utf-8')
+x = "%s"
+""" % SNOWMAN).encode('utf-8')
         sourcePath = self.makeTempFile(source)
         self.assertHasErrors(
             sourcePath, ["%s: problem decoding source\n" % (sourcePath,)])
@@ -428,11 +438,11 @@
         os.mkdir(os.path.join(tempdir, 'foo'))
         file1 = os.path.join(tempdir, 'foo', 'bar.py')
         fd = open(file1, 'wb')
-        fd.write("import baz\n")
+        fd.write("import baz\n".encode('ascii'))
         fd.close()
         file2 = os.path.join(tempdir, 'baz.py')
         fd = open(file2, 'wb')
-        fd.write("import contraband")
+        fd.write("import contraband".encode('ascii'))
         fd.close()
         log = []
         reporter = LoggingReporter(log)
@@ -488,6 +498,9 @@
                                  stdout=subprocess.PIPE, stderr=subprocess.PIPE)
             (stdout, stderr) = p.communicate()
         rv = p.wait()
+        if sys.version_info >= (3,):
+            stdout = stdout.decode('utf-8')
+            stderr = stderr.decode('utf-8')
         return (stdout, stderr, rv)
 
 
@@ -508,7 +521,7 @@
         and the warnings are printed to stdout.
         """
         fd = open(self.tempfilepath, 'wb')
-        fd.write("import contraband\n")
+        fd.write("import contraband\n".encode('ascii'))
         fd.close()
         d = self.runPyflakes([self.tempfilepath])
         self.assertEqual(d, ("%s\n" % UnusedImport(self.tempfilepath, 1, 'contraband'), '', 1))
@@ -528,5 +541,5 @@
         """
         If no arguments are passed to C{pyflakes} then it reads from stdin.
         """
-        d = self.runPyflakes([], stdin='import contraband')
+        d = self.runPyflakes([], stdin='import contraband'.encode('ascii'))
         self.assertEqual(d, ("%s\n" % UnusedImport('<stdin>', 1, 'contraband'), '', 1))

=== modified file 'pyflakes/test/test_undefined_names.py'
--- pyflakes/test/test_undefined_names.py	2013-01-08 16:34:11 +0000
+++ pyflakes/test/test_undefined_names.py	2013-01-19 17:22:21 +0000
@@ -1,7 +1,8 @@
 
 from _ast import PyCF_ONLY_AST
+from sys import version_info
 
-from unittest2 import skip, TestCase
+from unittest2 import skip, skipIf, TestCase
 
 from pyflakes import messages as m, checker
 from pyflakes.test import harness
@@ -80,6 +81,7 @@
         bar
         ''', m.ImportStarUsed, m.UndefinedName)
 
+    @skipIf(version_info >= (3,), 'obsolete syntax')
     def test_unpackedParameter(self):
         '''Unpacked function parameters create bindings'''
         self.flakes('''
@@ -102,7 +104,7 @@
         self.flakes('''
         global x
         def foo():
-            print x
+            print(x)
         ''', m.UndefinedName)
 
     def test_del(self):
@@ -176,8 +178,8 @@
                 def h(self):
                     a = x
                     x = None
-                    print x, a
-            print x
+                    print(x, a)
+            print(x)
         ''', m.UndefinedLocal)
 
 
@@ -246,7 +248,7 @@
         '''star and double-star arg names are defined'''
         self.flakes('''
         def f(a, *b, **c):
-            print a, b, c
+            print(a, b, c)
         ''')
 
     def test_definedInGenExp(self):
@@ -254,7 +256,8 @@
         Using the loop variable of a generator expression results in no
         warnings.
         """
-        self.flakes('(a for a in xrange(10) if a)')
+        self.flakes('(a for a in %srange(10) if a)' %
+                    ('x' if version_info < (3,) else ''))
 
 
 

=== modified file 'setup.py'
--- setup.py	2011-09-03 16:31:04 +0000
+++ setup.py	2013-01-19 17:22:21 +0000
@@ -24,6 +24,8 @@
         "Intended Audience :: Developers",
         "License :: OSI Approved :: MIT License",
         "Programming Language :: Python",
+        "Programming Language :: Python :: 2",
+        "Programming Language :: Python :: 3",
         "Topic :: Software Development",
         "Topic :: Utilities",
         ])