Mercurial > ~astiob > upreckon > hgweb
view upreckon/config.py @ 205:166a23999bf7
Added confvar okexitcodemask; changed the validator protocol
Callable validators now return three-tuples (number granted, bool correct,
str comment) instead of two-tuples (number granted, str comment). They are
still allowed to return single numbers.
Callable validators must now explicitly raise
upreckon.exceptions.WrongAnswer if they want the verdict to be Wrong
Answer rather than Partly Correct.
okexitcodemask specifies a bitmask ANDed with the exit code of the
external validator to get a boolean flag showing whether the answer is to
be marked as 'OK' rather than 'partly correct'. The bits covered by the
bitmask are reset to zeroes before devising the number of points granted
from the resulting number.
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Wed, 17 Aug 2011 20:44:54 +0300 |
parents | dd1f715398f0 |
children | 946e8c09ba12 |
line wrap: on
line source
# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv> from __future__ import division, with_statement from .compat import * from . import files from __main__ import options, args if files.ZipArchive: try: import zipimport except ImportError: zipimport = None else: zipimport = None import imp, os, posixpath, sys, tempfile __all__ = 'load_problem', 'load_global', 'globalconf', 'nativize_path' defaults_problem = {'kind': 'batch', 'usegroups': False, 'maxcputime': None, 'maxwalltime': None, 'maxmemory': None, 'match': 'literal', 'dummies': (), 'testsexcluded': (), 'padtests': 0, 'paddummies': 0, 'taskweight': 100, 'groupweight': {}, 'pointmap': {}, 'stdio': False, 'binary': False, 'dummyinname': '', 'dummyoutname': '', 'tester': None, 'maxexitcode': 0, 'okexitcodeflag': 0, 'inname': '', 'ansname': '', 'force_zero_exitcode': True} defaults_global = {'problems': None} 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 nativize_path(portable_path): if portable_path.startswith('//:'): return portable_path[3:] comps = portable_path.split('/') for i, comp in enumerate(comps): if comp == '..': comps[i] = os.path.pardir elif comp == '.': comps[i] = os.path.curdir native_path = os.path.join(*comps) if posixpath.isabs(portable_path) and not os.path.isabs(native_path): abspath = os.path.abspath(native_path) parent = os.path.dirname(abspath) while parent != abspath: abspath, parent = parent, os.path.dirname(parent) native_path = os.path.join(parent, native_path) elif not posixpath.isabs(portable_path) and os.path.isabs(native_path): native_path = os.path.sep + native_path if posixpath.isabs(portable_path) != os.path.isabs(native_path): raise ValueError('cannot make native path relative/absolute') return native_path def load_problem(problem_name): global builtins try: dwb = sys.dont_write_bytecode sys.dont_write_bytecode = True except AttributeError: pass metafile = files.File(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'] module = Config(module, globalconf) 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 if (not hasattr(module, 'maxcputime') and not hasattr(module, 'maxwalltime') and hasattr(module, 'maxtime')): module.maxcputime = module.maxtime for name in defaults_problem: setattr(module, name, getattr(module, name, defaults_problem[name])) if not module.dummyinname: module.dummyinname = getattr(module, 'testcaseinname', module.dummyinname) if not module.dummyoutname: module.dummyoutname = getattr(module, 'testcaseoutname', module.dummyoutname) if hasattr(module, 'testee'): if isinstance(module.testee, basestring): module.path = nativize_path(module.testee) else: testee = tuple(module.testee) module.path = (nativize_path(testee[0]),) + testee[1:] elif 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 if module.tester: if isinstance(module.tester, basestring): module.tester = nativize_path(module.tester) elif not callable(module.tester): tester = tuple(module.tester) module.tester = (nativize_path(tester[0]),) + tester[1:] if not isinstance(module.taskweight, dict): try: module.taskweight = dict(zip(module.problems, module.taskweight)) except TypeError: pass try: module.taskweight = module.taskweight[problem_name] except KeyError: module.taskweight = defaults_problem['taskweight'] except TypeError: pass 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.maxcputime = module.maxwalltime = 0 if args: module.usegroups = False module.tests = args module.dummies = () try: sys.dont_write_bytecode = dwb except NameError: pass for name in patterns: if hasattr(module, name): setattr(module, name, getattr(module, name).replace('%', problem_name)) return module def load_global(): global builtins try: dwb = sys.dont_write_bytecode sys.dont_write_bytecode = True except AttributeError: pass 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 # Iterable and mapping taskweights cause re-iteration over problems try: len(module.problems) except Exception: module.problems = tuple(module.problems) global globalconf globalconf = module try: sys.dont_write_bytecode = dwb except NameError: pass return module