[Zope-Checkins] CVS: Zope3/utilities - license_check.py:1.2 unittestgui.py:1.2 ExtensionBuilder.py:1.5 FS.py:1.9 bbb.py:1.15 check_catalog.py:1.5 fixbbbts.py:1.3 load_site.py:1.10 requestprofiler.py:1.17 testrunner.py:1.26

Jim Fulton jim@zope.com
Mon, 10 Jun 2002 19:30:19 -0400


Update of /cvs-repository/Zope3/utilities
In directory cvs.zope.org:/tmp/cvs-serv20468/utilities

Modified Files:
	ExtensionBuilder.py FS.py bbb.py check_catalog.py fixbbbts.py 
	load_site.py requestprofiler.py testrunner.py 
Added Files:
	license_check.py unittestgui.py 
Log Message:
Merged Zope-3x-branch into newly forked Zope3 CVS Tree.

=== Zope3/utilities/license_check.py 1.1 => 1.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.
+# 
+##############################################################################
+"""
+
+$Id$ 
+"""
+
+# small script to check all Python files for a correct header,
+# specifically a presence of the correct license text from
+# $ZOPE_HOME/zpl.py
+
+usage = """\
+%s looks for the presence of the right license text in
+all Python files. It can be run in different modes.
+
+usage: %s -c|-w|--undo [-s, -v, --nobackup] path 
+
+-c : put a license text at the top of each file
+-w : print the names of files without a license text
+-s : strict checking. If this option is not set, only handle files
+     without any form of license text. If set, an exact license text
+     needs to be present
+-v : be verbose
+-h : Print this help
+
+-l=path : Give the absolute path to zpl.py
+
+--nobackup : Do not generate backup files
+--include_init : Process also the __init__.py files
+--undo : Rename the backup files to the original files, mainly for
+         testing.
+
+path : If not given start in the current directory. 
+
+Example: To put a license in all files
+license_check.py -c -v -s --nobackup --include_init 
+"""
+
+
+import os, fnmatch, sys
+import re
+
+class ZLicenseCheckError(Exception):
+    fname = ''
+    msg = ''
+    def __init__(self, msg, fname):
+        self.msg = msg
+        self.fname = fname
+        Exception.__init__(self, msg, fname)
+
+    def __str__(self):
+        return self.msg
+ 
+class GlobDirectoryWalker:
+    # a forward iterator that traverses a directory tree
+    # snippet posted by Frederik Lundh in c.l.p.
+    #
+    def __init__(self, directory, pattern="*"):
+        self.stack = [directory]
+        self.pattern = pattern
+        self.files = []
+        self.index = 0
+
+
+    def __getitem__(self, index):
+        while 1:
+            try:
+                file = self.files[self.index]
+                self.index = self.index + 1
+            except IndexError:
+                # pop next directory from stack
+                self.directory = self.stack.pop()
+                self.files = os.listdir(self.directory)
+                self.index = 0
+            else:
+                # got a filename
+                fullname = os.path.join(self.directory, file)
+                if os.path.isdir(fullname) and not os.path.islink(fullname):
+                    self.stack.append(fullname)
+                if fnmatch.fnmatch(file, self.pattern):
+                    return fullname
+
+class HeaderCheck:
+    """Make the header of a given file have a license text."""
+
+    def __init__(self, fname, zpl, verbose=0, backup=1):
+        """\
+        fname -> name of the checked file
+        zpl   -> instance of ZPL, class representing the license
+        """
+        self.fname = fname
+        self.header_length = 700
+        self.license = zpl.license_text
+        self.verbose = verbose
+        self.backup = backup
+    
+
+    def get_header(self):
+        """Get a number if lines of the file. The number is an
+        instance attribute"""
+        header = open(self.fname,'r').read(self.header_length)
+        return header
+        
+
+    def has_some_license(self):
+        """Search the file for some license text. If the text is
+        found, return 1"""
+        header = self.get_header()
+        if not re.search("ZPL", header, re.I):
+            return 0
+        else:
+            return 1
+
+
+    def has_license(self):
+        """Fast check for the exact license text in the header"""
+        header = self.get_header()
+        if header.find(self.license) == -1:
+            return 0
+        else:
+            return 1
+            
+
+    def include(self):
+        """Put a license text at the top of the lines. If the first line
+        starts with a bang-path start inserting at the second line"""
+        lines = open(self.fname,'r').readlines()
+        start = 0
+        if lines and re.match('#!', lines[0]):
+            start=1
+        lines.insert(start, self.license)
+        # keep the current stat_mod
+        fmode = os.stat(self.fname)[0]
+        # There can already be a backup file from the removal pass
+        if self.backup and not os.path.isfile(self.fname+'.no_license'):
+            os.rename(self.fname, self.fname+'.no_license')
+        open(self.fname, 'w').write(''.join(lines))
+        os.chmod(self.fname, fmode)
+        if self.verbose:
+            print 'License included: %s' % self.fname
+        return 
+ 
+
+    def change(self):
+        """Try to change the license text in the file, raise an exception
+        if not possible. 
+        """
+        if self.has_some_license():
+            # try to remove the old license
+            try:
+                self.remove()
+                self.include()
+            except ZLicenseCheckError:
+                open(self.fname+'.pathological','w')
+                raise ZLicenseCheckError('License could not be changed',
+                                         self.fname)
+
+        else:
+            self.include()
+        return
+
+
+    def remove(self):
+        lines = open(self.fname, 'r').readlines()
+        if not lines:
+            return
+        start = 0
+        save = []
+        if re.match('#!',lines[0]):
+            start = 1
+            save.extend(lines[0])
+        end=start
+        for line in lines[start:]:
+            if line[0] == '#' or line.isspace():
+                end += 1
+            else:
+                break
+
+        license = ''.join(lines[start:end])
+        # test if we really have the license
+        lookfor = 'copyright|Zope Public|license|All rights reserved'
+        if not re.search(lookfor, license, re.I):
+            raise ZLicenseCheckError('No clear license text', self.fname)
+        else:
+            save.extend(lines[end:])
+            # keep the current stat_mod
+            fmode = os.stat(self.fname)[0]
+            if self.backup:
+                os.rename(self.fname, self.fname+'.no_license')
+            if self.verbose:
+                print 'License removed: %s' % self.fname
+            open(self.fname,'w').write(''.join(save))
+            os.chmod(self.fname, fmode)
+        return
+        
+
+    def warn(self):
+        print 'File %s has no license text' % self.fname
+
+class Config:
+    """Container to keep configuration options"""
+    def __init__(self, **kws):
+        self.verbose = 0
+        self.warning = 0
+        self.strict = 0
+        self.backup = 1
+        self.undo = 0
+        self.include_init = 0
+        self.license_path='./'
+
+        if kws:
+            for key,value in kws:
+                setattr(self, key, value)
+
+        return
+
+
+class ZPL:    
+    def __init__(self, path='./'):
+        self.path = path
+        self.license_text = self.get_text()
+
+    def get_text(self):
+        zhome = os.environ.get('ZOPE_HOME', self.path)
+        try:
+            data = open(os.path.join(zhome,'zpl.py'),'r').read()
+        except IOError:
+            sys.exit('Could not open license file zpl.py')
+
+        license = data[:data.find('"""')]
+        return license
+
+
+class CheckerApp:
+    def __init__(self, config):
+        self.conf = config
+        self.zpl = ZPL()
+        self.pathological = []
+        
+        # Which test (uses unbound methods)
+        if self.conf.strict:
+            self.condition = HeaderCheck.has_license
+        else:
+            self.condition = HeaderCheck.has_some_license
+
+        # Wich action
+        if self.conf.warning:
+            self.action = HeaderCheck.warn
+        else:
+            if self.conf.strict:
+                self.action = HeaderCheck.change
+            else:
+                self.action = HeaderCheck.include
+            
+    def run(self):
+        if self.conf.undo:
+            for fname in GlobDirectoryWalker(self.conf.path,"*.no_license"):
+                old_name = os.path.splitext(fname)[0]
+                os.rename(fname, old_name)
+        else:
+            for fname in GlobDirectoryWalker(".", "*.py"):
+                if (os.path.split(fname)[-1] == '__init__.py') and \
+                        not self.conf.include_init:
+                    if self.conf.verbose:
+                        print 'Skipping: %s' % fname
+                    continue
+
+                hc = HeaderCheck(fname, self.zpl, 
+                                 verbose=self.conf.verbose,
+                                 backup=self.conf.backup)
+
+                # unbound methods need an instance
+                if not self.condition(hc):
+                    try:
+                        self.action(hc)
+                    except ZLicenseCheckError, error:
+                        print error, '(%s)' %error.fname
+                        self.pathological.append(fname)
+
+        if self.conf.verbose:
+            for fname in self.pathological:
+                print 'Could not be changed: %s' % fname
+            print 'Number of pathological files: %s' % \
+                   len(self.pathological) 
+
+
+def print_usage(msg=0):
+    print usage % (sys.argv[0], sys.argv[0])
+    if msg:
+        print msg
+        msg=1
+    sys.exit(msg)
+
+    
+def main():
+    import getopt
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "vwcshl:",
+        ["nobackup","undo","include_init"])
+    except getopt.GetoptError, error:
+        print_usage(str(error).capitalize())
+
+    if not opts:
+        print_usage('Need at least optin -w OR -c OR --undo')
+
+    if (('-w','') in opts) and (('-c','') in opts):
+        print_usage('Only option -w OR -c can be used')
+        
+    conf=Config()
+    for o,a in opts:
+        if o == '-v' : conf.verbose = 1
+        elif o == '-c': conf.change = 1
+        elif o == '-w': conf.warning = 1
+        elif o == '-s': conf.strict = 1
+        elif o == '-h': print_usage()
+        elif o == '--include_init': conf.include_init = 1
+        elif o == '--nobackup': conf.backup = 0
+        elif o == '--undo': conf.undo = 1
+        elif o == '-l':
+            if not a:
+                print_usage('Need to get a path for option -l')
+            conf.license_path = a
+
+    if not args:
+        conf.path = os.getcwd()
+    else:
+        conf.path = args[0]
+
+    # test presence of working directory
+    if not os.path.isdir(conf.path):
+        print_usage('Can not find directory %s' % conf.path)
+        
+    checker = CheckerApp(conf)
+    checker.run()
+    
+    
+if __name__ == '__main__':   
+    main()


