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