#!/usr/bin/python # # tester.py: run Pathan 2 tests # # Copyright (c) 2003 DecisionSoft Ltd. # Stephen White (swhite) Tue Feb 18 09:34:42 2003 # # Id: $Id: tester,v 1.3 2005/06/03 20:51:58 alby Exp $ import getopt, sys, os, string, xml from xml.dom.minidom import Document,parse,Node try: import readline except: pass # Windows or something inferior :) useUnicode = 0 try: import codecs useUnicode = 1 except: pass # Python 1.5 try: on_cygwin = (string.find(os.uname()[0],"CYGWIN") == 0) except: on_cygwin = False pass # On Windows, os.uname() does not exist def asciiStr(str): if useUnicode: return str.encode('ascii','replace') else: return str def utf8Str(str): if useUnicode: return str.encode('utf-8','strict') else: return str # # usage: display usage message # def usage(): print """ usage: %s [ options ] directory Runs a set of Pathan 2 tests from the given directory, according to the tester.xml file contained in that directory. Options: -h Displays this help message -x Displays help on the XML file format -f Run tests which are supposed to fail (instead) -a Run all tests -u Update the policy file if 'fail' tests are found to pass (only makes sense with either -a or -f) -v be more verbose -vv be very verbose -A/--add Add a 'pass' case, based on how the program currently behaves. This is interactive. -o convert old policy.test and base files into the new XML format. This will almost certainly be broken in the not-too-distant future and should be removed. You may wish to use xmlpp or an xml editor on the output! """ % (sys.argv[0]) sys.exit(0) # # xmlFormat: displays an annotated example of the XML file format # def xmlFormat(): print """ <- define a test with a given purpose <- policies define how this test will be run <- optional section that defines default eval values that apply to all policies. test.xml xs http://foo/ <- this policy uses the default values <- this policy checks that the program behaves in the same way when using the -d option <- This is a test that should pass. string(.) <- Running the program according to the more text 'debug' policy should produce this output some text <- Running the program according to every other policy should produce this output unique-ID(/testcase/namespace::*[1]) """ sys.exit(0) # # Remove me, this converts the evil files used by the old perl codeinto fluffy # XML # def convertOldFormat(): testXML = Document() testroot = testXML.createElement("test") testXML.appendChild(testroot) policyf = open("policy.test","r") testroot.setAttribute("purpose",string.strip(policyf.readline())) policies = testXML.createElement("policies") testroot.appendChild(policies) defProg = None for l in policyf.readlines(): if l[0]=='#': continue (name,cmd,args) = string.split(l+"\t\t","\t",2) # always exactly 3 elements command = string.split(string.strip(cmd)) arguments = string.split(string.strip(args)) if defProg == None: defProg = command[0] policy = testXML.createElement("policy") policy.setAttribute("name",name) policies.appendChild(policy) # Program name if command[0] != defProg: program = testXML.createElement("program") t = testXML.createTextNode(command[0]) program.appendChild(t) policy.appendChild(program) del command[0] # Options while len(command) > 0: option = command[0] value = "" del command[0] while (len(command)>0) and (command[0][0]!="-"): value = string.strip(value + " " + command[0]) del command[0] opt = testXML.createElement("addOption") opt.setAttribute("name",option) if value: t = testXML.createTextNode(value) opt.appendChild(t) policy.appendChild(opt) # Arguments for arg in arguments: if arg != "2>&1": # We do this regardless a = testXML.createElement("addArg") t = testXML.createTextNode(arg) a.appendChild(t) policy.appendChild(a) policyf.close() defaults = testXML.createElement("default") policies.appendChild(defaults) program = testXML.createElement("program") t = testXML.createTextNode(defProg) program.appendChild(t) defaults.appendChild(program) # In theory we should now scan all the policies and take any common options # and put them in the default section bases = open("base","r") while 1: s = string.strip(bases.readline()) if not s: break while s[0]=='#': s = string.strip(bases.readline()) (input,comment) = string.split(s+"##","#",1) # Ensure split is 2 parts while input[-1] == "\\": # Cope with escaped #s (i,comment) = string.split(comment,"#",1) input = input[:-1] + '#' + i comment = string.strip(string.replace(comment,"#"," ")) input = string.replace(input,"PWD","$PWD$") type = "pass" if string.find(comment,"FAIL")==0: comment = string.strip(string.replace(comment,"FAIL","")) type = "fail" output = '' s = bases.readline() while s and (string.strip(s) != '%'): if s[0] != '#': output = output + string.replace(s,"\#","#") # Grr s = bases.readline() output = string.replace(output,"PWD","$PWD$") base = testXML.createElement("base") base.setAttribute("type",type) if comment: base.setAttribute("comment",comment) inp = testXML.createElement("arg") t = testXML.createTextNode(input) inp.appendChild(t) base.appendChild(inp) out = testXML.createElement("output") t = testXML.createTextNode(output) out.appendChild(t) base.appendChild(out) testroot.appendChild(base) bases.close() f = open("tester.xml","w") testXML.writexml(f) f.close() # # main: # try: optlist, args = getopt.getopt(sys.argv[1:],"hfxovauA",["help","add","unordered"]) except getopt.error, e: print "Error:",e usage() testType = 'pass' convertOld = 0 verbose = 0 updateFile = 0 doAdd = 0 unorderedMode = 0 for o, a in optlist: if o in ["-h","--help"]: usage() if o in ["-A","--add"]: doAdd = 1 if o=="-x": xmlFormat() if o=="-f": testType = 'fail' if o=="-a": testType = None if o=="-v": verbose = verbose + 1 if o=="-o": convertOld = 1 if o=="-u": updateFile = 1 if o=="--unordered": unorderedMode = 1 if len(args)==0: usage() os.chdir(args[0]) if convertOld: convertOldFormat() # # Get all direct (element) children of 'node' with the given name # def getChildrenCalled(name,node): list = [] for n in node.childNodes: if (n.nodeType == Node.ELEMENT_NODE) and (n.localName == name): list.append(n) return list # # Gets a child of 'node' with the given name. Raises an exception if # there isn't exactly one child with the given name # def getChildCalled(name,node): l = getChildrenCalled(name,node) if len(l) == 1: return l[0] else: if len(l) == 0: raise "NoSuchChild",name else: raise "TooManyChildrenCalled",name # # Get the concatenation of all the TextNodes contained as direct children of # the given node # def textValue(node): text = "" for n in node.childNodes: if n.nodeType == Node.TEXT_NODE: text = text + n.nodeValue return text # # Read tester.xml and run the tests! # try: testXML = parse('tester.xml') except xml.sax._exceptions.SAXParseException, e: print "Error parsing XML in tester.xml:",os.getcwd() print e sys.exit(1) testroot = testXML.documentElement policies = getChildCalled("policies",testroot) defaultArgs = [] defaultOpts = [] defaultProg = None try: default = getChildCalled("default",policies) try: defaultProg = textValue(getChildCalled("program",default)) except "NoSuchChild",name: pass for n in getChildrenCalled("addArg",default): defaultArgs.append(textValue(n)) for n in getChildrenCalled("addOption",default): defaultOpts.append(n.getAttribute("name"),textValue(n)) except "NoSuchChild",name: pass # the default section is optional totalFailures = 0 failPasses = [] def arg2cmdline(arg): if os.name == "nt": # See http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp result = [] bs_buf = [] result.append('"') for c in arg: if c == '\\': # Don't know if we need to double yet. bs_buf.append(c) elif c == '"': # Double backspaces. result.append('\\' * len(bs_buf)*2) bs_buf = [] result.append('\\"') else: # Normal char if bs_buf: result.extend(bs_buf) bs_buf = [] result.append(c) # Add remaining backspaces, if any. if bs_buf: result.extend(bs_buf) result.extend(bs_buf) result.append('"') s = ''.join(result) return s else: return "'"+string.replace(arg,"'","'\"'\"'")+"'" def runTest(prog,opts,args,arg): arg = arg2cmdline(arg) cwd = string.replace(os.getcwd(), os.pathsep, "/") arg = string.replace(arg,"$PWD$",cwd) s = string.join([prog]+opts+[arg]+args+["2>&1"]," ") o = os.popen(utf8Str(s)) realoutput = string.strip(string.join(o.readlines(),"")) o.close() if useUnicode: return realoutput.decode('utf-8') else: return realoutput for policy in getChildrenCalled("policy",policies): print "#####" print "# Testing:",testroot.getAttribute("purpose"),"("+policy.getAttribute("name")+")" print "#" try: prog = getChildCalled("program",policy).nodeValue except "NoSuchChild",name: prog = defaultProg if os.name == "nt": prog = string.replace(prog,"/","\\") prog = prog + ".exe" if not os.access(prog,os.X_OK): raise "ProgramNotFoundOrNotExecutable",prog args = defaultArgs[:] opts = defaultOpts[:] for n in getChildrenCalled("addArg",policy): args.append(textValue(n)) for n in getChildrenCalled("addOption",policy): opts.append(n.getAttribute("name"),textValue(n)) if verbose: print "Program:",asciiStr(prog) print "Options:",asciiStr(string.join(opts," ")) print "Arguments:",asciiStr(string.join(args," ")) passed = 0 skipped = 0 failures = 0 policyName = policy.getAttribute("name") failureLogName = "failures."+testroot.getAttribute("purpose")+"."+policyName failureLogName=string.replace(failureLogName," ","") failureLogName=string.replace(failureLogName,"/","") failureLog = open(failureLogName,"w") print "Running tests: ", for b in getChildrenCalled("base",testroot): type = b.getAttribute("type") if (testType == None) or (type == testType): arg = textValue(getChildCalled("arg",b)) if unorderedMode == 1: arg = "unordered(" + arg + ")" if verbose >= 2: print "\nTest:",asciiStr(arg),"(type: "+type+") ", output = '' for o in getChildrenCalled("output", b): if (not o.hasAttribute("policy")) or (policyName == o.getAttribute("policy")): output = string.strip(textValue(o)) if on_cygwin: # Kludge to allow for running of windows binaries using the cygwin copy of python output = string.replace(output,"$PWD$",string.replace(os.getcwd(),"cygdrive/c","c:")) else: output = string.replace(output,"$PWD$",os.getcwd()) realoutput = runTest(prog,opts,args,arg) if on_cygwin: # Don't really understand why this is occuring realoutput = string.replace(realoutput,"\r","") output = string.replace(output,"\r","") if output==realoutput: passed = passed + 1 sys.stdout.write(".") if type == "fail": # A fail type passed! failPasses.append(arg) b.setAttribute("type","pass") else: failures = failures + 1 sys.stdout.write("!") if verbose >= 2: print "\nGot:",asciiStr(realoutput) print "Expected:",asciiStr(output) failureLog.write("Failed with input "+utf8Str(arg)+".\n") failureLog.write("Expected output:\n"+utf8Str(output)+"\n") failureLog.write("Actual output:\n"+utf8Str(realoutput)+"\n\n") else: skipped = skipped + 1 if verbose: sys.stdout.write("x") sys.stdout.flush() print failureLog.close() print "Stats:",passed,"passed,",failures,"failed ("+str(failures)+"/"+str(failures+passed)+"),",skipped,"skipped" if failures > 0: print "More information:",failureLogName totalFailures = totalFailures + failures else: os.unlink(failureLogName) if doAdd: #os.system("stty", '-icanon', 'eol', "\001") # set STDIN to one byte at a time #os.system("stty", 'icanon', 'eol', '^@') # sets STDIN to one line at a time ans = "" while ans != "n": ans = string.lower(string.strip(raw_input("Do you want to add a test? (Y/N) "))) if ans == "y": arg = string.strip(raw_input("Argument to use: ")) output = runTest(prog,opts,args,arg) print asciiStr(output) ok = string.lower(string.strip(raw_input("Is this ok? (Y/N)"))) if ok == "y": base = testXML.createElement("base") base.setAttribute("type","pass") inp = testXML.createElement("arg") t = testXML.createTextNode(arg) inp.appendChild(t) base.appendChild(inp) out = testXML.createElement("output") t = testXML.createTextNode(output) out.appendChild(t) base.appendChild(out) testroot.appendChild(base) updateFile = 1 if len(failPasses)>0: print "--> Some tests marked as 'fail' passed:" for p in failPasses: print " ",p print if not updateFile: print "Re-run with \"-u\" to update the policy file with these tests changed to 'pass'" if updateFile: if useUnicode: f = codecs.open("tester.xml","w","utf-8") else: f = open("tester.xml","w") #testXML.writexml(f) #Kludge alert! f.write('\n') for node in testXML.childNodes: node.writexml(f, "", "", "") f.close() print "XML file updated" sys.exit(totalFailures) # Should probably take min(totalFailures,255)