diff --git a/unittest.py b/unittest.py index e7ac0c1..654c1c7 100644 --- a/unittest.py +++ b/unittest.py @@ -1,273 +1,276 @@ -# usage: python check.py --help -# -#@author: michael.karlen@epfl.ch -#@version: 1.0 -#@date: 27. 1. 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 - 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 - 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) - - return "".join(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") % (e, 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 +# 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 + 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 + 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) + if ret [-1] == "\r": + ret = ret[:-1] + 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