=== Zope3/utilities/unittestgui.py 1.1 => 1.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.
+# 
+##############################################################################
+"""
+GUI framework and application for use with Python unit testing framework.
+Execute tests written using the framework provided by the 'unittest' module.
+
+Further information is available in the bundled documentation, and from
+
+  http://pyunit.sourceforge.net/
+
+Copyright (c) 1999, 2000, 2001 Steve Purcell
+This module is free software, and you may redistribute it and/or modify
+it under the same terms as Python itself, so long as this copyright message
+and disclaimer are retained in their original form.
+
+IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
+SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF
+THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
+THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
+AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
+SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+"""
+
+__author__ = "Steve Purcell (stephen_purcell@yahoo.com)"
+__version__ = "$Revision$"[11:-2]
+
+import linecache
+import unittest
+import ScrolledText
+import sys
+import Tkinter
+import tkMessageBox
+import traceback
+
+import string
+import re
+tk = Tkinter # Alternative to the messy 'from Tkinter import *' often seen
+
+
+##############################################################################
+# GUI framework classes
+##############################################################################
+
+class BaseGUITestRunner:
+    """Subclass this class to create a GUI TestRunner that uses a specific
+    windowing toolkit. The class takes care of running tests in the correct
+    manner, and making callbacks to the derived class to obtain information
+    or signal that events have occurred.
+    """
+    def __init__(self, *args, **kwargs):
+        self.currentResult = None
+        self.running = 0
+        self.__rollbackImporter = None
+        apply(self.initGUI, args, kwargs)
+
+    def getSelectedTestName(self):
+        "Override to return the name of the test selected to be run"
+        pass
+
+    def errorDialog(self, title, message):
+        "Override to display an error arising from GUI usage"
+        pass
+
+    def runClicked( self
+                  , COMMA_SPACE=re.compile( '[, ]+' )
+                  ):
+        "To be called in response to user choosing to run a test"
+        if self.running: return
+        testName = self.getSelectedTestName()
+        if not testName:
+            self.errorDialog("Test name entry", "You must enter a test name")
+            return
+        if self.__rollbackImporter:
+            self.__rollbackImporter.rollbackImports()
+        self.__rollbackImporter = RollbackImporter()
+        try:
+            test = unittest.defaultTestLoader.loadTestsFromNames(
+                                COMMA_SPACE.split( testName ) )
+            warnings = getattr( test, 'warnings', [] )
+            for sub_test in test._tests:
+                warnings.extend( getattr( sub_test, 'warnings', [] ) )
+            test.warnings = warnings
+        except:
+            exc_type, exc_value, exc_tb = sys.exc_info()
+            apply(traceback.print_exception,sys.exc_info())
+            self.errorDialog("Unable to run test '%s'" % testName,
+                             "Error loading specified test: %s, %s" % \
+                             (exc_type, exc_value))
+            return
+        self.currentResult = GUITestResult(self)
+        self.totalTests = test.countTestCases()
+        self.running = 1
+        self.notifyRunning()
+        warnings = getattr( test, 'warnings', () )
+        for warning in warnings:
+            self.notifyWarning( warning[0], warning[1] )
+        test.run(self.currentResult)
+        self.running = 0
+        self.notifyStopped()
+
+    def stopClicked(self):
+        "To be called in response to user stopping the running of a test"
+        if self.currentResult:
+            self.currentResult.stop()
+
+    # Required callbacks
+
+    def notifyRunning(self):
+        "Override to set GUI in 'running' mode, enabling 'stop' button etc."
+        pass
+
+    def notifyStopped(self):
+        "Override to set GUI in 'stopped' mode, enabling 'run' button etc."
+        pass
+
+    def notifyWarning(self, msg, tb_str):
+        "Override to log a warning message (e.g., couldn't import a test)."
+
+    def notifyTestFailed(self, test, err):
+        "Override to indicate that a test has just failed"
+        pass
+
+    def notifyTestErrored(self, test, err):
+        "Override to indicate that a test has just errored"
+        pass
+
+    def notifyTestStarted(self, test):
+        "Override to indicate that a test is about to run"
+        pass
+
+    def notifyTestFinished(self, test):
+        """Override to indicate that a test has finished (it may already have
+           failed or errored)"""
+        pass
+
+
+class GUITestResult(unittest.TestResult):
+    """A TestResult that makes callbacks to its associated GUI TestRunner.
+    Used by BaseGUITestRunner. Need not be created directly.
+    """
+    def __init__(self, callback):
+        unittest.TestResult.__init__(self)
+        self.callback = callback
+
+    def addError(self, test, err):
+        unittest.TestResult.addError(self, test, err)
+        self.callback.notifyTestErrored(test, err)
+
+    def addFailure(self, test, err):
+        unittest.TestResult.addFailure(self, test, err)
+        self.callback.notifyTestFailed(test, err)
+
+    def stopTest(self, test):
+        unittest.TestResult.stopTest(self, test)
+        self.callback.notifyTestFinished(test)
+
+    def startTest(self, test):
+        unittest.TestResult.startTest(self, test)
+        self.callback.notifyTestStarted(test)
+
+
+class RollbackImporter:
+    """This tricky little class is used to make sure that modules under test
+    will be reloaded the next time they are imported.
+    """
+    def __init__(self):
+        self.previousModules = sys.modules.copy()
+        
+    def rollbackImports(self):
+        for modname in sys.modules.keys():
+            if not self.previousModules.has_key(modname):
+                # Force reload when modname next imported
+                del(sys.modules[modname])
+        linecache.checkcache()
+
+
+##############################################################################
+# Tkinter GUI
+##############################################################################
+
+_ABOUT_TEXT="""\
+PyUnit unit testing framework.
+
+For more information, visit
+http://pyunit.sourceforge.net/
+
+Copyright (c) 2000 Steve Purcell
+<stephen_purcell@yahoo.com>
+"""
+_HELP_TEXT="""\
+Enter the name of a callable object which, when called, will return a \
+TestCase or TestSuite. Click 'start', and the test thus produced will be run.
+
+Double click on an error in the listbox to see more information about it,\
+including the stack trace.
+
+For more information, visit
+http://pyunit.sourceforge.net/
+or see the bundled documentation
+"""
+
+class TkTestRunner(BaseGUITestRunner):
+    """An implementation of BaseGUITestRunner using Tkinter.
+    """
+    def initGUI(self, root, initialTestName):
+        """Set up the GUI inside the given root window. The test name entry
+        field will be pre-filled with the given initialTestName.
+        """
+        self.root = root
+        # Set up values that will be tied to widgets
+        self.suiteNameVar = tk.StringVar()
+        self.suiteNameVar.set(initialTestName)
+        self.statusVar = tk.StringVar()
+        self.statusVar.set("Idle")
+        self.runCountVar = tk.IntVar()
+        self.failCountVar = tk.IntVar()
+        self.errorCountVar = tk.IntVar()
+        self.remainingCountVar = tk.IntVar()
+        self.top = tk.Frame()
+        self.top.pack(fill=tk.BOTH, expand=1)
+        self.createWidgets()
+
+    def createWidgets(self):
+        """Creates and packs the various widgets.
+        
+        Why is it that GUI code always ends up looking a mess, despite all the
+        best intentions to keep it tidy? Answers on a postcard, please.
+        """
+        # Status bar
+        statusFrame = tk.Frame(self.top, relief=tk.SUNKEN, borderwidth=2)
+        statusFrame.pack(anchor=tk.SW, fill=tk.X, side=tk.BOTTOM)
+        tk.Label(statusFrame, textvariable=self.statusVar).pack(side=tk.LEFT)
+
+        # Area to enter name of test to run
+        leftFrame = tk.Frame(self.top, borderwidth=3)
+        leftFrame.pack(fill=tk.BOTH, side=tk.LEFT, anchor=tk.NW, expand=1)
+        suiteNameFrame = tk.Frame(leftFrame, borderwidth=3)
+        suiteNameFrame.pack(fill=tk.X)
+        tk.Label(suiteNameFrame, text="Enter test name:").pack(side=tk.LEFT)
+        e = tk.Entry(suiteNameFrame, textvariable=self.suiteNameVar, width=80)
+        e.pack(side=tk.LEFT, fill=tk.X, expand=1)
+        e.focus_set()
+        e.bind('<Key-Return>', lambda e, self=self: self.runClicked())
+
+        # Progress bar
+        progressFrame = tk.Frame(leftFrame, relief=tk.GROOVE, borderwidth=2)
+        progressFrame.pack(fill=tk.X, expand=0, anchor=tk.NW)
+        tk.Label(progressFrame, text="Progress:").pack(anchor=tk.W)
+        self.progressBar = ProgressBar(progressFrame, relief=tk.SUNKEN,
+                                       borderwidth=2)
+        self.progressBar.pack(fill=tk.X, expand=1)
+
+        # Area with buttons to start/stop tests and quit
+        buttonFrame = tk.Frame(self.top, borderwidth=3)
+        buttonFrame.pack(side=tk.LEFT, anchor=tk.NW, fill=tk.Y)
+        self.stopGoButton = tk.Button(buttonFrame, text="Start",
+                                      command=self.runClicked)
+        self.stopGoButton.pack(fill=tk.X)
+        tk.Button(buttonFrame, text="Close",
+                  command=self.top.quit).pack(side=tk.BOTTOM, fill=tk.X)
+        tk.Button(buttonFrame, text="About",
+                  command=self.showAboutDialog).pack(side=tk.BOTTOM, fill=tk.X)
+        tk.Button(buttonFrame, text="Help",
+                  command=self.showHelpDialog).pack(side=tk.BOTTOM, fill=tk.X)
+
+        # Area with labels reporting results
+        for label, var in (('Run:', self.runCountVar),
+                           ('Failures:', self.failCountVar),
+                           ('Errors:', self.errorCountVar),
+                           ('Remaining:', self.remainingCountVar)):
+            tk.Label(progressFrame, text=label).pack(side=tk.LEFT)
+            tk.Label(progressFrame, textvariable=var,
+                     foreground="blue").pack(side=tk.LEFT, fill=tk.X,
+                                             expand=1, anchor=tk.W)
+
+        # List box showing errors and failures
+        tk.Label(leftFrame, text="Failures and errors:").pack(anchor=tk.W)
+        listFrame = tk.Frame(leftFrame, relief=tk.SUNKEN, borderwidth=2)
+        listFrame.pack(fill=tk.BOTH, anchor=tk.NW, expand=1)
+        self.errorListbox = tk.Listbox(listFrame, foreground='red',
+                                       selectmode=tk.SINGLE,
+                                       selectborderwidth=0)
+        self.errorListbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=1,
+                               anchor=tk.NW)
+        listScroll = tk.Scrollbar(listFrame, command=self.errorListbox.yview)
+        listScroll.pack(side=tk.LEFT, fill=tk.Y, anchor=tk.N)
+        self.errorListbox.bind("<Double-1>",
+                               lambda e, self=self: self.showSelectedError())
+        self.errorListbox.configure(yscrollcommand=listScroll.set)
+
+        # List box showing warnings
+        tk.Label(leftFrame, text="Warnings:").pack(anchor=tk.W)
+        warnFrame = tk.Frame(leftFrame, relief=tk.SUNKEN, borderwidth=2)
+        warnFrame.pack(fill=tk.BOTH, anchor=tk.NW, expand=1)
+        self.warningListbox = tk.Listbox(warnFrame, foreground='black',
+                                       selectmode=tk.SINGLE,
+                                       selectborderwidth=0)
+        self.warningListbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=1,
+                               anchor=tk.NW)
+        wListScroll = tk.Scrollbar(warnFrame, command=self.warningListbox.yview)
+        wListScroll.pack(side=tk.LEFT, fill=tk.Y, anchor=tk.N)
+        self.warningListbox.bind("<Double-1>",
+                               lambda e, self=self: self.showSelectedWarning())
+        self.warningListbox.configure(yscrollcommand=wListScroll.set)
+
+
+    def getSelectedTestName(self):
+        return self.suiteNameVar.get()
+
+    def errorDialog(self, title, message):
+        tkMessageBox.showerror(parent=self.root, title=title,
+                               message=message)
+
+    def notifyRunning(self):
+        self.runCountVar.set(0)
+        self.failCountVar.set(0)
+        self.errorCountVar.set(0)
+        self.remainingCountVar.set(self.totalTests)
+        self.errorInfo = []
+        self.warnInfo = []
+        while self.errorListbox.size():
+            self.errorListbox.delete(0)
+        while self.warningListbox.size():
+            self.warningListbox.delete(0)
+        #Stopping seems not to work, so simply disable the start button
+        #self.stopGoButton.config(command=self.stopClicked, text="Stop")
+        self.stopGoButton.config(state=tk.DISABLED)
+        self.progressBar.setProgressFraction(0.0)
+        self.top.update_idletasks()
+
+    def notifyStopped(self):
+        self.stopGoButton.config(state=tk.ACTIVE)
+        #self.stopGoButton.config(command=self.runClicked, text="Start")
+        self.statusVar.set("Idle")
+
+    def notifyTestStarted(self, test):
+        self.statusVar.set(str(test))
+        self.top.update_idletasks()
+
+    def notifyWarning(self, msg, tb_str):
+        self.warningListbox.insert(tk.END, "Warning: %s" % msg)
+        self.warnInfo.append((msg,tb_str))
+        
+    def notifyTestFailed(self, test, err):
+        self.failCountVar.set(1 + self.failCountVar.get())
+        self.errorListbox.insert(tk.END, "Failure: %s" % test)
+        self.errorInfo.append((test,err))
+
+    def notifyTestErrored(self, test, err):
+        self.errorCountVar.set(1 + self.errorCountVar.get())
+        self.errorListbox.insert(tk.END, "Error: %s" % test)
+        self.errorInfo.append((test,err))
+
+    def notifyTestFinished(self, test):
+        self.remainingCountVar.set(self.remainingCountVar.get() - 1)
+        self.runCountVar.set(1 + self.runCountVar.get())
+        fractionDone = float(self.runCountVar.get())/float(self.totalTests)
+        fillColor = len(self.errorInfo) and "red" or "green"
+        self.progressBar.setProgressFraction(fractionDone, fillColor)
+
+    def showAboutDialog(self):
+        tkMessageBox.showinfo(parent=self.root, title="About PyUnit",
+                              message=_ABOUT_TEXT)
+
+    def showHelpDialog(self):
+        tkMessageBox.showinfo(parent=self.root, title="PyUnit help",
+                              message=_HELP_TEXT)
+
+    def showSelectedError(self):
+        selection = self.errorListbox.curselection()
+        if not selection: return
+        selected = int(selection[0])
+        txt = self.errorListbox.get(selected)
+        window = tk.Toplevel(self.root)
+        window.title(txt)
+        window.protocol('WM_DELETE_WINDOW', window.quit)
+        test, error = self.errorInfo[selected]
+        tk.Label(window, text=str(test),
+                 foreground="red", justify=tk.LEFT).pack(anchor=tk.W)
+        tracebackLines = apply(traceback.format_exception, error + (10,))
+        tracebackText = string.join(tracebackLines,'')
+        text = ScrolledText.ScrolledText(window)
+        text.tag_config('sel', relief=tk.FLAT)
+        text.insert(tk.END, tracebackText)
+        if len(tracebackLines) < 20:
+            text.config(height=len(tracebackLines) + 5)
+        text.yview_pickplace(tk.END)
+        text['state'] = tk.DISABLED
+        text.pack(expand=1, fill=tk.BOTH)
+        b = tk.Button(window, text="Close",
+                      command=window.quit)
+        b.pack(side=tk.BOTTOM)
+        b.focus_set()
+        window.bind('<Key-Return>', lambda e, w=window: w.quit())
+        window.mainloop()
+        window.destroy()
+
+    def showSelectedWarning(self):
+        selection = self.warningListbox.curselection()
+        if not selection: return
+        selected = int(selection[0])
+        txt = self.warningListbox.get(selected)
+        window = tk.Toplevel(self.root)
+        window.title(txt)
+        window.protocol('WM_DELETE_WINDOW', window.quit)
+        test, error = self.warnInfo[selected]
+        tk.Label(window, text=str(test),
+                 foreground="black", justify=tk.LEFT).pack(anchor=tk.W)
+        tk.Label(window, text=error, justify=tk.LEFT).pack()
+        tk.Button(window, text="Close",
+                  command=window.quit).pack(side=tk.BOTTOM)
+        window.bind('<Key-Return>', lambda e, w=window: w.quit())
+        window.mainloop()
+        window.destroy()
+
+
+class ProgressBar(tk.Frame):
+    """A simple progress bar that shows a percentage progress in
+    the given colour."""
+
+    def __init__(self, *args, **kwargs):
+        apply(tk.Frame.__init__, (self,) + args, kwargs)
+        self.canvas = tk.Canvas(self, height='20', width='60',
+                                background='white', borderwidth=3)
+        self.canvas.pack(fill=tk.X, expand=1)
+        self.rect = self.text = None
+        self.canvas.bind('<Configure>', self.paint)
+        self.setProgressFraction(0.0)
+
+    def setProgressFraction(self, fraction, color='blue'):
+        self.fraction = fraction
+        self.color = color
+        self.paint()
+        self.canvas.update_idletasks()
+        
+    def paint(self, *args):
+        totalWidth = self.canvas.winfo_width()
+        width = int(self.fraction * float(totalWidth))
+        height = self.canvas.winfo_height()
+        if self.rect is not None: self.canvas.delete(self.rect)
+        if self.text is not None: self.canvas.delete(self.text)
+        self.rect = self.canvas.create_rectangle(0, 0, width, height,
+                                                 fill=self.color)
+        percentString = "%3.0f%%" % (100.0 * self.fraction)
+        self.text = self.canvas.create_text(totalWidth/2, height/2,
+                                            anchor=tk.CENTER,
+                                            text=percentString)
+
+def main(initialTestName=""):
+    root = tk.Tk()
+    root.title("PyUnit")
+    runner = TkTestRunner(root, initialTestName)
+    root.protocol('WM_DELETE_WINDOW', root.quit)
+    root.mainloop()
+
+
+if __name__ == '__main__':
+    import sys
+    if len(sys.argv) == 2:
+        main(sys.argv[1])
+    else:
+        main()


