[Zodb-checkins] CVS: ZODB4 - log.ini:1.1 test.py:1.16
Jeremy Hylton
jeremy at zope.com
Tue Jun 24 16:33:31 EDT 2003
Update of /cvs-repository/ZODB4
In directory cvs.zope.org:/tmp/cvs-serv28989
Modified Files:
test.py
Added Files:
log.ini
Log Message:
Restore the log.ini added last week and use it from test.py.
The log.ini was also used by z3.py to configure logging. It was just
a bug that test.py didn't try to load it.
=== Added File ZODB4/log.ini ===
# This file configures the logging module: critical errors are logged
# to z3.log; everything else is ignored.
# To use this configuration, use logging.config.fileConfig("log.ini").
# Documentation for the file format is at
# http://www.red-dove.com/python_logging.html#config
[logger_root]
level=CRITICAL
handlers=normal
[handler_normal]
class=FileHandler
level=NOTSET
formatter=common
args=('z3.log', 'a')
filename=z3.log
mode=a
[formatter_common]
format=------
%(asctime)s %(levelname)s %(name)s %(message)s
datefmt=%Y-%m-%dT%H:%M:%S
[loggers]
keys=root
[handlers]
keys=normal
[formatters]
keys=common
=== ZODB4/test.py 1.15 => 1.16 ===
--- ZODB4/test.py:1.15 Wed Mar 12 18:32:52 2003
+++ ZODB4/test.py Tue Jun 24 15:33:30 2003
@@ -1,18 +1,19 @@
+#! /usr/bin/env python2.2
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
-#
+#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
-#
+#
##############################################################################
"""
-test.py [-abCdgGLvvt] [modfilter [testfilter]]
+test.py [-aBbcdDfgGhLmprtTuv] [modfilter [testfilter]]
Test harness.
@@ -41,9 +42,19 @@
Unfortunately, the debug harness doesn't print the name of the
test, so Use With Care.
+--dir directory
+ Option to limit where tests are searched for. This is
+ important when you *really* want to limit the code that gets run.
+ For example, if refactoring interfaces, you don't want to see the way
+ you have broken setups for tests in other packages. You *just* want to
+ run the interface tests.
+
-D
Works like -d, except that it loads pdb when an exception occurs.
+-f
+ Run functional tests instead of unit tests.
+
-g threshold
Set the garbage collector generation0 threshold. This can be used to
stress memory and gc correctness. Some crashes are only reproducible when
@@ -55,17 +66,14 @@
of the DEBUG_ flags defined bythe Python gc module. Multiple options
can be specified by using "-G OPTION1 -G OPTION2."
--v
- With one -v, unittest prints a dot (".") for each test run. With
- -vv, unittest prints the name of each test (for some definition of
- "name" ...). Witn no -v, unittest is silent until the end of the
- run, except when errors occur.
-
--libdir test_root
Search for tests starting in the specified start directory
(useful for testing components being developed outside the main
"src" or "build" trees).
+--keepbytecode
+ Do not delete all stale bytecode before running tests
+
-L
Keep running the selected tests in a loop. You may experience
memory leakage.
@@ -77,6 +85,10 @@
-p
Show running progress. It can be combined with -v or -vv.
+-r
+ Look for refcount problems.
+ This requires that Python was built --with-pydebug.
+
-T
Use the trace module from Python for code coverage. XXX This only works
if trace.py is explicitly added to PYTHONPATH. The current utility writes
@@ -167,28 +179,48 @@
self.dots = False
self._progressWithNames = True
self._lastWidth = 0
- self._maxWidth = 80 # would be nice to determine terminal width
+ self._maxWidth = 80
+ try:
+ import curses
+ except ImportError:
+ pass
+ else:
+ import curses.wrapper
+ def get_max_width(scr, self=self):
+ self._maxWidth = scr.getmaxyx()[1]
+ try:
+ curses.wrapper(get_max_width)
+ except curses.error:
+ pass
self._maxWidth -= len("xxxx/xxxx (xxx.x%): ") + 1
def stopTest(self, test):
self._testtimes[test] = time.time() - self._testtimes[test]
if gc.garbage:
+ print "The following test left garbage:"
print test
print gc.garbage
-
- def print_times(self):
+ # XXX Perhaps eat the garbage here, so that the garbage isn't
+ # printed for every subsequent test.
+
+ def print_times(self, stream, count=None):
results = self._testtimes.items()
results.sort(lambda x, y: cmp(y[1], x[1]))
- n = min(50, len(results))
+ if count:
+ n = min(count, len(results))
+ if n:
+ print >>stream, "Top %d longest tests:" % n
+ else:
+ n = len(results)
if not n:
return
- print "Top %d longest tests:" % n
for i in range(n):
- print "%6dms" % int(results[i][1] * 1000), results[i][0]
+ print >>stream, "%6dms" % int(results[i][1] * 1000), results[i][0]
def _print_traceback(self, msg, err, test, errlist):
- if self.showAll or self.dots:
+ if self.showAll or self.dots or self._progress:
self.stream.writeln("\n")
+ self._lastWidth = 0
tb = "".join(traceback.format_exception(*err))
self.stream.writeln(msg)
@@ -205,16 +237,31 @@
self.stream.write(": ")
elif self._progressWithNames:
# XXX will break with multibyte strings
- name = self.getDescription(test)
+ name = self.getShortDescription(test)
width = len(name)
if width < self._lastWidth:
name += " " * (self._lastWidth - width)
- self.stream.write(": %s" % name[:self._maxWidth])
+ self.stream.write(": %s" % name)
self._lastWidth = width
self.stream.flush()
self.__super_startTest(test)
self._testtimes[test] = time.time()
+ def getShortDescription(self, test):
+ s = self.getDescription(test)
+ if len(s) > self._maxWidth:
+ pos = s.find(" (")
+ if pos >= 0:
+ w = self._maxWidth - (pos + 5)
+ if w < 1:
+ # first portion (test method name) is too long
+ s = s[:self._maxWidth-3] + "..."
+ else:
+ pre = s[:pos+2]
+ post = s[-w:]
+ s = "%s...%s" % (pre, post)
+ return s[:self._maxWidth]
+
def addError(self, test, err):
if self._progress:
self.stream.write("\r")
@@ -242,7 +289,7 @@
self.stream.writeln("%s: %s" % (flavor, self.getDescription(test)))
self.stream.writeln(self.separator2)
self.stream.writeln(err)
-
+
class ImmediateTestRunner(unittest.TextTestRunner):
@@ -296,13 +343,25 @@
self.cwd = os.getcwd()
sys.path.insert(0, os.path.join(self.cwd, self.libdir))
# Hack again for external products.
+ global functional
+ kind = functional and "functional" or "unit"
if libdir:
extra = os.path.join(org_cwd, libdir)
- print "Running tests from", extra
+ print "Running %s tests from %s" % (kind, extra)
self.libdir = extra
sys.path.insert(0, extra)
else:
- print "Running tests from", self.cwd
+ print "Running %s tests from %s" % (kind, self.cwd)
+ # Make sure functional tests find ftesting.zcml
+ if functional:
+ config_file = 'ftesting.zcml'
+ if not self.inplace:
+ # We chdired into build, so ftesting.zcml is in the
+ # parent directory
+ config_file = os.path.join('..', 'ftesting.zcml')
+ print "Parsing %s" % config_file
+ from zope.testing.functional import FunctionalTestSetup
+ FunctionalTestSetup(config_file)
def match(rx, s):
if not rx:
@@ -315,10 +374,17 @@
class TestFileFinder:
def __init__(self, prefix):
self.files = []
- self._plen = len(prefix)+1
+ self._plen = len(prefix)
+ if not prefix.endswith(os.sep):
+ self._plen += 1
+ global functional
+ if functional:
+ self.dirname = "ftests"
+ else:
+ self.dirname = "tests"
def visit(self, rx, dir, files):
- if dir[-5:] != "tests":
+ if os.path.split(dir)[1] != self.dirname:
return
# ignore tests that aren't in packages
if not "__init__.py" in files:
@@ -326,6 +392,16 @@
return
print "not a package", dir
return
+
+ # Put matching files in matches. If matches is non-empty,
+ # then make sure that the package is importable.
+ matches = []
+ for file in files:
+ if file.startswith('test') and os.path.splitext(file)[-1] == '.py':
+ path = os.path.join(dir, file)
+ if match(rx, path):
+ matches.append(path)
+
# ignore tests when the package can't be imported, possibly due to
# dependency failures.
pkg = dir[self._plen:].replace(os.sep, '.')
@@ -337,12 +413,8 @@
if VERBOSE:
print "skipping %s because: %s" % (pkg, e)
return
-
- for file in files:
- if file.startswith('test') and os.path.splitext(file)[-1] == '.py':
- path = os.path.join(dir, file)
- if match(rx, path):
- self.files.append(path)
+ else:
+ self.files.extend(matches)
def module_from_path(self, path):
"""Return the Python package name indicated by the filesystem path."""
@@ -351,10 +423,29 @@
mod = path.replace(os.sep, ".")
return mod
+def walk_with_symlinks(top, func, arg):
+ """Like os.path.walk, but follows symlinks on POSIX systems.
+
+ This could theoreticaly result in an infinite loop, if you create symlink
+ cycles in your Zope sandbox, so don't do that.
+ """
+ try:
+ names = os.listdir(top)
+ except os.error:
+ return
+ func(arg, top, names)
+ exceptions = ('.', '..')
+ for name in names:
+ if name not in exceptions:
+ name = os.path.join(top, name)
+ if os.path.isdir(name):
+ walk_with_symlinks(name, func, arg)
+
def find_tests(rx):
global finder
finder = TestFileFinder(pathinit.libdir)
- os.path.walk(pathinit.libdir, finder.visit, rx)
+ walkdir = test_dir or pathinit.libdir
+ walk_with_symlinks(walkdir, finder.visit, rx)
return finder.files
def package_import(modname):
@@ -402,7 +493,7 @@
if build_inplace:
utildir = os.path.join(os.getcwd(), "utilities")
else:
- utildir = os.path.join(os.getcwd(), "../utilities")
+ utildir = os.path.join(os.getcwd(), "..", "utilities")
sys.path.append(utildir)
import unittestgui
suites = []
@@ -413,6 +504,40 @@
minimal = (GUI == "minimal")
unittestgui.main(suites, minimal)
+class TrackRefs:
+ """Object to track reference counts across test runs."""
+
+ def __init__(self):
+ self.type2count = {}
+ self.type2all = {}
+
+ def update(self):
+ obs = sys.getobjects(0)
+ type2count = {}
+ type2all = {}
+ for o in obs:
+ all = sys.getrefcount(o)
+ t = type(o)
+ if t in type2count:
+ type2count[t] += 1
+ type2all[t] += all
+ else:
+ type2count[t] = 1
+ type2all[t] = all
+
+ ct = [(type2count[t] - self.type2count.get(t, 0),
+ type2all[t] - self.type2all.get(t, 0),
+ t)
+ for t in type2count.iterkeys()]
+ ct.sort()
+ ct.reverse()
+ for delta1, delta2, t in ct:
+ if delta1 or delta2:
+ print "%-55s %8d %8d" % (t, delta1, delta2)
+
+ self.type2count = type2count
+ self.type2all = type2all
+
def runner(files, test_filter, debug):
runner = ImmediateTestRunner(verbosity=VERBOSE, debug=debug,
progress=progress)
@@ -426,8 +551,12 @@
suite.addTest(s)
try:
r = runner.run(suite)
+ if timesfn:
+ r.print_times(open(timesfn, "w"))
+ if VERBOSE:
+ print "Wrote timing data to", timesfn
if timetests:
- r.print_times()
+ r.print_times(sys.stdout, timetests)
except:
if debugger:
pdb.post_mortem(sys.exc_info()[2])
@@ -445,17 +574,17 @@
os.unlink(fullname)
def main(module_filter, test_filter, libdir):
- global pathinit
-
- os.path.walk(os.curdir, remove_stale_bytecode, None)
+ if not keepStaleBytecode:
+ os.path.walk(os.curdir, remove_stale_bytecode, None)
# Get the log.ini file from the current directory instead of possibly
# buried in the build directory. XXX This isn't perfect because if
# log.ini specifies a log file, it'll be relative to the build directory.
# Hmm...
- logini = os.path.abspath('log.ini')
+ logini = os.path.abspath("log.ini")
# Initialize the path and cwd
+ global pathinit
pathinit = PathInit(build, build_inplace, libdir)
# Initialize the logging module.
@@ -467,6 +596,8 @@
else:
level = logging.CRITICAL
logging.root.setLevel(level)
+ if os.path.exists(logini):
+ logging.config.fileConfig(logini)
files = find_tests(module_filter)
files.sort()
@@ -474,8 +605,20 @@
if GUI:
gui_runner(files, test_filter)
elif LOOP:
+ if REFCOUNT:
+ rc = sys.gettotalrefcount()
+ track = TrackRefs()
while True:
runner(files, test_filter, debug)
+ gc.collect()
+ if gc.garbage:
+ print "GARBAGE:", len(gc.garbage), gc.garbage
+ return
+ if REFCOUNT:
+ prev = rc
+ rc = sys.gettotalrefcount()
+ print "totalrefcount=%-8d change=%-6d" % (rc, rc - prev)
+ track.update()
else:
runner(files, test_filter, debug)
@@ -488,14 +631,19 @@
global LOOP
global GUI
global TRACE
+ global REFCOUNT
global debug
global debugger
global build
global level
global libdir
+ global timesfn
global timetests
global progress
global build_inplace
+ global keepStaleBytecode
+ global functional
+ global test_dir
if argv is None:
argv = sys.argv
@@ -506,6 +654,7 @@
LOOP = False
GUI = False
TRACE = False
+ REFCOUNT = False
debug = False # Don't collect test results; simply let tests crash
debugger = False
build = False
@@ -516,14 +665,19 @@
level = 1
libdir = None
progress = False
+ timesfn = None
timetests = 0
+ keepStaleBytecode = 0
+ functional = False
+ test_dir = None
try:
- opts, args = getopt.getopt(sys.argv[1:], "a:bBcdDgGhLmptTuv",
- ["all", "help", "libdir="])
+ opts, args = getopt.getopt(argv[1:], "a:bBcdDfg:G:hLmprtTuv",
+ ["all", "help", "libdir=", "times=",
+ "keepbytecode", "dir="])
except getopt.error, msg:
print msg
- print "Try `python %s -h' for more information." % sys.argv[0]
+ print "Try `python %s -h' for more information." % argv[0]
sys.exit(2)
for k, v in opts:
@@ -545,6 +699,8 @@
elif k == "-D":
debug = True
debugger = True
+ elif k == "-f":
+ functional = True
elif k in ("-h", "--help"):
print __doc__
sys.exit(0)
@@ -555,6 +711,8 @@
print "-G argument must be DEBUG_ flag, not", repr(v)
sys.exit(1)
gcflags.append(v)
+ elif k == '--keepbytecode':
+ keepStaleBytecode = 1
elif k == '--libdir':
libdir = v
elif k == "-L":
@@ -563,14 +721,28 @@
GUI = "minimal"
elif k == "-p":
progress = True
+ elif k == "-r":
+ if hasattr(sys, "gettotalrefcount"):
+ REFCOUNT = True
+ else:
+ print "-r ignored, because it needs a debug build of Python"
elif k == "-T":
TRACE = True
elif k == "-t":
- timetests = True
+ if not timetests:
+ timetests = 50
elif k == "-u":
GUI = 1
elif k == "-v":
VERBOSE += 1
+ elif k == "--times":
+ try:
+ timetests = int(v)
+ except ValueError:
+ # must be a filename to write
+ timesfn = v
+ elif k == '--dir':
+ test_dir = v
if gcthresh is not None:
if gcthresh == 0:
@@ -581,7 +753,6 @@
print "gc threshold:", gc.get_threshold()
if gcflags:
- import gc
val = 0
for flag in gcflags:
v = getattr(gc, flag, None)
@@ -612,10 +783,11 @@
sys.exit(1)
if VERBOSE:
+ kind = functional and "functional" or "unit"
if level == 0:
- print "Running tests at all levels"
+ print "Running %s tests at all levels" % kind
else:
- print "Running tests at level", level
+ print "Running %s tests at level %d" % (kind, level)
if args:
if len(args) > 1:
More information about the Zodb-checkins
mailing list