Annotation of embedaddon/libxml2/regressions.py, revision 1.1.1.1
1.1 misho 1: #!/usr/bin/python -u
2: import glob, os, string, sys, thread, time
3: # import difflib
4: import libxml2
5:
6: ###
7: #
8: # This is a "Work in Progress" attempt at a python script to run the
9: # various regression tests. The rationale for this is that it should be
10: # possible to run this on most major platforms, including those (such as
11: # Windows) which don't support gnu Make.
12: #
13: # The script is driven by a parameter file which defines the various tests
14: # to be run, together with the unique settings for each of these tests. A
15: # script for Linux is included (regressions.xml), with comments indicating
16: # the significance of the various parameters. To run the tests under Windows,
17: # edit regressions.xml and remove the comment around the default parameter
18: # "<execpath>" (i.e. make it point to the location of the binary executables).
19: #
20: # Note that this current version requires the Python bindings for libxml2 to
21: # have been previously installed and accessible
22: #
23: # See Copyright for the status of this software.
24: # William Brack (wbrack@mmm.com.hk)
25: #
26: ###
27: defaultParams = {} # will be used as a dictionary to hold the parsed params
28:
29: # This routine is used for comparing the expected stdout / stdin with the results.
30: # The expected data has already been read in; the result is a file descriptor.
31: # Within the two sets of data, lines may begin with a path string. If so, the
32: # code "relativises" it by removing the path component. The first argument is a
33: # list already read in by a separate thread; the second is a file descriptor.
34: # The two 'base' arguments are to let me "relativise" the results files, allowing
35: # the script to be run from any directory.
36: def compFiles(res, expected, base1, base2):
37: l1 = len(base1)
38: exp = expected.readlines()
39: expected.close()
40: # the "relativisation" is done here
41: for i in range(len(res)):
42: j = string.find(res[i],base1)
43: if (j == 0) or ((j == 2) and (res[i][0:2] == './')):
44: col = string.find(res[i],':')
45: if col > 0:
46: start = string.rfind(res[i][:col], '/')
47: if start > 0:
48: res[i] = res[i][start+1:]
49:
50: for i in range(len(exp)):
51: j = string.find(exp[i],base2)
52: if (j == 0) or ((j == 2) and (exp[i][0:2] == './')):
53: col = string.find(exp[i],':')
54: if col > 0:
55: start = string.rfind(exp[i][:col], '/')
56: if start > 0:
57: exp[i] = exp[i][start+1:]
58:
59: ret = 0
60: # ideally we would like to use difflib functions here to do a
61: # nice comparison of the two sets. Unfortunately, during testing
62: # (using python 2.3.3 and 2.3.4) the following code went into
63: # a dead loop under windows. I'll pursue this later.
64: # diff = difflib.ndiff(res, exp)
65: # diff = list(diff)
66: # for line in diff:
67: # if line[:2] != ' ':
68: # print string.strip(line)
69: # ret = -1
70:
71: # the following simple compare is fine for when the two data sets
72: # (actual result vs. expected result) are equal, which should be true for
73: # us. Unfortunately, if the test fails it's not nice at all.
74: rl = len(res)
75: el = len(exp)
76: if el != rl:
77: print 'Length of expected is %d, result is %d' % (el, rl)
78: ret = -1
79: for i in range(min(el, rl)):
80: if string.strip(res[i]) != string.strip(exp[i]):
81: print '+:%s-:%s' % (res[i], exp[i])
82: ret = -1
83: if el > rl:
84: for i in range(rl, el):
85: print '-:%s' % exp[i]
86: ret = -1
87: elif rl > el:
88: for i in range (el, rl):
89: print '+:%s' % res[i]
90: ret = -1
91: return ret
92:
93: # Separate threads to handle stdout and stderr are created to run this function
94: def readPfile(file, list, flag):
95: data = file.readlines() # no call by reference, so I cheat
96: for l in data:
97: list.append(l)
98: file.close()
99: flag.append('ok')
100:
101: # This routine runs the test program (e.g. xmllint)
102: def runOneTest(testDescription, filename, inbase, errbase):
103: if 'execpath' in testDescription:
104: dir = testDescription['execpath'] + '/'
105: else:
106: dir = ''
107: cmd = os.path.abspath(dir + testDescription['testprog'])
108: if 'flag' in testDescription:
109: for f in string.split(testDescription['flag']):
110: cmd += ' ' + f
111: if 'stdin' not in testDescription:
112: cmd += ' ' + inbase + filename
113: if 'extarg' in testDescription:
114: cmd += ' ' + testDescription['extarg']
115:
116: noResult = 0
117: expout = None
118: if 'resext' in testDescription:
119: if testDescription['resext'] == 'None':
120: noResult = 1
121: else:
122: ext = '.' + testDescription['resext']
123: else:
124: ext = ''
125: if not noResult:
126: try:
127: fname = errbase + filename + ext
128: expout = open(fname, 'rt')
129: except:
130: print "Can't open result file %s - bypassing test" % fname
131: return
132:
133: noErrors = 0
134: if 'reserrext' in testDescription:
135: if testDescription['reserrext'] == 'None':
136: noErrors = 1
137: else:
138: if len(testDescription['reserrext'])>0:
139: ext = '.' + testDescription['reserrext']
140: else:
141: ext = ''
142: else:
143: ext = ''
144: if not noErrors:
145: try:
146: fname = errbase + filename + ext
147: experr = open(fname, 'rt')
148: except:
149: experr = None
150: else:
151: experr = None
152:
153: pin, pout, perr = os.popen3(cmd)
154: if 'stdin' in testDescription:
155: infile = open(inbase + filename, 'rt')
156: pin.writelines(infile.readlines())
157: infile.close()
158: pin.close()
159:
160: # popen is great fun, but can lead to the old "deadly embrace", because
161: # synchronizing the writing (by the task being run) of stdout and stderr
162: # with respect to the reading (by this task) is basically impossible. I
163: # tried several ways to cheat, but the only way I have found which works
164: # is to do a *very* elementary multi-threading approach. We can only hope
165: # that Python threads are implemented on the target system (it's okay for
166: # Linux and Windows)
167:
168: th1Flag = [] # flags to show when threads finish
169: th2Flag = []
170: outfile = [] # lists to contain the pipe data
171: errfile = []
172: th1 = thread.start_new_thread(readPfile, (pout, outfile, th1Flag))
173: th2 = thread.start_new_thread(readPfile, (perr, errfile, th2Flag))
174: while (len(th1Flag)==0) or (len(th2Flag)==0):
175: time.sleep(0.001)
176: if not noResult:
177: ret = compFiles(outfile, expout, inbase, 'test/')
178: if ret != 0:
179: print 'trouble with %s' % cmd
180: else:
181: if len(outfile) != 0:
182: for l in outfile:
183: print l
184: print 'trouble with %s' % cmd
185: if experr != None:
186: ret = compFiles(errfile, experr, inbase, 'test/')
187: if ret != 0:
188: print 'trouble with %s' % cmd
189: else:
190: if not noErrors:
191: if len(errfile) != 0:
192: for l in errfile:
193: print l
194: print 'trouble with %s' % cmd
195:
196: if 'stdin' not in testDescription:
197: pin.close()
198:
199: # This routine is called by the parameter decoding routine whenever the end of a
200: # 'test' section is encountered. Depending upon file globbing, a large number of
201: # individual tests may be run.
202: def runTest(description):
203: testDescription = defaultParams.copy() # set defaults
204: testDescription.update(description) # override with current ent
205: if 'testname' in testDescription:
206: print "## %s" % testDescription['testname']
207: if not 'file' in testDescription:
208: print "No file specified - can't run this test!"
209: return
210: # Set up the source and results directory paths from the decoded params
211: dir = ''
212: if 'srcdir' in testDescription:
213: dir += testDescription['srcdir'] + '/'
214: if 'srcsub' in testDescription:
215: dir += testDescription['srcsub'] + '/'
216:
217: rdir = ''
218: if 'resdir' in testDescription:
219: rdir += testDescription['resdir'] + '/'
220: if 'ressub' in testDescription:
221: rdir += testDescription['ressub'] + '/'
222:
223: testFiles = glob.glob(os.path.abspath(dir + testDescription['file']))
224: if testFiles == []:
225: print "No files result from '%s'" % testDescription['file']
226: return
227:
228: # Some test programs just don't work (yet). For now we exclude them.
229: count = 0
230: excl = []
231: if 'exclfile' in testDescription:
232: for f in string.split(testDescription['exclfile']):
233: glb = glob.glob(dir + f)
234: for g in glb:
235: excl.append(os.path.abspath(g))
236:
237: # Run the specified test program
238: for f in testFiles:
239: if not os.path.isdir(f):
240: if f not in excl:
241: count = count + 1
242: runOneTest(testDescription, os.path.basename(f), dir, rdir)
243:
244: #
245: # The following classes are used with the xmlreader interface to interpret the
246: # parameter file. Once a test section has been identified, runTest is called
247: # with a dictionary containing the parsed results of the interpretation.
248: #
249:
250: class testDefaults:
251: curText = '' # accumulates text content of parameter
252:
253: def addToDict(self, key):
254: txt = string.strip(self.curText)
255: # if txt == '':
256: # return
257: if key not in defaultParams:
258: defaultParams[key] = txt
259: else:
260: defaultParams[key] += ' ' + txt
261:
262: def processNode(self, reader, curClass):
263: if reader.Depth() == 2:
264: if reader.NodeType() == 1:
265: self.curText = '' # clear the working variable
266: elif reader.NodeType() == 15:
267: if (reader.Name() != '#text') and (reader.Name() != '#comment'):
268: self.addToDict(reader.Name())
269: elif reader.Depth() == 3:
270: if reader.Name() == '#text':
271: self.curText += reader.Value()
272:
273: elif reader.NodeType() == 15: # end of element
274: print "Defaults have been set to:"
275: for k in defaultParams.keys():
276: print " %s : '%s'" % (k, defaultParams[k])
277: curClass = rootClass()
278: return curClass
279:
280:
281: class testClass:
282: def __init__(self):
283: self.testParams = {} # start with an empty set of params
284: self.curText = '' # and empty text
285:
286: def addToDict(self, key):
287: data = string.strip(self.curText)
288: if key not in self.testParams:
289: self.testParams[key] = data
290: else:
291: if self.testParams[key] != '':
292: data = ' ' + data
293: self.testParams[key] += data
294:
295: def processNode(self, reader, curClass):
296: if reader.Depth() == 2:
297: if reader.NodeType() == 1:
298: self.curText = '' # clear the working variable
299: if reader.Name() not in self.testParams:
300: self.testParams[reader.Name()] = ''
301: elif reader.NodeType() == 15:
302: if (reader.Name() != '#text') and (reader.Name() != '#comment'):
303: self.addToDict(reader.Name())
304: elif reader.Depth() == 3:
305: if reader.Name() == '#text':
306: self.curText += reader.Value()
307:
308: elif reader.NodeType() == 15: # end of element
309: runTest(self.testParams)
310: curClass = rootClass()
311: return curClass
312:
313:
314: class rootClass:
315: def processNode(self, reader, curClass):
316: if reader.Depth() == 0:
317: return curClass
318: if reader.Depth() != 1:
319: print "Unexpected junk: Level %d, type %d, name %s" % (
320: reader.Depth(), reader.NodeType(), reader.Name())
321: return curClass
322: if reader.Name() == 'test':
323: curClass = testClass()
324: curClass.testParams = {}
325: elif reader.Name() == 'defaults':
326: curClass = testDefaults()
327: return curClass
328:
329: def streamFile(filename):
330: try:
331: reader = libxml2.newTextReaderFilename(filename)
332: except:
333: print "unable to open %s" % (filename)
334: return
335:
336: curClass = rootClass()
337: ret = reader.Read()
338: while ret == 1:
339: curClass = curClass.processNode(reader, curClass)
340: ret = reader.Read()
341:
342: if ret != 0:
343: print "%s : failed to parse" % (filename)
344:
345: # OK, we're finished with all the routines. Now for the main program:-
346: if len(sys.argv) != 2:
347: print "Usage: maketest {filename}"
348: sys.exit(-1)
349:
350: streamFile(sys.argv[1])
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>