Blame regressions.py

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