view upreckon-vcs @ 76:0e5ae28e0b2b

Points are now weighted on a test context basis In particular, this has allowed for simple extensions to the format of testconf to award points to whole test groups without at the same time compromising the future ability of giving partial score for correct but slow solutions. Specifically, the groupweight configuration variable has been added and normally has the format {groupindex: points} where groupindex is the group's index in the tests configuration variable. The backwards incompatible change is that test contexts are no longer guaranteed to learn the score awarded or the maximum possible score for every test case and may instead be notified about them in batches. In other news, the pointmap and groupweight configuration variables can (now) be given as sequences in addition to mappings. (Technically, the distinction currently made is dict versus everything else.) Items of a sequence pointmap/groupweight correspond directly to the test cases/ groups defined in the tests configuration variable; in particular, when groups are used, tests=[1],[2,3];pointmap={1:1,2:2,3:3} can now be written as pointmap=tests=[1],[2,3]. Missing items are handled in the same way in which they are handled when the variable is a mapping. Note that the items of groupweight correspond to whole test groups rather than individual test cases. In other news again, the wording of problem total lines has been changed from '<unweighted> points; weighted score: <weighted>' to '<weighted> points (<unweighted> before weighting)', and group total lines now properly report fractional numbers of points (this is a bug fix).
author Oleg Oshmyan <chortos@inbox.lv>
date Sat, 08 Jan 2011 16:03:35 +0200
parents b9d5857f7b9a
children d46bd7ee3e69
line wrap: on
line source

#! /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.legacy:
	compat.pseudobuiltins += 'xrange',

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:
	import testcases
except ImportError:
	import_error(sys.exc_info()[1])

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 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)