view config.py @ 80:809b77302b21

Win32-specific module with memory and CPU time limits The Win32-specific implementation of call() and friends now lives in module win32, looks clean and in addition is able to enforce memory and CPU time limits on NT kernels, in particular on Windows 2000 and up asking the system to terminate the process as soon as or (in the case of CPU time) almost as soon as the limits are broken. According to my observations, malloc() in the limited process does not return NULL when memory usage is close to the limit and instead crashes the process (which Upreckon happily translates into 'memory limit exceeded'). The catch is that the module is not actually used yet; coming soon.
author Oleg Oshmyan <chortos@inbox.lv>
date Wed, 16 Feb 2011 00:01:33 +0000
parents ee8a99dcaaed
children 06356af50bf9
line wrap: on
line source

#! /usr/bin/env python
# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv>

from __future__ import division, with_statement

try:
	from compat import *
	import files
except ImportError:
	import __main__
	__main__.import_error(sys.exc_info()[1])
else:
	from __main__ import options

if files.ZipArchive:
	try:
		import zipimport
	except ImportError:
		zipimport = None
else:
	zipimport = None

import imp, os, sys, tempfile

__all__ = 'load_problem', 'load_global', 'globalconf'

defaults_problem = {'kind': 'batch',
                    'usegroups': False,
                    'maxtime': None,
                    'maxmemory': None,
                    'dummies': {},
                    'testsexcluded': (),
                    'padtests': 0,
                    'paddummies': 0,
                    'taskweight': 100,
                    'groupweight': {},
                    'pointmap': {},
                    'stdio': False,
                    'dummyinname': '',
                    'dummyoutname': '',
                    'tester': None,
                    'maxexitcode': 0,
                    'inname': '',
                    'ansname': ''}
defaults_global = {'problems': None,
                   'force_zero_exitcode': True}
defaults_noerase = {'inname': '%.in',
                    'outname': '%.out',
                    'ansname': '%.ans'}
patterns = ('inname', 'outname', 'ansname', 'testcaseinname',
            'testcaseoutname', 'dummyinname', 'dummyoutname')

class Config(object):
	__slots__ = 'modules', '__dict__'
	
	def __init__(self, *modules):
		self.modules = modules
	
	def __getattr__(self, name):
		for module in self.modules:
			try:
				return getattr(module, name)
			except AttributeError:
				pass
		# TODO: provide a message
		raise AttributeError(name)

# A helper context manager
class ReadDeleting(object):
	__slots__ = 'name', 'file'
	
	def __init__(self, name):
		self.name = name
	
	def __enter__(self):
		try:
			self.file = open(self.name, 'rU')
			return self.file
		except:
			try:
				self.__exit__(None, None, None)
			except:
				pass
			raise
	
	def __exit__(self, exc_type, exc_val, exc_tb):
		self.file.close()
		os.remove(self.name)

def load_problem(problem_name):
	global builtins
	dwb = sys.dont_write_bytecode
	sys.dont_write_bytecode = True
	metafile = files.File('/'.join((problem_name, 'testconf.py')), True, 'configuration')
	module = None
	with CompatBuiltins() as builtins:
		if zipimport and isinstance(metafile.archive, files.ZipArchive):
			try:
				module = zipimport.zipimporter(os.path.dirname(metafile.full_real_path)).load_module('testconf')
			except zipimport.ZipImportError:
				pass
			else:
				del sys.modules['testconf']
		if not module:
			try:
				with metafile.open() as f:
					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
			# Handle the case when f is not a true file object but imp requires one
			except ValueError:
				# FIXME: 2.5 lacks the delete parameter
				with tempfile.NamedTemporaryFile(delete=False) as f:
					inputdatafname = f.name
				metafile.copy(inputdatafname)
				with ReadDeleting(inputdatafname) as f:
					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
			del sys.modules['testconf']
	if hasattr(module, 'padwithzeroestolength'):
		if not hasattr(module, 'padtests'):
			try:
				module.padtests = module.padwithzeroestolength[0]
			except TypeError:
				module.padtests = module.padwithzeroestolength
		if not hasattr(module, 'paddummies'):
			try:
				module.paddummies = module.padwithzeroestolength[1]
			except TypeError:
				module.paddummies = module.padwithzeroestolength
	for name in defaults_problem:
		if not hasattr(globalconf, name):
			setattr(module, name, getattr(module, name, defaults_problem[name]))
	module = Config(module, globalconf)
	if not module.dummyinname:
		module.dummyinname = getattr(module, 'testcaseinname', module.dummyinname)
	if not module.dummyoutname:
		module.dummyoutname = getattr(module, 'testcaseoutname', module.dummyoutname)
	if not hasattr(module, 'path'):
		if hasattr(module, 'name'):
			module.path = module.name
		elif sys.platform != 'win32':
			module.path = os.path.join(os.path.curdir, problem_name)
		else:
			module.path = problem_name
	for name in 'pointmap', 'groupweight':
		oldmap = getattr(module, name)
		if isinstance(oldmap, dict):
			newmap = {}
			for key in oldmap:
				if not options.legacy and isinstance(key, basestring):
					newmap[key] = oldmap[key]
				else:
					try:
						for k in key:
							newmap[k] = oldmap[key]
					except TypeError:
						newmap[key] = oldmap[key]
			setattr(module, name, newmap)
	if options.no_maxtime:
		module.maxtime = 0
	sys.dont_write_bytecode = dwb
	for name in patterns:
		if hasattr(module, name):
			setattr(module, name, getattr(module, name).replace('%', problem_name))
	return module

def load_global():
	global builtins
	dwb = sys.dont_write_bytecode
	sys.dont_write_bytecode = True
	metafile = files.File('testconf.py', True, 'configuration')
	module = None
	with CompatBuiltins() as builtins:
		if zipimport and isinstance(metafile.archive, files.ZipArchive):
			try:
				module = zipimport.zipimporter(os.path.dirname(metafile.full_real_path)).load_module('testconf')
			except zipimport.ZipImportError:
				pass
			else:
				del sys.modules['testconf']
		if not module:
			try:
				with metafile.open() as f:
					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
			# Handle the case when f is not a true file object but imp requires one
			except ValueError:
				# FIXME: 2.5 lacks the delete parameter
				with tempfile.NamedTemporaryFile(delete=False) as f:
					inputdatafname = f.name
				metafile.copy(inputdatafname)
				with ReadDeleting(inputdatafname) as f:
					module = imp.load_module('testconf', f, metafile.full_real_path, ('.py', 'r', imp.PY_SOURCE))
			del sys.modules['testconf']
	for name in defaults_global:
		setattr(module, name, getattr(module, name, defaults_global[name]))
	if not options.erase:
		for name in defaults_noerase:
			setattr(module, name, getattr(module, name, defaults_noerase[name]))
	if hasattr(module, 'tasknames'):
		module.problems = module.tasknames
	global globalconf
	globalconf = module
	sys.dont_write_bytecode = dwb
	return module