diff test.py @ 49:245150080c48 1.20 1.20.3

Converted 1.20 into a branch
author Oleg Oshmyan <chortos@inbox.lv>
date Sun, 19 Dec 2010 23:23:24 +0200
parents 1.20/test.py@5bfa23cd638d
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test.py	Sun Dec 19 23:23:24 2010 +0200
@@ -0,0 +1,899 @@
+#! /usr/bin/python
+# Copyright (c) 2009, 2010 Chortos-2 <chortos@inbox.lv>
+
+import os, sys, shutil, time, subprocess, filecmp, optparse, signal, tempfile, tarfile, zipfile
+
+parser = optparse.OptionParser(version='test.py 1.20.3', usage='usage: %prog [options] [problem names] [[path/to/]solution-app] [test case numbers]\n\nTest case numbers can be specified in plain text or as a Python expression\nif there is only one positional argument.\n\nOnly problem names listed in testconf.py are recognized.')
+parser.add_option('-e', '--exclude', dest='exclude', action='append', help='test case number(s) to exclude, as a Python expression; multiple -e options can be supplied')
+parser.add_option('-c', '--cleanup', dest='clean', action='store_true', default=False, help='delete the copies of input/output files and exit')
+parser.add_option('-s', '--save-io', dest='erase', action='store_false', default=True, help='do not delete the copies of input/output files after the last test case; create copies of input files and store output in files even if the solution uses standard I/O; delete the stored input/output files if the solution uses standard I/O and the -c/--cleanup option is specified')
+parser.add_option('-m', '--copy-io', dest='copyonly', action='store_true', default=False, help='only create a copy of the input/output files of the last test case for manual testing; to delete them, use options -cs')
+parser.add_option('-x', '--auto-exit', dest='pause', action='store_false', default=True, help='do not wait for a key to be pressed when finished testing')
+parser.add_option('-p', '--python', action='store_true', default=False, help='always parse all positional arguments as a single Python expression (including the first argument even if it names an executable file)')
+parser.add_option('-t', '--detect-time', dest='autotime', action='store_true', default=False, help='spend a second detecting the most precise time measurement function')
+
+options, args = parser.parse_args()
+parser.destroy()
+del parser
+
+globals1 = set(globals())
+
+# Initialize some configuration variables with default values
+tasknames = ('.',)
+maxtime = 0
+tests = ()
+dummies = ()
+testsexcluded = ()
+padwithzeroestolength = 0
+taskweight = 100
+pointmap = {}
+stdio = False
+dummyinname = ''
+dummyoutname = ''
+tester = ''
+
+def exectestconf_helper(name):
+	if os.path.isfile('tests.tar'):
+		f = tarfile.open('tests.tar')
+		try:
+			exec f.extractfile(name).read() in globals()
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.zip'):
+		f = zipfile.ZipFile('tests.zip')
+		try:
+			exec f.open(name, 'rU').read() in globals()
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tgz'):
+		f = tarfile.open('tests.tgz')
+		try:
+			exec f.extractfile(name).read() in globals()
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tar.gz'):
+		f = tarfile.open('tests.tar.gz')
+		try:
+			exec f.extractfile(name).read() in globals()
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tbz2'):
+		f = tarfile.open('tests.tbz2')
+		try:
+			exec f.extractfile(name).read() in globals()
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tar.bz2'):
+		f = tarfile.open('tests.tar.bz2')
+		try:
+			exec f.extractfile(name).read() in globals()
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	return False
+
+try:
+	execfile('testconf.py')
+except IOError, error:
+	exc_info = sys.exc_info()[2]
+	try:
+		execfile('tests/testconf.py')
+	except IOError:
+		if not exectestconf_helper('testconf.py'):
+			raise IOError, (error.errno, 'The configuration file is missing', error.filename), exc_info
+	del exc_info
+
+globals2 = set(globals())
+globals2.remove('globals1')
+globals2 -= globals1
+del globals1
+
+shared = {}
+g = globals()
+for k in globals2:
+	shared[k] = g[k]
+
+newtasknames = []
+while len(args) and args[0] in tasknames:
+	newtasknames.append(args[0])
+	del args[0]
+if len(newtasknames):
+	tasknames = newtasknames
+
+scoresumoveralltasks = 0
+scoremaxoveralltasks = 0
+ntasks = 0
+nfulltasks = 0
+cwd = '' # At any time this is either '' or taskname + '/'
+
+if options.autotime:
+	c = time.clock()
+	time.sleep(1)
+	c = time.clock() - c
+	if int(c + .99999) == 1:
+		clock = time.clock
+	else:
+		clock = time.time
+elif os.name == 'nt':
+	clock = time.clock
+else:
+	clock = time.time
+
+if options.copyonly:
+	options.erase = False
+
+def existstestcase_helper(name):
+	if os.path.isfile('tests.tar'):
+		f = tarfile.open('tests.tar')
+		try:
+			f.getmember(name)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.zip'):
+		f = zipfile.ZipFile('tests.zip')
+		try:
+			f.getinfo(name)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tgz'):
+		f = tarfile.open('tests.tgz')
+		try:
+			f.getmember(name)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tar.gz'):
+		f = tarfile.open('tests.tar.gz')
+		try:
+			f.getmember(name)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tbz2'):
+		f = tarfile.open('tests.tbz2')
+		try:
+			f.getmember(name)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tar.bz2'):
+		f = tarfile.open('tests.tar.bz2')
+		try:
+			f.getmember(name)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	return False
+
+def existstestcase(name):
+	if os.path.isfile('tests/' + taskname + '/' + name) or os.path.isfile('tests/' + name):
+		return True
+	if cwd and (os.path.isfile(oldcwd + '/tests/' + cwd + name) or os.path.isfile(oldcwd + '/tests/' + name)):
+		return True
+	if existstestcase_helper(taskname + '/' + name) or existstestcase_helper(name):
+		return True
+	if cwd:
+		os.chdir(oldcwd)
+		if existstestcase_helper(cwd + name) or existstestcase_helper(name):
+			os.chdir(cwd)
+			return True
+		os.chdir(cwd)
+	return False
+
+def opentestcase_helper(name):
+	if os.path.isfile('tests.tar'):
+		f = tarfile.open('tests.tar')
+		try:
+			c = f.extractfile(name)
+			return c
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.zip'):
+		f = zipfile.ZipFile('tests.zip')
+		try:
+			c = f.open(name, 'rU')
+			f.close()
+			return c
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tgz'):
+		f = tarfile.open('tests.tgz')
+		try:
+			c = f.extractfile(name)
+			return c
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tar.gz'):
+		f = tarfile.open('tests.tar.gz')
+		try:
+			c = f.extractfile(name)
+			return c
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tbz2'):
+		f = tarfile.open('tests.tbz2')
+		try:
+			c = f.extractfile(name)
+			return c
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tar.bz2'):
+		f = tarfile.open('tests.tar.bz2')
+		try:
+			c = f.extractfile(name)
+			return c
+		except KeyError:
+			f.close()
+	return None
+
+def opentestcase(name):
+	if os.path.isfile('tests/' + taskname + '/' + name):
+		return open('tests/' + taskname + '/' + name, 'rU')
+	elif os.path.isfile('tests/' + name):
+		return open('tests/' + name, 'rU')
+	f = opentestcase_helper(taskname + '/' + name)
+	if not f:
+		f = opentestcase_helper(name)
+	if f:
+		return f
+	if cwd:
+		if os.path.isfile(oldcwd + '/tests/' + cwd + name):
+			return open(oldcwd + '/tests/' + cwd + name, 'rU')
+		elif os.path.isfile(oldcwd + '/tests/' + name):
+			return open(oldcwd + '/tests/' + name, 'rU')
+		os.chdir(oldcwd)
+		f = opentestcase_helper(cwd + name)
+		if not f:
+			f = opentestcase_helper(name)
+		os.chdir(cwd)
+		if f:
+			return f
+	raise KeyError, 'The test-case-defining file \'' + name + '\' cannot be found'
+
+def copytestcase_helper(name, target):
+	if os.path.isfile('tests.tar'):
+		f = tarfile.open('tests.tar')
+		try:
+			m = f.getmember(name)
+			m.name = target
+			f.extract(m)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.zip'):
+		if not target.startswith('/'):
+			f = zipfile.ZipFile('tests.zip')
+			try:
+				m = f.getinfo(name)
+				m.filename = target
+				f.extract(m)
+				f.close()
+				return True
+			except KeyError:
+				f.close()
+		else:
+			oldcwd = os.getcwdu()
+			os.chdir('/')
+			f = zipfile.ZipFile(oldcwd + '/tests.zip')
+			try:
+				m = f.getinfo(name)
+				m.filename = os.path.relpath(target)
+				f.extract(m)
+				f.close()
+				os.chdir(oldcwd)
+				return True
+			except KeyError:
+				f.close()
+				os.chdir(oldcwd)
+	if os.path.isfile('tests.tgz'):
+		f = tarfile.open('tests.tgz')
+		try:
+			m = f.getmember(name)
+			m.name = target
+			f.extract(m)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tar.gz'):
+		f = tarfile.open('tests.tar.gz')
+		try:
+			m = f.getmember(name)
+			m.name = target
+			f.extract(m)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tbz2'):
+		f = tarfile.open('tests.tbz2')
+		try:
+			m = f.getmember(name)
+			m.name = target
+			f.extract(m)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	if os.path.isfile('tests.tar.bz2'):
+		f = tarfile.open('tests.tar.bz2')
+		try:
+			m = f.getmember(name)
+			m.name = target
+			f.extract(m)
+			f.close()
+			return True
+		except KeyError:
+			f.close()
+	return False
+
+def copytestcase(name, target):
+	if os.path.isfile('tests/' + taskname + '/' + name):
+		shutil.copyfile('tests/' + taskname + '/' + name, target)
+		return
+	elif os.path.isfile('tests/' + name):
+		shutil.copyfile('tests/' + name, target)
+		return
+	if copytestcase_helper(taskname + '/' + name, target) or copytestcase_helper(name, target):
+		return
+	if cwd:
+		if os.path.isfile(oldcwd + '/tests/' + cwd + name):
+			shutil.copyfile(oldcwd + '/tests/' + cwd + name, target)
+			return
+		elif os.path.isfile(oldcwd + '/tests/' + name):
+			shutil.copyfile(oldcwd + '/tests/' + name, target)
+			return
+		os.chdir(oldcwd)
+		if copytestcase_helper(cwd + name, target) or copytestcase_helper(name, target):
+			os.chdir(cwd)
+			return
+		os.chdir(cwd)
+	raise KeyError, 'The test-case-defining file \'' + name + '\' cannot be found'
+
+# Always chdir if the directory exists but use any existing config
+def chdir_and_exec_testconf():
+	global cwd
+	cwd = ''
+	if os.path.isdir(taskname):
+		os.chdir(taskname)
+		if taskname != '.':
+			cwd = taskname + '/'
+		try:
+			execfile('testconf.py', globals())
+			return
+		except IOError:
+			pass
+	if not cwd:
+		if os.path.isfile('tests/' + taskname + '/testconf.py'):
+			execfile('tests/' + taskname + '/testconf.py', globals())
+			return
+		if os.path.isfile('tests/testconf.py'):
+			execfile('tests/testconf.py', globals())
+			return
+	if exectestconf_helper(taskname + '/testconf.py') or exectestconf_helper('testconf.py'):
+		return
+	if cwd:
+		os.chdir(oldcwd)
+		if os.path.isfile('tests/' + cwd + 'testconf.py'):
+			execfile('tests/' + cwd + 'testconf.py', globals())
+			os.chdir(cwd)
+			return
+		if os.path.isfile('tests/testconf.py'):
+			execfile('tests/testconf.py', globals())
+			os.chdir(cwd)
+			return
+		if exectestconf_helper(cwd + 'testconf.py') or exectestconf_helper('testconf.py'):
+			os.chdir(cwd)
+			return
+		if os.path.isfile('testconf.py'):
+			execfile('testconf.py', globals())
+			os.chdir(cwd)
+			return
+		os.chdir(cwd)
+	elif os.path.isfile('testconf.py'):
+		execfile('testconf.py', globals())
+		return
+	raise KeyError, 'The configuration file for task ' + taskname + ' is missing'
+
+try:
+	name
+	namedefined = True
+except Exception:
+	namedefined = False
+
+for taskname in tasknames:
+	if ntasks:
+		print
+	
+	try:
+		if len(tasknames) > 1:
+			print taskname
+	except Exception:
+		if taskname != '.' or ntasks:
+			print taskname
+	
+	try: del inname
+	except NameError: pass
+	try: del outname
+	except NameError: pass
+	try: del ansname
+	except NameError: pass
+	
+	if not namedefined and taskname != '.':
+		name = './' + taskname
+	for k in shared:
+		g[k] = shared[k]
+	
+	oldcwd = os.getcwdu()
+	chdir_and_exec_testconf()
+	
+	if options.clean:
+		try:
+			if not stdio or tester:
+				if not tester:
+					inname
+				outname
+			if tester:
+				ansname
+		except NameError, error:
+			raise NameError, 'configuration ' + str(error).replace('name ', 'variable ', 1), sys.exc_info()[2]
+		if not options.erase:
+			try:
+				inname = inname.replace('%', taskname)
+			except NameError:
+				inname = taskname + '.in'
+			try:
+				outname = outname.replace('%', taskname)
+			except NameError:
+				outname = taskname + '.out'
+			try:
+				ansname = ansname.replace('%', taskname)
+			except NameError:
+				ansname = taskname + '.ans'
+		else:
+			inname = inname.replace('%', taskname)
+			outname = outname.replace('%', taskname)
+			if tester:
+				ansname = ansname.replace('%', taskname)
+		if not stdio or tester or not options.erase:
+			if os.path.exists(inname): os.remove(inname)
+			if os.path.exists(outname): os.remove(outname)
+		if (tester or not options.erase) and ansname:
+			if os.path.exists(ansname): os.remove(ansname)
+		continue
+	
+	try:
+		name
+	except NameError, error:
+		if str(error).count('name') == 1:
+			raise NameError, 'configuration ' + str(error), sys.exc_info()[2]
+		else:
+			raise NameError, 'configuration ' + str(error).replace('name ', 'variable ', 1), sys.exc_info()[2]
+	
+	try:
+		if not stdio:
+			inname
+			outname
+		testcaseinname
+		if tester:
+			outname
+			if ansname:
+				testcaseoutname
+		else:
+			testcaseoutname
+	except NameError, error:
+		raise NameError, 'configuration ' + str(error).replace('name ', 'variable ', 1), sys.exc_info()[2]
+	
+	if not options.erase:
+		try:
+			inname
+		except NameError:
+			inname = taskname + '.in'
+		try:
+			outname
+		except NameError:
+			outname = taskname + '.out'
+		try:
+			ansname
+		except NameError:
+			ansname = taskname + '.ans'
+	
+	if options.pause:
+		try:
+			pause
+		except NameError, error:
+			if os.name == 'posix':
+				pause = 'read -s -n 1'
+				print 'Configuration ' + str(error).replace('name ', 'variable ') + '; it was devised automatically but the choice might be incorrect, so test.py might exit immediately after the testing is complete.'
+			elif os.name == 'nt':
+				pause = 'pause'
+			else:
+				raise NameError, 'configuration ' + str(error).replace('name ', 'variable ') + ' and cannot be devised automatically', sys.exc_info()[2]
+	
+	if not dummyinname:
+		dummyinname = testcaseinname
+	if not dummyoutname and (not tester or ansname):
+		dummyoutname = testcaseoutname
+	
+	dummyinname = dummyinname.replace('%', taskname)
+	dummyoutname = dummyoutname.replace('%', taskname)
+	testcaseinname = testcaseinname.replace('%', taskname)
+	if not stdio or not options.erase:
+		inname = inname.replace('%', taskname)
+		outname = outname.replace('%', taskname)
+		try:
+			ansname = ansname.replace('%', taskname)
+		except NameError:
+			pass
+	if tester:
+		try: inname = inname.replace('%', taskname)
+		except NameError: pass
+		outname = outname.replace('%', taskname)
+		if ansname:
+			ansname = ansname.replace('%', taskname)
+			testcaseoutname = testcaseoutname.replace('%', taskname)
+	else:
+		testcaseoutname = testcaseoutname.replace('%', taskname)
+	
+	if isinstance(padwithzeroestolength, tuple):
+		padwithzeroestolength, paddummieswithzeroestolength = padwithzeroestolength
+	else:
+		paddummieswithzeroestolength = padwithzeroestolength
+	
+	if options.python:
+		dummies = ()
+		s = ' '.join(args)
+		tests = eval(s)
+		try:
+			tests.__iter__
+		except AttributeError:
+			tests = (tests,)
+	elif len(args):
+		if os.path.exists(args[0]):
+			name = args[0]
+			del args[0]
+		if len(args) > 1:
+			dummies = ()
+			tests = args
+		elif len(args):
+			dummies = ()
+			s = args[0]
+			if len(s) < padwithzeroestolength:
+				s = s.zfill(padwithzeroestolength)
+			if existstestcase(testcaseinname.replace('$', s)):
+				tests = (s,)
+			else:
+				try:
+					tests = eval(args[0])
+					try:
+						tests.__iter__
+					except AttributeError:
+						tests = (tests,)
+				except Exception:
+					tests = (s,)
+	
+	if options.exclude:
+		testsexcluded = []
+		for i in options.exclude:
+			v = eval(i)
+			try:
+				testsexcluded.extend(v)
+			except TypeError:
+				testsexcluded.append(v)
+	
+	# Windows doesn't like paths beginning with .\ and not ending with an extension
+	name = os.path.normcase(name)
+	if name.startswith('.\\'):
+		name = name[2:]
+	
+	newpointmap = {}
+	
+	for i in pointmap:
+		try:
+			for j in i:
+				newpointmap[j] = pointmap[i]
+		except TypeError:
+			newpointmap[i] = pointmap[i]
+	
+	pointmap = newpointmap
+	
+	if maxtime > 0:
+		strmaxtime = '/%.3f' % maxtime
+	else:
+		strmaxtime = ''
+	
+	padoutputtolength = 0
+	ntests = []
+	
+	for j in dummies:
+		try:
+			j.__iter__
+		except AttributeError:
+			j = (j,)
+		ntests.append((j, True))
+		for i in j:
+			s = str(i)
+			if len(s) < paddummieswithzeroestolength:
+				s = s.zfill(paddummieswithzeroestolength)
+			s = 'sample ' + s
+			if padoutputtolength < len(s):
+				padoutputtolength = len(s)
+	
+	for j in tests:
+		try:
+			j.__iter__
+		except AttributeError:
+			j = (j,)
+		ntests.append((j, False))
+		for i in j:
+			s = str(i)
+			if len(s) < padwithzeroestolength:
+				s = s.zfill(padwithzeroestolength)
+			if padoutputtolength < len(s):
+				padoutputtolength = len(s)
+	
+	tests = ntests
+	score = maxpoints = ncorrect = ntotal = ncorrectvalued = nvalued = 0
+	
+	if options.copyonly:
+		j, isdummy = tests[-1]
+		if isdummy:
+			realinname = dummyinname
+			realoutname = dummyoutname
+		else:
+			realinname = testcaseinname
+			realoutname = testcaseoutname
+		for i in j:
+			if i in testsexcluded and not isdummy:
+				continue
+			s = str(i)
+			if isdummy:
+				if len(s) < paddummieswithzeroestolength:
+					s = s.zfill(paddummieswithzeroestolength)
+			else:
+				if len(s) < padwithzeroestolength:
+					s = s.zfill(padwithzeroestolength)
+			copytestcase(realinname.replace('$', s), inname)
+			if ansname:
+				copytestcase(realoutname.replace('$', s), ansname)
+		continue
+	
+	for j, isdummy in tests:
+		ncorrectgrp = 0
+		ntotalgrp = 0
+		scoregrp = 0
+		maxpointsgrp = 0
+		if isdummy:
+			realinname = dummyinname
+			realoutname = dummyoutname
+		else:
+			realinname = testcaseinname
+			realoutname = testcaseoutname
+		for i in j:
+			if i in testsexcluded and not isdummy:
+				continue
+			ntotalgrp += 1
+			s = str(i)
+			if isdummy:
+				npoints = 0
+				if len(s) < paddummieswithzeroestolength:
+					s = s.zfill(paddummieswithzeroestolength)
+				spref = 'sample '
+			else:
+				npoints = pointmap.get(None, 1)
+				npoints = pointmap.get(i, npoints)
+				maxpointsgrp += npoints
+				if npoints:
+					nvalued += 1
+				if len(s) < padwithzeroestolength:
+					s = s.zfill(padwithzeroestolength)
+				spref = ''
+			print ' ' * (padoutputtolength - len(spref + s)) + spref + s + ':',
+			sys.stdout.flush()
+			outputdata = open(os.devnull, 'w')
+			if stdio:
+				f = tempfile.NamedTemporaryFile(delete=False)
+				inputdatafname = f.name
+				f.close()
+				copytestcase(realinname.replace('$', s), inputdatafname)
+				inputdata = open(inputdatafname, 'rU')
+				if options.erase:
+					tempoutput = tempfile.TemporaryFile('w+')
+				else:
+					tempoutput = open(outname, 'w+')
+				try:
+					proc = subprocess.Popen(name, stdin=inputdata, stdout=tempoutput, stderr=outputdata, universal_newlines=True)
+				except OSError, error:
+					raise OSError, 'The program to be tested cannot be launched: ' + str(error), sys.exc_info()[2]
+			else:
+				if os.path.exists(outname):
+					os.remove(outname)
+				copytestcase(realinname.replace('$', s), inname)
+				try:
+					proc = subprocess.Popen(name, stdin=outputdata, stdout=outputdata, stderr=outputdata, universal_newlines=True)
+				except OSError, error:
+					raise OSError, 'The program to be tested cannot be launched: ' + str(error), sys.exc_info()[2]
+			cl = clock()
+			if maxtime > 0:
+				while 1:
+					proc.poll()
+					elapsed = clock() - cl
+					if proc.returncode == None:
+						if elapsed >= maxtime:
+							print '%.3f%s s, 0/%d, time limit exceeded' % (elapsed, strmaxtime, npoints)
+							sys.stdout.flush()
+							while proc.returncode == None:
+								try:
+									proc.terminate()
+								except OSError:
+									pass
+								except AttributeError:
+									try:
+										os.kill(proc.pid, signal.SIGTERM)
+									except Exception:
+										pass
+								proc.poll()
+							outputdata.close()
+							if stdio:
+								tempoutput.close()
+							break
+					else:
+						print '%.3f%s s,' % (elapsed, strmaxtime),
+						sys.stdout.flush()
+						elapsed = 0
+						if stdio:
+							tempoutput.seek(0)
+							lines = tempoutput.readlines()
+							tempoutput.close()
+						break
+				if elapsed >= maxtime:
+					continue
+			else:
+				data = proc.communicate()
+				elapsed = clock() - cl
+				print '%.3f%s s,' % (elapsed, strmaxtime),
+				sys.stdout.flush()
+				if stdio:
+					tempoutput.seek(0)
+					lines = tempoutput.readlines()
+					tempoutput.close()
+			outputdata.close()
+			if stdio:
+				inputdata.close()
+				try:
+					os.unlink(inputdatafname)
+				except Exception:
+					pass
+			if proc.returncode > 0:
+				print '0/%d, non-zero return code %d' % (npoints, proc.returncode)
+				sys.stdout.flush()
+			elif proc.returncode < 0:
+				print '0/%d, terminated by signal %d' % (npoints, -proc.returncode)
+				sys.stdout.flush()
+			else:
+				if not tester:
+					if stdio:
+						outputdata = opentestcase(realoutname.replace('$', s))
+						r = 0
+						data = outputdata.read().splitlines(True)
+						if len(lines) != len(data):
+							r = 1
+						else:
+							for i in zip(lines, data):
+								if i[0] != i[1]:
+									r = 1
+									break
+						outputdata.close()
+					else:
+						try:
+							inputdata = open(outname, 'rU')
+						except IOError:
+							print '0/%g, output file not created or not readable' % npoints
+							sys.stdout.flush()
+							r = None
+						else:
+							outputdata = opentestcase(realoutname.replace('$', s))
+							r = 0
+							lines = inputdata.readlines()
+							data = outputdata.read().splitlines(True)
+							if len(lines) != len(data):
+								r = 1
+							else:
+								for i in zip(lines, data):
+									if i[0] != i[1]:
+										r = 1
+										break
+							inputdata.close()
+							outputdata.close()
+				else:
+					if ansname:
+						copytestcase(realoutname.replace('$', s), ansname)
+					if stdio:
+						try: copytestcase(realinname.replace('$', s), inname)
+						except NameError: pass
+						outputdata = open(outname, 'w')
+						outputdata.writelines(lines)
+						outputdata.close()
+					try:
+						proc = subprocess.Popen(tester, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
+					except OSError, error:
+						raise OSError, 'The tester application cannot be launched: ' + str(error), sys.exc_info()[2]
+					data = proc.communicate()
+					r = proc.returncode
+				if tester and data[0]:
+					data = ''.join((' (', data[0].strip(), ')'))
+				else:
+					data = ''
+				if r:
+					print '0/%g, wrong answer%s' % (npoints, data)
+					sys.stdout.flush()
+				elif r == 0:
+					print '%g/%g, OK%s' % (npoints, npoints, data)
+					sys.stdout.flush()
+					scoregrp += npoints
+					ncorrectgrp += 1
+					if npoints:
+						ncorrectvalued += 1
+		if ntotalgrp:
+			if scoregrp < maxpointsgrp:
+				scoregrp = 0
+			if ntotalgrp > 1:
+				print 'Group total: %d/%d tests; %g/%g points' % (ncorrectgrp, ntotalgrp, scoregrp, maxpointsgrp)
+				sys.stdout.flush()
+			ncorrect += ncorrectgrp
+			ntotal += ntotalgrp
+			score += scoregrp
+			maxpoints += maxpointsgrp
+	
+	if options.erase:
+		if not stdio or tester:
+			if os.path.exists(inname): os.remove(inname)
+			if os.path.exists(outname): os.remove(outname)
+		if tester and ansname:
+			if os.path.exists(ansname): os.remove(ansname)
+	elif stdio:
+		copytestcase(realinname.replace('$', s), inname)
+		copytestcase(realoutname.replace('$', s), ansname)
+	if nvalued != ntotal:
+		print 'Grand total: %d/%d tests (%d/%d valued); %g/%g points; weighted score: %g/%g' % (ncorrect, ntotal, ncorrectvalued, nvalued, score, maxpoints, (score*taskweight/maxpoints if not score*taskweight%maxpoints else float(score*taskweight)/maxpoints) if maxpoints else 0, taskweight)
+	else:
+		print 'Grand total: %d/%d tests; %g/%g points; weighted score: %g/%g' % (ncorrect, ntotal, score, maxpoints, (score*taskweight/maxpoints if not score*taskweight%maxpoints else float(score*taskweight)/maxpoints) if maxpoints else 0, taskweight)
+	
+	scoresumoveralltasks += (score*taskweight/maxpoints if not score*taskweight%maxpoints else float(score*taskweight)/maxpoints) if maxpoints else 0
+	scoremaxoveralltasks += taskweight
+	ntasks += 1
+	nfulltasks += int((score == maxpoints) if maxpoints else (taskweight == 0))
+	
+	os.chdir(oldcwd)
+
+if options.clean or options.copyonly:
+	sys.exit()
+
+if ntasks != 1:
+	print
+	print 'Grand grand total: %g/%g weighted points; %d/%d problems solved fully' % (scoresumoveralltasks, scoremaxoveralltasks, nfulltasks, ntasks)
+
+if options.pause:
+	print 'Press any key to exit... ',
+	sys.stdout.flush()
+	os.system(pause + ' >' + os.devnull)