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 |