--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/reportlab/lib/testutils.py Mon Sep 15 11:16:32 2008 +0000
@@ -0,0 +1,318 @@
+#Copyright ReportLab Europe Ltd. 2000-2008
+#see license.txt for license details
+"""Utilities for testing Python packages.
+"""
+__version__='''$Id$'''
+import sys, os, string, fnmatch, copy, re
+from ConfigParser import ConfigParser
+import unittest
+
+# Helper functions.
+def isWritable(D):
+ try:
+ fn = '00DELETE.ME'
+ f = open(fn, 'w')
+ f.write('test of writability - can be deleted')
+ f.close()
+ if os.path.isfile(fn):
+ os.remove(fn)
+ return 1
+ except:
+ return 0
+
+_OUTDIR = None
+def setOutDir(name):
+ """Is it a writable file system distro being invoked within
+ test directory? If so, can write test output here. If not,
+ it had better go in a temp directory. Only do this once per
+ process"""
+ global _OUTDIR
+ if _OUTDIR: return _OUTDIR
+ D = [d[9:] for d in sys.argv if d.startswith('--outdir=')]
+ if D:
+ _OUTDIR = D[-1]
+ try:
+ os.makedirs(_OUTDIR)
+ except:
+ pass
+ map(sys.argv.remove,D)
+ else:
+ assert name=='__main__',"setOutDir should only be called in the main script"
+ scriptDir=os.path.dirname(sys.argv[0])
+ if not scriptDir: scriptDir=os.getcwd()
+ _OUTDIR = scriptDir
+
+ if not isWritable(_OUTDIR):
+ _OUTDIR = get_rl_tempdir('reportlab_test')
+ return _OUTDIR
+
+def outputfile(fn):
+ """This works out where to write test output. If running
+ code in a locked down file system, this will be a
+ temp directory; otherwise, the output of 'test_foo.py' will
+ normally be a file called 'test_foo.pdf', next door.
+ """
+ D = setOutDir(__name__)
+ if fn: D = os.path.join(D,fn)
+ return D
+
+def printLocation(depth=1):
+ if sys._getframe(depth).f_locals.get('__name__')=='__main__':
+ outDir = outputfile('')
+ if outDir!=_OUTDIR:
+ print 'Logs and output files written to folder "%s"' % outDir
+
+def makeSuiteForClasses(*classes):
+ "Return a test suite with tests loaded from provided classes."
+
+ suite = unittest.TestSuite()
+ loader = unittest.TestLoader()
+ for C in classes:
+ suite.addTest(loader.loadTestsFromTestCase(C))
+ return suite
+
+def getCVSEntries(folder, files=1, folders=0):
+ """Returns a list of filenames as listed in the CVS/Entries file.
+
+ 'folder' is the folder that should contain the CVS subfolder.
+ If there is no such subfolder an empty list is returned.
+ 'files' is a boolean; 1 and 0 means to return files or not.
+ 'folders' is a boolean; 1 and 0 means to return folders or not.
+ """
+
+ join = os.path.join
+ split = string.split
+
+ # If CVS subfolder doesn't exist return empty list.
+ try:
+ f = open(join(folder, 'CVS', 'Entries'))
+ except IOError:
+ return []
+
+ # Return names of files and/or folders in CVS/Entries files.
+ allEntries = []
+ for line in f.readlines():
+ if folders and line[0] == 'D' \
+ or files and line[0] != 'D':
+ entry = split(line, '/')[1]
+ if entry:
+ allEntries.append(join(folder, entry))
+
+ return allEntries
+
+
+# Still experimental class extending ConfigParser's behaviour.
+class ExtConfigParser(ConfigParser):
+ "A slightly extended version to return lists of strings."
+
+ pat = re.compile('\s*\[.*\]\s*')
+
+ def getstringlist(self, section, option):
+ "Coerce option to a list of strings or return unchanged if that fails."
+
+ value = apply(ConfigParser.get, (self, section, option))
+
+ # This seems to allow for newlines inside values
+ # of the config file, but be careful!!
+ val = string.replace(value, '\n', '')
+
+ if self.pat.match(val):
+ return eval(val)
+ else:
+ return value
+
+
+# This class as suggested by /F with an additional hook
+# to be able to filter filenames.
+
+class GlobDirectoryWalker:
+ "A forward iterator that traverses files in a directory tree."
+
+ def __init__(self, directory, pattern='*'):
+ self.index = 0
+ self.pattern = pattern
+ directory.replace('/',os.sep)
+ if os.path.isdir(directory):
+ self.stack = [directory]
+ self.files = []
+ else:
+ from reportlab.lib.utils import isCompactDistro, __loader__, rl_isdir
+ if not isCompactDistro() or not __loader__ or not rl_isdir(directory):
+ raise ValueError('"%s" is not a directory' % directory)
+ self.directory = directory[len(__loader__.archive)+len(os.sep):]
+ pfx = self.directory+os.sep
+ n = len(pfx)
+ self.files = map(lambda x, n=n: x[n:],filter(lambda x,pfx=pfx: x.startswith(pfx),__loader__._files.keys()))
+ self.stack = []
+
+ 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)
+ # now call the hook
+ self.files = self.filterFiles(self.directory, self.files)
+ 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
+
+ def filterFiles(self, folder, files):
+ "Filter hook, overwrite in subclasses as needed."
+
+ return files
+
+
+class RestrictedGlobDirectoryWalker(GlobDirectoryWalker):
+ "An restricted directory tree iterator."
+
+ def __init__(self, directory, pattern='*', ignore=None):
+ apply(GlobDirectoryWalker.__init__, (self, directory, pattern))
+
+ if ignore == None:
+ ignore = []
+ self.ignoredPatterns = []
+ if type(ignore) == type([]):
+ for p in ignore:
+ self.ignoredPatterns.append(p)
+ elif type(ignore) == type(''):
+ self.ignoredPatterns.append(ignore)
+
+
+ def filterFiles(self, folder, files):
+ "Filters all items from files matching patterns to ignore."
+
+ indicesToDelete = []
+ for i in xrange(len(files)):
+ f = files[i]
+ for p in self.ignoredPatterns:
+ if fnmatch.fnmatch(f, p):
+ indicesToDelete.append(i)
+ indicesToDelete.reverse()
+ for i in indicesToDelete:
+ del files[i]
+
+ return files
+
+
+class CVSGlobDirectoryWalker(GlobDirectoryWalker):
+ "An directory tree iterator that checks for CVS data."
+
+ def filterFiles(self, folder, files):
+ """Filters files not listed in CVS subfolder.
+
+ This will look in the CVS subfolder of 'folder' for
+ a file named 'Entries' and filter all elements from
+ the 'files' list that are not listed in 'Entries'.
+ """
+
+ join = os.path.join
+ cvsFiles = getCVSEntries(folder)
+ if cvsFiles:
+ indicesToDelete = []
+ for i in xrange(len(files)):
+ f = files[i]
+ if join(folder, f) not in cvsFiles:
+ indicesToDelete.append(i)
+ indicesToDelete.reverse()
+ for i in indicesToDelete:
+ del files[i]
+
+ return files
+
+
+# An experimental untested base class with additional 'security'.
+
+class SecureTestCase(unittest.TestCase):
+ """Secure testing base class with additional pre- and postconditions.
+
+ We try to ensure that each test leaves the environment it has
+ found unchanged after the test is performed, successful or not.
+
+ Currently we restore sys.path and the working directory, but more
+ of this could be added easily, like removing temporary files or
+ similar things.
+
+ Use this as a base class replacing unittest.TestCase and call
+ these methods in subclassed versions before doing your own
+ business!
+ """
+
+ def setUp(self):
+ "Remember sys.path and current working directory."
+
+ self._initialPath = copy.copy(sys.path)
+ self._initialWorkDir = os.getcwd()
+
+
+ def tearDown(self):
+ "Restore previous sys.path and working directory."
+
+ sys.path = self._initialPath
+ os.chdir(self._initialWorkDir)
+
+class NearTestCase(unittest.TestCase):
+ def assertNear(a,b,accuracy=1e-5):
+ if isinstance(a,(float,int)):
+ if abs(a-b)>accuracy:
+ raise AssertionError("%s not near %s" % (a, b))
+ else:
+ for ae,be in zip(a,b):
+ if abs(ae-be)>accuracy:
+ raise AssertionError("%s not near %s" % (a, b))
+ assertNear = staticmethod(assertNear)
+
+class ScriptThatMakesFileTest(unittest.TestCase):
+ """Runs a Python script at OS level, expecting it to produce a file.
+
+ It CDs to the working directory to run the script."""
+ def __init__(self, scriptDir, scriptName, outFileName, verbose=0):
+ self.scriptDir = scriptDir
+ self.scriptName = scriptName
+ self.outFileName = outFileName
+ self.verbose = verbose
+ # normally, each instance is told which method to run)
+ unittest.TestCase.__init__(self)
+
+ def setUp(self):
+ self.cwd = os.getcwd()
+ from tests.utils import testsFolder
+ scriptDir=self.scriptDir
+ if not os.path.isabs(scriptDir):
+ scriptDir=os.path.join(testsFolder,scriptDir)
+
+ os.chdir(scriptDir)
+ assert os.path.isfile(self.scriptName), "Script %s not found!" % self.scriptName
+ if os.path.isfile(self.outFileName):
+ os.remove(self.outFileName)
+
+ def tearDown(self):
+ os.chdir(self.cwd)
+
+ def runTest(self):
+ fmt = sys.platform=='win32' and '"%s" %s' or '%s %s'
+ p = os.popen(fmt % (sys.executable,self.scriptName),'r')
+ out = p.read()
+ if self.verbose:
+ print out
+ status = p.close()
+ assert os.path.isfile(self.outFileName), "File %s not created!" % self.outFileName
+
+import tests as testsFolder
+testsFolder=testsFolder.__path__[0]
+if not os.path.isabs(testsFolder): testsFolder=os.path.normpath(os.path.abspath(testsFolder))
+RL_HOME=os.path.join(testsFolder,'src','reportlab')
+if not os.path.isdir(RL_HOME):
+ RL_HOME=os.path.join(testsFolder,'reportlab')
+if not os.path.isdir(RL_HOME):
+ import reportlab as RL_HOME
+ RL_HOME=RL_HOME.__path__[0]
+ if not os.path.isabs(RL_HOME): RL_HOME=os.path.normpath(os.path.abspath(RL_HOME))