Mercurial > ~astiob > upreckon > hgweb
changeset 82:06356af50bf9
Finished testcases reorganization and CPU time limit implementation
We now have:
* Win32-specific code in the win32 module (including bug fixes),
* UNIX-specific and generic code in the unix module,
* a much cleaner testcases module,
* wait4-based resource limits working on Python 3 (this is a bug fix),
* no warning/error reported on non-Win32 when -x is not passed
but standard input does not come from a terminal,
* the maxtime configuration variable replaced with two new variables
named maxcputime and maxwalltime,
* CPU time reported if it can be determined unless an error occurs sooner
than it is determined (e. g. if the wall-clock time limit is exceeded),
* memory limits enforced even if Upreckon's forking already breaks them,
* CPU time limits and private virtual memory limits honoured on Win32,
* CPU time limits honoured on UNIX(-like) platforms supporting wait4
or getrusage,
* address space limits honoured on UNIX(-like) platforms supporting
setrlimit with RLIMIT_AS/RLIMIT_VMEM,
* resident set size limits honoured on UNIX(-like) platforms supporting
wait4.
author | Oleg Oshmyan <chortos@inbox.lv> |
---|---|
date | Wed, 23 Feb 2011 23:35:27 +0000 |
parents | 24752db487c5 |
children | 37c4ad87583c |
files | config.py problem.py testcases.py unix.py win32.py |
diffstat | 5 files changed, 308 insertions(+), 450 deletions(-) [+] |
line wrap: on
line diff
--- a/config.py Wed Feb 16 15:30:57 2011 +0000 +++ b/config.py Wed Feb 23 23:35:27 2011 +0000 @@ -26,7 +26,8 @@ defaults_problem = {'kind': 'batch', 'usegroups': False, - 'maxtime': None, + 'maxcputime': None, + 'maxwalltime': None, 'maxmemory': None, 'dummies': {}, 'testsexcluded': (), @@ -155,7 +156,7 @@ newmap[key] = oldmap[key] setattr(module, name, newmap) if options.no_maxtime: - module.maxtime = 0 + module.maxcputime = module.maxwalltime = 0 sys.dont_write_bytecode = dwb for name in patterns: if hasattr(module, name):
--- a/problem.py Wed Feb 16 15:30:57 2011 +0000 +++ b/problem.py Wed Feb 23 23:35:27 2011 +0000 @@ -141,8 +141,10 @@ granted = case(lambda: (say('%7.3f%s s, ' % (case.time_stopped - case.time_started, case.time_limit_string), end=''), sys.stdout.flush())) except testcases.CanceledByUser: verdict = 'canceled by the user' - except testcases.TimeLimitExceeded: - verdict = 'time limit exceeded' + except testcases.WallTimeLimitExceeded: + verdict = 'wall-clock time limit exceeded' + except testcases.CPUTimeLimitExceeded: + verdict = 'CPU time limit exceeded' except testcases.MemoryLimitExceeded: verdict = 'memory limit exceeded' except testcases.WrongAnswer:
--- a/testcases.py Wed Feb 16 15:30:57 2011 +0000 +++ b/testcases.py Wed Feb 23 23:35:27 2011 +0000 @@ -21,290 +21,17 @@ devnull = open(os.path.devnull, 'w+') try: - from signal import SIGTERM, SIGKILL -except ImportError: - SIGTERM = 15 - SIGKILL = 9 - -try: - from _subprocess import TerminateProcess -except ImportError: - # CPython 2.5 does define _subprocess.TerminateProcess even though it is - # not used in the subprocess module, but maybe something else does not - try: - import ctypes - TerminateProcess = ctypes.windll.kernel32.TerminateProcess - except (ImportError, AttributeError): - TerminateProcess = None - - -# Do not show error messages due to errors in the program being tested -try: - import ctypes - try: - errmode = ctypes.windll.kernel32.GetErrorMode() - except AttributeError: - errmode = ctypes.windll.kernel32.SetErrorMode(0) - errmode |= 0x8003 - ctypes.windll.kernel32.SetErrorMode(errmode) + from win32 import * except Exception: - pass - - -# Do the hacky-wacky dark magic needed to catch presses of the Escape button. -# If only Python supported forcible termination of threads... -if not sys.stdin.isatty(): - canceled = init_canceled = lambda: False - pause = None -else: - try: - # Windows has select() too, but it is not the select() we want - import msvcrt - except ImportError: - try: - from select import select - import termios, tty, atexit - except ImportError: - # It cannot be helped! - # Silently disable support for killing the program being tested - canceled = init_canceled = lambda: False - pause = None - else: - def cleanup(old=termios.tcgetattr(sys.stdin.fileno())): - termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old) - atexit.register(cleanup) - del cleanup - tty.setcbreak(sys.stdin.fileno()) - def canceled(select=select, stdin=sys.stdin, read=sys.stdin.read): - while select((stdin,), (), (), 0)[0]: - if read(1) == '\33': - return True - return False - def init_canceled(): - while select((sys.stdin,), (), (), 0)[0]: - sys.stdin.read(1) - def pause(): - sys.stdin.read(1) - else: - def canceled(kbhit=msvcrt.kbhit, getch=msvcrt.getch): - while kbhit(): - c = getch() - if c == '\33': - return True - elif c == '\0': - # Let's hope no-one is fiddling with this - getch() - return False - def init_canceled(): - while msvcrt.kbhit(): - msvcrt.getch() - def pause(): - msvcrt.getch() - -try: - from signal import SIGCHLD, signal, SIG_DFL - from select import select, error as select_error - from errno import EINTR - import fcntl - try: - import cPickle as pickle - except ImportError: - import pickle -except ImportError: - try: - from _subprocess import WAIT_OBJECT_0, STD_INPUT_HANDLE, INFINITE - except ImportError: - WAIT_OBJECT_0 = 0 - STD_INPUT_HANDLE = -10 - INFINITE = -1 - try: - import ctypes - SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode - FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer - WaitForMultipleObjects = ctypes.windll.kernel32.WaitForMultipleObjects - ReadConsoleInputA = ctypes.windll.kernel32.ReadConsoleInputA - try: - from _subprocess import GetStdHandle - except ImportError: - GetStdHandle = ctypes.windll.kernel32.GetStdHandle - except (ImportError, AttributeError): - console_input = False - else: - hStdin = GetStdHandle(STD_INPUT_HANDLE) - console_input = bool(SetConsoleMode(hStdin, 1)) - if console_input: - FlushConsoleInputBuffer(hStdin) - class KEY_EVENT_RECORD(ctypes.Structure): - _fields_ = (("bKeyDown", ctypes.c_int), - ("wRepeatCount", ctypes.c_ushort), - ("wVirtualKeyCode", ctypes.c_ushort), - ("wVirtualScanCode", ctypes.c_ushort), - ("UnicodeChar", ctypes.c_wchar), - ("dwControlKeyState", ctypes.c_uint)) - class INPUT_RECORD(ctypes.Structure): - _fields_ = (("EventType", ctypes.c_int), - ("KeyEvent", KEY_EVENT_RECORD)) - # Memory limits (currently) are not supported - def call(*args, **kwargs): - case = kwargs.pop('case') - try: - case.process = Popen(*args, **kwargs) - except OSError: - raise CannotStartTestee(sys.exc_info()[1]) - case.time_started = clock() - if not console_input: - if case.maxtime: - if WaitForSingleObject(case.process._handle, int(case.maxtime * 1000)) != WAIT_OBJECT_0: - raise TimeLimitExceeded - else: - case.process.wait() - else: - ir = INPUT_RECORD() - n = ctypes.c_int() - lpHandles = (ctypes.c_int * 2)(hStdin, case.process._handle) - if case.maxtime: - time_end = clock() + case.maxtime - while case.process.poll() is None: - remaining = time_end - clock() - if remaining > 0: - if WaitForMultipleObjects(2, lpHandles, False, int(remaining * 1000)) == WAIT_OBJECT_0: - ReadConsoleInputA(hStdin, ctypes.byref(ir), 1, ctypes.byref(n)) - if ir.EventType == 1 and ir.KeyEvent.bKeyDown and ir.KeyEvent.wVirtualKeyCode == 27: - raise CanceledByUser - else: - raise TimeLimitExceeded - else: - while case.process.poll() is None: - if WaitForMultipleObjects(2, lpHandles, False, INFINITE) == WAIT_OBJECT_0: - ReadConsoleInputA(hStdin, ctypes.byref(ir), 1, ctypes.byref(n)) - if ir.EventType == 1 and ir.KeyEvent.bKeyDown and ir.KeyEvent.wVirtualKeyCode == 27: - raise CanceledByUser - case.time_stopped = clock() - if not console_input: - try: - try: - from _subprocess import WaitForSingleObject - except ImportError: - import ctypes - WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject - except (ImportError, AttributeError): - # TODO: move the default implementation here - call = None -else: - # Make SIGCHLD interrupt sleep() and select() - def bury_child(signum, frame): - try: - bury_child.case.time_stopped = clock() - except Exception: - pass - signal(SIGCHLD, bury_child) - - # If you want this to work, don't set any stdio argument to PIPE - def call_real(*args, **kwargs): - bury_child.case = case = kwargs.pop('case') - preexec_fn_ = kwargs.get('preexec_fn', None) - read, write = os.pipe() - def preexec_fn(): - os.close(read) - if preexec_fn_: - preexec_fn_() - fcntl.fcntl(write, fcntl.F_SETFD, fcntl.fcntl(write, fcntl.F_GETFD) | getattr(fcntl, 'FD_CLOEXEC', 1)) - fwrite = os.fdopen(write, 'wb') - pickle.dump(clock(), fwrite, 1) - kwargs['preexec_fn'] = preexec_fn - try: - case.process = Popen(*args, **kwargs) - except OSError: - os.close(read) - raise CannotStartTestee(sys.exc_info()[1]) - finally: - os.close(write) - try: - if pause is None: - if case.maxtime: - time.sleep(case.maxtime) - if case.process.poll() is None: - raise TimeLimitExceeded - else: - case.process.wait() - else: - if not case.maxtime: - try: - while case.process.poll() is None: - if select((sys.stdin,), (), ())[0]: - if sys.stdin.read(1) == '\33': - raise CanceledByUser - except select_error: - if sys.exc_info()[1].args[0] != EINTR: - raise - else: - case.process.poll() - else: - time_end = clock() + case.maxtime - try: - while case.process.poll() is None: - remaining = time_end - clock() - if remaining > 0: - if select((sys.stdin,), (), (), remaining)[0]: - if sys.stdin.read(1) == '\33': - raise CanceledByUser - else: - raise TimeLimitExceeded - except select_error: - if sys.exc_info()[1].args[0] != EINTR: - raise - else: - case.process.poll() - finally: - case.time_started = pickle.loads(os.read(read, 512)) - os.close(read) - del bury_child.case - def call(*args, **kwargs): - if 'preexec_fn' in kwargs: - try: - return call_real(*args, **kwargs) - except MemoryError: - # If there is not enough memory for the forked test.py, - # opt for silent dropping of the limit - # TODO: show a warning somewhere - del kwargs['preexec_fn'] - return call_real(*args, **kwargs) - else: - return call_real(*args, **kwargs) - -# Emulate memory limits on platforms compatible with 4.3BSD but not XSI -# I say 'emulate' because the OS will allow excessive memory usage -# anyway; Upreckon will just treat the test case as not passed. -# To do this, we not only require os.wait4 to be present but also -# assume things about the implementation of subprocess.Popen. -try: - def waitpid_emu(pid, options, _wait4=os.wait4): - global last_rusage - pid, status, last_rusage = _wait4(pid, options) - return pid, status - _waitpid = os.waitpid - os.waitpid = waitpid_emu - try: - defaults = Popen._internal_poll.__func__.__defaults__ - except AttributeError: - # Python 2.5 - defaults = Popen._internal_poll.im_func.func_defaults - i = defaults.index(_waitpid) - defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:] - try: - Popen._internal_poll.__func__.__defaults__ = defaults - except AttributeError: - pass - Popen._internal_poll.im_func.func_defaults = defaults -except (AttributeError, ValueError): - pass + from unix import * __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed', 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer', 'NonZeroExitCode', 'CannotStartTestee', 'CannotStartValidator', 'CannotReadOutputFile', 'CannotReadInputFile', 'CannotReadAnswerFile', - 'MemoryLimitExceeded') + 'MemoryLimitExceeded', 'CPUTimeLimitExceeded', + 'WallTimeLimitExceeded') @@ -312,6 +39,8 @@ class TestCaseNotPassed(Exception): __slots__ = () class TimeLimitExceeded(TestCaseNotPassed): __slots__ = () +class CPUTimeLimitExceeded(TimeLimitExceeded): __slots__ = () +class WallTimeLimitExceeded(TimeLimitExceeded): __slots__ = () class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = () class CanceledByUser(TestCaseNotPassed): __slots__ = () @@ -384,9 +113,11 @@ class TestCase(object): __slots__ = ('problem', 'id', 'isdummy', 'infile', 'outfile', 'points', - 'process', 'time_started', 'time_stopped', 'time_limit_string', - 'realinname', 'realoutname', 'maxtime', 'maxmemory', - 'has_called_back', 'files_to_delete') + 'process', 'time_started', 'time_stopped', + 'realinname', 'realoutname', 'maxcputime', 'maxwalltime', + 'maxmemory', 'has_called_back', 'files_to_delete', + 'cpu_time_limit_string', 'wall_time_limit_string', + 'time_limit_string') if ABCMeta: __metaclass__ = ABCMeta @@ -396,12 +127,17 @@ case.id = id case.isdummy = isdummy case.points = points - case.maxtime = case.problem.config.maxtime + case.maxcputime = case.problem.config.maxcputime + case.maxwalltime = case.problem.config.maxwalltime case.maxmemory = case.problem.config.maxmemory - if case.maxtime: - case.time_limit_string = '/%.3f' % case.maxtime + if case.maxcputime: + case.cpu_time_limit_string = '/%.3f' % case.maxcputime else: - case.time_limit_string = '' + case.cpu_time_limit_string = '' + if case.maxwalltime: + case.wall_time_limit_string = '/%.3f' % case.maxwalltime + else: + case.wall_time_limit_string = '' if not isdummy: case.realinname = case.problem.config.testcaseinname case.realoutname = case.problem.config.testcaseoutname @@ -415,13 +151,14 @@ def __call__(case, callback): case.has_called_back = False case.files_to_delete = [] + case.time_limit_string = case.wall_time_limit_string try: return case.test(callback) finally: now = clock() - if not getattr(case, 'time_started', None): + if getattr(case, 'time_started', None) is None: case.time_started = case.time_stopped = now - elif not getattr(case, 'time_stopped', None): + elif getattr(case, 'time_stopped', None) is None: case.time_stopped = now if not case.has_called_back: callback() @@ -432,21 +169,11 @@ # case.infile.close() #if getattr(case, 'outfile', None): # case.outfile.close() - if getattr(case, 'process', None): - # Try killing after three unsuccessful TERM attempts in a row - # (except on Windows, where TERMing is killing) + if getattr(case, 'process', None) and case.process.returncode is None: + # Try KILLing after three unsuccessful TERM attempts in a row for i in range(3): try: - try: - case.process.terminate() - except AttributeError: - # Python 2.5 - if TerminateProcess and hasattr(proc, '_handle'): - # Windows API - TerminateProcess(proc._handle, 1) - else: - # POSIX - os.kill(proc.pid, SIGTERM) + terminate(case.process) except Exception: time.sleep(0) case.process.poll() @@ -458,16 +185,7 @@ # just silently stop trying for i in range(3): try: - try: - case.process.kill() - except AttributeError: - # Python 2.5 - if TerminateProcess and hasattr(proc, '_handle'): - # Windows API - TerminateProcess(proc._handle, 1) - else: - # POSIX - os.kill(proc.pid, SIGKILL) + kill(case.process) except Exception: time.sleep(0) case.process.poll() @@ -547,28 +265,8 @@ __slots__ = () def test(case, callback): - init_canceled() - if sys.platform == 'win32' or not case.maxmemory: - preexec_fn = None - else: - def preexec_fn(): - try: - import resource - maxmemory = int(case.maxmemory * 1048576) - resource.setrlimit(resource.RLIMIT_AS, (maxmemory, maxmemory)) - # I would also set a CPU time limit but I do not want the time - # that passes between the calls to fork and exec to be counted in - except MemoryError: - # We do not have enough memory for ourselves; - # let the parent know about this - raise - except Exception: - # Well, at least we tried - pass case.open_infile() case.time_started = None - global last_rusage - last_rusage = None if case.problem.config.stdio: if options.erase and not case.validator or not case.problem.config.inname: # TODO: re-use the same file name if possible @@ -582,110 +280,18 @@ with contextmgr: with open(inputdatafname) as infile: with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile: - if call is not None: - call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) - else: - try: - try: - case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) - except MemoryError: - # If there is not enough memory for the forked test.py, - # opt for silent dropping of the limit - # TODO: show a warning somewhere - case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) - except OSError: - raise CannotStartTestee(sys.exc_info()[1]) - case.time_started = clock() - time_next_check = case.time_started + .15 - if not case.maxtime: - while True: - exitcode, now = case.process.poll(), clock() - if exitcode is not None: - case.time_stopped = now - break - # For some reason (probably Microsoft's fault), - # msvcrt.kbhit() is slow as hell - else: - if now >= time_next_check: - if canceled(): - raise CanceledByUser - else: - time_next_check = now + .15 - time.sleep(.001) - else: - time_end = case.time_started + case.maxtime - while True: - exitcode, now = case.process.poll(), clock() - if exitcode is not None: - case.time_stopped = now - break - elif now >= time_end: - raise TimeLimitExceeded - else: - if now >= time_next_check: - if canceled(): - raise CanceledByUser - else: - time_next_check = now + .15 - time.sleep(.001) + call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: raise NonZeroExitCode(case.process.returncode) - if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576): - raise MemoryLimitExceeded callback() case.has_called_back = True outfile.seek(0) return case.validate(outfile) else: case.infile.copy(case.problem.config.inname) - if call is not None: - call(case.problem.config.path, case=case, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) - else: - try: - try: - case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) - except MemoryError: - # If there is not enough memory for the forked test.py, - # opt for silent dropping of the limit - # TODO: show a warning somewhere - case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT) - except OSError: - raise CannotStartTestee(sys.exc_info()[1]) - case.time_started = clock() - time_next_check = case.time_started + .15 - if not case.maxtime: - while True: - exitcode, now = case.process.poll(), clock() - if exitcode is not None: - case.time_stopped = now - break - else: - if now >= time_next_check: - if canceled(): - raise CanceledByUser - else: - time_next_check = now + .15 - time.sleep(.001) - else: - time_end = case.time_started + case.maxtime - while True: - exitcode, now = case.process.poll(), clock() - if exitcode is not None: - case.time_stopped = now - break - elif now >= time_end: - raise TimeLimitExceeded - else: - if now >= time_next_check: - if canceled(): - raise CanceledByUser - else: - time_next_check = now + .15 - time.sleep(.001) + call(case.problem.config.path, case=case, stdin=devnull, stdout=devnull, stderr=STDOUT) if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: raise NonZeroExitCode(case.process.returncode) - if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576): - raise MemoryLimitExceeded callback() case.has_called_back = True with open(case.problem.config.outname, 'rU') as output:
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/unix.py Wed Feb 23 23:35:27 2011 +0000 @@ -0,0 +1,239 @@ +# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv> + +from __future__ import division, with_statement +import sys + +try: + from compat import * + import testcases # mutual import +except ImportError: + import __main__ + __main__.import_error(sys.exc_info()[1]) + +from __main__ import clock +from subprocess import Popen +import os, sys + +try: + from signal import SIGTERM, SIGKILL +except ImportError: + SIGTERM = 15 + SIGKILL = 9 + +__all__ = 'call', 'kill', 'terminate', 'pause' + + +if not sys.stdin.isatty(): + pause = lambda: sys.stdin.read(1) + catch_escape = False +else: + try: + from select import select + import termios, tty, atexit + except ImportError: + pause = None + catch_escape = False + else: + catch_escape = True + def cleanup(old=termios.tcgetattr(sys.stdin.fileno())): + termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old) + atexit.register(cleanup) + tty.setcbreak(sys.stdin.fileno()) + def pause(): + sys.stdin.read(1) + +try: + from signal import SIGCHLD, signal, SIG_DFL + from select import select, error as SelectError + from errno import EINTR + from fcntl import fcntl, F_SETFD, F_GETFD + try: + import cPickle as pickle + except ImportError: + import pickle +except ImportError: + def call(*args, **kwargs): + case = kwargs.pop('case') + try: + case.process = Popen(*args, **kwargs) + except OSError: + raise CannotStartTestee(sys.exc_info()[1]) + case.time_started = clock() + if not case.maxtime: + while True: + exitcode, now = case.process.poll(), clock() + if exitcode is not None: + case.time_stopped = now + break + else: + time.sleep(.001) + else: + time_end = case.time_started + case.maxtime + while True: + exitcode, now = case.process.poll(), clock() + if exitcode is not None: + case.time_stopped = now + break + elif now >= time_end: + raise TimeLimitExceeded + else: + time.sleep(.001) +else: + try: + from fcntl import FD_CLOEXEC + except ImportError: + FD_CLOEXEC = 1 + + try: + from resource import getrusage, RUSAGE_SELF, RUSAGE_CHILDREN + except ImportError: + from time import clock as cpuclock + getrusage = lambda who: None + else: + def cpuclock(): + rusage = getrusage(RUSAGE_SELF) + return rusage.ru_utime + rusage.ru_stime + + try: + from resource import setrlimit + try: + from resource import RLIMIT_AS + except ImportError: + from resource import RLIMIT_VMEM + except ImportError: + setrlimit = None + + # Make SIGCHLD interrupt sleep() and select() + def bury_child(signum, frame): + try: + bury_child.case.time_stopped = clock() + except Exception: + pass + signal(SIGCHLD, bury_child) + + # If you want this to work portably, don't set any stdio argument to PIPE + def call(*args, **kwargs): + global last_rusage + bury_child.case = case = kwargs.pop('case') + read, write = os.pipe() + fcntl(write, F_SETFD, fcntl(write, F_GETFD) | FD_CLOEXEC) + def preexec_fn(): + os.close(read) + if setrlimit and case.maxmemory: + maxmemory = ceil(case.maxmemory * 1048576) + setrlimit(RLIMIT_AS, (maxmemory, maxmemory)) + # I would also set a CPU time limit but I do not want the time + # passing between the calls to fork and exec to be counted in + os.write(write, pickle.dumps((clock(), cpuclock()), 1)) + kwargs['preexec_fn'] = preexec_fn + old_rusage = getrusage(RUSAGE_CHILDREN) + last_rusage = None + try: + case.process = Popen(*args, **kwargs) + except OSError: + os.close(read) + raise testcases.CannotStartTestee(sys.exc_info()[1]) + finally: + os.close(write) + try: + if not catch_escape: + if case.maxwalltime: + time.sleep(case.maxwalltime) + if case.process.poll() is None: + raise testcases.WallTimeLimitExceeded + else: + case.process.wait() + else: + if not case.maxwalltime: + try: + while case.process.poll() is None: + if select((sys.stdin,), (), ())[0]: + if sys.stdin.read(1) == '\33': + raise testcases.CanceledByUser + except SelectError: + if sys.exc_info()[1].args[0] != EINTR: + raise + else: + case.process.poll() + else: + time_end = clock() + case.maxwalltime + try: + while case.process.poll() is None: + remaining = time_end - clock() + if remaining > 0: + if select((sys.stdin,), (), (), remaining)[0]: + if sys.stdin.read(1) == '\33': + raise testcases.CanceledByUser + else: + raise testcases.WallTimeLimitExceeded + except SelectError: + if sys.exc_info()[1].args[0] != EINTR: + raise + else: + case.process.poll() + finally: + case.time_started, cpustart = pickle.loads(os.read(read, 512)) + os.close(read) + del bury_child.case + new_rusage = getrusage(RUSAGE_CHILDREN) + if new_rusage and (case.maxcputime or not case.maxwalltime): + case.time_started = cpustart + case.time_stopped = new_rusage.ru_utime + new_rusage.ru_stime + case.time_limit_string = case.cpu_time_limit_string + if case.maxcputime and new_rusage: + oldtime = old_rusage.ru_utime + old_rusage.ru_stime + newtime = new_rusage.ru_utime + new_rusage.ru_stime + if newtime - oldtime - cpustart > case.maxcputime: + raise testcases.CPUTimeLimitExceeded + if case.maxmemory: + if sys.platform != 'darwin': + maxrss = case.maxmemory * 1024 + else: + maxrss = case.maxmemory * 1048576 + if last_rusage and last_rusage.ru_maxrss > maxrss: + raise testcases.MemoryLimitExceeded + elif (new_rusage and + new_rusage.ru_maxrss > old_rusage.ru_maxrss and + new_rusage.ru_maxrss > maxrss): + raise testcases.MemoryLimitExceeded + +# Emulate memory limits on platforms compatible with 4.3BSD but not XSI +# I say 'emulate' because the OS will allow excessive memory usage +# anyway; Upreckon will just treat the test case as not passed. +# To do this, we not only require os.wait4 to be present but also +# assume things about the implementation of subprocess.Popen. +try: + def waitpid_emu(pid, options, _wait4=os.wait4): + global last_rusage + pid, status, last_rusage = _wait4(pid, options) + return pid, status + _waitpid = os.waitpid + os.waitpid = waitpid_emu + try: + defaults = Popen._internal_poll.__func__.__defaults__ + except AttributeError: + # Python 2.5 + defaults = Popen._internal_poll.im_func.func_defaults + i = defaults.index(_waitpid) + defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:] + try: + Popen._internal_poll.__func__.__defaults__ = defaults + except AttributeError: + # Python 2.5 again + Popen._internal_poll.im_func.func_defaults = defaults +except (AttributeError, ValueError): + pass + + +def kill(process): + try: + process.kill() + except AttributeError: + os.kill(process.pid, SIGKILL) + + +def terminate(process): + try: + process.terminate() + except AttributeError: + os.kill(process.pid, SIGTERM) \ No newline at end of file
--- a/win32.py Wed Feb 16 15:30:57 2011 +0000 +++ b/win32.py Wed Feb 23 23:35:27 2011 +0000 @@ -1,19 +1,20 @@ -# Copyright (c) 2011 Chortos-2 <chortos@inbox.lv> +# Copyright (c) 2010-2011 Chortos-2 <chortos@inbox.lv> from __future__ import division, with_statement +import sys try: from compat import * - from testcases import (TimeLimitExceeded, MemoryLimitExceeded, - CanceledByUser, CannotStartTestee) + import testcases # mutual import except ImportError: import __main__ __main__.import_error(sys.exc_info()[1]) -from __main__ import clock from ctypes import * from ctypes.wintypes import * +from msvcrt import getch as pause from subprocess import Popen +from __main__ import clock # Defaults that may be overwritten by values from _subprocess INFINITE = -1 @@ -42,7 +43,7 @@ else: ProcessTimes = namedtuple('ProcessTimes', 'kernel user') -__all__ = 'call', 'kill', 'terminate' +__all__ = 'call', 'kill', 'terminate', 'pause' # Automatically convert _subprocess handle objects into low-level HANDLEs @@ -315,7 +316,7 @@ ('PeakProcessMemoryUsed', SIZE_T), ('PeakJobMemoryUsed', SIZE_T)) -prototype = WINFUNCTYPE(BOOL, HANDLE, c_int, c_void_p, DWORD) +prototype = WINFUNCTYPE(BOOL, Handle, c_int, c_void_p, DWORD) flags = (1, 'job'), (1, 'infoclass'), (1, 'info'), (1, 'infosize') try: _setjobinfo = prototype(('SetInformationJobObject',windll.kernel32), flags) @@ -327,7 +328,7 @@ if not result: raise WinError() _setjobinfo.errcheck = errcheck def SetInformationJobObject(job, infoclass, info): - return _setjobinfo(job, infoclass, info, sizeof(info)) + return _setjobinfo(job, infoclass, byref(info), sizeof(info)) ( JobObjectBasicAccountingInformation, @@ -417,13 +418,6 @@ console_input = True FlushConsoleInputBuffer(stdin) -def kill(process): - try: - process.terminate() - except AttributeError: - TerminateProcess(process._handle) -terminate = kill - def call(*args, **kwargs): case = kwargs.pop('case') job = CreateJobObject(None) @@ -443,14 +437,14 @@ try: case.process = Popen(*args, **kwargs) except OSError: - raise CannotStartTestee(sys.exc_info()[1]) + raise testcases.CannotStartTestee(sys.exc_info()[1]) case.time_started = clock() AssignProcessToJobObject(job, case.process._handle) if not console_input: if case.maxwalltime: if (WaitForSingleObject(case.process._handle, case.maxwalltime) != WAIT_OBJECT_0): - raise TimeLimitExceeded + raise testcases.WallTimeLimitExceeded else: case.process.wait() else: @@ -467,9 +461,9 @@ ir.EventType == 1 and ir.Event.KeyEvent.bKeyDown and ir.Event.KeyEvent.wVirtualKeyCode == 27): - raise CanceledByUser + raise testcases.CanceledByUser else: - raise TimeLimitExceeded + raise testcases.WallTimeLimitExceeded else: while case.process.poll() is None: if (WaitForMultipleObjects(handles, False, INFINITE) == @@ -479,16 +473,24 @@ ir.EventType == 1 and ir.Event.KeyEvent.bKeyDown and ir.Event.KeyEvent.wVirtualKeyCode == 27): - raise CanceledByUser + raise testcases.CanceledByUser case.time_stopped = clock() - if case.maxcputime and GetProcessTimes: + if GetProcessTimes: try: times = GetProcessTimes(case.process._handle) except WindowsError: pass else: - if times.kernel + times.user > case.maxcputime: - raise TimeLimitExceeded + time = times.kernel + times.user + case.time_stopped = time + case.time_started = 0 + case.time_limit_string = case.cpu_time_limit_string + if case.maxcputime and time > case.maxcputime: + raise testcases.CPUTimeLimitExceeded + if case.maxcputime and case.process.returncode == 1816: + raise testcases.CPUTimeLimitExceeded + if case.maxmemory and case.process.returncode == -0x3ffffffb: + raise testcases.MemoryLimitExceeded if case.maxmemory and GetProcessMemoryInfo: try: counters = GetProcessMemoryInfo(case.process._handle) @@ -496,4 +498,12 @@ pass else: if counters.PeakPagefileUsage > case.maxmemory * 1048576: - raise MemoryLimitExceeded \ No newline at end of file + raise testcases.MemoryLimitExceeded + + +def kill(process): + try: + process.terminate() + except AttributeError: + TerminateProcess(process._handle) +terminate = kill \ No newline at end of file