[Zope3-checkins] SVN: zope.testing/branches/tim-pyc/src/zope/testing/testrunner.py Implementing --usecompiled.

Tim Peters tim.one at comcast.net
Tue Sep 27 18:38:09 EDT 2005


Log message for revision 38657:
  Implementing --usecompiled.
  
  Sped find_test_files() by hoisting an invariant
  test out of a loop.
  

Changed:
  U   zope.testing/branches/tim-pyc/src/zope/testing/testrunner.py

-=-
Modified: zope.testing/branches/tim-pyc/src/zope/testing/testrunner.py
===================================================================
--- zope.testing/branches/tim-pyc/src/zope/testing/testrunner.py	2005-09-27 22:35:06 UTC (rev 38656)
+++ zope.testing/branches/tim-pyc/src/zope/testing/testrunner.py	2005-09-27 22:38:09 UTC (rev 38657)
@@ -69,13 +69,57 @@
             threading.settrace(None)
         self.starte = False
 
-
 class EndRun(Exception):
     """Indicate that the existing run call should stop
 
     Used to prevent additional test output after post-mortem debugging.
     """
 
+def strip_py_ext(options, path):
+    """Return path without its .py (or .pyc or .pyo) extension, or None.
+
+    If options.usecompiled is false:
+        If path ends with ".py", the path without the extension is returned.
+        Else None is returned.
+
+    If options.usecompiled is true:
+        If Python is running with -O, a .pyo extension is also accepted.
+        If Python is running without -O, a .pyc extension is also accepted.
+    """
+    if path.endswith(".py"):
+        return path[:-3]
+    if options.usecompiled:
+        if __debug__:
+            # Python is running without -O.
+            ext = ".pyc"
+        else:
+            # Python is running with -O.
+            ext = ".pyo"
+        if path.endswith(ext):
+            return path[:-len(ext)]
+    return None
+
+def contains_init_py(options, fnamelist):
+    """Return true iff fnamelist contains a suitable spelling of __init__.py.
+
+    If options.usecompiled is false, this is so iff "__init__.py" is in
+    the list.
+
+    If options.usecompiled is true, then "__init__.pyo" is also acceptable
+    if Python is running with -O, and "__init__.pyc" is also acceptable if
+    Python is running without -O.
+    """
+    if "__init__.py" in fnamelist:
+        return True
+    if options.usecompiled:
+        if __debug__:
+            # Python is running without -O.
+            return "__init__.pyc" in fnamelist
+        else:
+            # Python is running with -O.
+            return "__init__.pyo" in fnamelist
+    return False
+
 def run(defaults=None, args=None):
     if args is None:
         args = sys.argv
@@ -707,7 +751,10 @@
         for prefix in options.prefix:
             if fpath.startswith(prefix):
                 # strip prefix, strip .py suffix and convert separator to dots
-                module_name = fpath[len(prefix):-3].replace(os.path.sep, '.')
+                noprefix = fpath[len(prefix):]
+                noext = strip_py_ext(options, noprefix)
+                assert noext is not None
+                module_name = noext.replace(os.path.sep, '.')
                 try:
                     module = import_name(module_name)
                 except:
@@ -737,35 +784,60 @@
 def find_test_files(options):
     found = {}
     for f in find_test_files_(options):
+        if f in found:
+            continue
         for filter in options.module:
             if filter(f):
-                if f not in found:
-                    found[f] = 1
-                    yield f
-                    break
+                found[f] = 1
+                yield f
+                break
 
 identifier = re.compile(r'[_a-zA-Z]\w*$').match
 def find_test_files_(options):
     tests_pattern = options.tests_pattern
     test_file_pattern = options.test_file_pattern
+
+    # If options.usecompiled, we can accept .pyc or .pyo files instead
+    # of .py files.  We'd rather use a .py file if one exists.  `root2ext`
+    # maps a test file path, sans extension, to the path with the best
+    # extension found (.py if it exists, else .pyc or .pyo).
+    # Note that "py" < "pyc" < "pyo", so if more than one extension is
+    # found, the lexicographically smaller one is best.
+
+    # Found a new test file, in directory `dirname`.  `noext` is the
+    # file name without an extension, and `withext` is the file name
+    # with its extension.
+    def update_root2ext(dirname, noext, withext):
+        key = os.path.join(dirname, noext)
+        new = os.path.join(dirname, withext)
+        if key in root2ext:
+            root2ext[key] = min(root2ext[key], new)
+        else:
+            root2ext[key] = new
+
     for p in test_dirs(options, {}):
         for dirname, dirs, files in walk_with_symlinks(options, p):
-            if (dirname != p) and ('__init__.py' not in files):
-                continue
+            if dirname != p and not contains_init_py(options, files):
+                continue    # not a plausible test directory
+            root2ext = {}
             dirs[:] = filter(identifier, dirs)
             d = os.path.split(dirname)[1]
-            if tests_pattern(d) and ('__init__.py' in files):
+            if tests_pattern(d) and contains_init_py(options, files):
                 # tests directory
                 for file in files:
-                    if file.endswith('.py') and test_file_pattern(file[:-3]):
-                        f = os.path.join(dirname, file)
-                        yield f
+                    noext = strip_py_ext(options, file)
+                    if noext and test_file_pattern(noext):
+                        update_root2ext(dirname, noext, file)
 
             for file in files:
-                if file.endswith('.py') and tests_pattern(file[:-3]):
-                    f = os.path.join(dirname, file)
-                    yield f
+                noext = strip_py_ext(options, file)
+                if noext and tests_pattern(noext):
+                    update_root2ext(dirname, noext, file)
 
+            winners = root2ext.values()
+            winners.sort()
+            for file in winners:
+                yield file
 
 def walk_with_symlinks(options, dir):
     # TODO -- really should have test of this that uses symlinks
@@ -1134,6 +1206,19 @@
 the tests, can make the test run go much faster.
 """)
 
+other.add_option(
+    '--usecompiled', action="store_true", dest='usecompiled',
+    help="""\
+Normally, a package must contain an __init__.py file, and only .py files
+can contain test code.  When this option is specified, compiled Python
+files (.pyc and .pyo) can be used instead:  a directory containing
+__init__.pyc or __init__.pyo is also considered to be a package, and if
+file XYZ.py contains tests but is absent while XYZ.pyc or XYZ.pyo exists
+then the compiled files will be used.  This is necessary when running
+tests against a tree where the .py files have been removed after
+compilation to .pyc/.pyo.  Use of this option implies --keepbytecode.
+""")
+
 parser.add_option_group(other)
 
 ######################################################################
@@ -1218,6 +1303,9 @@
 
     options.layer = options.layer and dict([(l, 1) for l in options.layer])
 
+    if options.usecompiled:
+        options.keepbytecode = options.usecompiled
+
     return options
 
 # Command-line UI



More information about the Zope3-Checkins mailing list