changeset 45:5afefe51dcdc

Initial rename to Upreckon Bonus: eliminated the last remaining mentions of Subversion.
author Oleg Oshmyan <chortos@inbox.lv>
date Sun, 19 Dec 2010 19:34:20 +0200
parents e92e6d4aa2ef
children ba5182222c34
files 2.00/publish.sh 2.00/test-svn.py 2.00/test.py.sublime-project 2.00/upreckon-vcs 2.00/upreckon.sublime-project publish.sh test-svn.py test-vcs.py
diffstat 8 files changed, 1147 insertions(+), 1135 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2.00/publish.sh	Sun Dec 19 19:34:20 2010 +0200
@@ -0,0 +1,11 @@
+#! /bin/sh
+
+VERSION=`hg summary | grep '^parent' | sed -En 's/^parent: [0-9]*:([0-9a-f]+) .*$/\1/p'`
+if [ -z "$VERSION" ]
+then
+	echo The current Mercurial changeset could not be determined. >&2
+	exit 1
+fi
+
+sed 's/$$REV$\$/hg '"$VERSION/" upreckon-vcs >upreckon
+chmod +x upreckon
\ No newline at end of file
--- a/2.00/test-svn.py	Thu Dec 09 19:05:06 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-#! /usr/bin/env python
-# Copyright (c) 2009-2010 Chortos-2 <chortos@inbox.lv>
-
-from __future__ import division, with_statement
-import optparse, sys, compat
-
-def import_error(e):
-	say('Error: your installation of test.py is incomplete;', str(e).lower() + '.', file=sys.stderr)
-	sys.exit(3)
-
-from compat import *
-
-version = '2.00.0 ($$REV$$)'
-parser = optparse.OptionParser(version='test.py '+version, epilog='Python 2.5 or newer is required.')
-parser.add_option('-1', dest='legacy', action='store_true', default=False, help='handle configuration files in a way more compatible with test.py 1.x')
-parser.add_option('-u', '--update', dest='update', action='store_true', default=False, help='update the installed test.py to the latest publicly available version')
-parser.add_option('-p', '--problem', dest='problems', metavar='PROBLEM', action='append', help='test only the PROBLEM (this option can be specified more than once with different problem names, all of which will be tested)')
-parser.add_option('-m', '--copy-io', dest='copyonly', action='store_true', default=False, help='create a copy of the input/output files of the last test case for manual testing and exit')
-parser.add_option('-x', '--auto-exit', dest='pause', action='store_false', default=True, help='do not wait for a key to be pressed after finishing testing')
-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('-t', '--detect-time', dest='autotime', action='store_true', default=False, help='spend a second detecting the most precise time measurement function')
-parser.add_option('--no-time-limits', dest='no_maxtime', action='store_true', default=False, help='disable all time limits')
-
-options, args = parser.parse_args()
-parser.destroy()
-del parser
-
-if options.update:
-	try:
-		urllib, urlread = compat.import_urllib()
-	except ImportError:
-		sys.exit('Error: the urllib Python module is missing. Without it, an automatic update is impossible.')
-	
-	latesttext = urlread('http://chortos.selfip.net/~astiob/test.py/version.txt')
-	latest = latesttext.split('.')
-	installed = version.split('.')
-	update = None
-	
-	if latest[0] > installed[0]:
-		update = 'major'
-	elif latest[0] == installed[0]:
-		if latest[1] > installed[1]:
-			update = 'feature'
-		elif latest[1] == installed[1]:
-			if latest[2] > installed[2]:
-				update = 'bug-fixing'
-			elif latest[2] == installed[2]:
-				say('You are using the latest publicly available version of test.py.')
-				sys.exit()
-	
-	if not update:
-		say('Your copy of test.py is newer than the publicly available version.')
-		sys.exit()
-	
-	say('A ' + update + ' update to test.py is available. Downloading...')
-	sys.stdout.flush()
-	urllib.urlretrieve('http://chortos.selfip.net/~astiob/test.py/test.py', sys.argv[0])
-	say('Downloaded and installed. Now you are using test.py ' + latesttext + '.')
-	sys.exit()
-
-import config, itertools, os, subprocess, sys, time
-
-if options.autotime:
-	# This is really a dirty hack that assumes that sleep() does not spend
-	# the CPU time of the current process and that if clock() measures
-	# wall-clock time, then it is more precise than time() is. Both these
-	# assumptions are true on all platforms I have tested this on so far,
-	# but I am not aware of any guarantee that they will both be true
-	# on every other platform.
-	c = time.clock()
-	time.sleep(1)
-	c = time.clock() - c
-	if int(c + .5) == 1:
-		clock = time.clock
-	else:
-		clock = time.time
-elif sys.platform == 'win32':
-	clock = time.clock
-else:
-	clock = time.time
-
-try:
-	from testcases import pause
-except ImportError:
-	pause = None
-
-try:
-	globalconf = config.load_global()
-
-	# Do this check here so that if we have to warn them, we do it as early as possible
-	if options.pause and not pause and not hasattr(globalconf, 'pause'):
-		if os.name == 'posix':
-			globalconf.pause = 'read -s -n 1'
-			say('Warning: configuration variable pause is not defined; it was devised automatically but the choice might be incorrect, so test.py might exit immediately after the testing is completed.', file=sys.stderr)
-			sys.stderr.flush()
-		elif os.name == 'nt':
-			globalconf.pause = 'pause'
-		else:
-			sys.exit('Error: configuration variable pause is not defined and cannot be devised automatically.')
-
-	try:
-		from problem import *
-	except ImportError:
-		import_error(sys.exc_info()[1])
-
-	# Support single-problem configurations
-	if globalconf.tasknames is None:
-		shouldprintnames = False
-		globalconf.multiproblem = False
-		globalconf.tasknames = os.path.curdir,
-	else:
-		globalconf.multiproblem = True
-		shouldprintnames = True
-
-	ntasks = 0
-	nfulltasks = 0
-	maxscore = 0
-	realscore = 0
-
-	for taskname in (globalconf.tasknames if not options.problems else options.problems):
-		problem = Problem(taskname)
-		
-		if ntasks and not options.copyonly: say()
-		if shouldprintnames: say(taskname)
-		
-		if options.copyonly:
-			problem.copytestdata()
-		else:
-			real, max = problem.test()
-		
-		ntasks += 1
-		nfulltasks += real == max
-		realscore += real
-		maxscore += max
-
-	if options.copyonly:
-		sys.exit()
-
-	if ntasks != 1:
-		say()
-		say('Grand grand total: %g/%g weighted points; %d/%d problems solved fully' % (realscore, maxscore, nfulltasks, ntasks))
-except KeyboardInterrupt:
-	sys.exit('Exiting due to a keyboard interrupt.')
-
-if options.pause:
-	say('Press any key to exit...')
-	sys.stdout.flush()
-	
-	if pause:
-		pause()
-	elif callable(globalconf.pause):
-		globalconf.pause()
-	else:
-		with open(os.devnull, 'w') as devnull:
-			subprocess.call(globalconf.pause, stdout=devnull, stderr=subprocess.STDOUT)
\ No newline at end of file
--- a/2.00/test.py.sublime-project	Thu Dec 09 19:05:06 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-<!-- Documentation is available at http://www.sublimetext.com/docs/projects -->
-<project>
-	<mount name="2.00" dir="." exclude="*.png,*.jpg,*.exe,*.dll,*.obj,*.pyc" direxclude=".svn,.git,.hg,CVS,bin,*"/>
-	<!-- <mount name="1.21" dir=".." include="*.py" direxclude="*"/> -->
-	<options>
-<![CDATA[
-buildFile Packages/C++/Make.sublime-build
-]]>
-	</options>
-</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2.00/upreckon-vcs	Sun Dec 19 19:34:20 2010 +0200
@@ -0,0 +1,156 @@
+#! /usr/bin/env python
+# Copyright (c) 2009-2010 Chortos-2 <chortos@inbox.lv>
+
+from __future__ import division, with_statement
+import optparse, sys, compat
+
+def import_error(e):
+	say('Error: your installation of Upreckon is incomplete;', str(e).lower() + '.', file=sys.stderr)
+	sys.exit(3)
+
+from compat import *
+
+version = '2.00.0 ($$REV$$)'
+parser = optparse.OptionParser(version='Upreckon '+version, epilog='Python 2.5 or newer is required.')
+parser.add_option('-1', dest='legacy', action='store_true', default=False, help='handle configuration files in a way more compatible with test.py 1.x')
+parser.add_option('-u', '--update', dest='update', action='store_true', default=False, help='update the installed Upreckon to the latest publicly available version')
+parser.add_option('-p', '--problem', dest='problems', metavar='PROBLEM', action='append', help='test only the PROBLEM (this option can be specified more than once with different problem names, all of which will be tested)')
+parser.add_option('-m', '--copy-io', dest='copyonly', action='store_true', default=False, help='create a copy of the input/output files of the last test case for manual testing and exit')
+parser.add_option('-x', '--auto-exit', dest='pause', action='store_false', default=True, help='do not wait for a key to be pressed after finishing testing')
+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('-t', '--detect-time', dest='autotime', action='store_true', default=False, help='spend a second detecting the most precise time measurement function')
+parser.add_option('--no-time-limits', dest='no_maxtime', action='store_true', default=False, help='disable all time limits')
+
+options, args = parser.parse_args()
+parser.destroy()
+del parser
+
+if options.update:
+	try:
+		urllib, urlread = compat.import_urllib()
+	except ImportError:
+		sys.exit('Error: the urllib Python module is missing. Without it, an automatic update is impossible.')
+	
+	latesttext = urlread('http://chortos.selfip.net/~astiob/test.py/version.txt')
+	latest = latesttext.split('.')
+	installed = version.split('.')
+	update = None
+	
+	if latest[0] > installed[0]:
+		update = 'major'
+	elif latest[0] == installed[0]:
+		if latest[1] > installed[1]:
+			update = 'feature'
+		elif latest[1] == installed[1]:
+			if latest[2] > installed[2]:
+				update = 'bug-fixing'
+			elif latest[2] == installed[2]:
+				say('You are using the latest publicly available version of Upreckon.')
+				sys.exit()
+	
+	if not update:
+		say('Your copy of Upreckon is newer than the publicly available version.')
+		sys.exit()
+	
+	say('A ' + update + ' update to Upreckon is available. Downloading...')
+	sys.stdout.flush()
+	# FIXME: need to update all files!
+	urllib.urlretrieve('http://chortos.selfip.net/~astiob/test.py/test.py', sys.argv[0])
+	say('Downloaded and installed. Now you are using Upreckon ' + latesttext + '.')
+	sys.exit()
+
+import config, itertools, os, subprocess, sys, time
+
+if options.autotime:
+	# This is really a dirty hack that assumes that sleep() does not spend
+	# the CPU time of the current process and that if clock() measures
+	# wall-clock time, then it is more precise than time() is. Both these
+	# assumptions are true on all platforms I have tested this on so far,
+	# but I am not aware of any guarantee that they will both be true
+	# on every other platform.
+	c = time.clock()
+	time.sleep(1)
+	c = time.clock() - c
+	if int(c + .5) == 1:
+		clock = time.clock
+	else:
+		clock = time.time
+elif sys.platform == 'win32':
+	clock = time.clock
+else:
+	clock = time.time
+
+try:
+	from testcases import pause
+except ImportError:
+	pause = None
+
+try:
+	globalconf = config.load_global()
+
+	# Do this check here so that if we have to warn them, we do it as early as possible
+	if options.pause and not pause and not hasattr(globalconf, 'pause'):
+		if os.name == 'posix':
+			globalconf.pause = 'read -s -n 1'
+			say('Warning: configuration variable pause is not defined; it was devised automatically but the choice might be incorrect, so Upreckon might exit immediately after the testing is completed.', file=sys.stderr)
+			sys.stderr.flush()
+		elif os.name == 'nt':
+			globalconf.pause = 'pause'
+		else:
+			sys.exit('Error: configuration variable pause is not defined and cannot be devised automatically.')
+
+	try:
+		from problem import *
+	except ImportError:
+		import_error(sys.exc_info()[1])
+
+	# Support single-problem configurations
+	if globalconf.tasknames is None:
+		shouldprintnames = False
+		globalconf.multiproblem = False
+		globalconf.tasknames = os.path.curdir,
+	else:
+		globalconf.multiproblem = True
+		shouldprintnames = True
+
+	ntasks = 0
+	nfulltasks = 0
+	maxscore = 0
+	realscore = 0
+
+	for taskname in (globalconf.tasknames if not options.problems else options.problems):
+		problem = Problem(taskname)
+		
+		if ntasks and not options.copyonly: say()
+		if shouldprintnames: say(taskname)
+		
+		if options.copyonly:
+			problem.copytestdata()
+		else:
+			real, max = problem.test()
+		
+		ntasks += 1
+		nfulltasks += real == max
+		realscore += real
+		maxscore += max
+
+	if options.copyonly:
+		sys.exit()
+
+	if ntasks != 1:
+		say()
+		say('Grand grand total: %g/%g weighted points; %d/%d problems solved fully' % (realscore, maxscore, nfulltasks, ntasks))
+except KeyboardInterrupt:
+	sys.exit('Exiting due to a keyboard interrupt.')
+
+if options.pause:
+	say('Press any key to exit...')
+	sys.stdout.flush()
+	
+	if pause:
+		pause()
+	elif callable(globalconf.pause):
+		globalconf.pause()
+	else:
+		with open(os.devnull, 'w') as devnull:
+			subprocess.call(globalconf.pause, stdout=devnull, stderr=subprocess.STDOUT)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/2.00/upreckon.sublime-project	Sun Dec 19 19:34:20 2010 +0200
@@ -0,0 +1,10 @@
+<!-- Documentation is available at http://www.sublimetext.com/docs/projects -->
+<project>
+	<mount name="2.00" dir="." exclude="*.png,*.jpg,*.exe,*.dll,*.obj,*.pyc" direxclude=".svn,.git,.hg,CVS,bin,*"/>
+	<!-- <mount name="1.21" dir=".." include="*.py" direxclude="*"/> -->
+	<options>
+<![CDATA[
+buildFile Packages/C++/Make.sublime-build
+]]>
+	</options>
+</project>
--- a/publish.sh	Thu Dec 09 19:05:06 2010 +0200
+++ b/publish.sh	Sun Dec 19 19:34:20 2010 +0200
@@ -7,5 +7,5 @@
 	exit 1
 fi
 