=== Zope3/utilities/ExtensionBuilder.py 1.4 => 1.5 ===
+#
+# 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.
+# 
+##############################################################################
 """Build Python extension modules from Setup files. Particularly
    designed for building extensions for Zope in a way that works
    the same way for *nix and win32. Note that for building Zope
@@ -437,7 +450,7 @@
 
 
 win32_def="""EXPORTS
-	init%(module)s
+        init%(module)s
 """
 
 win32_mak="""# Microsoft Developer Studio Generated NMAKE File, Format Version 4.00
@@ -497,11 +510,11 @@
 ALL : "$(OUTDIR)\\%(module)s.dll"
 
 CLEAN : 
-	-@erase "$(OUTDIR)\\%(module)s.dll"
-	-@erase "$(OUTDIR)\\%(module)s.obj"%(other_clean_release)s
-	-@erase "$(OUTDIR)\\%(module)s.lib"
-	-@erase "$(OUTDIR)\\%(module)s.exp"
-	-@erase "$(OUTDIR)\\%(module)s.pch"
+        -@erase "$(OUTDIR)\\%(module)s.dll"
+        -@erase "$(OUTDIR)\\%(module)s.obj"%(other_clean_release)s
+        -@erase "$(OUTDIR)\\%(module)s.lib"
+        -@erase "$(OUTDIR)\\%(module)s.exp"
+        -@erase "$(OUTDIR)\\%(module)s.pch"
 
 "$(OUTDIR)" :
     if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
