# usage: python check.py --help # #@author: michael.karlen@epfl.ch #@version: 1.1 #@date: 2. 2. 2006 # #~iclamp/soft/bin/risc-emu COMPILER = "scala -cp classes zweic.GeneratorTest" #RISC = "java -cp risc/lib/risc.jar risc.emulator.Main" RISC = "/home/iclamp/soft/bin/risc-emu" OUTDIR = "asm" TABBING = '30' __doc__ = """ THIS CODE COMES WITH ABSOLUTELY NO WARRANTY This script helps you create automated unit tests for your Zwei parser. The idea is to put meta-comments into your zwei sources that indicate the desired output of your program. The script then compiles your script, executes it and compares stdout with your metainformation. usage: python unittest.py --help python unittest.py tests/5/*.zwei EXECUTING: ---------- These two commands have to work in your shell: scala -cp classes zweic.GeneratorTest java -cp risc/lib/risc.jar risc.emulator.Main risc.emulator.Main can be downloaded from the lamp homepage. If you are using different paths, please adapt them either by command line arguments or by changing the constants COMPILER and ASSEMBLER in the beginning of the file. The directory 'asm' will be created to store compiled sources. You also can adapt this. WARNING: Existing files will be replaced! TESTFILE STRUCTURE: ------------------- ** Variant 1 ** As soon as a comment with the syntax //#isut is encountered (no space between '#' and 'isut'), all following comments containing a '#' will be taken into consideration. //#spam\n for example expects 'spam' plus newline (printChar(10)) as output. Before the has you are alwas allowed to put any normal comments. //this is a comment #eggandbacon //this is still a normal comment //#ham expects 'eggandbaconham' as output. ** Variant 2 ** Meta information is tagged by '//#startut ... //#endut'. All comments figuring in between are expected as output of your code. The '#endut' tag is optional. //#startut //scala\n //genau //#endut //any normal comment here expects 'scala' newline 'genau' as output of your script. Please refer to the examples shipped with this code for reference. KNOWN BUGS ---------- - readChar does not work. ANYTHING? --------- Suggestions are welcome: michael.karlen@epfl.ch Please share your testcases! happy coding. """ import sys, os, re from optparse import OptionParser rx_isut = re.compile(r'//[^#]*#' \ 'isut', re.I) rx_startut = re.compile(r'//[^#]*#' \ 'startut', re.I) rx_endut = re.compile(r'//[^#]*#' \ 'endut', re.I) rx_tl = re.compile(r'//[^#]*#' \ r'(?!isut)' \ r'(.*)', re.I) rx_comment = re.compile(r'//(.*)', re.I) class Checker: c_tested = 0 c_broken = 0 c_other = 0 def __init__(self, spath, options): self.spreadUT = False self.utDef = False self.isUT = False fname = os.path.splitext(os.path.basename(spath))[0] self.options = options self.compile(spath, fname) solution = self.parseUnitTest(spath) if self.isUT: self.check(fname, solution) Checker.c_tested += 1 else: Checker.c_other += 1 print def compile(self, spath, fname): print '> compiling %s' % fname, # call compiler via internal shell and redirect output to file pipes = os.popen3(r'%s %s > %s' % (self.options.compiler, spath, os.path.join(self.options.outdir, fname+'.asm'))) errs = pipes[2].read() print '.' if errs: print >>sys.stderr, errs print >>sys.stderr #sys.exit(1) def check(self, fname, solution): # call risc emu via internal shell and pipe output pipes = os.popen3(r'%s asm/%s.asm' % (self.options.risc, fname)) errs = pipes[2].read() if errs: print >>sys.stderr, errs print >>sys.stderr #sys.exit(2) answer = pipes[1].read() if solution == answer: print "> tested: ok" else: Checker.c_broken += 1 self.error(fname, solution, answer) def parseUnitTest(self, path): ret = [] for line in open(path): if rx_endut.search(line): #this is a file width a meta header: stop self.utDef = False elif self.utDef == True: #this is a file width a meta-header match = rx_comment.search(line) if match: f = match.group(1).replace(r'\n','\n') ret.append(f) elif rx_isut.search(line): #this is a file spread meta information self.spreadUT = True self.isUT = True elif rx_startut.search(line): #this is a file width a meta header: start self.utDef = True self.isUT = True else: match = rx_tl.search(line) if self.spreadUT and match: #replace newlines f = match.group(1).replace(r'\n','\n') ret.append(f) ret = "".join(ret) ret = ret.replace('\r', '') return ret def error(self, fname, expected, found): # error message for broken files expected = expected.split('\n') found = found.split('\n') print >>sys.stderr, "* '%s' broken: " % fname print >>sys.stderr, ("%-" + options.tabbing + "s %s") \ % ("expected", "found") for e, f in xzip(expected, found): print >>sys.stderr, ("%-" + options.tabbing + "s %s") % (repr(e), repr(f)) def xzip(l1, l2): for x in range(max(len(l1), len(l2))): e = '' f = '' if len(l1) > x: e = l1[x] if len(l2) > x: f = l2[x] yield e, f if __name__ == '__main__': optpar = OptionParser() optpar.add_option( "-c", "--compiler", dest="compiler", help="Command to execute the compiler, default: '%s'" % COMPILER, default=COMPILER, metavar="COMPILER") optpar.add_option( "-a", "--risc", dest="risc", help="Command to execute the risc emulator, default: '%s'" % RISC, default=RISC, metavar="RISC") optpar.add_option( "-o", "--outdir", dest="outdir", help="Path where to store compiled sources, default: '%s'" % OUTDIR, default=OUTDIR, metavar="OUTDIR") optpar.add_option( "-t", "--tabbing", dest="tabbing", help="Defines the indent of 'broken source file' messages"\ ", default: %s" % TABBING, default=TABBING, metavar="INT") optpar.add_option( "-d", "--doc", dest="doc", action="store_true", help="Show the doc.", default=False) (options, args) = optpar.parse_args() #create directory try: os.makedirs(options.outdir) except Exception, (errno, strerror): if not errno == 17: print >>sys.stderr, strerror sys.exit(errno) if options.doc: print __doc__ sys.exit(0) for source in args: Checker(source, options) print >>sys.stderr, "-"*20 print >>sys.stderr, " tested: %s" % Checker.c_tested print >>sys.stderr, " broken: %s" % Checker.c_broken print >>sys.stderr, "untested: %s" % Checker.c_other