-sed 's/$$REV$\$/hg '"$VERSION/" test-svn.py >test.py
+sed 's/$$REV$\$/hg '"$VERSION/" test-vcs.py >test.py
 chmod +x test.py
\ No newline at end of file
--- a/test-svn.py	Thu Dec 09 19:05:06 2010 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,969 +0,0 @@
-#! /usr/bin/python
-# Copyright (c) 2009-2010 Chortos-2 <chortos@inbox.lv>
-
-import os, sys, shutil, time, subprocess, filecmp, optparse, signal, tempfile, tarfile, zipfile
-
-version = '1.21.0 ($$REV$$)'
-parser = optparse.OptionParser(version='test.py '+version, usage='usage: %prog [options] [problem names] [[path' + os.path.sep + 'to' + os.path.sep + ']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('-u', '--update', dest='update', action='store_true', default=False, help='check for an updated version of test.py')
-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 or -cm')
-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')
-parser.add_option('-b', dest='builtin', action='store_true', default=False)
-
-options, args = parser.parse_args()
-parser.destroy()
-del parser
-
-if options.builtin:
-	try:
-		if args[0] == 'run':
-			import resource
-			maxmemory = int(args[1])
-			resource.setrlimit(resource.RLIMIT_AS, (maxmemory*1024**2, maxmemory*1024**2))
-			os.execv(args[2], args[2:])
-		else:
-			sys.exit(2)
-	except:
-		sys.exit(2)
-
-def update():
-	import urllib
-	latesttext = urllib.urlopen('http://chortos.selfip.net/~astiob/test.py/version.txt').read()
-	latest = latesttext.split('.')
-	installed = version.split('.')
-	update = ''
-	if latest[0] > installed[0]:
-		update = 'major'
-	elif latest[0] == installed[0]:
-		if latest[1] > installed[1]:
-			update = 'feature'
-		elif latest[1] == installed[1]:
-			if latest[2] > installed[2]:
-				update = 'bug-fixing'
-			elif latest[2] == installed[2]:
-				print 'You are using the latest publicly available version of test.py.'
-				return
-	if update == '':
-		print 'Your copy of test.py is newer than the publicly available version.'
-		return
-	print 'A ' + update + ' update to test.py is available. Downloading...'
-	sys.stdout.flush()
-	urllib.urlretrieve('http://chortos.selfip.net/~astiob/test.py/test.py', 'test.py')
-	print 'Downloaded and installed. Now you are using test.py ' + latesttext + '.'
-
-if options.update:
-	update()
-	sys.exit()
-
-try:
-	import resource
-	memlimit = True
-	def call(name):
-		pid = os.fork()
-		if not pid:
-			resource.setrlimit(resource.RLIMIT_AS, (maxmemory*1024**2, maxmemory*1024**2))
-			os.execl(name)
-		else:
-			return pid
-except ImportError:
-	memlimit = False
-
-globals1 = set(globals())
-
-# Initialize some configuration variables with default values
-tasknames = (os.path.curdir,)
-maxtime = 0
-tests = ()
-dummies = ()
-testsexcluded = ()
-padwithzeroestolength = 0
-taskweight = 100
-pointmap = {}
-stdio = False
-dummyinname = ''
-dummyoutname = ''
-tester = ''
-maxexitcode = 0
-
-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(os.path.join('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(os.path.join('tests', taskname, name)) or os.path.isfile(os.path.join('tests', name)):
-		return True
-	if cwd and (os.path.isfile(os.path.join(oldcwd, 'tests', cwd, name)) or os.path.isfile(os.path.join(oldcwd, 'tests', name))):
-		return True
-	if existstestcase_helper(os.path.join(taskname, name)) or existstestcase_helper(name):
-		return True
-	if cwd:
-		os.chdir(oldcwd)
-		if existstestcase_helper(os.path.join(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(os.path.join('tests', taskname, name)):
-		return open(os.path.join('tests', taskname, name), 'rU')
-	elif os.path.isfile(os.path.join('tests', name)):
-		return open(os.path.join('tests', name), 'rU')
-	f = opentestcase_helper(os.path.join(taskname, name))
-	if not f:
-		f = opentestcase_helper(name)
-	if f:
-		return f
-	if cwd:
-		if os.path.isfile(os.path.join(oldcwd, 'tests', cwd, name)):
-			return open(os.path.join(oldcwd, 'tests', cwd, name), 'rU')
-		elif os.path.isfile(os.path.join(oldcwd, 'tests', name)):
-			return open(os.path.join(oldcwd, 'tests', name), 'rU')
-		os.chdir(oldcwd)
-		f = opentestcase_helper(os.path.join(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 os.path.isabs(target):
-			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('/') # FIXME: portability?
-			f = zipfile.ZipFile(os.path.join(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(os.path.join('tests', taskname, name)):
-		shutil.copyfile(os.path.join('tests', taskname, name), target)
-		return
-	elif os.path.isfile(os.path.join('tests', name)):
-		shutil.copyfile(os.path.join('tests', name), target)
-		return
-	if copytestcase_helper(os.path.join(taskname, name), target) or copytestcase_helper(name, target):
-		return
-	if cwd:
-		if os.path.isfile(os.path.join(oldcwd, 'tests', cwd, name)):
-			shutil.copyfile(os.path.join(oldcwd, 'tests', cwd, name), target)
-			return
-		elif os.path.isfile(os.path.join(oldcwd, 'tests', name)):
-			shutil.copyfile(os.path.join(oldcwd, 'tests', name), target)
-			return
-		os.chdir(oldcwd)
-		if copytestcase_helper(os.path.join(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 != os.path.curdir:
-			cwd = taskname
-		try:
-			execfile('testconf.py', globals())
-			return
-		except IOError:
-			pass
-	if not cwd:
-		if os.path.isfile(os.path.join('tests', taskname, 'testconf.py')):
-			execfile(os.path.join('tests', taskname, 'testconf.py'), globals())
-			return
-		if os.path.isfile(os.path.join('tests', 'testconf.py')):
-			execfile(os.path.join('tests', 'testconf.py'), globals())
-			return
-	if exectestconf_helper(os.path.join(taskname, 'testconf.py')) or exectestconf_helper('testconf.py'):
-		return
-	if cwd:
-		os.chdir(oldcwd)
-		if os.path.isfile(os.path.join('tests', cwd, 'testconf.py')):
-			execfile(os.path.join('tests', cwd, 'testconf.py'), globals())
-			os.chdir(cwd)
-			return
-		if os.path.isfile(os.path.join('tests', 'testconf.py')):
-			execfile(os.path.join('tests', 'testconf.py'), globals())
-			os.chdir(cwd)
-			return
-		if exectestconf_helper(os.path.join(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 != os.path.curdir 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 != os.path.curdir:
-		name = os.path.join(os.path.curdir, 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'
-		elif not stdio or tester or not options.erase:
-			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 completed.'
-			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 os.name == 'nt' and 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 not tester:
-		maxexitcode = 0
-	
-	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, maxexitcode if maxexitcode and isinstance(maxexitcode, int) else 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 not maxexitcode and r or maxexitcode and not r:
-					print '0/%g, wrong answer%s' % (npoints, data)
-					sys.stdout.flush()
-				elif not maxexitcode and r == 0 or maxexitcode and r >= maxexitcode:
-					print '%g/%g, OK%s' % (npoints, npoints, data)
-					sys.stdout.flush()
-					scoregrp += npoints
-					ncorrectgrp += 1
-					if npoints:
-						ncorrectvalued += 1
-				elif maxexitcode and r != None:
-					actualpoints = npoints*r/maxexitcode if not npoints*r%maxexitcode else float(npoints*r)/maxexitcode
-					print '%g/%g, partly OK%s' % (actualpoints, npoints, data)
-					sys.stdout.flush()
-					scoregrp += actualpoints
-					ncorrectgrp += 1
-					if npoints:
-						ncorrectvalued += 1
-		if ntotalgrp:
-			if ncorrectgrp < ntotalgrp:
-				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)
-	actualpoints = (score*taskweight/maxpoints if not score*taskweight%maxpoints else float(score*taskweight)/maxpoints) if maxpoints else 0
-	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, actualpoints, taskweight)
-	else:
-		print 'Grand total: %d/%d tests; %g/%g points; weighted score: %g/%g' % (ncorrect, ntotal, score, maxpoints, actualpoints, taskweight)
-	
-	scoresumoveralltasks += actualpoints
-	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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test-vcs.py	Sun Dec 19 19:34:20 2010 +0200
@@ -0,0 +1,969 @@
+#! /usr/bin/python
+# Copyright (c) 2009-2010 Chortos-2 <chortos@inbox.lv>
+
+import os, sys, shutil, time, subprocess, filecmp, optparse, signal, tempfile, tarfile, zipfile
+
+version = '1.21.0 ($$REV$$)'
+parser = optparse.OptionParser(version='test.py '+version, usage='usage: %prog [options] [problem names] [[path' + os.path.sep + 'to' + os.path.sep + ']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('-u', '--update', dest='update', action='store_true', default=False, help='check for an updated version of test.py')
+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 or -cm')
+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')
+parser.add_option('-b', dest='builtin', action='store_true', default=False)
+
+options, args = parser.parse_args()
+parser.destroy()
+del parser
+
+if options.builtin:
+	try:
+		if args[0] == 'run':
+			import resource
+			maxmemory = int(args[1])
+			resource.setrlimit(resource.RLIMIT_AS, (maxmemory*1024**2, maxmemory*1024**2))
+			os.execv(args[2], args[2:])
+		else:
+			sys.exit(2)
+	except:
+		sys.exit(2)
+
+def update():
+	import urllib
+	latesttext = urllib.urlopen('http://chortos.selfip.net/~astiob/test.py/version.txt').read()
+	latest = latesttext.split('.')
+	installed = version.split('.')
+	update = ''
+	if latest[0] > installed[0]:
+		update = 'major'
+	elif latest[0] == installed[0]:
+		if latest[1] > installed[1]:
+			update = 'feature'
+		elif latest[1] == installed[1]:
+			if latest[2] > installed[2]:
+				update = 'bug-fixing'
+			elif latest[2] == installed[2]:
+				print 'You are using the latest publicly available version of test.py.'
+				return
+	if update == '':
+		print 'Your copy of test.py is newer than the publicly available version.'
+		return
+	print 'A ' + update + ' update to test.py is available. Downloading...'
+	sys.stdout.flush()
+	urllib.urlretrieve('http://chortos.selfip.net/~astiob/test.py/test.py', 'test.py')
+	print 'Downloaded and installed. Now you are using test.py ' + latesttext + '.'
+
+if options.update:
+	update()
+	sys.exit()
+
+try:
+	import resource
+	memlimit = True
+	def call(name):
+		pid = os.fork()
+		if not pid:
+			resource.setrlimit(resource.RLIMIT_AS, (maxmemory*1024**2, maxmemory*1024**2))
+			os.execl(name)
+		else:
+			return pid
+except ImportError:
+	memlimit = False
+
+globals1 = set(globals())
+
+# Initialize some configuration variables with default values
+tasknames = (os.path.curdir,)
+maxtime = 0
+tests = ()
+dummies = ()
+testsexcluded = ()
+padwithzeroestolength = 0
+taskweight = 100
+pointmap = {}
+stdio = False
+dummyinname = ''
+dummyoutname = ''
+tester = ''
+maxexitcode = 0
+
+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(os.path.join('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(os.path.join('tests', taskname, name)) or os.path.isfile(os.path.join('tests', name)):
+		return True
+	if cwd and (os.path.isfile(os.path.join(oldcwd, 'tests', cwd, name)) or os.path.isfile(os.path.join(oldcwd, 'tests', name))):
+		return True
+	if existstestcase_helper(os.path.join(taskname, name)) or existstestcase_helper(name):
+		return True
+	if cwd:
+		os.chdir(oldcwd)
+		if existstestcase_helper(os.path.join(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(os.path.join('tests', taskname, name)):
+		return open(os.path.join('tests', taskname, name), 'rU')
+	elif os.path.isfile(os.path.join('tests', name)):
+		return open(os.path.join('tests', name), 'rU')
+	f = opentestcase_helper(os.path.join(taskname, name))
+	if not f:
+		f = opentestcase_helper(name)
+	if f:
+		return f
+	if cwd:
+		if os.path.isfile(os.path.join(oldcwd, 'tests', cwd, name)):
+			return open(os.path.join(oldcwd, 'tests', cwd, name), 'rU')
+		elif os.path.isfile(os.path.join(oldcwd, 'tests', name)):
+			return open(os.path.join(oldcwd, 'tests', name), 'rU')
+		os.chdir(oldcwd)
+		f = opentestcase_helper(os.path.join(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 os.path.isabs(target):
+			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('/') # FIXME: portability?
+			f = zipfile.ZipFile(os.path.join(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(os.path.join('tests', taskname, name)):
+		shutil.copyfile(os.path.join('tests', taskname, name), target)
+		return
+	elif os.path.isfile(os.path.join('tests', name)):
+		shutil.copyfile(os.path.join('tests', name), target)
+		return
+	if copytestcase_helper(os.path.join(taskname, name), target) or copytestcase_helper(name, target):
+		return
+	if cwd:
+		if os.path.isfile(os.path.join(oldcwd, 'tests', cwd, name)):
+			shutil.copyfile(os.path.join(oldcwd, 'tests', cwd, name), target)
+			return
+		elif os.path.isfile(os.path.join(oldcwd, 'tests', name)):
+			shutil.copyfile(os.path.join(oldcwd, 'tests', name), target)
+			return
+		os.chdir(oldcwd)
+		if copytestcase_helper(os.path.join(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 != os.path.curdir:
+			cwd = taskname
+		try:
+			execfile('testconf.py', globals())
+			return
+		except IOError:
+			pass
+	if not cwd:
+		if os.path.isfile(os.path.join('tests', taskname, 'testconf.py')):
+			execfile(os.path.join('tests', taskname, 'testconf.py'), globals())
+			return
+		if os.path.isfile(os.path.join('tests', 'testconf.py')):
+			execfile(os.path.join('tests', 'testconf.py'), globals())
+			return
+	if exectestconf_helper(os.path.join(taskname, 'testconf.py')) or exectestconf_helper('testconf.py'):
+		return
+	if cwd:
+		os.chdir(oldcwd)
+		if os.path.isfile(os.path.join('tests', cwd, 'testconf.py')):
+			execfile(os.path.join('tests', cwd, 'testconf.py'), globals())
+			os.chdir(cwd)
+			return
+		if os.path.isfile(os.path.join('tests', 'testconf.py')):
+			execfile(os.path.join('tests', 'testconf.py'), globals())
+			os.chdir(cwd)
+			return
+		if exectestconf_helper(os.path.join(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 != os.path.curdir 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 != os.path.curdir:
+		name = os.path.join(os.path.curdir, 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'
+		elif not stdio or tester or not options.erase:
+			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 completed.'
+			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 os.name == 'nt' and 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 not tester:
+		maxexitcode = 0
+	
+	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, maxexitcode if maxexitcode and isinstance(maxexitcode, int) else 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 not maxexitcode and r or maxexitcode and not r:
+					print '0/%g, wrong answer%s' % (npoints, data)
+					sys.stdout.flush()
+				elif not maxexitcode and r == 0 or maxexitcode and r >= maxexitcode:
+					print '%g/%g, OK%s' % (npoints, npoints, data)
+					sys.stdout.flush()
+					scoregrp += npoints
+					ncorrectgrp += 1
+					if npoints:
+						ncorrectvalued += 1
+				elif maxexitcode and r != None:
+					actualpoints = npoints*r/maxexitcode if not npoints*r%maxexitcode else float(npoints*r)/maxexitcode
+					print '%g/%g, partly OK%s' % (actualpoints, npoints, data)
+					sys.stdout.flush()
+					scoregrp += actualpoints
+					ncorrectgrp += 1
+					if npoints:
+						ncorrectvalued += 1
+		if ntotalgrp:
+			if ncorrectgrp < ntotalgrp:
+				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)
+	actualpoints = (score*taskweight/maxpoints if not score*taskweight%maxpoints else float(score*taskweight)/maxpoints) if maxpoints else 0
+	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, actualpoints, taskweight)
+	else:
+		print 'Grand total: %d/%d tests; %g/%g points; weighted score: %g/%g' % (ncorrect, ntotal, score, maxpoints, actualpoints, taskweight)
+	
+	scoresumoveralltasks += actualpoints
+	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)