@@ -531,10 +544,10 @@
  /pdb:"$(OUTDIR)\\%(module)s.pdb" /machine:I386 /def:".\\%(module)s.def"\\
  /out:"$(OUTDIR)\\%(module)s.dll" /implib:"$(OUTDIR)\\%(module)s.lib" %(libdirs)s
 DEF_FILE= \\
-	".\\%(module)s.def"
+        ".\\%(module)s.def"
 LINK32_OBJS= \\
-	"$(INTDIR)\\%(module)s.obj" \\%(other_link)s
-	"%(pyhome)s\\libs\\%(pythonlib)s"
+        "$(INTDIR)\\%(module)s.obj" \\%(other_link)s
+        "%(pyhome)s\\libs\\%(pythonlib)s"
 
 "$(OUTDIR)\\%(module)s.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
     $(LINK32) @<<
@@ -559,15 +572,15 @@
 ALL : "$(OUTDIR)\\%(module)s.dll"
 
 CLEAN : 
-	-@erase "$(OUTDIR)\\%(module)s.dll"
-	-@erase "$(OUTDIR)\\%(module)s.obj"%(other_clean_debug)s
-	-@erase "$(OUTDIR)\\%(module)s.ilk"
-	-@erase "$(OUTDIR)\\%(module)s.lib"
-	-@erase "$(OUTDIR)\\%(module)s.exp"
-	-@erase "$(OUTDIR)\\%(module)s.pdb"
-	-@erase "$(OUTDIR)\\%(module)s.pch"
-	-@erase "$(OUTDIR)\\pcbuild.pdb"
-	-@erase "$(OUTDIR)\\pcbuild.idb"
+        -@erase "$(OUTDIR)\\%(module)s.dll"
+        -@erase "$(OUTDIR)\\%(module)s.obj"%(other_clean_debug)s
+        -@erase "$(OUTDIR)\\%(module)s.ilk"
+        -@erase "$(OUTDIR)\\%(module)s.lib"
+        -@erase "$(OUTDIR)\\%(module)s.exp"
+        -@erase "$(OUTDIR)\\%(module)s.pdb"
+        -@erase "$(OUTDIR)\\%(module)s.pch"
+        -@erase "$(OUTDIR)\\pcbuild.pdb"
+        -@erase "$(OUTDIR)\\pcbuild.idb"
 
 "$(OUTDIR)" :
     if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"
