1#Copyright ReportLab Europe Ltd. 2000-2017 2#see license.txt for license details 3import reportlab 4reportlab._rl_testing=True 5del reportlab 6__version__='3.3.0' 7__doc__="""Provides support for the test suite. 8 9The test suite as a whole, and individual tests, need to share 10certain support functions. We have to put these in here so they 11can always be imported, and so that individual tests need to import 12nothing more than "reportlab.whatever..." 13""" 14 15import sys, os, fnmatch, re 16try: 17 from configparser import ConfigParser 18except ImportError: 19 from ConfigParser import ConfigParser 20import unittest 21from reportlab.lib.utils import isCompactDistro, __rl_loader__, rl_isdir, asUnicode 22from reportlab import ascii 23 24# Helper functions. 25def isWritable(D): 26 try: 27 fn = '00DELETE.ME' 28 f = open(fn, 'w') 29 f.write('test of writability - can be deleted') 30 f.close() 31 if os.path.isfile(fn): 32 os.remove(fn) 33 return 1 34 except: 35 return 0 36 37_OUTDIR = None 38RL_HOME = None 39testsFolder = None 40def setOutDir(name): 41 """Is it a writable file system distro being invoked within 42 test directory? If so, can write test output here. If not, 43 it had better go in a temp directory. Only do this once per 44 process""" 45 global _OUTDIR, RL_HOME, testsFolder 46 if _OUTDIR: return _OUTDIR 47 D = [d[9:] for d in sys.argv if d.startswith('--outdir=')] 48 if not D: 49 D = os.environ.get('RL_TEST_OUTDIR','') 50 if D: D=[D] 51 if D: 52 _OUTDIR = D[-1] 53 try: 54 os.makedirs(_OUTDIR) 55 except: 56 pass 57 for d in D: 58 if d in sys.argv: 59 sys.argv.remove(d) 60 else: 61 assert name=='__main__',"setOutDir should only be called in the main script" 62 scriptDir=os.path.dirname(sys.argv[0]) 63 if not scriptDir: scriptDir=os.getcwd() 64 _OUTDIR = scriptDir 65 66 if not isWritable(_OUTDIR): 67 _OUTDIR = get_rl_tempdir('reportlab_test') 68 69 import reportlab 70 RL_HOME=reportlab.__path__[0] 71 if not os.path.isabs(RL_HOME): RL_HOME=os.path.normpath(os.path.abspath(RL_HOME)) 72 topDir = os.path.dirname(RL_HOME) 73 testsFolder = os.path.join(topDir,'tests') 74 if not os.path.isdir(testsFolder): 75 testsFolder = os.path.join(os.path.dirname(topDir),'tests') 76 if not os.path.isdir(testsFolder): 77 if name=='__main__': 78 scriptDir=os.path.dirname(sys.argv[0]) 79 if not scriptDir: scriptDir=os.getcwd() 80 testsFolder = os.path.abspath(scriptDir) 81 else: 82 testsFolder = None 83 if testsFolder: 84 sys.path.insert(0,os.path.dirname(testsFolder)) 85 return _OUTDIR 86 87def outputfile(fn): 88 """This works out where to write test output. If running 89 code in a locked down file system, this will be a 90 temp directory; otherwise, the output of 'test_foo.py' will 91 normally be a file called 'test_foo.pdf', next door. 92 """ 93 D = setOutDir(__name__) 94 if fn: D = os.path.join(D,fn) 95 return D 96 97def printLocation(depth=1): 98 if sys._getframe(depth).f_locals.get('__name__')=='__main__': 99 outDir = outputfile('') 100 if outDir!=_OUTDIR: 101 print('Logs and output files written to folder "%s"' % outDir) 102 103def makeSuiteForClasses(*classes): 104 "Return a test suite with tests loaded from provided classes." 105 106 suite = unittest.TestSuite() 107 loader = unittest.TestLoader() 108 for C in classes: 109 suite.addTest(loader.loadTestsFromTestCase(C)) 110 return suite 111 112def getCVSEntries(folder, files=1, folders=0): 113 """Returns a list of filenames as listed in the CVS/Entries file. 114 115 'folder' is the folder that should contain the CVS subfolder. 116 If there is no such subfolder an empty list is returned. 117 'files' is a boolean; 1 and 0 means to return files or not. 118 'folders' is a boolean; 1 and 0 means to return folders or not. 119 """ 120 121 join = os.path.join 122 123 # If CVS subfolder doesn't exist return empty list. 124 try: 125 f = open(join(folder, 'CVS', 'Entries')) 126 except IOError: 127 return [] 128 129 # Return names of files and/or folders in CVS/Entries files. 130 allEntries = [] 131 for line in f.readlines(): 132 if folders and line[0] == 'D' \ 133 or files and line[0] != 'D': 134 entry = line.split('/')[1] 135 if entry: 136 allEntries.append(join(folder, entry)) 137 138 return allEntries 139 140 141# Still experimental class extending ConfigParser's behaviour. 142class ExtConfigParser(ConfigParser): 143 "A slightly extended version to return lists of strings." 144 145 pat = re.compile(r'\s*\[.*\]\s*') 146 147 def getstringlist(self, section, option): 148 "Coerce option to a list of strings or return unchanged if that fails." 149 150 value = ConfigParser.get(self, section, option) 151 152 # This seems to allow for newlines inside values 153 # of the config file, but be careful!! 154 val = value.replace('\n', '') 155 156 if self.pat.match(val): 157 return eval(val,{__builtins__:None}) 158 else: 159 return value 160 161 162# This class as suggested by /F with an additional hook 163# to be able to filter filenames. 164 165class GlobDirectoryWalker: 166 "A forward iterator that traverses files in a directory tree." 167 168 def __init__(self, directory, pattern='*'): 169 self.index = 0 170 self.pattern = pattern 171 directory.replace('/',os.sep) 172 if os.path.isdir(directory): 173 self.stack = [directory] 174 self.files = [] 175 else: 176 if not isCompactDistro() or not __rl_loader__ or not rl_isdir(directory): 177 raise ValueError('"%s" is not a directory' % directory) 178 self.directory = directory[len(__rl_loader__.archive)+len(os.sep):] 179 pfx = self.directory+os.sep 180 n = len(pfx) 181 self.files = list(map(lambda x, n=n: x[n:],list(filter(lambda x,pfx=pfx: x.startswith(pfx),list(__rl_loader__._files.keys()))))) 182 self.files.sort() 183 self.stack = [] 184 185 def __getitem__(self, index): 186 while 1: 187 try: 188 file = self.files[self.index] 189 self.index = self.index + 1 190 except IndexError: 191 # pop next directory from stack 192 self.directory = self.stack.pop() 193 self.files = os.listdir(self.directory) 194 # now call the hook 195 self.files = self.filterFiles(self.directory, self.files) 196 self.index = 0 197 else: 198 # got a filename 199 fullname = os.path.join(self.directory, file) 200 if os.path.isdir(fullname) and not os.path.islink(fullname): 201 self.stack.append(fullname) 202 if fnmatch.fnmatch(file, self.pattern): 203 return fullname 204 205 def filterFiles(self, folder, files): 206 "Filter hook, overwrite in subclasses as needed." 207 208 return files 209 210 211class RestrictedGlobDirectoryWalker(GlobDirectoryWalker): 212 "An restricted directory tree iterator." 213 214 def __init__(self, directory, pattern='*', ignore=None): 215 GlobDirectoryWalker.__init__(self, directory, pattern) 216 217 if ignore == None: 218 ignore = [] 219 ip = [].append 220 if isinstance(ignore,(tuple,list)): 221 for p in ignore: 222 ip(p) 223 elif isinstance(ignore,str): 224 ip(ignore) 225 self.ignorePatterns = ([_.replace('/',os.sep) for _ in ip.__self__] if os.sep != '/' 226 else ip.__self__) 227 228 def filterFiles(self, folder, files): 229 "Filters all items from files matching patterns to ignore." 230 231 fnm = fnmatch.fnmatch 232 indicesToDelete = [] 233 for i,f in enumerate(files): 234 for p in self.ignorePatterns: 235 if fnm(f, p) or fnm(os.path.join(folder,f),p): 236 indicesToDelete.append(i) 237 indicesToDelete.reverse() 238 for i in indicesToDelete: 239 del files[i] 240 241 return files 242 243 244class CVSGlobDirectoryWalker(GlobDirectoryWalker): 245 "An directory tree iterator that checks for CVS data." 246 247 def filterFiles(self, folder, files): 248 """Filters files not listed in CVS subfolder. 249 250 This will look in the CVS subfolder of 'folder' for 251 a file named 'Entries' and filter all elements from 252 the 'files' list that are not listed in 'Entries'. 253 """ 254 255 join = os.path.join 256 cvsFiles = getCVSEntries(folder) 257 if cvsFiles: 258 indicesToDelete = [] 259 for i in range(len(files)): 260 f = files[i] 261 if join(folder, f) not in cvsFiles: 262 indicesToDelete.append(i) 263 indicesToDelete.reverse() 264 for i in indicesToDelete: 265 del files[i] 266 267 return files 268 269 270# An experimental untested base class with additional 'security'. 271 272class SecureTestCase(unittest.TestCase): 273 """Secure testing base class with additional pre- and postconditions. 274 275 We try to ensure that each test leaves the environment it has 276 found unchanged after the test is performed, successful or not. 277 278 Currently we restore sys.path and the working directory, but more 279 of this could be added easily, like removing temporary files or 280 similar things. 281 282 Use this as a base class replacing unittest.TestCase and call 283 these methods in subclassed versions before doing your own 284 business! 285 """ 286 287 def setUp(self): 288 "Remember sys.path and current working directory." 289 self._initialPath = sys.path[:] 290 self._initialWorkDir = os.getcwd() 291 292 def tearDown(self): 293 "Restore previous sys.path and working directory." 294 sys.path = self._initialPath 295 os.chdir(self._initialWorkDir) 296 297class NearTestCase(unittest.TestCase): 298 def assertNear(a,b,accuracy=1e-5): 299 if isinstance(a,(float,int)): 300 if abs(a-b)>accuracy: 301 raise AssertionError("%s not near %s" % (a, b)) 302 else: 303 for ae,be in zip(a,b): 304 if abs(ae-be)>accuracy: 305 raise AssertionError("%s not near %s" % (a, b)) 306 assertNear = staticmethod(assertNear) 307 308class ScriptThatMakesFileTest(unittest.TestCase): 309 """Runs a Python script at OS level, expecting it to produce a file. 310 311 It CDs to the working directory to run the script.""" 312 def __init__(self, scriptDir, scriptName, outFileName, verbose=0): 313 self.scriptDir = scriptDir 314 self.scriptName = scriptName 315 self.outFileName = outFileName 316 self.verbose = verbose 317 # normally, each instance is told which method to run) 318 unittest.TestCase.__init__(self) 319 320 def setUp(self): 321 self.cwd = os.getcwd() 322 global testsFolder 323 scriptDir=self.scriptDir 324 if not os.path.isabs(scriptDir): 325 scriptDir=os.path.join(testsFolder,scriptDir) 326 327 os.chdir(scriptDir) 328 assert os.path.isfile(self.scriptName), "Script %s not found!" % self.scriptName 329 if os.path.isfile(self.outFileName): 330 os.remove(self.outFileName) 331 332 def tearDown(self): 333 os.chdir(self.cwd) 334 335 def runTest(self): 336 fmt = sys.platform=='win32' and '"%s" %s' or '%s %s' 337 import subprocess 338 out = subprocess.check_output((sys.executable,self.scriptName)) 339 #p = os.popen(fmt % (sys.executable,self.scriptName),'r') 340 #out = p.read() 341 if self.verbose: 342 print(out) 343 #status = p.close() 344 assert os.path.isfile(self.outFileName), "File %s not created!" % self.outFileName 345 346def equalStrings(a,b,enc='utf8'): 347 return a==b if type(a)==type(b) else asUnicode(a,enc)==asUnicode(b,enc) 348 349def eqCheck(r,x): 350 if r!=x: 351 print('Strings unequal\nexp: %s\ngot: %s' % (ascii(x),ascii(r))) 352