[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