Commits

Ned Batchelder committed a81ab3e

The best way to get py3k support: same source runs on both, with some contortions.

Comments (0)

Files changed (26)

 
 clean:
 	python test/test_farm.py clean
-	-rm -rf build coverage.egg-info dist htmlcov three
+	-rm -rf build coverage.egg-info dist htmlcov
 	-rm -f *.pyd */*.pyd 
 	-rm -f *.pyc */*.pyc */*/*.pyc */*/*/*.pyc */*/*/*/*.pyc */*/*/*/*/*.pyc
 	-rm -f *.pyo */*.pyo */*/*.pyo */*/*/*.pyo */*/*/*/*.pyo */*/*/*/*/*.pyo
 set TAR_OPTIONS=
 
 @REM Py3k
-cd three
 call \ned\bin\switchpy 31
 python setup.py bdist_wininst
-
-set TAR_OPTIONS=--group=100
-python setup.py sdist --formats=gztar
-set TAR_OPTIONS=
-
-cd ..
-copy three\dist\*.* dist
 del coverage\tracer.pyd
 set COVERAGE_TEST_TRACER=py
 nosetests %1
+
+call \ned\bin\switchpy 31
+python setup.py -q install
+set COVERAGE_TEST_TRACER=c
+python \python31\Scripts\nosetests3 %1
+del \python31\lib\site-packages\coverage\tracer.pyd
+set COVERAGE_TEST_TRACER=py
+python \python31\Scripts\nosetests3 %1

coverage/backward.py

 """Add things to old Pythons so I can pretend they are newer."""
 
-# pylint: disable-msg=W0622
-# (Redefining built-in blah)
-# The whole point of this file is to redefine built-ins, so shut up about it.
+# This file does lots of tricky stuff, so disable a bunch of lintisms.
+# pylint: disable-msg=F0401,W0611,W0622
+# F0401: Unable to import blah
+# W0611: Unused import blah
+# W0622: Redefining built-in blah
 
 import os, sys
 
         lst.sort()
         return lst
 
-# Py2k and 3k don't agree on how to run commands in a subprocess.
+# Pythons 2 and 3 differ on where to get StringIO
+
 try:
-    import subprocess
+    from cStringIO import StringIO
 except ImportError:
-    def run_command(cmd):
-        """Run a command in a subprocess.
-        
-        Returns the exit code and the combined stdout and stderr.
-        
-        """
-        _, stdouterr = os.popen4(cmd)
-        return 0, stdouterr.read()
+    from io import StringIO
+
+# What's a string called?
+
+try:
+    string_class = basestring
+except NameError:
+    string_class = str
+
+# Where do pickles come from?
+
+try:
+    import cPickle as pickle
+except ImportError:
+    import pickle
+
+# Exec is a statement in Py2, a function in Py3
+
+if sys.hexversion > 0x03000000:
+    def exec_function(source, filename, global_map):
+        """A wrapper around exec()."""
+        exec(compile(source, filename, "exec"), global_map)
 else:
-    def run_command(cmd):
-        """Run a command in a subprocess.
-        
-        Returns the exit code and the combined stdout and stderr.
-        
-        """
-
-        if sys.hexversion > 0x03000000 and cmd.startswith("coverage "):
-            # We don't have a coverage command on 3.x, so fix it up to call the
-            # script. Eventually we won't need this.
-            cmd = "python " + sys.prefix + os.sep + "Scripts" + os.sep + cmd
-
-        proc = subprocess.Popen(cmd, shell=True,
-                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                stderr=subprocess.STDOUT
-                )
-        retcode = proc.wait()
-        
-        # Get the output, and canonicalize it to strings with newlines.
-        output = proc.stdout.read()
-        if not isinstance(output, str):
-            output = output.decode('utf-8')
-        output = output.replace('\r', '')
-        
-        return retcode, output
+    # OK, this is pretty gross.  In Py2, exec was a statement, but that will
+    # be a syntax error if we try to put it in a Py3 file, even if it is never
+    # executed.  So hide it inside an evaluated string literal instead.
+    eval(compile("""\
+def exec_function(source, filename, global_map):
+    exec compile(source, filename, "exec") in global_map
+""",
+    "<exec_function>", "exec"
+    ))

coverage/cmdline.py

         """Display an error message, or the named topic."""
         assert error or topic or parser
         if error:
-            print error
-            print "Use 'coverage help' for help."
+            print(error)
+            print("Use 'coverage help' for help.")
         elif parser:
-            print parser.format_help(),
+            print(parser.format_help().strip())
         else:
             # Parse out the topic we want from HELP_TOPICS
             import re
             topic_list = re.split("(?m)^=+ (\w+) =+$", HELP_TOPICS)
             topics = dict(zip(topic_list[1::2], topic_list[2::2]))
             help_msg = topics[topic].strip()
-            print help_msg % self.covpkg.__dict__
+            print(help_msg % self.covpkg.__dict__)
 
     def command_line(self, argv):
         """The bulk of the command line interface to Coverage.

coverage/codeunit.py

 
 import glob, os
 
+from coverage.backward import string_class
+
+
 def code_unit_factory(morfs, file_locator, omit_prefixes=None):
     """Construct a list of CodeUnits from polymorphic inputs.
     
     # On Windows, the shell doesn't expand wildcards.  Do it here.
     globbed = []
     for morf in morfs:
-        if isinstance(morf, basestring) and ('?' in morf or '*' in morf):
+        if isinstance(morf, string_class) and ('?' in morf or '*' in morf):
             globbed.extend(glob.glob(morf))
         else:
             globbed.append(morf)

coverage/control.py

 import os, socket
 
 from coverage.annotate import AnnotateReporter
+from coverage.backward import string_class
 from coverage.codeunit import code_unit_factory
 from coverage.collector import Collector
 from coverage.data import CoverageData
 
         # Create the data file.
         if data_suffix:
-            if not isinstance(data_suffix, basestring):
+            if not isinstance(data_suffix, string_class):
                 # if data_suffix=True, use .machinename.pid
                 data_suffix = ".%s.%s" % (socket.gethostname(), os.getpid())
         else:
         def _should_trace(self, filename, frame):   # pylint: disable-msg=E0102
             """A logging decorator around the real _should_trace function."""
             ret = self._real_should_trace(filename, frame)
-            print "should_trace: %r -> %r" % (filename, ret)
+            print("should_trace: %r -> %r" % (filename, ret))
             return ret
 
     def use_cache(self, usecache):
 """Coverage data for Coverage."""
 
 import os
-import cPickle as pickle
 
-from coverage.backward import sorted    # pylint: disable-msg=W0622
+from coverage.backward import pickle, sorted    # pylint: disable-msg=W0622
 
 
 class CoverageData:
 
     def executed_files(self):
         """A list of all files that had been measured as executed."""
-        return self.lines.keys()
+        return list(self.lines.keys())
 
     def executed_lines(self, filename):
         """A map containing all the line numbers executed in `filename`.

coverage/execfile.py

 
 import imp, os, sys
 
+from coverage.backward import exec_function
+
+
 try:
     # In Py 2.x, the builtins were in __builtin__
     BUILTINS = sys.modules['__builtin__']
 
     try:
         source = open(filename, 'rU').read()
-        exec compile(source, filename, "exec") in main_mod.__dict__
+        exec_function(source, filename, main_mod.__dict__)
     finally:
         # Restore the old __main__
         sys.modules['__main__'] = old_main_mod

coverage/files.py

         An absolute path with no redundant components and normalized case.
         
         """
-        if not self.canonical_filename_cache.has_key(filename):
+        if filename not in self.canonical_filename_cache:
             f = filename
             if os.path.isabs(f) and not os.path.exists(f):
                 if not self.get_zip_data(f):

coverage/parser.py

 """Code parsing for Coverage."""
 
 import re, sys, token, tokenize, types
-import cStringIO as StringIO
 
 from coverage.misc import nice_pair, CoverageException
-from coverage.backward import set   # pylint: disable-msg=W0622
+from coverage.backward import set, StringIO   # pylint: disable-msg=W0622
 
 
     
         prev_toktype = token.INDENT
         first_line = None
 
-        tokgen = tokenize.generate_tokens(StringIO.StringIO(text).readline)
+        tokgen = tokenize.generate_tokens(StringIO(text).readline)
         for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen:
             if self.show_tokens:
-                print "%10s %5s %-20r %r" % (
+                print("%10s %5s %-20r %r" % (
                     tokenize.tok_name.get(toktype, toktype),
                     nice_pair((slineno, elineno)), ttext, ltext
-                    )
+                    ))
             if toktype == token.INDENT:
                 indent += 1
             elif toktype == token.DEDENT:
             elif toktype == token.STRING and prev_toktype == token.INDENT:
                 # Strings that are first on an indented line are docstrings.
                 # (a trick from trace.py in the stdlib.)
-                for i in xrange(slineno, elineno+1):
+                for i in range(slineno, elineno+1):
                     self.docstrings.add(i)
             elif toktype == token.NEWLINE:
                 if first_line is not None and elineno != first_line:
                     # different line than the first line of the statement,
                     # so record a multi-line range.
                     rng = (first_line, elineno)
-                    for l in xrange(first_line, elineno+1):
+                    for l in range(first_line, elineno+1):
                         self.multiline[l] = rng
                 first_line = None
                 
             # text ends nicely for them.
             text += '\n'
             code = compile(text, filename, "exec")
-        except SyntaxError, synerr:
+        except SyntaxError:
+            _, synerr, _ = sys.exc_info()
             raise CoverageException(
                 "Couldn't parse '%s' as Python source: '%s' at line %d" %
                     (filename, synerr.msg, synerr.lineno)
                 m1 = '"'
             if lineno in self.excluded:
                 m2 = 'x'
-            print "%4d %s%s%s %s" % (lineno, m0, m1, m2, ltext)
+            print("%4d %s%s%s %s" % (lineno, m0, m1, m2, ltext))
 
 
 if __name__ == '__main__':

coverage/templite.py

                     value = getattr(value, dot)
                 except AttributeError:
                     value = value[dot]
-                if callable(value):
+                if hasattr(value, '__call__'):
                     value = value()
         else:
             value = self.context[key]
         self.call_count = 0
         self.call_args_list = []
         self.method_calls = []
-        for child in self._children.itervalues():
+        for child in self._children.values():
             child.reset_mock()
         if isinstance(self._return_value, Mock):
             self._return_value.reset_mock()
     from setuptools import setup
     from distutils.core import Extension
 
-    package_name = 'coverage'
-
     more_setup_args = dict(
         entry_points = {
             'console_scripts': [
     # No setuptools yet for Py 3.x, so do without.
     from distutils.core import setup, Extension
 
-    package_name = 'coverage3k'
-
     more_setup_args = dict(
         scripts = [
             'scripts/coverage',
 # Set it up!
 
 setup(
-    name = package_name,
+    name = 'coverage',
     version = __version__,
 
     packages = [
+"""Add things to old Pythons so I can pretend they are newer, for tests."""
+
+# pylint: disable-msg=W0622
+# (Redefining built-in blah)
+# The whole point of this file is to redefine built-ins, so shut up about it.
+
+import os, sys
+
+# Py2k and 3k don't agree on how to run commands in a subprocess.
+try:
+    import subprocess
+except ImportError:
+    def run_command(cmd):
+        """Run a command in a subprocess.
+        
+        Returns the exit code and the combined stdout and stderr.
+        
+        """
+        _, stdouterr = os.popen4(cmd)
+        return 0, stdouterr.read()
+else:
+    def run_command(cmd):
+        """Run a command in a subprocess.
+        
+        Returns the exit code and the combined stdout and stderr.
+        
+        """
+
+        if sys.hexversion > 0x03000000 and cmd.startswith("coverage "):
+            # We don't have a coverage command on 3.x, so fix it up to call the
+            # script. Eventually we won't need this.
+            cmd = "python " + sys.prefix + os.sep + "Scripts" + os.sep + cmd
+
+        proc = subprocess.Popen(cmd, shell=True,
+                stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                stderr=subprocess.STDOUT
+                )
+        retcode = proc.wait()
+        
+        # Get the output, and canonicalize it to strings with newlines.
+        output = proc.stdout.read()
+        if not isinstance(output, str):
+            output = output.decode('utf-8')
+        output = output.replace('\r', '')
+        
+        return retcode, output
+
+# No more execfile in Py3k
+try:
+    execfile = execfile
+except NameError:
+    def execfile(filename, globs):
+        """A Python 3 implementation of execfile."""
+        exec(compile(open(filename).read(), filename, 'exec'), globs)

test/coveragetest.py

 """Base test case class for coverage testing."""
 
 import imp, os, random, shutil, sys, tempfile, textwrap, unittest
-from cStringIO import StringIO
 
 import coverage
-from coverage.backward import set, run_command   # pylint: disable-msg=W0622
+from coverage.backward import set, StringIO   # pylint: disable-msg=W0622
+from backtest import run_command
 
 
 class Tee(object):
         """
         try:
             callableObj(*args, **kwargs)
-        except excClass, exc:
+        except excClass:
+            _, exc, _ = sys.exc_info()
             excMsg = str(exc)
             if not msg:
                 # No message provided: it passes.
         os.environ['PYTHONPATH'] = pypath
         
         _, output = run_command(cmd)
-        print output
+        print(output)
         return output
 
     def assert_equal_sets(self, s1, s2):

test/farm/run/src/chdir.py

 import os
-print "Line One"
+print("Line One")
 os.chdir("subdir")
-print "Line Two"
+print("Line Two")
 

test/farm/run/src/showtrace.py

 
 import sys
 
-# Print the argument as a label for the output.
-print sys.argv[1],
-
 # Show what the trace function is.  If a C-based function is used, then f_trace
 # is None.
 trace_fn = sys._getframe(0).f_trace
 if trace_fn is None:
-    print "None"
+    trace_name = "None"
 else:
-    print trace_fn.im_class.__name__
+    # Get the name of the tracer class.  Py3k has a different way to get it.
+    try:
+        trace_name = trace_fn.im_class.__name__
+    except AttributeError:
+        trace_name = trace_fn.__self__.__class__.__name__
+
+print("%s %s" % (sys.argv[1], trace_name))
 """Tests for Coverage's api."""
 
 import os, re, sys, textwrap
-from cStringIO import StringIO
 
 import coverage
+from coverage.backward import StringIO
+
+sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
 from coveragetest import CoverageTest
 
 

test/test_cmdline.py

 """Test cmdline.py for coverage."""
 
-import re, shlex, textwrap, unittest
+import os, re, shlex, sys, textwrap, unittest
 import mock
 import coverage
+
+sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
 from coveragetest import CoverageTest
 
 

test/test_codeunit.py

 
 from coverage.codeunit import code_unit_factory
 from coverage.files import FileLocator
+
+sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
 from coveragetest import CoverageTest
 
 # pylint: disable-msg=F0401

test/test_coverage.py

 # http://nedbatchelder.com/code/coverage
 
 import os, sys, unittest
-from cStringIO import StringIO
 
 import coverage
+from coverage.backward import StringIO
 coverage.use_cache(0)
 
+sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
 from coveragetest import CoverageTest
 
 
 
 
 if __name__ == '__main__':
-    print "Testing under Python version: %s" % sys.version
+    print("Testing under Python version: %s" % sys.version)
     unittest.main()
 
 

test/test_data.py

 """Tests for coverage.data"""
 
-import cPickle as pickle
+import os, sys
+
+from coverage.backward import pickle
 from coverage.data import CoverageData
+
+sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
 from coveragetest import CoverageTest
 
 

test/test_execfile.py

 """Tests for coverage.execfile"""
 
-import os
+import os, sys
 
 from coverage.execfile import run_python_file
+
+sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
 from coveragetest import CoverageTest
 
 here = os.path.dirname(__file__)

test/test_farm.py

 """Run tests in the farm subdirectory.  Designed for nose."""
 
 import filecmp, fnmatch, glob, os, shutil, sys
-from coverage.backward import run_command
+
+sys.path.insert(0, os.path.split(__file__)[0]) # Force relative import for Py3k
+from backtest import run_command, execfile # pylint: disable-msg=W0622
 
 
 def test_farm(clean_only=False):
                 if not cmd:
                     continue
                 retcode, output = run_command(cmd)
-                print output,
+                print(output.rstrip())
                 if outfile:
                     open(outfile, "a+").write(output)
                 if retcode:
         for test in test_farm(clean_only=True):
             test[0].run_fully()
     else:
-        print "Need an operation: run, out, clean"
+        print("Need an operation: run, out, clean")
     
 # So that we can run just one farm run.py at a time.
 if __name__ == '__main__':

three.cmd

-@REM Copy the sources, convert to Py3k, and run the tests.
-
-rmdir /s/q three ..\three
-xcopy /s/h/i/e /exclude:notsource.txt . ..\three
-move ..\three .
-cd three
-call switchpy 31
-python \python31\Tools\Scripts\2to3.py --write --nobackups --no-diffs coverage test mock.py
-
-make clean
-make testdata
-
-python setup.py install
-
-@REM Run both modes of tracer
-set COVERAGE_TEST_TRACER=c
-python \python31\Scripts\nosetests3 %1
-del \python31\lib\site-packages\coverage\tracer.pyd
-set COVERAGE_TEST_TRACER=py
-python \python31\Scripts\nosetests3 %1
-
-cd ..