@@ -598,10 +611,10 @@
  /pdb:"$(OUTDIR)\\%(module)s.pdb" /debug /machine:I386 /def:".\\%(module)s.def"\\
  /out:"$(OUTDIR)\\%(module)s.dll" /implib:"$(OUTDIR)\\%(module)s.lib" %(libdirs)s
 DEF_FILE= \\
-	".\\%(module)s.def"
+        ".\\%(module)s.def"
 LINK32_OBJS= \\
-	"$(INTDIR)\\%(module)s.obj" \\%(other_link)s
-	"%(pyhome)s\\libs\\%(pythonlib)s"
+        "$(INTDIR)\\%(module)s.obj" \\%(other_link)s
+        "%(pyhome)s\\libs\\%(pythonlib)s"
 
 "$(OUTDIR)\\%(module)s.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS)
     $(LINK32) @<<


=== Zope3/utilities/FS.py 1.8 => 1.9 ===
 #
-# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 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
+# FOR A PARTICULAR PURPOSE.
 # 
 ##############################################################################
-
 import struct, tempfile, string, time, pickle, os, sys
 from struct import pack, unpack
 from cStringIO import StringIO


=== Zope3/utilities/bbb.py 1.14 => 1.15 ===
 #
-# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 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
+# FOR A PARTICULAR PURPOSE.
 # 
 ##############################################################################
 """Read and (re-)format BoboPOS 2 database files


=== Zope3/utilities/check_catalog.py 1.4 => 1.5 ===
-
 ##############################################################################
 #
-# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 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
+# FOR A PARTICULAR PURPOSE.
 # 
 ##############################################################################
-
 """ script to consistency of a ZCatalog """
 
 __version__='$Revision$'[11:-2]


=== Zope3/utilities/fixbbbts.py 1.2 => 1.3 ===
+#
+# 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.
+# 
+##############################################################################
 import sys
 
 __doc__="""Fix BoboPOS time stamps


