Newer
Older
zweic / unittest.py
@glproj03 glproj03 on 6 Feb 2006 6 KB Added impossible test case
# 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