看到TB神牛写了个sh评测脚本,我就也写了一个python评测脚本。

通过这个脚本学了不少python知识,比如子进程控制、文件比对、异常处理。

这还只是个简单的实现,不能卡时、卡内存。不过我对卡时已经有想法了,不久就会写出来。

使用方法:

judge.py 评测数据目录 程序源码或程序

eg: judge.py ~/media/pet/ ~/src/pet.cpp

程序必须采用屏幕输入输出。

输入数据文件扩展名.in,输出扩展名.out或.ans。根据文件名,相同文件名的一组作为同一组数据。文件比对忽略空行。

支持的源代码(按扩展名判定):

C/C++ (c,cpp,cxx),Pascal (pas),Python (py),Ruby (rb),Shell脚本(sh),普通可执行文件(exe)

#!/usr/bin/python2
# -*- coding: utf-8 -*-

def PrintError(s, flexit=0 ):
	print '  (!!)',s
	if flexit:
		print '  (!!) 发生严重错误,评测中止'
		exit(1)


def gocompile (s):
	""" 编译源代码    s:源代码文件"""
	import subprocess
	sourceext={
		".c"	:	["gcc", "-o", "/tmp/temp.exe", "-O3"],
		".cpp"	:	["g++", "-o", "/tmp/temp.exe", "-O3"],
		".cxx"	:	["g++", "-o", "/tmp/temp.exe", "-O3"],
		".pas"	:	["fpc", "-o", "/tmp/temp.exe", "-O3"]
	}
	compilecmd = sourceext.get(os.path.splitext(s)[1])
	if compilecmd!=None:
		compilecmd.append(s)
		try:
			proc = subprocess.Popen(compilecmd, stdin = subprocess.PIPE, \
						stdout = subprocess.PIPE, stderr = subprocess.PIPE, \
						shell = False) 
		except OSError:
			PrintError(compilecmd[0]+" 编译器未找到", 1)
		
		ret = proc.wait()
		if ret==0:
			return "/tmp/temp.exe"
		else:
			PrintError("编译错误,请检查源代码", 1)
	else:
		return s


def gorun (s, fin):
	""" 运行程序    s:可执行文件 fin:输入文件"""
	exeext={
		".exe"	:	[],
		".sh"	:	["sh"],
		".py"	:	["python2"],
		".rb"	:	["ruby"]
	}
	import subprocess
	execmd = exeext.get(os.path.splitext(s)[1])
	if execmd!=None:
		execmd.append(s)
		try:
			proc = subprocess.Popen(execmd, stdin = subprocess.PIPE, \
					stdout = subprocess.PIPE, stderr = subprocess.PIPE, \
					shell = False) 
		except OSError:
			PrintError("未发现可执行文件", 1)

		try:
			proc.stdin.write(file(fin, "r").read())
		except IOError:
			PrintError("运行时错误: IOError")

		ret = proc.wait()
		if ret != 0:
			PrintError("运行时错误 %d" % ret)

		return proc.stdout
	else:
		PrintError("未发现可执行文件", 1)


def checkanswer(srcfile, basefile):
	""" 检查答案    srcfile:比较的文件 fin:基准文件"""
	import difflib
	s = difflib.SequenceMatcher( None , 
			filter(lambda x: not x in " \n\t", srcfile.read().split("\n")),
			filter(lambda x: not x in " \n\t", basefile.read().split("\n"))) 
	for tag, i1, i2, j1, j2 in s.get_opcodes():
		if tag!='equal':
			PrintError("输出对比不同") 
			print (tag, i1, i2, j1, j2)
			return 1
	return 0


def getfinout(s):
	""" 获取输入输出文件列表    s:搜索目录"""
	import glob
	finlist = glob.glob( s + os.sep + '*.in')
	flist = []
	for fin in finlist:
		basename = os.path.splitext(fin)[0]
		if os.path.isfile( basename + '.out'):
			flist.append((fin, os.path.splitext(fin)[0] + '.out'))
		elif os.path.isfile( basename + '.ans'):
			flist.append((fin, os.path.splitext(fin)[0] + '.ans'))
	return flist


if __name__ == '__main__':
	import sys, os

	if len(sys.argv)<3 or \
		(not os.path.isdir(sys.argv[1]) or not os.path.isfile(sys.argv[2])):
		PrintError("参数格式错误", 1)

	exefile = gocompile(sys.argv[2])
	count = 0
	score = 0
	for fin, fout in getfinout(sys.argv[1]):
		print "\n输入: %s\n输出: %s" % (fin, fout)
		print "  运行程序..."
		count+=1
		if checkanswer(gorun(exefile, fin), open(fout, "r")) == 0:
			print "  < 正确 >"
			score+=1
		else:
			print "  < 错误 >"

	print "\n\n评测结束!"
	if count>0:
		print "得分: %d (%d/%d)" % \
				(int(float(score)/float(count)*100), score, count)