=== Zope3/utilities/load_site.py 1.9 => 1.10 ===
 #
-# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 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
+# FOR A PARTICULAR PURPOSE.
 # 
 ##############################################################################
 """Load a Zope site from a collection of files or directories


=== Zope3/utilities/requestprofiler.py 1.16 => 1.17 === (479/579 lines abridged)
-
+#!/usr/bin/env python2.1
 ##############################################################################
 #
-# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+# 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
+# FOR A PARTICULAR PURPOSE.
 # 
 ##############################################################################
-
 """ Request log profiler script """
 
 __version__='$Revision$'[11:-2]
 
-import string, sys, time, getopt, tempfile, math, cPickle
-try: import gzip
-except: pass
+import string, sys, time, getopt, tempfile
 
 class ProfileException(Exception): pass
 
@@ -62,13 +59,6 @@
             return time.strftime('%Y-%m-%dT%H:%M:%S', t)
         else:
             return "NA"
-
-    def shortprettystart(self):
-        if self.start is not None:
-            t = time.localtime(self.start)
-            return time.strftime('%H:%M:%S', t)
-        else:
-            return "NA"
         
     def win(self):
         if self.t_recdinput is not None and self.start is not None:
@@ -263,68 +253,45 @@
             file.seek(file.tell() - linelen)
 
     return retn
-
-def get_requests(files, start=None, end=None, statsfname=None,
-                 writestats=None, readstats=None):

[-=- -=- -=- 479 lines omitted -=- -=- -=-]

-                now = time.localtime(time.time() - int(val)*3600*24 )
+                now = time.gmtime(time.time())
                 # for testing - now = (2001, 04, 19, 0, 0, 0, 0, 0, -1)
                 start = list(now)
                 start[3] = start[4] = start[5] = 0
@@ -782,11 +615,7 @@
                 mode='cumulative'
             if opt=='--timed':
                 mode='timed'
-            if opt=='--urlfocus':
-                mode='urlfocus'
-                urlfocusurl = val
-            if opt=='--urlfocustime':
-                urlfocustime=int(val)
+
 
         validcumsorts = ['url', 'hits', 'hangs', 'max', 'min', 'median',
                          'mean', 'total']
@@ -810,16 +639,13 @@
                 sortf = codesort
             else:
                 sortf = Sort(sortby)
+
         elif mode=='timed':
-            sortf = None
-        elif mode=='urlfocus':
-            sortf = Sort('start', ascending=1)
+            sortf = timesort
         else:
             raise 'Invalid mode'
-
-        req=get_requests(files, start, end, statsfname, writestats, readstats)
-        analyze(req, top, sortf, start, end, mode, resolution, urlfocusurl,
-                urlfocustime)
+        
+        analyze(files, top, sortf, start, end, mode, resolution)
 
     except AssertionError, val:
         a = "%s is not a valid %s sort spec, use one of %s"
@@ -838,3 +664,10 @@
         traceback.print_exc()
         print usage()
         sys.exit(0)
+
+
+
+
+
+
+


=== Zope3/utilities/testrunner.py 1.25 => 1.26 ===
+#
+# 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.
+# 
+##############################################################################
 """testrunner - a Zope test suite utility.
 
 The testrunner utility is used to execute PyUnit test suites. You can find
