Mercurial > ~astiob > upreckon > hgweb
comparison testcases.py @ 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 |
comparison
equal
deleted
inserted
replaced
| 81:24752db487c5 | 82:06356af50bf9 |
|---|---|
| 19 | 19 |
| 20 import os | 20 import os |
| 21 devnull = open(os.path.devnull, 'w+') | 21 devnull = open(os.path.devnull, 'w+') |
| 22 | 22 |
| 23 try: | 23 try: |
| 24 from signal import SIGTERM, SIGKILL | 24 from win32 import * |
| 25 except ImportError: | |
| 26 SIGTERM = 15 | |
| 27 SIGKILL = 9 | |
| 28 | |
| 29 try: | |
| 30 from _subprocess import TerminateProcess | |
| 31 except ImportError: | |
| 32 # CPython 2.5 does define _subprocess.TerminateProcess even though it is | |
| 33 # not used in the subprocess module, but maybe something else does not | |
| 34 try: | |
| 35 import ctypes | |
| 36 TerminateProcess = ctypes.windll.kernel32.TerminateProcess | |
| 37 except (ImportError, AttributeError): | |
| 38 TerminateProcess = None | |
| 39 | |
| 40 | |
| 41 # Do not show error messages due to errors in the program being tested | |
| 42 try: | |
| 43 import ctypes | |
| 44 try: | |
| 45 errmode = ctypes.windll.kernel32.GetErrorMode() | |
| 46 except AttributeError: | |
| 47 errmode = ctypes.windll.kernel32.SetErrorMode(0) | |
| 48 errmode |= 0x8003 | |
| 49 ctypes.windll.kernel32.SetErrorMode(errmode) | |
| 50 except Exception: | 25 except Exception: |
| 51 pass | 26 from unix import * |
| 52 | |
| 53 | |
| 54 # Do the hacky-wacky dark magic needed to catch presses of the Escape button. | |
| 55 # If only Python supported forcible termination of threads... | |
| 56 if not sys.stdin.isatty(): | |
| 57 canceled = init_canceled = lambda: False | |
| 58 pause = None | |
| 59 else: | |
| 60 try: | |
| 61 # Windows has select() too, but it is not the select() we want | |
| 62 import msvcrt | |
| 63 except ImportError: | |
| 64 try: | |
| 65 from select import select | |
| 66 import termios, tty, atexit | |
| 67 except ImportError: | |
| 68 # It cannot be helped! | |
| 69 # Silently disable support for killing the program being tested | |
| 70 canceled = init_canceled = lambda: False | |
| 71 pause = None | |
| 72 else: | |
| 73 def cleanup(old=termios.tcgetattr(sys.stdin.fileno())): | |
| 74 termios.tcsetattr(sys.stdin.fileno(), termios.TCSAFLUSH, old) | |
| 75 atexit.register(cleanup) | |
| 76 del cleanup | |
| 77 tty.setcbreak(sys.stdin.fileno()) | |
| 78 def canceled(select=select, stdin=sys.stdin, read=sys.stdin.read): | |
| 79 while select((stdin,), (), (), 0)[0]: | |
| 80 if read(1) == '\33': | |
| 81 return True | |
| 82 return False | |
| 83 def init_canceled(): | |
| 84 while select((sys.stdin,), (), (), 0)[0]: | |
| 85 sys.stdin.read(1) | |
| 86 def pause(): | |
| 87 sys.stdin.read(1) | |
| 88 else: | |
| 89 def canceled(kbhit=msvcrt.kbhit, getch=msvcrt.getch): | |
| 90 while kbhit(): | |
| 91 c = getch() | |
| 92 if c == '\33': | |
| 93 return True | |
| 94 elif c == '\0': | |
| 95 # Let's hope no-one is fiddling with this | |
| 96 getch() | |
| 97 return False | |
| 98 def init_canceled(): | |
| 99 while msvcrt.kbhit(): | |
| 100 msvcrt.getch() | |
| 101 def pause(): | |
| 102 msvcrt.getch() | |
| 103 | |
| 104 try: | |
| 105 from signal import SIGCHLD, signal, SIG_DFL | |
| 106 from select import select, error as select_error | |
| 107 from errno import EINTR | |
| 108 import fcntl | |
| 109 try: | |
| 110 import cPickle as pickle | |
| 111 except ImportError: | |
| 112 import pickle | |
| 113 except ImportError: | |
| 114 try: | |
| 115 from _subprocess import WAIT_OBJECT_0, STD_INPUT_HANDLE, INFINITE | |
| 116 except ImportError: | |
| 117 WAIT_OBJECT_0 = 0 | |
| 118 STD_INPUT_HANDLE = -10 | |
| 119 INFINITE = -1 | |
| 120 try: | |
| 121 import ctypes | |
| 122 SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode | |
| 123 FlushConsoleInputBuffer = ctypes.windll.kernel32.FlushConsoleInputBuffer | |
| 124 WaitForMultipleObjects = ctypes.windll.kernel32.WaitForMultipleObjects | |
| 125 ReadConsoleInputA = ctypes.windll.kernel32.ReadConsoleInputA | |
| 126 try: | |
| 127 from _subprocess import GetStdHandle | |
| 128 except ImportError: | |
| 129 GetStdHandle = ctypes.windll.kernel32.GetStdHandle | |
| 130 except (ImportError, AttributeError): | |
| 131 console_input = False | |
| 132 else: | |
| 133 hStdin = GetStdHandle(STD_INPUT_HANDLE) | |
| 134 console_input = bool(SetConsoleMode(hStdin, 1)) | |
| 135 if console_input: | |
| 136 FlushConsoleInputBuffer(hStdin) | |
| 137 class KEY_EVENT_RECORD(ctypes.Structure): | |
| 138 _fields_ = (("bKeyDown", ctypes.c_int), | |
| 139 ("wRepeatCount", ctypes.c_ushort), | |
| 140 ("wVirtualKeyCode", ctypes.c_ushort), | |
| 141 ("wVirtualScanCode", ctypes.c_ushort), | |
| 142 ("UnicodeChar", ctypes.c_wchar), | |
| 143 ("dwControlKeyState", ctypes.c_uint)) | |
| 144 class INPUT_RECORD(ctypes.Structure): | |
| 145 _fields_ = (("EventType", ctypes.c_int), | |
| 146 ("KeyEvent", KEY_EVENT_RECORD)) | |
| 147 # Memory limits (currently) are not supported | |
| 148 def call(*args, **kwargs): | |
| 149 case = kwargs.pop('case') | |
| 150 try: | |
| 151 case.process = Popen(*args, **kwargs) | |
| 152 except OSError: | |
| 153 raise CannotStartTestee(sys.exc_info()[1]) | |
| 154 case.time_started = clock() | |
| 155 if not console_input: | |
| 156 if case.maxtime: | |
| 157 if WaitForSingleObject(case.process._handle, int(case.maxtime * 1000)) != WAIT_OBJECT_0: | |
| 158 raise TimeLimitExceeded | |
| 159 else: | |
| 160 case.process.wait() | |
| 161 else: | |
| 162 ir = INPUT_RECORD() | |
| 163 n = ctypes.c_int() | |
| 164 lpHandles = (ctypes.c_int * 2)(hStdin, case.process._handle) | |
| 165 if case.maxtime: | |
| 166 time_end = clock() + case.maxtime | |
| 167 while case.process.poll() is None: | |
| 168 remaining = time_end - clock() | |
| 169 if remaining > 0: | |
| 170 if WaitForMultipleObjects(2, lpHandles, False, int(remaining * 1000)) == WAIT_OBJECT_0: | |
| 171 ReadConsoleInputA(hStdin, ctypes.byref(ir), 1, ctypes.byref(n)) | |
| 172 if ir.EventType == 1 and ir.KeyEvent.bKeyDown and ir.KeyEvent.wVirtualKeyCode == 27: | |
| 173 raise CanceledByUser | |
| 174 else: | |
| 175 raise TimeLimitExceeded | |
| 176 else: | |
| 177 while case.process.poll() is None: | |
| 178 if WaitForMultipleObjects(2, lpHandles, False, INFINITE) == WAIT_OBJECT_0: | |
| 179 ReadConsoleInputA(hStdin, ctypes.byref(ir), 1, ctypes.byref(n)) | |
| 180 if ir.EventType == 1 and ir.KeyEvent.bKeyDown and ir.KeyEvent.wVirtualKeyCode == 27: | |
| 181 raise CanceledByUser | |
| 182 case.time_stopped = clock() | |
| 183 if not console_input: | |
| 184 try: | |
| 185 try: | |
| 186 from _subprocess import WaitForSingleObject | |
| 187 except ImportError: | |
| 188 import ctypes | |
| 189 WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject | |
| 190 except (ImportError, AttributeError): | |
| 191 # TODO: move the default implementation here | |
| 192 call = None | |
| 193 else: | |
| 194 # Make SIGCHLD interrupt sleep() and select() | |
| 195 def bury_child(signum, frame): | |
| 196 try: | |
| 197 bury_child.case.time_stopped = clock() | |
| 198 except Exception: | |
| 199 pass | |
| 200 signal(SIGCHLD, bury_child) | |
| 201 | |
| 202 # If you want this to work, don't set any stdio argument to PIPE | |
| 203 def call_real(*args, **kwargs): | |
| 204 bury_child.case = case = kwargs.pop('case') | |
| 205 preexec_fn_ = kwargs.get('preexec_fn', None) | |
| 206 read, write = os.pipe() | |
| 207 def preexec_fn(): | |
| 208 os.close(read) | |
| 209 if preexec_fn_: | |
| 210 preexec_fn_() | |
| 211 fcntl.fcntl(write, fcntl.F_SETFD, fcntl.fcntl(write, fcntl.F_GETFD) | getattr(fcntl, 'FD_CLOEXEC', 1)) | |
| 212 fwrite = os.fdopen(write, 'wb') | |
| 213 pickle.dump(clock(), fwrite, 1) | |
| 214 kwargs['preexec_fn'] = preexec_fn | |
| 215 try: | |
| 216 case.process = Popen(*args, **kwargs) | |
| 217 except OSError: | |
| 218 os.close(read) | |
| 219 raise CannotStartTestee(sys.exc_info()[1]) | |
| 220 finally: | |
| 221 os.close(write) | |
| 222 try: | |
| 223 if pause is None: | |
| 224 if case.maxtime: | |
| 225 time.sleep(case.maxtime) | |
| 226 if case.process.poll() is None: | |
| 227 raise TimeLimitExceeded | |
| 228 else: | |
| 229 case.process.wait() | |
| 230 else: | |
| 231 if not case.maxtime: | |
| 232 try: | |
| 233 while case.process.poll() is None: | |
| 234 if select((sys.stdin,), (), ())[0]: | |
| 235 if sys.stdin.read(1) == '\33': | |
| 236 raise CanceledByUser | |
| 237 except select_error: | |
| 238 if sys.exc_info()[1].args[0] != EINTR: | |
| 239 raise | |
| 240 else: | |
| 241 case.process.poll() | |
| 242 else: | |
| 243 time_end = clock() + case.maxtime | |
| 244 try: | |
| 245 while case.process.poll() is None: | |
| 246 remaining = time_end - clock() | |
| 247 if remaining > 0: | |
| 248 if select((sys.stdin,), (), (), remaining)[0]: | |
| 249 if sys.stdin.read(1) == '\33': | |
| 250 raise CanceledByUser | |
| 251 else: | |
| 252 raise TimeLimitExceeded | |
| 253 except select_error: | |
| 254 if sys.exc_info()[1].args[0] != EINTR: | |
| 255 raise | |
| 256 else: | |
| 257 case.process.poll() | |
| 258 finally: | |
| 259 case.time_started = pickle.loads(os.read(read, 512)) | |
| 260 os.close(read) | |
| 261 del bury_child.case | |
| 262 def call(*args, **kwargs): | |
| 263 if 'preexec_fn' in kwargs: | |
| 264 try: | |
| 265 return call_real(*args, **kwargs) | |
| 266 except MemoryError: | |
| 267 # If there is not enough memory for the forked test.py, | |
| 268 # opt for silent dropping of the limit | |
| 269 # TODO: show a warning somewhere | |
| 270 del kwargs['preexec_fn'] | |
| 271 return call_real(*args, **kwargs) | |
| 272 else: | |
| 273 return call_real(*args, **kwargs) | |
| 274 | |
| 275 # Emulate memory limits on platforms compatible with 4.3BSD but not XSI | |
| 276 # I say 'emulate' because the OS will allow excessive memory usage | |
| 277 # anyway; Upreckon will just treat the test case as not passed. | |
| 278 # To do this, we not only require os.wait4 to be present but also | |
| 279 # assume things about the implementation of subprocess.Popen. | |
| 280 try: | |
| 281 def waitpid_emu(pid, options, _wait4=os.wait4): | |
| 282 global last_rusage | |
| 283 pid, status, last_rusage = _wait4(pid, options) | |
| 284 return pid, status | |
| 285 _waitpid = os.waitpid | |
| 286 os.waitpid = waitpid_emu | |
| 287 try: | |
| 288 defaults = Popen._internal_poll.__func__.__defaults__ | |
| 289 except AttributeError: | |
| 290 # Python 2.5 | |
| 291 defaults = Popen._internal_poll.im_func.func_defaults | |
| 292 i = defaults.index(_waitpid) | |
| 293 defaults = defaults[:i] + (waitpid_emu,) + defaults[i+1:] | |
| 294 try: | |
| 295 Popen._internal_poll.__func__.__defaults__ = defaults | |
| 296 except AttributeError: | |
| 297 pass | |
| 298 Popen._internal_poll.im_func.func_defaults = defaults | |
| 299 except (AttributeError, ValueError): | |
| 300 pass | |
| 301 | 27 |
| 302 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed', | 28 __all__ = ('TestCase', 'load_problem', 'TestCaseNotPassed', |
| 303 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer', | 29 'TimeLimitExceeded', 'CanceledByUser', 'WrongAnswer', |
| 304 'NonZeroExitCode', 'CannotStartTestee', | 30 'NonZeroExitCode', 'CannotStartTestee', |
| 305 'CannotStartValidator', 'CannotReadOutputFile', | 31 'CannotStartValidator', 'CannotReadOutputFile', |
| 306 'CannotReadInputFile', 'CannotReadAnswerFile', | 32 'CannotReadInputFile', 'CannotReadAnswerFile', |
| 307 'MemoryLimitExceeded') | 33 'MemoryLimitExceeded', 'CPUTimeLimitExceeded', |
| 34 'WallTimeLimitExceeded') | |
| 308 | 35 |
| 309 | 36 |
| 310 | 37 |
| 311 # Exceptions | 38 # Exceptions |
| 312 | 39 |
| 313 class TestCaseNotPassed(Exception): __slots__ = () | 40 class TestCaseNotPassed(Exception): __slots__ = () |
| 314 class TimeLimitExceeded(TestCaseNotPassed): __slots__ = () | 41 class TimeLimitExceeded(TestCaseNotPassed): __slots__ = () |
| 42 class CPUTimeLimitExceeded(TimeLimitExceeded): __slots__ = () | |
| 43 class WallTimeLimitExceeded(TimeLimitExceeded): __slots__ = () | |
| 315 class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = () | 44 class MemoryLimitExceeded(TestCaseNotPassed): __slots__ = () |
| 316 class CanceledByUser(TestCaseNotPassed): __slots__ = () | 45 class CanceledByUser(TestCaseNotPassed): __slots__ = () |
| 317 | 46 |
| 318 class WrongAnswer(TestCaseNotPassed): | 47 class WrongAnswer(TestCaseNotPassed): |
| 319 __slots__ = 'comment' | 48 __slots__ = 'comment' |
| 382 | 111 |
| 383 # Test case types | 112 # Test case types |
| 384 | 113 |
| 385 class TestCase(object): | 114 class TestCase(object): |
| 386 __slots__ = ('problem', 'id', 'isdummy', 'infile', 'outfile', 'points', | 115 __slots__ = ('problem', 'id', 'isdummy', 'infile', 'outfile', 'points', |
| 387 'process', 'time_started', 'time_stopped', 'time_limit_string', | 116 'process', 'time_started', 'time_stopped', |
| 388 'realinname', 'realoutname', 'maxtime', 'maxmemory', | 117 'realinname', 'realoutname', 'maxcputime', 'maxwalltime', |
| 389 'has_called_back', 'files_to_delete') | 118 'maxmemory', 'has_called_back', 'files_to_delete', |
| 119 'cpu_time_limit_string', 'wall_time_limit_string', | |
| 120 'time_limit_string') | |
| 390 | 121 |
| 391 if ABCMeta: | 122 if ABCMeta: |
| 392 __metaclass__ = ABCMeta | 123 __metaclass__ = ABCMeta |
| 393 | 124 |
| 394 def __init__(case, prob, id, isdummy, points): | 125 def __init__(case, prob, id, isdummy, points): |
| 395 case.problem = prob | 126 case.problem = prob |
| 396 case.id = id | 127 case.id = id |
| 397 case.isdummy = isdummy | 128 case.isdummy = isdummy |
| 398 case.points = points | 129 case.points = points |
| 399 case.maxtime = case.problem.config.maxtime | 130 case.maxcputime = case.problem.config.maxcputime |
| 131 case.maxwalltime = case.problem.config.maxwalltime | |
| 400 case.maxmemory = case.problem.config.maxmemory | 132 case.maxmemory = case.problem.config.maxmemory |
| 401 if case.maxtime: | 133 if case.maxcputime: |
| 402 case.time_limit_string = '/%.3f' % case.maxtime | 134 case.cpu_time_limit_string = '/%.3f' % case.maxcputime |
| 403 else: | 135 else: |
| 404 case.time_limit_string = '' | 136 case.cpu_time_limit_string = '' |
| 137 if case.maxwalltime: | |
| 138 case.wall_time_limit_string = '/%.3f' % case.maxwalltime | |
| 139 else: | |
| 140 case.wall_time_limit_string = '' | |
| 405 if not isdummy: | 141 if not isdummy: |
| 406 case.realinname = case.problem.config.testcaseinname | 142 case.realinname = case.problem.config.testcaseinname |
| 407 case.realoutname = case.problem.config.testcaseoutname | 143 case.realoutname = case.problem.config.testcaseoutname |
| 408 else: | 144 else: |
| 409 case.realinname = case.problem.config.dummyinname | 145 case.realinname = case.problem.config.dummyinname |
| 413 def test(case): raise NotImplementedError | 149 def test(case): raise NotImplementedError |
| 414 | 150 |
| 415 def __call__(case, callback): | 151 def __call__(case, callback): |
| 416 case.has_called_back = False | 152 case.has_called_back = False |
| 417 case.files_to_delete = [] | 153 case.files_to_delete = [] |
| 154 case.time_limit_string = case.wall_time_limit_string | |
| 418 try: | 155 try: |
| 419 return case.test(callback) | 156 return case.test(callback) |
| 420 finally: | 157 finally: |
| 421 now = clock() | 158 now = clock() |
| 422 if not getattr(case, 'time_started', None): | 159 if getattr(case, 'time_started', None) is None: |
| 423 case.time_started = case.time_stopped = now | 160 case.time_started = case.time_stopped = now |
| 424 elif not getattr(case, 'time_stopped', None): | 161 elif getattr(case, 'time_stopped', None) is None: |
| 425 case.time_stopped = now | 162 case.time_stopped = now |
| 426 if not case.has_called_back: | 163 if not case.has_called_back: |
| 427 callback() | 164 callback() |
| 428 case.cleanup() | 165 case.cleanup() |
| 429 | 166 |
| 430 def cleanup(case): | 167 def cleanup(case): |
| 431 #if getattr(case, 'infile', None): | 168 #if getattr(case, 'infile', None): |
| 432 # case.infile.close() | 169 # case.infile.close() |
| 433 #if getattr(case, 'outfile', None): | 170 #if getattr(case, 'outfile', None): |
| 434 # case.outfile.close() | 171 # case.outfile.close() |
| 435 if getattr(case, 'process', None): | 172 if getattr(case, 'process', None) and case.process.returncode is None: |
| 436 # Try killing after three unsuccessful TERM attempts in a row | 173 # Try KILLing after three unsuccessful TERM attempts in a row |
| 437 # (except on Windows, where TERMing is killing) | |
| 438 for i in range(3): | 174 for i in range(3): |
| 439 try: | 175 try: |
| 440 try: | 176 terminate(case.process) |
| 441 case.process.terminate() | |
| 442 except AttributeError: | |
| 443 # Python 2.5 | |
| 444 if TerminateProcess and hasattr(proc, '_handle'): | |
| 445 # Windows API | |
| 446 TerminateProcess(proc._handle, 1) | |
| 447 else: | |
| 448 # POSIX | |
| 449 os.kill(proc.pid, SIGTERM) | |
| 450 except Exception: | 177 except Exception: |
| 451 time.sleep(0) | 178 time.sleep(0) |
| 452 case.process.poll() | 179 case.process.poll() |
| 453 else: | 180 else: |
| 454 case.process.wait() | 181 case.process.wait() |
| 456 else: | 183 else: |
| 457 # If killing the process is unsuccessful three times in a row, | 184 # If killing the process is unsuccessful three times in a row, |
| 458 # just silently stop trying | 185 # just silently stop trying |
| 459 for i in range(3): | 186 for i in range(3): |
| 460 try: | 187 try: |
| 461 try: | 188 kill(case.process) |
| 462 case.process.kill() | |
| 463 except AttributeError: | |
| 464 # Python 2.5 | |
| 465 if TerminateProcess and hasattr(proc, '_handle'): | |
| 466 # Windows API | |
| 467 TerminateProcess(proc._handle, 1) | |
| 468 else: | |
| 469 # POSIX | |
| 470 os.kill(proc.pid, SIGKILL) | |
| 471 except Exception: | 189 except Exception: |
| 472 time.sleep(0) | 190 time.sleep(0) |
| 473 case.process.poll() | 191 case.process.poll() |
| 474 else: | 192 else: |
| 475 case.process.wait() | 193 case.process.wait() |
| 545 | 263 |
| 546 class BatchTestCase(ValidatedTestCase): | 264 class BatchTestCase(ValidatedTestCase): |
| 547 __slots__ = () | 265 __slots__ = () |
| 548 | 266 |
| 549 def test(case, callback): | 267 def test(case, callback): |
| 550 init_canceled() | |
| 551 if sys.platform == 'win32' or not case.maxmemory: | |
| 552 preexec_fn = None | |
| 553 else: | |
| 554 def preexec_fn(): | |
| 555 try: | |
| 556 import resource | |
| 557 maxmemory = int(case.maxmemory * 1048576) | |
| 558 resource.setrlimit(resource.RLIMIT_AS, (maxmemory, maxmemory)) | |
| 559 # I would also set a CPU time limit but I do not want the time | |
| 560 # that passes between the calls to fork and exec to be counted in | |
| 561 except MemoryError: | |
| 562 # We do not have enough memory for ourselves; | |
| 563 # let the parent know about this | |
| 564 raise | |
| 565 except Exception: | |
| 566 # Well, at least we tried | |
| 567 pass | |
| 568 case.open_infile() | 268 case.open_infile() |
| 569 case.time_started = None | 269 case.time_started = None |
| 570 global last_rusage | |
| 571 last_rusage = None | |
| 572 if case.problem.config.stdio: | 270 if case.problem.config.stdio: |
| 573 if options.erase and not case.validator or not case.problem.config.inname: | 271 if options.erase and not case.validator or not case.problem.config.inname: |
| 574 # TODO: re-use the same file name if possible | 272 # TODO: re-use the same file name if possible |
| 575 # FIXME: 2.5 lacks the delete parameter | 273 # FIXME: 2.5 lacks the delete parameter |
| 576 with tempfile.NamedTemporaryFile(delete=False) as f: | 274 with tempfile.NamedTemporaryFile(delete=False) as f: |
| 580 inputdatafname = case.problem.config.inname | 278 inputdatafname = case.problem.config.inname |
| 581 contextmgr = Copying(case.infile, inputdatafname) | 279 contextmgr = Copying(case.infile, inputdatafname) |
| 582 with contextmgr: | 280 with contextmgr: |
| 583 with open(inputdatafname) as infile: | 281 with open(inputdatafname) as infile: |
| 584 with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile: | 282 with tempfile.TemporaryFile('w+') if options.erase and not case.validator else open(case.problem.config.outname, 'w+') as outfile: |
| 585 if call is not None: | 283 call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) |
| 586 call(case.problem.config.path, case=case, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) | |
| 587 else: | |
| 588 try: | |
| 589 try: | |
| 590 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1, preexec_fn=preexec_fn) | |
| 591 except MemoryError: | |
| 592 # If there is not enough memory for the forked test.py, | |
| 593 # opt for silent dropping of the limit | |
| 594 # TODO: show a warning somewhere | |
| 595 case.process = Popen(case.problem.config.path, stdin=infile, stdout=outfile, stderr=devnull, universal_newlines=True, bufsize=-1) | |
| 596 except OSError: | |
| 597 raise CannotStartTestee(sys.exc_info()[1]) | |
| 598 case.time_started = clock() | |
| 599 time_next_check = case.time_started + .15 | |
| 600 if not case.maxtime: | |
| 601 while True: | |
| 602 exitcode, now = case.process.poll(), clock() | |
| 603 if exitcode is not None: | |
| 604 case.time_stopped = now | |
| 605 break | |
| 606 # For some reason (probably Microsoft's fault), | |
| 607 # msvcrt.kbhit() is slow as hell | |
| 608 else: | |
| 609 if now >= time_next_check: | |
| 610 if canceled(): | |
| 611 raise CanceledByUser | |
| 612 else: | |
| 613 time_next_check = now + .15 | |
| 614 time.sleep(.001) | |
| 615 else: | |
| 616 time_end = case.time_started + case.maxtime | |
| 617 while True: | |
| 618 exitcode, now = case.process.poll(), clock() | |
| 619 if exitcode is not None: | |
| 620 case.time_stopped = now | |
| 621 break | |
| 622 elif now >= time_end: | |
| 623 raise TimeLimitExceeded | |
| 624 else: | |
| 625 if now >= time_next_check: | |
| 626 if canceled(): | |
| 627 raise CanceledByUser | |
| 628 else: | |
| 629 time_next_check = now + .15 | |
| 630 time.sleep(.001) | |
| 631 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: | 284 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: |
| 632 raise NonZeroExitCode(case.process.returncode) | 285 raise NonZeroExitCode(case.process.returncode) |
| 633 if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576): | |
| 634 raise MemoryLimitExceeded | |
| 635 callback() | 286 callback() |
| 636 case.has_called_back = True | 287 case.has_called_back = True |
| 637 outfile.seek(0) | 288 outfile.seek(0) |
| 638 return case.validate(outfile) | 289 return case.validate(outfile) |
| 639 else: | 290 else: |
| 640 case.infile.copy(case.problem.config.inname) | 291 case.infile.copy(case.problem.config.inname) |
| 641 if call is not None: | 292 call(case.problem.config.path, case=case, stdin=devnull, stdout=devnull, stderr=STDOUT) |
| 642 call(case.problem.config.path, case=case, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) | |
| 643 else: | |
| 644 try: | |
| 645 try: | |
| 646 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT, preexec_fn=preexec_fn) | |
| 647 except MemoryError: | |
| 648 # If there is not enough memory for the forked test.py, | |
| 649 # opt for silent dropping of the limit | |
| 650 # TODO: show a warning somewhere | |
| 651 case.process = Popen(case.problem.config.path, stdin=devnull, stdout=devnull, stderr=STDOUT) | |
| 652 except OSError: | |
| 653 raise CannotStartTestee(sys.exc_info()[1]) | |
| 654 case.time_started = clock() | |
| 655 time_next_check = case.time_started + .15 | |
| 656 if not case.maxtime: | |
| 657 while True: | |
| 658 exitcode, now = case.process.poll(), clock() | |
| 659 if exitcode is not None: | |
| 660 case.time_stopped = now | |
| 661 break | |
| 662 else: | |
| 663 if now >= time_next_check: | |
| 664 if canceled(): | |
| 665 raise CanceledByUser | |
| 666 else: | |
| 667 time_next_check = now + .15 | |
| 668 time.sleep(.001) | |
| 669 else: | |
| 670 time_end = case.time_started + case.maxtime | |
| 671 while True: | |
| 672 exitcode, now = case.process.poll(), clock() | |
| 673 if exitcode is not None: | |
| 674 case.time_stopped = now | |
| 675 break | |
| 676 elif now >= time_end: | |
| 677 raise TimeLimitExceeded | |
| 678 else: | |
| 679 if now >= time_next_check: | |
| 680 if canceled(): | |
| 681 raise CanceledByUser | |
| 682 else: | |
| 683 time_next_check = now + .15 | |
| 684 time.sleep(.001) | |
| 685 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: | 293 if config.globalconf.force_zero_exitcode and case.process.returncode or case.process.returncode < 0: |
| 686 raise NonZeroExitCode(case.process.returncode) | 294 raise NonZeroExitCode(case.process.returncode) |
| 687 if case.maxmemory and last_rusage and last_rusage.ru_maxrss > case.maxmemory * (1024 if sys.platform != 'darwin' else 1048576): | |
| 688 raise MemoryLimitExceeded | |
| 689 callback() | 295 callback() |
| 690 case.has_called_back = True | 296 case.has_called_back = True |
| 691 with open(case.problem.config.outname, 'rU') as output: | 297 with open(case.problem.config.outname, 'rU') as output: |
| 692 return case.validate(output) | 298 return case.validate(output) |
| 693 | 299 |
