summaryrefslogtreecommitdiff
path: root/regressions.py
diff options
context:
space:
mode:
authorMike Hommey <mh@glandium.org>2004-07-06 12:57:17 +0000
committerMike Hommey <mh@glandium.org>2004-07-06 12:57:17 +0000
commitc14c53a3645d81281058d4bb4cff24fa8d6faf33 (patch)
tree29bccc2e7499af078a3d1cdcfb517a1dee891be5 /regressions.py
parentd4e028c96af89ade493b440d4f2de6b684c03a06 (diff)
downloadlibxml2-c14c53a3645d81281058d4bb4cff24fa8d6faf33.tar.gz
Load /tmp/tmp.DIvcnD/libxml2-2.6.11 intoupstream/2.6.11
packages/libxml2/branches/upstream/current.
Diffstat (limited to 'regressions.py')
-rwxr-xr-xregressions.py350
1 files changed, 350 insertions, 0 deletions
diff --git a/regressions.py b/regressions.py
new file mode 100755
index 0000000..0e813c2
--- /dev/null
+++ b/regressions.py
@@ -0,0 +1,350 @@
+#!/usr/bin/python -u
+import glob, os, string, sys, thread, time
+# import difflib
+import libxml2
+
+###
+#
+# This is a "Work in Progress" attempt at a python script to run the
+# various regression tests. The rationale for this is that it should be
+# possible to run this on most major platforms, including those (such as
+# Windows) which don't support gnu Make.
+#
+# The script is driven by a parameter file which defines the various tests
+# to be run, together with the unique settings for each of these tests. A
+# script for Linux is included (regressions.xml), with comments indicating
+# the significance of the various parameters. To run the tests under Windows,
+# edit regressions.xml and remove the comment around the default parameter
+# "<execpath>" (i.e. make it point to the location of the binary executables).
+#
+# Note that this current version requires the Python bindings for libxml2 to
+# have been previously installed and accessible
+#
+# See Copyright for the status of this software.
+# William Brack (wbrack@mmm.com.hk)
+#
+###
+defaultParams = {} # will be used as a dictionary to hold the parsed params
+
+# This routine is used for comparing the expected stdout / stdin with the results.
+# The expected data has already been read in; the result is a file descriptor.
+# Within the two sets of data, lines may begin with a path string. If so, the
+# code "relativises" it by removing the path component. The first argument is a
+# list already read in by a separate thread; the second is a file descriptor.
+# The two 'base' arguments are to let me "relativise" the results files, allowing
+# the script to be run from any directory.
+def compFiles(res, expected, base1, base2):
+ l1 = len(base1)
+ exp = expected.readlines()
+ expected.close()
+ # the "relativisation" is done here
+ for i in range(len(res)):
+ j = string.find(res[i],base1)
+ if (j == 0) or ((j == 2) and (res[i][0:2] == './')):
+ col = string.find(res[i],':')
+ if col > 0:
+ start = string.rfind(res[i][:col], '/')
+ if start > 0:
+ res[i] = res[i][start+1:]
+
+ for i in range(len(exp)):
+ j = string.find(exp[i],base2)
+ if (j == 0) or ((j == 2) and (exp[i][0:2] == './')):
+ col = string.find(exp[i],':')
+ if col > 0:
+ start = string.rfind(exp[i][:col], '/')
+ if start > 0:
+ exp[i] = exp[i][start+1:]
+
+ ret = 0
+ # ideally we would like to use difflib functions here to do a
+ # nice comparison of the two sets. Unfortunately, during testing
+ # (using python 2.3.3 and 2.3.4) the following code went into
+ # a dead loop under windows. I'll pursue this later.
+# diff = difflib.ndiff(res, exp)
+# diff = list(diff)
+# for line in diff:
+# if line[:2] != ' ':
+# print string.strip(line)
+# ret = -1
+
+ # the following simple compare is fine for when the two data sets
+ # (actual result vs. expected result) are equal, which should be true for
+ # us. Unfortunately, if the test fails it's not nice at all.
+ rl = len(res)
+ el = len(exp)
+ if el != rl:
+ print 'Length of expected is %d, result is %d' % (el, rl)
+ ret = -1
+ for i in range(min(el, rl)):
+ if string.strip(res[i]) != string.strip(exp[i]):
+ print '+:%s-:%s' % (res[i], exp[i])
+ ret = -1
+ if el > rl:
+ for i in range(rl, el):
+ print '-:%s' % exp[i]
+ ret = -1
+ elif rl > el:
+ for i in range (el, rl):
+ print '+:%s' % res[i]
+ ret = -1
+ return ret
+
+# Separate threads to handle stdout and stderr are created to run this function
+def readPfile(file, list, flag):
+ data = file.readlines() # no call by reference, so I cheat
+ for l in data:
+ list.append(l)
+ file.close()
+ flag.append('ok')
+
+# This routine runs the test program (e.g. xmllint)
+def runOneTest(testDescription, filename, inbase, errbase):
+ if 'execpath' in testDescription:
+ dir = testDescription['execpath'] + '/'
+ else:
+ dir = ''
+ cmd = os.path.abspath(dir + testDescription['testprog'])
+ if 'flag' in testDescription:
+ for f in string.split(testDescription['flag']):
+ cmd += ' ' + f
+ if 'stdin' not in testDescription:
+ cmd += ' ' + inbase + filename
+ if 'extarg' in testDescription:
+ cmd += ' ' + testDescription['extarg']
+
+ noResult = 0
+ expout = None
+ if 'resext' in testDescription:
+ if testDescription['resext'] == 'None':
+ noResult = 1
+ else:
+ ext = '.' + testDescription['resext']
+ else:
+ ext = ''
+ if not noResult:
+ try:
+ fname = errbase + filename + ext
+ expout = open(fname, 'rt')
+ except:
+ print "Can't open result file %s - bypassing test" % fname
+ return
+
+ noErrors = 0
+ if 'reserrext' in testDescription:
+ if testDescription['reserrext'] == 'None':
+ noErrors = 1
+ else:
+ if len(testDescription['reserrext'])>0:
+ ext = '.' + testDescription['reserrext']
+ else:
+ ext = ''
+ else:
+ ext = ''
+ if not noErrors:
+ try:
+ fname = errbase + filename + ext
+ experr = open(fname, 'rt')
+ except:
+ experr = None
+ else:
+ experr = None
+
+ pin, pout, perr = os.popen3(cmd)
+ if 'stdin' in testDescription:
+ infile = open(inbase + filename, 'rt')
+ pin.writelines(infile.readlines())
+ infile.close()
+ pin.close()
+
+ # popen is great fun, but can lead to the old "deadly embrace", because
+ # synchronizing the writing (by the task being run) of stdout and stderr
+ # with respect to the reading (by this task) is basically impossible. I
+ # tried several ways to cheat, but the only way I have found which works
+ # is to do a *very* elementary multi-threading approach. We can only hope
+ # that Python threads are implemented on the target system (it's okay for
+ # Linux and Windows)
+
+ th1Flag = [] # flags to show when threads finish
+ th2Flag = []
+ outfile = [] # lists to contain the pipe data
+ errfile = []
+ th1 = thread.start_new_thread(readPfile, (pout, outfile, th1Flag))
+ th2 = thread.start_new_thread(readPfile, (perr, errfile, th2Flag))
+ while (len(th1Flag)==0) or (len(th2Flag)==0):
+ time.sleep(0.001)
+ if not noResult:
+ ret = compFiles(outfile, expout, inbase, 'test/')
+ if ret != 0:
+ print 'trouble with %s' % cmd
+ else:
+ if len(outfile) != 0:
+ for l in outfile:
+ print l
+ print 'trouble with %s' % cmd
+ if experr != None:
+ ret = compFiles(errfile, experr, inbase, 'test/')
+ if ret != 0:
+ print 'trouble with %s' % cmd
+ else:
+ if not noErrors:
+ if len(errfile) != 0:
+ for l in errfile:
+ print l
+ print 'trouble with %s' % cmd
+
+ if 'stdin' not in testDescription:
+ pin.close()
+
+# This routine is called by the parameter decoding routine whenever the end of a
+# 'test' section is encountered. Depending upon file globbing, a large number of
+# individual tests may be run.
+def runTest(description):
+ testDescription = defaultParams.copy() # set defaults
+ testDescription.update(description) # override with current ent
+ if 'testname' in testDescription:
+ print "## %s" % testDescription['testname']
+ if not 'file' in testDescription:
+ print "No file specified - can't run this test!"
+ return
+ # Set up the source and results directory paths from the decoded params
+ dir = ''
+ if 'srcdir' in testDescription:
+ dir += testDescription['srcdir'] + '/'
+ if 'srcsub' in testDescription:
+ dir += testDescription['srcsub'] + '/'
+
+ rdir = ''
+ if 'resdir' in testDescription:
+ rdir += testDescription['resdir'] + '/'
+ if 'ressub' in testDescription:
+ rdir += testDescription['ressub'] + '/'
+
+ testFiles = glob.glob(os.path.abspath(dir + testDescription['file']))
+ if testFiles == []:
+ print "No files result from '%s'" % testDescription['file']
+ return
+
+ # Some test programs just don't work (yet). For now we exclude them.
+ count = 0
+ excl = []
+ if 'exclfile' in testDescription:
+ for f in string.split(testDescription['exclfile']):
+ glb = glob.glob(dir + f)
+ for g in glb:
+ excl.append(os.path.abspath(g))
+
+ # Run the specified test program
+ for f in testFiles:
+ if not os.path.isdir(f):
+ if f not in excl:
+ count = count + 1
+ runOneTest(testDescription, os.path.basename(f), dir, rdir)
+
+#
+# The following classes are used with the xmlreader interface to interpret the
+# parameter file. Once a test section has been identified, runTest is called
+# with a dictionary containing the parsed results of the interpretation.
+#
+
+class testDefaults:
+ curText = '' # accumulates text content of parameter
+
+ def addToDict(self, key):
+ txt = string.strip(self.curText)
+# if txt == '':
+# return
+ if key not in defaultParams:
+ defaultParams[key] = txt
+ else:
+ defaultParams[key] += ' ' + txt
+
+ def processNode(self, reader, curClass):
+ if reader.Depth() == 2:
+ if reader.NodeType() == 1:
+ self.curText = '' # clear the working variable
+ elif reader.NodeType() == 15:
+ if (reader.Name() != '#text') and (reader.Name() != '#comment'):
+ self.addToDict(reader.Name())
+ elif reader.Depth() == 3:
+ if reader.Name() == '#text':
+ self.curText += reader.Value()
+
+ elif reader.NodeType() == 15: # end of element
+ print "Defaults have been set to:"
+ for k in defaultParams.keys():
+ print " %s : '%s'" % (k, defaultParams[k])
+ curClass = rootClass()
+ return curClass
+
+
+class testClass:
+ def __init__(self):
+ self.testParams = {} # start with an empty set of params
+ self.curText = '' # and empty text
+
+ def addToDict(self, key):
+ data = string.strip(self.curText)
+ if key not in self.testParams:
+ self.testParams[key] = data
+ else:
+ if self.testParams[key] != '':
+ data = ' ' + data
+ self.testParams[key] += data
+
+ def processNode(self, reader, curClass):
+ if reader.Depth() == 2:
+ if reader.NodeType() == 1:
+ self.curText = '' # clear the working variable
+ if reader.Name() not in self.testParams:
+ self.testParams[reader.Name()] = ''
+ elif reader.NodeType() == 15:
+ if (reader.Name() != '#text') and (reader.Name() != '#comment'):
+ self.addToDict(reader.Name())
+ elif reader.Depth() == 3:
+ if reader.Name() == '#text':
+ self.curText += reader.Value()
+
+ elif reader.NodeType() == 15: # end of element
+ runTest(self.testParams)
+ curClass = rootClass()
+ return curClass
+
+
+class rootClass:
+ def processNode(self, reader, curClass):
+ if reader.Depth() == 0:
+ return curClass
+ if reader.Depth() != 1:
+ print "Unexpected junk: Level %d, type %d, name %s" % (
+ reader.Depth(), reader.NodeType(), reader.Name())
+ return curClass
+ if reader.Name() == 'test':
+ curClass = testClass()
+ curClass.testParams = {}
+ elif reader.Name() == 'defaults':
+ curClass = testDefaults()
+ return curClass
+
+def streamFile(filename):
+ try:
+ reader = libxml2.newTextReaderFilename(filename)
+ except:
+ print "unable to open %s" % (filename)
+ return
+
+ curClass = rootClass()
+ ret = reader.Read()
+ while ret == 1:
+ curClass = curClass.processNode(reader, curClass)
+ ret = reader.Read()
+
+ if ret != 0:
+ print "%s : failed to parse" % (filename)
+
+# OK, we're finished with all the routines. Now for the main program:-
+if len(sys.argv) != 2:
+ print "Usage: maketest {filename}"
+ sys.exit(-1)
+
+streamFile(sys.argv[1])