@@ -53,20 +66,10 @@
         name, ext=os.path.splitext(filename)
         file, pathname, desc=imp.find_module(name, [path])
         saved_syspath = sys.path[:]
-        module = None
         try:
-            sys.path.append(path)       # let module find things in its dir
-	    try:
-            	module=imp.load_module(name, file, pathname, desc)
-	    except:
-	    	(tb_t, tb_v, tb_tb) = sys.exc_info()
-		self.report("Module %s failed to load\n%s: %s" % (pathname,
-			tb_t, tb_v))
-		self.report(string.join(traceback.format_tb(tb_tb)) + '\n')
-		del tb_tb
+            module=imp.load_module(name, file, pathname, desc)
         finally:
             file.close()
-            sys.path.pop()              # Remove module level path
             sys.path[:] = saved_syspath
         function=getattr(module, 'test_suite', None)
         if function is None:
@@ -90,7 +93,7 @@
         return 0
 
     def runSuite(self, suite):
-        runner=unittest.TextTestRunner(stream=sys.stderr, verbosity=self.verbosity)
+        runner=unittest.TextTestRunner(verbosity=self.verbosity)
         self.results.append(runner.run(suite))
 
     def report(self, message):
@@ -192,15 +195,6 @@
             sys.stderr.write( '*** Restoring directory to: %s\n' % working_dir )
         os.chdir(working_dir)
 
