[Zope3-checkins]
SVN: zope.testing/trunk/src/zope/testing/testrunner
Merge tim-pyc branch.
Tim Peters
tim.one at comcast.net
Wed Sep 28 18:15:02 EDT 2005
Log message for revision 38672:
Merge tim-pyc branch.
Adds a new `--usecompiled` option. When specified, tests will run even if
.py source files are missing, provided the appropriate .pyc or .pyo files
exist.
Sped find_test_files() by hoisting an invariant test out of a loop.
Miscellaneous whitespace and typo repairs.
Changed:
U zope.testing/trunk/src/zope/testing/testrunner-edge-cases.txt
A zope.testing/trunk/src/zope/testing/testrunner-ex/usecompiled/
_U zope.testing/trunk/src/zope/testing/testrunner-ex/usecompiled/README.txt
_U zope.testing/trunk/src/zope/testing/testrunner-ex/usecompiled/__init__.py
_U zope.testing/trunk/src/zope/testing/testrunner-ex/usecompiled/compiletest.py
_U zope.testing/trunk/src/zope/testing/testrunner-ex/usecompiled/package/__init__.py
_U zope.testing/trunk/src/zope/testing/testrunner-ex/usecompiled/package/compiletest.py
U zope.testing/trunk/src/zope/testing/testrunner.py
U zope.testing/trunk/src/zope/testing/testrunner.txt
-=-
Modified: zope.testing/trunk/src/zope/testing/testrunner-edge-cases.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner-edge-cases.txt 2005-09-28 21:22:25 UTC (rev 38671)
+++ zope.testing/trunk/src/zope/testing/testrunner-edge-cases.txt 2005-09-28 22:15:02 UTC (rev 38672)
@@ -144,7 +144,7 @@
<BLANKLINE>
Error in test test_post_mortem2 (sample3.sampletests_d.TestSomething)
Traceback (most recent call last):
- File "testrunner-ex/sample3/sampletests_d.py",
+ File "testrunner-ex/sample3/sampletests_d.py",
line 37, in test_post_mortem2
g()
File "testrunner-ex/sample3/sampletests_d.py", line 46, in g
@@ -184,7 +184,7 @@
exc_info)
File "zope/testing/doctest.py", line 1737, in report_unexpected_exception
raise UnexpectedException(test, example, exc_info)
- UnexpectedException:
+ UnexpectedException:
from testrunner-ex/sample3/sampletests_d.py:61 (2 examples)>
<BLANKLINE>
exceptions.ValueError:
@@ -310,7 +310,7 @@
<BLANKLINE>
Error in test post_mortem_failure2 (sample3.sampletests_d)
<BLANKLINE>
- File "testrunner-ex/sample3/sampletests_d.py",
+ File "testrunner-ex/sample3/sampletests_d.py",
line 81, in sample3.sampletests_d.post_mortem_failure2
<BLANKLINE>
x
@@ -366,7 +366,7 @@
(Pdb) c
True
-Post-mortem debugging with tripple verbosity
+Post-mortem debugging with triple verbosity
>>> sys.argv = 'test --layer samplelayers.Layer1$ -vvv -D'.split()
>>> testrunner.run(defaults)
Copied: zope.testing/trunk/src/zope/testing/testrunner-ex/usecompiled (from rev 38671, zope.testing/branches/tim-pyc/src/zope/testing/testrunner-ex/usecompiled)
Modified: zope.testing/trunk/src/zope/testing/testrunner.py
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner.py 2005-09-28 21:22:25 UTC (rev 38671)
+++ zope.testing/trunk/src/zope/testing/testrunner.py 2005-09-28 22:15:02 UTC (rev 38672)
@@ -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
Modified: zope.testing/trunk/src/zope/testing/testrunner.txt
===================================================================
--- zope.testing/trunk/src/zope/testing/testrunner.txt 2005-09-28 21:22:25 UTC (rev 38671)
+++ zope.testing/trunk/src/zope/testing/testrunner.txt 2005-09-28 22:15:02 UTC (rev 38672)
@@ -239,8 +239,8 @@
In most of the examples here, we set up `sys.argv`. In normal usage,
the testrunner just uses `sys.argv`. It is possible to pass athiments
-explicitly.
-
+explicitly.
+
>>> testrunner.run(defaults, 'test --layer 111'.split())
Running samplelayers.Layer111 tests:
Set up samplelayers.Layerx in N.NNN seconds.
@@ -1395,7 +1395,7 @@
File "testrunner-ex/sample2/sampletests_1.py", line 17, in eek
<BLANKLINE>
----------------------------------------------------------------------
- File "testrunner-ex/sample2/sampletests_1.py", line 19,
+ File "testrunner-ex/sample2/sampletests_1.py", line 19,
in sample2.sampletests_1.eek
Failed example:
x = y
@@ -1407,7 +1407,7 @@
x = y
NameError: name 'y' is not defined
----------------------------------------------------------------------
- File "testrunner-ex/sample2/sampletests_1.py", line 21,
+ File "testrunner-ex/sample2/sampletests_1.py", line 21,
in sample2.sampletests_1.eek
Failed example:
x
@@ -1419,7 +1419,7 @@
x
NameError: name 'x' is not defined
----------------------------------------------------------------------
- File "testrunner-ex/sample2/sampletests_1.py", line 24,
+ File "testrunner-ex/sample2/sampletests_1.py", line 24,
in sample2.sampletests_1.eek
Failed example:
z = x + 1
@@ -1449,7 +1449,7 @@
File "testrunner-ex/sample2/sampletests_1.py", line 17, in eek
<BLANKLINE>
----------------------------------------------------------------------
- File "testrunner-ex/sample2/sampletests_1.py", line 19,
+ File "testrunner-ex/sample2/sampletests_1.py", line 19,
in sample2.sampletests_1.eek
Failed example:
x = y
@@ -1592,7 +1592,7 @@
break in the pdb.set_trace function. It was necessary to use 'next'
or 'up' to get to the application code that called pdb.set_trace. In
Python 2.4, pdb.set_trace causes pdb to stop right after the call to
-pdb.set_trace.
+pdb.set_trace.
You can also do post-mortem debugging, using the --post-mortem (-D)
option:
@@ -1608,7 +1608,7 @@
<BLANKLINE>
Error in test test_post_mortem1 (sample3.sampletests_d.TestSomething)
Traceback (most recent call last):
- File "testrunner-ex/sample3/sampletests_d.py",
+ File "testrunner-ex/sample3/sampletests_d.py",
line 34, in test_post_mortem1
raise ValueError
ValueError
@@ -1926,3 +1926,89 @@
>>> import shutil
>>> shutil.rmtree('coverage_dir')
+
+Running Without Source Code
+---------------------------
+
+The ``--usecompiled`` option allows running tests in a tree without .py
+source code, provided compiled .pyc or .pyo files exist (without
+``--usecompiled``, .py files are necessary).
+
+We have a very simple directory tree, under ``usecompiled/``, to test
+this. Because we're going to delete its .py files, we want to work
+in a copy of that:
+
+ >>> NEWNAME = "unlikely_package_name"
+ >>> src = os.path.join(this_directory, 'testrunner-ex', 'usecompiled')
+ >>> os.path.isdir(src)
+ True
+ >>> dst = os.path.join(this_directory, 'testrunner-ex', NEWNAME)
+ >>> os.path.isdir(dst)
+ False
+
+Have to use our own copying code, to avoid copying read-only SVN files that
+can't be deleted later.
+
+ >>> n = len(src) + 1
+ >>> for root, dirs, files in os.walk(src):
+ ... dirs[:] = [d for d in dirs if d == "package"] # prune cruft
+ ... os.mkdir(os.path.join(dst, root[n:]))
+ ... for f in files:
+ ... shutil.copy(os.path.join(root, f),
+ ... os.path.join(dst, root[n:], f))
+
+Now run the tests in the copy:
+
+ >>> mydefaults = [
+ ... '--path', directory_with_tests,
+ ... '--tests-pattern', '^compiletest$',
+ ... '--package', NEWNAME,
+ ... '-vv',
+ ... ]
+ >>> sys.argv = ['test']
+ >>> testrunner.run(mydefaults)
+ Running tests at level 1
+ Running unit tests:
+ Running:
+ test1 (unlikely_package_name.compiletest.Test)
+ test2 (unlikely_package_name.compiletest.Test)
+ test1 (unlikely_package_name.package.compiletest.Test)
+ test2 (unlikely_package_name.package.compiletest.Test)
+ Ran 4 tests with 0 failures and 0 errors in N.NNN seconds.
+ False
+
+If we delete the source files, it's normally a disaster: the test runner
+doesn't believe any test files, or even packages, exist. Note that we pass
+``--keepbytecode`` this time, because otherwise the test runner would
+delete the compiled Python files too:
+
+ >>> for root, dirs, files in os.walk(dst):
+ ... dirs[:] = [d for d in dirs if d == "package"] # prune cruft
+ ... for f in files:
+ ... if f.endswith(".py"):
+ ... os.remove(os.path.join(root, f))
+ >>> testrunner.run(mydefaults, ["test", "--keepbytecode"])
+ Running tests at level 1
+ Total: 0 tests, 0 failures, 0 errors
+ False
+
+Finally, passing ``--usecompiled`` asks the test runner to treat .pyc
+and .pyo files as adequate replacements for .py files. Note that the
+output is the same as when running with .py source above. The absence
+of "removing stale bytecode ..." messages shows that ``--usecompiled``
+also implies ``--keepbytecode``:
+
+ >>> testrunner.run(mydefaults, ["test", "--usecompiled"])
+ Running tests at level 1
+ Running unit tests:
+ Running:
+ test1 (unlikely_package_name.compiletest.Test)
+ test2 (unlikely_package_name.compiletest.Test)
+ test1 (unlikely_package_name.package.compiletest.Test)
+ test2 (unlikely_package_name.package.compiletest.Test)
+ Ran 4 tests with 0 failures and 0 errors in N.NNN seconds.
+ False
+
+Remove the copy:
+
+ >>> shutil.rmtree(dst)
More information about the Zope3-Checkins
mailing list