-def remove_stale_bytecode(arg, dirname, names):
-    names = map(os.path.normcase, names)
-    for name in names:
-        if name.endswith(".pyc") or name.endswith(".pyo"):
-            srcname = name[:-1]
-            if srcname not in names:
-                fullname = os.path.join(dirname, name)
-                print "Removing stale bytecode file", fullname
-                os.unlink(fullname)
 
 def main(args):
 
@@ -266,11 +260,6 @@
           whether it succeeded.  Running with -q is the same as
           running with -v1.
 
-       -o filename
-
-          Output test results to the specified file rather than
-          to stderr.
-
        -h
 
           Display usage information.
@@ -283,7 +272,7 @@
     mega_suite = 1
     set_python_path = 1
 
-    options, arg=getopt.getopt(args, 'amPhd:f:v:qMo:')
+    options, arg=getopt.getopt(args, 'amPhd:f:v:qM')
     if not options:
         err_exit(usage_msg)
     for name, value in options:
@@ -308,14 +297,9 @@
             verbosity = int(value)
         elif name == 'q':
             verbosity = 1
-        elif name == 'o':
-            f = open(value,'w')
-            sys.stderr = f
         else:
             err_exit(usage_msg)
 
-    os.path.walk(os.curdir, remove_stale_bytecode, None)
-
     testrunner = TestRunner( os.getcwd()
                            , verbosity=verbosity
                            , mega_suite=mega_suite)
@@ -329,13 +313,6 @@
             testrunner.report( "Adding %s to sys.path." % sw_home )
         sys.path.insert( 0, sw_home )
         os.environ['SOFTWARE_HOME'] = sw_home
-
-    try:
-        # Try to set up the testing environment (esp. INSTANCE_HOME,
-        # so we use the right custom_zodb.py.)
-        import Testing
-    except ImportError:
-        pass
 
     if test_all:
         testrunner.runAllTests()